@freecodecamp/universe-cli 0.5.1 → 0.7.0

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.
Files changed (4) hide show
  1. package/README.md +3 -43
  2. package/dist/index.cjs +1123 -244
  3. package/dist/index.js +422 -194
  4. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { cac } from "cac";
6
6
  // src/commands/deploy.ts
7
7
  import { readFile as readFile2 } from "fs/promises";
8
8
  import { resolve as resolve2 } from "path";
9
- import { log } from "@clack/prompts";
9
+ import { log as log2, spinner } from "@clack/prompts";
10
10
 
11
11
  // src/output/exit-codes.ts
12
12
  var EXIT_USAGE = 10;
@@ -429,6 +429,14 @@ function suggest(target, candidates, threshold = 2) {
429
429
  }
430
430
 
431
431
  // src/lib/proxy-client.ts
432
+ var DEFAULT_FETCH_TIMEOUT_MS = 3e4;
433
+ function parseFetchTimeoutMs(env) {
434
+ const raw = env["UNIVERSE_FETCH_TIMEOUT_MS"];
435
+ if (raw === void 0 || raw === "") return void 0;
436
+ const n = Number(raw);
437
+ if (!Number.isFinite(n) || n < 0) return void 0;
438
+ return Math.floor(n);
439
+ }
432
440
  var ProxyError = class extends CliError {
433
441
  exitCode;
434
442
  status;
@@ -440,6 +448,13 @@ var ProxyError = class extends CliError {
440
448
  this.exitCode = mapExitCode(status);
441
449
  }
442
450
  };
451
+ var AliasDriftError = class extends ProxyError {
452
+ current;
453
+ constructor(message, current) {
454
+ super(409, "alias_drift", message);
455
+ this.current = current;
456
+ }
457
+ };
443
458
  function mapExitCode(status) {
444
459
  if (status === 401 || status === 403) return EXIT_CREDENTIALS;
445
460
  if (status === 422 || status === 0 || status >= 500) return EXIT_STORAGE;
@@ -475,9 +490,11 @@ async function readErrorEnvelope(response) {
475
490
  };
476
491
  }
477
492
  if (isProxyErrorEnvelope(raw) && raw.error) {
493
+ const current = typeof raw.current === "string" ? raw.current : void 0;
478
494
  return {
479
495
  code: raw.error.code ?? `http_${status}`,
480
- message: raw.error.message ?? response.statusText ?? "request failed"
496
+ message: raw.error.message ?? response.statusText ?? "request failed",
497
+ ...current === void 0 ? {} : { current }
481
498
  };
482
499
  }
483
500
  return {
@@ -485,12 +502,36 @@ async function readErrorEnvelope(response) {
485
502
  message: response.statusText || "request failed"
486
503
  };
487
504
  }
505
+ function throwProxyError(status, env) {
506
+ if (status === 409 && env.code === "alias_drift") {
507
+ throw new AliasDriftError(env.message, env.current ?? "");
508
+ }
509
+ throw new ProxyError(status, env.code, env.message);
510
+ }
488
511
  function stripTrailingSlash(url) {
489
512
  return url.endsWith("/") ? url.slice(0, -1) : url;
490
513
  }
