@mutmutco/hub 4.4.12 → 4.4.14

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 (2) hide show
  1. package/dist/index.cjs +58 -6
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -282,7 +282,26 @@ var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
282
282
  // ../installer/face/src/spinner.ts
283
283
  var import_node_fs2 = require("node:fs");
284
284
  var import_node_worker_threads = require("node:worker_threads");
285
- var FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
285
+
286
+ // ../installer/face/src/encoding.ts
287
+ var UNICODE_SPINNER_FRAMES = Object.freeze(["\u25D2", "\u25D0", "\u25D3", "\u25D1"]);
288
+ var ASCII_SPINNER_FRAMES = Object.freeze(["-", "\\", "|", "/"]);
289
+ function consoleRendersUnicode(env = process.env, platform = process.platform) {
290
+ if (env.MM_FACE_ASCII === "1" || env.NO_UNICODE === "1") return false;
291
+ const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
292
+ if (locale === "C" || locale === "POSIX" || /^C\./u.test(locale) || /^POSIX\./u.test(locale)) return false;
293
+ if (/cp437|ibm437|\b437\b/iu.test(locale)) return false;
294
+ if (/utf-?8/iu.test(locale)) return true;
295
+ if (platform === "win32") {
296
+ return Boolean(env.WT_SESSION) || env.TERM_PROGRAM === "vscode" || env.CHCP === "65001" || env.ConEmuANSI === "ON";
297
+ }
298
+ return true;
299
+ }
300
+ function spinnerFramesFor(env = process.env, platform = process.platform) {
301
+ return consoleRendersUnicode(env, platform) ? UNICODE_SPINNER_FRAMES : ASCII_SPINNER_FRAMES;
302
+ }
303
+
304
+ // ../installer/face/src/spinner.ts
286
305
  var WORKER_SOURCE = `
287
306
  const { parentPort, workerData } = require('node:worker_threads');
288
307
  const { writeSync, appendFileSync } = require('node:fs');
@@ -307,8 +326,10 @@ parentPort.on('message', (next) => {
307
326
  });
308
327
  setInterval(draw, workerData.intervalMs);
309
328
  `;
310
- function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath }) {
329
+ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath, env = process.env, platform = process.platform }) {
311
330
  animate = animate && !face.nested && !face.emitsProgress;
331
+ const glyphs = spinnerFramesFor(env, platform);
332
+ const ascii = glyphs[0] === ASCII_SPINNER_FRAMES[0];
312
333
  let timer = null;
313
334
  let worker = null;
314
335
  let control = null;
@@ -326,7 +347,8 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
326
347
  };
327
348
  const render = (text, measure) => {
328
349
  const line = face.step(text, measure ?? "__elapsed__", "note").split("\n")[0];
329
- frames = line ? FRAMES.map((glyph) => `\r\x1B[2K${line.replace(GLYPH.dot, glyph)}`) : [];
350
+ const prepared = line && ascii ? line.replaceAll(GLYPH.bar, "|") : line;
351
+ frames = prepared ? glyphs.map((glyph) => `\r\x1B[2K${prepared.replace(GLYPH.dot, glyph)}`) : [];
330
352
  };
331
353
  const draw = () => {
332
354
  if (animate && !suspended && frames.length) write(frames[frame++ % frames.length].replace("__elapsed__", `${Math.floor((Date.now() - started) / 1e3)}s`));
@@ -403,7 +425,6 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
403
425
  }
404
426
  };
405
427
  }
406
- var SPINNER_FRAMES = Object.freeze([...FRAMES]);
407
428
 
408
429
  // ../installer/face/src/conformance.ts
409
430
  var ALLOWED = new Set(Object.values(GLYPH));
@@ -634,6 +655,14 @@ var PHASES = {
634
655
  doctor: ["Checking health", "Checked health"],
635
656
  rollback: ["Restoring the previous version", "Restored the previous version"]
636
657
  };
