@hominis/fireforge 0.41.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -34
- package/dist/build-info.json +4 -4
- package/dist/src/cli.js +21 -2
- package/dist/src/commands/build.js +7 -1
- package/dist/src/commands/config.js +16 -27
- package/dist/src/commands/discard.js +1 -1
- package/dist/src/commands/export-flow.js +13 -3
- package/dist/src/commands/export-placement-conflicts.js +1 -1
- package/dist/src/commands/export-shared.d.ts +1 -1
- package/dist/src/commands/export-shared.js +4 -5
- package/dist/src/commands/furnace/chrome-doc-remove.js +3 -0
- package/dist/src/commands/furnace/chrome-doc.js +3 -0
- package/dist/src/commands/furnace/create.js +3 -0
- package/dist/src/commands/furnace/override.js +3 -0
- package/dist/src/commands/furnace/preview.js +3 -0
- package/dist/src/commands/furnace/refresh.js +3 -0
- package/dist/src/commands/furnace/remove.js +3 -0
- package/dist/src/commands/furnace/rename.js +3 -0
- package/dist/src/commands/furnace/scan.js +3 -0
- package/dist/src/commands/import.js +1 -1
- package/dist/src/commands/lint-per-patch.js +16 -8
- package/dist/src/commands/lint-per-run-checkjs.d.ts +4 -4
- package/dist/src/commands/lint-per-run-checkjs.js +4 -4
- package/dist/src/commands/lint-report.js +1 -1
- package/dist/src/commands/lint.js +2 -2
- package/dist/src/commands/patch/lint-ignore.js +1 -1
- package/dist/src/commands/patch/move-files-create.js +5 -3
- package/dist/src/commands/patch/move-files-into.d.ts +1 -1
- package/dist/src/commands/patch/move-files-into.js +5 -3
- package/dist/src/commands/patch/move-files.js +1 -1
- package/dist/src/commands/patch/rename.js +1 -1
- package/dist/src/commands/patch/reorder.d.ts +1 -2
- package/dist/src/commands/patch/reorder.js +1 -2
- package/dist/src/commands/patch/split.js +5 -3
- package/dist/src/commands/patch/staged-dependency-validate.js +1 -1
- package/dist/src/commands/patch/staged-dependency.js +4 -4
- package/dist/src/commands/re-export-adjacent.js +1 -1
- package/dist/src/commands/re-export-bulk-scan.d.ts +1 -1
- package/dist/src/commands/re-export-bulk-scan.js +5 -4
- package/dist/src/commands/re-export-drift.d.ts +4 -4
- package/dist/src/commands/re-export-drift.js +47 -8
- package/dist/src/commands/re-export-lint.d.ts +7 -5
- package/dist/src/commands/re-export-lint.js +9 -8
- package/dist/src/commands/re-export-single.d.ts +1 -1
- package/dist/src/commands/re-export-single.js +5 -4
- package/dist/src/commands/re-export.js +26 -14
- package/dist/src/commands/reset.js +1 -1
- package/dist/src/commands/run.js +1 -1
- package/dist/src/commands/source.js +6 -6
- package/dist/src/commands/status-check.d.ts +1 -1
- package/dist/src/commands/status-check.js +1 -1
- package/dist/src/commands/status-json.d.ts +2 -2
- package/dist/src/commands/status-json.js +1 -1
- package/dist/src/commands/status-output.d.ts +1 -1
- package/dist/src/commands/status-output.js +1 -1
- package/dist/src/commands/status.js +21 -7
- package/dist/src/commands/test-build-phase.d.ts +2 -2
- package/dist/src/commands/test-build-phase.js +9 -6
- package/dist/src/commands/test-diagnose.js +8 -1
- package/dist/src/commands/test-modes.d.ts +2 -2
- package/dist/src/commands/test-modes.js +3 -3
- package/dist/src/commands/test-register.js +7 -2
- package/dist/src/commands/test-run.d.ts +6 -0
- package/dist/src/commands/test-run.js +32 -3
- package/dist/src/commands/test-stale-gate.js +7 -2
- package/dist/src/commands/test.js +4 -4
- package/dist/src/commands/tree.js +2 -2
- package/dist/src/commands/typecheck.d.ts +2 -1
- package/dist/src/commands/typecheck.js +59 -3
- package/dist/src/commands/verify.js +26 -1
- package/dist/src/core/ast-utils.js +14 -2
- package/dist/src/core/branding.d.ts +16 -0
- package/dist/src/core/branding.js +18 -2
- package/dist/src/core/browser-wire.js +9 -1
- package/dist/src/core/build-baseline-types.d.ts +1 -1
- package/dist/src/core/build-baseline.d.ts +1 -1
- package/dist/src/core/build-baseline.js +1 -1
- package/dist/src/core/build-overwrite-guard.d.ts +49 -0
- package/dist/src/core/build-overwrite-guard.js +112 -0
- package/dist/src/core/build-prepare.d.ts +13 -5
- package/dist/src/core/build-prepare.js +42 -9
- package/dist/src/core/config-mutate.d.ts +3 -2
- package/dist/src/core/config-mutate.js +5 -2
- package/dist/src/core/config.d.ts +3 -2
- package/dist/src/core/config.js +2 -0
- package/dist/src/core/coverage-extend.d.ts +1 -1
- package/dist/src/core/coverage-extend.js +1 -1
- package/dist/src/core/destructive.d.ts +23 -0
- package/dist/src/core/destructive.js +45 -4
- package/dist/src/core/diff-hunks.js +31 -17
- package/dist/src/core/discard-baseline.d.ts +1 -1
- package/dist/src/core/discard-baseline.js +1 -1
- package/dist/src/core/display-state.d.ts +52 -0
- package/dist/src/core/display-state.js +89 -0
- package/dist/src/core/engine-session-lock.d.ts +20 -0
- package/dist/src/core/engine-session-lock.js +84 -14
- package/dist/src/core/export-moved-code-shape.d.ts +60 -0
- package/dist/src/core/export-moved-code-shape.js +129 -0
- package/dist/src/core/file-lock.d.ts +44 -0
- package/dist/src/core/file-lock.js +157 -42
- package/dist/src/core/furnace-apply-helpers.d.ts +1 -1
- package/dist/src/core/furnace-apply-helpers.js +1 -1
- package/dist/src/core/furnace-apply-output.js +1 -1
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +2 -2
- package/dist/src/core/furnace-apply-overwrite-warn.js +2 -2
- package/dist/src/core/furnace-apply-undeploy-actions.d.ts +1 -1
- package/dist/src/core/furnace-apply-undeploy-actions.js +1 -1
- package/dist/src/core/furnace-apply.js +13 -1
- package/dist/src/core/furnace-config-custom.d.ts +2 -1
- package/dist/src/core/furnace-config-order.d.ts +2 -1
- package/dist/src/core/furnace-config-order.js +9 -4
- package/dist/src/core/furnace-config-validate.d.ts +4 -3
- package/dist/src/core/furnace-config-validate.js +3 -3
- package/dist/src/core/furnace-config.js +5 -0
- package/dist/src/core/furnace-operation.d.ts +12 -0
- package/dist/src/core/furnace-operation.js +125 -1
- package/dist/src/core/furnace-rollback.d.ts +17 -0
- package/dist/src/core/furnace-rollback.js +35 -1
- package/dist/src/core/git-base.js +7 -2
- package/dist/src/core/git-diff.js +11 -2
- package/dist/src/core/git-readonly-index.d.ts +59 -0
- package/dist/src/core/git-readonly-index.js +137 -0
- package/dist/src/core/license-headers.d.ts +1 -1
- package/dist/src/core/license-headers.js +1 -1
- package/dist/src/core/lint-cache.d.ts +15 -5
- package/dist/src/core/lint-cache.js +35 -8
- package/dist/src/core/mach-known-noise-filter.d.ts +21 -0
- package/dist/src/core/mach-known-noise-filter.js +23 -0
- package/dist/src/core/mach-mozconfig.js +21 -3
- package/dist/src/core/module-resolution-preflight.d.ts +51 -0
- package/dist/src/core/module-resolution-preflight.js +155 -0
- package/dist/src/core/patch-export.js +2 -1
- package/dist/src/core/patch-identifier-suggest.js +29 -3
- package/dist/src/core/patch-lint-checkjs.d.ts +5 -5
- package/dist/src/core/patch-lint-checkjs.js +6 -6
- package/dist/src/core/patch-lint-creators.d.ts +1 -1
- package/dist/src/core/patch-lint-creators.js +1 -1
- package/dist/src/core/patch-lint-cross.d.ts +1 -1
- package/dist/src/core/patch-lint-cross.js +4 -4
- package/dist/src/core/patch-lint-css.js +1 -1
- package/dist/src/core/patch-lint-diff.d.ts +1 -1
- package/dist/src/core/patch-lint-diff.js +1 -1
- package/dist/src/core/patch-lint-jsdoc.js +4 -2
- package/dist/src/core/patch-lint-module-registration.d.ts +13 -0
- package/dist/src/core/patch-lint-module-registration.js +13 -4
- package/dist/src/core/patch-lint-mozbuild.d.ts +1 -1
- package/dist/src/core/patch-lint-mozbuild.js +1 -1
- package/dist/src/core/patch-lint-ownership.d.ts +2 -2
- package/dist/src/core/patch-lint-ownership.js +2 -2
- package/dist/src/core/patch-lint.d.ts +2 -2
- package/dist/src/core/patch-lint.js +3 -3
- package/dist/src/core/patch-lock.d.ts +21 -0
- package/dist/src/core/patch-lock.js +29 -2
- package/dist/src/core/patch-manifest-io.d.ts +2 -1
- package/dist/src/core/patch-manifest-io.js +49 -0
- package/dist/src/core/patch-parse.d.ts +18 -0
- package/dist/src/core/patch-parse.js +24 -1
- package/dist/src/core/patch-policy.d.ts +2 -2
- package/dist/src/core/patch-policy.js +2 -2
- package/dist/src/core/smoke-patterns.d.ts +1 -1
- package/dist/src/core/status-binary.d.ts +1 -1
- package/dist/src/core/status-binary.js +1 -1
- package/dist/src/core/status-classify.d.ts +1 -1
- package/dist/src/core/status-classify.js +2 -2
- package/dist/src/core/test-harness-crash.d.ts +69 -3
- package/dist/src/core/test-harness-crash.js +174 -9
- package/dist/src/core/test-stale-check.d.ts +1 -1
- package/dist/src/core/test-stale-check.js +1 -1
- package/dist/src/core/token-category.js +1 -1
- package/dist/src/core/token-manager.d.ts +1 -1
- package/dist/src/core/token-manager.js +5 -5
- package/dist/src/core/tree-cow.js +1 -1
- package/dist/src/core/tree-guard.js +2 -2
- package/dist/src/core/tree-store.js +1 -1
- package/dist/src/core/typecheck-shim.d.ts +2 -2
- package/dist/src/core/typecheck-shim.js +2 -2
- package/dist/src/core/typecheck.js +10 -0
- package/dist/src/core/xpcshell-profile-dir.js +1 -1
- package/dist/src/errors/base.d.ts +22 -1
- package/dist/src/errors/base.js +32 -1
- package/dist/src/errors/codes.d.ts +10 -0
- package/dist/src/errors/codes.js +10 -0
- package/dist/src/types/commands/options.d.ts +27 -11
- package/dist/src/types/commands/patches.d.ts +1 -1
- package/dist/src/types/config.d.ts +2 -2
- package/dist/src/types/furnace.d.ts +1 -1
- package/dist/src/types/json.d.ts +22 -0
- package/dist/src/types/json.js +11 -0
- package/dist/src/utils/assert.d.ts +46 -0
- package/dist/src/utils/assert.js +83 -0
- package/dist/src/utils/build-info.d.ts +1 -1
- package/dist/src/utils/build-info.js +1 -1
- package/dist/src/utils/logger.d.ts +20 -0
- package/dist/src/utils/logger.js +26 -0
- package/dist/src/utils/options.d.ts +12 -0
- package/dist/src/utils/options.js +39 -0
- package/dist/src/utils/stdio-drain.d.ts +1 -1
- package/dist/src/utils/stdio-drain.js +1 -1
- package/dist/src/utils/validation.d.ts +12 -1
- package/dist/src/utils/validation.js +13 -1
- package/package.json +3 -3
|
@@ -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
|
/**
|
|
@@ -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
|