491
514
  function createProxyClient(cfg) {
492
515
  const base = stripTrailingSlash(cfg.baseUrl);
493
516
  const fetchImpl = cfg.fetch ?? globalThis.fetch.bind(globalThis);
517
+ const timeoutMs = cfg.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
518
+ function withTimeoutSignal(init) {
519
+ if (timeoutMs <= 0) return init;
520
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
521
+ const merged = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;
522
+ return { ...init, signal: merged };
523
+ }
524
+ function translateFetchError(err) {
525
+ if (err instanceof DOMException && (err.name === "TimeoutError" || err.name === "AbortError")) {
526
+ throw new ProxyError(
527
+ 0,
528
+ "timeout",
529
+ `proxy timed out after ${timeoutMs}ms`
530
+ );
531
+ }
532
+ const message = err instanceof Error ? err.message : String(err);
533
+ throw new ProxyError(0, "network_error", `proxy unreachable: ${message}`);
534
+ }
494
535
  async function userBearer() {
495
536
  const tok = await cfg.getAuthToken();
496
537
  return `Bearer ${tok}`;
@@ -498,14 +539,13 @@ function createProxyClient(cfg) {
498
539
  async function call(url, init) {
499
540
  let response;
500
541
  try {
501
- response = await fetchImpl(url, init);
542
+ response = await fetchImpl(url, withTimeoutSignal(init));
502
543
  } catch (err) {
503
- const message = err instanceof Error ? err.message : String(err);
504
- throw new ProxyError(0, "network_error", `proxy unreachable: ${message}`);
544
+ translateFetchError(err);
505
545
  }
506
546
  if (!response.ok) {
507
547
  const env = await readErrorEnvelope(response);
508
- throw new ProxyError(response.status, env.code, env.message);
548
+ throwProxyError(response.status, env);
509
549
  }
510
550
  if (response.status === 204) {
511
551
  return void 0;
@@ -569,18 +609,53 @@ function createProxyClient(cfg) {
569
609
  }
570
610
  });
571
611
  },
612
+ async getAlias(req) {
613
+ const url = `${base}/api/site/${encodeURIComponent(req.site)}/alias/${encodeURIComponent(req.mode)}`;
614
+ let response;
615
+ try {
616
+ response = await fetchImpl(
617
+ url,
618
+ withTimeoutSignal({
619
+ method: "GET",
620
+ headers: {
621
+ Authorization: await userBearer(),
622
+ Accept: "application/json"
623
+ }
624
+ })
625
+ );
626
+ } catch (err) {
627
+ translateFetchError(err);
628
+ }
629
+ if (response.status === 404) return null;
630
+ if (!response.ok) {
631
+ const env = await readErrorEnvelope(response);
632
+ throwProxyError(response.status, env);
633
+ }
634
+ return await response.json();
635
+ },
572
636
  async sitePromote(req) {
573
637
  const url = `${base}/api/site/${encodeURIComponent(req.site)}/promote`;
638
+ const headers = {
639
+ Authorization: await userBearer(),
640
+ Accept: "application/json"
641
+ };
642
+ const body = {};
643
+ if (req.deployId !== void 0) body.deployId = req.deployId;
644
+ if (req.expectedCurrent !== void 0)
645
+ body.expectedCurrent = req.expectedCurrent;
646
+ const hasBody = Object.keys(body).length > 0;
647
+ if (hasBody) headers["Content-Type"] = "application/json";
574
648
  return call(url, {
575
649
  method: "POST",
576
- headers: {
577
- Authorization: await userBearer(),
578
- Accept: "application/json"
579
- }
650
+ headers,
651
+ ...hasBody ? { body: JSON.stringify(body) } : {}
580
652
  });
581
653
  },
582
654
  async siteRollback(req) {
583
655
  const url = `${base}/api/site/${encodeURIComponent(req.site)}/rollback`;
656
+ const body = { to: req.to };
657
+ if (req.expectedCurrent !== void 0)
658
+ body.expectedCurrent = req.expectedCurrent;
584
659
  return call(url, {
585
660
  method: "POST",
586
661
  headers: {
@@ -588,7 +663,7 @@ function createProxyClient(cfg) {
588
663
  Accept: "application/json",
589
664
  "Content-Type": "application/json"
590
665
  },
591
- body: JSON.stringify({ to: req.to })
666
+ body: JSON.stringify(body)
592
667
  });
593
668
  },
594
669
  async registerSite(req) {
@@ -793,6 +868,106 @@ function buildErrorEnvelope(command, code, message, issues) {
793
868
  };
794
869
  }
795
870
 
871
+ // src/output/format.ts
872
+ import { log } from "@clack/prompts";
873
+
874
+ // src/output/redact.ts
875
+ var AWS_KEY_PREFIX_PATTERN = /(?:AKIA|ASIA|AROA|AIDA|ACCA|ANPA|ABIA|AGPA)[A-Z0-9]{12,}/g;
876
+ var URL_CREDS_PATTERN = /https?:\/\/[^@\s]+@/g;
877
+ var CREDENTIAL_CONTEXT_PATTERN = /(?:access_key_id|secret_access_key|accessKeyId|secretAccessKey|secret|password|token|key|credential|auth)\s*[=:]\s*([A-Za-z0-9/+=]{21,})/gi;
878
+ var HEX_CREDENTIAL_CONTEXT_PATTERN = /(?:secret|password|token|key|credential|auth|access_key_id|secret_access_key)\s*[=:]\s*([a-f0-9]{32,})/gi;
879
+ var JSON_CREDENTIAL_PATTERN = /"(?:secret|password|token|key|credential|auth|access_key_id|secret_access_key|accessKeyId|secretAccessKey)"\s*:\s*"[^"]+"/gi;
880
+ var BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
881
+ var CREDENTIAL_KEY_PATTERN = /(?:secret|password|token|key|credential|auth)/i;
882
+ var EXACT_CREDENTIAL_KEYS = /* @__PURE__ */ new Set([
883
+ "accesskeyid",
884
+ "secretaccesskey",
885
+ "access_key_id",
886
+ "secret_access_key"
887
+ ]);
888
+ var STANDALONE_LONG_SECRET = /^[A-Za-z0-9/+=]{21,}$/;
889
+ function maskAwsKey(match) {
890
+ return match.slice(0, 4) + "****" + match.slice(-4);
891
+ }
892
+ function maskUrlCreds(match) {
893
+ const atIndex = match.lastIndexOf("@");
894
+ const protocolEnd = match.indexOf("://") + 3;
895
+ return match.slice(0, protocolEnd) + "****:****@" + match.slice(atIndex + 1);
896
+ }
897
+ function redact(value) {
898
+ let result = value;
899
+ result = result.replace(URL_CREDS_PATTERN, maskUrlCreds);
900
+ result = result.replace(AWS_KEY_PREFIX_PATTERN, maskAwsKey);
901
+ result = result.replace(BEARER_PATTERN, "Bearer ****");
902
+ result = result.replace(JSON_CREDENTIAL_PATTERN, (match) => {
903
+ const colonIndex = match.indexOf(":");
904
+ return match.slice(0, colonIndex + 1) + '"****"';
905
+ });
906
+ result = result.replace(
907
+ CREDENTIAL_CONTEXT_PATTERN,
908
+ (_match, _secret, _offset, _full) => {
909
+ const eqIndex = _match.indexOf("=");
910
+ const colonIndex = _match.indexOf(":");
911
+ const sepIndex = eqIndex >= 0 && colonIndex >= 0 ? Math.min(eqIndex, colonIndex) : eqIndex >= 0 ? eqIndex : colonIndex;
912
+ const prefix = _match.slice(0, sepIndex + 1);
913
+ return prefix + "****";
914
+ }
915
+ );
916
+ result = result.replace(HEX_CREDENTIAL_CONTEXT_PATTERN, (_match, _secret) => {
917
+ const eqIndex = _match.indexOf("=");
918
+ const colonIndex = _match.indexOf(":");
919
+ const sepIndex = eqIndex >= 0 && colonIndex >= 0 ? Math.min(eqIndex, colonIndex) : eqIndex >= 0 ? eqIndex : colonIndex;
920
+ const prefix = _match.slice(0, sepIndex + 1);
921
+ return prefix + "****";
922
+ });
923
+ return result;
924
+ }
925
+ function redactValue(value, keyName) {
926
+ if (typeof value === "string") {
927
+ if (keyName && EXACT_CREDENTIAL_KEYS.has(keyName.toLowerCase())) {
928
+ return "****";
929
+ }
930
+ const redacted = redact(value);
931
+ if (redacted === value && keyName && CREDENTIAL_KEY_PATTERN.test(keyName) && STANDALONE_LONG_SECRET.test(value)) {
932
+ return "****";
933
+ }
934
+ return redacted;
935
+ }
936
+ if (Array.isArray(value)) {
937
+ return value.map((v) => redactValue(v, keyName));
938
+ }
939
+ if (value !== null && typeof value === "object") {
940
+ return redactObject(value);
941
+ }
942
+ return value;
943
+ }
944
+ function redactObject(obj) {
945
+ const result = {};
946
+ for (const [key, value] of Object.entries(obj)) {
947
+ result[key] = redactValue(value, key);
948
+ }
949
+ return result;
950
+ }
951
+
952
+ // src/output/format.ts
953
+ function outputError(ctx, code, message, optsOrIssues) {
954
+ const opts = Array.isArray(optsOrIssues) ? { issues: optsOrIssues } : optsOrIssues ?? {};
955
+ const redactedMessage = redact(message);
956
+ const redactedIssues = opts.issues?.map(redact);
957
+ if (ctx.json) {
958
+ const envelope = buildErrorEnvelope(
959
+ ctx.command,
960
+ code,
961
+ redactedMessage,
962
+ redactedIssues
963
+ );
964
+ const payload = opts.extras ? { ...envelope, ...redactObject(opts.extras) } : envelope;
965
+ process.stdout.write(JSON.stringify(payload) + "\n");
966
+ } else {
967
+ (opts.logError ?? ((m) => log.error(m)))(redactedMessage);
968
+ }
969
+ }
970
+
796
971
  // src/commands/deploy.ts
797
972
  var defaultReadPlatformYaml = async (cwd) => {
798
973
  return readFile2(resolve2(cwd, "platform.yaml"), "utf-8");
@@ -886,10 +1061,11 @@ async function deploy(options, deps = {}) {
886
1061
  const build = deps.runBuild ?? runBuild;
887
1062
  const walk = deps.walkFiles ?? walkFiles;
888
1063
  const upload = deps.uploadFiles ?? uploadFiles;
889
- const success = deps.logSuccess ?? ((s) => log.success(s));
890
- const info = deps.logInfo ?? ((s) => log.info(s));
891
- const warn = deps.logWarn ?? ((s) => log.warn(s));
892
- const error = deps.logError ?? ((s) => log.error(s));
1064
+ const mkSpinner = deps.createSpinner ?? (() => spinner());
1065
+ const success = deps.logSuccess ?? ((s) => log2.success(s));
1066
+ const info = deps.logInfo ?? ((s) => log2.info(s));
1067
+ const warn = deps.logWarn ?? ((s) => log2.warn(s));
1068
+ const error = deps.logError ?? ((s) => log2.error(s));
893
1069
  const exit = deps.exit ?? exitWithCode;
894
1070
  try {
895
1071
  const identity = await resolveId({ env });
@@ -902,7 +1078,8 @@ async function deploy(options, deps = {}) {
902
1078
  const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL;
903
1079
  const client = mkClient({
904
1080
  baseUrl,
905
- getAuthToken: () => identity.token
1081
+ getAuthToken: () => identity.token,
1082
+ timeoutMs: parseFetchTimeoutMs(env)
906
1083
  });
907
1084
  let me;
908
1085
  try {
@@ -953,17 +1130,22 @@ async function deploy(options, deps = {}) {
953
1130
  } catch (err) {
954
1131
  rethrowProxy("deploy init failed", err);
955
1132
  }
1133
+ const spin = options.json ? null : mkSpinner();
1134
+ spin?.start(`Uploading 0/${filtered.length} files`);
956
1135
  const uploadResult = await upload({
957
1136
  client,
958
1137
  deployId: initResult.deployId,
959
1138
  jwt: initResult.jwt,
960
- files: filtered
1139
+ files: filtered,
1140
+ onProgress: spin ? (p) => spin.message(`Uploading ${p.uploaded}/${p.total} \u2014 ${p.current}`) : void 0
961
1141
  });
962
1142
  if (uploadResult.errors.length > 0) {
1143
+ spin?.error(`Upload failed: ${uploadResult.errors.length} file(s)`);
963
1144
  const message = `Upload partially failed: ${uploadResult.errors.length} file(s) failed:
964
1145
  - ${uploadResult.errors.join("\n - ")}`;
965
1146
  throw new PartialUploadError(message);
966
1147
  }
1148
+ spin?.stop(`Uploaded ${uploadResult.fileCount} files`);
967
1149
  const mode = options.promote ? "production" : "preview";
968
1150
  let finalizeResult;
969
1151
  try {
@@ -976,6 +1158,25 @@ async function deploy(options, deps = {}) {
976
1158
  } catch (err) {
977
1159
  rethrowProxy("deploy finalize failed", err);
978
1160
  }
1161
+ if (options.promote && !options.json) {
1162
+ try {
1163
+ const preview = await client.getAlias({
1164
+ site: config.site,
1165
+ mode: "preview"
1166
+ });
1167
+ if (preview && preview.deployId !== finalizeResult.deployId) {
1168
+ warn(
1169
+ `Preview alias still points to ${preview.deployId}; it will not auto-update. Run \`universe static deploy\` (without --promote) to refresh preview.`
1170
+ );
1171
+ }
1172
+ } catch (err) {
1173
+ if (err instanceof ProxyError && (err.status === 401 || err.status === 403)) {
1174
+ warn(
1175
+ `Preview alias probe got ${err.status} (${err.code}) \u2014 token may need rotation: ${err.message}`
1176
+ );
1177
+ }
1178
+ }
1179
+ }
979
1180
  if (options.json) {
980
1181
  emitJson(
981
1182
  buildEnvelope("deploy", true, {
@@ -1022,17 +1223,15 @@ async function deploy(options, deps = {}) {
1022
1223
  code = EXIT_USAGE;
1023
1224
  message = String(err);
1024
1225
  }
1025
- if (options.json) {
1026
- emitJson(buildErrorEnvelope("deploy", code, message));
1027
- } else {
1028
- error(message);
1029
- }
1226
+ outputError({ json: options.json, command: "deploy" }, code, message, {
1227
+ logError: error
1228
+ });
1030
1229
  exit(code);
1031
1230
  }
1032
1231
  }
1033
1232
 
1034
1233
  // src/commands/login.ts
1035
- import { log as log2 } from "@clack/prompts";
1234
+ import { log as log3 } from "@clack/prompts";
1036
1235
 
1037
1236
  // src/lib/device-flow.ts
1038
1237
  var DEVICE_CODE_URL = "https://github.com/login/device/code";
@@ -1117,9 +1316,9 @@ async function login(options, deps = {}) {
1117
1316
  const runFlow = deps.runDeviceFlow ?? runDeviceFlow;
1118
1317
  const save = deps.saveToken ?? saveToken;
1119
1318
  const load = deps.loadToken ?? loadToken;
1120
- const success = deps.logSuccess ?? ((s) => log2.success(s));
1121
- const info = deps.logInfo ?? ((s) => log2.info(s));
1122
- const error = deps.logError ?? ((s) => log2.error(s));
1319
+ const success = deps.logSuccess ?? ((s) => log3.success(s));
1320
+ const info = deps.logInfo ?? ((s) => log3.info(s));
1321
+ const error = deps.logError ?? ((s) => log3.error(s));
1123
1322
  const exit = deps.exit ?? exitWithCode;
1124
1323
  const envClientId = env["UNIVERSE_GH_CLIENT_ID"];
1125
1324
  const clientId = envClientId && envClientId.trim().length > 0 ? envClientId : DEFAULT_GH_CLIENT_ID;
@@ -1170,17 +1369,12 @@ async function login(options, deps = {}) {
1170
1369
  });
1171
1370
  } catch (err) {
1172
1371
  const message = err instanceof Error ? err.message : String(err);
1173
- if (options.json) {
1174
- emitJson2({
1175
- schemaVersion: "1",
1176
- command: "login",
1177
- success: false,
1178
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1179
- error: { code: EXIT_CREDENTIALS, message }
1180
- });
1181
- } else {
1182
- error(message);
1183
- }
1372
+ outputError(
1373
+ { json: options.json, command: "login" },
1374
+ EXIT_CREDENTIALS,
1375
+ message,
1376
+ { logError: error }
1377
+ );
1184
1378
  exit(EXIT_CREDENTIALS);
1185
1379
  return;
1186
1380
  }
@@ -1193,15 +1387,15 @@ async function login(options, deps = {}) {
1193
1387
  }
1194
1388
 
1195
1389
  // src/commands/logout.ts
1196
- import { log as log3 } from "@clack/prompts";
1390
+ import { log as log4 } from "@clack/prompts";
1197
1391
  function emitJson3(envelope) {
1198
1392
  process.stdout.write(JSON.stringify(envelope) + "\n");
1199
1393
  }
1200
1394
  async function logout(options, deps = {}) {
1201
1395
  const load = deps.loadToken ?? loadToken;
1202
1396
  const del = deps.deleteToken ?? deleteToken;
1203
- const success = deps.logSuccess ?? ((s) => log3.success(s));
1204
- const info = deps.logInfo ?? ((s) => log3.info(s));
1397
+ const success = deps.logSuccess ?? ((s) => log4.success(s));
1398
+ const info = deps.logInfo ?? ((s) => log4.info(s));
1205
1399
  const existing = await load();
1206
1400
  await del();
1207
1401
  if (options.json) {
@@ -1218,14 +1412,14 @@ async function logout(options, deps = {}) {
1218
1412
  // src/commands/ls.ts
1219
1413
  import { readFile as readFile3 } from "fs/promises";
1220
1414
  import { resolve as resolve3 } from "path";
1221
- import { log as log4 } from "@clack/prompts";
1415
+ import { log as log5 } from "@clack/prompts";
1222
1416
  var defaultReadPlatformYaml2 = async (cwd) => {
1223
1417
  return readFile3(resolve3(cwd, "platform.yaml"), "utf-8");
1224
1418
  };
1225
1419
  function emitJson4(envelope) {
1226
1420
  process.stdout.write(JSON.stringify(envelope) + "\n");
1227
1421
  }
1228
- var DEPLOY_ID_RE = /^(\d{8})-(\d{6})-([a-f0-9]+)$/i;
1422
+ var DEPLOY_ID_RE = /^(\d{8})-(\d{6})-(\S+)$/;
1229
1423
  function parseDeployId(deployId) {
1230
1424
  const m = DEPLOY_ID_RE.exec(deployId);
1231
1425
  if (!m) return { deployId, timestamp: null, sha: null };
@@ -1268,9 +1462,9 @@ async function ls(options, deps = {}) {
1268
1462
  const readYaml = deps.readPlatformYaml ?? defaultReadPlatformYaml2;
1269
1463
  const resolveId = deps.resolveIdentity ?? resolveIdentity;
1270
1464
  const mkClient = deps.createProxyClient ?? createProxyClient;
1271
- const success = deps.logSuccess ?? ((s) => log4.success(s));
1272
- const info = deps.logInfo ?? ((s) => log4.info(s));
1273
- const error = deps.logError ?? ((s) => log4.error(s));
1465
+ const success = deps.logSuccess ?? ((s) => log5.success(s));
1466
+ const info = deps.logInfo ?? ((s) => log5.info(s));
1467
+ const error = deps.logError ?? ((s) => log5.error(s));
1274
1468
  const exit = deps.exit ?? exitWithCode;
1275
1469
  try {
1276
1470
  const identity = await resolveId({ env });
@@ -1291,7 +1485,8 @@ async function ls(options, deps = {}) {
1291
1485
  const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL;
1292
1486
  const client = mkClient({
1293
1487
  baseUrl,
1294
- getAuthToken: () => identity.token
1488
+ getAuthToken: () => identity.token,
1489
+ timeoutMs: parseFetchTimeoutMs(env)
1295
1490
  });
1296
1491
  const raw = await client.siteDeploys({ site });
1297
1492
  const sorted = [...raw].sort(
@@ -1315,11 +1510,9 @@ async function ls(options, deps = {}) {
1315
1510
  success(formatTable(deploys));
1316
1511
  } catch (err) {
1317
1512
  const { code, message } = wrapProxyError("ls", err);
1318
- if (options.json) {
1319
- emitJson4(buildErrorEnvelope("ls", code, message));
1320
- } else {
1321
- error(message);
1322
- }
1513
+ outputError({ json: options.json, command: "ls" }, code, message, {
1514
+ logError: error
1515
+ });
1323
1516
  exit(code);
1324
1517
  }
1325
1518
  }
@@ -1327,7 +1520,12 @@ async function ls(options, deps = {}) {
1327
1520
  // src/commands/promote.ts
1328
1521
  import { readFile as readFile4 } from "fs/promises";
1329
1522
  import { resolve as resolve4 } from "path";
1330
- import { log as log5 } from "@clack/prompts";
1523
+ import { confirm, isCancel, log as log6 } from "@clack/prompts";
1524
+ var defaultPromptConfirm = async (msg) => {
1525
+ const r = await confirm({ message: msg, initialValue: false });
1526
+ if (isCancel(r)) return false;
1527
+ return r === true;
1528
+ };
1331
1529
  var defaultReadPlatformYaml3 = async (cwd) => {
1332
1530
  return readFile4(resolve4(cwd, "platform.yaml"), "utf-8");
1333
1531
  };
@@ -1356,9 +1554,10 @@ async function promote(options, deps = {}) {
1356
1554
  const readYaml = deps.readPlatformYaml ?? defaultReadPlatformYaml3;
1357
1555
  const resolveId = deps.resolveIdentity ?? resolveIdentity;
1358
1556
  const mkClient = deps.createProxyClient ?? createProxyClient;
1359
- const success = deps.logSuccess ?? ((s) => log5.success(s));
1360
- const error = deps.logError ?? ((s) => log5.error(s));
1557
+ const success = deps.logSuccess ?? ((s) => log6.success(s));
1558
+ const error = deps.logError ?? ((s) => log6.error(s));
1361
1559
  const exit = deps.exit ?? exitWithCode;
1560
+ const promptConfirm = deps.promptConfirm ?? defaultPromptConfirm;
1362
1561
  try {
1363
1562
  const identity = await resolveId({ env });
1364
1563
  if (!identity) {
@@ -1370,16 +1569,80 @@ async function promote(options, deps = {}) {
1370
1569
  const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL;
1371
1570
  const client = mkClient({
1372
1571
  baseUrl,
1373
- getAuthToken: () => identity.token
1572
+ getAuthToken: () => identity.token,
1573
+ timeoutMs: parseFetchTimeoutMs(env)
1374
1574
  });
1375
1575
  let result;
1376
1576
  if (options.from) {
1377
- result = await client.siteRollback({
1577
+ const prod = await client.getAlias({
1378
1578
  site: config.site,
1379
- to: options.from
1579
+ mode: "production"
1380
1580
  });
1581
+ const initialExpected = prod?.deployId ?? "";
1582
+ try {
1583
+ result = await client.siteRollback({
1584
+ site: config.site,
1585
+ to: options.from,
1586
+ expectedCurrent: initialExpected
1587
+ });
1588
+ } catch (err) {
1589
+ if (!(err instanceof AliasDriftError)) throw err;
1590
+ if (options.json) throw err;
1591
+ error(
1592
+ `drift: production moved to ${err.current}, expected ${initialExpected}`
1593
+ );
1594
+ const ok = await promptConfirm(
1595
+ `Retry promote --from with expectedCurrent='${err.current}'?`
1596
+ );
1597
+ if (!ok) throw err;
1598
+ result = await client.siteRollback({
1599
+ site: config.site,
1600
+ to: options.from,
1601
+ expectedCurrent: err.current
1602
+ });
1603
+ }
1381
1604
  } else {
1382
- result = await client.sitePromote({ site: config.site });
1605
+ const preview = await client.getAlias({
1606
+ site: config.site,
1607
+ mode: "preview"
1608
+ });
1609
+ if (preview === null) {
1610
+ throw new ConfigError(
1611
+ "no preview alias to promote \u2014 run `universe static deploy` first"
1612
+ );
1613
+ }
1614
+ const prod = await client.getAlias({
1615
+ site: config.site,
1616
+ mode: "production"
1617
+ });
1618
+ if (!options.json) {
1619
+ success(
1620
+ `Promoting ${preview.deployId} \u2192 ${prod?.deployId ?? "<none>"}`
1621
+ );
1622
+ }
1623
+ const initialExpected = prod?.deployId ?? "";
1624
+ try {
1625
+ result = await client.sitePromote({
1626
+ site: config.site,
1627
+ deployId: preview.deployId,
1628
+ expectedCurrent: initialExpected
1629
+ });
1630
+ } catch (err) {
1631
+ if (!(err instanceof AliasDriftError)) throw err;
1632
+ if (options.json) throw err;
1633
+ error(
1634
+ `drift: production moved to ${err.current}, expected ${initialExpected}`
1635
+ );
1636
+ const ok = await promptConfirm(
1637
+ `Retry promote with expectedCurrent='${err.current}'?`
1638
+ );
1639
+ if (!ok) throw err;
1640
+ result = await client.sitePromote({
1641
+ site: config.site,
1642
+ deployId: preview.deployId,
1643
+ expectedCurrent: err.current
1644
+ });
1645
+ }
1383
1646
  }
1384
1647
  if (options.json) {
1385
1648
  emitJson5(
@@ -1405,11 +1668,11 @@ async function promote(options, deps = {}) {
1405
1668
  }
1406
1669
  } catch (err) {
1407
1670
  const { code, message } = wrapProxyError("promote", err);
1408
- if (options.json) {
1409
- emitJson5(buildErrorEnvelope("promote", code, message));
1410
- } else {
1411
- error(message);
1412
- }
1671
+ const extras = err instanceof AliasDriftError ? { current: err.current } : void 0;
1672
+ outputError({ json: options.json, command: "promote" }, code, message, {
1673
+ logError: error,
1674
+ extras
1675
+ });
1413
1676
  exit(code);
1414
1677
  }
1415
1678
  }
@@ -1417,7 +1680,12 @@ async function promote(options, deps = {}) {
1417
1680
  // src/commands/rollback.ts
1418
1681
  import { readFile as readFile5 } from "fs/promises";
1419
1682
  import { resolve as resolve5 } from "path";
1420
- import { log as log6 } from "@clack/prompts";
1683
+ import { confirm as confirm2, isCancel as isCancel2, log as log7 } from "@clack/prompts";
1684
+ var defaultPromptConfirm2 = async (msg) => {
1685
+ const r = await confirm2({ message: msg, initialValue: false });
1686
+ if (isCancel2(r)) return false;
1687
+ return r === true;
1688
+ };
1421
1689
  var defaultReadPlatformYaml4 = async (cwd) => {
1422
1690
  return readFile5(resolve5(cwd, "platform.yaml"), "utf-8");
1423
1691
  };
@@ -1446,9 +1714,10 @@ async function rollback(options, deps = {}) {
1446
1714
  const readYaml = deps.readPlatformYaml ?? defaultReadPlatformYaml4;
1447
1715
  const resolveId = deps.resolveIdentity ?? resolveIdentity;
1448
1716
  const mkClient = deps.createProxyClient ?? createProxyClient;
1449
- const success = deps.logSuccess ?? ((s) => log6.success(s));
1450
- const error = deps.logError ?? ((s) => log6.error(s));
1717
+ const success = deps.logSuccess ?? ((s) => log7.success(s));
1718
+ const error = deps.logError ?? ((s) => log7.error(s));
1451
1719
  const exit = deps.exit ?? exitWithCode;
1720
+ const promptConfirm = deps.promptConfirm ?? defaultPromptConfirm2;
1452
1721
  try {
1453
1722
  if (!options.to || options.to.trim().length === 0) {
1454
1723
  throw new UsageError(
@@ -1465,12 +1734,38 @@ async function rollback(options, deps = {}) {
1465
1734
  const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL;
1466
1735
  const client = mkClient({
1467
1736
  baseUrl,
1468
- getAuthToken: () => identity.token
1737
+ getAuthToken: () => identity.token,
1738
+ timeoutMs: parseFetchTimeoutMs(env)
1469
1739
  });
1470
- const result = await client.siteRollback({
1740
+ const to = options.to.trim();
1741
+ const prod = await client.getAlias({
1471
1742
  site: config.site,
1472
- to: options.to.trim()
1743
+ mode: "production"
1473
1744
  });
1745
+ const initialExpected = prod?.deployId ?? "";
1746
+ let result;
1747
+ try {
1748
+ result = await client.siteRollback({
1749
+ site: config.site,
1750
+ to,
1751
+ expectedCurrent: initialExpected
1752
+ });
1753
+ } catch (err) {
1754
+ if (!(err instanceof AliasDriftError)) throw err;
1755
+ if (options.json) throw err;
1756
+ error(
1757
+ `drift: production moved to ${err.current}, expected ${initialExpected}`
1758
+ );
1759
+ const ok = await promptConfirm(
1760
+ `Retry rollback with expectedCurrent='${err.current}'?`
1761
+ );
1762
+ if (!ok) throw err;
1763
+ result = await client.siteRollback({
1764
+ site: config.site,
1765
+ to,
1766
+ expectedCurrent: err.current
1767
+ });
1768
+ }
1474
1769
  if (options.json) {
1475
1770
  emitJson6(
1476
1771
  buildEnvelope("rollback", true, {
@@ -1493,17 +1788,17 @@ async function rollback(options, deps = {}) {
1493
1788
  }
1494
1789
  } catch (err) {
1495
1790
  const { code, message } = wrapProxyError("rollback", err);
1496
- if (options.json) {
1497
- emitJson6(buildErrorEnvelope("rollback", code, message));
1498
- } else {
1499
- error(message);
1500
- }
1791
+ const extras = err instanceof AliasDriftError ? { current: err.current } : void 0;
1792
+ outputError({ json: options.json, command: "rollback" }, code, message, {
1793
+ logError: error,
1794
+ extras
1795
+ });
1501
1796
  exit(code);
1502
1797
  }
1503
1798
  }
1504
1799
 
1505
1800
  // src/commands/whoami.ts
1506
- import { log as log7 } from "@clack/prompts";
1801
+ import { log as log8 } from "@clack/prompts";
1507
1802
  var DEFAULT_PROXY_URL2 = "https://uploads.freecode.camp";
1508
1803
  function emitJson7(envelope) {
1509
1804
  process.stdout.write(JSON.stringify(envelope) + "\n");
@@ -1512,24 +1807,26 @@ async function whoami(options, deps = {}) {
1512
1807
  const env = deps.env ?? process.env;
1513
1808
  const resolve6 = deps.resolveIdentity ?? resolveIdentity;
1514
1809
  const mkClient = deps.createProxyClient ?? createProxyClient;
1515
- const success = deps.logSuccess ?? ((s) => log7.success(s));
1516
- const error = deps.logError ?? ((s) => log7.error(s));
1810
+ const success = deps.logSuccess ?? ((s) => log8.success(s));
1811
+ const error = deps.logError ?? ((s) => log8.error(s));
1517
1812
  const exit = deps.exit ?? exitWithCode;
1518
1813
  const identity = await resolve6({ env });
1519
1814
  if (!identity) {
1520
1815
  const msg = "No GitHub identity available. Run `universe login`, set $GITHUB_TOKEN, or install the gh CLI.";
1521
- if (options.json) {
1522
- emitJson7(buildErrorEnvelope("whoami", EXIT_CREDENTIALS, msg));
1523
- } else {
1524
- error(msg);
1525
- }
1816
+ outputError(
1817
+ { json: options.json, command: "whoami" },
1818
+ EXIT_CREDENTIALS,
1819
+ msg,
1820
+ { logError: error }
1821
+ );
1526
1822
  exit(EXIT_CREDENTIALS);
1527
1823
  return;
1528
1824
  }
1529
1825
  const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL2;
1530
1826
  const client = mkClient({
1531
1827
  baseUrl,
1532
- getAuthToken: () => identity.token
1828
+ getAuthToken: () => identity.token,
1829
+ timeoutMs: parseFetchTimeoutMs(env)
1533
1830
  });
1534
1831
  try {
1535
1832
  const result = await client.whoami();
@@ -1555,17 +1852,15 @@ async function whoami(options, deps = {}) {
1555
1852
  } catch (err) {
1556
1853
  const exitCode = err instanceof CliError ? err.exitCode : EXIT_CREDENTIALS;
1557
1854
  const message = err instanceof ProxyError ? `whoami failed (${err.code}): ${err.message}` : err instanceof Error ? err.message : String(err);
1558
- if (options.json) {
1559
- emitJson7(buildErrorEnvelope("whoami", exitCode, message));
1560
- } else {
1561
- error(message);
1562
- }
1855
+ outputError({ json: options.json, command: "whoami" }, exitCode, message, {
1856
+ logError: error
1857
+ });
1563
1858
  exit(exitCode);
1564
1859
  }
1565
1860
  }
1566
1861
 
1567
1862
  // src/commands/sites/ls.ts
1568
- import { log as log8 } from "@clack/prompts";
1863
+ import { log as log9 } from "@clack/prompts";
1569
1864
 
1570
1865
  // src/commands/sites/_shared.ts
1571
1866
  function emitJson8(envelope) {
@@ -1589,7 +1884,8 @@ async function setupClient(deps) {
1589
1884
  const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL;
1590
1885
  const client = mkClient({
1591
1886
  baseUrl,
1592
- getAuthToken: () => identity.token
1887
+ getAuthToken: () => identity.token,
1888
+ timeoutMs: parseFetchTimeoutMs(env)
1593
1889
  });
1594
1890
  return { client, identitySource: identity.source };
1595
1891
  }
@@ -1612,11 +1908,11 @@ function formatTable2(rows) {
1612
1908
  }
1613
1909
  async function ls2(options, deps = {}) {
1614
1910
  const command = "sites ls";
1615
- const success = deps.logSuccess ?? ((s) => log8.message(s));
1616
- const error = deps.logError ?? ((s) => log8.error(s));
1911
+ const success = deps.logSuccess ?? ((s) => log9.message(s));
1912
+ const error = deps.logError ?? ((s) => log9.error(s));
1617
1913
  const exit = deps.exit ?? exitWithCode;
1618
1914
  try {
1619
- const { client } = await setupClient(deps);
1915
+ const { client, identitySource } = await setupClient(deps);
1620
1916
  let rows = await client.listSites();
1621
1917
  let scope = "all";
1622
1918
  if (options.mine) {
@@ -1630,7 +1926,8 @@ async function ls2(options, deps = {}) {
1630
1926
  buildEnvelope(command, true, {
1631
1927
  count: rows.length,
1632
1928
  scope,
1633
- sites: rows
1929
+ sites: rows,
1930
+ identitySource
1634
1931
  })
1635
1932
  );
1636
1933
  } else {
@@ -1638,28 +1935,26 @@ async function ls2(options, deps = {}) {
1638
1935
  }
1639
1936
  } catch (err) {
1640
1937
  const { code, message } = wrapProxyError(command, err);
1641
- if (options.json) {
1642
- emitJson8(buildErrorEnvelope(command, code, message));
1643
- } else {
1644
- error(message);
1645
- }
1938
+ outputError({ json: options.json, command }, code, message, {
1939
+ logError: error
1940
+ });
1646
1941
  exit(code);
1647
1942
  }
1648
1943
  }
1649
1944
 
1650
1945
  // src/commands/sites/register.ts
1651
- import { log as log9 } from "@clack/prompts";
1946
+ import { log as log10 } from "@clack/prompts";
1652
1947
  async function register(options, deps = {}) {
1653
1948
  const command = "sites register";
1654
- const success = deps.logSuccess ?? ((s) => log9.success(s));
1655
- const error = deps.logError ?? ((s) => log9.error(s));
1949
+ const success = deps.logSuccess ?? ((s) => log10.success(s));
1950
+ const error = deps.logError ?? ((s) => log10.error(s));
1656
1951
  const exit = deps.exit ?? exitWithCode;
1657
1952
  try {
1658
1953
  if (!options.slug || options.slug.trim().length === 0) {
1659
1954
  throw new UsageError("slug is required (positional argument)");
1660
1955
  }
1661
1956
  const teams = parseTeamsFlag(options.team);
1662
- const { client } = await setupClient(deps);
1957
+ const { client, identitySource } = await setupClient(deps);
1663
1958
  const row = await client.registerSite({
1664
1959
  slug: options.slug,
1665
1960
  teams: teams.length > 0 ? teams : void 0
@@ -1670,7 +1965,8 @@ async function register(options, deps = {}) {
1670
1965
  slug: row.slug,
1671
1966
  teams: row.teams,
1672
1967
  createdAt: row.createdAt,
1673
- createdBy: row.createdBy
1968
+ createdBy: row.createdBy,
1969
+ identitySource
1674
1970
  })
1675
1971
  );
1676
1972
  } else {
@@ -1687,33 +1983,32 @@ async function register(options, deps = {}) {
1687
1983
  }
1688
1984
  } catch (err) {
1689
1985
  const { code, message } = wrapProxyError(command, err);
1690
- if (options.json) {
1691
- emitJson8(buildErrorEnvelope(command, code, message));
1692
- } else {
1693
- error(message);
1694
- }
1986
+ outputError({ json: options.json, command }, code, message, {
1987
+ logError: error
1988
+ });
1695
1989
  exit(code);
1696
1990
  }
1697
1991
  }
1698
1992
 
1699
1993
  // src/commands/sites/rm.ts
1700
- import { log as log10 } from "@clack/prompts";
1994
+ import { log as log11 } from "@clack/prompts";
1701
1995
  async function rm2(options, deps = {}) {
1702
1996
  const command = "sites rm";
1703
- const success = deps.logSuccess ?? ((s) => log10.success(s));
1704
- const error = deps.logError ?? ((s) => log10.error(s));
1997
+ const success = deps.logSuccess ?? ((s) => log11.success(s));
1998
+ const error = deps.logError ?? ((s) => log11.error(s));
1705
1999
  const exit = deps.exit ?? exitWithCode;
1706
2000
  try {
1707
2001
  if (!options.slug || options.slug.trim().length === 0) {
1708
2002
  throw new UsageError("slug is required (positional argument)");
1709
2003
  }
1710
- const { client } = await setupClient(deps);
2004
+ const { client, identitySource } = await setupClient(deps);
1711
2005
  await client.deleteSite({ slug: options.slug });
1712
2006
  if (options.json) {
1713
2007
  emitJson8(
1714
2008
  buildEnvelope(command, true, {
1715
2009
  slug: options.slug,
1716
- deleted: true
2010
+ deleted: true,
2011
+ identitySource
1717
2012
  })
1718
2013
  );
1719
2014
  } else {
@@ -1728,21 +2023,19 @@ async function rm2(options, deps = {}) {
1728
2023
  }
1729
2024
  } catch (err) {
1730
2025
  const { code, message } = wrapProxyError(command, err);
1731
- if (options.json) {
1732
- emitJson8(buildErrorEnvelope(command, code, message));
1733
- } else {
1734
- error(message);
1735
- }
2026
+ outputError({ json: options.json, command }, code, message, {
2027
+ logError: error
2028
+ });
1736
2029
  exit(code);
1737
2030
  }
1738
2031
  }
1739
2032
 
1740
2033
  // src/commands/sites/update.ts
1741
- import { log as log11 } from "@clack/prompts";
2034
+ import { log as log12 } from "@clack/prompts";
1742
2035
  async function update(options, deps = {}) {
1743
2036
  const command = "sites update";
1744
- const success = deps.logSuccess ?? ((s) => log11.success(s));
1745
- const error = deps.logError ?? ((s) => log11.error(s));
2037
+ const success = deps.logSuccess ?? ((s) => log12.success(s));
2038
+ const error = deps.logError ?? ((s) => log12.error(s));
1746
2039
  const exit = deps.exit ?? exitWithCode;
1747
2040
  try {
1748
2041
  if (!options.slug || options.slug.trim().length === 0) {
@@ -1754,7 +2047,7 @@ async function update(options, deps = {}) {
1754
2047
  "--team is required with at least one slug; use `sites rm` to remove a site"
1755
2048
  );
1756
2049
  }
1757
- const { client } = await setupClient(deps);
2050
+ const { client, identitySource } = await setupClient(deps);
1758
2051
  const row = await client.updateSite({
1759
2052
  slug: options.slug,
1760
2053
  teams
@@ -1764,7 +2057,8 @@ async function update(options, deps = {}) {
1764
2057
  buildEnvelope(command, true, {
1765
2058
  slug: row.slug,
1766
2059
  teams: row.teams,
1767
- updatedAt: row.updatedAt
2060
+ updatedAt: row.updatedAt,
2061
+ identitySource
1768
2062
  })
1769
2063
  );
1770
2064
  } else {
@@ -1780,81 +2074,15 @@ async function update(options, deps = {}) {
1780
2074
  }
1781
2075
  } catch (err) {
1782
2076
  const { code, message } = wrapProxyError(command, err);
1783
- if (options.json) {
1784
- emitJson8(buildErrorEnvelope(command, code, message));
1785
- } else {
1786
- error(message);
1787
- }
2077
+ outputError({ json: options.json, command }, code, message, {
2078
+ logError: error
2079
+ });
1788
2080
  exit(code);
1789
2081
  }
1790
2082
  }
1791
2083
 
1792
- // src/output/format.ts
1793
- import { log as log12 } from "@clack/prompts";
1794
-
1795
- // src/output/redact.ts
1796
- var AWS_KEY_PREFIX_PATTERN = /(?:AKIA|ASIA|AROA|AIDA|ACCA|ANPA|ABIA|AGPA)[A-Z0-9]{12,}/g;
1797
- var URL_CREDS_PATTERN = /https?:\/\/[^@\s]+@/g;
1798
- var CREDENTIAL_CONTEXT_PATTERN = /(?:access_key_id|secret_access_key|accessKeyId|secretAccessKey|secret|password|token|key|credential|auth)\s*[=:]\s*([A-Za-z0-9/+=]{21,})/gi;
1799
- var HEX_CREDENTIAL_CONTEXT_PATTERN = /(?:secret|password|token|key|credential|auth|access_key_id|secret_access_key)\s*[=:]\s*([a-f0-9]{32,})/gi;
1800
- var JSON_CREDENTIAL_PATTERN = /"(?:secret|password|token|key|credential|auth|access_key_id|secret_access_key|accessKeyId|secretAccessKey)"\s*:\s*"[^"]+"/gi;
1801
- var BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
1802
- function maskAwsKey(match) {
1803
- return match.slice(0, 4) + "****" + match.slice(-4);
1804
- }
1805
- function maskUrlCreds(match) {
1806
- const atIndex = match.lastIndexOf("@");
1807
- const protocolEnd = match.indexOf("://") + 3;
1808
- return match.slice(0, protocolEnd) + "****:****@" + match.slice(atIndex + 1);
1809
- }
1810
- function redact(value) {
1811
- let result = value;
1812
- result = result.replace(URL_CREDS_PATTERN, maskUrlCreds);
1813
- result = result.replace(AWS_KEY_PREFIX_PATTERN, maskAwsKey);
1814
- result = result.replace(BEARER_PATTERN, "Bearer ****");
1815
- result = result.replace(JSON_CREDENTIAL_PATTERN, (match) => {
1816
- const colonIndex = match.indexOf(":");
1817
- return match.slice(0, colonIndex + 1) + '"****"';
1818
- });
1819
- result = result.replace(
1820
- CREDENTIAL_CONTEXT_PATTERN,
1821
- (_match, _secret, _offset, _full) => {
1822
- const eqIndex = _match.indexOf("=");
1823
- const colonIndex = _match.indexOf(":");
1824
- const sepIndex = eqIndex >= 0 && colonIndex >= 0 ? Math.min(eqIndex, colonIndex) : eqIndex >= 0 ? eqIndex : colonIndex;
1825
- const prefix = _match.slice(0, sepIndex + 1);
1826
- return prefix + "****";
1827
- }
1828
- );
1829
- result = result.replace(HEX_CREDENTIAL_CONTEXT_PATTERN, (_match, _secret) => {
1830
- const eqIndex = _match.indexOf("=");
1831
- const colonIndex = _match.indexOf(":");
1832
- const sepIndex = eqIndex >= 0 && colonIndex >= 0 ? Math.min(eqIndex, colonIndex) : eqIndex >= 0 ? eqIndex : colonIndex;
1833
- const prefix = _match.slice(0, sepIndex + 1);
1834
- return prefix + "****";
1835
- });
1836
- return result;
1837
- }
1838
-
1839
- // src/output/format.ts
1840
- function outputError(ctx, code, message, issues) {
1841
- const redactedMessage = redact(message);
1842
- const redactedIssues = issues?.map(redact);
1843
- if (ctx.json) {
1844
- const envelope = buildErrorEnvelope(
1845
- ctx.command,
1846
- code,
1847
- redactedMessage,
1848
- redactedIssues
1849
- );
1850
- process.stdout.write(JSON.stringify(envelope) + "\n");
1851
- } else {
1852
- log12.error(redactedMessage);
1853
- }
1854
- }
1855
-
1856
2084
  // src/cli.ts
1857
- var version = true ? "0.5.1" : "0.0.0";
2085
+ var version = true ? "0.7.0" : "0.0.0";
1858
2086
  function handleActionError(command, json, err) {
1859
2087
  const ctx = { json, command };
1860
2088
  const message = err instanceof Error ? err.message : "unknown error";