@mutmutco/installer-face 0.2.1 → 0.4.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # 0.4.1
2
+
3
+ - Capture bootstrap output and validate recorded installer transcripts.
4
+ - Preserve one final receipt across native bootstrap continuation.
5
+ - Use completed phase labels and retain transcript framing.
6
+
7
+ # 0.4.0
8
+
9
+ - Own maintenance ordering and outcomes from a validated product declaration.
10
+ - Share payload entry generation instead of embedding per-product installer scripts.
11
+ - Keep one console owner across nested installation and self-update handoff.
12
+ - Carry deferred and failed child outcomes across Windows shims.
13
+ - Record channel-tagged transcripts with authentication data excluded.
14
+ - Enforce completed-run conformance and preserve narrow-terminal diagnostics.
package/README.md CHANGED
@@ -15,26 +15,67 @@ Jerv-Hub wrote this renderer, MM-Strategy copied it, and the copy immediately fo
15
15
  breaking its own two-space rule. Four copies are four drifts waiting, and prose cannot stop drift;
16
16
  only shared code and a shared guard can.
17
17
 
18
- ## Use
18
+ ## Use the shared run
19
+
20
+ Product install/update entrypoints call `runMaintenance(declaration, args, engines)`.
21
+ The package owns phase ordering, self-update handoff, scheduler completion, outcome reporting and
22
+ cleanup. Product engines return version, surface and failure facts; they do not draw a run.
23
+ The declaration contains the canonical product key (`mmi` and `jerv` aliases are accepted), gate
24
+ URL, canonical doctor command, and a nonempty unique surface list
25
+ `{ id, npm?, bin?, kind?, activation? }`.
26
+
27
+ `createInstallerRun` is the lower-level owner used by the shared launcher, not a reason for each
28
+ product to author another installer. `renderPayloadEntry` generates the npm-install/converge entry
29
+ from tarball names and a declared convergence command. Product release scripts supply those facts.
30
+
31
+ The package owns greeting, phase names, spinner, surface status, receipt and sign-off.
32
+ `phase` accepts preflight, resolve, download, verify, install, activate, doctor, sign-in, check,
33
+ arm and verify-release. Its facts are state (running/ok/fail/note), seconds, measure and safe detail.
34
+ `surface` accepts updated/current/failed/skipped/retry/pending/kept. `finish` accepts version,
35
+ total, updated, failed and optional retry, dryRun, installed, deferred and safe detail.
36
+ Missing version or deferred work never reports ready. Counts must describe the same operation set.
37
+ Pass `dryRun: true` in options so individual rows also say “would update”.
38
+
39
+ `operation: "install"` selects the canonical first-install welcome.
40
+ `start()` and `finish()` are idempotent. `stop()` clears transient work. The orchestration wrapper
41
+ always stops the spinner, including when an operation throws; the caller retains error handling.
42
+ Use the manual lifecycle for a self-reexec that transfers final receipt ownership to its child.
43
+
44
+ `signIn({url, code})` owns device-login instructions. The code is visible in the terminal and
45
+ redacted in transcripts. `relay(text, channel)` and `milestone({step,state,ms})` accept only safe
46
+ child diagnostics: never pass authentication responses, credentials or raw provider errors.
47
+
48
+ TTY, color, width, environment, animation and output sink can be injected through options.
49
+ Non-TTY output is plain append-only text. NO_COLOR disables color, not the visual structure.
50
+ Nested processes suppress greetings and send their final outcome to the console owner.
51
+ The private outcome channel works through Windows command shims as well as direct child processes;
52
+ a successful process exit never turns a deferred result into readiness.
53
+ Animations never write milestones. `MM_FACE_TRANSCRIPT` appends JSONL `{channel,text}` records
54
+ for emitted output, preserving ANSI and stdout/stderr/spinner channels. Capture the owning outer
55
+ run for the console transcript. Untrusted external output is displayed but represented by an
56
+ omission marker in saved transcripts; authentication output cannot be a golden reference.
57
+ Transcripts are opt-in and must never contain secrets. A recorded fixture is not evidence of a
58
+ real signed-in advertised installer run; that acceptance remains a separate human check.
59
+
60
+ The lower-level `createFace` API remains for doctor rows and compatibility. New installer consumers
61
+ must not compose their own lifecycle from its primitives.
62
+
63
+ ### Self re-exec continuation
64
+
65
+ A process that re-execs itself onto an inherited console sets `MM_FACE_CONTINUES` to the
66
+ comma-separated phase names already drawn. `createFace` reads it directly: `welcome()` returns no
67
+ lines, and each `face.continues(phase, kind)` consumes one inherited phase. A failure always returns
68
+ false and consumes the inherited phase, so neither it nor a later retry can be hidden.
19
69
 
