@farmslot/recipe-cli 0.7.0 → 0.9.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 (101) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/harness/adapter-plugins.d.ts +1 -0
  3. package/dist/harness/adapter-plugins.d.ts.map +1 -1
  4. package/dist/harness/adapter-plugins.js +26 -13
  5. package/dist/harness/adapter-plugins.js.map +1 -1
  6. package/dist/harness/adapters.d.ts +0 -2
  7. package/dist/harness/adapters.d.ts.map +1 -1
  8. package/dist/harness/adapters.js +0 -4
  9. package/dist/harness/adapters.js.map +1 -1
  10. package/dist/harness/command-contract.d.ts +16 -1
  11. package/dist/harness/command-contract.d.ts.map +1 -1
  12. package/dist/harness/command-contract.js +8 -0
  13. package/dist/harness/command-contract.js.map +1 -1
  14. package/dist/harness/commands/call.d.ts.map +1 -1
  15. package/dist/harness/commands/call.js +30 -9
  16. package/dist/harness/commands/call.js.map +1 -1
  17. package/dist/harness/commands/checklist.d.ts +16 -0
  18. package/dist/harness/commands/checklist.d.ts.map +1 -0
  19. package/dist/harness/commands/checklist.js +171 -0
  20. package/dist/harness/commands/checklist.js.map +1 -0
  21. package/dist/harness/commands/doctor.d.ts +45 -0
  22. package/dist/harness/commands/doctor.d.ts.map +1 -0
  23. package/dist/harness/commands/doctor.js +591 -0
  24. package/dist/harness/commands/doctor.js.map +1 -0
  25. package/dist/harness/commands/execution-template.d.ts +2 -0
  26. package/dist/harness/commands/execution-template.d.ts.map +1 -0
  27. package/dist/harness/commands/execution-template.js +27 -0
  28. package/dist/harness/commands/execution-template.js.map +1 -0
  29. package/dist/harness/commands/launch.js +3 -0
  30. package/dist/harness/commands/launch.js.map +1 -1
  31. package/dist/harness/commands/prepare.d.ts +35 -0
  32. package/dist/harness/commands/prepare.d.ts.map +1 -0
  33. package/dist/harness/commands/prepare.js +570 -0
  34. package/dist/harness/commands/prepare.js.map +1 -0
  35. package/dist/harness/commands/recipe-quality.d.ts +13 -0
  36. package/dist/harness/commands/recipe-quality.d.ts.map +1 -0
  37. package/dist/harness/commands/recipe-quality.js +112 -0
  38. package/dist/harness/commands/recipe-quality.js.map +1 -0
  39. package/dist/harness/commands/run.d.ts.map +1 -1
  40. package/dist/harness/commands/run.js +13 -1
  41. package/dist/harness/commands/run.js.map +1 -1
  42. package/dist/harness/commands/status-watch.d.ts +20 -0
  43. package/dist/harness/commands/status-watch.d.ts.map +1 -0
  44. package/dist/harness/commands/status-watch.js +402 -0
  45. package/dist/harness/commands/status-watch.js.map +1 -0
  46. package/dist/harness/commands/status.d.ts +5 -0
  47. package/dist/harness/commands/status.d.ts.map +1 -0
  48. package/dist/harness/commands/status.js +167 -0
  49. package/dist/harness/commands/status.js.map +1 -0
  50. package/dist/harness/commands/task-init.d.ts +5 -0
  51. package/dist/harness/commands/task-init.d.ts.map +1 -0
  52. package/dist/harness/commands/task-init.js +54 -0
  53. package/dist/harness/commands/task-init.js.map +1 -0
  54. package/dist/harness/doctor-report.d.ts +58 -0
  55. package/dist/harness/doctor-report.d.ts.map +1 -0
  56. package/dist/harness/doctor-report.js +154 -0
  57. package/dist/harness/doctor-report.js.map +1 -0
  58. package/dist/harness/harness-cli.d.ts +10 -1
  59. package/dist/harness/harness-cli.d.ts.map +1 -1
  60. package/dist/harness/harness-cli.js +9 -9
  61. package/dist/harness/harness-cli.js.map +1 -1
  62. package/dist/harness/heal-bounds.d.ts +1 -1
  63. package/dist/harness/heal-bounds.d.ts.map +1 -1
  64. package/dist/harness/heal-bounds.js +33 -2
  65. package/dist/harness/heal-bounds.js.map +1 -1
  66. package/dist/harness/index.d.ts +15 -3
  67. package/dist/harness/index.d.ts.map +1 -1
  68. package/dist/harness/index.js +14 -2
  69. package/dist/harness/index.js.map +1 -1
  70. package/dist/harness/json-stream.d.ts +7 -1
  71. package/dist/harness/json-stream.d.ts.map +1 -1
  72. package/dist/harness/json-stream.js +12 -0
  73. package/dist/harness/json-stream.js.map +1 -1
  74. package/dist/harness/paths.d.ts +2 -0
  75. package/dist/harness/paths.d.ts.map +1 -1
  76. package/dist/harness/paths.js +2 -0
  77. package/dist/harness/paths.js.map +1 -1
  78. package/dist/harness/readiness.d.ts +31 -0
  79. package/dist/harness/readiness.d.ts.map +1 -0
  80. package/dist/harness/readiness.js +12 -0
  81. package/dist/harness/readiness.js.map +1 -0
  82. package/dist/harness/recording-target.d.ts +11 -0
  83. package/dist/harness/recording-target.d.ts.map +1 -1
  84. package/dist/harness/recording-target.js +31 -2
  85. package/dist/harness/recording-target.js.map +1 -1
  86. package/dist/harness/run-engine.d.ts.map +1 -1
  87. package/dist/harness/run-engine.js.map +1 -1
  88. package/dist/harness/run-options.js +2 -11
  89. package/dist/harness/run-options.js.map +1 -1
  90. package/dist/harness/shared.d.ts.map +1 -1
  91. package/dist/harness/shared.js +60 -44
  92. package/dist/harness/shared.js.map +1 -1
  93. package/dist/harness/stage-progress.d.ts +25 -0
  94. package/dist/harness/stage-progress.d.ts.map +1 -0
  95. package/dist/harness/stage-progress.js +162 -0
  96. package/dist/harness/stage-progress.js.map +1 -0
  97. package/dist/harness/task-view.d.ts +142 -0
  98. package/dist/harness/task-view.d.ts.map +1 -0
  99. package/dist/harness/task-view.js +451 -0
  100. package/dist/harness/task-view.js.map +1 -0
  101. package/package.json +2 -2
