@hominis/fireforge 0.15.5 → 0.15.7
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 +49 -0
- package/README.md +70 -5
- package/dist/src/commands/build.js +60 -3
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +17 -0
- package/dist/src/commands/furnace/chrome-doc-templates.js +18 -0
- package/dist/src/commands/furnace/chrome-doc-tests.d.ts +23 -0
- package/dist/src/commands/furnace/chrome-doc-tests.js +120 -0
- package/dist/src/commands/furnace/chrome-doc.d.ts +11 -0
- package/dist/src/commands/furnace/chrome-doc.js +37 -4
- package/dist/src/commands/furnace/create-dry-run.d.ts +31 -0
- package/dist/src/commands/furnace/create-dry-run.js +95 -0
- package/dist/src/commands/furnace/create-templates.js +14 -0
- package/dist/src/commands/furnace/create.js +28 -24
- package/dist/src/commands/furnace/index.js +3 -1
- package/dist/src/commands/lint.d.ts +17 -2
- package/dist/src/commands/lint.js +25 -2
- package/dist/src/commands/register.d.ts +1 -1
- package/dist/src/commands/register.js +30 -7
- package/dist/src/commands/test.js +16 -1
- package/dist/src/core/build-audit-platform.d.ts +3 -1
- package/dist/src/core/build-audit-platform.js +87 -20
- package/dist/src/core/build-audit-registration.d.ts +80 -0
- package/dist/src/core/build-audit-registration.js +187 -0
- package/dist/src/core/build-audit-transforms.d.ts +23 -0
- package/dist/src/core/build-audit-transforms.js +94 -0
- package/dist/src/core/build-audit.js +210 -3
- package/dist/src/core/furnace-validate-registration.d.ts +6 -4
- package/dist/src/core/furnace-validate-registration.js +66 -6
- package/dist/src/core/mach-build-artifacts.d.ts +44 -0
- package/dist/src/core/mach-build-artifacts.js +104 -3
- package/dist/src/core/mach.d.ts +1 -1
- package/dist/src/core/mach.js +1 -1
- package/dist/src/core/test-stale-check.d.ts +42 -0
- package/dist/src/core/test-stale-check.js +114 -0
- package/dist/src/types/commands/options.d.ts +16 -0
- package/package.json +1 -1
|
@@ -10,10 +10,23 @@
|
|
|
10
10
|
* detection, the audit fires a "missing packaged artifact" warning for
|
|
11
11
|
* every gated file on every off-platform build — pure noise.
|
|
12
12
|
*
|
|
13
|
+
* Two gate sources are consulted, in order:
|
|
14
|
+
* 1. Python-style `if CONFIG[...]:` blocks in the owning `moz.build`.
|
|
15
|
+
* 2. Path-convention gates — certain directory fragments are packaged
|
|
16
|
+
* by platform-specific Makefile.in / NSIS recipes that FireForge
|
|
17
|
+
* does not parse, so a file living under `browser/installer/windows/`
|
|
18
|
+
* or any `/stubinstaller/` subtree is Windows-only regardless of
|
|
19
|
+
* what its nearest moz.build says. (The branding stubinstaller CSS
|
|
20
|
+
* is the motivating case: referenced from
|
|
21
|
+
* `browser/installer/windows/Makefile.in` / `nsis/stub.nsh` with no
|
|
22
|
+
* `if CONFIG[…]:` in any moz.build ancestor.)
|
|
23
|
+
*
|
|
13
24
|
* The detection is intentionally lightweight: we walk up from the
|
|
14
25
|
* source file looking for the closest `moz.build`, scan it for an
|
|
15
26
|
* occurrence of the source basename inside an `if CONFIG[...]:` block,
|
|
16
27
|
* and check whether the gate expression matches the host platform.
|
|
28
|
+
* The path-convention pass kicks in only when no moz.build gate is
|
|
29
|
+
* found, so an explicit moz.build gate always wins.
|
|
17
30
|
*
|
|
18
31
|
* This is best-effort. False negatives (we miss a gate and warn anyway)
|
|
19
32
|
* are tolerable — the audit is warn-only. False positives (we wrongly
|
|
@@ -128,9 +141,58 @@ export function findEnclosingGate(content, basename) {
|
|
|
128
141
|
}
|
|
129
142
|
return undefined;
|
|
130
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Path-convention gates: directories whose files are packaged by
|
|
146
|
+
* platform-specific build recipes (NSIS stub installer, DMG creation,
|
|
147
|
+
* Linux installer scripts) that live outside the moz.build graph. A
|
|
148
|
+
* file under any of these fragments is platform-restricted regardless
|
|
149
|
+
* of what its nearest `moz.build` says.
|
|
150
|
+
*
|
|
151
|
+
* `stubinstaller/` is the Windows NSIS stub installer asset tree. It
|
|
152
|
+
* is referenced from `browser/installer/windows/Makefile.in` (via
|
|
153
|
+
* `FILES` / `_WIDGET_FILES` lists) and `nsis/stub.nsh`, never through
|
|
154
|
+
* an `if CONFIG[…]:` block an ancestor moz.build exposes. Without
|
|
155
|
+
* this path-level gate, the audit warns on every touched branding
|
|
156
|
+
* stubinstaller CSS on every non-Windows build.
|
|
157
|
+
*/
|
|
158
|
+
const PATH_GATES = [
|
|
159
|
+
{ fragment: '/stubinstaller/', platform: 'win32', label: 'path convention: /stubinstaller/' },
|
|
160
|
+
{
|
|
161
|
+
fragment: '/browser/installer/windows/',
|
|
162
|
+
platform: 'win32',
|
|
163
|
+
label: 'path convention: browser/installer/windows/',
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
fragment: '/browser/installer/macosx/',
|
|
167
|
+
platform: 'darwin',
|
|
168
|
+
label: 'path convention: browser/installer/macosx/',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
fragment: '/browser/installer/linux/',
|
|
172
|
+
platform: 'linux',
|
|
173
|
+
label: 'path convention: browser/installer/linux/',
|
|
174
|
+
},
|
|
175
|
+
];
|
|
176
|
+
/**
|
|
177
|
+
* Returns a path-convention gate for `sourcePath` when one applies.
|
|
178
|
+
* Leading slash added so `startsWith`-style prefix traps
|
|
179
|
+
* (`browser/installer/windows/…`) match whether or not the input
|
|
180
|
+
* starts with a separator.
|
|
181
|
+
*/
|
|
182
|
+
function findPathConventionGate(sourcePath) {
|
|
183
|
+
const normalised = `/${sourcePath}`.replace(/\/+/g, '/');
|
|
184
|
+
for (const entry of PATH_GATES) {
|
|
185
|
+
if (normalised.includes(entry.fragment)) {
|
|
186
|
+
return { platform: entry.platform, label: entry.label };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
131
191
|
/**
|
|
132
192
|
* Determines whether the given source file is gated off on the current
|
|
133
|
-
* host by an enclosing `if CONFIG[...]:` block in its owning moz.build
|
|
193
|
+
* host by an enclosing `if CONFIG[...]:` block in its owning moz.build,
|
|
194
|
+
* OR by a path-convention rule for installer-tree subdirectories that
|
|
195
|
+
* are packaged via Makefile.in recipes the audit does not parse.
|
|
134
196
|
* Returns `gatedOff: false` and no expression when no gate is found —
|
|
135
197
|
* the file is not platform-restricted, so the caller should audit it
|
|
136
198
|
* normally.
|
|
@@ -140,31 +202,36 @@ export function findEnclosingGate(content, basename) {
|
|
|
140
202
|
* @returns Detection result
|
|
141
203
|
*/
|
|
142
204
|
export async function detectPlatformGate(engineDir, sourcePath) {
|
|
143
|
-
const sourceDir = dirname(join(engineDir, sourcePath));
|
|
144
|
-
const mozBuild = await findOwningMozBuild(engineDir, sourceDir);
|
|
145
|
-
if (!mozBuild)
|
|
146
|
-
return { gatedOff: false };
|
|
147
|
-
let content;
|
|
148
|
-
try {
|
|
149
|
-
content = await readText(mozBuild);
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
return { gatedOff: false };
|
|
153
|
-
}
|
|
154
|
-
const sourceBasename = sourcePath.split('/').pop() ?? '';
|
|
155
|
-
const expression = findEnclosingGate(content, sourceBasename);
|
|
156
|
-
if (!expression)
|
|
157
|
-
return { gatedOff: false };
|
|
158
205
|
let host;
|
|
159
206
|
try {
|
|
160
207
|
host = getPlatform();
|
|
161
208
|
}
|
|
162
209
|
catch {
|
|
163
|
-
|
|
210
|
+
host = undefined;
|
|
211
|
+
}
|
|
212
|
+
const sourceDir = dirname(join(engineDir, sourcePath));
|
|
213
|
+
const mozBuild = await findOwningMozBuild(engineDir, sourceDir);
|
|
214
|
+
if (mozBuild) {
|
|
215
|
+
let content;
|
|
216
|
+
try {
|
|
217
|
+
content = await readText(mozBuild);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
content = '';
|
|
221
|
+
}
|
|
222
|
+
const sourceBasename = sourcePath.split('/').pop() ?? '';
|
|
223
|
+
const expression = findEnclosingGate(content, sourceBasename);
|
|
224
|
+
if (expression) {
|
|
225
|
+
if (host && isGateOffHost(expression, host)) {
|
|
226
|
+
return { gatedOff: true, gateExpression: expression };
|
|
227
|
+
}
|
|
228
|
+
return { gatedOff: false, gateExpression: expression };
|
|
229
|
+
}
|
|
164
230
|
}
|
|
165
|
-
|
|
166
|
-
|
|
231
|
+
const pathGate = findPathConventionGate(sourcePath);
|
|
232
|
+
if (pathGate && host && host !== pathGate.platform) {
|
|
233
|
+
return { gatedOff: true, gateExpression: pathGate.label };
|
|
167
234
|
}
|
|
168
|
-
return { gatedOff: false
|
|
235
|
+
return { gatedOff: false };
|
|
169
236
|
}
|
|
170
237
|
//# sourceMappingURL=build-audit-platform.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** Parsed jar.mn registration anchored to a specific engine source path. */
|
|
2
|
+
export interface RegistrationHit {
|
|
3
|
+
/** Target path extracted from the entry (POSIX). */
|
|
4
|
+
target: string;
|
|
5
|
+
/** Source path from the entry (POSIX, relative to the jar.mn directory). */
|
|
6
|
+
source: string;
|
|
7
|
+
/** Absolute path of the jar.mn that owns the registration. */
|
|
8
|
+
jarManifest: string;
|
|
9
|
+
}
|
|
10
|
+
/** Result of a registration-aware dist probe. */
|
|
11
|
+
export interface RegistrationProbeResult {
|
|
12
|
+
/** Absolute path of the packaged artifact matching the registration target. */
|
|
13
|
+
artifact: string;
|
|
14
|
+
/** The registration entry that anchored the match. */
|
|
15
|
+
hit: RegistrationHit;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parses a single jar.mn line into `{ target, source }` when the line is a
|
|
19
|
+
* content entry with an explicit `(source)` reference. Returns undefined
|
|
20
|
+
* for comments, headers (`browser.jar:`), `%` manifest directives, blank
|
|
21
|
+
* lines, and entries without a source reference.
|
|
22
|
+
*
|
|
23
|
+
* Accepted entry shapes:
|
|
24
|
+
* ` content/browser/foo.js (content/foo.js)` bare
|
|
25
|
+
* `* content/browser/foo.js (content/foo.js)` `*` = preprocessed
|
|
26
|
+
* `en-US.jar: content/foo.js (content/foo.js)` locale-prefixed
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseJarMnEntry(line: string): {
|
|
29
|
+
target: string;
|
|
30
|
+
source: string;
|
|
31
|
+
} | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Scans a jar.mn file's contents for an entry whose source reference
|
|
34
|
+
* matches `relativeSource` (POSIX, relative to the jar.mn directory).
|
|
35
|
+
* Returns the first match; jar.mn enforces uniqueness of `(source)` in
|
|
36
|
+
* practice, so a first-match wins behaviour is adequate.
|
|
37
|
+
*/
|
|
38
|
+
export declare function findJarMnEntryForSource(content: string, relativeSource: string): {
|
|
39
|
+
target: string;
|
|
40
|
+
source: string;
|
|
41
|
+
} | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Walks from the source's directory upward to the engine root, returning
|
|
44
|
+
* the first jar.mn entry that registers the given source. Returns undefined
|
|
45
|
+
* when no ancestor jar.mn claims the source.
|
|
46
|
+
*
|
|
47
|
+
* @param engineDir Absolute engine root; walk halts here.
|
|
48
|
+
* @param source Engine-relative POSIX source path.
|
|
49
|
+
*/
|
|
50
|
+
export declare function findRegisteredTarget(engineDir: string, source: string): Promise<RegistrationHit | undefined>;
|
|
51
|
+
/**
|
|
52
|
+
* Probes the dist tree for the artifact registered against the given
|
|
53
|
+
* source. Returns the matched candidate and the registration hit that
|
|
54
|
+
* anchored it; undefined when the source has no owning jar.mn or when
|
|
55
|
+
* no same-basename candidate under the search roots ends with the
|
|
56
|
+
* registered target path.
|
|
57
|
+
*
|
|
58
|
+
* Suffix-matching against the target path is intentional: jar.mn targets
|
|
59
|
+
* are relative to the jar root (`browser.jar:`, `toolkit.jar:`), but the
|
|
60
|
+
* dist tree prefixes every entry with a jar-specific directory
|
|
61
|
+
* (`.../chrome/browser/content/browser/…`). The source basename plus the
|
|
62
|
+
* target suffix are unambiguous across every packaging convention we
|
|
63
|
+
* care about.
|
|
64
|
+
*
|
|
65
|
+
* @param engineDir Absolute engine root.
|
|
66
|
+
* @param source Engine-relative POSIX source path.
|
|
67
|
+
* @param searchRoots Absolute roots to probe (dist/, _tests/).
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveArtifactByRegistration(engineDir: string, source: string, searchRoots: readonly string[]): Promise<RegistrationProbeResult | undefined>;
|
|
70
|
+
/**
|
|
71
|
+
* Returns the absolute paths of every same-basename candidate under the
|
|
72
|
+
* given search roots. Used by the audit to enumerate ALL false-match
|
|
73
|
+
* candidates when the heuristic fallback downgrades to "missing" — the
|
|
74
|
+
* operator needs to see the full set, not just the scorer's pick, to
|
|
75
|
+
* distinguish a registration bug from a genuine packaging drop.
|
|
76
|
+
*
|
|
77
|
+
* @param source Engine-relative POSIX source path.
|
|
78
|
+
* @param searchRoots Absolute roots to scan.
|
|
79
|
+
*/
|
|
80
|
+
export declare function collectSameBasenameCandidates(source: string, searchRoots: readonly string[]): Promise<string[]>;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/*
|
|
3
|
+
* Registration-aware artifact resolution for the post-build dist-tree audit.
|
|
4
|
+
*
|
|
5
|
+
* The basename-plus-similarity heuristic in `build-audit-resolve.ts` cannot
|
|
6
|
+
* distinguish two unrelated files that share a source basename. Motivating
|
|
7
|
+
* case: one fork registers `content/mybrowser.js` in `browser/base/jar.mn`
|
|
8
|
+
* (packaged under `chrome/browser/content/browser/mybrowser.js`) while an
|
|
9
|
+
* unrelated patch registers a `mybrowser.js` pref file under
|
|
10
|
+
* `browser/defaults/preferences/`. The basename walker surfaces both
|
|
11
|
+
* candidates, `scoreCandidate` awards them an equal trailing-overlap score
|
|
12
|
+
* (basename only, no meaningful mid-path match), and whichever the
|
|
13
|
+
* directory walk hits first wins — frequently the wrong one.
|
|
14
|
+
*
|
|
15
|
+
* This module anchors resolution to the `(source)` reference inside
|
|
16
|
+
* `jar.mn`. For a source under audit we walk its ancestor directories for
|
|
17
|
+
* an owning `jar.mn`, find the entry whose `(source)` resolves to our
|
|
18
|
+
* path, and expose both the target path recorded in the entry and the
|
|
19
|
+
* manifest that owns it. Callers in `build-audit.ts` prefer candidates
|
|
20
|
+
* whose absolute dist-tree path ends with the registered target, and
|
|
21
|
+
* report an unambiguous "registered but not packaged" miss when no such
|
|
22
|
+
* candidate exists — rather than falling through to the heuristic which
|
|
23
|
+
* would pick an unrelated same-basename file.
|
|
24
|
+
*
|
|
25
|
+
* Fallback semantics: when no jar.mn registration is found, the caller
|
|
26
|
+
* is expected to use the similarity heuristic. Registration wins when it
|
|
27
|
+
* exists; the heuristic fills the gap for sources registered through
|
|
28
|
+
* moz.build (`FINAL_TARGET_FILES`, `JS_PREFERENCE_FILES`, etc.) or
|
|
29
|
+
* `package-manifest.in` entries, which this module intentionally does
|
|
30
|
+
* not parse — supporting every Firefox registration surface would bloat
|
|
31
|
+
* the audit, and the heuristic's remaining weak case (unrelated same-
|
|
32
|
+
* basename hits) is surfaced to the operator in the warning copy.
|
|
33
|
+
*/
|
|
34
|
+
import { basename, dirname, join, relative, sep } from 'node:path';
|
|
35
|
+
import { pathExists, readText } from '../utils/fs.js';
|
|
36
|
+
import { findAllByBasename } from './build-audit-resolve.js';
|
|
37
|
+
/** Ceiling on ancestor-directory hops when searching for an owning jar.mn. */
|
|
38
|
+
const MAX_JAR_MN_SCAN_DEPTH = 8;
|
|
39
|
+
/**
|
|
40
|
+
* Parses a single jar.mn line into `{ target, source }` when the line is a
|
|
41
|
+
* content entry with an explicit `(source)` reference. Returns undefined
|
|
42
|
+
* for comments, headers (`browser.jar:`), `%` manifest directives, blank
|
|
43
|
+
* lines, and entries without a source reference.
|
|
44
|
+
*
|
|
45
|
+
* Accepted entry shapes:
|
|
46
|
+
* ` content/browser/foo.js (content/foo.js)` bare
|
|
47
|
+
* `* content/browser/foo.js (content/foo.js)` `*` = preprocessed
|
|
48
|
+
* `en-US.jar: content/foo.js (content/foo.js)` locale-prefixed
|
|
49
|
+
*/
|
|
50
|
+
export function parseJarMnEntry(line) {
|
|
51
|
+
if (!line)
|
|
52
|
+
return undefined;
|
|
53
|
+
const trimmed = line.trim();
|
|
54
|
+
if (trimmed === '' || trimmed.startsWith('#') || trimmed.startsWith('%'))
|
|
55
|
+
return undefined;
|
|
56
|
+
// Leading `*` (preprocessed) and optional `locale.jar:` prefix are dropped
|
|
57
|
+
// before matching the target/(source) pair. Both are whitespace-separated
|
|
58
|
+
// from the target entry.
|
|
59
|
+
const stripped = trimmed.replace(/^\*\s+/, '').replace(/^[A-Za-z0-9.\-_]+\.jar:\s+/, '');
|
|
60
|
+
const match = /^(\S+)\s+\(([^)]+)\)\s*$/.exec(stripped);
|
|
61
|
+
if (!match)
|
|
62
|
+
return undefined;
|
|
63
|
+
const target = (match[1] ?? '').trim();
|
|
64
|
+
const source = (match[2] ?? '').trim();
|
|
65
|
+
if (!target || !source)
|
|
66
|
+
return undefined;
|
|
67
|
+
return { target, source };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Scans a jar.mn file's contents for an entry whose source reference
|
|
71
|
+
* matches `relativeSource` (POSIX, relative to the jar.mn directory).
|
|
72
|
+
* Returns the first match; jar.mn enforces uniqueness of `(source)` in
|
|
73
|
+
* practice, so a first-match wins behaviour is adequate.
|
|
74
|
+
*/
|
|
75
|
+
export function findJarMnEntryForSource(content, relativeSource) {
|
|
76
|
+
const normalized = relativeSource.replace(/\\/g, '/');
|
|
77
|
+
for (const line of content.split('\n')) {
|
|
78
|
+
const parsed = parseJarMnEntry(line);
|
|
79
|
+
if (!parsed)
|
|
80
|
+
continue;
|
|
81
|
+
if (parsed.source === normalized)
|
|
82
|
+
return parsed;
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Walks from the source's directory upward to the engine root, returning
|
|
88
|
+
* the first jar.mn entry that registers the given source. Returns undefined
|
|
89
|
+
* when no ancestor jar.mn claims the source.
|
|
90
|
+
*
|
|
91
|
+
* @param engineDir Absolute engine root; walk halts here.
|
|
92
|
+
* @param source Engine-relative POSIX source path.
|
|
93
|
+
*/
|
|
94
|
+
export async function findRegisteredTarget(engineDir, source) {
|
|
95
|
+
const sourceAbs = join(engineDir, source);
|
|
96
|
+
const root = engineDir.replace(/[/\\]+$/, '');
|
|
97
|
+
let current = dirname(sourceAbs);
|
|
98
|
+
let depth = 0;
|
|
99
|
+
while (depth <= MAX_JAR_MN_SCAN_DEPTH &&
|
|
100
|
+
(current === root || current.startsWith(`${root}/`) || current.startsWith(`${root}\\`))) {
|
|
101
|
+
const jarMn = join(current, 'jar.mn');
|
|
102
|
+
if (await pathExists(jarMn)) {
|
|
103
|
+
let content;
|
|
104
|
+
try {
|
|
105
|
+
content = await readText(jarMn);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
content = '';
|
|
109
|
+
}
|
|
110
|
+
const rel = relative(current, sourceAbs).split(sep).join('/');
|
|
111
|
+
const entry = findJarMnEntryForSource(content, rel);
|
|
112
|
+
if (entry) {
|
|
113
|
+
return { target: entry.target, source: entry.source, jarManifest: jarMn };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const parent = dirname(current);
|
|
117
|
+
if (parent === current)
|
|
118
|
+
break;
|
|
119
|
+
current = parent;
|
|
120
|
+
depth += 1;
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Normalises an absolute filesystem path to POSIX separators so the
|
|
126
|
+
* suffix comparison against a jar.mn target (always POSIX) is platform-
|
|
127
|
+
* independent.
|
|
128
|
+
*/
|
|
129
|
+
function toPosix(path) {
|
|
130
|
+
return path.split(sep).join('/');
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Probes the dist tree for the artifact registered against the given
|
|
134
|
+
* source. Returns the matched candidate and the registration hit that
|
|
135
|
+
* anchored it; undefined when the source has no owning jar.mn or when
|
|
136
|
+
* no same-basename candidate under the search roots ends with the
|
|
137
|
+
* registered target path.
|
|
138
|
+
*
|
|
139
|
+
* Suffix-matching against the target path is intentional: jar.mn targets
|
|
140
|
+
* are relative to the jar root (`browser.jar:`, `toolkit.jar:`), but the
|
|
141
|
+
* dist tree prefixes every entry with a jar-specific directory
|
|
142
|
+
* (`.../chrome/browser/content/browser/…`). The source basename plus the
|
|
143
|
+
* target suffix are unambiguous across every packaging convention we
|
|
144
|
+
* care about.
|
|
145
|
+
*
|
|
146
|
+
* @param engineDir Absolute engine root.
|
|
147
|
+
* @param source Engine-relative POSIX source path.
|
|
148
|
+
* @param searchRoots Absolute roots to probe (dist/, _tests/).
|
|
149
|
+
*/
|
|
150
|
+
export async function resolveArtifactByRegistration(engineDir, source, searchRoots) {
|
|
151
|
+
const hit = await findRegisteredTarget(engineDir, source);
|
|
152
|
+
if (!hit)
|
|
153
|
+
return undefined;
|
|
154
|
+
const name = basename(source);
|
|
155
|
+
const targetSuffix = `/${hit.target.replace(/^\/+/, '')}`;
|
|
156
|
+
const candidates = [];
|
|
157
|
+
for (const root of searchRoots) {
|
|
158
|
+
const found = await findAllByBasename(root, name);
|
|
159
|
+
candidates.push(...found);
|
|
160
|
+
}
|
|
161
|
+
for (const candidate of candidates) {
|
|
162
|
+
if (toPosix(candidate).endsWith(targetSuffix)) {
|
|
163
|
+
return { artifact: candidate, hit };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Returns the absolute paths of every same-basename candidate under the
|
|
170
|
+
* given search roots. Used by the audit to enumerate ALL false-match
|
|
171
|
+
* candidates when the heuristic fallback downgrades to "missing" — the
|
|
172
|
+
* operator needs to see the full set, not just the scorer's pick, to
|
|
173
|
+
* distinguish a registration bug from a genuine packaging drop.
|
|
174
|
+
*
|
|
175
|
+
* @param source Engine-relative POSIX source path.
|
|
176
|
+
* @param searchRoots Absolute roots to scan.
|
|
177
|
+
*/
|
|
178
|
+
export async function collectSameBasenameCandidates(source, searchRoots) {
|
|
179
|
+
const name = basename(source);
|
|
180
|
+
const out = [];
|
|
181
|
+
for (const root of searchRoots) {
|
|
182
|
+
const found = await findAllByBasename(root, name);
|
|
183
|
+
out.push(...found);
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=build-audit-registration.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the expected chrome-tree suffix for an engine-relative POSIX
|
|
3
|
+
* source path when the path falls under a known transform prefix;
|
|
4
|
+
* undefined otherwise.
|
|
5
|
+
*
|
|
6
|
+
* @param source Engine-relative POSIX source path.
|
|
7
|
+
*/
|
|
8
|
+
export declare function expectedChromeSuffix(source: string): string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Probes the dist tree for the artifact implied by a known
|
|
11
|
+
* source→chrome transform. Returns the first absolute candidate whose
|
|
12
|
+
* POSIX path ends with the expected chrome suffix, or undefined when
|
|
13
|
+
* no transform applies or no candidate matches.
|
|
14
|
+
*
|
|
15
|
+
* The transform check is treated as high-confidence by `build-audit.ts`
|
|
16
|
+
* (callers pass `{ registered: true }` to `evaluateArtifactMtime`), so
|
|
17
|
+
* a match bypasses the structural-relation check that rejects generic
|
|
18
|
+
* basename collisions.
|
|
19
|
+
*
|
|
20
|
+
* @param source Engine-relative POSIX source path.
|
|
21
|
+
* @param searchRoots Absolute roots to probe (dist/, _tests/).
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveArtifactByKnownTransform(source: string, searchRoots: readonly string[]): Promise<string | undefined>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/*
|
|
3
|
+
* Known source→packaging path transforms for the post-build dist-tree audit.
|
|
4
|
+
*
|
|
5
|
+
* Motivating case: a source at `engine/browser/base/content/foo.js` ships
|
|
6
|
+
* under `chrome/browser/content/browser/foo.js`. If an unrelated patch
|
|
7
|
+
* registers a different `foo.js` elsewhere in the tree (e.g. a pref file
|
|
8
|
+
* under `browser/defaults/preferences/foo.js`), the basename walker
|
|
9
|
+
* surfaces both candidates, `scoreCandidate` awards them an identical
|
|
10
|
+
* trailing-overlap score (basename only; every intermediate segment is
|
|
11
|
+
* in the generic list), and whichever the directory walk hits first wins.
|
|
12
|
+
* The heuristic then declares the chosen candidate "not structurally
|
|
13
|
+
* related" and reports the correctly-packaged chrome resource as missing.
|
|
14
|
+
*
|
|
15
|
+
* The transforms below anchor resolution to the well-known subtree→chrome
|
|
16
|
+
* conventions used by upstream mozilla-central jar.mn. When a source
|
|
17
|
+
* matches one of these prefixes, a candidate whose absolute path ends
|
|
18
|
+
* with the implied chrome suffix is treated as a confident match — the
|
|
19
|
+
* scorer never runs and the structural-relation check is bypassed.
|
|
20
|
+
*
|
|
21
|
+
* Scope is intentionally narrow: only subtrees whose packaging target is
|
|
22
|
+
* stable across every fork we know about. A fork that reroutes a known
|
|
23
|
+
* subtree can still win by adding `(source)` annotations in its own
|
|
24
|
+
* `jar.mn`, which `resolveArtifactByRegistration` consults first.
|
|
25
|
+
*/
|
|
26
|
+
import { basename, sep } from 'node:path';
|
|
27
|
+
import { findAllByBasename } from './build-audit-resolve.js';
|
|
28
|
+
/**
|
|
29
|
+
* Table of `prefix → chrome-suffix` transforms. Each rule names an
|
|
30
|
+
* engine-relative subtree prefix and produces the expected dist-tree
|
|
31
|
+
* suffix for a file under it. Rules are evaluated in array order and
|
|
32
|
+
* the first-matching prefix wins, so `toolkit/content/widgets/` must
|
|
33
|
+
* precede the looser `toolkit/content/`.
|
|
34
|
+
*/
|
|
35
|
+
const KNOWN_TRANSFORMS = [
|
|
36
|
+
{
|
|
37
|
+
prefix: 'browser/base/content/',
|
|
38
|
+
build: (rest) => `chrome/browser/content/browser/${rest}`,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
prefix: 'toolkit/content/widgets/',
|
|
42
|
+
build: (rest) => `chrome/toolkit/content/global/elements/${rest}`,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
prefix: 'toolkit/content/',
|
|
46
|
+
build: (rest) => `chrome/toolkit/content/global/${rest}`,
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
/**
|
|
50
|
+
* Returns the expected chrome-tree suffix for an engine-relative POSIX
|
|
51
|
+
* source path when the path falls under a known transform prefix;
|
|
52
|
+
* undefined otherwise.
|
|
53
|
+
*
|
|
54
|
+
* @param source Engine-relative POSIX source path.
|
|
55
|
+
*/
|
|
56
|
+
export function expectedChromeSuffix(source) {
|
|
57
|
+
for (const rule of KNOWN_TRANSFORMS) {
|
|
58
|
+
if (source.startsWith(rule.prefix)) {
|
|
59
|
+
return rule.build(source.slice(rule.prefix.length));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Probes the dist tree for the artifact implied by a known
|
|
66
|
+
* source→chrome transform. Returns the first absolute candidate whose
|
|
67
|
+
* POSIX path ends with the expected chrome suffix, or undefined when
|
|
68
|
+
* no transform applies or no candidate matches.
|
|
69
|
+
*
|
|
70
|
+
* The transform check is treated as high-confidence by `build-audit.ts`
|
|
71
|
+
* (callers pass `{ registered: true }` to `evaluateArtifactMtime`), so
|
|
72
|
+
* a match bypasses the structural-relation check that rejects generic
|
|
73
|
+
* basename collisions.
|
|
74
|
+
*
|
|
75
|
+
* @param source Engine-relative POSIX source path.
|
|
76
|
+
* @param searchRoots Absolute roots to probe (dist/, _tests/).
|
|
77
|
+
*/
|
|
78
|
+
export async function resolveArtifactByKnownTransform(source, searchRoots) {
|
|
79
|
+
const suffix = expectedChromeSuffix(source);
|
|
80
|
+
if (!suffix)
|
|
81
|
+
return undefined;
|
|
82
|
+
const name = basename(source);
|
|
83
|
+
const suffixWithSlash = `/${suffix}`;
|
|
84
|
+
for (const root of searchRoots) {
|
|
85
|
+
const candidates = await findAllByBasename(root, name);
|
|
86
|
+
for (const candidate of candidates) {
|
|
87
|
+
if (candidate.split(sep).join('/').endsWith(suffixWithSlash)) {
|
|
88
|
+
return candidate;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=build-audit-transforms.js.map
|