@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
|
@@ -7,15 +7,25 @@ import { getPackageVersion } from '../utils/package-root.js';
|
|
|
7
7
|
import { getFurnacePaths } from './furnace-config.js';
|
|
8
8
|
import { git } from './git-base.js';
|
|
9
9
|
import { collectNewFileCreatorsByPath } from './patch-lint.js';
|
|
10
|
-
// Schema 2
|
|
10
|
+
// Schema 2: entries additionally carry the lintIgnore-suppressed
|
|
11
11
|
// issues and the measured non-binary line count, so a warm run can report
|
|
12
12
|
// waived size measurements identically to a cold one (the F5-class hazard:
|
|
13
13
|
// a cache hit must never surface LESS than a fresh lint).
|
|
14
|
-
// Schema 3
|
|
14
|
+
// Schema 3: the key additionally content-hashes the
|
|
15
15
|
// `patchLint.checkJsTestShim` file exactly as `checkJsExtraShim`'s already
|
|
16
16
|
// was — before this, editing the test shim in place replayed every cached
|
|
17
17
|
// verdict (the same warm-run-reports-less-than-cold hazard class).
|
|
18
|
-
|
|
18
|
+
// Schema 4: each entry RECORDS the lint-ignore waiver set that
|
|
19
|
+
// produced it, and a lookup whose effective waiver set differs is refused
|
|
20
|
+
// even when the key matches. The key already hashes `patch.lintIgnore`, so
|
|
21
|
+
// this is belt-and-braces on purpose: a waiver change is the one mutation
|
|
22
|
+
// that makes a replayed verdict actively WRONG (an operator writes a
|
|
23
|
+
// waiver, re-runs, and is shown the finding they just waived — or worse,
|
|
24
|
+
// a pre-waiver verdict replayed under a post-waiver key), and the class
|
|
25
|
+
// must not depend on every future key input staying complete. Bumping the
|
|
26
|
+
// schema also discards every pre-4 entry, which is itself the cure for a
|
|
27
|
+
// cache that already holds a mismatched verdict.
|
|
28
|
+
export const LINT_CACHE_SCHEMA_VERSION = 4;
|
|
19
29
|
const LINT_IMPLEMENTATION_VERSION = 1;
|
|
20
30
|
const LINT_CACHE_DIRNAME = 'lint-cache';
|
|
21
31
|
const PER_PATCH_CACHE_FILENAME = 'per-patch-v1.json';
|
|
@@ -108,7 +118,7 @@ export async function buildPerPatchLintCacheKey(input) {
|
|
|
108
118
|
: { path: shimPath, hash: await fileHash(resolve(input.projectRoot, shimPath)) };
|
|
109
119
|
// Both shims feed compiled checkJs programs, so both are content-hashed —
|
|
110
120
|
// the config block alone only carries their PATHS, and an in-place edit
|
|
111
|
-
// must invalidate the programs it feeds
|
|
121
|
+
// must invalidate the programs it feeds.
|
|
112
122
|
const [extraShim, testShim] = await Promise.all([
|
|
113
123
|
shimHashOf(input.config.patchLint?.checkJsExtraShim),
|
|
114
124
|
shimHashOf(input.config.patchLint?.checkJsTestShim),
|
|
@@ -118,7 +128,7 @@ export async function buildPerPatchLintCacheKey(input) {
|
|
|
118
128
|
engineHeadSha: input.engineHeadSha ?? null,
|
|
119
129
|
lintImplementationVersion: LINT_IMPLEMENTATION_VERSION,
|
|
120
130
|
// Deliberately the PLAIN semver, not the +g<sha> build identity
|
|
121
|
-
|
|
131
|
+
//: identity churns every commit at the same version and
|
|
122
132
|
// would invalidate the cache for no correctness gain.
|
|
123
133
|
packageVersion: input.packageVersion ?? getPackageVersion(),
|
|
124
134
|
patchFile: await fileHash(join(input.patchesDir, input.patch.filename)),
|
|
@@ -144,6 +154,8 @@ function isCacheEntry(value) {
|
|
|
144
154
|
Array.isArray(entry.issues) &&
|
|
145
155
|
Array.isArray(entry.suppressed) &&
|
|
146
156
|
typeof entry.lineCount === 'number' &&
|
|
157
|
+
Array.isArray(entry.lintIgnore) &&
|
|
158
|
+
entry.lintIgnore.every((id) => typeof id === 'string') &&
|
|
147
159
|
typeof entry.updatedAt === 'string');
|
|
148
160
|
}
|
|
149
161
|
/** Loads the per-patch lint cache, treating missing or invalid files as empty. */
|
|
@@ -187,11 +199,25 @@ export async function savePerPatchLintCache(projectRoot, cache) {
|
|
|
187
199
|
export async function clearPerPatchLintCache(projectRoot) {
|
|
188
200
|
await writeJson(getPerPatchLintCachePath(projectRoot), createEmptyPerPatchLintCache());
|
|
189
201
|
}
|
|
190
|
-
/**
|
|
191
|
-
|
|
202
|
+
/** Canonical (sorted, de-duplicated) form of a waiver set for comparison. */
|
|
203
|
+
function normalizeLintIgnoreSet(lintIgnore) {
|
|
204
|
+
return [...new Set(lintIgnore ?? [])].sort((a, b) => a.localeCompare(b));
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Returns the cached lint payload for a patch when the stored key still
|
|
208
|
+
* matches AND the entry was computed under the same lint-ignore waiver set
|
|
209
|
+
*. A waiver change makes a replayed verdict actively wrong, so
|
|
210
|
+
* the waiver set is compared explicitly rather than trusted to the key.
|
|
211
|
+
*/
|
|
212
|
+
export function getCachedPerPatchLintIssues(cache, patchFilename, key, lintIgnore) {
|
|
192
213
|
const entry = cache.entries[patchFilename];
|
|
193
214
|
if (!entry || entry.key !== key)
|
|
194
215
|
return undefined;
|
|
216
|
+
const expected = normalizeLintIgnoreSet(lintIgnore);
|
|
217
|
+
const stored = normalizeLintIgnoreSet(entry.lintIgnore);
|
|
218
|
+
if (expected.length !== stored.length || expected.some((id, i) => id !== stored[i])) {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
195
221
|
return {
|
|
196
222
|
issues: entry.issues.map((issue) => ({ ...issue })),
|
|
197
223
|
suppressed: entry.suppressed.map((issue) => ({ ...issue })),
|
|
@@ -199,13 +225,14 @@ export function getCachedPerPatchLintIssues(cache, patchFilename, key) {
|
|
|
199
225
|
};
|
|
200
226
|
}
|
|
201
227
|
/** Stores the per-patch lint payload after a successful lint calculation. */
|
|
202
|
-
export function setCachedPerPatchLintIssues(cache, patchFilename, key, issues, suppressed, lineCount) {
|
|
228
|
+
export function setCachedPerPatchLintIssues(cache, patchFilename, key, issues, suppressed, lineCount, lintIgnore) {
|
|
203
229
|
cache.entries[patchFilename] = {
|
|
204
230
|
key,
|
|
205
231
|
patchFilename,
|
|
206
232
|
issues: issues.map((issue) => ({ ...issue })),
|
|
207
233
|
suppressed: suppressed.map((issue) => ({ ...issue })),
|
|
208
234
|
lineCount,
|
|
235
|
+
lintIgnore: normalizeLintIgnoreSet(lintIgnore),
|
|
209
236
|
updatedAt: new Date().toISOString(),
|
|
210
237
|
};
|
|
211
238
|
}
|
|
@@ -27,6 +27,27 @@
|
|
|
27
27
|
* - The hold buffer is bounded; on overflow the block is flushed verbatim
|
|
28
28
|
* and the filter returns to pass-through, so output is never lost.
|
|
29
29
|
*/
|
|
30
|
+
/**
|
|
31
|
+
* True when CAPTURED output carries the documented mozsystemmonitor
|
|
32
|
+
* teardown traceback.
|
|
33
|
+
*
|
|
34
|
+
* The echo filter above only affects what a human sees. The CLASSIFIER
|
|
35
|
+
* reads the raw capture, and it needs the same recognition to tell "the
|
|
36
|
+
* suite finished clean and then upstream fell over at shutdown" from "the
|
|
37
|
+
* suite did not finish". Deliberately the same two-signal test the echo
|
|
38
|
+
* filter applies — a novel attribute or a traceback from anywhere but
|
|
39
|
+
* `resourcemonitor.py` is NOT this incident and must keep failing the run.
|
|
40
|
+
*
|
|
41
|
+
* The shutdown-marker precondition the echo filter adds is intentionally
|
|
42
|
+
* omitted: the whole point at the classification layer is the case where
|
|
43
|
+
* the teardown crash prevented the shutdown marker from printing.
|
|
44
|
+
*
|
|
45
|
+
* Pure; exported for the classifier and for direct unit testing.
|
|
46
|
+
*
|
|
47
|
+
* @param output - Raw captured stdout/stderr
|
|
48
|
+
* @returns True when the recognized teardown traceback is present
|
|
49
|
+
*/
|
|
50
|
+
export declare function hasKnownTeardownNoise(output: string): boolean;
|
|
30
51
|
/**
|
|
31
52
|
* Shared across the stdout and stderr filter instances of ONE mach run:
|
|
32
53
|
* SUITE_END typically arrives on stdout while the teardown traceback lands
|
|
@@ -49,6 +49,29 @@ const SHUTDOWN_MARKER_PATTERN = /\bSUITE_END\b/;
|
|
|
49
49
|
function isRecognizedTeardownNoise(block) {
|
|
50
50
|
return (KNOWN_TEARDOWN_ATTRIBUTE_ERROR_PATTERN.test(block) && RESOURCEMONITOR_FRAME_PATTERN.test(block));
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* True when CAPTURED output carries the documented mozsystemmonitor
|
|
54
|
+
* teardown traceback.
|
|
55
|
+
*
|
|
56
|
+
* The echo filter above only affects what a human sees. The CLASSIFIER
|
|
57
|
+
* reads the raw capture, and it needs the same recognition to tell "the
|
|
58
|
+
* suite finished clean and then upstream fell over at shutdown" from "the
|
|
59
|
+
* suite did not finish". Deliberately the same two-signal test the echo
|
|
60
|
+
* filter applies — a novel attribute or a traceback from anywhere but
|
|
61
|
+
* `resourcemonitor.py` is NOT this incident and must keep failing the run.
|
|
62
|
+
*
|
|
63
|
+
* The shutdown-marker precondition the echo filter adds is intentionally
|
|
64
|
+
* omitted: the whole point at the classification layer is the case where
|
|
65
|
+
* the teardown crash prevented the shutdown marker from printing.
|
|
66
|
+
*
|
|
67
|
+
* Pure; exported for the classifier and for direct unit testing.
|
|
68
|
+
*
|
|
69
|
+
* @param output - Raw captured stdout/stderr
|
|
70
|
+
* @returns True when the recognized teardown traceback is present
|
|
71
|
+
*/
|
|
72
|
+
export function hasKnownTeardownNoise(output) {
|
|
73
|
+
return isRecognizedTeardownNoise(output);
|
|
74
|
+
}
|
|
52
75
|
/** Fresh per-run context — hand the same instance to both stream filters. */
|
|
53
76
|
export function createTeardownNoiseContext() {
|
|
54
77
|
return { shutdownSeen: false };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { MozconfigError } from '../errors/build.js';
|
|
4
|
-
import { pathExists, readText,
|
|
4
|
+
import { pathExists, readText, writeTextIfChanged } from '../utils/fs.js';
|
|
5
|
+
import { verbose } from '../utils/logger.js';
|
|
5
6
|
import { getPlatform } from '../utils/platform.js';
|
|
6
7
|
import { BrandingMozconfigMismatchError, splitAppId } from './branding.js';
|
|
7
8
|
/**
|
|
@@ -114,7 +115,16 @@ export async function generateMozconfig(configsDir, engineDir, config) {
|
|
|
114
115
|
}
|
|
115
116
|
const platformContent = await readText(platformPath);
|
|
116
117
|
content += `# Platform configuration (${platform})\n${replaceVariables(platformContent, variables)}`;
|
|
117
|
-
|
|
118
|
+
// Write-if-changed: `mozconfig` is a mach CONFIGURE INPUT, so
|
|
119
|
+
// its mtime — not its content — is what `config.status` compares against.
|
|
120
|
+
// Rewriting a byte-identical file on every build-capable invocation made
|
|
121
|
+
// mach re-run `configure` plus backend regeneration ("Backend config
|
|
122
|
+
// changed; N files touched") on EVERY run, each run re-arming the next.
|
|
123
|
+
// Only touch the file when the rendered content actually differs.
|
|
124
|
+
const wrote = await writeTextIfChanged(outputPath, content);
|
|
125
|
+
if (!wrote) {
|
|
126
|
+
verbose(`mozconfig already up to date (content unchanged): ${outputPath}`);
|
|
127
|
+
}
|
|
118
128
|
// Preflight: the mozconfig we just wrote must reference the branding
|
|
119
129
|
// directory FireForge actually set up. Catching the drift here (after the
|
|
120
130
|
// write, before anything consumes mozconfig) keeps `generateMozconfig`
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine-aware resolution preflight for queue-owned system modules.
|
|
3
|
+
*
|
|
4
|
+
* An `.sys.mjs` that a packaged module imports but whose `EXTRA_JS_MODULES`
|
|
5
|
+
* registration never landed kills every xpcshell suite
|
|
6
|
+
* with `xpcshell return code: -11` and ZERO output — no import error, no
|
|
7
|
+
* stack, nothing. The 0.41.0 `unregistered-system-module` check catches
|
|
8
|
+
* this class, but only in one shape: a module NEWLY CREATED by a patch in
|
|
9
|
+
* the projected queue. The class recurred through the other shape — three
|
|
10
|
+
* modules imported from an ALREADY-EXISTING module before their moz.build
|
|
11
|
+
* lines landed — and cost a full rebuild cycle before anyone recognised it.
|
|
12
|
+
*
|
|
13
|
+
* This preflight closes that gap by asking the question against the ENGINE
|
|
14
|
+
* rather than against a projected diff: for every `resource:///modules/…`
|
|
15
|
+
* specifier imported by any queue-owned module, does the module it names
|
|
16
|
+
* exist, and is it registered in a moz.build that covers it?
|
|
17
|
+
*
|
|
18
|
+
* Scope is the queue-owned file set on purpose. A specifier that resolves
|
|
19
|
+
* to no owned file is an UPSTREAM Firefox module, which FireForge does not
|
|
20
|
+
* police and cannot cheaply enumerate; policing only what the fork owns is
|
|
21
|
+
* both the actionable set and the one that produced every recorded
|
|
22
|
+
* incident.
|
|
23
|
+
*/
|
|
24
|
+
/** Why a queue-owned module import does not resolve to a packaged module. */
|
|
25
|
+
export type UnresolvedModuleReason = 'missing-file' | 'unregistered';
|
|
26
|
+
/** One unresolvable or unregistered system-module import. */
|
|
27
|
+
export interface UnresolvedModuleImport {
|
|
28
|
+
/** Engine-relative path of the module the specifier names. */
|
|
29
|
+
module: string;
|
|
30
|
+
/** The `resource://` specifier as written. */
|
|
31
|
+
specifier: string;
|
|
32
|
+
/** Engine-relative paths of the files that import it, sorted. */
|
|
33
|
+
importers: string[];
|
|
34
|
+
reason: UnresolvedModuleReason;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolves every `resource://` system-module import made by the
|
|
38
|
+
* queue-owned files and reports the ones a packaged browser could not
|
|
39
|
+
* load.
|
|
40
|
+
*
|
|
41
|
+
* @param engineDir - Path to the engine checkout
|
|
42
|
+
* @param ownedFiles - Engine-relative paths the patch queue owns
|
|
43
|
+
* @returns Findings sorted by module path; empty when everything resolves
|
|
44
|
+
*/
|
|
45
|
+
export declare function findUnresolvedSystemModuleImports(engineDir: string, ownedFiles: readonly string[]): Promise<UnresolvedModuleImport[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Renders findings as operator-facing lines, naming the unregistered or
|
|
48
|
+
* unresolvable module, so the failure is a sentence instead of a bare
|
|
49
|
+
* `-11`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function formatUnresolvedSystemModuleImports(findings: readonly UnresolvedModuleImport[]): string[];
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Engine-aware resolution preflight for queue-owned system modules.
|
|
4
|
+
*
|
|
5
|
+
* An `.sys.mjs` that a packaged module imports but whose `EXTRA_JS_MODULES`
|
|
6
|
+
* registration never landed kills every xpcshell suite
|
|
7
|
+
* with `xpcshell return code: -11` and ZERO output — no import error, no
|
|
8
|
+
* stack, nothing. The 0.41.0 `unregistered-system-module` check catches
|
|
9
|
+
* this class, but only in one shape: a module NEWLY CREATED by a patch in
|
|
10
|
+
* the projected queue. The class recurred through the other shape — three
|
|
11
|
+
* modules imported from an ALREADY-EXISTING module before their moz.build
|
|
12
|
+
* lines landed — and cost a full rebuild cycle before anyone recognised it.
|
|
13
|
+
*
|
|
14
|
+
* This preflight closes that gap by asking the question against the ENGINE
|
|
15
|
+
* rather than against a projected diff: for every `resource:///modules/…`
|
|
16
|
+
* specifier imported by any queue-owned module, does the module it names
|
|
17
|
+
* exist, and is it registered in a moz.build that covers it?
|
|
18
|
+
*
|
|
19
|
+
* Scope is the queue-owned file set on purpose. A specifier that resolves
|
|
20
|
+
* to no owned file is an UPSTREAM Firefox module, which FireForge does not
|
|
21
|
+
* police and cannot cheaply enumerate; policing only what the fork owns is
|
|
22
|
+
* both the actionable set and the one that produced every recorded
|
|
23
|
+
* incident.
|
|
24
|
+
*/
|
|
25
|
+
import { dirname, join } from 'node:path';
|
|
26
|
+
import { toError } from '../utils/errors.js';
|
|
27
|
+
import { pathExists, readText } from '../utils/fs.js';
|
|
28
|
+
import { verbose } from '../utils/logger.js';
|
|
29
|
+
import { escapeRegex } from '../utils/regex.js';
|
|
30
|
+
import { extractResourceModuleSpecifiers, moduleSourceSuffix, } from './patch-lint-module-registration.js';
|
|
31
|
+
/** Extensions whose contents are scanned for resource-module imports. */
|
|
32
|
+
const IMPORTABLE_EXTENSIONS = ['.mjs', '.sys.mjs', '.js', '.jsm'];
|
|
33
|
+
/**
|
|
34
|
+
* Reads a file, returning undefined (with a verbose note) rather than
|
|
35
|
+
* throwing: a preflight must not fail a command over one unreadable path.
|
|
36
|
+
*/
|
|
37
|
+
async function readIfPresent(path) {
|
|
38
|
+
try {
|
|
39
|
+
if (!(await pathExists(path)))
|
|
40
|
+
return undefined;
|
|
41
|
+
return await readText(path);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
verbose(`Module resolution preflight: could not read ${path} — ${toError(error).message}`);
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* True when some `moz.build` at or above `moduleRelPath`'s directory (up
|
|
50
|
+
* to, and including, the engine root) mentions the module's basename.
|
|
51
|
+
*
|
|
52
|
+
* Walking ancestors — rather than checking only the sibling `moz.build` —
|
|
53
|
+
* matches how Mozilla's build system actually works: a module list may sit
|
|
54
|
+
* in a parent directory's `moz.build` with a path-qualified entry. The
|
|
55
|
+
* check is a quoted-basename match for the same reason the queue-level
|
|
56
|
+
* rule uses one: parsing moz.build's Python is not worth it for a
|
|
57
|
+
* preflight, and a quoted basename has no meaningful false-positive shape.
|
|
58
|
+
*/
|
|
59
|
+
async function isRegisteredInAncestorMozBuild(engineDir, moduleRelPath, mozBuildCache) {
|
|
60
|
+
const leaf = moduleRelPath.slice(moduleRelPath.lastIndexOf('/') + 1);
|
|
61
|
+
// The entry may be the bare basename (`"Foo.sys.mjs"`, sibling
|
|
62
|
+
// moz.build) or path-qualified relative to the moz.build that declares
|
|
63
|
+
// it (`"sub/Foo.sys.mjs"`, ancestor moz.build). Both are the same
|
|
64
|
+
// registration; anchor on the closing quote and require a path boundary
|
|
65
|
+
// so `NotFoo.sys.mjs` cannot satisfy `Foo.sys.mjs`.
|
|
66
|
+
const quotedLeaf = new RegExp(`["'](?:[^"']*/)?${escapeRegex(leaf)}["']`);
|
|
67
|
+
let dir = dirname(moduleRelPath);
|
|
68
|
+
for (;;) {
|
|
69
|
+
const key = dir === '.' ? '' : dir;
|
|
70
|
+
if (!mozBuildCache.has(key)) {
|
|
71
|
+
mozBuildCache.set(key, await readIfPresent(join(engineDir, key, 'moz.build')));
|
|
72
|
+
}
|
|
73
|
+
const content = mozBuildCache.get(key);
|
|
74
|
+
if (content !== undefined && quotedLeaf.test(content))
|
|
75
|
+
return true;
|
|
76
|
+
if (dir === '.' || dir === '' || dir === '/')
|
|
77
|
+
return false;
|
|
78
|
+
const parent = dirname(dir);
|
|
79
|
+
if (parent === dir)
|
|
80
|
+
return false;
|
|
81
|
+
dir = parent;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Resolves every `resource://` system-module import made by the
|
|
86
|
+
* queue-owned files and reports the ones a packaged browser could not
|
|
87
|
+
* load.
|
|
88
|
+
*
|
|
89
|
+
* @param engineDir - Path to the engine checkout
|
|
90
|
+
* @param ownedFiles - Engine-relative paths the patch queue owns
|
|
91
|
+
* @returns Findings sorted by module path; empty when everything resolves
|
|
92
|
+
*/
|
|
93
|
+
export async function findUnresolvedSystemModuleImports(engineDir, ownedFiles) {
|
|
94
|
+
const owned = [...new Set(ownedFiles)];
|
|
95
|
+
/** specifier → importing owned files. */
|
|
96
|
+
const importersBySpecifier = new Map();
|
|
97
|
+
for (const file of owned) {
|
|
98
|
+
if (!IMPORTABLE_EXTENSIONS.some((extension) => file.endsWith(extension)))
|
|
99
|
+
continue;
|
|
100
|
+
const content = await readIfPresent(join(engineDir, file));
|
|
101
|
+
if (content === undefined)
|
|
102
|
+
continue;
|
|
103
|
+
for (const specifier of extractResourceModuleSpecifiers(content)) {
|
|
104
|
+
const importers = importersBySpecifier.get(specifier) ?? new Set();
|
|
105
|
+
importers.add(file);
|
|
106
|
+
importersBySpecifier.set(specifier, importers);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const mozBuildCache = new Map();
|
|
110
|
+
const findings = [];
|
|
111
|
+
for (const [specifier, importers] of importersBySpecifier) {
|
|
112
|
+
const suffix = moduleSourceSuffix(specifier);
|
|
113
|
+
if (suffix === undefined)
|
|
114
|
+
continue;
|
|
115
|
+
// Only fork-owned targets are policed; anything else is upstream.
|
|
116
|
+
const target = owned.find((file) => file.endsWith(suffix));
|
|
117
|
+
if (target === undefined)
|
|
118
|
+
continue;
|
|
119
|
+
const sortedImporters = [...importers].sort((a, b) => a.localeCompare(b));
|
|
120
|
+
if (!(await pathExists(join(engineDir, target)))) {
|
|
121
|
+
findings.push({
|
|
122
|
+
module: target,
|
|
123
|
+
specifier,
|
|
124
|
+
importers: sortedImporters,
|
|
125
|
+
reason: 'missing-file',
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (!(await isRegisteredInAncestorMozBuild(engineDir, target, mozBuildCache))) {
|
|
130
|
+
findings.push({
|
|
131
|
+
module: target,
|
|
132
|
+
specifier,
|
|
133
|
+
importers: sortedImporters,
|
|
134
|
+
reason: 'unregistered',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Deterministic order regardless of Map iteration or owned-file order.
|
|
139
|
+
return findings.sort((a, b) => a.module.localeCompare(b.module) || a.specifier.localeCompare(b.specifier));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Renders findings as operator-facing lines, naming the unregistered or
|
|
143
|
+
* unresolvable module, so the failure is a sentence instead of a bare
|
|
144
|
+
* `-11`.
|
|
145
|
+
*/
|
|
146
|
+
export function formatUnresolvedSystemModuleImports(findings) {
|
|
147
|
+
return findings.map((finding) => finding.reason === 'missing-file'
|
|
148
|
+
? `${finding.module}: imported as ${finding.specifier} by ${finding.importers.join(', ')}, ` +
|
|
149
|
+
'but the file does not exist in engine/. A packaged browser cannot load it; xpcshell ' +
|
|
150
|
+
'dies on SIGSEGV with no output.'
|
|
151
|
+
: `${finding.module}: imported as ${finding.specifier} by ${finding.importers.join(', ')}, ` +
|
|
152
|
+
'but no moz.build at or above its directory registers it. Add it to EXTRA_JS_MODULES ' +
|
|
153
|
+
'(or the appropriate namespaced module list) — until then a packaged browser cannot ' +
|
|
154
|
+
'load it and xpcshell dies on SIGSEGV with no output.');
|
|
155
|
+
}
|
|
@@ -8,6 +8,7 @@ import { discoverPatches, withPatchDirectoryLock } from './patch-apply.js';
|
|
|
8
8
|
import { normalizePatchArtifact } from './patch-artifact-normalize.js';
|
|
9
9
|
import { findAllPatchesForFilesWithDetails, } from './patch-export-coverage.js';
|
|
10
10
|
import { addPatchToManifest, loadPatchesManifestForWrite, PATCHES_MANIFEST, savePatchesManifest, } from './patch-manifest.js';
|
|
11
|
+
import { requirePatchOrder } from './patch-parse.js';
|
|
11
12
|
import { allocatePolicyOrder, enforcePatchPolicy } from './patch-policy.js';
|
|
12
13
|
export { findAllPatchesForFiles, findAllPatchesForFilesWithDetails, findSupersededPatches, isPatchFullyCovered, } from './patch-export-coverage.js';
|
|
13
14
|
import { escapeRegex } from '../utils/regex.js';
|
|
@@ -264,7 +265,7 @@ async function computeExportPlanUnderLock(input) {
|
|
|
264
265
|
const patchPath = join(input.patchesDir, patchFilename);
|
|
265
266
|
const metadata = {
|
|
266
267
|
filename: patchFilename,
|
|
267
|
-
order:
|
|
268
|
+
order: requirePatchOrder(patchFilename),
|
|
268
269
|
category: input.category,
|
|
269
270
|
name: input.name,
|
|
270
271
|
description: input.description,
|
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
*/
|
|
19
19
|
/** Maximum Levenshtein distance accepted as a "did you mean" suggestion. */
|
|
20
20
|
const SUGGESTION_DISTANCE_THRESHOLD = 3;
|
|
21
|
+
/**
|
|
22
|
+
* Shortest side accepted for a prefix relation. Below this a prefix match
|
|
23
|
+
* is noise: a one-character ordinal prefixes almost anything.
|
|
24
|
+
*/
|
|
25
|
+
const MIN_PREFIX_MATCH_LENGTH = 4;
|
|
21
26
|
/** Maximum number of suggestions to surface in the error message. */
|
|
22
27
|
const SUGGESTION_LIMIT = 3;
|
|
23
28
|
/**
|
|
@@ -83,10 +88,31 @@ function collectAcceptedIdentifiers(patches) {
|
|
|
83
88
|
* candidate whose distance exceeds {@link SUGGESTION_DISTANCE_THRESHOLD}.
|
|
84
89
|
*/
|
|
85
90
|
function rankSuggestions(identifier, candidates) {
|
|
91
|
+
const needle = identifier.toLowerCase();
|
|
86
92
|
return candidates
|
|
87
|
-
.map((candidate) =>
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
.map((candidate) => {
|
|
94
|
+
const lower = candidate.toLowerCase();
|
|
95
|
+
// Prefix relation in EITHER direction is a stronger signal than edit
|
|
96
|
+
// distance and is not bounded by it: a partial ordinal or
|
|
97
|
+
// an abbreviated slug ("ui-foo" for "002-ui-foo.patch") is many edits
|
|
98
|
+
// away from the full identifier yet is obviously the intended target.
|
|
99
|
+
// `lower.startsWith(needle)`: the operator typed an abbreviation of
|
|
100
|
+
// a real identifier. The reverse relation is only meaningful when
|
|
101
|
+
// the candidate is substantial — otherwise the single-character
|
|
102
|
+
// ordinal "9" would "match" every input starting with a 9.
|
|
103
|
+
const prefixMatch = needle.length >= MIN_PREFIX_MATCH_LENGTH &&
|
|
104
|
+
(lower.startsWith(needle) ||
|
|
105
|
+
(lower.length >= MIN_PREFIX_MATCH_LENGTH && needle.startsWith(lower)));
|
|
106
|
+
return {
|
|
107
|
+
candidate,
|
|
108
|
+
prefixMatch,
|
|
109
|
+
distance: levenshtein(identifier, candidate),
|
|
110
|
+
};
|
|
111
|
+
})
|
|
112
|
+
.filter((entry) => entry.prefixMatch || entry.distance <= SUGGESTION_DISTANCE_THRESHOLD)
|
|
113
|
+
.sort((a, b) => Number(b.prefixMatch) - Number(a.prefixMatch) ||
|
|
114
|
+
a.distance - b.distance ||
|
|
115
|
+
a.candidate.localeCompare(b.candidate))
|
|
90
116
|
.slice(0, SUGGESTION_LIMIT)
|
|
91
117
|
.map((entry) => entry.candidate);
|
|
92
118
|
}
|
|
@@ -32,7 +32,7 @@ export interface CheckJsMode {
|
|
|
32
32
|
compilerOptions?: PatchLintCheckJsCompilerOptions;
|
|
33
33
|
/**
|
|
34
34
|
* How to report undefined free identifiers (TS2304/TS2552). Default
|
|
35
|
-
* 'warning' — see `patchLint.undefinedIdentifiers
|
|
35
|
+
* 'warning' — see `patchLint.undefinedIdentifiers`.
|
|
36
36
|
*/
|
|
37
37
|
undefinedIdentifiers?: PatchLintSeverityGate;
|
|
38
38
|
}
|
|
@@ -68,7 +68,7 @@ export interface GroupedCheckJsResult {
|
|
|
68
68
|
* against the real owning sources while unrelated owned files are never
|
|
69
69
|
* parsed. `.mjs` files are module-scoped, so a root's diagnostics are
|
|
70
70
|
* identical whether other owned files are roots or mere resolution
|
|
71
|
-
*
|
|
71
|
+
* targets (pinned by the rootScope parity test).
|
|
72
72
|
* @returns Diagnostics grouped per owning file plus run-level errors
|
|
73
73
|
*/
|
|
74
74
|
export declare function runCheckJsGrouped(repoDir: string, resolutionOwned: Set<string>, extraShimPath?: string, projectRoot?: string, mode?: CheckJsMode, builtinShimSuffix?: string, rootScope?: ReadonlySet<string>): Promise<GroupedCheckJsResult>;
|
|
@@ -102,7 +102,7 @@ export declare function runCheckJs(repoDir: string, patchOwnedFiles: Set<string>
|
|
|
102
102
|
*/
|
|
103
103
|
export declare function invokePatchLintCheckJs(repoDir: string, patchOwnedFiles: Set<string>, patchLint: PatchLintConfig, projectRoot: string, reportScope?: ReadonlySet<string>): Promise<PatchLintIssue[]>;
|
|
104
104
|
/**
|
|
105
|
-
* Runs the checkJs pass over patch-owned test `.js` files (
|
|
105
|
+
* Runs the checkJs pass over patch-owned test `.js` files (
|
|
106
106
|
* `patchLint.checkJsTestFiles`). Each test file gets its OWN small
|
|
107
107
|
* program — mochitest scripts share top-level scope only with their
|
|
108
108
|
* directory's `head*.js` helpers, so one multi-script program would emit
|
|
@@ -131,7 +131,7 @@ export declare function runCheckJsTestFilesGrouped(repoDir: string, testFiles: S
|
|
|
131
131
|
* @param patchLint - The resolved `patchLint` config block
|
|
132
132
|
* @param projectRoot - FireForge project root for shim resolution
|
|
133
133
|
* @param rootScope - Optional subset of files to use as program roots
|
|
134
|
-
*
|
|
134
|
+
* (`--patches`); resolution still spans the whole queue
|
|
135
135
|
*/
|
|
136
136
|
export declare function invokePatchLintCheckJsGrouped(repoDir: string, patchOwnedFiles: Set<string>, patchLint: PatchLintConfig, projectRoot: string, rootScope?: ReadonlySet<string>): Promise<GroupedCheckJsResult>;
|
|
137
137
|
/**
|
|
@@ -141,7 +141,7 @@ export declare function invokePatchLintCheckJsGrouped(repoDir: string, patchOwne
|
|
|
141
141
|
* when `checkJsTestFiles` is on). The built program itself never
|
|
142
142
|
* contributes globals (see {@link runCheckJsGrouped}), so a warm
|
|
143
143
|
* all-cache-hit run can satisfy the "warm never reports less than cold"
|
|
144
|
-
* invariant
|
|
144
|
+
* invariant with this probe instead of building the whole
|
|
145
145
|
* TypeScript program. Issue objects are byte-identical to the build
|
|
146
146
|
* path's, deduplicated by message like {@link runCheckJsTestFilesGrouped}.
|
|
147
147
|
*/
|
|
@@ -154,7 +154,7 @@ function createPathsResolver(ts, paths, baseDir, fileExists, onResolved) {
|
|
|
154
154
|
* against the real owning sources while unrelated owned files are never
|
|
155
155
|
* parsed. `.mjs` files are module-scoped, so a root's diagnostics are
|
|
156
156
|
* identical whether other owned files are roots or mere resolution
|
|
157
|
-
*
|
|
157
|
+
* targets (pinned by the rootScope parity test).
|
|
158
158
|
* @returns Diagnostics grouped per owning file plus run-level errors
|
|
159
159
|
*/
|
|
160
160
|
export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath, projectRoot, mode, builtinShimSuffix, rootScope) {
|
|
@@ -206,7 +206,7 @@ export async function runCheckJsGrouped(repoDir, resolutionOwned, extraShimPath,
|
|
|
206
206
|
let shimSource;
|
|
207
207
|
try {
|
|
208
208
|
const composed = await composeShimSource(projectRoot ?? repoDir, extraShimPath);
|
|
209
|
-
// The suffix (e.g. the loose test-harness baseline
|
|
209
|
+
// The suffix (e.g. the loose test-harness baseline) goes
|
|
210
210
|
// AFTER the consumer's extra shim: TypeScript resolves conflicting
|
|
211
211
|
// `declare var` redeclarations to the FIRST declaration, so a typed
|
|
212
212
|
// consumer declaration must precede the loose fallback to win.
|
|
@@ -420,7 +420,7 @@ export async function invokePatchLintCheckJs(repoDir, patchOwnedFiles, patchLint
|
|
|
420
420
|
return runCheckJs(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, modeFromPatchLintConfig(patchLint), reportScope);
|
|
421
421
|
}
|
|
422
422
|
/**
|
|
423
|
-
* Runs the checkJs pass over patch-owned test `.js` files (
|
|
423
|
+
* Runs the checkJs pass over patch-owned test `.js` files (
|
|
424
424
|
* `patchLint.checkJsTestFiles`). Each test file gets its OWN small
|
|
425
425
|
* program — mochitest scripts share top-level scope only with their
|
|
426
426
|
* directory's `head*.js` helpers, so one multi-script program would emit
|
|
@@ -446,7 +446,7 @@ export async function runCheckJsTestFilesGrouped(repoDir, testFiles, patchLint,
|
|
|
446
446
|
for (const file of files) {
|
|
447
447
|
// Under a rootScope only the scoped files get their own program, but
|
|
448
448
|
// head.js helper discovery still spans the full owned set so a scoped
|
|
449
|
-
// test keeps its cross-patch harness globals
|
|
449
|
+
// test keeps its cross-patch harness globals.
|
|
450
450
|
if (rootScope !== undefined && !rootScope.has(file))
|
|
451
451
|
continue;
|
|
452
452
|
const dir = file.slice(0, file.lastIndexOf('/') + 1);
|
|
@@ -498,7 +498,7 @@ function modeFromPatchLintConfig(patchLint) {
|
|
|
498
498
|
* @param patchLint - The resolved `patchLint` config block
|
|
499
499
|
* @param projectRoot - FireForge project root for shim resolution
|
|
500
500
|
* @param rootScope - Optional subset of files to use as program roots
|
|
501
|
-
*
|
|
501
|
+
* (`--patches`); resolution still spans the whole queue
|
|
502
502
|
*/
|
|
503
503
|
export async function invokePatchLintCheckJsGrouped(repoDir, patchOwnedFiles, patchLint, projectRoot, rootScope) {
|
|
504
504
|
return runCheckJsGrouped(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, modeFromPatchLintConfig(patchLint), undefined, rootScope);
|
|
@@ -510,7 +510,7 @@ export async function invokePatchLintCheckJsGrouped(repoDir, patchOwnedFiles, pa
|
|
|
510
510
|
* when `checkJsTestFiles` is on). The built program itself never
|
|
511
511
|
* contributes globals (see {@link runCheckJsGrouped}), so a warm
|
|
512
512
|
* all-cache-hit run can satisfy the "warm never reports less than cold"
|
|
513
|
-
* invariant
|
|
513
|
+
* invariant with this probe instead of building the whole
|
|
514
514
|
* TypeScript program. Issue objects are byte-identical to the build
|
|
515
515
|
* path's, deduplicated by message like {@link runCheckJsTestFilesGrouped}.
|
|
516
516
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `patch-lint-cross.ts` (at the per-file line budget). Re-exported from
|
|
4
4
|
* there so callers keep importing from `patch-lint.ts` unchanged.
|
|
5
5
|
*
|
|
6
|
-
* Memoised per context object
|
|
6
|
+
* Memoised per context object: ownership resolution and
|
|
7
7
|
* cache-key fingerprints call this 2-3× per linted patch, and each call
|
|
8
8
|
* used to re-run `detectNewFilesInDiff` over EVERY queue entry's diff.
|
|
9
9
|
* Keyed weakly so a discarded context frees its map; a caller that
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `patch-lint-cross.ts` (at the per-file line budget). Re-exported from
|
|
5
5
|
* there so callers keep importing from `patch-lint.ts` unchanged.
|
|
6
6
|
*
|
|
7
|
-
* Memoised per context object
|
|
7
|
+
* Memoised per context object: ownership resolution and
|
|
8
8
|
* cache-key fingerprints call this 2-3× per linted patch, and each call
|
|
9
9
|
* used to re-run `detectNewFilesInDiff` over EVERY queue entry's diff.
|
|
10
10
|
* Keyed weakly so a discarded context frees its map; a caller that
|
|
@@ -62,7 +62,7 @@ export interface PatchQueueContext {
|
|
|
62
62
|
* Optional patchPolicy config. When present, the forward-import rule
|
|
63
63
|
* validates its "Closest legal ordinal" suggestion against the importing
|
|
64
64
|
* patch's category range and suppresses it when no legal ordinal exists
|
|
65
|
-
|
|
65
|
+
*. Absent = current behavior (hint always printed).
|
|
66
66
|
*/
|
|
67
67
|
patchPolicy?: PatchPolicyConfig;
|
|
68
68
|
}
|
|
@@ -85,7 +85,7 @@ export async function buildPatchQueueContext(patchesDir, config) {
|
|
|
85
85
|
}
|
|
86
86
|
// The creators map (split out of `lintPatchQueueDuplicateCreations` so
|
|
87
87
|
// `status --ownership` can consume the structured data) lives in
|
|
88
|
-
// `patch-lint-creators.ts` with its per-context memoisation
|
|
88
|
+
// `patch-lint-creators.ts` with its per-context memoisation;
|
|
89
89
|
// re-exported here so callers keep importing from `patch-lint.ts`.
|
|
90
90
|
export { collectNewFileCreatorsByPath, invalidateNewFileCreatorsCache, } from './patch-lint-creators.js';
|
|
91
91
|
/**
|
|
@@ -276,7 +276,7 @@ export function extractImportSpecifiersWithLines(source) {
|
|
|
276
276
|
* `modifiedFileAdditions`: a patch that inserts one property into an
|
|
277
277
|
* existing getter object never carries the `defineESModuleGetters(` opener
|
|
278
278
|
* in its added lines, so the balanced-brace walk in
|
|
279
|
-
* {@link extractImportSpecifiersWithLines} cannot see it
|
|
279
|
+
* {@link extractImportSpecifiersWithLines} cannot see it. The
|
|
280
280
|
* URL-scheme restriction keeps ordinary object literals
|
|
281
281
|
* (`label: "Foo.sys.mjs"`) out; the leaf-extension filter downstream does
|
|
282
282
|
* the rest.
|
|
@@ -474,7 +474,7 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
474
474
|
// when the only fix is reordering. When patchPolicy is available and
|
|
475
475
|
// that ordinal falls outside the importing patch's category range,
|
|
476
476
|
// `patch reorder` would refuse it — suppress the hint and point at
|
|
477
|
-
// the staged-dependency remedy instead
|
|
477
|
+
// the staged-dependency remedy instead.
|
|
478
478
|
const suggestedOrder = Math.max(...laterOwners.map((o) => o.order)) + 1;
|
|
479
479
|
const category = entry.metadata?.category ?? /^\d+-([a-z]+)-/.exec(entry.filename)?.[1];
|
|
480
480
|
let ordinalHint = `Closest legal ordinal that satisfies this dependency: ${suggestedOrder}.`;
|
|
@@ -487,7 +487,7 @@ export function lintPatchQueueForwardImports(ctx) {
|
|
|
487
487
|
'creating patch(es); declaring the staged dependency is the recommended remedy.';
|
|
488
488
|
}
|
|
489
489
|
// The exact per-owner invocation turns the refusal into a paste-and-run
|
|
490
|
-
// remedy instead of a flag-discovery exercise
|
|
490
|
+
// remedy instead of a flag-discovery exercise.
|
|
491
491
|
const stagedCommands = laterOwners
|
|
492
492
|
.map((o) => `fireforge patch staged-dependency ${entry.filename} --add ` +
|
|
493
493
|
`--file ${sitePath} --specifier "${specifier}" ` +
|
|
@@ -41,7 +41,7 @@ async function loadCssTokenContext(repoDir) {
|
|
|
41
41
|
/**
|
|
42
42
|
* Masks CSS block comments with spaces, preserving newlines so line
|
|
43
43
|
* numbers stay stable. An unclosed trailing `/*` is masked to EOF —
|
|
44
|
-
* matching how a CSS parser treats it
|
|
44
|
+
* matching how a CSS parser treats it.
|
|
45
45
|
*/
|
|
46
46
|
function maskCssComments(source) {
|
|
47
47
|
const masked = source.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, ' '));
|
|
@@ -23,7 +23,7 @@ export declare function extractAddedLinesPerFile(diffContent: string): Map<strin
|
|
|
23
23
|
* unified diff. Walking each hunk with a new-file line counter (`+` and
|
|
24
24
|
* context lines advance it, `-` lines do not) lets rules that need
|
|
25
25
|
* full-file context — e.g. comment masking across the context/added
|
|
26
|
-
* boundary
|
|
26
|
+
* boundary — map the patch's additions onto the applied file.
|
|
27
27
|
*/
|
|
28
28
|
export declare function extractAddedLineNumbersPerFile(diffContent: string): Map<string, number[]>;
|
|
29
29
|
/**
|