@@ -0,0 +1,27 @@
1
+ // execution-template — the host's entrypoint for the shared checklist catalog
2
+ // (@farmslot/agent-runtime).
3
+ import { spawnSync } from 'node:child_process';
4
+ import { createRequire } from 'node:module';
5
+ import { harnessHost } from '../host.js';
6
+ const require = createRequire(import.meta.url);
7
+ export async function handleExecutionTemplate(argv) {
8
+ const host = harnessHost().name;
9
+ let entrypoint;
10
+ try {
11
+ entrypoint = require.resolve('@farmslot/agent-runtime/scripts/execution-template-cli.mjs');
12
+ }
13
+ catch (error) {
14
+ console.error(`${host} execution-template: installed @farmslot/agent-runtime does not expose the catalog command: ${error instanceof Error ? error.message : String(error)}`);
15
+ return 1;
16
+ }
17
+ const result = spawnSync(process.execPath, [entrypoint, ...argv], {
18
+ env: process.env,
19
+ stdio: 'inherit',
20
+ });
21
+ if (result.error) {
22
+ console.error(`${host} execution-template: ${result.error.message}`);
23
+ return 1;
24
+ }
25
+ return result.status ?? 1;
26
+ }
27
+ //# sourceMappingURL=execution-template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-template.js","sourceRoot":"","sources":["../../../src/harness/commands/execution-template.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6BAA6B;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,IAAc;IAC1D,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC,IAAI,CAAC;IAChC,IAAI,UAAkB,CAAC;IACvB,IAAI,CAAC;QACH,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,4DAA4D,CAAC,CAAC;IAC7F,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CACX,GAAG,IAAI,+FACL,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,CACH,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,EAAE;QAChE,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,KAAK,EAAE,SAAS;KACjB,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,wBAAwB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACrE,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;AAC5B,CAAC"}
@@ -80,6 +80,9 @@ async function handleLaunchLocked(argv, stream) {
80
80
  return launchUsage(jsonOutput, stream, undetectedAdapterMessage(target), adapterDetectNext());
81
81
  }
82
82
  stream.phase('resolve', { target, adapter, platform: platformTarget ?? null });