20
70
  ```ts
21
- import { createFace, assertFaceConformance } from '@mutmutco/installer-face';
22
-
23
- const face = createFace({
24
- product: 'jerv-hub',
25
- color: Boolean(process.stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== 'dumb'),
26
- columns: process.stdout.columns,
27
- });
28
-
29
- for (const line of face.welcome()) console.log(line);
30
- console.log(face.step('Registering MCP server', 2));
31
- console.log(face.relay(childOutput)); // another program's output, inside the rail
32
- for (const line of face.receipt([...])) console.log(line);
33
- console.log(face.signOff());
71
+ if (!face.continues('preflight', result.ok ? 'ok' : 'fail')) {
72
+ console.log(face.step('preflight', result.seconds, result.ok ? 'ok' : 'fail'));
73
+ }
34
74
  ```
35
75
 
36
- Colour is a DECISION the caller passes in, never sniffed here: the same face is used by processes
37
- whose stdout is a pipe, and a face drawn into a pipe is the bug this package exists to prevent.
76
+ The optional `env` input exists for tests; normal callers use `process.env`. Missing or empty means a
77
+ fresh face. Only an inheriting spawn sets the variable never a person starting a wrapped command
78
+ on a fresh console.
38
79
 
39
80
  ### Served one-liners
40
81
 
@@ -4,6 +4,14 @@ export interface ConformanceOptions {
4
4
  columns?: number;
5
5
  /** False asserts the PLAIN lane: no colour, and no glyphs or frames at all. */
6
6
  tty?: boolean;
7
+ /**
8
+ * True asserts the NESTED lane — a run under `MM_OUTER_CONSOLE=1`, where an outer program already
9
+ * opened the console and will close it. Steps and failures still print; a greeting, a success
10
+ * receipt or a sign-off from here is the duplicate face the owner saw on 2026-09-20 (#6893).
11
+ */
12
+ nested?: boolean;
13
+ /** Assert a completed human run, not a single renderer fragment. */
14
+ complete?: boolean;
7
15
  }
8
16
  /**
9
17
  * Assert a rendered surface against the contract. `lines` is what the surface actually printed,
package/dist/face.d.ts CHANGED
@@ -38,17 +38,48 @@ export declare function faceWidth(columns?: number): number;
38
38
  /** Break at word boundaries, hard-slicing a token longer than the line itself. */
39
39
  export declare function wrapWords(text: string, width: number): string[];
40
40
  export type StepKind = 'ok' | 'fail' | 'note';
