@hominis/fireforge 0.42.0 → 0.43.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -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/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 +165 -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 +12 -2
- 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
|
@@ -50,7 +50,7 @@ export function extractAddedLinesPerFile(diffContent) {
|
|
|
50
50
|
* unified diff. Walking each hunk with a new-file line counter (`+` and
|
|
51
51
|
* context lines advance it, `-` lines do not) lets rules that need
|
|
52
52
|
* full-file context — e.g. comment masking across the context/added
|
|
53
|
-
* boundary
|
|
53
|
+
* boundary — map the patch's additions onto the applied file.
|
|
54
54
|
*/
|
|
55
55
|
export function extractAddedLineNumbersPerFile(diffContent) {
|
|
56
56
|
const result = new Map();
|
|
@@ -133,10 +133,12 @@ function walkForReturn(node) {
|
|
|
133
133
|
node.type === 'ArrowFunctionExpression') {
|
|
134
134
|
return false;
|
|
135
135
|
}
|
|
136
|
-
|
|
136
|
+
// `entries` erases the node's static shape without asserting a new one;
|
|
137
|
+
// the `any` from `Object.entries`' fallback overload widens to `unknown`.
|
|
138
|
+
const entries = Object.entries(node);
|
|
139
|
+
for (const [key, val] of entries) {
|
|
137
140
|
if (key === 'type')
|
|
138
141
|
continue;
|
|
139
|
-
const val = node[key];
|
|
140
142
|
if (val && typeof val === 'object') {
|
|
141
143
|
if (Array.isArray(val)) {
|
|
142
144
|
for (const child of val) {
|
|
@@ -7,6 +7,19 @@ interface ModuleRegistrationQueueEntry {
|
|
|
7
7
|
interface ModuleRegistrationQueueContext {
|
|
8
8
|
entries: readonly ModuleRegistrationQueueEntry[];
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Maps Firefox's common system-module resource URLs to their source
|
|
12
|
+
* suffix. Exported alongside {@link extractResourceModuleSpecifiers} so
|
|
13
|
+
* the resolution preflight resolves specifiers identically.
|
|
14
|
+
*/
|
|
15
|
+
export declare function moduleSourceSuffix(specifier: string): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Resource-module strings in actual code (comments removed), de-duplicated.
|
|
18
|
+
* Exported for the engine-aware resolution preflight, which must recognise
|
|
19
|
+
* exactly the same specifier shapes this queue-level rule does — two
|
|
20
|
+
* extractors would drift.
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractResourceModuleSpecifiers(content: string): string[];
|
|
10
23
|
/**
|
|
11
24
|
* Flags a new `.sys.mjs` imported through a Firefox resource URL when the
|
|
12
25
|
* projected queue never adds it to a moz.build list.
|
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
import { basename } from 'node:path';
|
|
6
6
|
import { escapeRegex, stripJsComments } from '../utils/regex.js';
|
|
7
7
|
const IMPORTABLE_EXTENSIONS = ['.mjs', '.sys.mjs', '.js', '.jsm'];
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Maps Firefox's common system-module resource URLs to their source
|
|
10
|
+
* suffix. Exported alongside {@link extractResourceModuleSpecifiers} so
|
|
11
|
+
* the resolution preflight resolves specifiers identically.
|
|
12
|
+
*/
|
|
13
|
+
export function moduleSourceSuffix(specifier) {
|
|
10
14
|
const cleaned = specifier.split(/[?#]/)[0] ?? specifier;
|
|
11
15
|
for (const prefix of ['resource:///modules/', 'resource://gre/modules/']) {
|
|
12
16
|
if (cleaned.startsWith(prefix))
|
|
@@ -14,8 +18,13 @@ function moduleSourceSuffix(specifier) {
|
|
|
14
18
|
}
|
|
15
19
|
return undefined;
|
|
16
20
|
}
|
|
17
|
-
/**
|
|
18
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Resource-module strings in actual code (comments removed), de-duplicated.
|
|
23
|
+
* Exported for the engine-aware resolution preflight, which must recognise
|
|
24
|
+
* exactly the same specifier shapes this queue-level rule does — two
|
|
25
|
+
* extractors would drift.
|
|
26
|
+
*/
|
|
27
|
+
export function extractResourceModuleSpecifiers(content) {
|
|
19
28
|
const stripped = stripJsComments(content);
|
|
20
29
|
const found = new Set();
|
|
21
30
|
const pattern = /["'](resource:\/\/(?:\/|gre\/)modules\/[^"']+\.sys\.mjs(?:[?#][^"']*)?)["']/g;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-patch lint check for moz.build sorted-list violations
|
|
2
|
+
* Per-patch lint check for moz.build sorted-list violations.
|
|
3
3
|
*
|
|
4
4
|
* mozbuild enforces alphabetical ordering on `StrictOrderingOnAppendList`
|
|
5
5
|
* variables and raises `mozbuild.util.UnsortedError: … expected "X" but got
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Per-patch lint check for moz.build sorted-list violations
|
|
3
|
+
* Per-patch lint check for moz.build sorted-list violations.
|
|
4
4
|
*
|
|
5
5
|
* mozbuild enforces alphabetical ordering on `StrictOrderingOnAppendList`
|
|
6
6
|
* variables and raises `mozbuild.util.UnsortedError: … expected "X" but got
|
|
@@ -42,7 +42,7 @@ export declare function resolvePatchOwnedSysMjs(currentNewFiles: Set<string>, pa
|
|
|
42
42
|
*/
|
|
43
43
|
export declare function resolvePatchOwnedChromeScripts(currentNewFiles: Set<string>, patchQueueCtx?: PatchQueueContext): Set<string>;
|
|
44
44
|
/**
|
|
45
|
-
* Test-script predicate for the `checkJsTestFiles` pass
|
|
45
|
+
* Test-script predicate for the `checkJsTestFiles` pass:
|
|
46
46
|
* plain `.js` files (not `.sys.mjs` modules) that live under a `/test/`
|
|
47
47
|
* path or carry a `browser_` / `test_` / `xpcshell_` basename. Duplicates
|
|
48
48
|
* `patch-lint.ts`'s `isTestFile` shape rather than importing it —
|
|
@@ -52,7 +52,7 @@ export declare function resolvePatchOwnedChromeScripts(currentNewFiles: Set<stri
|
|
|
52
52
|
*/
|
|
53
53
|
export declare function isTestScriptFile(file: string): boolean;
|
|
54
54
|
/**
|
|
55
|
-
* Returns the set of patch-owned test `.js` files
|
|
55
|
+
* Returns the set of patch-owned test `.js` files. Same
|
|
56
56
|
* ownership semantics as {@link resolvePatchOwnedSysMjs}; consumed by the
|
|
57
57
|
* `patchLint.checkJsTestFiles` pass, which checks each as its own small
|
|
58
58
|
* script-scope program.
|
|
@@ -68,7 +68,7 @@ export function resolvePatchOwnedChromeScripts(currentNewFiles, patchQueueCtx) {
|
|
|
68
68
|
return resolveOwned(currentNewFiles, patchQueueCtx, (file) => file.endsWith('.js') && !file.endsWith('.sys.mjs'));
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
* Test-script predicate for the `checkJsTestFiles` pass
|
|
71
|
+
* Test-script predicate for the `checkJsTestFiles` pass:
|
|
72
72
|
* plain `.js` files (not `.sys.mjs` modules) that live under a `/test/`
|
|
73
73
|
* path or carry a `browser_` / `test_` / `xpcshell_` basename. Duplicates
|
|
74
74
|
* `patch-lint.ts`'s `isTestFile` shape rather than importing it —
|
|
@@ -85,7 +85,7 @@ export function isTestScriptFile(file) {
|
|
|
85
85
|
return /^(?:browser_|test_|xpcshell_).*\.js$/.test(basename);
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
88
|
-
* Returns the set of patch-owned test `.js` files
|
|
88
|
+
* Returns the set of patch-owned test `.js` files. Same
|
|
89
89
|
* ownership semantics as {@link resolvePatchOwnedSysMjs}; consumed by the
|
|
90
90
|
* `patchLint.checkJsTestFiles` pass, which checks each as its own small
|
|
91
91
|
* script-scope program.
|
|
@@ -137,7 +137,7 @@ export type PatchSizeTierDecision = {
|
|
|
137
137
|
*/
|
|
138
138
|
export declare function resolvePatchSizeTier(filesAffected: ReadonlyArray<string>, patchTier?: 'branding'): PatchSizeTierDecision;
|
|
139
139
|
/**
|
|
140
|
-
* Read-only view of the size thresholds a tier enforces
|
|
140
|
+
* Read-only view of the size thresholds a tier enforces.
|
|
141
141
|
* Public-API companion to {@link resolvePatchSizeTier} /
|
|
142
142
|
* {@link countNonBinaryDiffLines} so consumers can build waiver-freshness
|
|
143
143
|
* checks against the SAME numbers `large-patch-lines` /
|
|
@@ -204,7 +204,7 @@ export interface LintExportedPatchOptions {
|
|
|
204
204
|
precomputedCheckJs?: readonly PatchLintIssue[];
|
|
205
205
|
/**
|
|
206
206
|
* Invoked with the issues DROPPED by the `ignoreChecks` waiver filter,
|
|
207
|
-
* when any were
|
|
207
|
+
* when any were. Suppression semantics are unchanged — the
|
|
208
208
|
* returned issue list still excludes them — but the caller can report
|
|
209
209
|
* the measurement a waived size finding carried (per-patch NOTICE lines
|
|
210
210
|
* and the `--report` JSON) instead of losing it entirely.
|
|
@@ -145,7 +145,7 @@ const PATCH_FILES_THRESHOLDS = {
|
|
|
145
145
|
branding: 60,
|
|
146
146
|
};
|
|
147
147
|
/**
|
|
148
|
-
* Shared remedy sentence for the over-budget findings
|
|
148
|
+
* Shared remedy sentence for the over-budget findings: the two
|
|
149
149
|
* places an operator stands when a patch outgrows its budget must name the
|
|
150
150
|
* sanctioned one-transaction way out, or they reach for the wrong tools
|
|
151
151
|
* first (the field report tried two before finding it). One constant, four
|
|
@@ -560,7 +560,7 @@ export function resolvePatchSizeTier(filesAffected, patchTier) {
|
|
|
560
560
|
return { tier: 'general' };
|
|
561
561
|
}
|
|
562
562
|
/**
|
|
563
|
-
* Read-only view of the size thresholds a tier enforces
|
|
563
|
+
* Read-only view of the size thresholds a tier enforces.
|
|
564
564
|
* Public-API companion to {@link resolvePatchSizeTier} /
|
|
565
565
|
* {@link countNonBinaryDiffLines} so consumers can build waiver-freshness
|
|
566
566
|
* checks against the SAME numbers `large-patch-lines` /
|
|
@@ -735,7 +735,7 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
735
735
|
}
|
|
736
736
|
else if (config.patchLint?.checkJs) {
|
|
737
737
|
issues.push(...(await invokePatchLintCheckJs(repoDir, patchOwnedFiles, config.patchLint, dirname(repoDir), options?.checkJsReportScope)));
|
|
738
|
-
// Export-time twin of per-patch lint's test-file pass
|
|
738
|
+
// Export-time twin of per-patch lint's test-file pass, so
|
|
739
739
|
// export/re-export and `lint --per-patch` agree on the checked surface.
|
|
740
740
|
if (config.patchLint.checkJsTestFiles === true) {
|
|
741
741
|
const testGrouped = await runCheckJsTestFilesGrouped(repoDir, resolvePatchOwnedTestScripts(newFiles, patchQueueCtx), config.patchLint, dirname(repoDir));
|
|
@@ -12,6 +12,27 @@ export interface PatchDirectoryLockOptions {
|
|
|
12
12
|
/** Command name recorded in the lock's owner metadata for holder diagnostics. */
|
|
13
13
|
command?: string | undefined;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Asserts that this process is inside {@link withPatchDirectoryLock} for the
|
|
17
|
+
* given patches directory.
|
|
18
|
+
*
|
|
19
|
+
* The manifest mutators document "under the caller's lock" and are reached
|
|
20
|
+
* from six different command bodies, none of which the mutator can see. This
|
|
21
|
+
* turns that comment into a check at the point it matters: filename
|
|
22
|
+
* allocation and manifest read-modify-writes are only atomic while the lock
|
|
23
|
+
* is held, so an unlocked caller silently reintroduces the interleaving the
|
|
24
|
+
* lock exists to prevent.
|
|
25
|
+
*
|
|
26
|
+
* Fail-open on an unreadable owner record, deliberately: `withFileLock`
|
|
27
|
+
* treats writing that record as non-fatal, so a live holder can legitimately
|
|
28
|
+
* have no readable PID and asserting on its presence would fire on a lock we
|
|
29
|
+
* really do hold.
|
|
30
|
+
*
|
|
31
|
+
* @param patchesDir - The patches directory whose lock must be held
|
|
32
|
+
* @param context - What is about to be done, for the failure message
|
|
33
|
+
* @throws {@link InternalInvariantError} when the lock is not held by us.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assertPatchDirectoryLockHeld(patchesDir: string, context: string): Promise<void>;
|
|
15
36
|
/**
|
|
16
37
|
* Runs a patch directory mutation while holding an exclusive filesystem lock.
|
|
17
38
|
* This serializes filename allocation and manifest writes across parallel exports.
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { FireForgeError } from '../errors/base.js';
|
|
7
7
|
import { PatchError } from '../errors/patch.js';
|
|
8
|
+
import { assert } from '../utils/assert.js';
|
|
8
9
|
import { toError } from '../utils/errors.js';
|
|
9
10
|
import { info } from '../utils/logger.js';
|
|
10
|
-
import { withFileLock } from './file-lock.js';
|
|
11
|
+
import { readLockStatus, withFileLock } from './file-lock.js';
|
|
11
12
|
const PATCH_DIRECTORY_LOCK = '.fireforge-patches.lock';
|
|
12
13
|
/**
|
|
13
14
|
* Formats the periodic operator-facing waiting line printed while
|
|
@@ -22,6 +23,32 @@ function formatWaitProgressLine(waitedMs, timeoutMs, holder) {
|
|
|
22
23
|
const details = holder.metadata.length > 0 ? ` (${holder.metadata.join(', ')})` : '';
|
|
23
24
|
return `Waiting for the FireForge patch directory lock held by PID ${String(holder.pid)}${details} — ${progress}.`;
|
|
24
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Asserts that this process is inside {@link withPatchDirectoryLock} for the
|
|
28
|
+
* given patches directory.
|
|
29
|
+
*
|
|
30
|
+
* The manifest mutators document "under the caller's lock" and are reached
|
|
31
|
+
* from six different command bodies, none of which the mutator can see. This
|
|
32
|
+
* turns that comment into a check at the point it matters: filename
|
|
33
|
+
* allocation and manifest read-modify-writes are only atomic while the lock
|
|
34
|
+
* is held, so an unlocked caller silently reintroduces the interleaving the
|
|
35
|
+
* lock exists to prevent.
|
|
36
|
+
*
|
|
37
|
+
* Fail-open on an unreadable owner record, deliberately: `withFileLock`
|
|
38
|
+
* treats writing that record as non-fatal, so a live holder can legitimately
|
|
39
|
+
* have no readable PID and asserting on its presence would fire on a lock we
|
|
40
|
+
* really do hold.
|
|
41
|
+
*
|
|
42
|
+
* @param patchesDir - The patches directory whose lock must be held
|
|
43
|
+
* @param context - What is about to be done, for the failure message
|
|
44
|
+
* @throws {@link InternalInvariantError} when the lock is not held by us.
|
|
45
|
+
*/
|
|
46
|
+
export async function assertPatchDirectoryLockHeld(patchesDir, context) {
|
|
47
|
+
const status = await readLockStatus(join(patchesDir, PATCH_DIRECTORY_LOCK));
|
|
48
|
+
assert(status.held, () => `patch directory lock is held before ${context}`);
|
|
49
|
+
assert(status.holder === undefined || status.holder.pid === process.pid, () => `patch directory lock is owned by this process before ${context} ` +
|
|
50
|
+
`(held by PID ${String(status.holder?.pid)}, we are ${String(process.pid)})`);
|
|
51
|
+
}
|
|
25
52
|
/**
|
|
26
53
|
* Runs a patch directory mutation while holding an exclusive filesystem lock.
|
|
27
54
|
* This serializes filename allocation and manifest writes across parallel exports.
|
|
@@ -44,7 +71,7 @@ export async function withPatchDirectoryLock(patchesDir, operation, options = {}
|
|
|
44
71
|
...(command !== undefined
|
|
45
72
|
? { ownerMetadata: [`command=${command}`, `started=${new Date().toISOString()}`] }
|
|
46
73
|
: {}),
|
|
47
|
-
// Reason first, remedy second
|
|
74
|
+
// Reason first, remedy second. The remedy is waiting, not
|
|
48
75
|
// deleting: bulk exports/re-exports legitimately hold this lock for
|
|
49
76
|
// minutes, FireForge reaps genuinely stale locks on its own, and the
|
|
50
77
|
// old advice ("rm -rf" while a holder was alive) destroyed a live
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { FireForgeError } from '../errors/base.js';
|
|
10
10
|
import type { PatchesManifest, PatchMetadata } from '../types/commands/index.js';
|
|
11
|
+
import type { JsonObject } from '../types/json.js';
|
|
11
12
|
/** Filename for the patches manifest */
|
|
12
13
|
export declare const PATCHES_MANIFEST = "patches.json";
|
|
13
14
|
/** Internal state returned by loadPatchesManifestState. */
|
|
@@ -69,7 +70,7 @@ export declare function savePatchesManifest(patchesDir: string, manifest: Patche
|
|
|
69
70
|
* `sourceVersion` derived from legacy `sourceEsrVersion`, into unrelated patch
|
|
70
71
|
* entries during partial metadata updates.
|
|
71
72
|
*/
|
|
72
|
-
export declare function mutatePatchRowsInManifest(patchesDir: string, filenames: readonly string[], mutator: (existing: PatchMetadata, rawExisting: Readonly<
|
|
73
|
+
export declare function mutatePatchRowsInManifest(patchesDir: string, filenames: readonly string[], mutator: (existing: PatchMetadata, rawExisting: Readonly<JsonObject>) => PatchManifestRowMutation | null): Promise<PatchManifestRowMutationResult[] | null>;
|
|
73
74
|
/**
|
|
74
75
|
* Adds or updates a patch entry in the manifest.
|
|
75
76
|
* @param patchesDir - Path to the patches directory
|
|
@@ -13,10 +13,12 @@ import { join } from 'node:path';
|
|
|
13
13
|
import { FireForgeError } from '../errors/base.js';
|
|
14
14
|
import { ExitCode } from '../errors/codes.js';
|
|
15
15
|
import { PatchManifestCorruptError } from '../errors/patch.js';
|
|
16
|
+
import { assert } from '../utils/assert.js';
|
|
16
17
|
import { toError } from '../utils/errors.js';
|
|
17
18
|
import { pathExistsStrict, readJson, removeFile, writeJson } from '../utils/fs.js';
|
|
18
19
|
import { warn } from '../utils/logger.js';
|
|
19
20
|
import { isArray, isObject } from '../utils/validation.js';
|
|
21
|
+
import { assertPatchDirectoryLockHeld } from './patch-lock.js';
|
|
20
22
|
import { validatePatchesManifest } from './patch-manifest-validate.js';
|
|
21
23
|
/** Filename for the patches manifest */
|
|
22
24
|
export const PATCHES_MANIFEST = 'patches.json';
|
|
@@ -117,6 +119,11 @@ export async function mutatePatchRowsInManifest(patchesDir, filenames, mutator)
|
|
|
117
119
|
const existing = beforeManifest.patches[index];
|
|
118
120
|
if (!existing)
|
|
119
121
|
continue;
|
|
122
|
+
// Manifest rows come out of JSON.parse and were object-checked above,
|
|
123
|
+
// so the JsonObject contract holds. The row stays a plain record
|
|
124
|
+
// internally because `mutation.set` values carry interface types
|
|
125
|
+
// (`Partial<PatchMetadata>`) that the compiler cannot relate to
|
|
126
|
+
// `JsonValue` structurally.
|
|
120
127
|
const mutation = mutator(existing, rawPatch);
|
|
121
128
|
if (!mutation)
|
|
122
129
|
continue;
|
|
@@ -191,6 +198,36 @@ export async function removePatchFromManifest(patchesDir, filename) {
|
|
|
191
198
|
await savePatchesManifest(patchesDir, manifest);
|
|
192
199
|
return true;
|
|
193
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Checks the shape a renumbered manifest is supposed to have, immediately
|
|
203
|
+
* before it becomes durable.
|
|
204
|
+
*
|
|
205
|
+
* Three properties, each of which the queue silently depends on: `order` is
|
|
206
|
+
* unique (two patches claiming one slot make apply order arbitrary),
|
|
207
|
+
* `order` matches the numeric prefix of `filename` (the prefix is what an
|
|
208
|
+
* operator reads and what `patch` commands parse back out), and the rows are
|
|
209
|
+
* sorted ascending (readers take manifest order as apply order without
|
|
210
|
+
* re-sorting).
|
|
211
|
+
*
|
|
212
|
+
* Contiguity itself is deliberately NOT checked: `patchPolicy.ranges`
|
|
213
|
+
* reserves gaps on purpose, so a hole is a legitimate queue shape.
|
|
214
|
+
*
|
|
215
|
+
* @param patches - The manifest rows about to be written
|
|
216
|
+
*/
|
|
217
|
+
function assertRenumberedOrderIsWellFormed(patches) {
|
|
218
|
+
const seenOrders = new Set();
|
|
219
|
+
let previousOrder = Number.NEGATIVE_INFINITY;
|
|
220
|
+
for (const patch of patches) {
|
|
221
|
+
assert(!seenOrders.has(patch.order), () => `renumbered patch orders are unique (order ${patch.order} appears twice)`);
|
|
222
|
+
seenOrders.add(patch.order);
|
|
223
|
+
assert(patch.order >= previousOrder, () => `renumbered manifest rows are sorted by order (${patch.filename} is out of place)`);
|
|
224
|
+
previousOrder = patch.order;
|
|
225
|
+
const prefixMatch = /^(\d+)-/.exec(patch.filename);
|
|
226
|
+
assert(prefixMatch !== null, () => `renumbered patch filename carries a numeric prefix (got "${patch.filename}")`);
|
|
227
|
+
assert(Number(prefixMatch[1]) === patch.order, () => `renumbered patch filename prefix matches its order ` +
|
|
228
|
+
`("${patch.filename}" carries order ${patch.order})`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
194
231
|
/**
|
|
195
232
|
* Rewrites `stagedDependencies.forwardImports[].owner` and
|
|
196
233
|
* `stagedDependencies.registrations[].owner` references on one patch
|
|
@@ -275,6 +312,7 @@ export function rewriteStagedDependencyOwners(patch, renameLookup) {
|
|
|
275
312
|
export async function renumberPatchesInManifest(patchesDir, renameMap) {
|
|
276
313
|
if (renameMap.size === 0)
|
|
277
314
|
return;
|
|
315
|
+
await assertPatchDirectoryLockHeld(patchesDir, 'renumbering the patch manifest');
|
|
278
316
|
const manifest = await loadPatchesManifestForWrite(patchesDir);
|
|
279
317
|
if (!manifest) {
|
|
280
318
|
throw new Error('Cannot renumber patches: patches.json is missing.');
|
|
@@ -389,6 +427,16 @@ export async function renumberPatchesInManifest(patchesDir, renameMap) {
|
|
|
389
427
|
});
|
|
390
428
|
updatedPatches.sort((a, b) => a.order - b.order || a.filename.localeCompare(b.filename));
|
|
391
429
|
try {
|
|
430
|
+
// Phase 3 is the one write that makes the renumber visible. Every file
|
|
431
|
+
// has already moved on disk, so a manifest that disagrees with the new
|
|
432
|
+
// filenames is the worst outcome available here — the queue would apply
|
|
433
|
+
// in an order nothing on disk reflects. The callers compute the rename
|
|
434
|
+
// map (compact, reorder, export placement) and each maintains contiguity
|
|
435
|
+
// on its own; this checks the property they are all supposed to produce,
|
|
436
|
+
// at the single point where it becomes durable. Inside the try, so a
|
|
437
|
+
// violation unwinds through the Phase 3 rollback that puts the filenames
|
|
438
|
+
// back.
|
|
439
|
+
assertRenumberedOrderIsWellFormed(updatedPatches);
|
|
392
440
|
await savePatchesManifest(patchesDir, {
|
|
393
441
|
...manifest,
|
|
394
442
|
patches: updatedPatches,
|
|
@@ -459,6 +507,7 @@ export class PatchDeleteRollbackError extends FireForgeError {
|
|
|
459
507
|
* @param filename - Patch filename to delete
|
|
460
508
|
*/
|
|
461
509
|
export async function removePatchFileAndManifest(patchesDir, filename) {
|
|
510
|
+
await assertPatchDirectoryLockHeld(patchesDir, 'deleting a patch and its manifest row');
|
|
462
511
|
const patchPath = join(patchesDir, filename);
|
|
463
512
|
// ForWrite: deleting the patch FILE while a corrupt manifest still
|
|
464
513
|
// references it would strand the queue; abort on corruption instead.
|
|
@@ -18,6 +18,24 @@
|
|
|
18
18
|
* @returns Order number, or Infinity if no prefix
|
|
19
19
|
*/
|
|
20
20
|
export declare function extractOrder(filename: string): number;
|
|
21
|
+
/**
|
|
22
|
+
* Extracts the order number from a patch filename that is about to be
|
|
23
|
+
* written into the manifest.
|
|
24
|
+
*
|
|
25
|
+
* The write-side counterpart to {@link extractOrder}. That one answers
|
|
26
|
+
* `Infinity` for a prefix-less filename, which sorts such a patch last and
|
|
27
|
+
* is the right answer for a read: an unparseable name should not crash a
|
|
28
|
+
* status listing. Persisting `Infinity` (or the `0` some call sites used to
|
|
29
|
+
* fall back to) is a different matter — it puts an order in the manifest
|
|
30
|
+
* that no filename on disk agrees with, and the queue then applies in an
|
|
31
|
+
* order the operator cannot see. Callers here have always just built or
|
|
32
|
+
* looked up a real prefixed filename, so absence is a bug, not input.
|
|
33
|
+
*
|
|
34
|
+
* @param filename - Patch filename carrying a numeric order prefix
|
|
35
|
+
* @returns The parsed order
|
|
36
|
+
* @throws {@link InternalInvariantError} when `filename` has no numeric prefix.
|
|
37
|
+
*/
|
|
38
|
+
export declare function requirePatchOrder(filename: string): number;
|
|
21
39
|
/**
|
|
22
40
|
* A parsed `diff --git` header: both sides' paths, unquoted and with any
|
|
23
41
|
* CRLF residue stripped.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { assert } from '../utils/assert.js';
|
|
2
3
|
/**
|
|
3
4
|
* Pure parsing functions for extracting information from patch files.
|
|
4
5
|
* All functions are synchronous and operate on string content.
|
|
@@ -25,6 +26,28 @@ export function extractOrder(filename) {
|
|
|
25
26
|
}
|
|
26
27
|
return Infinity;
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Extracts the order number from a patch filename that is about to be
|
|
31
|
+
* written into the manifest.
|
|
32
|
+
*
|
|
33
|
+
* The write-side counterpart to {@link extractOrder}. That one answers
|
|
34
|
+
* `Infinity` for a prefix-less filename, which sorts such a patch last and
|
|
35
|
+
* is the right answer for a read: an unparseable name should not crash a
|
|
36
|
+
* status listing. Persisting `Infinity` (or the `0` some call sites used to
|
|
37
|
+
* fall back to) is a different matter — it puts an order in the manifest
|
|
38
|
+
* that no filename on disk agrees with, and the queue then applies in an
|
|
39
|
+
* order the operator cannot see. Callers here have always just built or
|
|
40
|
+
* looked up a real prefixed filename, so absence is a bug, not input.
|
|
41
|
+
*
|
|
42
|
+
* @param filename - Patch filename carrying a numeric order prefix
|
|
43
|
+
* @returns The parsed order
|
|
44
|
+
* @throws {@link InternalInvariantError} when `filename` has no numeric prefix.
|
|
45
|
+
*/
|
|
46
|
+
export function requirePatchOrder(filename) {
|
|
47
|
+
const match = /^(\d+)-/.exec(filename);
|
|
48
|
+
assert(match?.[1] !== undefined, () => `patch filename carries a numeric order prefix (got "${filename}")`);
|
|
49
|
+
return parseInt(match[1], 10);
|
|
50
|
+
}
|
|
28
51
|
/**
|
|
29
52
|
* Un-escapes a path from git's C-style quoted form (`"a/pfad m\303\244..."`).
|
|
30
53
|
* Handles the escapes git emits: `\\`, `\"`, `\t`, `\n`, `\r` and three-digit
|
|
@@ -172,7 +195,7 @@ export function parseDiffSections(diffContent) {
|
|
|
172
195
|
// `git diff --binary` always records full blob hashes here (git
|
|
173
196
|
// apply requires them); text sections carry abbreviated ones. The
|
|
174
197
|
// hashes let binary sections be compared by identity when their
|
|
175
|
-
// payload cannot be applied as text
|
|
198
|
+
// payload cannot be applied as text.
|
|
176
199
|
const indexMatch = /^index ([0-9a-f]{7,64})\.\.([0-9a-f]{7,64})(?: \d{6})?$/.exec(line);
|
|
177
200
|
if (indexMatch?.[1] !== undefined && indexMatch[2] !== undefined) {
|
|
178
201
|
current.indexOldHash = indexMatch[1];
|
|
@@ -37,14 +37,14 @@ export declare function getPatchPolicyCategories(config: FireForgeConfig): strin
|
|
|
37
37
|
export declare function isCategoryAllowedByConfig(config: FireForgeConfig, category: string): boolean;
|
|
38
38
|
/**
|
|
39
39
|
* Human-readable label for a category's configured ranges, e.g. `300-399`.
|
|
40
|
-
* Exported for the forward-import hint
|
|
40
|
+
* Exported for the forward-import hint, which must render the
|
|
41
41
|
* range it found no legal ordinal in.
|
|
42
42
|
*/
|
|
43
43
|
export declare function categoryRangeLabel(ranges: readonly PatchPolicyRange[], category: string): string;
|
|
44
44
|
/**
|
|
45
45
|
* Returns the configured range that contains `order` for `category`, or
|
|
46
46
|
* null when no such range exists. Exported for the forward-import hint
|
|
47
|
-
|
|
47
|
+
*, which suppresses ordinal suggestions the reorder policy
|
|
48
48
|
* would refuse.
|
|
49
49
|
*/
|
|
50
50
|
export declare function categoryRangeForOrder(cfg: PatchPolicyConfig, category: string, order: number): PatchPolicyRange | null;
|
|
@@ -47,7 +47,7 @@ function rangeLabel(range) {
|
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* Human-readable label for a category's configured ranges, e.g. `300-399`.
|
|
50
|
-
* Exported for the forward-import hint
|
|
50
|
+
* Exported for the forward-import hint, which must render the
|
|
51
51
|
* range it found no legal ordinal in.
|
|
52
52
|
*/
|
|
53
53
|
export function categoryRangeLabel(ranges, category) {
|
|
@@ -62,7 +62,7 @@ function reservedRangeForOrder(cfg, order) {
|
|
|
62
62
|
/**
|
|
63
63
|
* Returns the configured range that contains `order` for `category`, or
|
|
64
64
|
* null when no such range exists. Exported for the forward-import hint
|
|
65
|
-
|
|
65
|
+
*, which suppresses ordinal suggestions the reorder policy
|
|
66
66
|
* would refuse.
|
|
67
67
|
*/
|
|
68
68
|
export function categoryRangeForOrder(cfg, category, order) {
|
|
@@ -20,7 +20,7 @@ export declare const SMOKE_ERROR_PATTERNS: readonly RegExp[];
|
|
|
20
20
|
export declare function matchesSmokeError(line: string): boolean;
|
|
21
21
|
/**
|
|
22
22
|
* One compiled allowlist entry with its provenance retained so the smoke
|
|
23
|
-
* summary can attribute hits per entry
|
|
23
|
+
* summary can attribute hits per entry — an entry that silently
|
|
24
24
|
* stops matching (its suppressed shape changed upstream) is only
|
|
25
25
|
* detectable when zero-hit entries are visible.
|
|
26
26
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Binary-aware drift classification for patch-owned files
|
|
2
|
+
* Binary-aware drift classification for patch-owned files.
|
|
3
3
|
*
|
|
4
4
|
* The text classifier compares utf-8 decoded content against
|
|
5
5
|
* `computePatchedContent`, which cannot apply `GIT binary patch` bodies —
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Binary-aware drift classification for patch-owned files
|
|
3
|
+
* Binary-aware drift classification for patch-owned files.
|
|
4
4
|
*
|
|
5
5
|
* The text classifier compares utf-8 decoded content against
|
|
6
6
|
* `computePatchedContent`, which cannot apply `GIT binary patch` bodies —
|
|
@@ -59,7 +59,7 @@ export interface ClassifiedFile extends StatusFile {
|
|
|
59
59
|
* (patch-backed, patch-owned-drift, and single-owner furnace entries).
|
|
60
60
|
* Unset for unowned, branding-generated, unowned-furnace, and conflict
|
|
61
61
|
* entries — conflicts carry `claimedBy` instead. Exposed through
|
|
62
|
-
* `status --json` as the `patch` field
|
|
62
|
+
* `status --json` as the `patch` field.
|
|
63
63
|
*/
|
|
64
64
|
owner?: string;
|
|
65
65
|
}
|
|
@@ -76,7 +76,7 @@ async function classifySingleOwnerFile(entry, engineDir, patchesDir, matchClassi
|
|
|
76
76
|
// Binary comparisons cannot go through the utf-8 content path below —
|
|
77
77
|
// binary patch bodies parse to zero hunks, so the patched-content
|
|
78
78
|
// computation returned HEAD content unchanged and the comparison
|
|
79
|
-
// reported `patch-owned-drift` forever
|
|
79
|
+
// reported `patch-owned-drift` forever. Settle by blob hash,
|
|
80
80
|
// or classify explicitly as `binary-unsupported` when no hash exists.
|
|
81
81
|
const binaryResult = await classifyBinaryOwnedFile({
|
|
82
82
|
entry,
|
|
@@ -133,7 +133,7 @@ export async function classifyFiles(files, engineDir, patchesDir, binaryName, fu
|
|
|
133
133
|
// One manifest load + patch discovery + memoized body reads for the
|
|
134
134
|
// whole batch — the previous per-file computation re-ran all three for
|
|
135
135
|
// every dirty file (O(dirtyFiles × patches) redundant IO on a broad
|
|
136
|
-
// engine edit session
|
|
136
|
+
// engine edit session).
|
|
137
137
|
const ctx = await createPatchedContentContext(patchesDir, engineDir);
|
|
138
138
|
// Build a multimap from file path → list of claiming patch
|
|
139
139
|
// filenames so we can detect cross-patch ownership conflicts. The
|
|
@@ -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`
|