@mutmutco/installer-launcher 0.1.10 → 0.1.12

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/launcher.js CHANGED
@@ -311,7 +311,7 @@ import { appendFileSync as appendFileSync2 } from "node:fs";
311
311
  // ../face/src/outcome.ts
312
312
  import { readFileSync, writeFileSync } from "node:fs";
313
313
  var counts = ["total", "updated", "failed"];
314
- var strings = ["version", "retry", "detail"];
314
+ var strings = ["version", "retry", "detail", "logPath"];
315
315
  var flags = ["dryRun", "installed", "deferred", "operationFailed"];
316
316
  function validateInstallerOutcome(value) {
317
317
  const invalid = () => {
@@ -319,12 +319,15 @@ function validateInstallerOutcome(value) {
319
319
  };
320
320
  if (!value || typeof value !== "object" || Array.isArray(value)) return invalid();
321
321
  const facts = value;
322
- const allowed = [...counts, ...strings, ...flags];
322
+ const allowed = [...counts, ...strings, ...flags, "rollback", "logState"];
323
323
  if (Object.keys(facts).some((key) => !allowed.includes(key))) return invalid();
324
324
  for (const key of counts) if (!Number.isSafeInteger(facts[key]) || facts[key] < 0) return invalid();
325
325
  if (facts.updated + facts.failed > facts.total) return invalid();
326
326
  for (const key of strings) if (facts[key] !== void 0 && (typeof facts[key] !== "string" || facts[key].length > 4096)) return invalid();
327
327
  for (const key of flags) if (facts[key] !== void 0 && typeof facts[key] !== "boolean") return invalid();
328
+ if (facts.rollback !== void 0 && !["completed", "partial", "not-needed", "unknown"].includes(facts.rollback)) return invalid();
329
+ if (facts.logState !== void 0 && !["unavailable", "omitted"].includes(facts.logState)) return invalid();
330
+ if (facts.logPath !== void 0 && (!facts.logPath.trim() || /[\x00-\x1f\x7f]/u.test(facts.logPath))) return invalid();
328
331
  return { ...facts };
329
332
  }
330
333
  function writeInstallerOutcome(path, value) {
@@ -397,7 +400,8 @@ var PHASES = {
397
400
  verify: ["Verifying the payload", "Verified the payload"],
398
401
  install: ["Installing the product", "Installed the product"],
399
402
  activate: ["Activating surfaces", "Activated surfaces"],
400
- doctor: ["Checking health", "Checked health"]
403
+ doctor: ["Checking health", "Checked health"],
404
+ rollback: ["Restoring the previous version", "Restored the previous version"]
401
405
  };
402
406
  function createInstallerRun(value, options = {}) {
403
407
  const declaration = validateInstallerProduct(value);
@@ -408,11 +412,18 @@ function createInstallerRun(value, options = {}) {
408
412
  product: declaration.product,
409
413
  columns: options.columns,
410
414
  env,
411
- color: tty && options.color !== false && env.NO_COLOR === void 0
415
+ color: tty && options.color !== false && env.NO_COLOR === void 0 && env.TERM !== "dumb"
412
416
  });
413
- const write = options.write ?? ((text, channel) => {
417
+ const errors = [];
418
+ const write = options.write ? (text, channel) => {
419
+ try {
420
+ options.write(text, channel);
421
+ } catch (error) {
422
+ errors.push(`installer output observer: ${error instanceof Error ? error.message : String(error)}`);
423
+ }
424
+ } : (text, channel) => {
414
425
  (channel === "stdout" ? process.stdout : process.stderr).write(text);
415
- });
426
+ };
416
427
  const emit = (text, channel = "stdout", recorded = text) => {
417
428
  if (!text) return;
418
429
  write(text, channel);
@@ -437,9 +448,13 @@ function createInstallerRun(value, options = {}) {
437
448
  const start = () => {
438
449
  if (started || finished) return;
439
450
  started = true;
451
+ if (options.quiet) return;
440
452
  const welcome = face.welcome();
441
453
  if (tty) lines(welcome);
442
- else if (welcome.length) lines([`${face.identity.name} \u2014 Mutatis Mutandis`, options.operation === "install" ? face.identity.installWarm : face.identity.warm]);
454
+ else if (welcome.length) {
455
+ const warm = options.operation === "install" ? face.identity.installWarm : face.identity.warm;
456
+ lines([`${face.identity.name} - Mutatis Mutandis`, warm.replaceAll("\u2014", "-").replaceAll("\u2026", "...")]);
457
+ }
443
458
  };
444
459
  const durable = (title, measure, kind) => {
445
460
  spinner.stop();
@@ -447,6 +462,9 @@ function createInstallerRun(value, options = {}) {
447
462
  if (rendered) lines([tty ? rendered : `${kind === "fail" ? "Failed: " : ""}${title}${measure === null ? "" : ` (${typeof measure === "number" ? `${Math.max(0, Math.round(measure))}s` : measure})`}`]);
448
463
  };
449
464
  const run2 = {
465
+ get errors() {
466
+ return [...errors];
467
+ },
450
468
  start,
451
469
  phase(id, facts = {}) {
452
470
  if (finished) throw new Error("installer run already finished");
@@ -466,12 +484,13 @@ function createInstallerRun(value, options = {}) {
466
484
  const surface = declaration.surfaces.find((surface2) => surface2.id === facts.id);
467
485
  if (!surface) throw new Error("installer run: undeclared surface");
468
486
  start();
469
- const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} \u2192 ${facts.to}` : ` ${facts.to}` : "";
487
+ const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} ${tty ? "\u2192" : "->"} ${facts.to}` : ` ${facts.to}` : "";
470
488
  const status = { updated: options.dryRun ? "would update" : "updated", current: "already current", failed: "failed", skipped: "skipped", retry: "retrying", pending: "pending", kept: "kept" }[facts.state];
471
489
  if (!status) throw new Error("installer run: unknown surface state");
472
- const activation = facts.state === "updated" && surface.activation ? ` \xB7 ${surface.activation}` : "";
490
+ const separator = tty ? "\xB7" : "-";
491
+ const activation = facts.state === "updated" && surface.activation ? ` ${separator} ${surface.activation}` : "";
473
492
  const completed = ["updated", "current", "kept", "skipped"].includes(facts.state);
474
- durable(`${facts.id}${versions} \xB7 ${status}${activation}`, completed ? facts.seconds ?? null : null, facts.state === "failed" ? "fail" : facts.state === "updated" ? "ok" : "note");
493
+ durable(`${facts.id}${versions} ${separator} ${status}${activation}`, completed ? facts.seconds ?? null : null, facts.state === "failed" ? "fail" : facts.state === "updated" ? "ok" : "note");
475
494
  if (facts.detail) run2.relay(facts.detail);
476
495
  },
477
496
  milestone({ step, state, ms }) {
@@ -498,6 +517,20 @@ function createInstallerRun(value, options = {}) {
498
517
  `);
499
518
  }
500
519
  },
520
+ cancel() {
521
+ if (finished) return;
522
+ if (env.MM_INSTALLER_OUTCOME_FILE) writeInstallerOutcome(
523
+ env.MM_INSTALLER_OUTCOME_FILE,
524
+ { total: 0, updated: 0, failed: 0, deferred: true, detail: "Operation cancelled." }
525
+ );
526
+ start();
527
+ spinner.stop();
528
+ finished = true;
529
+ if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE) {
530
+ lines(tty ? face.receipt(["Operation cancelled."], { ready: false }) : ["Operation cancelled."]);
531
+ }
532
+ if (tty) lines([face.signOff()]);
533
+ },
501
534
  finish(facts) {
502
535
  if (finished) return;
503
536
  validateInstallerOutcome(facts);
@@ -505,6 +538,7 @@ function createInstallerRun(value, options = {}) {
505
538
  start();
506
539
  spinner.stop();
507
540
  finished = true;
541
+ if (options.quiet && facts.updated === 0 && facts.failed === 0 && !facts.operationFailed) return;
508
542
  const changed = !facts.version ? "No release target is available." : facts.dryRun ? `Would update ${facts.updated} of ${facts.total} surfaces to ${facts.version}.` : facts.installed ? `Installed ${facts.version} across ${facts.total} surfaces.` : `Updated ${facts.updated} of ${facts.total} surfaces to ${facts.version}.`;
509
543
  const ready = facts.failed === 0 && !facts.dryRun && !facts.deferred && !facts.operationFailed && Boolean(facts.version);
510
544
  const body = [
@@ -512,7 +546,9 @@ function createInstallerRun(value, options = {}) {
512
546
  changed,
513
547
  ...facts.failed ? [`Failed ${facts.failed} of ${facts.total} surfaces.`] : [],
514
548
  ...facts.detail ? [facts.detail] : [],
515
- facts.retry && facts.failed ? `Retry: ${facts.retry}` : `Check health any time: ${declaration.doctor}`
549
+ ...facts.rollback ? [`Rollback: ${facts.rollback}`] : [],
550
+ ...facts.logPath ? [`Log: ${facts.logPath}`] : facts.logState ? [`Log: ${facts.logState}`] : [],
551
+ facts.retry && (facts.failed > 0 || facts.operationFailed) ? `Retry: ${facts.retry}` : `Check health any time: ${declaration.doctor}`
516
552
  ];
517
553
  if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE && (facts.failed > 0 || facts.operationFailed)) {
518
554
  lines(tty ? face.receipt(body, { ready }) : body.map((row) => row.replace(/^[✔✖●] /u, "")));
@@ -1253,7 +1289,7 @@ function wipeProductDir(dir) {
1253
1289
  }
1254
1290
 
1255
1291
  // src/index.ts
1256
- var LAUNCHER_VERSION = true ? "0.1.10" : readVersionFromPackage();
1292
+ var LAUNCHER_VERSION = true ? "0.1.12" : readVersionFromPackage();
1257
1293
  function defaultPrint(message) {
1258
1294
  process.stdout.write(`${message}
1259
1295
  `);
@@ -1538,10 +1574,7 @@ async function installOrUpdate(config, dir, options, print, update) {
1538
1574
  operation: update ? "update" : "install",
1539
1575
  tty: options.tty,
1540
1576
  animate: !options.print && Boolean(process.stderr.isTTY),
1541
- write: (text, channel) => {
1542
- if (options.print) print(text.replace(/\n$/, ""));
1543
- else (channel === "stdout" ? process.stdout : process.stderr).write(text);
1544
- }
1577
+ ...options.print ? { write: (text) => print(text.replace(/\n$/, "")) } : {}
1545
1578
  });
1546
1579
  const fetchImpl = options.fetchImpl ?? fetch;
1547
1580
  let version = readState(dir)?.version ?? "unknown";
@@ -342,7 +342,7 @@ var import_node_fs4 = require("node:fs");
342
342
  // ../face/src/outcome.ts
343
343
  var import_node_fs3 = require("node:fs");
344
344
  var counts = ["total", "updated", "failed"];
345
- var strings = ["version", "retry", "detail"];
345
+ var strings = ["version", "retry", "detail", "logPath"];
346
346
  var flags = ["dryRun", "installed", "deferred", "operationFailed"];
347
347
  function validateInstallerOutcome(value) {
348
348
  const invalid = () => {
@@ -350,12 +350,15 @@ function validateInstallerOutcome(value) {
350
350
  };
351
351
  if (!value || typeof value !== "object" || Array.isArray(value)) return invalid();
352
352
  const facts = value;
353
- const allowed = [...counts, ...strings, ...flags];
353
+ const allowed = [...counts, ...strings, ...flags, "rollback", "logState"];
354
354
  if (Object.keys(facts).some((key) => !allowed.includes(key))) return invalid();
355
355
  for (const key of counts) if (!Number.isSafeInteger(facts[key]) || facts[key] < 0) return invalid();
356
356
  if (facts.updated + facts.failed > facts.total) return invalid();
357
357
  for (const key of strings) if (facts[key] !== void 0 && (typeof facts[key] !== "string" || facts[key].length > 4096)) return invalid();
358
358
  for (const key of flags) if (facts[key] !== void 0 && typeof facts[key] !== "boolean") return invalid();
359
+ if (facts.rollback !== void 0 && !["completed", "partial", "not-needed", "unknown"].includes(facts.rollback)) return invalid();
360
+ if (facts.logState !== void 0 && !["unavailable", "omitted"].includes(facts.logState)) return invalid();
361
+ if (facts.logPath !== void 0 && (!facts.logPath.trim() || /[\x00-\x1f\x7f]/u.test(facts.logPath))) return invalid();
359
362
  return { ...facts };
360
363
  }
361
364
  function writeInstallerOutcome(path, value) {
@@ -428,7 +431,8 @@ var PHASES = {
428
431
  verify: ["Verifying the payload", "Verified the payload"],
429
432
  install: ["Installing the product", "Installed the product"],
430
433
  activate: ["Activating surfaces", "Activated surfaces"],
431
- doctor: ["Checking health", "Checked health"]
434
+ doctor: ["Checking health", "Checked health"],
435
+ rollback: ["Restoring the previous version", "Restored the previous version"]
432
436
  };
433
437
  function createInstallerRun(value, options = {}) {
434
438
  const declaration = validateInstallerProduct(value);
@@ -439,11 +443,18 @@ function createInstallerRun(value, options = {}) {
439
443
  product: declaration.product,
440
444
  columns: options.columns,
441
445
  env,
442
- color: tty && options.color !== false && env.NO_COLOR === void 0
446
+ color: tty && options.color !== false && env.NO_COLOR === void 0 && env.TERM !== "dumb"
443
447
  });
444
- const write = options.write ?? ((text, channel) => {
448
+ const errors = [];
449
+ const write = options.write ? (text, channel) => {
450
+ try {
451
+ options.write(text, channel);
452
+ } catch (error) {
453
+ errors.push(`installer output observer: ${error instanceof Error ? error.message : String(error)}`);
454
+ }
455
+ } : (text, channel) => {
445
456
  (channel === "stdout" ? process.stdout : process.stderr).write(text);
446
- });
457
+ };
447
458
  const emit = (text, channel = "stdout", recorded = text) => {
448
459
  if (!text) return;
449
460
  write(text, channel);
@@ -468,9 +479,13 @@ function createInstallerRun(value, options = {}) {
468
479
  const start = () => {
469
480
  if (started || finished) return;
470
481
  started = true;
482
+ if (options.quiet) return;
471
483
  const welcome = face.welcome();
472
484
  if (tty) lines(welcome);
473
- else if (welcome.length) lines([`${face.identity.name} \u2014 Mutatis Mutandis`, options.operation === "install" ? face.identity.installWarm : face.identity.warm]);
485
+ else if (welcome.length) {
486
+ const warm = options.operation === "install" ? face.identity.installWarm : face.identity.warm;
487
+ lines([`${face.identity.name} - Mutatis Mutandis`, warm.replaceAll("\u2014", "-").replaceAll("\u2026", "...")]);
488
+ }
474
489
  };
475
490
  const durable = (title, measure, kind) => {
476
491
  spinner.stop();
@@ -478,6 +493,9 @@ function createInstallerRun(value, options = {}) {
478
493
  if (rendered) lines([tty ? rendered : `${kind === "fail" ? "Failed: " : ""}${title}${measure === null ? "" : ` (${typeof measure === "number" ? `${Math.max(0, Math.round(measure))}s` : measure})`}`]);
479
494
  };
480
495
  const run2 = {
496
+ get errors() {
497
+ return [...errors];
498
+ },
481
499
  start,
482
500
  phase(id, facts = {}) {
483
501
  if (finished) throw new Error("installer run already finished");
@@ -497,12 +515,13 @@ function createInstallerRun(value, options = {}) {
497
515
  const surface = declaration.surfaces.find((surface2) => surface2.id === facts.id);
498
516
  if (!surface) throw new Error("installer run: undeclared surface");
499
517
  start();
500
- const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} \u2192 ${facts.to}` : ` ${facts.to}` : "";
518
+ const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} ${tty ? "\u2192" : "->"} ${facts.to}` : ` ${facts.to}` : "";
501
519
  const status = { updated: options.dryRun ? "would update" : "updated", current: "already current", failed: "failed", skipped: "skipped", retry: "retrying", pending: "pending", kept: "kept" }[facts.state];
502
520
  if (!status) throw new Error("installer run: unknown surface state");
503
- const activation = facts.state === "updated" && surface.activation ? ` \xB7 ${surface.activation}` : "";
521
+ const separator = tty ? "\xB7" : "-";
522
+ const activation = facts.state === "updated" && surface.activation ? ` ${separator} ${surface.activation}` : "";
504
523
  const completed = ["updated", "current", "kept", "skipped"].includes(facts.state);
505
- durable(`${facts.id}${versions} \xB7 ${status}${activation}`, completed ? facts.seconds ?? null : null, facts.state === "failed" ? "fail" : facts.state === "updated" ? "ok" : "note");
524
+ durable(`${facts.id}${versions} ${separator} ${status}${activation}`, completed ? facts.seconds ?? null : null, facts.state === "failed" ? "fail" : facts.state === "updated" ? "ok" : "note");
506
525
  if (facts.detail) run2.relay(facts.detail);
507
526
  },
508
527
  milestone({ step, state, ms }) {
@@ -529,6 +548,20 @@ function createInstallerRun(value, options = {}) {
529
548
  `);
530
549
  }
531
550
  },
551
+ cancel() {
552
+ if (finished) return;
553
+ if (env.MM_INSTALLER_OUTCOME_FILE) writeInstallerOutcome(
554
+ env.MM_INSTALLER_OUTCOME_FILE,
555
+ { total: 0, updated: 0, failed: 0, deferred: true, detail: "Operation cancelled." }
556
+ );
557
+ start();
558
+ spinner.stop();
559
+ finished = true;
560
+ if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE) {
561
+ lines(tty ? face.receipt(["Operation cancelled."], { ready: false }) : ["Operation cancelled."]);
562
+ }
563
+ if (tty) lines([face.signOff()]);
564
+ },
532
565
  finish(facts) {
533
566
  if (finished) return;
534
567
  validateInstallerOutcome(facts);
@@ -536,6 +569,7 @@ function createInstallerRun(value, options = {}) {
536
569
  start();
537
570
  spinner.stop();
538
571
  finished = true;
572
+ if (options.quiet && facts.updated === 0 && facts.failed === 0 && !facts.operationFailed) return;
539
573
  const changed = !facts.version ? "No release target is available." : facts.dryRun ? `Would update ${facts.updated} of ${facts.total} surfaces to ${facts.version}.` : facts.installed ? `Installed ${facts.version} across ${facts.total} surfaces.` : `Updated ${facts.updated} of ${facts.total} surfaces to ${facts.version}.`;
540
574
  const ready = facts.failed === 0 && !facts.dryRun && !facts.deferred && !facts.operationFailed && Boolean(facts.version);
541
575
  const body = [
@@ -543,7 +577,9 @@ function createInstallerRun(value, options = {}) {
543
577
  changed,
544
578
  ...facts.failed ? [`Failed ${facts.failed} of ${facts.total} surfaces.`] : [],
545
579
  ...facts.detail ? [facts.detail] : [],
546
- facts.retry && facts.failed ? `Retry: ${facts.retry}` : `Check health any time: ${declaration.doctor}`
580
+ ...facts.rollback ? [`Rollback: ${facts.rollback}`] : [],
581
+ ...facts.logPath ? [`Log: ${facts.logPath}`] : facts.logState ? [`Log: ${facts.logState}`] : [],
582
+ facts.retry && (facts.failed > 0 || facts.operationFailed) ? `Retry: ${facts.retry}` : `Check health any time: ${declaration.doctor}`
547
583
  ];
548
584
  if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE && (facts.failed > 0 || facts.operationFailed)) {
549
585
  lines(tty ? face.receipt(body, { ready }) : body.map((row) => row.replace(/^[✔✖●] /u, "")));
@@ -1285,7 +1321,7 @@ function wipeProductDir(dir) {
1285
1321
  }
1286
1322
 
1287
1323
  // src/index.ts
1288
- var LAUNCHER_VERSION = true ? "0.1.10" : readVersionFromPackage();
1324
+ var LAUNCHER_VERSION = true ? "0.1.12" : readVersionFromPackage();
1289
1325
  function defaultPrint(message) {
1290
1326
  process.stdout.write(`${message}
1291
1327
  `);
@@ -1570,10 +1606,7 @@ async function installOrUpdate(config, dir, options, print, update) {
1570
1606
  operation: update ? "update" : "install",
1571
1607
  tty: options.tty,
1572
1608
  animate: !options.print && Boolean(process.stderr.isTTY),
1573
- write: (text, channel) => {
1574
- if (options.print) print(text.replace(/\n$/, ""));
1575
- else (channel === "stdout" ? process.stdout : process.stderr).write(text);
1576
- }
1609
+ ...options.print ? { write: (text) => print(text.replace(/\n$/, "")) } : {}
1577
1610
  });
1578
1611
  const fetchImpl = options.fetchImpl ?? fetch;
1579
1612
  let version = readState(dir)?.version ?? "unknown";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/installer-launcher",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Single-executable (SEA) launcher: sign-in, gated payload fetch, self-update. One copy ships per product.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "scripts": {
26
26
  "build": "node build.mjs",
27
- "test": "vitest run",
27
+ "test": "vitest run --no-file-parallelism",
28
28
  "typecheck": "tsc --noEmit"
29
29
  },
30
30
  "devDependencies": {