@hominis/fireforge 0.36.0 → 0.38.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 +26 -0
- package/README.md +1 -1
- package/dist/src/commands/build.js +6 -6
- package/dist/src/commands/config.js +1 -0
- package/dist/src/commands/doctor-orphaned-harness.d.ts +52 -0
- package/dist/src/commands/doctor-orphaned-harness.js +132 -0
- package/dist/src/commands/doctor.js +2 -0
- package/dist/src/commands/download.js +5 -2
- package/dist/src/commands/export-flow.d.ts +5 -1
- package/dist/src/commands/export-flow.js +10 -7
- package/dist/src/commands/export-placement-gate.js +3 -5
- package/dist/src/commands/export-placement-policy.d.ts +11 -3
- package/dist/src/commands/export-placement-policy.js +32 -29
- package/dist/src/commands/export.js +12 -5
- package/dist/src/commands/furnace/index.js +23 -18
- package/dist/src/commands/patch/move-files-create.d.ts +19 -0
- package/dist/src/commands/patch/move-files-create.js +116 -0
- package/dist/src/commands/patch/move-files.d.ts +3 -1
- package/dist/src/commands/patch/move-files.js +49 -4
- package/dist/src/commands/patch/split.d.ts +11 -1
- package/dist/src/commands/patch/split.js +6 -5
- package/dist/src/commands/patch/staged-dependency.d.ts +1 -1
- package/dist/src/commands/patch/staged-dependency.js +113 -51
- package/dist/src/commands/re-export-files.js +4 -0
- package/dist/src/commands/re-export-scan.d.ts +17 -0
- package/dist/src/commands/re-export-scan.js +51 -0
- package/dist/src/commands/re-export.js +25 -6
- package/dist/src/commands/source.js +28 -3
- package/dist/src/commands/test-diagnose.js +20 -1
- package/dist/src/commands/test-register.js +6 -5
- package/dist/src/commands/test-stale-gate.d.ts +44 -0
- package/dist/src/commands/test-stale-gate.js +81 -0
- package/dist/src/commands/test.js +40 -23
- package/dist/src/core/build-audit.d.ts +12 -0
- package/dist/src/core/build-audit.js +14 -0
- package/dist/src/core/build-baseline-types.d.ts +58 -0
- package/dist/src/core/build-baseline.d.ts +10 -2
- package/dist/src/core/build-baseline.js +51 -7
- package/dist/src/core/config-paths.d.ts +1 -1
- package/dist/src/core/config-paths.js +1 -0
- package/dist/src/core/config-validate.js +6 -1
- package/dist/src/core/engine-session-lock.d.ts +10 -1
- package/dist/src/core/engine-session-lock.js +28 -2
- package/dist/src/core/file-lock.d.ts +33 -0
- package/dist/src/core/file-lock.js +32 -6
- package/dist/src/core/firefox-archive.d.ts +2 -1
- package/dist/src/core/firefox-archive.js +32 -8
- package/dist/src/core/firefox.d.ts +8 -3
- package/dist/src/core/firefox.js +11 -6
- package/dist/src/core/furnace-config-custom.js +10 -0
- package/dist/src/core/furnace-stale-export.d.ts +59 -0
- package/dist/src/core/furnace-stale-export.js +123 -0
- package/dist/src/core/furnace-validate-compatibility.js +9 -2
- package/dist/src/core/furnace-validate-helpers.js +28 -1
- package/dist/src/core/furnace-validate-structure.js +3 -2
- package/dist/src/core/license-headers.d.ts +12 -0
- package/dist/src/core/license-headers.js +76 -1
- package/dist/src/core/mach-known-noise-filter.d.ts +60 -0
- package/dist/src/core/mach-known-noise-filter.js +193 -0
- package/dist/src/core/mach.d.ts +8 -0
- package/dist/src/core/mach.js +36 -3
- package/dist/src/core/patch-lint-cross.js +80 -2
- package/dist/src/core/patch-lint.d.ts +3 -0
- package/dist/src/core/patch-lint.js +25 -7
- package/dist/src/core/patch-manifest-io.d.ts +3 -2
- package/dist/src/core/patch-manifest-io.js +27 -16
- package/dist/src/core/patch-manifest-validate.js +24 -0
- package/dist/src/core/status-classify.d.ts +11 -2
- package/dist/src/core/status-classify.js +49 -25
- package/dist/src/core/test-harness-crash.d.ts +16 -0
- package/dist/src/core/test-harness-crash.js +55 -0
- package/dist/src/core/test-stale-check.d.ts +69 -1
- package/dist/src/core/test-stale-check.js +134 -1
- package/dist/src/types/commands/index.d.ts +1 -1
- package/dist/src/types/commands/options.d.ts +84 -7
- package/dist/src/types/commands/patches.d.ts +29 -0
- package/dist/src/types/config.d.ts +7 -0
- package/dist/src/types/furnace.d.ts +13 -0
- package/dist/src/utils/options.d.ts +20 -0
- package/dist/src/utils/options.js +39 -0
- package/dist/src/utils/process-group.d.ts +33 -0
- package/dist/src/utils/process-group.js +161 -0
- package/dist/src/utils/process.d.ts +15 -0
- package/dist/src/utils/process.js +88 -44
- package/dist/src/utils/validation.d.ts +5 -0
- package/dist/src/utils/validation.js +7 -0
- package/package.json +3 -3
|
@@ -294,6 +294,58 @@ function realUnexpectedFailureLines(output) {
|
|
|
294
294
|
}
|
|
295
295
|
return [...new Set(matches.map((line) => line.trim()))].filter((line) => !SHUTDOWN_REENTRY_PATTERN.test(line));
|
|
296
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Non-global copies of the unexpected/assertion patterns for per-line use —
|
|
299
|
+
* the `g`-flagged module patterns carry `lastIndex` state across `.test()`
|
|
300
|
+
* calls and must never be reused line-by-line.
|
|
301
|
+
*/
|
|
302
|
+
const UNEXPECTED_LINE_SINGLE = /\bTEST-UNEXPECTED-[A-Z-]+\b/;
|
|
303
|
+
/**
|
|
304
|
+
* Context lines the chrome/xpcshell harnesses print directly under a
|
|
305
|
+
* TEST-UNEXPECTED line: `Got …` / `Expected …` / `Actual …`, Assert
|
|
306
|
+
* messages, diff markers, stack-trace heads, and indented continuation
|
|
307
|
+
* lines.
|
|
308
|
+
*/
|
|
309
|
+
const FAILURE_CONTEXT_LINE_PATTERN = /^(?:\s*(?:Got\b|Expected\b|Actual\b|Assert\w*\b|Stack trace:|[-+]\s)|\s{2,}\S)/;
|
|
310
|
+
/** Cap on context lines echoed under a single TEST-UNEXPECTED line. */
|
|
311
|
+
const FAILURE_BLOCK_CONTEXT_LIMIT = 6;
|
|
312
|
+
/**
|
|
313
|
+
* Collects the first `limit` TEST-UNEXPECTED-* lines with their trailing
|
|
314
|
+
* assertion/diff context, one string per block. The shutdown-reentry
|
|
315
|
+
* artifact is excluded (it is harness noise, not a test result). When more
|
|
316
|
+
* than `limit` blocks exist, a final `…(+N more …)` note is appended so the
|
|
317
|
+
* operator knows the echo was truncated. Pure; exported for unit tests.
|
|
318
|
+
*/
|
|
319
|
+
export function collectUnexpectedFailureBlocks(output, limit = 5) {
|
|
320
|
+
const lines = output.split(/\r?\n/);
|
|
321
|
+
const blocks = [];
|
|
322
|
+
let skipped = 0;
|
|
323
|
+
for (let i = 0; i < lines.length; i++) {
|
|
324
|
+
const line = lines[i] ?? '';
|
|
325
|
+
if (!UNEXPECTED_LINE_SINGLE.test(line) || SHUTDOWN_REENTRY_PATTERN.test(line))
|
|
326
|
+
continue;
|
|
327
|
+
if (blocks.length >= limit) {
|
|
328
|
+
skipped++;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const block = [line.trim()];
|
|
332
|
+
let contextCount = 0;
|
|
333
|
+
for (let j = i + 1; j < lines.length && contextCount < FAILURE_BLOCK_CONTEXT_LIMIT; j++) {
|
|
334
|
+
const next = lines[j] ?? '';
|
|
335
|
+
if (UNEXPECTED_LINE_SINGLE.test(next))
|
|
336
|
+
break;
|
|
337
|
+
if (!FAILURE_CONTEXT_LINE_PATTERN.test(next))
|
|
338
|
+
break;
|
|
339
|
+
block.push(next.trimEnd());
|
|
340
|
+
contextCount++;
|
|
341
|
+
}
|
|
342
|
+
blocks.push(block.join('\n'));
|
|
343
|
+
}
|
|
344
|
+
if (skipped > 0) {
|
|
345
|
+
blocks.push(`…(+${skipped} more TEST-UNEXPECTED line${skipped === 1 ? '' : 's'} not shown)`);
|
|
346
|
+
}
|
|
347
|
+
return blocks;
|
|
348
|
+
}
|
|
297
349
|
function detectSecondaryHarnessNoise(output) {
|
|
298
350
|
const evidence = stripNonSignalNoise(output);
|
|
299
351
|
if (TRACEBACK_PATTERN.test(evidence)) {
|
|
@@ -397,6 +449,7 @@ export function detectHarnessCrashSignature(output) {
|
|
|
397
449
|
export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
398
450
|
const realFailures = realUnexpectedFailureLines(output);
|
|
399
451
|
const firstRealFailure = realFailures[0];
|
|
452
|
+
const failureBlocks = collectUnexpectedFailureBlocks(output);
|
|
400
453
|
const secondaryHarnessSignature = firstRealFailure !== undefined ? detectSecondaryHarnessNoise(output) : undefined;
|
|
401
454
|
const signature = detectHarnessCrashSignature(output);
|
|
402
455
|
if (signature) {
|
|
@@ -431,6 +484,7 @@ export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
|
431
484
|
return {
|
|
432
485
|
kind: 'test-failures',
|
|
433
486
|
...(firstRealFailure !== undefined ? { realFailureLine: firstRealFailure } : {}),
|
|
487
|
+
...(failureBlocks.length > 0 ? { realFailureBlocks: failureBlocks } : {}),
|
|
434
488
|
...(secondaryHarnessSignature !== undefined ? { secondaryHarnessSignature } : {}),
|
|
435
489
|
greenSummaryRejected: {
|
|
436
490
|
...(crashLine !== undefined ? { crashLine } : {}),
|
|
@@ -444,6 +498,7 @@ export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
|
444
498
|
return {
|
|
445
499
|
kind: 'test-failures',
|
|
446
500
|
...(firstRealFailure !== undefined ? { realFailureLine: firstRealFailure } : {}),
|
|
501
|
+
...(failureBlocks.length > 0 ? { realFailureBlocks: failureBlocks } : {}),
|
|
447
502
|
...(secondaryHarnessSignature !== undefined ? { secondaryHarnessSignature } : {}),
|
|
448
503
|
};
|
|
449
504
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { isXpcomManifestPath } from './build-audit.js';
|
|
2
|
+
import type { BuildBaseline, TestPackagingCoverage } from './build-baseline-types.js';
|
|
3
|
+
export { isXpcomManifestPath };
|
|
2
4
|
/** Result of the stale-build preflight probe. */
|
|
3
5
|
export interface StaleBuildResult {
|
|
4
6
|
/** True when at least one packageable engine file changed since the baseline. */
|
|
@@ -40,3 +42,69 @@ export declare function checkStaleBuildForTest(projectRoot: string, engineDir: s
|
|
|
40
42
|
* result without matching the rendered copy.
|
|
41
43
|
*/
|
|
42
44
|
export declare function formatStaleBuildWarning(result: StaleBuildResult): string;
|
|
45
|
+
/**
|
|
46
|
+
* Sentinel returned by {@link findUncoveredRequestPaths} when a full-suite
|
|
47
|
+
* request (no paths) is checked against a scoped coverage record.
|
|
48
|
+
*/
|
|
49
|
+
export declare const FULL_SUITE_REQUEST = "(entire suite)";
|
|
50
|
+
/**
|
|
51
|
+
* Compares the requested test paths against the packaged runtime's coverage
|
|
52
|
+
* claim recorded in the baseline. Returns the requested paths the recorded
|
|
53
|
+
* packaging does NOT cover — the runs that would dispatch against missing
|
|
54
|
+
* `_tests/` support fixtures and hang rather than fail.
|
|
55
|
+
*
|
|
56
|
+
* Coverage semantics: `undefined` (pre-0.37.0 baseline) and `'full'` cover
|
|
57
|
+
* everything. A scoped list covers a request path when the request equals a
|
|
58
|
+
* covered entry, sits beneath a covered directory entry, or shares a
|
|
59
|
+
* manifest granule with a covered entry ({@link toManifestGranule} — a
|
|
60
|
+
* scoped `test --build` packages the whole manifest directory, so a
|
|
61
|
+
* same-manifest sibling of a covered file is packaged too). Both sides are
|
|
62
|
+
* normalized to forward slashes so Windows-style CLI input cannot defeat
|
|
63
|
+
* the prefix rule (baseline paths are POSIX by convention). A request with
|
|
64
|
+
* no paths is a full-suite run and is never covered by a scoped list — the
|
|
65
|
+
* {@link FULL_SUITE_REQUEST} sentinel is returned so the refusal can name it.
|
|
66
|
+
*/
|
|
67
|
+
export declare function findUncoveredRequestPaths(coverage: TestPackagingCoverage | undefined, requestedPaths: readonly string[]): string[];
|
|
68
|
+
/**
|
|
69
|
+
* Formats the refusal shown when a non-`--build` run requests paths the
|
|
70
|
+
* packaged runtime's scoped `test --build` coverage does not include —
|
|
71
|
+
* enforced on every such run, with or without `--allow-stale-build`. Kept
|
|
72
|
+
* separate from the matcher so tests can pin structure and copy
|
|
73
|
+
* independently (same split as {@link formatStaleBuildWarning}).
|
|
74
|
+
*/
|
|
75
|
+
export declare function formatTestCoverageRefusal(uncovered: string[], coverage: string[]): string;
|
|
76
|
+
/** Result of the compiled-StaticComponents staleness probe. */
|
|
77
|
+
export interface StaticComponentsStaleResult {
|
|
78
|
+
/** True when at least one `components.conf` genuinely diverged from the anchor. */
|
|
79
|
+
stale: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Engine-relative `components.conf` paths changed since the last FULL
|
|
82
|
+
* build. Sorted; NOT capped — {@link formatStaticComponentsRefusal}
|
|
83
|
+
* applies the render cap.
|
|
84
|
+
*/
|
|
85
|
+
changedManifests: string[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Probes whether any `components.conf` changed since the last FULL
|
|
89
|
+
* `fireforge build` — i.e. since the compiled StaticComponents table was
|
|
90
|
+
* last regenerated. `components.conf` entries bake into compiled code; a
|
|
91
|
+
* scoped `test --build` packages the file but the child process resolves
|
|
92
|
+
* the OLD table and fails with `NS_ERROR_MALFORMED_URI` that reads as a
|
|
93
|
+
* test bug.
|
|
94
|
+
*
|
|
95
|
+
* The diff anchors to the baseline's `staticComponentsBaseline` (the last
|
|
96
|
+
* full build's engine HEAD SHA), NOT the baseline's own `engineHeadSha`
|
|
97
|
+
* which a scoped `test --build` advances. Dirty candidates are hash-checked
|
|
98
|
+
* against the anchor's fingerprints so only genuine content divergence
|
|
99
|
+
* counts. No baseline / no anchor (pre-0.38.0 marker) → fresh. Never
|
|
100
|
+
* throws — the probes it composes degrade to verbose lines and empty
|
|
101
|
+
* results on git failure, matching {@link checkStaleBuildForTest}.
|
|
102
|
+
*/
|
|
103
|
+
export declare function checkStaticComponentsStale(engineDir: string, baseline: BuildBaseline | undefined): Promise<StaticComponentsStaleResult>;
|
|
104
|
+
/**
|
|
105
|
+
* Formats the refusal shown when a run would dispatch against a stale
|
|
106
|
+
* compiled StaticComponents table. Same probe/copy split as
|
|
107
|
+
* {@link formatStaleBuildWarning} so tests can pin structure and wording
|
|
108
|
+
* independently.
|
|
109
|
+
*/
|
|
110
|
+
export declare function formatStaticComponentsRefusal(changedManifests: string[]): string;
|
|
@@ -28,9 +28,10 @@ import { readFile } from 'node:fs/promises';
|
|
|
28
28
|
import { join } from 'node:path';
|
|
29
29
|
import { toError } from '../utils/errors.js';
|
|
30
30
|
import { verbose } from '../utils/logger.js';
|
|
31
|
-
import { isPackageablePath } from './build-audit.js';
|
|
31
|
+
import { isPackageablePath, isXpcomManifestPath } from './build-audit.js';
|
|
32
32
|
import { readBuildBaseline } from './build-baseline.js';
|
|
33
33
|
import { collectChangedEnginePaths } from './engine-changes.js';
|
|
34
|
+
export { isXpcomManifestPath };
|
|
34
35
|
/** Cap on the number of changed paths rendered inline. */
|
|
35
36
|
const STALE_PATHS_LIMIT = 10;
|
|
36
37
|
/**
|
|
@@ -112,3 +113,135 @@ export function formatStaleBuildWarning(result) {
|
|
|
112
113
|
'packaged chrome / jar.mn resources, rerun with "fireforge test --build" (or ' +
|
|
113
114
|
'"fireforge build --ui") first. Passing --build skips this check.');
|
|
114
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Sentinel returned by {@link findUncoveredRequestPaths} when a full-suite
|
|
118
|
+
* request (no paths) is checked against a scoped coverage record.
|
|
119
|
+
*/
|
|
120
|
+
export const FULL_SUITE_REQUEST = '(entire suite)';
|
|
121
|
+
/**
|
|
122
|
+
* Compares the requested test paths against the packaged runtime's coverage
|
|
123
|
+
* claim recorded in the baseline. Returns the requested paths the recorded
|
|
124
|
+
* packaging does NOT cover — the runs that would dispatch against missing
|
|
125
|
+
* `_tests/` support fixtures and hang rather than fail.
|
|
126
|
+
*
|
|
127
|
+
* Coverage semantics: `undefined` (pre-0.37.0 baseline) and `'full'` cover
|
|
128
|
+
* everything. A scoped list covers a request path when the request equals a
|
|
129
|
+
* covered entry, sits beneath a covered directory entry, or shares a
|
|
130
|
+
* manifest granule with a covered entry ({@link toManifestGranule} — a
|
|
131
|
+
* scoped `test --build` packages the whole manifest directory, so a
|
|
132
|
+
* same-manifest sibling of a covered file is packaged too). Both sides are
|
|
133
|
+
* normalized to forward slashes so Windows-style CLI input cannot defeat
|
|
134
|
+
* the prefix rule (baseline paths are POSIX by convention). A request with
|
|
135
|
+
* no paths is a full-suite run and is never covered by a scoped list — the
|
|
136
|
+
* {@link FULL_SUITE_REQUEST} sentinel is returned so the refusal can name it.
|
|
137
|
+
*/
|
|
138
|
+
export function findUncoveredRequestPaths(coverage, requestedPaths) {
|
|
139
|
+
if (coverage === undefined || coverage === 'full') {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
const covered = coverage.map(normalizeCoveragePath).filter((p) => p.length > 0);
|
|
143
|
+
if (requestedPaths.length === 0) {
|
|
144
|
+
return [FULL_SUITE_REQUEST];
|
|
145
|
+
}
|
|
146
|
+
return requestedPaths.filter((requested) => {
|
|
147
|
+
const path = normalizeCoveragePath(requested);
|
|
148
|
+
const granule = toManifestGranule(path);
|
|
149
|
+
return !covered.some((c) => path === c || path.startsWith(`${c}/`) || granule === toManifestGranule(c));
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Maps a normalized request/coverage path to the "manifest granule" the
|
|
154
|
+
* packaged runtime actually staged: an extension-bearing basename (a test
|
|
155
|
+
* FILE) maps to its containing directory, a directory (no dot in the
|
|
156
|
+
* basename) maps to itself. Purely lexical — the directory-as-manifest
|
|
157
|
+
* approximation holds because xpcshell/mochitest manifests live next to
|
|
158
|
+
* their test files and a scoped `test --build` stages the whole manifest
|
|
159
|
+
* directory into `obj-*`/`_tests/`, not single files. Caveat: a DIRECTORY
|
|
160
|
+
* whose basename contains a dot is misread as a file and mapped to its
|
|
161
|
+
* parent, which widens (never narrows) the covered granule.
|
|
162
|
+
*/
|
|
163
|
+
function toManifestGranule(path) {
|
|
164
|
+
const slash = path.lastIndexOf('/');
|
|
165
|
+
const base = slash === -1 ? path : path.slice(slash + 1);
|
|
166
|
+
if (!base.includes('.')) {
|
|
167
|
+
return path;
|
|
168
|
+
}
|
|
169
|
+
return slash === -1 ? '' : path.slice(0, slash);
|
|
170
|
+
}
|
|
171
|
+
/** Normalizes a path for coverage comparison: forward slashes, no trailing slash. */
|
|
172
|
+
function normalizeCoveragePath(path) {
|
|
173
|
+
return path.trim().replace(/\\/g, '/').replace(/\/+$/, '');
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Formats the refusal shown when a non-`--build` run requests paths the
|
|
177
|
+
* packaged runtime's scoped `test --build` coverage does not include —
|
|
178
|
+
* enforced on every such run, with or without `--allow-stale-build`. Kept
|
|
179
|
+
* separate from the matcher so tests can pin structure and copy
|
|
180
|
+
* independently (same split as {@link formatStaleBuildWarning}).
|
|
181
|
+
*/
|
|
182
|
+
export function formatTestCoverageRefusal(uncovered, coverage) {
|
|
183
|
+
const cap = (paths) => {
|
|
184
|
+
const head = paths.slice(0, STALE_PATHS_LIMIT);
|
|
185
|
+
const truncated = paths.length - head.length;
|
|
186
|
+
return head.join(', ') + (truncated > 0 ? `, … (+${truncated} more)` : '');
|
|
187
|
+
};
|
|
188
|
+
const rebuildTargets = uncovered.filter((p) => p !== FULL_SUITE_REQUEST);
|
|
189
|
+
const rebuildHint = rebuildTargets.length > 0
|
|
190
|
+
? `Rerun "fireforge test --build ${rebuildTargets.slice(0, STALE_PATHS_LIMIT).join(' ')}" to package them, or run "fireforge build" for full coverage.`
|
|
191
|
+
: 'Run "fireforge build" (or a path-less "fireforge test --build") for full coverage first.';
|
|
192
|
+
return (`The packaged test runtime was produced by a scoped "fireforge test --build" covering only: ${cap(coverage)}.\n` +
|
|
193
|
+
`The requested run needs ${cap(uncovered)}, which that packaging does not cover — support ` +
|
|
194
|
+
'fixtures for those manifests may be missing from obj-*/_tests/, and the run can hang ' +
|
|
195
|
+
`rather than fail. ${rebuildHint} ` +
|
|
196
|
+
'(--allow-stale-build does not bypass this check — it accepts stale content, not missing coverage.)');
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Probes whether any `components.conf` changed since the last FULL
|
|
200
|
+
* `fireforge build` — i.e. since the compiled StaticComponents table was
|
|
201
|
+
* last regenerated. `components.conf` entries bake into compiled code; a
|
|
202
|
+
* scoped `test --build` packages the file but the child process resolves
|
|
203
|
+
* the OLD table and fails with `NS_ERROR_MALFORMED_URI` that reads as a
|
|
204
|
+
* test bug.
|
|
205
|
+
*
|
|
206
|
+
* The diff anchors to the baseline's `staticComponentsBaseline` (the last
|
|
207
|
+
* full build's engine HEAD SHA), NOT the baseline's own `engineHeadSha`
|
|
208
|
+
* which a scoped `test --build` advances. Dirty candidates are hash-checked
|
|
209
|
+
* against the anchor's fingerprints so only genuine content divergence
|
|
210
|
+
* counts. No baseline / no anchor (pre-0.38.0 marker) → fresh. Never
|
|
211
|
+
* throws — the probes it composes degrade to verbose lines and empty
|
|
212
|
+
* results on git failure, matching {@link checkStaleBuildForTest}.
|
|
213
|
+
*/
|
|
214
|
+
export async function checkStaticComponentsStale(engineDir, baseline) {
|
|
215
|
+
const anchor = baseline?.staticComponentsBaseline;
|
|
216
|
+
if (baseline === undefined || anchor === undefined) {
|
|
217
|
+
return { stale: false, changedManifests: [] };
|
|
218
|
+
}
|
|
219
|
+
const changed = await collectChangedEnginePaths(engineDir, { ...baseline, engineHeadSha: anchor.engineHeadSha }, 'Static-components preflight');
|
|
220
|
+
const changedManifests = [];
|
|
221
|
+
for (const path of changed.filter((p) => isXpcomManifestPath(p))) {
|
|
222
|
+
const recorded = anchor.fingerprints[path];
|
|
223
|
+
const live = await hashEngineFile(engineDir, path);
|
|
224
|
+
if (recorded === undefined || live === undefined || recorded !== live) {
|
|
225
|
+
changedManifests.push(path);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return { stale: changedManifests.length > 0, changedManifests };
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Formats the refusal shown when a run would dispatch against a stale
|
|
232
|
+
* compiled StaticComponents table. Same probe/copy split as
|
|
233
|
+
* {@link formatStaleBuildWarning} so tests can pin structure and wording
|
|
234
|
+
* independently.
|
|
235
|
+
*/
|
|
236
|
+
export function formatStaticComponentsRefusal(changedManifests) {
|
|
237
|
+
const head = changedManifests.slice(0, STALE_PATHS_LIMIT);
|
|
238
|
+
const truncated = changedManifests.length - head.length;
|
|
239
|
+
const list = head.join(', ') + (truncated > 0 ? `, … (+${truncated} more)` : '');
|
|
240
|
+
return (`The compiled StaticComponents table is stale: ${list} changed since the last full "fireforge build".\n` +
|
|
241
|
+
'components.conf registrations are baked into compiled code that only a FULL build ' +
|
|
242
|
+
'regenerates — a scoped "fireforge test --build" repackages files but the child process ' +
|
|
243
|
+
'resolves the old component table and fails with NS_ERROR_MALFORMED_URI that reads as a ' +
|
|
244
|
+
'test bug. Run "fireforge build" first. (--allow-stale-build does not bypass this check — ' +
|
|
245
|
+
'it accepts stale packaged content, not a stale compiled registration. Pass ' +
|
|
246
|
+
'--allow-stale-components only if you rebuilt out-of-band and accept the risk.)');
|
|
247
|
+
}
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Re-exports all command-related types from focused sub-modules.
|
|
3
3
|
*/
|
|
4
4
|
export type { BuildOptions, DiscardOptions, DoctorOptions, DownloadOptions, ExportOptions, FurnaceApplyOptions, FurnaceCreateOptions, FurnaceDeployOptions, FurnaceOverrideOptions, FurnacePreviewOptions, FurnaceRefreshOptions, FurnaceRemoveOptions, FurnaceSyncOptions, FurnaceValidateOptions, GlobalOptions, ImportOptions, LintCommandOptions, PackageOptions, PatchCompactOptions, PatchDeleteOptions, PatchLintIgnoreOptions, PatchMoveFilesOptions, PatchRenameOptions, PatchReorderOptions, PatchSplitOptions, PatchStagedDependencyOptions, PatchTierOptions, RebaseOptions, ReExportOptions, RegisterOptions, ResetOptions, RunOptions, SetupOptions, SourceSetOptions, StatusOptions, TestOptions, TokenAddOptions, WireOptions, } from './options.js';
|
|
5
|
-
export type { ImportSummary, PatchCategory, PatchesManifest, PatchInfo, PatchLintIssue, PatchMetadata, PatchResult, PatchStagedDependencies, PatchStagedForwardImport, } from './patches.js';
|
|
5
|
+
export type { ImportSummary, PatchCategory, PatchesManifest, PatchInfo, PatchLintIssue, PatchMetadata, PatchResult, PatchStagedDependencies, PatchStagedForwardImport, PatchStagedRegistration, } from './patches.js';
|
|
6
6
|
export type { DoctorCheck, ProjectStatus, TokenCoverageFileEntry, TokenCoverageReport, } from './project.js';
|
|
@@ -43,6 +43,10 @@ export interface SourceSetOptions {
|
|
|
43
43
|
sha256?: string;
|
|
44
44
|
/** Clear any existing pinned SHA-256 */
|
|
45
45
|
clearSha256?: boolean;
|
|
46
|
+
/** Optional release-candidate build directory (e.g. "build2") */
|
|
47
|
+
candidate?: string;
|
|
48
|
+
/** Clear any existing release-candidate build directory */
|
|
49
|
+
clearCandidate?: boolean;
|
|
46
50
|
}
|
|
47
51
|
/**
|
|
48
52
|
* Options for the build command.
|
|
@@ -62,6 +66,12 @@ export interface BuildOptions {
|
|
|
62
66
|
* for any mismatch the rewriter cannot prove safe.
|
|
63
67
|
*/
|
|
64
68
|
rewriteMozinfo?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Parsed `--wait-lock [seconds]` value (`true` for the bare flag, meaning
|
|
71
|
+
* 60). Consumed at the CLI layer to bound the engine-session-lock wait;
|
|
72
|
+
* the command implementation ignores it.
|
|
73
|
+
*/
|
|
74
|
+
waitLock?: number | boolean;
|
|
65
75
|
}
|
|
66
76
|
/**
|
|
67
77
|
* Options for the export command.
|
|
@@ -99,6 +109,12 @@ export interface ExportOptions {
|
|
|
99
109
|
forceUnsafe?: boolean;
|
|
100
110
|
/** Exclude furnace-managed file paths from the export. */
|
|
101
111
|
excludeFurnace?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Export the deployed engine copy even when the `components/` source
|
|
114
|
+
* changed since the last furnace apply. Without this flag the export is
|
|
115
|
+
* refused so a stale deployed copy cannot silently land in the patch.
|
|
116
|
+
*/
|
|
117
|
+
allowStaleFurnace?: boolean;
|
|
102
118
|
/**
|
|
103
119
|
* Acknowledge that the export will create cross-patch ownership overlap
|
|
104
120
|
* with existing non-superseded patches. Without this flag, `export`
|
|
@@ -106,6 +122,12 @@ export interface ExportOptions {
|
|
|
106
122
|
* another patch, because the resulting queue fails `verify` immediately.
|
|
107
123
|
*/
|
|
108
124
|
allowOverlap?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Parsed `--wait-lock [seconds]` value (`true` for the bare flag, meaning
|
|
127
|
+
* 60). Consumed at the CLI layer to bound the engine-session-lock wait;
|
|
128
|
+
* the command implementation ignores it.
|
|
129
|
+
*/
|
|
130
|
+
waitLock?: number | boolean;
|
|
109
131
|
/**
|
|
110
132
|
* Force a tier override on the new patch's `PatchMetadata.tier`. Only
|
|
111
133
|
* `"branding"` is currently recognised — Commander rejects other values
|
|
@@ -210,6 +232,12 @@ export interface ReExportOptions {
|
|
|
210
232
|
* before the interactive/`--yes` confirmation path.
|
|
211
233
|
*/
|
|
212
234
|
allowShrink?: boolean;
|
|
235
|
+
/**
|
|
236
|
+
* Export the deployed engine copy even when the `components/` source
|
|
237
|
+
* changed since the last furnace apply. Without this flag the re-export
|
|
238
|
+
* is refused so a stale deployed copy cannot silently land in the patch.
|
|
239
|
+
*/
|
|
240
|
+
allowStaleFurnace?: boolean;
|
|
213
241
|
/** Bypass cross-patch lint refusal on projected shrink state */
|
|
214
242
|
forceUnsafe?: boolean;
|
|
215
243
|
/**
|
|
@@ -239,6 +267,12 @@ export interface ReExportOptions {
|
|
|
239
267
|
* patch to also suppress X").
|
|
240
268
|
*/
|
|
241
269
|
lintIgnore?: string[];
|
|
270
|
+
/**
|
|
271
|
+
* Parsed `--wait-lock [seconds]` value (`true` for the bare flag, meaning
|
|
272
|
+
* 60). Consumed at the CLI layer to bound the engine-session-lock wait;
|
|
273
|
+
* the command implementation ignores it.
|
|
274
|
+
*/
|
|
275
|
+
waitLock?: number | boolean;
|
|
242
276
|
}
|
|
243
277
|
/**
|
|
244
278
|
* Options for the `fireforge patch tier` subcommand. Sets or clears the
|
|
@@ -278,16 +312,27 @@ export interface PatchLintIgnoreOptions {
|
|
|
278
312
|
* declarations, or clear all staged dependencies from the patch.
|
|
279
313
|
*/
|
|
280
314
|
export interface PatchStagedDependencyOptions {
|
|
281
|
-
/** Add a
|
|
315
|
+
/** Add a staged dependency declaration. */
|
|
282
316
|
add?: boolean;
|
|
283
|
-
/** Remove matching
|
|
317
|
+
/** Remove matching staged dependency declarations. */
|
|
284
318
|
remove?: boolean;
|
|
285
319
|
/** Drop the stagedDependencies field entirely. */
|
|
286
320
|
clear?: boolean;
|
|
287
|
-
/**
|
|
321
|
+
/**
|
|
322
|
+
* Declaration shape: `import` (forward import, the default when unset) or
|
|
323
|
+
* `registration` (jar.mn packaging line, customElements or actor
|
|
324
|
+
* registration). Registration entries use `line` instead of `specifier`.
|
|
325
|
+
*/
|
|
326
|
+
kind?: string;
|
|
327
|
+
/** Declaring file path relative to engine/. */
|
|
288
328
|
file?: string;
|
|
289
|
-
/** Exact import specifier as it appears in source. */
|
|
329
|
+
/** Exact import specifier as it appears in source (`kind: import`). */
|
|
290
330
|
specifier?: string;
|
|
331
|
+
/**
|
|
332
|
+
* Registration/packaging line as the patch adds it, compared
|
|
333
|
+
* whitespace-trimmed (`kind: registration`).
|
|
334
|
+
*/
|
|
335
|
+
line?: string;
|
|
291
336
|
/** Later-created file path relative to engine/. */
|
|
292
337
|
creates?: string;
|
|
293
338
|
/** Optional exact patch filename expected to create `creates`. */
|
|
@@ -327,13 +372,31 @@ export interface PatchSplitOptions {
|
|
|
327
372
|
skipLint?: boolean;
|
|
328
373
|
}
|
|
329
374
|
/**
|
|
330
|
-
* Options for the
|
|
331
|
-
*
|
|
332
|
-
* `re-export --files` commands needed to perform it.
|
|
375
|
+
* Options for the `fireforge patch move-files` subcommand. Without
|
|
376
|
+
* `--create` it is preview-only: it validates an ownership transfer and
|
|
377
|
+
* prints the explicit `re-export --files` commands needed to perform it.
|
|
378
|
+
* With `--create --order <n>` the target patch is created at that order
|
|
379
|
+
* and the files move into it as one split-style transaction.
|
|
333
380
|
*/
|
|
334
381
|
export interface PatchMoveFilesOptions {
|
|
335
382
|
/** File paths relative to engine/ to move from the source patch to the target patch. */
|
|
336
383
|
file?: string[];
|
|
384
|
+
/** Create the target patch (transactional bootstrap of a split). Requires `order`. */
|
|
385
|
+
create?: boolean;
|
|
386
|
+
/** Exact sparse order for the created patch. Only valid with `create`. */
|
|
387
|
+
order?: number;
|
|
388
|
+
/** Category for the created patch; defaults to the source patch's. */
|
|
389
|
+
category?: string;
|
|
390
|
+
/** Description for the created patch. */
|
|
391
|
+
description?: string;
|
|
392
|
+
/** Preview the create+move without writing. */
|
|
393
|
+
dryRun?: boolean;
|
|
394
|
+
/** Skip interactive confirmation (required for non-TTY). */
|
|
395
|
+
yes?: boolean;
|
|
396
|
+
/** Bypass projected-lint refusals. */
|
|
397
|
+
forceUnsafe?: boolean;
|
|
398
|
+
/** Skip per-patch lint of the projected bodies. */
|
|
399
|
+
skipLint?: boolean;
|
|
337
400
|
}
|
|
338
401
|
/**
|
|
339
402
|
* Options for the rebase command.
|
|
@@ -434,6 +497,14 @@ export interface TestOptions {
|
|
|
434
497
|
killStaleMarionette?: boolean;
|
|
435
498
|
/** Permit tests against packageable engine edits newer than the last successful build. */
|
|
436
499
|
allowStaleBuild?: boolean;
|
|
500
|
+
/**
|
|
501
|
+
* Permit tests despite `components.conf` changes that only a full
|
|
502
|
+
* `fireforge build` compiles into the StaticComponents table — the
|
|
503
|
+
* packaged child process will resolve the OLD table. For operators who
|
|
504
|
+
* rebuilt out-of-band and accept the risk; distinct from
|
|
505
|
+
* `allowStaleBuild`, which only accepts stale packaged content.
|
|
506
|
+
*/
|
|
507
|
+
allowStaleComponents?: boolean;
|
|
437
508
|
/** Run the configured short harness canary. `true` means use `fireforge.json`'s test.canaryPath. */
|
|
438
509
|
canary?: string | boolean;
|
|
439
510
|
/**
|
|
@@ -464,6 +535,12 @@ export interface TestOptions {
|
|
|
464
535
|
* checkers that consume a sample artifact after the run.
|
|
465
536
|
*/
|
|
466
537
|
perfSamples?: string;
|
|
538
|
+
/**
|
|
539
|
+
* Parsed `--wait-lock [seconds]` value (`true` for the bare flag, meaning
|
|
540
|
+
* 60). Consumed at the CLI layer to bound the engine-session-lock wait;
|
|
541
|
+
* the command implementation ignores it.
|
|
542
|
+
*/
|
|
543
|
+
waitLock?: number | boolean;
|
|
467
544
|
}
|
|
468
545
|
/**
|
|
469
546
|
* Options for the furnace apply command.
|
|
@@ -127,6 +127,15 @@ export interface PatchMetadata {
|
|
|
127
127
|
export interface PatchStagedDependencies {
|
|
128
128
|
/** Exact forward-import declarations allowed for this patch. */
|
|
129
129
|
forwardImports?: PatchStagedForwardImport[];
|
|
130
|
+
/**
|
|
131
|
+
* Registration-shaped forward dependencies: packaging or registration
|
|
132
|
+
* LINES (a jar.mn entry, a customElements registration, an actor
|
|
133
|
+
* registration) this patch adds that reference a file a later patch
|
|
134
|
+
* creates. Unlike `forwardImports`, these are validated by matching the
|
|
135
|
+
* declared line against the patch's added content, not by finding an
|
|
136
|
+
* import specifier — packaging lines have no import to match.
|
|
137
|
+
*/
|
|
138
|
+
registrations?: PatchStagedRegistration[];
|
|
130
139
|
}
|
|
131
140
|
/** A single intentional forward import to a later-created file. */
|
|
132
141
|
export interface PatchStagedForwardImport {
|
|
@@ -141,6 +150,26 @@ export interface PatchStagedForwardImport {
|
|
|
141
150
|
/** Optional human-readable rationale for the staged dependency. */
|
|
142
151
|
reason?: string;
|
|
143
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* A single intentional registration/packaging line referencing a
|
|
155
|
+
* later-created file.
|
|
156
|
+
*/
|
|
157
|
+
export interface PatchStagedRegistration {
|
|
158
|
+
/** Declaring file path relative to engine/ (e.g. `toolkit/content/jar.mn`). */
|
|
159
|
+
file: string;
|
|
160
|
+
/**
|
|
161
|
+
* The registration/packaging line exactly as the patch adds it. Compared
|
|
162
|
+
* whitespace-trimmed against the patch's added lines in `file`, so
|
|
163
|
+
* indentation differences do not break the match.
|
|
164
|
+
*/
|
|
165
|
+
line: string;
|
|
166
|
+
/** Later-created file path relative to engine/. */
|
|
167
|
+
creates: string;
|
|
168
|
+
/** Optional exact patch filename expected to create `creates`. */
|
|
169
|
+
owner?: string;
|
|
170
|
+
/** Optional human-readable rationale for the staged dependency. */
|
|
171
|
+
reason?: string;
|
|
172
|
+
}
|
|
144
173
|
/**
|
|
145
174
|
* Schema for patches/patches.json file.
|
|
146
175
|
*/
|
|
@@ -12,6 +12,13 @@ export interface FirefoxConfig {
|
|
|
12
12
|
product: FirefoxProduct;
|
|
13
13
|
/** Optional pinned SHA-256 for the resolved source archive */
|
|
14
14
|
sha256?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Optional release-candidate build directory (e.g. "build2"). When set,
|
|
17
|
+
* the source archive resolves under
|
|
18
|
+
* `pub/<product>/candidates/<version>-candidates/<candidate>/` instead of
|
|
19
|
+
* `pub/<product>/releases/<version>/`, for pre-release verification.
|
|
20
|
+
*/
|
|
21
|
+
candidate?: string;
|
|
15
22
|
}
|
|
16
23
|
/**
|
|
17
24
|
* Supported project license SPDX identifiers.
|
|
@@ -46,6 +46,19 @@ export interface CustomComponentConfig {
|
|
|
46
46
|
targetPath: string;
|
|
47
47
|
/** Whether to register in customElements.js */
|
|
48
48
|
register: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* What the component IS. `'element'` (the default when unset) is a custom
|
|
51
|
+
* element with its own tag. `'library'` is a base class + helpers module
|
|
52
|
+
* that defines no element of its own (e.g. a shared MozLitElement
|
|
53
|
+
* subclass other components extend): `furnace validate` skips the
|
|
54
|
+
* `no-custom-element-define` / `not-moz-lit-element` compatibility checks
|
|
55
|
+
* and the `missing-css` structural advisory for it, while headers, module
|
|
56
|
+
* shape, `relative-import`, and (when a `.css` exists) the CSS
|
|
57
|
+
* compatibility rules still apply. A library exports no element, so
|
|
58
|
+
* `kind: "library"` requires `register: false` — the combination with
|
|
59
|
+
* `register: true` is rejected at config parse time.
|
|
60
|
+
*/
|
|
61
|
+
kind?: 'element' | 'library';
|
|
49
62
|
/** Whether this component uses Fluent l10n */
|
|
50
63
|
localized: boolean;
|
|
51
64
|
/** Stock component tag names composed internally by this component */
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
export declare function pickDefined<T extends Record<string, unknown>>(obj: T): {
|
|
7
7
|
[K in keyof T]+?: Exclude<T[K], undefined>;
|
|
8
8
|
};
|
|
9
|
+
import type { Command } from 'commander';
|
|
9
10
|
/**
|
|
10
11
|
* Wraps an option-argument parser so its failures surface through
|
|
11
12
|
* commander's own invalid-argument channel.
|
|
@@ -21,3 +22,22 @@ export declare function pickDefined<T extends Record<string, unknown>>(obj: T):
|
|
|
21
22
|
* helper (or throw commander's InvalidArgumentError directly).
|
|
22
23
|
*/
|
|
23
24
|
export declare function commanderArgParser<T>(parse: (raw: string) => T): (raw: string) => T;
|
|
25
|
+
/**
|
|
26
|
+
* Resolves the parsed `--wait-lock [seconds]` option value into a wait budget
|
|
27
|
+
* in seconds for `withEngineSessionLock`:
|
|
28
|
+
* - absent (`undefined`) → `undefined` (legacy ~1 s fail-fast),
|
|
29
|
+
* - bare flag (`true`) → {@link DEFAULT_WAIT_LOCK_SECONDS},
|
|
30
|
+
* - explicit value → integer validated into 1..3600.
|
|
31
|
+
*
|
|
32
|
+
* Accepts the raw string too, so it doubles as the option's arg parser (wrap
|
|
33
|
+
* with {@link commanderArgParser} so failures surface through commander's
|
|
34
|
+
* invalid-argument channel).
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveWaitLockSeconds(value: string | number | boolean | undefined): number | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Registers the shared `--wait-lock [seconds]` flag on an engine-mutating
|
|
39
|
+
* command. The parsed option value is `true` for the bare flag or a validated
|
|
40
|
+
* integer; feed it through {@link resolveWaitLockSeconds} at the call site to
|
|
41
|
+
* obtain the `waitLockSeconds` for `withEngineSessionLock`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function addWaitLockOption(command: Command): Command;
|
|
@@ -14,6 +14,7 @@ export function pickDefined(obj) {
|
|
|
14
14
|
return result;
|
|
15
15
|
}
|
|
16
16
|
import { InvalidArgumentError as CommanderInvalidArgumentError } from 'commander';
|
|
17
|
+
import { GeneralError } from '../errors/base.js';
|
|
17
18
|
/**
|
|
18
19
|
* Wraps an option-argument parser so its failures surface through
|
|
19
20
|
* commander's own invalid-argument channel.
|
|
@@ -38,3 +39,41 @@ export function commanderArgParser(parse) {
|
|
|
38
39
|
}
|
|
39
40
|
};
|
|
40
41
|
}
|
|
42
|
+
/** Wait budget applied when `--wait-lock` is passed without a value. */
|
|
43
|
+
const DEFAULT_WAIT_LOCK_SECONDS = 60;
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the parsed `--wait-lock [seconds]` option value into a wait budget
|
|
46
|
+
* in seconds for `withEngineSessionLock`:
|
|
47
|
+
* - absent (`undefined`) → `undefined` (legacy ~1 s fail-fast),
|
|
48
|
+
* - bare flag (`true`) → {@link DEFAULT_WAIT_LOCK_SECONDS},
|
|
49
|
+
* - explicit value → integer validated into 1..3600.
|
|
50
|
+
*
|
|
51
|
+
* Accepts the raw string too, so it doubles as the option's arg parser (wrap
|
|
52
|
+
* with {@link commanderArgParser} so failures surface through commander's
|
|
53
|
+
* invalid-argument channel).
|
|
54
|
+
*/
|
|
55
|
+
export function resolveWaitLockSeconds(value) {
|
|
56
|
+
if (value === undefined || value === false) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
if (value === true) {
|
|
60
|
+
return DEFAULT_WAIT_LOCK_SECONDS;
|
|
61
|
+
}
|
|
62
|
+
if (typeof value === 'number') {
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
const n = Number.parseInt(value, 10);
|
|
66
|
+
if (!Number.isFinite(n) || n < 1 || n > 3600) {
|
|
67
|
+
throw new GeneralError(`--wait-lock must be an integer in 1..3600 (got "${value}")`);
|
|
68
|
+
}
|
|
69
|
+
return n;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Registers the shared `--wait-lock [seconds]` flag on an engine-mutating
|
|
73
|
+
* command. The parsed option value is `true` for the bare flag or a validated
|
|
74
|
+
* integer; feed it through {@link resolveWaitLockSeconds} at the call site to
|
|
75
|
+
* obtain the `waitLockSeconds` for `withEngineSessionLock`.
|
|
76
|
+
*/
|
|
77
|
+
export function addWaitLockOption(command) {
|
|
78
|
+
return command.option('--wait-lock [seconds]', 'Wait up to this many seconds (default 60) for another FireForge engine-mutating command to release the engine lock, instead of failing immediately', commanderArgParser((raw) => resolveWaitLockSeconds(raw)));
|
|
79
|
+
}
|