@hominis/fireforge 0.41.0 → 0.43.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 +81 -34
- package/dist/build-info.json +4 -4
- package/dist/src/cli.js +21 -2
- package/dist/src/commands/build.js +7 -1
- package/dist/src/commands/config.js +16 -27
- package/dist/src/commands/discard.js +1 -1
- package/dist/src/commands/export-flow.js +13 -3
- package/dist/src/commands/export-placement-conflicts.js +1 -1
- package/dist/src/commands/export-shared.d.ts +1 -1
- package/dist/src/commands/export-shared.js +4 -5
- package/dist/src/commands/furnace/chrome-doc-remove.js +3 -0
- package/dist/src/commands/furnace/chrome-doc.js +3 -0
- package/dist/src/commands/furnace/create.js +3 -0
- package/dist/src/commands/furnace/override.js +3 -0
- package/dist/src/commands/furnace/preview.js +3 -0
- package/dist/src/commands/furnace/refresh.js +3 -0
- package/dist/src/commands/furnace/remove.js +3 -0
- package/dist/src/commands/furnace/rename.js +3 -0
- package/dist/src/commands/furnace/scan.js +3 -0
- package/dist/src/commands/import.js +1 -1
- package/dist/src/commands/lint-per-patch.js +16 -8
- package/dist/src/commands/lint-per-run-checkjs.d.ts +4 -4
- package/dist/src/commands/lint-per-run-checkjs.js +4 -4
- package/dist/src/commands/lint-report.js +1 -1
- package/dist/src/commands/lint.js +2 -2
- package/dist/src/commands/patch/lint-ignore.js +1 -1
- package/dist/src/commands/patch/move-files-create.js +5 -3
- package/dist/src/commands/patch/move-files-into.d.ts +1 -1
- package/dist/src/commands/patch/move-files-into.js +5 -3
- package/dist/src/commands/patch/move-files.js +1 -1
- package/dist/src/commands/patch/rename.js +1 -1
- package/dist/src/commands/patch/reorder.d.ts +1 -2
- package/dist/src/commands/patch/reorder.js +1 -2
- package/dist/src/commands/patch/split.js +5 -3
- package/dist/src/commands/patch/staged-dependency-validate.js +1 -1
- package/dist/src/commands/patch/staged-dependency.js +4 -4
- package/dist/src/commands/re-export-adjacent.js +1 -1
- package/dist/src/commands/re-export-bulk-scan.d.ts +1 -1
- package/dist/src/commands/re-export-bulk-scan.js +5 -4
- package/dist/src/commands/re-export-drift.d.ts +4 -4
- package/dist/src/commands/re-export-drift.js +47 -8
- package/dist/src/commands/re-export-lint.d.ts +7 -5
- package/dist/src/commands/re-export-lint.js +9 -8
- package/dist/src/commands/re-export-single.d.ts +1 -1
- package/dist/src/commands/re-export-single.js +5 -4
- package/dist/src/commands/re-export.js +26 -14
- package/dist/src/commands/reset.js +1 -1
- package/dist/src/commands/run.js +1 -1
- package/dist/src/commands/source.js +6 -6
- package/dist/src/commands/status-check.d.ts +1 -1
- package/dist/src/commands/status-check.js +1 -1
- package/dist/src/commands/status-json.d.ts +2 -2
- package/dist/src/commands/status-json.js +1 -1
- package/dist/src/commands/status-output.d.ts +1 -1
- package/dist/src/commands/status-output.js +1 -1
- package/dist/src/commands/status.js +21 -7
- package/dist/src/commands/test-build-phase.d.ts +2 -2
- package/dist/src/commands/test-build-phase.js +9 -6
- package/dist/src/commands/test-diagnose.js +8 -1
- package/dist/src/commands/test-modes.d.ts +2 -2
- package/dist/src/commands/test-modes.js +3 -3
- package/dist/src/commands/test-register.js +7 -2
- package/dist/src/commands/test-run.d.ts +6 -0
- package/dist/src/commands/test-run.js +32 -3
- package/dist/src/commands/test-stale-gate.js +7 -2
- package/dist/src/commands/test.js +4 -4
- package/dist/src/commands/tree.js +2 -2
- package/dist/src/commands/typecheck.d.ts +2 -1
- package/dist/src/commands/typecheck.js +59 -3
- package/dist/src/commands/verify.js +26 -1
- package/dist/src/core/ast-utils.js +14 -2
- package/dist/src/core/branding.d.ts +16 -0
- package/dist/src/core/branding.js +18 -2
- package/dist/src/core/browser-wire.js +9 -1
- package/dist/src/core/build-baseline-types.d.ts +1 -1
- package/dist/src/core/build-baseline.d.ts +1 -1
- package/dist/src/core/build-baseline.js +1 -1
- package/dist/src/core/build-overwrite-guard.d.ts +49 -0
- package/dist/src/core/build-overwrite-guard.js +112 -0
- package/dist/src/core/build-prepare.d.ts +13 -5
- package/dist/src/core/build-prepare.js +42 -9
- package/dist/src/core/config-mutate.d.ts +3 -2
- package/dist/src/core/config-mutate.js +5 -2
- package/dist/src/core/config.d.ts +3 -2
- package/dist/src/core/config.js +2 -0
- package/dist/src/core/coverage-extend.d.ts +1 -1
- package/dist/src/core/coverage-extend.js +1 -1
- package/dist/src/core/destructive.d.ts +23 -0
- package/dist/src/core/destructive.js +45 -4
- package/dist/src/core/diff-hunks.js +31 -17
- package/dist/src/core/discard-baseline.d.ts +1 -1
- package/dist/src/core/discard-baseline.js +1 -1
- package/dist/src/core/display-state.d.ts +52 -0
- package/dist/src/core/display-state.js +89 -0
- package/dist/src/core/engine-session-lock.d.ts +20 -0
- package/dist/src/core/engine-session-lock.js +84 -14
- package/dist/src/core/export-moved-code-shape.d.ts +60 -0
- package/dist/src/core/export-moved-code-shape.js +129 -0
- package/dist/src/core/file-lock.d.ts +44 -0
- package/dist/src/core/file-lock.js +157 -42
- package/dist/src/core/furnace-apply-helpers.d.ts +1 -1
- package/dist/src/core/furnace-apply-helpers.js +1 -1
- package/dist/src/core/furnace-apply-output.js +1 -1
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +2 -2
- package/dist/src/core/furnace-apply-overwrite-warn.js +2 -2
- package/dist/src/core/furnace-apply-undeploy-actions.d.ts +1 -1
- package/dist/src/core/furnace-apply-undeploy-actions.js +1 -1
- package/dist/src/core/furnace-apply.js +13 -1
- package/dist/src/core/furnace-config-custom.d.ts +2 -1
- package/dist/src/core/furnace-config-order.d.ts +2 -1
- package/dist/src/core/furnace-config-order.js +9 -4
- package/dist/src/core/furnace-config-validate.d.ts +4 -3
- package/dist/src/core/furnace-config-validate.js +3 -3
- package/dist/src/core/furnace-config.js +5 -0
- package/dist/src/core/furnace-operation.d.ts +12 -0
- package/dist/src/core/furnace-operation.js +125 -1
- package/dist/src/core/furnace-rollback.d.ts +17 -0
- package/dist/src/core/furnace-rollback.js +35 -1
- package/dist/src/core/git-base.js +7 -2
- package/dist/src/core/git-diff.js +11 -2
- package/dist/src/core/git-readonly-index.d.ts +59 -0
- package/dist/src/core/git-readonly-index.js +137 -0
- package/dist/src/core/license-headers.d.ts +1 -1
- package/dist/src/core/license-headers.js +1 -1
- package/dist/src/core/lint-cache.d.ts +15 -5
- package/dist/src/core/lint-cache.js +35 -8
- package/dist/src/core/mach-known-noise-filter.d.ts +21 -0
- package/dist/src/core/mach-known-noise-filter.js +23 -0
- package/dist/src/core/mach-mozconfig.js +21 -3
- package/dist/src/core/module-resolution-preflight.d.ts +51 -0
- package/dist/src/core/module-resolution-preflight.js +155 -0
- package/dist/src/core/patch-export.js +2 -1
- package/dist/src/core/patch-identifier-suggest.js +29 -3
- package/dist/src/core/patch-lint-checkjs.d.ts +5 -5
- package/dist/src/core/patch-lint-checkjs.js +6 -6
- package/dist/src/core/patch-lint-creators.d.ts +1 -1
- package/dist/src/core/patch-lint-creators.js +1 -1
- package/dist/src/core/patch-lint-cross.d.ts +1 -1
- package/dist/src/core/patch-lint-cross.js +4 -4
- package/dist/src/core/patch-lint-css.js +1 -1
- package/dist/src/core/patch-lint-diff.d.ts +1 -1
- package/dist/src/core/patch-lint-diff.js +1 -1
- package/dist/src/core/patch-lint-jsdoc.js +4 -2
- package/dist/src/core/patch-lint-module-registration.d.ts +13 -0
- package/dist/src/core/patch-lint-module-registration.js +13 -4
- package/dist/src/core/patch-lint-mozbuild.d.ts +1 -1
- package/dist/src/core/patch-lint-mozbuild.js +1 -1
- package/dist/src/core/patch-lint-ownership.d.ts +2 -2
- package/dist/src/core/patch-lint-ownership.js +2 -2
- package/dist/src/core/patch-lint.d.ts +2 -2
- package/dist/src/core/patch-lint.js +3 -3
- package/dist/src/core/patch-lock.d.ts +21 -0
- package/dist/src/core/patch-lock.js +29 -2
- package/dist/src/core/patch-manifest-io.d.ts +2 -1
- package/dist/src/core/patch-manifest-io.js +49 -0
- package/dist/src/core/patch-parse.d.ts +18 -0
- package/dist/src/core/patch-parse.js +24 -1
- package/dist/src/core/patch-policy.d.ts +2 -2
- package/dist/src/core/patch-policy.js +2 -2
- package/dist/src/core/smoke-patterns.d.ts +1 -1
- package/dist/src/core/status-binary.d.ts +1 -1
- package/dist/src/core/status-binary.js +1 -1
- package/dist/src/core/status-classify.d.ts +1 -1
- package/dist/src/core/status-classify.js +2 -2
- package/dist/src/core/test-harness-crash.d.ts +69 -3
- package/dist/src/core/test-harness-crash.js +174 -9
- package/dist/src/core/test-stale-check.d.ts +1 -1
- package/dist/src/core/test-stale-check.js +1 -1
- package/dist/src/core/token-category.js +1 -1
- package/dist/src/core/token-manager.d.ts +1 -1
- package/dist/src/core/token-manager.js +5 -5
- package/dist/src/core/tree-cow.js +1 -1
- package/dist/src/core/tree-guard.js +2 -2
- package/dist/src/core/tree-store.js +1 -1
- package/dist/src/core/typecheck-shim.d.ts +2 -2
- package/dist/src/core/typecheck-shim.js +2 -2
- package/dist/src/core/typecheck.js +10 -0
- package/dist/src/core/xpcshell-profile-dir.js +1 -1
- package/dist/src/errors/base.d.ts +22 -1
- package/dist/src/errors/base.js +32 -1
- package/dist/src/errors/codes.d.ts +10 -0
- package/dist/src/errors/codes.js +10 -0
- package/dist/src/types/commands/options.d.ts +27 -11
- package/dist/src/types/commands/patches.d.ts +1 -1
- package/dist/src/types/config.d.ts +2 -2
- package/dist/src/types/furnace.d.ts +1 -1
- package/dist/src/types/json.d.ts +22 -0
- package/dist/src/types/json.js +11 -0
- package/dist/src/utils/assert.d.ts +46 -0
- package/dist/src/utils/assert.js +83 -0
- package/dist/src/utils/build-info.d.ts +1 -1
- package/dist/src/utils/build-info.js +1 -1
- package/dist/src/utils/logger.d.ts +20 -0
- package/dist/src/utils/logger.js +26 -0
- package/dist/src/utils/options.d.ts +12 -0
- package/dist/src/utils/options.js +39 -0
- package/dist/src/utils/stdio-drain.d.ts +1 -1
- package/dist/src/utils/stdio-drain.js +1 -1
- package/dist/src/utils/validation.d.ts +12 -1
- package/dist/src/utils/validation.js +13 -1
- package/package.json +3 -3
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* above so the command layer can retry them with a bounded budget instead
|
|
22
22
|
* of reporting phantom test failures (or phantom passes).
|
|
23
23
|
*/
|
|
24
|
+
import type { DisplaySleepState } from './display-state.js';
|
|
24
25
|
/** How a completed harness run should be interpreted. */
|
|
25
26
|
export type HarnessRunClassification = 'tests-ran-ok' | 'test-failures' | 'harness-crash' | 'no-tests';
|
|
26
27
|
/** A recognized harness-crash shape with its evidence line. */
|
|
@@ -66,6 +67,13 @@ export interface HarnessRunVerdict extends HarnessSummaryCounts {
|
|
|
66
67
|
* Callers must fail the run and surface the evidence.
|
|
67
68
|
*/
|
|
68
69
|
greenSummaryRejected?: GreenSummaryRejection;
|
|
70
|
+
/**
|
|
71
|
+
* Short parenthetical appended to the `FIREFORGE-VERDICT:` line when the
|
|
72
|
+
* bare status would under-describe the run — currently "harness teardown
|
|
73
|
+
* noise ignored" and the headed display-asleep stall. Advisory text
|
|
74
|
+
* only; the status and reason keys are unaffected.
|
|
75
|
+
*/
|
|
76
|
+
note?: string;
|
|
69
77
|
}
|
|
70
78
|
/** Why a green-looking embedded summary was not trusted. */
|
|
71
79
|
export interface GreenSummaryRejection {
|
|
@@ -187,14 +195,72 @@ export declare function buildGreenSummaryRejectedMessage(rejection: GreenSummary
|
|
|
187
195
|
export declare function buildHarnessCrashMessage(signature: HarnessCrashSignature, attempts: number, commandLabel?: string): string;
|
|
188
196
|
/**
|
|
189
197
|
* Optional hint appended to the harness-crash message when a HEADED run on
|
|
190
|
-
* macOS died at the no-output timeout
|
|
191
|
-
*
|
|
192
|
-
*
|
|
198
|
+
* macOS died at the no-output timeout.
|
|
199
|
+
*
|
|
200
|
+
* When `displayState` names a MEASURED display state, the hint states it
|
|
201
|
+
* as fact rather than as one of three possibilities — that is the whole
|
|
202
|
+
* point of probing: an operator staring at a bare test failure should not
|
|
203
|
+
* have to rediscover that their machine dimmed. `caffeinate` is described
|
|
204
|
+
* accurately (it prevents sleep; it cannot wake an already-sleeping
|
|
205
|
+
* display), because the previous wording sent operators to a command that
|
|
206
|
+
* could not have helped them.
|
|
207
|
+
*
|
|
208
|
+
* Pure — the platform and the probed state are injected so unit tests need
|
|
209
|
+
* no mocking.
|
|
210
|
+
*
|
|
211
|
+
* @param signature - The recognized crash shape
|
|
212
|
+
* @param context - Run mode, platform, and the probed display state
|
|
213
|
+
* @returns The hint text, or undefined when the shape does not apply
|
|
193
214
|
*/
|
|
194
215
|
export declare function headedNoOutputTimeoutHint(signature: HarnessCrashSignature, context: {
|
|
195
216
|
headless: boolean;
|
|
196
217
|
platform: string;
|
|
218
|
+
displayState?: DisplaySleepState;
|
|
219
|
+
}): string | undefined;
|
|
220
|
+
/**
|
|
221
|
+
* Verdict-line note for a headed no-output stall whose display was
|
|
222
|
+
* measured asleep. Returned as the parenthetical the verdict line carries,
|
|
223
|
+
* so the one greppable line an automated consumer reads already names the
|
|
224
|
+
* environmental cause instead of a bare `FAIL reason=crash`.
|
|
225
|
+
*/
|
|
226
|
+
export declare function headedDisplayAsleepVerdictNote(signature: HarnessCrashSignature, context: {
|
|
227
|
+
headless: boolean;
|
|
228
|
+
platform: string;
|
|
229
|
+
displayState: DisplaySleepState;
|
|
197
230
|
}): string | undefined;
|
|
231
|
+
/**
|
|
232
|
+
* True when a harness run died on SIGSEGV having produced no test evidence
|
|
233
|
+
* whatsoever.
|
|
234
|
+
*
|
|
235
|
+
* `xpcshell return code: -11` with zero output is the signature of an
|
|
236
|
+
* `.sys.mjs` that a packaged module imports but whose `EXTRA_JS_MODULES`
|
|
237
|
+
* registration never landed: the module loader faults before any logging
|
|
238
|
+
* exists, so there is no import error, no stack, no test output — just a
|
|
239
|
+
* dead process. It is indistinguishable from a genuine product crash
|
|
240
|
+
* unless someone names the cause.
|
|
241
|
+
*
|
|
242
|
+
* "Produced no evidence" is deliberately semantic rather than a byte
|
|
243
|
+
* budget: no execution signal, no real failure line, and no mozcrash
|
|
244
|
+
* marker. A run that got far enough to start a test, report a failure, or
|
|
245
|
+
* leave a crash dump has evidence of its own, and the regular diagnosis
|
|
246
|
+
* chain reads it — this branch is only for the case where there is
|
|
247
|
+
* nothing else to say.
|
|
248
|
+
*
|
|
249
|
+
* Pure; exported for direct unit testing.
|
|
250
|
+
*
|
|
251
|
+
* @param exitCode - The harness process exit code
|
|
252
|
+
* @param output - Combined stdout/stderr from the run
|
|
253
|
+
*/
|
|
254
|
+
export declare function isSilentSegfault(exitCode: number, output: string): boolean;
|
|
255
|
+
/**
|
|
256
|
+
* Names the known cause of a silent SIGSEGV so the operator checks
|
|
257
|
+
* moz.build registration FIRST instead of paying a full rebuild cycle to
|
|
258
|
+
* rediscover it.
|
|
259
|
+
*
|
|
260
|
+
* @param exitCode - The harness process exit code
|
|
261
|
+
* @param requestedPaths - Test paths the run requested
|
|
262
|
+
*/
|
|
263
|
+
export declare function buildSilentSegfaultMessage(exitCode: number, requestedPaths: readonly string[]): string;
|
|
198
264
|
/**
|
|
199
265
|
* Builds the message for a run that produced no `TEST-START` despite
|
|
200
266
|
* requesting paths — including exit-code-zero runs whose `Passed: 0`
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* above so the command layer can retry them with a bounded budget instead
|
|
23
23
|
* of reporting phantom test failures (or phantom passes).
|
|
24
24
|
*/
|
|
25
|
+
import { hasKnownTeardownNoise } from './mach-known-noise-filter.js';
|
|
25
26
|
const TEST_START_PATTERN = /\bTEST-START\b/;
|
|
26
27
|
/**
|
|
27
28
|
* Structured-log execution marker (`mach xpcshell-test` mozlog output uses
|
|
@@ -452,6 +453,21 @@ export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
|
452
453
|
const firstRealFailure = realFailures[0];
|
|
453
454
|
const failureBlocks = collectUnexpectedFailureBlocks(output);
|
|
454
455
|
const secondaryHarnessSignature = firstRealFailure !== undefined ? detectSecondaryHarnessNoise(output) : undefined;
|
|
456
|
+
// Checked BEFORE signature detection: a suite that ran clean
|
|
457
|
+
// and then died in KNOWN upstream teardown noise is a PASS, not a crash.
|
|
458
|
+
// The teardown traceback matches the startup-traceback cluster, and the
|
|
459
|
+
// existing green-summary veto over that cluster requires a `SUITE_END`
|
|
460
|
+
// marker — which this very traceback is what prevents from printing. So
|
|
461
|
+
// a substantively green suite (`Ran N checks` / `Unexpected results: 0`,
|
|
462
|
+
// no unexpected lines) was reported CRASH, indistinguishable at the
|
|
463
|
+
// summary level from a red run. Every hard-evidence veto still applies:
|
|
464
|
+
// a crash marker, a non-zero unexpected count, any real TEST-UNEXPECTED
|
|
465
|
+
// line, the shutdown-re-entry shape, or a requested file that never
|
|
466
|
+
// started keeps the run failing. Only the missing shutdown marker is
|
|
467
|
+
// forgiven, and only when the recognized teardown traceback explains it.
|
|
468
|
+
if (isGreenSuiteEndedByKnownTeardownNoise(output, counts, realFailures, requestedPaths)) {
|
|
469
|
+
return { kind: 'tests-ran-ok', note: 'harness teardown noise ignored', ...counts };
|
|
470
|
+
}
|
|
455
471
|
const signature = detectHarnessCrashSignature(output);
|
|
456
472
|
if (signature) {
|
|
457
473
|
return { kind: 'harness-crash', signature, ...counts };
|
|
@@ -505,6 +521,40 @@ export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
|
505
521
|
...(secondaryHarnessSignature !== undefined ? { secondaryHarnessSignature } : {}),
|
|
506
522
|
};
|
|
507
523
|
}
|
|
524
|
+
/**
|
|
525
|
+
* True when the capture shows a substantively GREEN suite whose only
|
|
526
|
+
* unexplained residue is the recognized mozsystemmonitor teardown
|
|
527
|
+
* traceback.
|
|
528
|
+
*
|
|
529
|
+
* Requires, all of them: tests actually executed; the summary printed
|
|
530
|
+
* `Ran N checks` with an explicit `Unexpected results: 0`; no non-zero
|
|
531
|
+
* unexpected count anywhere; no real `TEST-UNEXPECTED-*`/assertion line;
|
|
532
|
+
* no crash marker; every requested test file both started and ended; and
|
|
533
|
+
* the recognized teardown traceback is present. Anything short of that
|
|
534
|
+
* falls through to the normal (failing) chain.
|
|
535
|
+
*/
|
|
536
|
+
function isGreenSuiteEndedByKnownTeardownNoise(output, counts, realFailures, requestedPaths) {
|
|
537
|
+
if (!hasKnownTeardownNoise(output))
|
|
538
|
+
return false;
|
|
539
|
+
if (!hasExecutionSignal(output))
|
|
540
|
+
return false;
|
|
541
|
+
if (counts.checks === undefined || counts.unexpected !== 0)
|
|
542
|
+
return false;
|
|
543
|
+
if (!GREEN_UNEXPECTED_SUMMARY_PATTERN.test(output))
|
|
544
|
+
return false;
|
|
545
|
+
if (NONZERO_UNEXPECTED_SUMMARY_PATTERN.test(output))
|
|
546
|
+
return false;
|
|
547
|
+
if (realFailures.length > 0)
|
|
548
|
+
return false;
|
|
549
|
+
if (findCrashMarkerLine(output) !== undefined)
|
|
550
|
+
return false;
|
|
551
|
+
// The post-green shutdown re-entry shape is deliberately a crash verdict
|
|
552
|
+
// on an otherwise-green log; it must not be swept up here.
|
|
553
|
+
if (SHUTDOWN_REENTRY_PATTERN.test(output))
|
|
554
|
+
return false;
|
|
555
|
+
const completeness = analyzeTestCompleteness(output, requestedPaths);
|
|
556
|
+
return completeness.neverStarted.length === 0 && completeness.neverEnded.length === 0;
|
|
557
|
+
}
|
|
508
558
|
/**
|
|
509
559
|
* Parses the numeric counts from the harness's embedded result summary
|
|
510
560
|
* (`Ran N checks` / `Unexpected results: N`). The LAST occurrence of each
|
|
@@ -547,15 +597,16 @@ export function formatFireforgeVerdictLine(verdict, shards) {
|
|
|
547
597
|
const counts = (verdict.checks !== undefined ? ` checks=${verdict.checks}` : '') +
|
|
548
598
|
(verdict.unexpected !== undefined ? ` unexpected=${verdict.unexpected}` : '');
|
|
549
599
|
const shardSuffix = shards ? ` shards=${shards.passed}/${shards.total}` : '';
|
|
600
|
+
const note = verdict.note !== undefined ? ` (${verdict.note})` : '';
|
|
550
601
|
if (verdict.kind === 'tests-ran-ok') {
|
|
551
|
-
return `FIREFORGE-VERDICT: PASS${counts}${shardSuffix}`;
|
|
602
|
+
return `FIREFORGE-VERDICT: PASS${counts}${shardSuffix}${note}`;
|
|
552
603
|
}
|
|
553
604
|
const reason = verdict.kind === 'harness-crash'
|
|
554
605
|
? 'crash'
|
|
555
606
|
: verdict.kind === 'no-tests'
|
|
556
607
|
? 'no-tests'
|
|
557
608
|
: 'test-failures';
|
|
558
|
-
return `FIREFORGE-VERDICT: FAIL reason=${reason}${counts}${shardSuffix}`;
|
|
609
|
+
return `FIREFORGE-VERDICT: FAIL reason=${reason}${counts}${shardSuffix}${note}`;
|
|
559
610
|
}
|
|
560
611
|
/**
|
|
561
612
|
* Builds the operator-facing failure message for a green-looking summary
|
|
@@ -592,21 +643,135 @@ export function buildHarnessCrashMessage(signature, attempts, commandLabel = 'ma
|
|
|
592
643
|
'If it persists across many runs, inspect the mach virtualenv (mach resyncs psutil on its own; ' +
|
|
593
644
|
'patching it manually does not stick).');
|
|
594
645
|
}
|
|
646
|
+
/**
|
|
647
|
+
* The three recorded causes of the `timed out … with no output` / `Ran 0
|
|
648
|
+
* checks` signature, ordered so one stall points at the discriminating
|
|
649
|
+
* next step. Printed verbatim under the hint.
|
|
650
|
+
*/
|
|
651
|
+
const NO_OUTPUT_STALL_TRIAGE = [
|
|
652
|
+
' 1. A sleeping or locked display on an unattended HEADED run (macOS). The browser never ' +
|
|
653
|
+
'paints and never reaches its first test.',
|
|
654
|
+
' 2. A headless SWGL compositor failure — re-run with --headless to separate this from (1): ' +
|
|
655
|
+
'if --headless passes, the stall was display/compositor-side, not product-side.',
|
|
656
|
+
' 3. A broken `chrome://` image on the startup page, which stalls first paint. Run a ' +
|
|
657
|
+
'known-good control test first; if the control stalls too, the cause is not the test ' +
|
|
658
|
+
'under investigation.',
|
|
659
|
+
];
|
|
595
660
|
/**
|
|
596
661
|
* Optional hint appended to the harness-crash message when a HEADED run on
|
|
597
|
-
* macOS died at the no-output timeout
|
|
598
|
-
*
|
|
599
|
-
*
|
|
662
|
+
* macOS died at the no-output timeout.
|
|
663
|
+
*
|
|
664
|
+
* When `displayState` names a MEASURED display state, the hint states it
|
|
665
|
+
* as fact rather than as one of three possibilities — that is the whole
|
|
666
|
+
* point of probing: an operator staring at a bare test failure should not
|
|
667
|
+
* have to rediscover that their machine dimmed. `caffeinate` is described
|
|
668
|
+
* accurately (it prevents sleep; it cannot wake an already-sleeping
|
|
669
|
+
* display), because the previous wording sent operators to a command that
|
|
670
|
+
* could not have helped them.
|
|
671
|
+
*
|
|
672
|
+
* Pure — the platform and the probed state are injected so unit tests need
|
|
673
|
+
* no mocking.
|
|
674
|
+
*
|
|
675
|
+
* @param signature - The recognized crash shape
|
|
676
|
+
* @param context - Run mode, platform, and the probed display state
|
|
677
|
+
* @returns The hint text, or undefined when the shape does not apply
|
|
600
678
|
*/
|
|
601
679
|
export function headedNoOutputTimeoutHint(signature, context) {
|
|
602
680
|
if (context.platform !== 'darwin' || context.headless)
|
|
603
681
|
return undefined;
|
|
604
682
|
if (!signature.reason.includes('no-output timeout'))
|
|
605
683
|
return undefined;
|
|
606
|
-
|
|
607
|
-
'
|
|
608
|
-
|
|
609
|
-
|
|
684
|
+
const lead = context.displayState === 'asleep'
|
|
685
|
+
? 'Hint: this was a HEADED run on macOS and the display was MEASURED ASLEEP ' +
|
|
686
|
+
'(IODisplayWrangler below its awake power state). A headed browser on a sleeping ' +
|
|
687
|
+
'display never paints and never starts a test, so this stall is environmental — not a ' +
|
|
688
|
+
'product or test failure.'
|
|
689
|
+
: context.displayState === 'awake'
|
|
690
|
+
? 'Hint: this was a HEADED run on macOS that died at the no-output timeout. The display ' +
|
|
691
|
+
'was measured AWAKE, so the sleeping-display cause below is ruled out for this run.'
|
|
692
|
+
: 'Hint: this was a HEADED run on macOS that died at the no-output timeout. The display ' +
|
|
693
|
+
'state could not be measured, so all three causes below remain open.';
|
|
694
|
+
const remedy = 'Note that `caffeinate -disu` PREVENTS sleep; it cannot WAKE a display that is already ' +
|
|
695
|
+
'asleep. Wake the display (or run on an attended machine), or pass --headless.';
|
|
696
|
+
return `${lead}\n\nKnown causes of this exact signature:\n${NO_OUTPUT_STALL_TRIAGE.join('\n')}\n\n${remedy}`;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Verdict-line note for a headed no-output stall whose display was
|
|
700
|
+
* measured asleep. Returned as the parenthetical the verdict line carries,
|
|
701
|
+
* so the one greppable line an automated consumer reads already names the
|
|
702
|
+
* environmental cause instead of a bare `FAIL reason=crash`.
|
|
703
|
+
*/
|
|
704
|
+
export function headedDisplayAsleepVerdictNote(signature, context) {
|
|
705
|
+
if (context.platform !== 'darwin' || context.headless)
|
|
706
|
+
return undefined;
|
|
707
|
+
if (context.displayState !== 'asleep')
|
|
708
|
+
return undefined;
|
|
709
|
+
if (!signature.reason.includes('no-output timeout'))
|
|
710
|
+
return undefined;
|
|
711
|
+
return 'headed run stalled with the display asleep';
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Exit codes that mean "the child died on SIGSEGV": Node reports a
|
|
715
|
+
* signal-killed child as the negated signal number, while a shell layer
|
|
716
|
+
* in between reports `128 + signal`. Both shapes reach FireForge
|
|
717
|
+
* depending on how mach wrapped the harness.
|
|
718
|
+
*/
|
|
719
|
+
const SIGSEGV_EXIT_CODES = new Set([-11, 139]);
|
|
720
|
+
/**
|
|
721
|
+
* True when a harness run died on SIGSEGV having produced no test evidence
|
|
722
|
+
* whatsoever.
|
|
723
|
+
*
|
|
724
|
+
* `xpcshell return code: -11` with zero output is the signature of an
|
|
725
|
+
* `.sys.mjs` that a packaged module imports but whose `EXTRA_JS_MODULES`
|
|
726
|
+
* registration never landed: the module loader faults before any logging
|
|
727
|
+
* exists, so there is no import error, no stack, no test output — just a
|
|
728
|
+
* dead process. It is indistinguishable from a genuine product crash
|
|
729
|
+
* unless someone names the cause.
|
|
730
|
+
*
|
|
731
|
+
* "Produced no evidence" is deliberately semantic rather than a byte
|
|
732
|
+
* budget: no execution signal, no real failure line, and no mozcrash
|
|
733
|
+
* marker. A run that got far enough to start a test, report a failure, or
|
|
734
|
+
* leave a crash dump has evidence of its own, and the regular diagnosis
|
|
735
|
+
* chain reads it — this branch is only for the case where there is
|
|
736
|
+
* nothing else to say.
|
|
737
|
+
*
|
|
738
|
+
* Pure; exported for direct unit testing.
|
|
739
|
+
*
|
|
740
|
+
* @param exitCode - The harness process exit code
|
|
741
|
+
* @param output - Combined stdout/stderr from the run
|
|
742
|
+
*/
|
|
743
|
+
export function isSilentSegfault(exitCode, output) {
|
|
744
|
+
if (!SIGSEGV_EXIT_CODES.has(exitCode))
|
|
745
|
+
return false;
|
|
746
|
+
if (hasExecutionSignal(output))
|
|
747
|
+
return false;
|
|
748
|
+
if (realUnexpectedFailureLines(output).length > 0)
|
|
749
|
+
return false;
|
|
750
|
+
return findCrashMarkerLine(output) === undefined;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Names the known cause of a silent SIGSEGV so the operator checks
|
|
754
|
+
* moz.build registration FIRST instead of paying a full rebuild cycle to
|
|
755
|
+
* rediscover it.
|
|
756
|
+
*
|
|
757
|
+
* @param exitCode - The harness process exit code
|
|
758
|
+
* @param requestedPaths - Test paths the run requested
|
|
759
|
+
*/
|
|
760
|
+
export function buildSilentSegfaultMessage(exitCode, requestedPaths) {
|
|
761
|
+
return (`The test harness died on SIGSEGV (exit ${exitCode}) having printed NO output at all.\n\n` +
|
|
762
|
+
'Known cause, check this FIRST: a `.sys.mjs` imported from a packaged module whose ' +
|
|
763
|
+
'`EXTRA_JS_MODULES` (or namespaced module list) registration is missing from moz.build. ' +
|
|
764
|
+
'The module loader faults before any logging exists, so an unregistered module produces ' +
|
|
765
|
+
'exactly this shape — a dead process with no import error and no stack.\n\n' +
|
|
766
|
+
'What to do, in order:\n' +
|
|
767
|
+
' 1. Run `fireforge lint --per-patch`: the unregistered-system-module check names a new ' +
|
|
768
|
+
'module imported as a resource URL with no moz.build line.\n' +
|
|
769
|
+
' 2. Run `fireforge verify`: its module-resolution preflight resolves every ' +
|
|
770
|
+
'`resource:///modules/…` specifier the queue-owned modules import, including imports ' +
|
|
771
|
+
'added to an EXISTING module — the shape the new-file lint cannot see, and the one that ' +
|
|
772
|
+
'recurs.\n' +
|
|
773
|
+
' 3. Only then look for a product crash: a real one prints a crash dump or a stack.\n\n' +
|
|
774
|
+
`Requested paths: ${requestedPaths.join(', ')}`);
|
|
610
775
|
}
|
|
611
776
|
/**
|
|
612
777
|
* Builds the message for a run that produced no `TEST-START` despite
|
|
@@ -111,7 +111,7 @@ export declare function formatStaticComponentsRefusal(changedManifests: string[]
|
|
|
111
111
|
/**
|
|
112
112
|
* Formats the post-mutation advisory printed by `fireforge reset` /
|
|
113
113
|
* `fireforge import` when `components.conf` diverged from the last full
|
|
114
|
-
* build
|
|
114
|
+
* build. Same probe as {@link formatStaticComponentsRefusal},
|
|
115
115
|
* different moment: this fires at mutation time so the operator learns a
|
|
116
116
|
* full build is needed BEFORE the next gate run refuses.
|
|
117
117
|
*/
|
|
@@ -229,7 +229,7 @@ export function formatStaticComponentsRefusal(changedManifests) {
|
|
|
229
229
|
/**
|
|
230
230
|
* Formats the post-mutation advisory printed by `fireforge reset` /
|
|
231
231
|
* `fireforge import` when `components.conf` diverged from the last full
|
|
232
|
-
* build
|
|
232
|
+
* build. Same probe as {@link formatStaticComponentsRefusal},
|
|
233
233
|
* different moment: this fires at mutation time so the operator learns a
|
|
234
234
|
* full build is needed BEFORE the next gate run refuses.
|
|
235
235
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
3
|
* Category banner and token-declaration helpers for the tokens CSS file
|
|
4
|
-
|
|
4
|
+
*. Split out of `token-manager.ts` to stay inside the per-file
|
|
5
5
|
* line budget.
|
|
6
6
|
*
|
|
7
7
|
* Banner matching is EXACT: the text between the `=` runs (single-line
|
|
@@ -51,7 +51,7 @@ export interface AddTokenResult {
|
|
|
51
51
|
/**
|
|
52
52
|
* When the add skipped because the token already lives in the TARGET
|
|
53
53
|
* category, names where the existing base-`:root` declaration sits so
|
|
54
|
-
* the skip message can point at it
|
|
54
|
+
* the skip message can point at it.
|
|
55
55
|
*/
|
|
56
56
|
skippedExisting?: TokenDeclarationLocation;
|
|
57
57
|
}
|
|
@@ -117,7 +117,7 @@ async function addVariantTokenToCSS(engineDir, options, tokensCssPath, variant)
|
|
|
117
117
|
return { added: true };
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
|
-
* Evaluates base-`:root` idempotency for one add
|
|
120
|
+
* Evaluates base-`:root` idempotency for one add: a token
|
|
121
121
|
* already declared in the TARGET category skips; a token declared
|
|
122
122
|
* anywhere else in the base block refuses loud — the pre-0.40.0
|
|
123
123
|
* whole-file substring check silently skipped (and discarded
|
|
@@ -151,7 +151,7 @@ export async function validateTokenAdd(root, options) {
|
|
|
151
151
|
validateDarkValue(options);
|
|
152
152
|
// Variant mode targets a `:root<attr>` block, not a category section —
|
|
153
153
|
// but the required --category must still name a REAL category rather
|
|
154
|
-
// than being silently discarded (
|
|
154
|
+
// than being silently discarded (bypass 2).
|
|
155
155
|
if (normalizeVariantOption(options) !== undefined) {
|
|
156
156
|
await assertTokenCategoryExists(engineDir, tokensCssPath, options.category, false);
|
|
157
157
|
return;
|
|
@@ -201,7 +201,7 @@ export async function addToken(root, options) {
|
|
|
201
201
|
// base token is not short-circuited by the global idempotency check.
|
|
202
202
|
if (normalizedVariant !== undefined) {
|
|
203
203
|
// The required --category must name a real category even though the
|
|
204
|
-
// declaration routes into the variant block (
|
|
204
|
+
// declaration routes into the variant block (bypass 2).
|
|
205
205
|
await assertTokenCategoryExists(engineDir, tokensCssPath, options.category, false);
|
|
206
206
|
const { added } = await addVariantTokenToCSS(engineDir, options, tokensCssPath, normalizedVariant);
|
|
207
207
|
return {
|
|
@@ -241,7 +241,7 @@ export async function addToken(root, options) {
|
|
|
241
241
|
* pair the media query with `:root[data-theme="dark"]` /
|
|
242
242
|
* `:root[data-theme="light"]` blocks (viewer-toggle theming) require every
|
|
243
243
|
* themed list to declare identical token sets, so an override add that only
|
|
244
|
-
* wrote the media block was a guaranteed half-finished edit
|
|
244
|
+
* wrote the media block was a guaranteed half-finished edit.
|
|
245
245
|
*/
|
|
246
246
|
const THEME_ATTRIBUTE_VARIANTS = [
|
|
247
247
|
{ variant: '[data-theme="dark"]', pick: 'dark' },
|
|
@@ -298,7 +298,7 @@ async function addTokenToCSS(engineDir, options, tokensCssPath) {
|
|
|
298
298
|
await assertTokenCategoryExists(engineDir, tokensCssPath, options.category, options.createCategory === true);
|
|
299
299
|
const content = await readText(filePath);
|
|
300
300
|
const lines = content.split('\n');
|
|
301
|
-
// Per-category idempotency
|
|
301
|
+
// Per-category idempotency: a declaration in the TARGET
|
|
302
302
|
// category skips; a declaration elsewhere in the base :root block
|
|
303
303
|
// throws instead of silently skipping (and silently discarding
|
|
304
304
|
// --create-category).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Copy-on-write cloning for `fireforge tree
|
|
3
|
+
* Copy-on-write cloning for `fireforge tree`.
|
|
4
4
|
*
|
|
5
5
|
* Capability is probed BY DOING — a tiny `cp -c` (APFS clonefile) or
|
|
6
6
|
* `cp --reflink=always` (btrfs/XFS) in the actual destination directory —
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Read-only guard for verification trees
|
|
3
|
+
* Read-only guard for verification trees.
|
|
4
4
|
*
|
|
5
5
|
* A tree is a snapshot: its `patches/` and `components/` are copies with
|
|
6
6
|
* no merge-back model, so any command that mutates project or engine
|
|
@@ -66,7 +66,7 @@ const TREE_CONDITIONAL_CHECKS = {
|
|
|
66
66
|
'export-all': ({ options }) => options['dryRun'] === true,
|
|
67
67
|
// Same rule for refreshing existing patches: the dry-run projection is a
|
|
68
68
|
// read (proven side-effect free and runtime-enforced by
|
|
69
|
-
// withDryRunPurityGuard
|
|
69
|
+
// withDryRunPurityGuard); a real re-export writes patches/.
|
|
70
70
|
're-export': ({ options }) => options['dryRun'] === true,
|
|
71
71
|
// `config` with no positional value prints; with a value it writes.
|
|
72
72
|
config: ({ args }) => args.filter((a) => typeof a === 'string').length <= 1,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Verification-tree storage and lifecycle for `fireforge tree
|
|
3
|
+
* Verification-tree storage and lifecycle for `fireforge tree`.
|
|
4
4
|
*
|
|
5
5
|
* A tree is a FULL project-root CoW snapshot under
|
|
6
6
|
* `.fireforge/trees/<name>` — `getProjectRoot()` walks up from cwd, so
|
|
@@ -15,7 +15,7 @@ export declare const SHIM_FILENAME = "__fireforge_firefox_globals.d.ts";
|
|
|
15
15
|
* Loose declarations for Firefox test-harness globals (mochitest
|
|
16
16
|
* browser-chrome and xpcshell), appended AFTER the composed
|
|
17
17
|
* Firefox-globals + consumer shim when `patchLint.checkJsTestFiles`
|
|
18
|
-
* extends the checkJs pass to patch-adopted test scripts
|
|
18
|
+
* extends the checkJs pass to patch-adopted test scripts.
|
|
19
19
|
* Deliberately `any`-typed — the pragmatic posture matches the main
|
|
20
20
|
* shim. A consumer that wants TYPED harness members (so e.g. a call to
|
|
21
21
|
* a method the harness does not declare fails at the patch boundary)
|
|
@@ -40,7 +40,7 @@ export declare const TEST_HARNESS_SHIM = "\n// \u2500\u2500 FireForge test-harne
|
|
|
40
40
|
export declare const SUPPRESSED_DIAGNOSTIC_CODES: ReadonlySet<number>;
|
|
41
41
|
/**
|
|
42
42
|
* Undefined-free-identifier codes, split out of
|
|
43
|
-
* {@link SUPPRESSED_DIAGNOSTIC_CODES}
|
|
43
|
+
* {@link SUPPRESSED_DIAGNOSTIC_CODES}. Unconditional
|
|
44
44
|
* suppression let a module reference a name with no import or declaration
|
|
45
45
|
* anywhere and still typecheck with 0 errors — the failure then surfaced
|
|
46
46
|
* as a runtime `ReferenceError`. Both flows now report these at a
|
|
@@ -133,7 +133,7 @@ declare module 'chrome:*';
|
|
|
133
133
|
* Loose declarations for Firefox test-harness globals (mochitest
|
|
134
134
|
* browser-chrome and xpcshell), appended AFTER the composed
|
|
135
135
|
* Firefox-globals + consumer shim when `patchLint.checkJsTestFiles`
|
|
136
|
-
* extends the checkJs pass to patch-adopted test scripts
|
|
136
|
+
* extends the checkJs pass to patch-adopted test scripts.
|
|
137
137
|
* Deliberately `any`-typed — the pragmatic posture matches the main
|
|
138
138
|
* shim. A consumer that wants TYPED harness members (so e.g. a call to
|
|
139
139
|
* a method the harness does not declare fails at the patch boundary)
|
|
@@ -189,7 +189,7 @@ export const SUPPRESSED_DIAGNOSTIC_CODES = new Set([
|
|
|
189
189
|
]);
|
|
190
190
|
/**
|
|
191
191
|
* Undefined-free-identifier codes, split out of
|
|
192
|
-
* {@link SUPPRESSED_DIAGNOSTIC_CODES}
|
|
192
|
+
* {@link SUPPRESSED_DIAGNOSTIC_CODES}. Unconditional
|
|
193
193
|
* suppression let a module reference a name with no import or declaration
|
|
194
194
|
* anywhere and still typecheck with 0 errors — the failure then surfaced
|
|
195
195
|
* as a runtime `ReferenceError`. Both flows now report these at a
|
|
@@ -222,8 +222,18 @@ async function runTypecheckForProject(ts, projectRoot, projectPath, shimSource,
|
|
|
222
222
|
noEmit: true,
|
|
223
223
|
allowJs: parsed.options.allowJs ?? true,
|
|
224
224
|
checkJs: parsed.options.checkJs ?? true,
|
|
225
|
+
// No incremental sidecar, ever. A user jsconfig under `engine/` that
|
|
226
|
+
// sets `incremental` (or names a `tsBuildInfoFile`)
|
|
227
|
+
// would have this command drop a `.tsbuildinfo` inside the primary
|
|
228
|
+
// engine checkout — a second writer that invalidates a concurrent
|
|
229
|
+
// `fireforge test`'s engine fingerprint. This command emits nothing,
|
|
230
|
+
// so the sidecar buys nothing either.
|
|
231
|
+
incremental: false,
|
|
225
232
|
// skipLibCheck is not forced; the user owns it via their jsconfig.
|
|
226
233
|
};
|
|
234
|
+
// `exactOptionalPropertyTypes` forbids assigning `undefined` to an
|
|
235
|
+
// optional `string`, so the sidecar path is dropped rather than unset.
|
|
236
|
+
delete options.tsBuildInfoFile;
|
|
227
237
|
// 5) The synthetic shim file. Use a project-rooted path with a
|
|
228
238
|
// hidden-style prefix so it is unlikely to collide with any real
|
|
229
239
|
// file — and never write it to disk. The CompilerHost below serves
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Per-invocation xpcshell profile directory
|
|
3
|
+
* Per-invocation xpcshell profile directory.
|
|
4
4
|
*
|
|
5
5
|
* Firefox's xpcshell harness defaults `XPCSHELL_TEST_PROFILE_DIR` to a
|
|
6
6
|
* FIXED path (`$TMPDIR/firefox/xpcshellprofile`), so two overlapping
|
|
@@ -25,6 +25,27 @@ export declare abstract class FireForgeError extends Error {
|
|
|
25
25
|
export declare class GeneralError extends FireForgeError {
|
|
26
26
|
readonly code: 1;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Raised when one of FireForge's own internal invariants did not hold —
|
|
30
|
+
* the failure of a runtime assertion from `src/utils/assert.ts`.
|
|
31
|
+
*
|
|
32
|
+
* Dedicated subclass (rather than raw GeneralError) because an invariant
|
|
33
|
+
* failure is categorically different from every other error the CLI
|
|
34
|
+
* raises: it is a bug in FireForge, not something the operator did or can
|
|
35
|
+
* fix. That difference is worth carrying all the way to the exit status
|
|
36
|
+
* (see {@link ExitCode.INTERNAL_ERROR}) so a CI job can escalate it
|
|
37
|
+
* instead of retrying it, and it is the one error class the CLI boundary
|
|
38
|
+
* prints a stack trace for — the stack is the report.
|
|
39
|
+
*
|
|
40
|
+
* Assertions exist so that a violated invariant stops the run at the
|
|
41
|
+
* first inconsistency instead of continuing to write. In the mutating
|
|
42
|
+
* paths that means the surrounding rollback journal restores the engine
|
|
43
|
+
* before this reaches the operator; see `docs/lifecycle-invariants.md`.
|
|
44
|
+
*/
|
|
45
|
+
export declare class InternalInvariantError extends FireForgeError {
|
|
46
|
+
readonly code: 11;
|
|
47
|
+
get userMessage(): string;
|
|
48
|
+
}
|
|
28
49
|
/**
|
|
29
50
|
* Raised when a legacy regex/brace-depth fallback parser decides it
|
|
30
51
|
* cannot safely perform its mutation — e.g. because a block it expected
|
|
@@ -85,7 +106,7 @@ export declare class ExecTimeoutError extends FireForgeError {
|
|
|
85
106
|
}
|
|
86
107
|
/**
|
|
87
108
|
* Error thrown when a file-lock wait times out because another process
|
|
88
|
-
* holds the lock
|
|
109
|
+
* holds the lock.
|
|
89
110
|
*
|
|
90
111
|
* Dedicated subclass so the CLI boundary renders lock contention as the
|
|
91
112
|
* one-line reason-first/remedy-second refusal it is — before this, the
|
package/dist/src/errors/base.js
CHANGED
|
@@ -36,6 +36,37 @@ export class FireForgeError extends Error {
|
|
|
36
36
|
export class GeneralError extends FireForgeError {
|
|
37
37
|
code = ExitCode.GENERAL_ERROR;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Raised when one of FireForge's own internal invariants did not hold —
|
|
41
|
+
* the failure of a runtime assertion from `src/utils/assert.ts`.
|
|
42
|
+
*
|
|
43
|
+
* Dedicated subclass (rather than raw GeneralError) because an invariant
|
|
44
|
+
* failure is categorically different from every other error the CLI
|
|
45
|
+
* raises: it is a bug in FireForge, not something the operator did or can
|
|
46
|
+
* fix. That difference is worth carrying all the way to the exit status
|
|
47
|
+
* (see {@link ExitCode.INTERNAL_ERROR}) so a CI job can escalate it
|
|
48
|
+
* instead of retrying it, and it is the one error class the CLI boundary
|
|
49
|
+
* prints a stack trace for — the stack is the report.
|
|
50
|
+
*
|
|
51
|
+
* Assertions exist so that a violated invariant stops the run at the
|
|
52
|
+
* first inconsistency instead of continuing to write. In the mutating
|
|
53
|
+
* paths that means the surrounding rollback journal restores the engine
|
|
54
|
+
* before this reaches the operator; see `docs/lifecycle-invariants.md`.
|
|
55
|
+
*/
|
|
56
|
+
export class InternalInvariantError extends FireForgeError {
|
|
57
|
+
code = ExitCode.INTERNAL_ERROR;
|
|
58
|
+
get userMessage() {
|
|
59
|
+
return (`Internal error: ${this.message}\n\n` +
|
|
60
|
+
'This is a bug in FireForge itself, not a problem with your project — ' +
|
|
61
|
+
'no configuration change on your side would have prevented it. The ' +
|
|
62
|
+
'operation stopped at the first inconsistency rather than continuing ' +
|
|
63
|
+
'to write.\n\n' +
|
|
64
|
+
'To report this:\n' +
|
|
65
|
+
' 1. Keep the full output above, including the stack trace.\n' +
|
|
66
|
+
' 2. Note the command you ran and what the project looked like at the time.\n' +
|
|
67
|
+
' 3. Open an issue at https://github.com/HominisBrowser/FireForge/issues');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
39
70
|
/**
|
|
40
71
|
* Raised when a legacy regex/brace-depth fallback parser decides it
|
|
41
72
|
* cannot safely perform its mutation — e.g. because a block it expected
|
|
@@ -115,7 +146,7 @@ export class ExecTimeoutError extends FireForgeError {
|
|
|
115
146
|
}
|
|
116
147
|
/**
|
|
117
148
|
* Error thrown when a file-lock wait times out because another process
|
|
118
|
-
* holds the lock
|
|
149
|
+
* holds the lock.
|
|
119
150
|
*
|
|
120
151
|
* Dedicated subclass so the CLI boundary renders lock contention as the
|
|
121
152
|
* one-line reason-first/remedy-second refusal it is — before this, the
|
|
@@ -25,6 +25,16 @@ export declare const ExitCode: {
|
|
|
25
25
|
readonly FURNACE_ERROR: 9;
|
|
26
26
|
/** Patch conflict resolution error */
|
|
27
27
|
readonly RESOLUTION_ERROR: 10;
|
|
28
|
+
/**
|
|
29
|
+
* An internal invariant did not hold — a bug in FireForge itself, not a
|
|
30
|
+
* user or environment failure. Distinct from GENERAL_ERROR because the
|
|
31
|
+
* two call for opposite responses: a GENERAL_ERROR usually means the
|
|
32
|
+
* operator must change something (fix a config, free a lock, retry), and
|
|
33
|
+
* an INTERNAL_ERROR means nothing the operator can change would have
|
|
34
|
+
* helped and the run should be reported instead. Scripts and CI that
|
|
35
|
+
* retry on failure want to retry the former and escalate the latter.
|
|
36
|
+
*/
|
|
37
|
+
readonly INTERNAL_ERROR: 11;
|
|
28
38
|
/**
|
|
29
39
|
* `fireforge run --smoke-exit` observed one or more unallowed console
|
|
30
40
|
* error lines inside the smoke window. Distinct from BUILD_ERROR so CI
|
package/dist/src/errors/codes.js
CHANGED
|
@@ -26,6 +26,16 @@ export const ExitCode = {
|
|
|
26
26
|
FURNACE_ERROR: 9,
|
|
27
27
|
/** Patch conflict resolution error */
|
|
28
28
|
RESOLUTION_ERROR: 10,
|
|
29
|
+
/**
|
|
30
|
+
* An internal invariant did not hold — a bug in FireForge itself, not a
|
|
31
|
+
* user or environment failure. Distinct from GENERAL_ERROR because the
|
|
32
|
+
* two call for opposite responses: a GENERAL_ERROR usually means the
|
|
33
|
+
* operator must change something (fix a config, free a lock, retry), and
|
|
34
|
+
* an INTERNAL_ERROR means nothing the operator can change would have
|
|
35
|
+
* helped and the run should be reported instead. Scripts and CI that
|
|
36
|
+
* retry on failure want to retry the former and escalate the latter.
|
|
37
|
+
*/
|
|
38
|
+
INTERNAL_ERROR: 11,
|
|
29
39
|
/**
|
|
30
40
|
* `fireforge run --smoke-exit` observed one or more unallowed console
|
|
31
41
|
* error lines inside the smoke window. Distinct from BUILD_ERROR so CI
|