@cliftonc/cfld 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { parseArgs } from "util";
|
|
5
5
|
import { readFileSync as readFileSync7, realpathSync } from "fs";
|
|
6
|
-
import { dirname, join as
|
|
6
|
+
import { dirname as dirname2, join as join9 } from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
import pc7 from "picocolors";
|
|
9
9
|
|
|
10
10
|
// src/commands/run.ts
|
|
11
|
-
import { join as
|
|
11
|
+
import { join as join7 } from "path";
|
|
12
12
|
|
|
13
13
|
// src/core/state-machine.ts
|
|
14
|
-
import { existsSync as existsSync2 } from "fs";
|
|
14
|
+
import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, renameSync, unlinkSync } from "fs";
|
|
15
|
+
import { dirname, join as join2 } from "path";
|
|
15
16
|
|
|
16
17
|
// src/util/paths.ts
|
|
17
18
|
import { homedir } from "os";
|
|
@@ -229,7 +230,8 @@ function cfargoTunnelUuid(target) {
|
|
|
229
230
|
import { writeFileSync } from "fs";
|
|
230
231
|
function renderIngressConfig(cfg) {
|
|
231
232
|
const rules = cfg.routes.flatMap((r) => {
|
|
232
|
-
const
|
|
233
|
+
const host = r.hostname.startsWith("*") ? JSON.stringify(r.hostname) : r.hostname;
|
|
234
|
+
const lines = [` - hostname: ${host}`];
|
|
233
235
|
if (r.path) lines.push(` path: ${r.path}`);
|
|
234
236
|
lines.push(` service: http://localhost:${r.port}`);
|
|
235
237
|
return lines;
|
|
@@ -300,15 +302,17 @@ async function reconcile(input, events = {}) {
|
|
|
300
302
|
let created = false;
|
|
301
303
|
if (existing) {
|
|
302
304
|
uuid = existing.id;
|
|
303
|
-
if (!existsSync2(credentialsPath(uuid))) {
|
|
304
|
-
step2("Refetching tunnel credentials");
|
|
305
|
-
await fetchToken(tunnelName, credentialsPath(uuid), certPath);
|
|
306
|
-
}
|
|
307
305
|
} else {
|
|
308
306
|
step2(`Creating tunnel ${tunnelName}`);
|
|
309
307
|
uuid = await createTunnel(tunnelName, certPath);
|
|
310
308
|
created = true;
|
|
311
309
|
}
|
|
310
|
+
if (!existsSync2(credentialsPath(uuid))) {
|
|
311
|
+
if (!adoptCredentialsBesideCert(uuid, certPath)) {
|
|
312
|
+
step2("Refetching tunnel credentials");
|
|
313
|
+
await fetchToken(tunnelName, credentialsPath(uuid), certPath);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
312
316
|
const prior = getEntry(tunnelName);
|
|
313
317
|
const owned = /* @__PURE__ */ new Set([
|
|
314
318
|
prior?.hostname,
|
|
@@ -358,9 +362,22 @@ async function reconcile(input, events = {}) {
|
|
|
358
362
|
function nowIso() {
|
|
359
363
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
360
364
|
}
|
|
365
|
+
function adoptCredentialsBesideCert(uuid, certPath) {
|
|
366
|
+
const beside = join2(dirname(certPath), `${uuid}.json`);
|
|
367
|
+
const target = credentialsPath(uuid);
|
|
368
|
+
if (beside === target || !existsSync2(beside)) return false;
|
|
369
|
+
mkdirSync2(dirname(target), { recursive: true });
|
|
370
|
+
try {
|
|
371
|
+
renameSync(beside, target);
|
|
372
|
+
} catch {
|
|
373
|
+
copyFileSync(beside, target);
|
|
374
|
+
unlinkSync(beside);
|
|
375
|
+
}
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
361
378
|
|
|
362
379
|
// src/core/cert-store.ts
|
|
363
|
-
import { copyFileSync, existsSync as existsSync4, mkdirSync as
|
|
380
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync } from "fs";
|
|
364
381
|
|
|
365
382
|
// src/core/cert.ts
|
|
366
383
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
@@ -392,10 +409,28 @@ function certHasZone(certPath) {
|
|
|
392
409
|
const token = readCertToken(certPath);
|
|
393
410
|
return Boolean(token?.zoneID);
|
|
394
411
|
}
|
|
412
|
+
async function certZoneName(certPath) {
|
|
413
|
+
const token = readCertToken(certPath);
|
|
414
|
+
if (!token?.zoneID || !token.apiToken) return void 0;
|
|
415
|
+
try {
|
|
416
|
+
const res = await fetch(
|
|
417
|
+
`https://api.cloudflare.com/client/v4/zones/${token.zoneID}`,
|
|
418
|
+
{
|
|
419
|
+
headers: { Authorization: `Bearer ${token.apiToken}` },
|
|
420
|
+
signal: AbortSignal.timeout(5e3)
|
|
421
|
+
}
|
|
422
|
+
);
|
|
423
|
+
if (!res.ok) return void 0;
|
|
424
|
+
const body = await res.json();
|
|
425
|
+
return body.result?.name?.toLowerCase();
|
|
426
|
+
} catch {
|
|
427
|
+
return void 0;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
395
430
|
|
|
396
431
|
// src/core/cert-store.ts
|
|
397
432
|
function ensureStoreDir() {
|
|
398
|
-
|
|
433
|
+
mkdirSync3(certStoreDir(), { recursive: true });
|
|
399
434
|
}
|
|
400
435
|
function storedCertForZone(zone) {
|
|
401
436
|
const p = certPathForZone(zone);
|
|
@@ -408,25 +443,36 @@ function listStoredZones() {
|
|
|
408
443
|
return [];
|
|
409
444
|
}
|
|
410
445
|
}
|
|
446
|
+
function storedZoneClaiming(certPath, except) {
|
|
447
|
+
const zoneID = readCertToken(certPath)?.zoneID;
|
|
448
|
+
if (!zoneID) return void 0;
|
|
449
|
+
return listStoredZones().find(
|
|
450
|
+
(z) => z !== except && readCertToken(certPathForZone(z))?.zoneID === zoneID
|
|
451
|
+
);
|
|
452
|
+
}
|
|
411
453
|
function resolveCert(zone) {
|
|
412
|
-
|
|
454
|
+
const stored = storedCertForZone(zone);
|
|
455
|
+
if (stored) return stored;
|
|
456
|
+
const def = defaultCertPath();
|
|
457
|
+
if (!certExists(def) || storedZoneClaiming(def, zone)) return void 0;
|
|
458
|
+
return def;
|
|
413
459
|
}
|
|
414
460
|
function importCert(zone, fromPath = defaultCertPath()) {
|
|
415
461
|
ensureStoreDir();
|
|
416
462
|
const dest = certPathForZone(zone);
|
|
417
|
-
|
|
463
|
+
copyFileSync2(fromPath, dest);
|
|
418
464
|
return dest;
|
|
419
465
|
}
|
|
420
466
|
|
|
421
467
|
// src/core/hostname.ts
|
|
422
468
|
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
423
|
-
import { basename, join as
|
|
469
|
+
import { basename, join as join3 } from "path";
|
|
424
470
|
function slugify(input) {
|
|
425
471
|
return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
426
472
|
}
|
|
427
473
|
function deriveSlug(cwd, nameFlag) {
|
|
428
474
|
if (nameFlag) return slugify(nameFlag);
|
|
429
|
-
const pkgPath =
|
|
475
|
+
const pkgPath = join3(cwd, "package.json");
|
|
430
476
|
if (existsSync5(pkgPath)) {
|
|
431
477
|
try {
|
|
432
478
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
@@ -512,12 +558,12 @@ function findFreePort() {
|
|
|
512
558
|
|
|
513
559
|
// src/core/project-config.ts
|
|
514
560
|
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
515
|
-
import { join as
|
|
561
|
+
import { join as join4 } from "path";
|
|
516
562
|
function dotConfigPath(cwd) {
|
|
517
|
-
return
|
|
563
|
+
return join4(cwd, PROJECT_CONFIG_FILE);
|
|
518
564
|
}
|
|
519
565
|
function readAuthored(cwd) {
|
|
520
|
-
const pkgPath =
|
|
566
|
+
const pkgPath = join4(cwd, "package.json");
|
|
521
567
|
if (!existsSync6(pkgPath)) return void 0;
|
|
522
568
|
try {
|
|
523
569
|
const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
@@ -936,7 +982,8 @@ function colorLevel(level) {
|
|
|
936
982
|
|
|
937
983
|
// src/commands/login.ts
|
|
938
984
|
import { spawn as spawn4 } from "child_process";
|
|
939
|
-
import { existsSync as existsSync8 } from "fs";
|
|
985
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync4, renameSync as renameSync2, rmSync } from "fs";
|
|
986
|
+
import { join as join5 } from "path";
|
|
940
987
|
|
|
941
988
|
// src/ui/prompts.ts
|
|
942
989
|
async function clack() {
|
|
@@ -1031,23 +1078,73 @@ function normalizeZone(input) {
|
|
|
1031
1078
|
return input.trim().toLowerCase().replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
1032
1079
|
}
|
|
1033
1080
|
async function loginCommand(flags) {
|
|
1034
|
-
const interactive = isInteractive();
|
|
1035
1081
|
const haveCert = certExists(defaultCertPath());
|
|
1082
|
+
let certPath = defaultCertPath();
|
|
1036
1083
|
if (flags.reauth || !haveCert) {
|
|
1037
|
-
await runLogin();
|
|
1084
|
+
certPath = await runLogin();
|
|
1038
1085
|
} else {
|
|
1039
1086
|
info("A Cloudflare certificate already exists. Use --reauth to authorize another domain.");
|
|
1040
1087
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1088
|
+
try {
|
|
1089
|
+
if (!certHasZone(certPath)) {
|
|
1090
|
+
printZeroZoneGuidance();
|
|
1091
|
+
throw new CfldError(
|
|
1092
|
+
"Logged in, but your account has no domain to authorize.",
|
|
1093
|
+
"Add a domain (see above), then run `cfld login --reauth`."
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
const zone = await saveCert(flags.zone ? normalizeZone(flags.zone) : void 0, certPath);
|
|
1097
|
+
done(`Saved cert for ${zone}. You're ready \u2014 run \`cfld\`.`);
|
|
1098
|
+
} finally {
|
|
1099
|
+
discardLoginCert(certPath);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
async function runLogin() {
|
|
1103
|
+
const bin = await resolveBinary();
|
|
1104
|
+
const def = defaultCertPath();
|
|
1105
|
+
const backup = existsSync8(def) ? `${def}.cfld-backup` : void 0;
|
|
1106
|
+
if (backup) renameSync2(def, backup);
|
|
1107
|
+
try {
|
|
1108
|
+
step("Opening your browser to authorize with Cloudflare (no API token needed)\u2026");
|
|
1109
|
+
note(" Pick the domain you want to use. Waiting for you to finish\u2026");
|
|
1110
|
+
await new Promise((resolve, reject) => {
|
|
1111
|
+
const child = spawn4(bin, ["tunnel", "login"], { stdio: "inherit" });
|
|
1112
|
+
child.on("error", reject);
|
|
1113
|
+
child.on("exit", (code) => {
|
|
1114
|
+
if (code === 0 || existsSync8(def)) resolve();
|
|
1115
|
+
else reject(new CfldError("Cloudflare login did not complete.", "Re-run `cfld login`."));
|
|
1116
|
+
});
|
|
1117
|
+
});
|
|
1118
|
+
if (!existsSync8(def)) {
|
|
1119
|
+
throw new CfldError(
|
|
1120
|
+
"Login finished but no certificate was written.",
|
|
1121
|
+
"Re-run `cfld login` and complete the browser step."
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
if (!backup) return def;
|
|
1125
|
+
mkdirSync4(cfldDir(), { recursive: true });
|
|
1126
|
+
const fresh = join5(cfldDir(), "login-cert.tmp");
|
|
1127
|
+
copyFileSync3(def, fresh);
|
|
1128
|
+
rmSync(def);
|
|
1129
|
+
return fresh;
|
|
1130
|
+
} finally {
|
|
1131
|
+
if (backup) renameSync2(backup, def);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
function discardLoginCert(certPath) {
|
|
1135
|
+
if (certPath !== defaultCertPath()) rmSync(certPath, { force: true });
|
|
1136
|
+
}
|
|
1137
|
+
async function saveCert(expected, certPath) {
|
|
1138
|
+
const actual = await certZoneName(certPath);
|
|
1139
|
+
if (expected && actual && actual !== expected) {
|
|
1043
1140
|
throw new CfldError(
|
|
1044
|
-
|
|
1045
|
-
|
|
1141
|
+
`You authorized ${actual} in the browser, not ${expected}.`,
|
|
1142
|
+
`Run \`cfld login --reauth --zone ${expected}\` and pick ${expected} this time.`
|
|
1046
1143
|
);
|
|
1047
1144
|
}
|
|
1048
|
-
let zone =
|
|
1145
|
+
let zone = expected ?? actual;
|
|
1049
1146
|
if (!zone) {
|
|
1050
|
-
if (!
|
|
1147
|
+
if (!isInteractive()) {
|
|
1051
1148
|
throw new CfldError(
|
|
1052
1149
|
"Pass --zone to record which domain this cert is for.",
|
|
1053
1150
|
"e.g. `cfld login --zone example.com`"
|
|
@@ -1055,48 +1152,57 @@ async function loginCommand(flags) {
|
|
|
1055
1152
|
}
|
|
1056
1153
|
zone = normalizeZone(await askText("Which domain did you authorize?", "example.com"));
|
|
1057
1154
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
}
|
|
1061
|
-
async function runLogin() {
|
|
1062
|
-
const bin = await resolveBinary();
|
|
1063
|
-
step("Opening your browser to authorize with Cloudflare (no API token needed)\u2026");
|
|
1064
|
-
note(" Pick the domain you want to use. Waiting for you to finish\u2026");
|
|
1065
|
-
await new Promise((resolve, reject) => {
|
|
1066
|
-
const child = spawn4(bin, ["tunnel", "login"], { stdio: "inherit" });
|
|
1067
|
-
child.on("error", reject);
|
|
1068
|
-
child.on("exit", (code) => {
|
|
1069
|
-
if (code === 0 || existsSync8(defaultCertPath())) resolve();
|
|
1070
|
-
else reject(new CfldError("Cloudflare login did not complete.", "Re-run `cfld login`."));
|
|
1071
|
-
});
|
|
1072
|
-
});
|
|
1073
|
-
if (!existsSync8(defaultCertPath())) {
|
|
1155
|
+
const clash = storedZoneClaiming(certPath, zone);
|
|
1156
|
+
if (clash && !actual) {
|
|
1074
1157
|
throw new CfldError(
|
|
1075
|
-
|
|
1076
|
-
|
|
1158
|
+
`That cert is for the same domain as your saved ${clash} cert, not ${zone}.`,
|
|
1159
|
+
`Run \`cfld login --reauth --zone ${zone}\` and pick ${zone} in the browser.`
|
|
1077
1160
|
);
|
|
1078
1161
|
}
|
|
1162
|
+
importCert(zone, certPath);
|
|
1163
|
+
return zone;
|
|
1079
1164
|
}
|
|
1080
1165
|
async function ensureCert(zone, opts = { interactive: true }) {
|
|
1081
|
-
const
|
|
1082
|
-
if (
|
|
1166
|
+
const usable = await usableCert(zone);
|
|
1167
|
+
if (usable) return usable;
|
|
1083
1168
|
if (!opts.interactive) {
|
|
1084
1169
|
throw new CfldError(
|
|
1085
1170
|
`No Cloudflare certificate found for ${zone}.`,
|
|
1086
|
-
|
|
1171
|
+
`Run \`cfld login --reauth --zone ${zone}\` first (needs a browser), or set TUNNEL_ORIGIN_CERT.`
|
|
1087
1172
|
);
|
|
1088
1173
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1174
|
+
info(`No certificate for ${zone} yet \u2014 authorize it in the browser.`);
|
|
1175
|
+
const fresh = await runLogin();
|
|
1176
|
+
try {
|
|
1177
|
+
if (!certHasZone(fresh)) {
|
|
1178
|
+
printZeroZoneGuidance();
|
|
1179
|
+
throw new CfldError(
|
|
1180
|
+
"Your Cloudflare account has no domain yet, so a persistent URL isn't possible.",
|
|
1181
|
+
"Add a domain (see above), then re-run. Or use `cfld --quick` for a throwaway URL now."
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
await saveCert(zone, fresh);
|
|
1185
|
+
} finally {
|
|
1186
|
+
discardLoginCert(fresh);
|
|
1096
1187
|
}
|
|
1097
|
-
const stored = importCert(zone);
|
|
1098
1188
|
info(`Saved cert for ${zone}.`);
|
|
1099
|
-
return
|
|
1189
|
+
return certPathForZone(zone);
|
|
1190
|
+
}
|
|
1191
|
+
async function usableCert(zone) {
|
|
1192
|
+
const stored = storedCertForZone(zone);
|
|
1193
|
+
if (stored) {
|
|
1194
|
+
if (!storedZoneClaiming(stored, zone)) return stored;
|
|
1195
|
+
const actual2 = await certZoneName(stored);
|
|
1196
|
+
if (!actual2 || actual2 === zone) return stored;
|
|
1197
|
+
warn(`The saved cert for ${zone} is actually for ${actual2} \u2014 discarding it.`);
|
|
1198
|
+
rmSync(stored);
|
|
1199
|
+
}
|
|
1200
|
+
const def = defaultCertPath();
|
|
1201
|
+
if (!certHasZone(def)) return void 0;
|
|
1202
|
+
const actual = await certZoneName(def);
|
|
1203
|
+
if (actual === zone) return importCert(zone, def);
|
|
1204
|
+
if (!actual && !storedZoneClaiming(def, zone)) return def;
|
|
1205
|
+
return void 0;
|
|
1100
1206
|
}
|
|
1101
1207
|
function printZeroZoneGuidance() {
|
|
1102
1208
|
warn("cfld can't create a permanent custom URL yet \u2014 your account has no domains.");
|
|
@@ -1123,11 +1229,11 @@ function printZeroZoneGuidance() {
|
|
|
1123
1229
|
|
|
1124
1230
|
// src/util/gitignore.ts
|
|
1125
1231
|
import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1126
|
-
import { join as
|
|
1232
|
+
import { join as join6 } from "path";
|
|
1127
1233
|
function ensureGitignored(cwd, pattern) {
|
|
1128
|
-
const gitignorePath =
|
|
1234
|
+
const gitignorePath = join6(cwd, ".gitignore");
|
|
1129
1235
|
const hasGitignore = existsSync9(gitignorePath);
|
|
1130
|
-
const inRepo = hasGitignore || existsSync9(
|
|
1236
|
+
const inRepo = hasGitignore || existsSync9(join6(cwd, ".git"));
|
|
1131
1237
|
if (!inRepo) return false;
|
|
1132
1238
|
const existing = hasGitignore ? readFileSync6(gitignorePath, "utf8") : "";
|
|
1133
1239
|
const present = existing.split("\n").map((l) => l.trim()).includes(pattern);
|
|
@@ -1309,7 +1415,7 @@ ${opts.reason}`);
|
|
|
1309
1415
|
);
|
|
1310
1416
|
process.once("SIGHUP", () => shutdown({ reason: "Terminal closed \u2014 stopping." }));
|
|
1311
1417
|
if (interactive) {
|
|
1312
|
-
const { startDashboard } = await import("./dashboard-
|
|
1418
|
+
const { startDashboard } = await import("./dashboard-Z4AN7H5V.js");
|
|
1313
1419
|
dashboard = startDashboard(
|
|
1314
1420
|
{
|
|
1315
1421
|
url,
|
|
@@ -1376,7 +1482,7 @@ ${opts.reason}`);
|
|
|
1376
1482
|
if (!up) warn(`Nothing is listening on :${ctx.port} yet \u2014 going live anyway.`);
|
|
1377
1483
|
}
|
|
1378
1484
|
if (!ctx.noEnv) {
|
|
1379
|
-
const { changed } = upsertEnv(
|
|
1485
|
+
const { changed } = upsertEnv(join7(ctx.cwd, ".env"), ctx.envKey, url);
|
|
1380
1486
|
if (changed && !dashboard) info(`Wrote ${ctx.envKey}=${url} to .env`);
|
|
1381
1487
|
}
|
|
1382
1488
|
if (dashboard) {
|
|
@@ -1553,7 +1659,7 @@ ${opts.reason ?? "Stopped ephemeral tunnel."}`);
|
|
|
1553
1659
|
});
|
|
1554
1660
|
}
|
|
1555
1661
|
if (interactive) {
|
|
1556
|
-
const { startDashboard } = await import("./dashboard-
|
|
1662
|
+
const { startDashboard } = await import("./dashboard-Z4AN7H5V.js");
|
|
1557
1663
|
dashboard = startDashboard({
|
|
1558
1664
|
url,
|
|
1559
1665
|
target,
|
|
@@ -1753,20 +1859,26 @@ async function setupCommand(flags) {
|
|
|
1753
1859
|
);
|
|
1754
1860
|
return;
|
|
1755
1861
|
}
|
|
1756
|
-
await runLogin();
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
if (
|
|
1760
|
-
|
|
1761
|
-
|
|
1862
|
+
const fresh = await runLogin();
|
|
1863
|
+
let zone;
|
|
1864
|
+
try {
|
|
1865
|
+
if (!certHasZone(fresh)) {
|
|
1866
|
+
printZeroZoneGuidance();
|
|
1867
|
+
if (await askConfirm("Start a temporary quick tunnel now instead?")) {
|
|
1868
|
+
await quickCommand(flags);
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
throw new CfldError(
|
|
1872
|
+
"Your Cloudflare account has no domain yet, so a persistent URL isn't possible.",
|
|
1873
|
+
"Add a domain (see above), then run `cfld setup` again."
|
|
1874
|
+
);
|
|
1762
1875
|
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
);
|
|
1876
|
+
const detected = await certZoneName(fresh);
|
|
1877
|
+
zone = await saveCert(detected ?? await askAuthorizedZone(knownZones), fresh);
|
|
1878
|
+
} finally {
|
|
1879
|
+
discardLoginCert(fresh);
|
|
1767
1880
|
}
|
|
1768
|
-
const
|
|
1769
|
-
const certPath = importCert(zone);
|
|
1881
|
+
const certPath = certPathForZone(zone);
|
|
1770
1882
|
info(`Saved cert for ${zone}.`);
|
|
1771
1883
|
await configureProject(flags, { zone, certPath, cwd });
|
|
1772
1884
|
await offerGoLive(flags, zone);
|
|
@@ -1997,9 +2109,9 @@ function resolveCurrentEntry2() {
|
|
|
1997
2109
|
import pc5 from "picocolors";
|
|
1998
2110
|
|
|
1999
2111
|
// src/core/service.ts
|
|
2000
|
-
import { existsSync as existsSync10, mkdirSync as
|
|
2112
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
2001
2113
|
import { homedir as homedir2, platform } from "os";
|
|
2002
|
-
import { join as
|
|
2114
|
+
import { join as join8 } from "path";
|
|
2003
2115
|
function serviceSlug(name) {
|
|
2004
2116
|
return name.replace(/^cfld-/, "");
|
|
2005
2117
|
}
|
|
@@ -2007,16 +2119,16 @@ function launchAgentLabel(name) {
|
|
|
2007
2119
|
return `com.cfld.${serviceSlug(name)}`;
|
|
2008
2120
|
}
|
|
2009
2121
|
function launchAgentPath(name) {
|
|
2010
|
-
return
|
|
2122
|
+
return join8(homedir2(), "Library", "LaunchAgents", `${launchAgentLabel(name)}.plist`);
|
|
2011
2123
|
}
|
|
2012
2124
|
function systemdUnitName(name) {
|
|
2013
2125
|
return `cfld-${serviceSlug(name)}.service`;
|
|
2014
2126
|
}
|
|
2015
2127
|
function systemdUnitPath(name) {
|
|
2016
|
-
return
|
|
2128
|
+
return join8(homedir2(), ".config", "systemd", "user", systemdUnitName(name));
|
|
2017
2129
|
}
|
|
2018
2130
|
function logDir() {
|
|
2019
|
-
return
|
|
2131
|
+
return join8(cfldDir(), "logs");
|
|
2020
2132
|
}
|
|
2021
2133
|
function renderLaunchAgent(spec) {
|
|
2022
2134
|
const args = [
|
|
@@ -2030,8 +2142,8 @@ function renderLaunchAgent(spec) {
|
|
|
2030
2142
|
spec.name
|
|
2031
2143
|
];
|
|
2032
2144
|
const argsXml = args.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
|
|
2033
|
-
const out =
|
|
2034
|
-
const err =
|
|
2145
|
+
const out = join8(logDir(), `${serviceSlug(spec.name)}.out.log`);
|
|
2146
|
+
const err = join8(logDir(), `${serviceSlug(spec.name)}.err.log`);
|
|
2035
2147
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2036
2148
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2037
2149
|
<plist version="1.0">
|
|
@@ -2096,10 +2208,10 @@ function assertSupported() {
|
|
|
2096
2208
|
}
|
|
2097
2209
|
async function installService(spec) {
|
|
2098
2210
|
const p = assertSupported();
|
|
2099
|
-
|
|
2211
|
+
mkdirSync5(logDir(), { recursive: true });
|
|
2100
2212
|
if (p === "darwin") {
|
|
2101
2213
|
const path = launchAgentPath(spec.name);
|
|
2102
|
-
|
|
2214
|
+
mkdirSync5(join8(homedir2(), "Library", "LaunchAgents"), { recursive: true });
|
|
2103
2215
|
writeFileSync6(path, renderLaunchAgent(spec), "utf8");
|
|
2104
2216
|
await exec("launchctl", ["unload", path]);
|
|
2105
2217
|
const res = await exec("launchctl", ["load", "-w", path]);
|
|
@@ -2111,7 +2223,7 @@ async function installService(spec) {
|
|
|
2111
2223
|
}
|
|
2112
2224
|
} else {
|
|
2113
2225
|
const path = systemdUnitPath(spec.name);
|
|
2114
|
-
|
|
2226
|
+
mkdirSync5(join8(homedir2(), ".config", "systemd", "user"), { recursive: true });
|
|
2115
2227
|
writeFileSync6(path, renderSystemdUnit(spec), "utf8");
|
|
2116
2228
|
await exec("systemctl", ["--user", "daemon-reload"]);
|
|
2117
2229
|
const res = await exec("systemctl", [
|
|
@@ -2134,13 +2246,13 @@ async function uninstallService(name) {
|
|
|
2134
2246
|
const path = launchAgentPath(name);
|
|
2135
2247
|
if (existsSync10(path)) {
|
|
2136
2248
|
await exec("launchctl", ["unload", "-w", path]);
|
|
2137
|
-
|
|
2249
|
+
rmSync2(path, { force: true });
|
|
2138
2250
|
}
|
|
2139
2251
|
} else {
|
|
2140
2252
|
const unit = systemdUnitName(name);
|
|
2141
2253
|
await exec("systemctl", ["--user", "disable", "--now", unit]);
|
|
2142
2254
|
const path = systemdUnitPath(name);
|
|
2143
|
-
if (existsSync10(path))
|
|
2255
|
+
if (existsSync10(path)) rmSync2(path, { force: true });
|
|
2144
2256
|
await exec("systemctl", ["--user", "daemon-reload"]);
|
|
2145
2257
|
}
|
|
2146
2258
|
}
|
|
@@ -2260,6 +2372,14 @@ async function doctorCommand() {
|
|
|
2260
2372
|
const zones = listStoredZones();
|
|
2261
2373
|
if (zones.length) out(` ${ok} cert store: ${zones.join(", ")}`);
|
|
2262
2374
|
else out(` ${pc6.dim("\xB7")} cert store empty`);
|
|
2375
|
+
for (const zone of zones) {
|
|
2376
|
+
const actual = await certZoneName(certPathForZone(zone));
|
|
2377
|
+
if (actual && actual !== zone) {
|
|
2378
|
+
out(
|
|
2379
|
+
` ${bad} ${zone}.pem is really a cert for ${actual} \u2014 fix with \`cfld login --reauth --zone ${zone}\``
|
|
2380
|
+
);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2263
2383
|
const entries = listEntries();
|
|
2264
2384
|
out(` ${ok} registry: ${entries.length} tunnel(s)`);
|
|
2265
2385
|
out("");
|
|
@@ -2268,8 +2388,8 @@ async function doctorCommand() {
|
|
|
2268
2388
|
// src/cli.ts
|
|
2269
2389
|
function readVersion() {
|
|
2270
2390
|
try {
|
|
2271
|
-
const here =
|
|
2272
|
-
const pkg = JSON.parse(readFileSync7(
|
|
2391
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
2392
|
+
const pkg = JSON.parse(readFileSync7(join9(here, "../package.json"), "utf8"));
|
|
2273
2393
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
2274
2394
|
} catch {
|
|
2275
2395
|
return "0.0.0";
|
|
@@ -94,7 +94,12 @@ function LogColumn({
|
|
|
94
94
|
borderLeft: Boolean(divider),
|
|
95
95
|
children: [
|
|
96
96
|
title ? /* @__PURE__ */ jsx(Text, { color: titleColor, bold: true, wrap: "truncate-end", children: title }) : null,
|
|
97
|
-
/* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, justifyContent: "flex-end", overflow: "hidden", children: lines.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: placeholder }) :
|
|
97
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, justifyContent: "flex-end", overflow: "hidden", children: lines.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: placeholder }) : (
|
|
98
|
+
// Wrap rather than truncate so long lines (e.g. magic-link / OAuth
|
|
99
|
+
// URLs) stay fully visible and selectable instead of ending in "…".
|
|
100
|
+
// The bottom-pinned box clips older wrapped lines off the top.
|
|
101
|
+
lines.slice(-rows).map((line, i) => /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: line }, i))
|
|
102
|
+
) })
|
|
98
103
|
]
|
|
99
104
|
}
|
|
100
105
|
);
|