@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.
- package/CHANGELOG.md +29 -0
- package/dist/harness/adapter-plugins.d.ts +1 -0
- package/dist/harness/adapter-plugins.d.ts.map +1 -1
- package/dist/harness/adapter-plugins.js +26 -13
- package/dist/harness/adapter-plugins.js.map +1 -1
- package/dist/harness/adapters.d.ts +0 -2
- package/dist/harness/adapters.d.ts.map +1 -1
- package/dist/harness/adapters.js +0 -4
- package/dist/harness/adapters.js.map +1 -1
- package/dist/harness/command-contract.d.ts +16 -1
- package/dist/harness/command-contract.d.ts.map +1 -1
- package/dist/harness/command-contract.js +8 -0
- package/dist/harness/command-contract.js.map +1 -1
- package/dist/harness/commands/call.d.ts.map +1 -1
- package/dist/harness/commands/call.js +30 -9
- package/dist/harness/commands/call.js.map +1 -1
- package/dist/harness/commands/checklist.d.ts +16 -0
- package/dist/harness/commands/checklist.d.ts.map +1 -0
- package/dist/harness/commands/checklist.js +171 -0
- package/dist/harness/commands/checklist.js.map +1 -0
- package/dist/harness/commands/doctor.d.ts +45 -0
- package/dist/harness/commands/doctor.d.ts.map +1 -0
- package/dist/harness/commands/doctor.js +591 -0
- package/dist/harness/commands/doctor.js.map +1 -0
- package/dist/harness/commands/execution-template.d.ts +2 -0
- package/dist/harness/commands/execution-template.d.ts.map +1 -0
- package/dist/harness/commands/execution-template.js +27 -0
- package/dist/harness/commands/execution-template.js.map +1 -0
- package/dist/harness/commands/launch.js +3 -0
- package/dist/harness/commands/launch.js.map +1 -1
- package/dist/harness/commands/prepare.d.ts +35 -0
- package/dist/harness/commands/prepare.d.ts.map +1 -0
- package/dist/harness/commands/prepare.js +570 -0
- package/dist/harness/commands/prepare.js.map +1 -0
- package/dist/harness/commands/recipe-quality.d.ts +13 -0
- package/dist/harness/commands/recipe-quality.d.ts.map +1 -0
- package/dist/harness/commands/recipe-quality.js +112 -0
- package/dist/harness/commands/recipe-quality.js.map +1 -0
- package/dist/harness/commands/run.d.ts.map +1 -1
- package/dist/harness/commands/run.js +13 -1
- package/dist/harness/commands/run.js.map +1 -1
- package/dist/harness/commands/status-watch.d.ts +20 -0
- package/dist/harness/commands/status-watch.d.ts.map +1 -0
- package/dist/harness/commands/status-watch.js +402 -0
- package/dist/harness/commands/status-watch.js.map +1 -0
- package/dist/harness/commands/status.d.ts +5 -0
- package/dist/harness/commands/status.d.ts.map +1 -0
- package/dist/harness/commands/status.js +167 -0
- package/dist/harness/commands/status.js.map +1 -0
- package/dist/harness/commands/task-init.d.ts +5 -0
- package/dist/harness/commands/task-init.d.ts.map +1 -0
- package/dist/harness/commands/task-init.js +54 -0
- package/dist/harness/commands/task-init.js.map +1 -0
- package/dist/harness/doctor-report.d.ts +58 -0
- package/dist/harness/doctor-report.d.ts.map +1 -0
- package/dist/harness/doctor-report.js +154 -0
- package/dist/harness/doctor-report.js.map +1 -0
- package/dist/harness/harness-cli.d.ts +10 -1
- package/dist/harness/harness-cli.d.ts.map +1 -1
- package/dist/harness/harness-cli.js +9 -9
- package/dist/harness/harness-cli.js.map +1 -1
- package/dist/harness/heal-bounds.d.ts +1 -1
- package/dist/harness/heal-bounds.d.ts.map +1 -1
- package/dist/harness/heal-bounds.js +33 -2
- package/dist/harness/heal-bounds.js.map +1 -1
- package/dist/harness/index.d.ts +15 -3
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +14 -2
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/json-stream.d.ts +7 -1
- package/dist/harness/json-stream.d.ts.map +1 -1
- package/dist/harness/json-stream.js +12 -0
- package/dist/harness/json-stream.js.map +1 -1
- package/dist/harness/paths.d.ts +2 -0
- package/dist/harness/paths.d.ts.map +1 -1
- package/dist/harness/paths.js +2 -0
- package/dist/harness/paths.js.map +1 -1
- package/dist/harness/readiness.d.ts +31 -0
- package/dist/harness/readiness.d.ts.map +1 -0
- package/dist/harness/readiness.js +12 -0
- package/dist/harness/readiness.js.map +1 -0
- package/dist/harness/recording-target.d.ts +11 -0
- package/dist/harness/recording-target.d.ts.map +1 -1
- package/dist/harness/recording-target.js +31 -2
- package/dist/harness/recording-target.js.map +1 -1
- package/dist/harness/run-engine.d.ts.map +1 -1
- package/dist/harness/run-engine.js.map +1 -1
- package/dist/harness/run-options.js +2 -11
- package/dist/harness/run-options.js.map +1 -1
- package/dist/harness/shared.d.ts.map +1 -1
- package/dist/harness/shared.js +60 -44
- package/dist/harness/shared.js.map +1 -1
- package/dist/harness/stage-progress.d.ts +25 -0
- package/dist/harness/stage-progress.d.ts.map +1 -0
- package/dist/harness/stage-progress.js +162 -0
- package/dist/harness/stage-progress.js.map +1 -0
- package/dist/harness/task-view.d.ts +142 -0
- package/dist/harness/task-view.d.ts.map +1 -0
- package/dist/harness/task-view.js +451 -0
- package/dist/harness/task-view.js.map +1 -0
- 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
|