@freecodecamp/universe-cli 0.6.0 → 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 (3) hide show
  1. package/dist/index.cjs +305 -164
  2. package/dist/index.js +270 -204
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -8421,6 +8421,27 @@ function w(r, t2) {
8421
8421
  const e = r;
8422
8422
  e.isTTY && e.setRawMode(t2);
8423
8423
  }
8424
+ function z({ input: r = import_node_process.stdin, output: t2 = import_node_process.stdout, overwrite: e = true, hideCursor: s = true } = {}) {
8425
+ const i = _.createInterface({ input: r, output: t2, prompt: "", tabSize: 1 });
8426
+ _.emitKeypressEvents(r, i), r instanceof import_node_tty.ReadStream && r.isTTY && r.setRawMode(true);
8427
+ const n = (o, { name: a, sequence: h }) => {
8428
+ const l = String(o);
8429
+ if (V([l, a, h], "cancel")) {
8430
+ s && t2.write(import_sisteransi.cursor.show), process.exit(0);
8431
+ return;
8432
+ }
8433
+ if (!e) return;
8434
+ const f = a === "return" ? 0 : -1, v = a === "return" ? -1 : 0;
8435
+ _.moveCursor(t2, f, v, () => {
8436
+ _.clearLine(t2, 1, () => {
8437
+ r.once("keypress", n);
8438
+ });
8439
+ });
8440
+ };
8441
+ return s && t2.write(import_sisteransi.cursor.hide), r.once("keypress", n), () => {
8442
+ r.off("keypress", n), s && t2.write(import_sisteransi.cursor.show), r instanceof import_node_tty.ReadStream && r.isTTY && !Y && r.setRawMode(false), i.terminal = false, i.close();
8443
+ };
8444
+ }
8424
8445
  var O = (r) => "columns" in r && typeof r.columns == "number" ? r.columns : 80;
8425
8446
  var A = (r) => "rows" in r && typeof r.rows == "number" ? r.rows : 20;