41
+ /**
42
+ * What the RIGHT column carries (#6895, owner 2026-09-20).
43
+ *
44
+ * One column, one rule: progress while the work runs, elapsed time when it finishes. A number is
45
+ * measured seconds and renders as `2s`; a string is the surface's own measure and renders verbatim
46
+ * (`6.9 MB`, `3.1/6.9 MB`). Before this there were two visual languages — a right-aligned time for
47
+ * steps and a homegrown `[████░░] 42%` bar for downloads — and no ASCII bar returns with it.
48
+ */
49
+ export type StepMeasure = number | string | null;
50
+ export interface ReceiptOptions {
51
+ /**
52
+ * False when the run did NOT reach ready. A failure always prints, in every lane — suppressing it
53
+ * would hide a failed install behind someone else's box (#6810, #6858).
54
+ */
55
+ ready?: boolean;
56
+ }
41
57
  export interface Face {
42
58
  readonly identity: ProductIdentity;
43
59
  readonly width: number;
44
- /** `◆ <product> — Mutatis Mutandis` and the product's own warm line. */
60
+ /**
61
+ * True when an OUTER console owns this run's outcome (`MM_OUTER_CONSOLE=1`). Read it to skip work
62
+ * that only the console owner should do — animating a spinner, printing a summary of its own.
63
+ */
64
+ readonly nested: boolean;
65
+ /** True when finished steps leave as milestones on the parent's channel instead of being drawn. */
66
+ readonly emitsProgress: boolean;
67
+ /** `◆ <product> — Mutatis Mutandis` and the product's own warm line; empty on a continuation. */
45
68
  welcome(): string[];
46
- /** One durable line for one finished step: glyph, title, and a measured time when truthful. */
47
- step(title: string, seconds?: number | null, kind?: StepKind): string;
69
+ /** Consume one phase inherited through MM_FACE_CONTINUES; failures are always new information. */
70
+ continues(phase: string, kind?: StepKind): boolean;
71
+ /** One durable line for one finished step: glyph, title, and the right-column measure. */
72
+ step(title: string, measure?: StepMeasure, kind?: StepKind): string;
73
+ /**
74
+ * The canonical success receipt, composed from the product table: `✔ <name> is ready.`, the one
75
+ * fact the surface alone knows, and the table's own health command. A surface supplies ONLY what
76
+ * changed — the other two lines are never retyped at a call site (#6895).
77
+ */
78
+ outcome(changed: string, options?: ReceiptOptions): string[];
48
79
  /** Another program's output, kept inside the face at the step-title column. */
49
80
  relay(text: string): string;
50
- /** The closing box: what changed, and one suggested command. */
51
- receipt(lines: string[] | string): string[];
81
+ /** The closing box: what changed, and one suggested command. Empty when an outer console owns it. */
82
+ receipt(lines: string[] | string, options?: ReceiptOptions): string[];
52
83
  /** The sign-off, in the product accent. */
53
84
  signOff(): string;
54
85
  /** A refusal that reads as a face line rather than a bare error string. */
@@ -56,9 +87,12 @@ export interface Face {
56
87
  paint(sgr: string, text: string): string;
57
88
  }
58
89
  export interface FaceOptions {
90
+ operation?: 'install' | 'update';
59
91
  product: string;
60
92
  /** False emits no escape codes at all, so a piped or logged run is byte-stable plain text. */
61
93
  color?: boolean;
62
94
  columns?: number;
95
+ /** Injectable only so tests do not mutate process.env; normal callers inherit process.env. */
96
+ env?: Readonly<NodeJS.ProcessEnv>;
63
97
  }
64
- export declare function createFace({ product, color, columns }: FaceOptions): Face;
98
+ export declare function createFace({ product, color, columns, env, operation }: FaceOptions): Face;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { createFace, faceWidth, stripColor, visibleWidth, wrapWords, GLYPH, PALETTE, TITLE_COLUMN } from './face.js';
2
- export type { Face, FaceOptions, StepKind } from './face.js';
2
+ export type { Face, FaceOptions, ReceiptOptions, StepKind, StepMeasure } from './face.js';
3
3
  export { PRODUCTS, identityFor } from './products.js';
4
4
  export type { ProductIdentity } from './products.js';
5
5
  export { renderShellFace, renderPowerShellFace } from './shell.js';
@@ -9,3 +9,12 @@ export { createSpinner, SPINNER_FRAMES } from './spinner.js';
9
9
  export type { Spinner, SpinnerOptions } from './spinner.js';
10
10
  export { assertFaceConformance, assertScriptConformance } from './conformance.js';
11
11
  export type { ConformanceOptions, ScriptConformanceOptions } from './conformance.js';
12
+ export { createInstallerRun, validateInstallerProduct, runInstaller } from './run.js';
13
+ export type { InstallerProduct, InstallerRun, InstallerRunOptions, InstallerPhase, InstallerPhaseFacts, InstallerSurfaceFacts, InstallerFinish, InstallerChannel } from './run.js';
14
+ export { renderPayloadEntry } from './payload.js';
15
+ export type { PayloadEntryOptions } from './payload.js';
16
+ export { runMaintenance } from './maintenance.js';
17
+ export type { MaintenanceArm, MaintenanceSummary } from './maintenance.js';
18
+ export { validateInstallerOutcome, readInstallerOutcome, writeInstallerOutcome } from './outcome.js';
19
+ export type { InstallerOutcome } from './outcome.js';
20
+ export { assertInstallerTranscript } from './transcript.js';