658
+ var UPDATE_CHECK_PHASES = /* @__PURE__ */ new Set([
659
+ "preflight",
660
+ "resolve",
661
+ "verify-release",
662
+ "download",
663
+ "sign-in",
664
+ "check"
665
+ ]);
637
666
  function createInstallerRun(value, options = {}) {
638
667
  const declaration = validateInstallerProduct(value);
639
668
  const env = options.env ?? process.env;
@@ -669,6 +698,7 @@ function createInstallerRun(value, options = {}) {
669
698
  };
670
699
  const spinner = createSpinner(face, {
671
700
  animate: tty && env.TERM !== "dumb" && (options.animate ?? Boolean(process.stderr.isTTY)),
701
+ env,
672
702
  ...options.write ? { stream: { write: (text) => {
673
703
  emit(String(text), "spinner");
674
704
  return true;
@@ -687,6 +717,7 @@ function createInstallerRun(value, options = {}) {
687
717
  };
688
718
  let started = false;
689
719
  let finished = false;
720
+ let operation = options.operation;
690
721
  const start = () => {
691
722
  if (started || finished) return;
692
723
  started = true;
@@ -694,7 +725,7 @@ function createInstallerRun(value, options = {}) {
694
725
  const welcome = face.welcome();
695
726
  if (tty) lines(welcome);
696
727
  else if (welcome.length) {
697
- const warm = options.operation === "install" ? face.identity.installWarm : face.identity.warm;
728
+ const warm = operation === "install" ? face.identity.installWarm : face.identity.warm;
698
729
  lines([`${face.identity.name} - Mutatis Mutandis`, warm.replaceAll("\u2014", "-").replaceAll("\u2026", "...")]);
699
730
  }
700
731
  };
@@ -710,13 +741,20 @@ function createInstallerRun(value, options = {}) {
710
741
  get errors() {
711
742
  return [...errors];
712
743
  },
744
+ get operation() {
745
+ return operation;
746
+ },
747
+ set operation(value2) {
748
+ operation = value2;
749
+ },
713
750
  start,
714
751
  phase(id, facts = {}) {
715
752
  if (finished) throw new Error("installer run already finished");
716
753
  if (!Object.hasOwn(PHASES, id)) throw new Error("installer run: unknown phase");
717
754
  start();
718
755
  const state = facts.state ?? "ok";
719
- const title = PHASES[id][state === "ok" ? 1 : 0];
756
+ const fold = operation === "update" && UPDATE_CHECK_PHASES.has(id) && state !== "fail";
757
+ const title = PHASES[fold ? "check" : id][state === "ok" ? 1 : 0];
720
758
  if (state === "running") {
721
759
  if (face.progress(title, facts.measure ?? null)) return;
722
760
  if (activeTitle === title) spinner.say(title, facts.measure ?? null);
@@ -726,6 +764,10 @@ function createInstallerRun(value, options = {}) {
726
764
  }
727
765
  return;
728
766
  }
767
+ if (fold && id !== "check") {
768
+ if (facts.detail) run.relay(facts.detail);
769
+ return;
770
+ }
729
771
  if (!face.continues(id, state)) durable(title, facts.seconds ?? facts.measure ?? null, state);
730
772
  if (facts.detail) run.relay(facts.detail);
731
773
  },
@@ -881,6 +923,7 @@ async function runMaintenance(product, args, dependencies) {
881
923
  quiet,
882
924
  operation: installed ? "install" : "update"
883
925
  });
926
+ if (run) run.operation = installed ? "install" : "update";
884
927
  if (!quiet) run?.start();
885
928
  let active;
886
929
  let activeFacts;
@@ -3118,6 +3161,9 @@ async function reconcile(options) {
3118
3161
  options.narrate?.(line);
3119
3162
  options.onPhase?.(line);
3120
3163
  };
3164
+ const phase = (id, state, detail) => {
3165
+ options.onPhaseEvent?.({ id, state, ...detail ? { detail } : {} });
3166
+ };
3121
3167
  const run = `${(/* @__PURE__ */ new Date()).toISOString()}-${process.pid}`;
3122
3168
  const paths = ensureState(env);
3123
3169
  const repoUrl = env.MMI_UPDATER_REPO || REPO_URL_DEFAULT;
@@ -3139,6 +3185,7 @@ async function reconcile(options) {
3139
3185
  say("acquiring single-writer lease");
3140
3186
  const leaseWaitMs = Number(env.MMI_UPDATER_LEASE_WAIT_MS) || void 0;
3141
3187
  return await withLease(paths.leasePath, async () => {
3188
+ phase("preflight", "running");
3142
3189
  const journalEvents = parseJournal(paths.journalPath);
3143
3190
  if (!options.dryRun) {
3144
3191
  const pendingBefore = readPending(paths);
@@ -3150,6 +3197,8 @@ async function reconcile(options) {
3150
3197
  summary.reap = reap({ paths, dryRun: options.dryRun, env });
3151
3198
  for (const row of summary.reap) journal({ kind: "reap", surface: row.category, verdict: row.verdict, count: row.reaped, detail: row.detail, durationMs: row.durationMs });
3152
3199
  for (const row of summary.reap.filter((entry) => entry.verdict !== "skip")) say(`reap ${row.category}: ${row.verdict}${row.detail ? " \u2014 " + row.detail : ""}`);
3200
+ phase("preflight", "ok");
3201
+ phase("resolve", "running");
3153
3202
  if (shipped) {
3154
3203
  say(`shipped target v${shipped} \u2014 the payload is the release; no npm poll, no gate`);
3155
3204
  summary.target = shipped;
@@ -3208,7 +3257,9 @@ async function reconcile(options) {
3208
3257
  }
3209
3258
  }
3210
3259
  say(`target v${summary.target} (${summary.gate})`);
3260
+ phase("resolve", "ok");
3211
3261
  options.onTargetResolved?.(summary.target, summary.gate);
3262
+ phase("check", "running");
3212
3263
  say("updater: self-bootstrap");
3213
3264
  const selfStartedAt = import_node_perf_hooks2.performance.now();
3214
3265
  const self = npmGlobalArm({
@@ -3271,6 +3322,7 @@ async function reconcile(options) {
3271
3322
  journal({ kind: "surface", surface: probe.id, present: true, verdict: "skip", detail: `present, its converge arm has not landed yet (target ${summary.target})` });
3272
3323
  }
3273
3324
  }
3325
+ phase("check", "ok");
3274
3326
  return summary;
3275
3327
  }, leaseWaitMs);
3276
3328
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/hub",
3
- "version": "4.4.12",
3
+ "version": "4.4.14",
4
4
  "description": "Install and maintain the MMI CLI and every present host surface from one release-gated Hub command.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",