8426
8447
  function R(r, t2, e, s = e) {
@@ -8579,6 +8600,7 @@ function Ze() {
8579
8600
  return import_node_process2.default.platform !== "win32" ? import_node_process2.default.env.TERM !== "linux" : !!import_node_process2.default.env.CI || !!import_node_process2.default.env.WT_SESSION || !!import_node_process2.default.env.TERMINUS_SUBLIME || import_node_process2.default.env.ConEmuTask === "{cmd::Cmder}" || import_node_process2.default.env.TERM_PROGRAM === "Terminus-Sublime" || import_node_process2.default.env.TERM_PROGRAM === "vscode" || import_node_process2.default.env.TERM === "xterm-256color" || import_node_process2.default.env.TERM === "alacritty" || import_node_process2.default.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
8580
8601
  }
8581
8602
  var ee = Ze();
8603
+ var ae = () => process.env.CI === "true";
8582
8604
  var w2 = (e, i) => ee ? e : i;
8583
8605
  var _e = w2("\u25C6", "*");
8584
8606
  var oe = w2("\u25A0", "x");
@@ -8671,6 +8693,59 @@ var O2 = { message: (e = [], { symbol: i = (0, import_node_util2.styleText)("gra
8671
8693
  }, error: (e, i) => {
8672
8694
  O2.message(e, { ...i, symbol: (0, import_node_util2.styleText)("red", ge) });
8673
8695
  } };
8696
+ var Ct = (e) => (0, import_node_util2.styleText)("magenta", e);
8697
+ var fe = ({ indicator: e = "dots", onCancel: i, output: s = process.stdout, cancelMessage: r, errorMessage: u2, frames: n = ee ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"], delay: o = ee ? 80 : 120, signal: c2, ...a } = {}) => {
8698
+ const l = ae();
8699
+ let $2, y2, p2 = false, m = false, g = "", S2, h = performance.now();
8700
+ const f = O(s), v = a?.styleFrame ?? Ct, T = (_2) => {
8701
+ const A2 = _2 > 1 ? u2 ?? u.messages.error : r ?? u.messages.cancel;
8702
+ m = _2 === 1, p2 && (W(A2, _2), m && typeof i == "function" && i());
8703
+ }, C2 = () => T(2), b = () => T(1), x = () => {
8704
+ process.on("uncaughtExceptionMonitor", C2), process.on("unhandledRejection", C2), process.on("SIGINT", b), process.on("SIGTERM", b), process.on("exit", T), c2 && c2.addEventListener("abort", b);
8705
+ }, G2 = () => {
8706
+ process.removeListener("uncaughtExceptionMonitor", C2), process.removeListener("unhandledRejection", C2), process.removeListener("SIGINT", b), process.removeListener("SIGTERM", b), process.removeListener("exit", T), c2 && c2.removeEventListener("abort", b);
8707
+ }, M2 = () => {
8708
+ if (S2 === void 0) return;
8709
+ l && s.write(`
8710
+ `);
8711
+ const _2 = wrapAnsi(S2, f, { hard: true, trim: false }).split(`
8712
+ `);
8713
+ _2.length > 1 && s.write(import_sisteransi2.cursor.up(_2.length - 1)), s.write(import_sisteransi2.cursor.to(0)), s.write(import_sisteransi2.erase.down());
8714
+ }, R2 = (_2) => _2.replace(/\.+$/, ""), j2 = (_2) => {
8715
+ const A2 = (performance.now() - _2) / 1e3, k = Math.floor(A2 / 60), L = Math.floor(A2 % 60);
8716
+ return k > 0 ? `[${k}m ${L}s]` : `[${L}s]`;
8717
+ }, D2 = a.withGuide ?? u.withGuide, ie = (_2 = "") => {
8718
+ p2 = true, $2 = z({ output: s }), g = R2(_2), h = performance.now(), D2 && s.write(`${(0, import_node_util2.styleText)("gray", d)}
8719
+ `);
8720
+ let A2 = 0, k = 0;
8721
+ x(), y2 = setInterval(() => {
8722
+ if (l && g === S2) return;
8723
+ M2(), S2 = g;
8724
+ const L = v(n[A2]);
8725
+ let Z;
8726
+ if (l) Z = `${L} ${g}...`;
8727
+ else if (e === "timer") Z = `${L} ${g} ${j2(h)}`;
8728
+ else {
8729
+ const Be = ".".repeat(Math.floor(k)).slice(0, 3);
8730
+ Z = `${L} ${g}${Be}`;
8731
+ }
8732
+ const Ne = wrapAnsi(Z, f, { hard: true, trim: false });
8733
+ s.write(Ne), A2 = A2 + 1 < n.length ? A2 + 1 : 0, k = k < 4 ? k + 0.125 : 0;
8734
+ }, o);
8735
+ }, W = (_2 = "", A2 = 0, k = false) => {
8736
+ if (!p2) return;
8737
+ p2 = false, clearInterval(y2), M2();
8738
+ const L = A2 === 0 ? (0, import_node_util2.styleText)("green", F) : A2 === 1 ? (0, import_node_util2.styleText)("red", oe) : (0, import_node_util2.styleText)("red", ue);
8739
+ g = _2 ?? g, k || (e === "timer" ? s.write(`${L} ${g} ${j2(h)}
8740
+ `) : s.write(`${L} ${g}
8741
+ `)), G2(), $2();
8742
+ };
8743
+ return { start: ie, stop: (_2 = "") => W(_2, 0), message: (_2 = "") => {
8744
+ g = R2(_2 ?? g);
8745
+ }, cancel: (_2 = "") => W(_2, 1), error: (_2 = "") => W(_2, 2), clear: () => W("", 0, true), get isCancelled() {
8746
+ return m;
8747
+ } };
8748
+ };
8674
8749
  var Ve = { light: w2("\u2500", "-"), heavy: w2("\u2501", "="), block: w2("\u2588", "#") };
8675
8750
  var je = `${(0, import_node_util2.styleText)("gray", d)} `;
8676
8751
 
@@ -22862,6 +22937,14 @@ function suggest(target, candidates, threshold = 2) {
22862
22937
  }
22863
22938
 
22864
22939
  // src/lib/proxy-client.ts
22940
+ var DEFAULT_FETCH_TIMEOUT_MS = 3e4;
22941
+ function parseFetchTimeoutMs(env) {
22942
+ const raw = env["UNIVERSE_FETCH_TIMEOUT_MS"];
22943
+ if (raw === void 0 || raw === "") return void 0;
22944
+ const n = Number(raw);
22945
+ if (!Number.isFinite(n) || n < 0) return void 0;
22946
+ return Math.floor(n);
22947
+ }
22865
22948
  var ProxyError = class extends CliError {
22866
22949
  exitCode;
22867
22950
  status;
@@ -22939,6 +23022,24 @@ function stripTrailingSlash(url2) {
22939
23022
  function createProxyClient(cfg) {
22940
23023
  const base = stripTrailingSlash(cfg.baseUrl);
22941
23024
  const fetchImpl = cfg.fetch ?? globalThis.fetch.bind(globalThis);
23025
+ const timeoutMs = cfg.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
23026
+ function withTimeoutSignal(init) {
23027
+ if (timeoutMs <= 0) return init;
23028
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
23029
+ const merged = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;
23030
+ return { ...init, signal: merged };
23031
+ }
23032
+ function translateFetchError(err) {
23033
+ if (err instanceof DOMException && (err.name === "TimeoutError" || err.name === "AbortError")) {
23034
+ throw new ProxyError(
23035
+ 0,
23036
+ "timeout",
23037
+ `proxy timed out after ${timeoutMs}ms`
23038
+ );
23039
+ }
23040
+ const message = err instanceof Error ? err.message : String(err);
23041
+ throw new ProxyError(0, "network_error", `proxy unreachable: ${message}`);
23042
+ }
22942
23043
  async function userBearer() {
22943
23044
  const tok = await cfg.getAuthToken();
22944
23045
  return `Bearer ${tok}`;
@@ -22946,10 +23047,9 @@ function createProxyClient(cfg) {
22946
23047
  async function call(url2, init) {
22947
23048
  let response;
22948
23049
  try {
22949
- response = await fetchImpl(url2, init);
23050
+ response = await fetchImpl(url2, withTimeoutSignal(init));
22950
23051
  } catch (err) {
22951
- const message = err instanceof Error ? err.message : String(err);
22952
- throw new ProxyError(0, "network_error", `proxy unreachable: ${message}`);
23052
+ translateFetchError(err);
22953
23053
  }
22954
23054
  if (!response.ok) {
22955
23055
  const env = await readErrorEnvelope(response);
@@ -23021,20 +23121,18 @@ function createProxyClient(cfg) {
23021
23121
  const url2 = `${base}/api/site/${encodeURIComponent(req.site)}/alias/${encodeURIComponent(req.mode)}`;
23022
23122
  let response;
23023
23123
  try {
23024
- response = await fetchImpl(url2, {
23025
- method: "GET",
23026
- headers: {
23027
- Authorization: await userBearer(),
23028
- Accept: "application/json"
23029
- }
23030
- });
23031
- } catch (err) {
23032
- const message = err instanceof Error ? err.message : String(err);
23033
- throw new ProxyError(
23034
- 0,
23035
- "network_error",
23036
- `proxy unreachable: ${message}`
23124
+ response = await fetchImpl(
23125
+ url2,
23126
+ withTimeoutSignal({
23127
+ method: "GET",
23128
+ headers: {
23129
+ Authorization: await userBearer(),
23130
+ Accept: "application/json"
23131
+ }
23132
+ })
23037
23133
  );
23134
+ } catch (err) {
23135
+ translateFetchError(err);
23038
23136
  }
23039
23137
  if (response.status === 404) return null;
23040
23138
  if (!response.ok) {
@@ -23278,6 +23376,103 @@ function buildErrorEnvelope(command, code, message, issues) {
23278
23376
  };
23279
23377
  }
23280
23378
 
23379
+ // src/output/redact.ts
23380
+ var AWS_KEY_PREFIX_PATTERN = /(?:AKIA|ASIA|AROA|AIDA|ACCA|ANPA|ABIA|AGPA)[A-Z0-9]{12,}/g;
23381
+ var URL_CREDS_PATTERN = /https?:\/\/[^@\s]+@/g;
23382
+ 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;
23383
+ var HEX_CREDENTIAL_CONTEXT_PATTERN = /(?:secret|password|token|key|credential|auth|access_key_id|secret_access_key)\s*[=:]\s*([a-f0-9]{32,})/gi;
23384
+ var JSON_CREDENTIAL_PATTERN = /"(?:secret|password|token|key|credential|auth|access_key_id|secret_access_key|accessKeyId|secretAccessKey)"\s*:\s*"[^"]+"/gi;
23385
+ var BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
23386
+ var CREDENTIAL_KEY_PATTERN = /(?:secret|password|token|key|credential|auth)/i;
23387
+ var EXACT_CREDENTIAL_KEYS = /* @__PURE__ */ new Set([
23388
+ "accesskeyid",
23389
+ "secretaccesskey",
23390
+ "access_key_id",
23391
+ "secret_access_key"
23392
+ ]);
23393
+ var STANDALONE_LONG_SECRET = /^[A-Za-z0-9/+=]{21,}$/;
23394
+ function maskAwsKey(match) {
23395
+ return match.slice(0, 4) + "****" + match.slice(-4);
23396
+ }
23397
+ function maskUrlCreds(match) {
23398
+ const atIndex = match.lastIndexOf("@");
23399
+ const protocolEnd = match.indexOf("://") + 3;
23400
+ return match.slice(0, protocolEnd) + "****:****@" + match.slice(atIndex + 1);
23401
+ }
23402
+ function redact(value) {
23403
+ let result = value;
23404
+ result = result.replace(URL_CREDS_PATTERN, maskUrlCreds);
23405
+ result = result.replace(AWS_KEY_PREFIX_PATTERN, maskAwsKey);
23406
+ result = result.replace(BEARER_PATTERN, "Bearer ****");
23407
+ result = result.replace(JSON_CREDENTIAL_PATTERN, (match) => {
23408
+ const colonIndex = match.indexOf(":");
23409
+ return match.slice(0, colonIndex + 1) + '"****"';
23410
+ });
23411
+ result = result.replace(
23412
+ CREDENTIAL_CONTEXT_PATTERN,
23413
+ (_match, _secret, _offset, _full) => {
23414
+ const eqIndex = _match.indexOf("=");
23415
+ const colonIndex = _match.indexOf(":");
23416
+ const sepIndex = eqIndex >= 0 && colonIndex >= 0 ? Math.min(eqIndex, colonIndex) : eqIndex >= 0 ? eqIndex : colonIndex;
23417
+ const prefix = _match.slice(0, sepIndex + 1);
23418
+ return prefix + "****";
23419
+ }
23420
+ );
23421
+ result = result.replace(HEX_CREDENTIAL_CONTEXT_PATTERN, (_match, _secret) => {
23422
+ const eqIndex = _match.indexOf("=");
23423
+ const colonIndex = _match.indexOf(":");
23424
+ const sepIndex = eqIndex >= 0 && colonIndex >= 0 ? Math.min(eqIndex, colonIndex) : eqIndex >= 0 ? eqIndex : colonIndex;
23425
+ const prefix = _match.slice(0, sepIndex + 1);
23426
+ return prefix + "****";
23427
+ });
23428
+ return result;
23429
+ }
23430
+ function redactValue(value, keyName) {
23431
+ if (typeof value === "string") {
23432
+ if (keyName && EXACT_CREDENTIAL_KEYS.has(keyName.toLowerCase())) {
23433
+ return "****";
23434
+ }
23435
+ const redacted = redact(value);
23436
+ if (redacted === value && keyName && CREDENTIAL_KEY_PATTERN.test(keyName) && STANDALONE_LONG_SECRET.test(value)) {
23437
+ return "****";
23438
+ }
23439
+ return redacted;
23440
+ }
23441
+ if (Array.isArray(value)) {
23442
+ return value.map((v) => redactValue(v, keyName));
23443
+ }
23444
+ if (value !== null && typeof value === "object") {
23445
+ return redactObject(value);
23446
+ }
23447
+ return value;
23448
+ }
23449
+ function redactObject(obj) {
23450
+ const result = {};
23451
+ for (const [key, value] of Object.entries(obj)) {
23452
+ result[key] = redactValue(value, key);
23453
+ }
23454
+ return result;
23455
+ }
23456
+
23457
+ // src/output/format.ts
23458
+ function outputError(ctx, code, message, optsOrIssues) {
23459
+ const opts = Array.isArray(optsOrIssues) ? { issues: optsOrIssues } : optsOrIssues ?? {};
23460
+ const redactedMessage = redact(message);
23461
+ const redactedIssues = opts.issues?.map(redact);
23462
+ if (ctx.json) {
23463
+ const envelope = buildErrorEnvelope(
23464
+ ctx.command,
23465
+ code,
23466
+ redactedMessage,
23467
+ redactedIssues
23468
+ );
23469
+ const payload = opts.extras ? { ...envelope, ...redactObject(opts.extras) } : envelope;
23470
+ process.stdout.write(JSON.stringify(payload) + "\n");
23471
+ } else {
23472
+ (opts.logError ?? ((m) => O2.error(m)))(redactedMessage);
23473
+ }
23474
+ }
23475
+
23281
23476
  // src/commands/deploy.ts
23282
23477
  var defaultReadPlatformYaml = async (cwd) => {
23283
23478
  return (0, import_promises4.readFile)((0, import_node_path5.resolve)(cwd, "platform.yaml"), "utf-8");
@@ -23371,6 +23566,7 @@ async function deploy(options, deps = {}) {
23371
23566
  const build = deps.runBuild ?? runBuild;
23372
23567
  const walk = deps.walkFiles ?? walkFiles;
23373
23568
  const upload = deps.uploadFiles ?? uploadFiles;
23569
+ const mkSpinner = deps.createSpinner ?? (() => fe());
23374
23570
  const success2 = deps.logSuccess ?? ((s) => O2.success(s));
23375
23571
  const info = deps.logInfo ?? ((s) => O2.info(s));
23376
23572
  const warn = deps.logWarn ?? ((s) => O2.warn(s));
@@ -23387,7 +23583,8 @@ async function deploy(options, deps = {}) {
23387
23583
  const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL;
23388
23584
  const client = mkClient({
23389
23585
  baseUrl,
23390
- getAuthToken: () => identity.token
23586
+ getAuthToken: () => identity.token,
23587
+ timeoutMs: parseFetchTimeoutMs(env)
23391
23588
  });
23392
23589
  let me2;
23393
23590
  try {
@@ -23438,17 +23635,22 @@ async function deploy(options, deps = {}) {
23438
23635
  } catch (err) {
23439
23636
  rethrowProxy("deploy init failed", err);
23440
23637
  }
23638
+ const spin = options.json ? null : mkSpinner();
23639
+ spin?.start(`Uploading 0/${filtered.length} files`);
23441
23640
  const uploadResult = await upload({
23442
23641
  client,
23443
23642
  deployId: initResult.deployId,
23444
23643
  jwt: initResult.jwt,
23445
- files: filtered
23644
+ files: filtered,
23645
+ onProgress: spin ? (p2) => spin.message(`Uploading ${p2.uploaded}/${p2.total} \u2014 ${p2.current}`) : void 0
23446
23646
  });
23447
23647
  if (uploadResult.errors.length > 0) {
23648
+ spin?.error(`Upload failed: ${uploadResult.errors.length} file(s)`);
23448
23649
  const message = `Upload partially failed: ${uploadResult.errors.length} file(s) failed:
23449
23650
  - ${uploadResult.errors.join("\n - ")}`;
23450
23651
  throw new PartialUploadError(message);
23451
23652
  }
23653
+ spin?.stop(`Uploaded ${uploadResult.fileCount} files`);
23452
23654
  const mode = options.promote ? "production" : "preview";
23453
23655
  let finalizeResult;
23454
23656
  try {
@@ -23461,6 +23663,25 @@ async function deploy(options, deps = {}) {
23461
23663
  } catch (err) {
23462
23664
  rethrowProxy("deploy finalize failed", err);
23463
23665
  }
23666
+ if (options.promote && !options.json) {
23667
+ try {
23668
+ const preview = await client.getAlias({
23669
+ site: config2.site,
23670
+ mode: "preview"
23671
+ });
23672
+ if (preview && preview.deployId !== finalizeResult.deployId) {
23673
+ warn(
23674
+ `Preview alias still points to ${preview.deployId}; it will not auto-update. Run \`universe static deploy\` (without --promote) to refresh preview.`
23675
+ );
23676
+ }
23677
+ } catch (err) {
23678
+ if (err instanceof ProxyError && (err.status === 401 || err.status === 403)) {
23679
+ warn(
23680
+ `Preview alias probe got ${err.status} (${err.code}) \u2014 token may need rotation: ${err.message}`
23681
+ );
23682
+ }
23683
+ }
23684
+ }
23464
23685
  if (options.json) {
23465
23686
  emitJson(
23466
23687
  buildEnvelope("deploy", true, {
@@ -23507,11 +23728,9 @@ async function deploy(options, deps = {}) {
23507
23728
  code = EXIT_USAGE;
23508
23729
  message = String(err);
23509
23730
  }
23510
- if (options.json) {
23511
- emitJson(buildErrorEnvelope("deploy", code, message));
23512
- } else {
23513
- error48(message);
23514
- }
23731
+ outputError({ json: options.json, command: "deploy" }, code, message, {
23732
+ logError: error48
23733
+ });
23515
23734
  exit(code);
23516
23735
  }
23517
23736
  }
@@ -23652,17 +23871,12 @@ async function login(options, deps = {}) {
23652
23871
  });
23653
23872
  } catch (err) {
23654
23873
  const message = err instanceof Error ? err.message : String(err);
23655
- if (options.json) {
23656
- emitJson2({
23657
- schemaVersion: "1",
23658
- command: "login",
23659
- success: false,
23660
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
23661
- error: { code: EXIT_CREDENTIALS, message }
23662
- });
23663
- } else {
23664
- error48(message);
23665
- }
23874
+ outputError(
23875
+ { json: options.json, command: "login" },
23876
+ EXIT_CREDENTIALS,
23877
+ message,
23878
+ { logError: error48 }
23879
+ );
23666
23880
  exit(EXIT_CREDENTIALS);
23667
23881
  return;
23668
23882
  }
@@ -23771,7 +23985,8 @@ async function ls(options, deps = {}) {
23771
23985
  const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL;
23772
23986
  const client = mkClient({
23773
23987
  baseUrl,
23774
- getAuthToken: () => identity.token
23988
+ getAuthToken: () => identity.token,
23989
+ timeoutMs: parseFetchTimeoutMs(env)
23775
23990
  });
23776
23991
  const raw = await client.siteDeploys({ site });
23777
23992
  const sorted = [...raw].sort(
@@ -23795,11 +24010,9 @@ async function ls(options, deps = {}) {
23795
24010
  success2(formatTable(deploys));
23796
24011
  } catch (err) {
23797
24012
  const { code, message } = wrapProxyError("ls", err);
23798
- if (options.json) {
23799
- emitJson4(buildErrorEnvelope("ls", code, message));
23800
- } else {
23801
- error48(message);
23802
- }
24013
+ outputError({ json: options.json, command: "ls" }, code, message, {
24014
+ logError: error48
24015
+ });
23803
24016
  exit(code);
23804
24017
  }
23805
24018
  }
@@ -23855,7 +24068,8 @@ async function promote(options, deps = {}) {
23855
24068
  const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL;
23856
24069
  const client = mkClient({
23857
24070
  baseUrl,
23858
- getAuthToken: () => identity.token
24071
+ getAuthToken: () => identity.token,
24072
+ timeoutMs: parseFetchTimeoutMs(env)
23859
24073
  });
23860
24074
  let result;
23861
24075
  if (options.from) {
@@ -23953,16 +24167,11 @@ async function promote(options, deps = {}) {
23953
24167
  }
23954
24168
  } catch (err) {
23955
24169
  const { code, message } = wrapProxyError("promote", err);
23956
- if (options.json) {
23957
- const envelope = buildErrorEnvelope("promote", code, message);
23958
- if (err instanceof AliasDriftError) {
23959
- emitJson5({ ...envelope, current: err.current });
23960
- } else {
23961
- emitJson5(envelope);
23962
- }
23963
- } else {
23964
- error48(message);
23965
- }
24170
+ const extras = err instanceof AliasDriftError ? { current: err.current } : void 0;
24171
+ outputError({ json: options.json, command: "promote" }, code, message, {
24172
+ logError: error48,
24173
+ extras
24174
+ });
23966
24175
  exit(code);
23967
24176
  }
23968
24177
  }
@@ -24023,7 +24232,8 @@ async function rollback(options, deps = {}) {
24023
24232
  const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL;
24024
24233
  const client = mkClient({
24025
24234
  baseUrl,
24026
- getAuthToken: () => identity.token
24235
+ getAuthToken: () => identity.token,
24236
+ timeoutMs: parseFetchTimeoutMs(env)
24027
24237
  });
24028
24238
  const to = options.to.trim();
24029
24239
  const prod = await client.getAlias({
@@ -24076,16 +24286,11 @@ async function rollback(options, deps = {}) {
24076
24286
  }
24077
24287
  } catch (err) {
24078
24288
  const { code, message } = wrapProxyError("rollback", err);
24079
- if (options.json) {
24080
- const envelope = buildErrorEnvelope("rollback", code, message);
24081
- if (err instanceof AliasDriftError) {
24082
- emitJson6({ ...envelope, current: err.current });
24083
- } else {
24084
- emitJson6(envelope);
24085
- }
24086
- } else {
24087
- error48(message);
24088
- }
24289
+ const extras = err instanceof AliasDriftError ? { current: err.current } : void 0;
24290
+ outputError({ json: options.json, command: "rollback" }, code, message, {
24291
+ logError: error48,
24292
+ extras
24293
+ });
24089
24294
  exit(code);
24090
24295
  }
24091
24296
  }
@@ -24105,18 +24310,20 @@ async function whoami(options, deps = {}) {
24105
24310
  const identity = await resolve6({ env });
24106
24311
  if (!identity) {
24107
24312
  const msg = "No GitHub identity available. Run `universe login`, set $GITHUB_TOKEN, or install the gh CLI.";
24108
- if (options.json) {
24109
- emitJson7(buildErrorEnvelope("whoami", EXIT_CREDENTIALS, msg));
24110
- } else {
24111
- error48(msg);
24112
- }
24313
+ outputError(
24314
+ { json: options.json, command: "whoami" },
24315
+ EXIT_CREDENTIALS,
24316
+ msg,
24317
+ { logError: error48 }
24318
+ );
24113
24319
  exit(EXIT_CREDENTIALS);
24114
24320
  return;
24115
24321
  }
24116
24322
  const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL2;
24117
24323
  const client = mkClient({
24118
24324
  baseUrl,
24119
- getAuthToken: () => identity.token
24325
+ getAuthToken: () => identity.token,
24326
+ timeoutMs: parseFetchTimeoutMs(env)
24120
24327
  });
24121
24328
  try {
24122
24329
  const result = await client.whoami();
@@ -24142,11 +24349,9 @@ async function whoami(options, deps = {}) {
24142
24349
  } catch (err) {
24143
24350
  const exitCode = err instanceof CliError ? err.exitCode : EXIT_CREDENTIALS;
24144
24351
  const message = err instanceof ProxyError ? `whoami failed (${err.code}): ${err.message}` : err instanceof Error ? err.message : String(err);
24145
- if (options.json) {
24146
- emitJson7(buildErrorEnvelope("whoami", exitCode, message));
24147
- } else {
24148
- error48(message);
24149
- }
24352
+ outputError({ json: options.json, command: "whoami" }, exitCode, message, {
24353
+ logError: error48
24354
+ });
24150
24355
  exit(exitCode);
24151
24356
  }
24152
24357
  }
@@ -24173,7 +24378,8 @@ async function setupClient(deps) {
24173
24378
  const baseUrl = env["UNIVERSE_PROXY_URL"] ?? DEFAULT_PROXY_URL;
24174
24379
  const client = mkClient({
24175
24380
  baseUrl,
24176
- getAuthToken: () => identity.token
24381
+ getAuthToken: () => identity.token,
24382
+ timeoutMs: parseFetchTimeoutMs(env)
24177
24383
  });
24178
24384
  return { client, identitySource: identity.source };
24179
24385
  }
@@ -24200,7 +24406,7 @@ async function ls2(options, deps = {}) {
24200
24406
  const error48 = deps.logError ?? ((s) => O2.error(s));
24201
24407
  const exit = deps.exit ?? exitWithCode;
24202
24408
  try {
24203
- const { client } = await setupClient(deps);
24409
+ const { client, identitySource } = await setupClient(deps);
24204
24410
  let rows = await client.listSites();
24205
24411
  let scope = "all";
24206
24412
  if (options.mine) {
@@ -24214,7 +24420,8 @@ async function ls2(options, deps = {}) {
24214
24420
  buildEnvelope(command, true, {
24215
24421
  count: rows.length,
24216
24422
  scope,
24217
- sites: rows
24423
+ sites: rows,
24424
+ identitySource
24218
24425
  })
24219
24426
  );
24220
24427
  } else {
@@ -24222,11 +24429,9 @@ async function ls2(options, deps = {}) {
24222
24429
  }
24223
24430
  } catch (err) {
24224
24431
  const { code, message } = wrapProxyError(command, err);
24225
- if (options.json) {
24226
- emitJson8(buildErrorEnvelope(command, code, message));
24227
- } else {
24228
- error48(message);
24229
- }
24432
+ outputError({ json: options.json, command }, code, message, {
24433
+ logError: error48
24434
+ });
24230
24435
  exit(code);
24231
24436
  }
24232
24437
  }
@@ -24242,7 +24447,7 @@ async function register(options, deps = {}) {
24242
24447
  throw new UsageError("slug is required (positional argument)");
24243
24448
  }
24244
24449
  const teams = parseTeamsFlag(options.team);
24245
- const { client } = await setupClient(deps);
24450
+ const { client, identitySource } = await setupClient(deps);
24246
24451
  const row = await client.registerSite({
24247
24452
  slug: options.slug,
24248
24453
  teams: teams.length > 0 ? teams : void 0
@@ -24253,7 +24458,8 @@ async function register(options, deps = {}) {
24253
24458
  slug: row.slug,
24254
24459
  teams: row.teams,
24255
24460
  createdAt: row.createdAt,
24256
- createdBy: row.createdBy
24461
+ createdBy: row.createdBy,
24462
+ identitySource
24257
24463
  })
24258
24464
  );
24259
24465
  } else {
@@ -24270,11 +24476,9 @@ async function register(options, deps = {}) {
24270
24476
  }
24271
24477
  } catch (err) {
24272
24478
  const { code, message } = wrapProxyError(command, err);
24273
- if (options.json) {
24274
- emitJson8(buildErrorEnvelope(command, code, message));
24275
- } else {
24276
- error48(message);
24277
- }
24479
+ outputError({ json: options.json, command }, code, message, {
24480
+ logError: error48
24481
+ });
24278
24482
  exit(code);
24279
24483
  }
24280
24484
  }
@@ -24289,13 +24493,14 @@ async function rm2(options, deps = {}) {
24289
24493
  if (!options.slug || options.slug.trim().length === 0) {
24290
24494
  throw new UsageError("slug is required (positional argument)");
24291
24495
  }
24292
- const { client } = await setupClient(deps);
24496
+ const { client, identitySource } = await setupClient(deps);
24293
24497
  await client.deleteSite({ slug: options.slug });
24294
24498
  if (options.json) {
24295
24499
  emitJson8(
24296
24500
  buildEnvelope(command, true, {
24297
24501
  slug: options.slug,
24298
- deleted: true
24502
+ deleted: true,
24503
+ identitySource
24299
24504
  })
24300
24505
  );
24301
24506
  } else {
@@ -24310,11 +24515,9 @@ async function rm2(options, deps = {}) {
24310
24515
  }
24311
24516
  } catch (err) {
24312
24517
  const { code, message } = wrapProxyError(command, err);
24313
- if (options.json) {
24314
- emitJson8(buildErrorEnvelope(command, code, message));
24315
- } else {
24316
- error48(message);
24317
- }
24518
+ outputError({ json: options.json, command }, code, message, {
24519
+ logError: error48
24520
+ });
24318
24521
  exit(code);
24319
24522
  }
24320
24523
  }
@@ -24335,7 +24538,7 @@ async function update(options, deps = {}) {
24335
24538
  "--team is required with at least one slug; use `sites rm` to remove a site"
24336
24539
  );
24337
24540
  }
24338
- const { client } = await setupClient(deps);
24541
+ const { client, identitySource } = await setupClient(deps);
24339
24542
  const row = await client.updateSite({
24340
24543
  slug: options.slug,
24341
24544
  teams
@@ -24345,7 +24548,8 @@ async function update(options, deps = {}) {
24345
24548
  buildEnvelope(command, true, {
24346
24549
  slug: row.slug,
24347
24550
  teams: row.teams,
24348
- updatedAt: row.updatedAt
24551
+ updatedAt: row.updatedAt,
24552
+ identitySource
24349
24553
  })
24350
24554
  );
24351
24555
  } else {
@@ -24361,78 +24565,15 @@ async function update(options, deps = {}) {
24361
24565
  }
24362
24566
  } catch (err) {
24363
24567
  const { code, message } = wrapProxyError(command, err);
24364
- if (options.json) {
24365
- emitJson8(buildErrorEnvelope(command, code, message));
24366
- } else {
24367
- error48(message);
24368
- }
24568
+ outputError({ json: options.json, command }, code, message, {
24569
+ logError: error48
24570
+ });
24369
24571
  exit(code);
24370
24572
  }
24371
24573
  }
24372
24574
 
24373
- // src/output/redact.ts
24374
- var AWS_KEY_PREFIX_PATTERN = /(?:AKIA|ASIA|AROA|AIDA|ACCA|ANPA|ABIA|AGPA)[A-Z0-9]{12,}/g;
24375
- var URL_CREDS_PATTERN = /https?:\/\/[^@\s]+@/g;
24376
- 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;
24377
- var HEX_CREDENTIAL_CONTEXT_PATTERN = /(?:secret|password|token|key|credential|auth|access_key_id|secret_access_key)\s*[=:]\s*([a-f0-9]{32,})/gi;
24378
- var JSON_CREDENTIAL_PATTERN = /"(?:secret|password|token|key|credential|auth|access_key_id|secret_access_key|accessKeyId|secretAccessKey)"\s*:\s*"[^"]+"/gi;
24379
- var BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
24380
- function maskAwsKey(match) {
24381
- return match.slice(0, 4) + "****" + match.slice(-4);
24382
- }
24383
- function maskUrlCreds(match) {
24384
- const atIndex = match.lastIndexOf("@");
24385
- const protocolEnd = match.indexOf("://") + 3;
24386
- return match.slice(0, protocolEnd) + "****:****@" + match.slice(atIndex + 1);
24387
- }
24388
- function redact(value) {
24389
- let result = value;
24390
- result = result.replace(URL_CREDS_PATTERN, maskUrlCreds);
24391
- result = result.replace(AWS_KEY_PREFIX_PATTERN, maskAwsKey);
24392
- result = result.replace(BEARER_PATTERN, "Bearer ****");
24393
- result = result.replace(JSON_CREDENTIAL_PATTERN, (match) => {
24394
- const colonIndex = match.indexOf(":");
24395
- return match.slice(0, colonIndex + 1) + '"****"';
24396
- });
24397
- result = result.replace(
24398
- CREDENTIAL_CONTEXT_PATTERN,
24399
- (_match, _secret, _offset, _full) => {
24400
- const eqIndex = _match.indexOf("=");
24401
- const colonIndex = _match.indexOf(":");
24402
- const sepIndex = eqIndex >= 0 && colonIndex >= 0 ? Math.min(eqIndex, colonIndex) : eqIndex >= 0 ? eqIndex : colonIndex;
24403
- const prefix = _match.slice(0, sepIndex + 1);
24404
- return prefix + "****";
24405
- }
24406
- );
24407
- result = result.replace(HEX_CREDENTIAL_CONTEXT_PATTERN, (_match, _secret) => {
24408
- const eqIndex = _match.indexOf("=");
24409
- const colonIndex = _match.indexOf(":");
24410
- const sepIndex = eqIndex >= 0 && colonIndex >= 0 ? Math.min(eqIndex, colonIndex) : eqIndex >= 0 ? eqIndex : colonIndex;
24411
- const prefix = _match.slice(0, sepIndex + 1);
24412
- return prefix + "****";
24413
- });
24414
- return result;
24415
- }
24416
-
24417
- // src/output/format.ts
24418
- function outputError(ctx, code, message, issues) {
24419
- const redactedMessage = redact(message);
24420
- const redactedIssues = issues?.map(redact);
24421
- if (ctx.json) {
24422
- const envelope = buildErrorEnvelope(
24423
- ctx.command,
24424
- code,
24425
- redactedMessage,
24426
- redactedIssues
24427
- );
24428
- process.stdout.write(JSON.stringify(envelope) + "\n");
24429
- } else {
24430
- O2.error(redactedMessage);
24431
- }
24432
- }
24433
-
24434
24575
  // src/cli.ts
24435
- var version2 = true ? "0.6.0" : "0.0.0";
24576
+ var version2 = true ? "0.7.0" : "0.0.0";
24436
24577
  function handleActionError(command, json2, err) {
24437
24578
  const ctx = { json: json2, command };
24438
24579
  const message = err instanceof Error ? err.message : "unknown error";