83
+ // A heartbeat for every adapter; one that reports its own stages speaks
84
+ // instead. The command's `complete` ends it.
85
+ stream.stage('launch', { index: 1, total: 1 });
83
86
  return harnessAdapter(adapter).launch({
84
87
  adapter,
85
88
  target,
@@ -1 +1 @@
1
- {"version":3,"file":"launch.js","sourceRoot":"","sources":["../../../src/harness/commands/launch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"launch.js","sourceRoot":"","sources":["../../../src/harness/commands/launch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import { type RunnerProvenanceOptions } from '../doctor-report.js';
2
+ /** A step the host runs after launch (its fixture setup, say). */
3
+ export interface PrepareStep {
4
+ id: string;
5
+ command: string;
6
+ hint?: string;
7
+ argv(context: {
8
+ device: readonly string[];
9
+ forwarded: readonly string[];
10
+ targetArgs: readonly string[];
11
+ }): string[];
12
+ }
13
+ export interface PrepareCommandOptions {
14
+ usage: string;
15
+ steps?: readonly PrepareStep[];
16
+ launchHint?: string;
17
+ deviceTarget?: {
18
+ option: string;
19
+ flag: string;
20
+ choices: readonly string[];
21
+ };
22
+ clearMetroOnly?: string;
23
+ forward?: {
24
+ option: string;
25
+ flag: string;
26
+ validate?(value: string): void;
27
+ };
28
+ provenance?: RunnerProvenanceOptions;
29
+ }
30
+ export interface StatusDevice {
31
+ platform?: string;
32
+ selected?: boolean;
33
+ }
34
+ export declare function handlePrepare(argv: string[], commandOptions: PrepareCommandOptions): Promise<number>;
35
+ //# sourceMappingURL=prepare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepare.d.ts","sourceRoot":"","sources":["../../../src/harness/commands/prepare.ts"],"names":[],"mappings":"AAeA,OAAO,EAAoB,KAAK,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAUrF,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IAEX,OAAO,EAAE,MAAM,CAAC;IAEhB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,OAAO,EAAE;QACZ,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;QAC1B,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;QAC7B,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;KAC/B,GAAG,MAAM,EAAE,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IAEpC,KAAK,EAAE,MAAM,CAAC;IAEd,KAAK,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAE/B,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,YAAY,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAC;IAE5E,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,OAAO,CAAC,EAAE;QACR,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QAEb,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KAChC,CAAC;IACF,UAAU,CAAC,EAAE,uBAAuB,CAAC;CACtC;AAUD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAuYD,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,cAAc,EAAE,qBAAqB,GACpC,OAAO,CAAC,MAAM,CAAC,CAyBjB"}
@@ -0,0 +1,570 @@
1
+ // prepare — run the readiness chain for a checkout and record the outcome as
2
+ // the protocol readiness record. One file, one verdict: callers read
3
+ // sandbox.json instead of re-deriving readiness from scattered step output.
4
+ import { spawn } from 'node:child_process';
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { READINESS_RECORD } from '@farmslot/protocol';
8
+ import { OperationOutputTail } from '@farmslot/recipe-runner/runtime/operation';
9
+ import { detectAdapter, harnessAdapter, harnessAdapters } from '../adapters.js';
10
+ import { acquireCheckoutLock, trackCheckoutChild } from '../checkout-lock.js';
11
+ import { color, stripAnsi } from '../cli-color.js';
12
+ import { recordCommandStage } from '../command-journal.js';
13
+ import { runnerProvenance } from '../doctor-report.js';
14
+ import { harnessHost, hostEnvName } from '../host.js';
15
+ import { optionFlag, optionString, parseArgs, targetPath, usageError } from '../parse-args.js';
16
+ import { harnessExecutable, PREPARE_PROGRESS_ARTIFACT, recipeRuntimeDir } from '../paths.js';
17
+ import { adapterReadiness } from '../readiness.js';
18
+ import { EXIT } from '../shared.js';
19
+ import { createStageReporter, STAGE_LINE } from '../stage-progress.js';
20
+ import { formatDuration } from './status-watch.js';
21
+ const REPORT_DIR = 'prepare';
22
+ const SKIPPED_AFTER_FAILURE = 'previous step failed';
23
+ // A step's stage name says what it runs when that is more than its id.
24
+ const STAGE_NAME = { doctor: 'doctor --fix', launch: 'launch --verify' };
25
+ // Longer than any stage line: past it the pending text (`\r` progress, say)
26
+ // goes to the bounded tail rather than growing until a newline.
27
+ const PARTIAL_LINE_LIMIT = 8 * 1024;
28
+ function reportPathFor(id) {
29
+ return path.join(REPORT_DIR, `${id}.json`);
30
+ }
31
+ // A step's verdict comes from its exit code and its own JSON report, never from
32
+ // scraped console text: banners, warnings, and update notices are not state.
33
+ function reportedError(stdout) {
34
+ let parsed;
35
+ try {
36
+ parsed = JSON.parse(stdout ?? '');
37
+ }
38
+ catch {
39
+ // Not every command reports a structured error; the exit code still stands.
40
+ return undefined;
41
+ }
42
+ const message = parsed?.error?.message;
43
+ return typeof message === 'string' && message ? message : undefined;
44
+ }
45
+ function skippedStep(id, command, reason) {
46
+ return { id, command, status: 'skipped', exitCode: 0, reason, durationMs: 0 };
47
+ }
48
+ const STEP_MARK = {
49
+ pending: '·',
50
+ running: '…',
51
+ pass: '✓',
52
+ fail: '✗',
53
+ skipped: '–',
54
+ };
55
+ const STEP_STYLE = {
56
+ pending: 'dim',
57
+ running: 'accent',
58
+ pass: 'ok',
59
+ fail: 'err',
60
+ skipped: 'dim',
61
+ };
62
+ // Steps long enough that silence reads as a hang. The note is on the row from
63
+ // the moment the list is drawn, not from when the step starts: the point is to
64
+ // set the expectation before the wait, not to explain it once someone is already
65
+ // wondering. Fast steps carry nothing, so a note always means "this one is slow".
66
+ // (The host supplies them: PrepareCommandOptions.launchHint and each step's hint.)
67
+ // The chain the platform will actually run, known before the first step so the
68
+ // whole list can be shown from the start rather than revealed one line at a time.
69
+ function stepChain(headless, steps) {
70
+ return headless
71
+ ? ['doctor', 'status', 'verify']
72
+ : ['doctor', 'status', 'launch', ...steps.map((step) => step.id), 'verify'];
73
+ }
74
+ // What the last prepare of this checkout spent on each step. It is the only
75
+ // honest estimate the harness has, and the one a reader wants while waiting.
76
+ function lastRunDurations(recordPath) {
77
+ let parsed;
78
+ try {
79
+ parsed = JSON.parse(fs.readFileSync(recordPath, 'utf8'));
80
+ }
81
+ catch {
82
+ // No previous record, or not a JSON document: there is simply no estimate
83
+ // to offer, and the hints stand on their static text alone.
84
+ return {};
85
+ }
86
+ const steps = parsed?.steps;
87
+ if (!Array.isArray(steps))
88
+ return {};
89
+ const durations = {};
90
+ for (const step of steps) {
91
+ if (typeof step?.id === 'string' &&
92
+ typeof step.durationMs === 'number' &&
93
+ step.durationMs > 0) {
94
+ durations[step.id] = step.durationMs;
95
+ }
96
+ }
97
+ return durations;
98
+ }
99
+ function stepHints(chain, lastRun, staticHints) {
100
+ const hints = {};
101
+ for (const id of chain) {
102
+ const previous = lastRun[id];
103
+ const parts = [];
104
+ if (staticHints[id])
105
+ parts.push(staticHints[id]);
106
+ if (previous !== undefined)
107
+ parts.push(`last run ${formatDuration(previous)}`);
108
+ if (parts.length > 0)
109
+ hints[id] = parts.join(', ');
110
+ }
111
+ return hints;
112
+ }
113
+ function stepRowLine(row, width) {
114
+ const out = (style, text) => color(style, text, { stream: process.stdout });
115
+ const running = row.state === 'pending' || row.state === 'running';
116
+ const elapsed = row.state === 'running' && row.startedAt !== undefined
117
+ ? ` ${out('dim', formatDuration(Date.now() - row.startedAt))}`
118
+ : '';
119
+ // While a step is ahead of or under way, the parenthesis explains the wait;
120
+ // once it is over, the same slot carries what it actually took.
121
+ const note = running
122
+ ? (row.detail ?? row.hint)
123
+ : row.durationMs
124
+ ? formatDuration(row.durationMs)
125
+ : undefined;
126
+ const mark = out(STEP_STYLE[row.state], STEP_MARK[row.state]);
127
+ return (`${mark} ${out(running ? 'bold' : 'dim', row.id.padEnd(width))}: ${out(STEP_STYLE[row.state], row.state)}` +
128
+ `${elapsed}${row.reason ? ` — ${row.reason}` : ''}${note ? ` ${out('dim', `(${note})`)}` : ''}`);
129
+ }
130
+ // The whole chain as one block: pending steps up front so the wait is bounded,
131
+ // the running one counting up, the finished ones carrying their duration. On a
132
+ // terminal the block is rewritten in place; piped, it is a plain log.
133
+ function createStepView(chain, hints) {
134
+ const rows = new Map(chain.map((id) => [id, { id, state: 'pending', hint: hints[id] }]));
135
+ const width = Math.max(...chain.map((id) => id.length));
136
+ const tty = Boolean(process.stdout.isTTY);
137
+ // A row wider than the terminal wraps onto a second physical line, and the
138
+ // redraw counts logical lines — one wrapped hint and every later repaint lands
139
+ // in the wrong place. Piped output has no width to respect.
140
+ const block = () => {
141
+ // Only a width we actually know truncates anything. A pty can report 0 (or
142
+ // nothing at all) when no one is looking at it, and trimming to that blanks
143
+ // the whole block instead of fitting it.
144
+ const terminalColumns = process.stdout.columns;
145
+ const columns = tty && typeof terminalColumns === 'number' && terminalColumns > 0
146
+ ? terminalColumns
147
+ : Number.POSITIVE_INFINITY;
148
+ return [...rows.values()].map((row) => {
149
+ const line = stepRowLine(row, width);
150
+ // Slicing a styled string can cut an escape in half, so an over-wide row
151
+ // gives up its colour rather than its shape.
152
+ return stripAnsi(line).length <= columns ? line : stripAnsi(line).slice(0, columns);
153
+ });
154
+ };
155
+ let painted = 0;
156
+ let lastFrame = '';
157
+ let ended = false;
158
+ let timer;
159
+ const repaint = () => {
160
+ const lines = block();
161
+ const frame = lines.join('\n');
162
+ // Nothing moved since the last paint; repainting would only cost flicker.
163
+ if (frame === lastFrame)
164
+ return;
165
+ lastFrame = frame;
166
+ const up = painted > 0 ? `\x1b[${painted}A` : '';
167
+ painted = lines.length;
168
+ process.stdout.write(`${up}${lines.map((line) => `\x1b[2K${line}`).join('\n')}\n`);
169
+ };
170
+ const writeBlock = () => {
171
+ process.stdout.write(`${block().join('\n')}\n`);
172
+ };
173
+ return {
174
+ begin() {
175
+ if (tty)
176
+ repaint();
177
+ else
178
+ writeBlock();
179
+ },
180
+ start(id, startedAt) {
181
+ const row = rows.get(id);
182
+ if (!row)
183
+ return;
184
+ row.state = 'running';
185
+ row.startedAt = startedAt;
186
+ if (!tty)
187
+ return;
188
+ repaint();
189
+ // Ticked four times a second, not once: a busy moment in the event loop
190
+ // then costs a late repaint rather than a whole missed second, and the
191
+ // clock on screen never sits further behind the wall than a quarter of a
192
+ // second. repaint writes nothing when the rendered second has not changed.
193
+ //
194
+ // The timer is deliberately NOT unref'd. An unref'd timer does not hold
195
+ // the loop awake, so through a step that stays quiet for minutes — verify
196
+ // waiting on its probe — the loop sleeps and the clock on screen stops
197
+ // while the wall clock does not. end() always clears it, so a ref'd timer
198
+ // cannot outlive the run.
199
+ timer = setInterval(repaint, 250);
200
+ },
201
+ finish(step) {
202
+ clearInterval(timer);
203
+ timer = undefined;
204
+ const row = rows.get(step.id);
205
+ // Core records launch and fixtures as skipped without ever listing them:
206
+ // its chain is doctor, status, verify, and the record keeps the rest.
207
+ if (!row)
208
+ return;
209
+ row.state = step.status;
210
+ row.startedAt = undefined;
211
+ row.reason = step.reason;
212
+ row.durationMs = step.durationMs;
213
+ if (tty)
214
+ repaint();
215
+ else
216
+ process.stdout.write(`${stepRowLine(row, width)}\n`);
217
+ },
218
+ // Shown on the next tick of the live view.
219
+ detail(id, text) {
220
+ const row = rows.get(id);
221
+ if (row)
222
+ row.detail = text;
223
+ },
224
+ // Idempotent: the caller ends the view where the final block belongs in the
225
+ // output, and a `finally` ends it again on the paths that never got there.
226
+ // The ticker holds the event loop awake, so this must run either way.
227
+ end() {
228
+ if (ended)
229
+ return;
230
+ ended = true;
231
+ clearInterval(timer);
232
+ timer = undefined;
233
+ // The terminal already holds the finished block; piped output gets it once.
234
+ if (!tty)
235
+ writeBlock();
236
+ },
237
+ };
238
+ }
239
+ // Capture a step's streams the way spawnSync did — stdout for the report file,
240
+ // stderr held back for the failure path — while leaving the event loop free so
241
+ // the running line can tick during a step that takes minutes. The child's own
242
+ // stage lines also go to onStageLine as they arrive.
243
+ function spawnStep(executable, args, target, onStageLine) {
244
+ return new Promise((resolve) => {
245
+ const child = spawn(executable, [...args], {
246
+ cwd: target,
247
+ stdio: ['ignore', 'pipe', 'pipe'],
248
+ // The update nudge is an interactive courtesy; it has no place in the
249
+ // stream a recorded readiness step captures.
250
+ env: { ...process.env, [hostEnvName('NO_UPDATE_CHECK')]: '1' },
251
+ });
252
+ if (child.pid)
253
+ trackCheckoutChild(target, child.pid);
254
+ let stdout = '';
255
+ const stderr = new OperationOutputTail();
256
+ child.stdout?.setEncoding('utf8');
257
+ child.stderr?.setEncoding('utf8');
258
+ child.stdout?.on('data', (chunk) => {
259
+ stdout += chunk;
260
+ });
261
+ let partial = '';
262
+ child.stderr?.on('data', (chunk) => {
263
+ const lines = `${partial}${chunk}`.split('\n');
264
+ partial = lines.pop() ?? '';
265
+ for (const line of lines) {
266
+ if (onStageLine && STAGE_LINE.test(line))
267
+ onStageLine(line);
268
+ // Kept in the tail too: a child's last `failed` stage line may be the cause.
269
+ stderr.append(`${line}\n`);
270
+ }
271
+ // After the whole lines, so the tail keeps the order they came in.
272
+ if (partial.length > PARTIAL_LINE_LIMIT) {
273
+ stderr.append(partial);
274
+ partial = '';
275
+ }
276
+ });
277
+ child.on('error', (error) => resolve({ error, status: null, stdout, stderr: stderr.toString() }));
278
+ child.on('close', (status) => {
279
+ stderr.append(partial);
280
+ resolve({ status, stdout, stderr: stderr.toString() });
281
+ });
282
+ });
283
+ }
284
+ // Each step is the harness re-entering itself, so a task-local install prepares
285
+ // the checkout with the same executable that will later run the proof.
286
+ async function runStep(executable, target, artifactsDir, id, args, { json, onSpawn, onStageLine, }) {
287
+ const command = [executable, ...args].join(' ');
288
+ // One timestamp, taken at the spawn: the elapsed a reader watches tick and the
289
+ // durationMs the record keeps are then the same measurement, not two.
290
+ const startedAt = Date.now();
291
+ onSpawn?.(startedAt);
292
+ const result = await spawnStep(executable, args, target, onStageLine);
293
+ const durationMs = Date.now() - startedAt;
294
+ if (result.error) {
295
+ return { id, command, status: 'fail', exitCode: 1, reason: result.error.message, durationMs };
296
+ }
297
+ const report = reportPathFor(id);
298
+ const file = path.join(artifactsDir, report);
299
+ fs.mkdirSync(path.dirname(file), { recursive: true });
300
+ fs.writeFileSync(file, result.stdout);
301
+ const exitCode = result.status ?? 1;
302
+ if (exitCode === 0) {
303
+ return { id, command, status: 'pass', exitCode, reportPath: report, durationMs };
304
+ }
305
+ // The failing step's own diagnostics carry the recovery a human must act on;
306
+ // --json keeps stdout a single document and leaves them in the report file.
307
+ if (!json && result.stderr)
308
+ process.stderr.write(result.stderr);
309
+ return {
310
+ id,
311
+ command,
312
+ status: 'fail',
313
+ exitCode,
314
+ reportPath: report,
315
+ reason: reportedError(result.stdout) ?? `exited ${exitCode}`,
316
+ durationMs,
317
+ };
318
+ }
319
+ function readDevices(artifactsDir) {
320
+ const file = path.join(artifactsDir, reportPathFor('status'));
321
+ let parsed;
322
+ try {
323
+ parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
324
+ }
325
+ catch {
326
+ // A status report that is absent or not a JSON document simply carries no
327
+ // device list; the caller then reports the target as ambiguous.
328
+ return [];
329
+ }
330
+ const devices = parsed?.devices;
331
+ return Array.isArray(devices) ? devices : [];
332
+ }
333
+ function ambiguousTarget(json, error) {
334
+ const { code, message, userAction } = error;
335
+ if (json) {
336
+ console.log(JSON.stringify({
337
+ schemaVersion: 1,
338
+ command: 'prepare',
339
+ status: 'fail',
340
+ exitCode: EXIT.runtime,
341
+ error: { code, message, userAction },
342
+ }, null, 2));
343
+ }
344
+ else {
345
+ console.error(`✗ ${harnessHost().name} prepare: ${message}`);
346
+ console.error(` Next: ${userAction}`);
347
+ }
348
+ return EXIT.runtime;
349
+ }
350
+ function resolvePlatform(requested, target, usage) {
351
+ const platform = requested ?? detectAdapter(target);
352
+ const surface = platform && harnessAdapters().has(platform) ? harnessAdapter(platform) : undefined;
353
+ const prepare = surface ? adapterReadiness(surface).prepare : undefined;
354
+ if (surface && prepare)
355
+ return { surface, prepare };
356
+ throw usageError(`could not detect the platform of ${target}\n Next: ${usage}`);
357
+ }
358
+ export async function handlePrepare(argv, commandOptions) {
359
+ const { options } = parseArgs(argv);
360
+ const target = targetPath(options);
361
+ if (!fs.existsSync(target))
362
+ return handlePrepareLocked(argv, commandOptions);
363
+ const lock = acquireCheckoutLock(target, 'prepare');
364
+ if ('message' in lock) {
365
+ if (optionFlag(options, 'json'))
366
+ console.log(JSON.stringify({
367
+ status: 'fail',
368
+ error: { code: 'SANDBOX_BUSY', message: lock.message },
369
+ exitCode: EXIT.runtime,
370
+ }));
371
+ else
372
+ console.error(`${lock.message} Next: inspect the owning operation with ${harnessHost().name} status --watch`);
373
+ return EXIT.runtime;
374
+ }
375
+ try {
376
+ return await handlePrepareLocked(argv, commandOptions);
377
+ }
378
+ finally {
379
+ lock.release();
380
+ }
381
+ }
382
+ async function handlePrepareLocked(argv, commandOptions) {
383
+ const { options } = parseArgs(argv);
384
+ const json = optionFlag(options, 'json');
385
+ const target = targetPath(options);
386
+ const usage = commandOptions.usage;
387
+ const { surface, prepare } = resolvePlatform(optionString(options, 'platform'), target, usage);
388
+ const platform = surface.id;
389
+ const hostSteps = commandOptions.steps ?? [];
390
+ const artifactsDir = path.resolve(optionString(options, 'artifactsDir') ?? path.join(target, recipeRuntimeDir()));
391
+ const executable = harnessExecutable();
392
+ const targetArgs = ['--target', target];
393
+ // doctor teaches "--clear-metro if the failure persists", so the sanctioned
394
+ // prepare path has to be able to carry it. It stays an explicit operator
395
+ // decision: nothing here ever clears the transform cache on its own.
396
+ const clearMetro = optionFlag(options, 'clearMetro');
397
+ if (clearMetro && !prepare.clearMetro) {
398
+ throw usageError(`--clear-metro is ${commandOptions.clearMetroOnly ?? 'not supported here'}; this checkout is ${platform}\n Next: ${usage}`);
399
+ }
400
+ // Forwarded verbatim to launch and to the host steps, which own its grammar;
401
+ // the step record keeps the full command, so sandbox.json shows the value.
402
+ const forward = commandOptions.forward;
403
+ const forwardValue = forward ? optionString(options, forward.option) : undefined;
404
+ if (forward && forwardValue !== undefined) {
405
+ if (surface.headless) {
406
+ throw usageError(`${forward.flag} needs an app runtime; this checkout is ${platform}\n Next: ${usage}`);
407
+ }
408
+ forward.validate?.(forwardValue);
409
+ }
410
+ const forwarded = forward && forwardValue !== undefined ? [forward.flag, forwardValue] : [];
411
+ const deviceTarget = commandOptions.deviceTarget;
412
+ let devicePlatform = deviceTarget ? optionString(options, deviceTarget.option) : undefined;
413
+ if (deviceTarget &&
414
+ devicePlatform !== undefined &&
415
+ !deviceTarget.choices.includes(devicePlatform)) {
416
+ throw usageError(`${deviceTarget.flag} must be ${deviceTarget.choices.join(' or ')}; received '${devicePlatform}'\n Next: ${usage}`);
417
+ }
418
+ const recordPath = path.join(artifactsDir, READINESS_RECORD);
419
+ const progressPath = path.join(artifactsDir, PREPARE_PROGRESS_ARTIFACT);
420
+ // --json owns stdout as a single document, so it gets no step block.
421
+ const chain = stepChain(surface.headless, hostSteps);
422
+ const staticHints = {
423
+ ...(commandOptions.launchHint ? { launch: commandOptions.launchHint } : {}),
424
+ ...Object.fromEntries(hostSteps.flatMap((step) => (step.hint ? [[step.id, step.hint]] : []))),
425
+ };
426
+ const view = json
427
+ ? undefined
428
+ : createStepView(chain, stepHints(chain, lastRunDurations(recordPath), staticHints));
429
+ view?.begin();
430
+ // Stage lines on stderr, unless the live view repaints the same terminal
431
+ // stderr goes to: there the running row carries the step's latest stage line.
432
+ const stages = json || !process.stdout.isTTY || !process.stderr.isTTY ? createStageReporter() : undefined;
433
+ const steps = [];
434
+ let activeStep;
435
+ const startedAt = new Date().toISOString();
436
+ const writeProgress = () => {
437
+ fs.mkdirSync(path.dirname(progressPath), { recursive: true });
438
+ const temporaryPath = `${progressPath}.${process.pid}.tmp`;
439
+ fs.writeFileSync(temporaryPath, `${JSON.stringify({
440
+ schemaVersion: 1,
441
+ startedAt,
442
+ updatedAt: new Date().toISOString(),
443
+ total: chain.length,
444
+ steps: steps
445
+ .filter(({ id }) => chain.includes(id))
446
+ .map(({ id, status, durationMs }) => ({ id, status, durationMs })),
447
+ ...(activeStep ? { active: activeStep } : {}),
448
+ })}\n`);
449
+ fs.renameSync(temporaryPath, progressPath);
450
+ };
451
+ const failed = () => steps.some((step) => step.status === 'fail');
452
+ const addStep = (step) => {
453
+ steps.push(step);
454
+ activeStep = undefined;
455
+ writeProgress();
456
+ view?.finish(step);
457
+ return step;
458
+ };
459
+ const run = async (id, args) => {
460
+ if (failed()) {
461
+ return addStep(skippedStep(id, [executable, ...args].join(' '), SKIPPED_AFTER_FAILURE));
462
+ }
463
+ let stage;
464
+ const step = await runStep(executable, target, artifactsDir, id, args, {
465
+ json,
466
+ onSpawn: (stepStartedAt) => {
467
+ recordCommandStage(id);
468
+ activeStep = { id, startedAt: new Date(stepStartedAt).toISOString() };
469
+ writeProgress();
470
+ view?.start(id, stepStartedAt);
471
+ stage = stages?.stage(STAGE_NAME[id] ?? id, {
472
+ index: chain.indexOf(id) + 1,
473
+ total: chain.length,
474
+ });
475
+ },
476
+ onStageLine: (line) => {
477
+ stage?.forward(line);
478
+ view?.detail(id, line);
479
+ },
480
+ });
481
+ if (step.status === 'pass')
482
+ stage?.done();
483
+ else
484
+ stage?.failed(step.reason);
485
+ return addStep(step);
486
+ };
487
+ const heartbeat = setInterval(() => {
488
+ if (activeStep)
489
+ writeProgress();
490
+ }, 10_000);
491
+ // The ticker holds the event loop awake so it cannot miss a second, which
492
+ // makes ending the view mandatory on every way out of the chain — the early
493
+ // return below, or a step that throws on an unwritable report path.
494
+ try {
495
+ await run('doctor', ['doctor', '--fix', '--json', '--adapter', platform, ...targetArgs]);
496
+ await run('status', ['status', '--json', ...targetArgs]);
497
+ const devicePlatformFor = prepare.devicePlatform;
498
+ if (devicePlatformFor && !devicePlatform && !failed()) {
499
+ devicePlatform = devicePlatformFor(target, readDevices(artifactsDir));
500
+ if (!devicePlatform) {
501
+ return ambiguousTarget(json, prepare.ambiguousTarget?.() ?? {
502
+ code: 'PREPARE_DEVICE_TARGET_AMBIGUOUS',
503
+ message: 'could not infer the device target from the connected devices',
504
+ userAction: `choose the device target, then rerun this command: ${usage}`,
505
+ });
506
+ }
507
+ }
508
+ if (surface.headless) {
509
+ // A headless platform reads its files directly and verify is its only
510
+ // readiness gate.
511
+ const reason = `${platform} is headless: no app surface to launch or seed`;
512
+ addStep(skippedStep('launch', `${executable} launch`, reason));
513
+ for (const step of hostSteps)
514
+ addStep(skippedStep(step.id, `${executable} ${step.command}`, reason));
515
+ }
516
+ else {
517
+ // devicePlatform is set unless an earlier step already failed, in which
518
+ // case these steps are recorded as skipped and the device target is
519
+ // unknowable. A platform without devices records the option but launches none.
520
+ const device = devicePlatformFor && devicePlatform ? [devicePlatform] : [];
521
+ await run('launch', [
522
+ 'launch',
523
+ ...device,
524
+ ...(clearMetro ? ['--clear-metro'] : []),
525
+ ...forwarded,
526
+ '--verify',
527
+ '--json',
528
+ ...targetArgs,
529
+ ]);
530
+ for (const step of hostSteps)
531
+ await run(step.id, step.argv({ device, forwarded, targetArgs }));
532
+ }
533
+ await run('verify', ['verify', '--json', '--adapter', platform, ...targetArgs]);
534
+ }
535
+ finally {
536
+ clearInterval(heartbeat);
537
+ stages?.close('failed', 'prepare stopped');
538
+ view?.end();
539
+ }
540
+ const provenance = runnerProvenance(surface.actions.manifestPath(), commandOptions.provenance);
541
+ const record = {
542
+ schemaVersion: 1,
543
+ harness: {
544
+ name: provenance.packageName,
545
+ version: provenance.version,
546
+ source: `${provenance.installKind}/${provenance.packageSource}`,
547
+ executable: provenance.executablePath,
548
+ },
549
+ platform,
550
+ // The protocol's mobilePlatform is ios or android; another device target is
551
+ // not recorded.
552
+ ...(devicePlatform && (devicePlatform === 'ios' || devicePlatform === 'android')
553
+ ? { mobilePlatform: devicePlatform }
554
+ : {}),
555
+ steps,
556
+ ready: steps.every((step) => step.status !== 'fail'),
557
+ recordedAt: new Date().toISOString(),
558
+ };
559
+ fs.mkdirSync(artifactsDir, { recursive: true });
560
+ fs.writeFileSync(recordPath, `${JSON.stringify(record, null, 2)}\n`);
561
+ fs.rmSync(progressPath, { force: true });
562
+ if (json) {
563
+ console.log(JSON.stringify(record, null, 2));
564
+ }
565
+ else {
566
+ console.log(`readiness record: ${recordPath}`);
567
+ }
568
+ return record.ready ? EXIT.ok : EXIT.runtime;
569
+ }
570
+ //# sourceMappingURL=prepare.js.map