@hominis/fireforge 0.17.0 → 0.18.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 +53 -0
- package/README.md +60 -33
- package/dist/src/commands/build.js +18 -4
- package/dist/src/commands/doctor-furnace-manifest-sync.d.ts +18 -0
- package/dist/src/commands/doctor-furnace-manifest-sync.js +159 -0
- package/dist/src/commands/doctor-furnace.js +2 -0
- package/dist/src/commands/doctor-working-tree.d.ts +29 -0
- package/dist/src/commands/doctor-working-tree.js +93 -0
- package/dist/src/commands/doctor.js +22 -12
- package/dist/src/commands/export-all.js +74 -4
- package/dist/src/commands/export-shared.d.ts +7 -1
- package/dist/src/commands/export-shared.js +21 -3
- package/dist/src/commands/furnace/create-xpcshell.js +4 -2
- package/dist/src/commands/furnace/override.js +23 -13
- package/dist/src/commands/furnace/preview.js +38 -0
- package/dist/src/commands/furnace/remove.js +75 -1
- package/dist/src/commands/furnace/rename-xpcshell.d.ts +35 -0
- package/dist/src/commands/furnace/rename-xpcshell.js +97 -0
- package/dist/src/commands/furnace/rename.js +32 -4
- package/dist/src/commands/lint.js +19 -6
- package/dist/src/commands/patch/delete.js +4 -1
- package/dist/src/commands/patch/reorder.js +4 -1
- package/dist/src/commands/re-export-files.js +3 -1
- package/dist/src/commands/re-export.js +4 -1
- package/dist/src/commands/rebase/index.js +19 -1
- package/dist/src/commands/register.js +11 -0
- package/dist/src/commands/status.js +44 -5
- package/dist/src/commands/test.js +68 -16
- package/dist/src/commands/token-coverage.js +10 -3
- package/dist/src/commands/verify.js +81 -6
- package/dist/src/commands/watch.js +43 -7
- package/dist/src/commands/wire.js +16 -0
- package/dist/src/core/browser-wire.js +21 -4
- package/dist/src/core/build-audit.js +10 -0
- package/dist/src/core/furnace-constants.d.ts +14 -0
- package/dist/src/core/furnace-constants.js +16 -0
- package/dist/src/core/furnace-validate.js +67 -1
- package/dist/src/core/git-base.d.ts +27 -2
- package/dist/src/core/git-base.js +41 -3
- package/dist/src/core/git-diff.js +21 -2
- package/dist/src/core/git.js +53 -14
- package/dist/src/core/mach.d.ts +26 -8
- package/dist/src/core/mach.js +24 -8
- package/dist/src/core/manifest-rules.js +10 -1
- package/dist/src/core/manifest-tokenizers.d.ts +6 -0
- package/dist/src/core/manifest-tokenizers.js +28 -0
- package/dist/src/core/marionette-preflight.d.ts +16 -0
- package/dist/src/core/marionette-preflight.js +19 -0
- package/dist/src/core/patch-lint-diff-tag.d.ts +20 -0
- package/dist/src/core/patch-lint-diff-tag.js +25 -0
- package/dist/src/core/patch-lint.d.ts +47 -2
- package/dist/src/core/patch-lint.js +94 -18
- package/dist/src/core/patch-manifest-consistency.js +15 -2
- package/dist/src/core/patch-manifest-io.js +10 -0
- package/dist/src/core/patch-manifest-resolve.d.ts +20 -1
- package/dist/src/core/patch-manifest-resolve.js +29 -2
- package/dist/src/core/patch-manifest-validate.js +25 -1
- package/dist/src/core/patch-registration-refs.d.ts +42 -0
- package/dist/src/core/patch-registration-refs.js +117 -0
- package/dist/src/core/token-coverage.js +24 -0
- package/dist/src/core/wire-destroy.d.ts +7 -3
- package/dist/src/core/wire-destroy.js +11 -6
- package/dist/src/core/wire-init.d.ts +9 -3
- package/dist/src/core/wire-init.js +18 -6
- package/dist/src/core/wire-subscript.d.ts +7 -3
- package/dist/src/core/wire-subscript.js +11 -4
- package/dist/src/core/xpcshell-appdir.d.ts +19 -5
- package/dist/src/core/xpcshell-appdir.js +46 -20
- package/dist/src/errors/git.d.ts +20 -0
- package/dist/src/errors/git.js +39 -0
- package/dist/src/types/commands/patches.d.ts +23 -0
- package/dist/src/types/furnace.d.ts +9 -0
- package/dist/src/utils/parse.d.ts +7 -0
- package/dist/src/utils/parse.js +15 -0
- package/package.json +1 -1
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
* AST-based implementation: finds onLoad() method body, locates existing
|
|
6
6
|
* fireforge init blocks (TryStatements containing typeof guards), and inserts
|
|
7
7
|
* after the correct position.
|
|
8
|
+
*
|
|
9
|
+
* `marker` is prepended (uppercased) to the generated comment line so the
|
|
10
|
+
* emitted block carries the patch-lint `// <MARKER>:` signature that
|
|
11
|
+
* `lintModificationComments` looks for. Otherwise the first export after
|
|
12
|
+
* `wire` trips `missing-modification-comment` on wire-generated edits —
|
|
13
|
+
* exactly the eval 1 Finding #9 regression.
|
|
8
14
|
*/
|
|
9
|
-
export declare function addInitAST(content: string, expression: string, after?: string): string;
|
|
15
|
+
export declare function addInitAST(content: string, expression: string, after?: string, marker?: string): string;
|
|
10
16
|
/**
|
|
11
17
|
* Legacy regex/line-based implementation preserved as fallback.
|
|
12
18
|
*/
|
|
13
|
-
export declare function legacyAddInit(content: string, expression: string, after?: string): string;
|
|
19
|
+
export declare function legacyAddInit(content: string, expression: string, after?: string, marker?: string): string;
|
|
14
20
|
/**
|
|
15
21
|
* Adds an init expression as the first statement(s) in gBrowserInit.onLoad()
|
|
16
22
|
* in browser-init.js, after any previously-wired fireforge init blocks.
|
|
@@ -20,4 +26,4 @@ export declare function legacyAddInit(content: string, expression: string, after
|
|
|
20
26
|
* @param after - Optional name to insert after (e.g., "MyComponent" to insert after its block)
|
|
21
27
|
* @returns true if added, false if already present
|
|
22
28
|
*/
|
|
23
|
-
export declare function addInitToBrowserInit(engineDir: string, expression: string, after?: string): Promise<boolean>;
|
|
29
|
+
export declare function addInitToBrowserInit(engineDir: string, expression: string, after?: string, marker?: string): Promise<boolean>;
|
|
@@ -12,12 +12,24 @@ import { detectIndent, getNodeSource, parseScript } from './ast-utils.js';
|
|
|
12
12
|
import { withParserFallback } from './parser-fallback.js';
|
|
13
13
|
import { assertBraceBalancePreserved, coerceToCall, extractNameFromExpression, findInsertionAfterFireforgeBlocks, findMethodBody, findMethodBraceIndex, validateWireName, walkToTryBlockEnd, } from './wire-utils.js';
|
|
14
14
|
const BROWSER_INIT_JS = 'browser/base/content/browser-init.js';
|
|
15
|
+
/**
|
|
16
|
+
* Default patch-lint marker used when a caller does not supply a
|
|
17
|
+
* project-specific one. Kept as a constant so test fixtures and
|
|
18
|
+
* fallback code paths agree on the shape.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_MARKER = 'FIREFORGE:';
|
|
15
21
|
/**
|
|
16
22
|
* AST-based implementation: finds onLoad() method body, locates existing
|
|
17
23
|
* fireforge init blocks (TryStatements containing typeof guards), and inserts
|
|
18
24
|
* after the correct position.
|
|
25
|
+
*
|
|
26
|
+
* `marker` is prepended (uppercased) to the generated comment line so the
|
|
27
|
+
* emitted block carries the patch-lint `// <MARKER>:` signature that
|
|
28
|
+
* `lintModificationComments` looks for. Otherwise the first export after
|
|
29
|
+
* `wire` trips `missing-modification-comment` on wire-generated edits —
|
|
30
|
+
* exactly the eval 1 Finding #9 regression.
|
|
19
31
|
*/
|
|
20
|
-
export function addInitAST(content, expression, after) {
|
|
32
|
+
export function addInitAST(content, expression, after, marker = DEFAULT_MARKER) {
|
|
21
33
|
const name = extractNameFromExpression(expression);
|
|
22
34
|
// `validateWireName` accepts both `Foo.bar` and `Foo.bar()` shapes. The
|
|
23
35
|
// template below interpolates the value verbatim, so a bare property
|
|
@@ -99,7 +111,7 @@ export function addInitAST(content, expression, after) {
|
|
|
99
111
|
}
|
|
100
112
|
}
|
|
101
113
|
const block = [
|
|
102
|
-
`${indent}// ${
|
|
114
|
+
`${indent}// ${marker} wire-init ${name} — must be first, before Firefox subsystem`,
|
|
103
115
|
`${indent}// inits that reference native UI elements we hide.`,
|
|
104
116
|
`${indent}try {`,
|
|
105
117
|
`${indent} if (typeof ${name} !== "undefined") {`,
|
|
@@ -115,7 +127,7 @@ export function addInitAST(content, expression, after) {
|
|
|
115
127
|
/**
|
|
116
128
|
* Legacy regex/line-based implementation preserved as fallback.
|
|
117
129
|
*/
|
|
118
|
-
export function legacyAddInit(content, expression, after) {
|
|
130
|
+
export function legacyAddInit(content, expression, after, marker = DEFAULT_MARKER) {
|
|
119
131
|
const name = extractNameFromExpression(expression);
|
|
120
132
|
// See `addInitAST` for the rationale — the AST and fallback paths must
|
|
121
133
|
// agree on whether the emitted block is a function call, otherwise
|
|
@@ -174,7 +186,7 @@ export function legacyAddInit(content, expression, after) {
|
|
|
174
186
|
const inner = baseIndent + ' ';
|
|
175
187
|
const inner2 = inner + ' ';
|
|
176
188
|
const block = [
|
|
177
|
-
`${baseIndent}// ${
|
|
189
|
+
`${baseIndent}// ${marker} wire-init ${name} — must be first, before Firefox subsystem`,
|
|
178
190
|
`${baseIndent}// inits that reference native UI elements we hide.`,
|
|
179
191
|
`${baseIndent}try {`,
|
|
180
192
|
`${inner}if (typeof ${name} !== "undefined") {`,
|
|
@@ -196,7 +208,7 @@ export function legacyAddInit(content, expression, after) {
|
|
|
196
208
|
* @param after - Optional name to insert after (e.g., "MyComponent" to insert after its block)
|
|
197
209
|
* @returns true if added, false if already present
|
|
198
210
|
*/
|
|
199
|
-
export async function addInitToBrowserInit(engineDir, expression, after) {
|
|
211
|
+
export async function addInitToBrowserInit(engineDir, expression, after, marker = DEFAULT_MARKER) {
|
|
200
212
|
validateWireName(expression, 'init expression');
|
|
201
213
|
const filePath = join(engineDir, BROWSER_INIT_JS);
|
|
202
214
|
if (!(await pathExists(filePath))) {
|
|
@@ -212,7 +224,7 @@ export async function addInitToBrowserInit(engineDir, expression, after) {
|
|
|
212
224
|
if (initPattern.test(content)) {
|
|
213
225
|
return false;
|
|
214
226
|
}
|
|
215
|
-
const { value, usedFallback } = withParserFallback(() => addInitAST(content, expression, after), () => legacyAddInit(content, expression, after), BROWSER_INIT_JS);
|
|
227
|
+
const { value, usedFallback } = withParserFallback(() => addInitAST(content, expression, after, marker), () => legacyAddInit(content, expression, after, marker), BROWSER_INIT_JS);
|
|
216
228
|
if (usedFallback) {
|
|
217
229
|
assertBraceBalancePreserved(content, value, BROWSER_INIT_JS);
|
|
218
230
|
}
|
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* AST-based implementation: finds the last try/catch containing
|
|
6
6
|
* `loadSubScript` and inserts a new try/catch block after it.
|
|
7
|
+
*
|
|
8
|
+
* The inserted block carries a `// <MARKER>: wire-subscript ...` comment
|
|
9
|
+
* so the emitted edit satisfies `lintModificationComments` (eval 1
|
|
10
|
+
* Finding #9).
|
|
7
11
|
*/
|
|
8
|
-
export declare function addSubscriptAST(content: string, name: string): string;
|
|
12
|
+
export declare function addSubscriptAST(content: string, name: string, marker?: string): string;
|
|
9
13
|
/**
|
|
10
14
|
* Legacy regex/line-based implementation preserved as fallback.
|
|
11
15
|
*/
|
|
12
|
-
export declare function legacyAddSubscript(content: string, name: string): string;
|
|
16
|
+
export declare function legacyAddSubscript(content: string, name: string, marker?: string): string;
|
|
13
17
|
/**
|
|
14
18
|
* Adds a loadSubScript entry to browser-main.js with try/catch error handling.
|
|
15
19
|
*
|
|
@@ -17,4 +21,4 @@ export declare function legacyAddSubscript(content: string, name: string): strin
|
|
|
17
21
|
* @param name - Subscript name (without .js extension)
|
|
18
22
|
* @returns true if added, false if already present
|
|
19
23
|
*/
|
|
20
|
-
export declare function addSubscriptToBrowserMain(engineDir: string, name: string): Promise<boolean>;
|
|
24
|
+
export declare function addSubscriptToBrowserMain(engineDir: string, name: string, marker?: string): Promise<boolean>;
|
|
@@ -11,11 +11,16 @@ import { detectIndent, getNodeSource, parseScript, walkAST, } from './ast-utils.
|
|
|
11
11
|
import { withParserFallback } from './parser-fallback.js';
|
|
12
12
|
import { assertBraceBalancePreserved, findNearestTryLine, validateWireName, walkToTryBlockEnd, } from './wire-utils.js';
|
|
13
13
|
const BROWSER_MAIN_JS = 'browser/base/content/browser-main.js';
|
|
14
|
+
const DEFAULT_MARKER = 'FIREFORGE:';
|
|
14
15
|
/**
|
|
15
16
|
* AST-based implementation: finds the last try/catch containing
|
|
16
17
|
* `loadSubScript` and inserts a new try/catch block after it.
|
|
18
|
+
*
|
|
19
|
+
* The inserted block carries a `// <MARKER>: wire-subscript ...` comment
|
|
20
|
+
* so the emitted edit satisfies `lintModificationComments` (eval 1
|
|
21
|
+
* Finding #9).
|
|
17
22
|
*/
|
|
18
|
-
export function addSubscriptAST(content, name) {
|
|
23
|
+
export function addSubscriptAST(content, name, marker = DEFAULT_MARKER) {
|
|
19
24
|
const ast = parseScript(content);
|
|
20
25
|
const ms = new MagicString(content);
|
|
21
26
|
// Collect all TryStatements containing loadSubScript
|
|
@@ -58,6 +63,7 @@ export function addSubscriptAST(content, name) {
|
|
|
58
63
|
indent = detectIndent(content, lastBrace);
|
|
59
64
|
}
|
|
60
65
|
const block = [
|
|
66
|
+
`${indent}// ${marker} wire-subscript ${name}`,
|
|
61
67
|
`${indent}try {`,
|
|
62
68
|
`${indent} Services.scriptloader.loadSubScript("chrome://browser/content/${name}.js", this);`,
|
|
63
69
|
`${indent}} catch (e) {`,
|
|
@@ -70,7 +76,7 @@ export function addSubscriptAST(content, name) {
|
|
|
70
76
|
/**
|
|
71
77
|
* Legacy regex/line-based implementation preserved as fallback.
|
|
72
78
|
*/
|
|
73
|
-
export function legacyAddSubscript(content, name) {
|
|
79
|
+
export function legacyAddSubscript(content, name, marker = DEFAULT_MARKER) {
|
|
74
80
|
const lines = content.split('\n');
|
|
75
81
|
let lastSubScriptLine = -1;
|
|
76
82
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -103,6 +109,7 @@ export function legacyAddSubscript(content, name) {
|
|
|
103
109
|
const ind = refLine?.match(/^(\s*)/)?.[1] ?? ' ';
|
|
104
110
|
const inner = ind + ' ';
|
|
105
111
|
const block = [
|
|
112
|
+
`${ind}// ${marker} wire-subscript ${name}`,
|
|
106
113
|
`${ind}try {`,
|
|
107
114
|
`${inner}Services.scriptloader.loadSubScript("chrome://browser/content/${name}.js", this);`,
|
|
108
115
|
`${ind}} catch (e) {`,
|
|
@@ -119,7 +126,7 @@ export function legacyAddSubscript(content, name) {
|
|
|
119
126
|
* @param name - Subscript name (without .js extension)
|
|
120
127
|
* @returns true if added, false if already present
|
|
121
128
|
*/
|
|
122
|
-
export async function addSubscriptToBrowserMain(engineDir, name) {
|
|
129
|
+
export async function addSubscriptToBrowserMain(engineDir, name, marker = DEFAULT_MARKER) {
|
|
123
130
|
validateWireName(name, 'subscript name');
|
|
124
131
|
const filePath = join(engineDir, BROWSER_MAIN_JS);
|
|
125
132
|
if (!(await pathExists(filePath))) {
|
|
@@ -130,7 +137,7 @@ export async function addSubscriptToBrowserMain(engineDir, name) {
|
|
|
130
137
|
if (content.includes(`content/${name}.js"`)) {
|
|
131
138
|
return false;
|
|
132
139
|
}
|
|
133
|
-
const { value, usedFallback } = withParserFallback(() => addSubscriptAST(content, name), () => legacyAddSubscript(content, name), BROWSER_MAIN_JS);
|
|
140
|
+
const { value, usedFallback } = withParserFallback(() => addSubscriptAST(content, name, marker), () => legacyAddSubscript(content, name, marker), BROWSER_MAIN_JS);
|
|
134
141
|
if (usedFallback) {
|
|
135
142
|
assertBraceBalancePreserved(content, value, BROWSER_MAIN_JS);
|
|
136
143
|
}
|
|
@@ -96,11 +96,25 @@ export declare function readMozinfoAppname(objDirPath: string): Promise<string>;
|
|
|
96
96
|
* (which fails with a different error than the original `firefox-appdir`
|
|
97
97
|
* symptom and confuses triage).
|
|
98
98
|
*
|
|
99
|
-
* Probe order
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
99
|
+
* Probe order differs by host platform:
|
|
100
|
+
*
|
|
101
|
+
* - **macOS (`darwin`)**: prefer `<objDir>/dist/<App>.app/Contents/Resources/
|
|
102
|
+
* <value>` FIRST, then fall back to `<objDir>/dist/bin/<value>`.
|
|
103
|
+
* 2026-04-24 eval Finding 8: on macOS `dist/bin` is symlinked to
|
|
104
|
+
* `dist/<App>.app/Contents/MacOS/` (the *binaries* directory), so
|
|
105
|
+
* `dist/bin/browser` actually resolves to `<App>.app/Contents/MacOS/
|
|
106
|
+
* browser/`. That is NOT where `resource:///modules/` is rooted — on
|
|
107
|
+
* macOS, `-a` for xpcshell must point at the `.app/Contents/Resources/
|
|
108
|
+
* <value>` subtree where modules / chrome.manifest live. Returning
|
|
109
|
+
* `dist/bin/browser` caused the injected `--app-path` to look
|
|
110
|
+
* successful (the info log showed it) but pointed at a directory
|
|
111
|
+
* without the modules tree, so every `resource:///modules/…` import
|
|
112
|
+
* still threw.
|
|
113
|
+
* - **non-macOS**: keep the historical order — `dist/bin/<value>` first,
|
|
114
|
+
* `.app/Contents/Resources/<value>` as fallback.
|
|
115
|
+
*
|
|
116
|
+
* On both platforms the final `.app` fallback iterates every `*.app`
|
|
117
|
+
* entry because a rebranded fork may pick an arbitrary app name.
|
|
104
118
|
*/
|
|
105
119
|
export declare function resolveAbsoluteAppPath(objDirAbs: string, relativeAppdir: string): Promise<string | null>;
|
|
106
120
|
/**
|
|
@@ -164,34 +164,60 @@ export async function readMozinfoAppname(objDirPath) {
|
|
|
164
164
|
* (which fails with a different error than the original `firefox-appdir`
|
|
165
165
|
* symptom and confuses triage).
|
|
166
166
|
*
|
|
167
|
-
* Probe order
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
167
|
+
* Probe order differs by host platform:
|
|
168
|
+
*
|
|
169
|
+
* - **macOS (`darwin`)**: prefer `<objDir>/dist/<App>.app/Contents/Resources/
|
|
170
|
+
* <value>` FIRST, then fall back to `<objDir>/dist/bin/<value>`.
|
|
171
|
+
* 2026-04-24 eval Finding 8: on macOS `dist/bin` is symlinked to
|
|
172
|
+
* `dist/<App>.app/Contents/MacOS/` (the *binaries* directory), so
|
|
173
|
+
* `dist/bin/browser` actually resolves to `<App>.app/Contents/MacOS/
|
|
174
|
+
* browser/`. That is NOT where `resource:///modules/` is rooted — on
|
|
175
|
+
* macOS, `-a` for xpcshell must point at the `.app/Contents/Resources/
|
|
176
|
+
* <value>` subtree where modules / chrome.manifest live. Returning
|
|
177
|
+
* `dist/bin/browser` caused the injected `--app-path` to look
|
|
178
|
+
* successful (the info log showed it) but pointed at a directory
|
|
179
|
+
* without the modules tree, so every `resource:///modules/…` import
|
|
180
|
+
* still threw.
|
|
181
|
+
* - **non-macOS**: keep the historical order — `dist/bin/<value>` first,
|
|
182
|
+
* `.app/Contents/Resources/<value>` as fallback.
|
|
183
|
+
*
|
|
184
|
+
* On both platforms the final `.app` fallback iterates every `*.app`
|
|
185
|
+
* entry because a rebranded fork may pick an arbitrary app name.
|
|
172
186
|
*/
|
|
173
187
|
export async function resolveAbsoluteAppPath(objDirAbs, relativeAppdir) {
|
|
174
188
|
const distBinCandidate = join(objDirAbs, 'dist', 'bin', relativeAppdir);
|
|
175
|
-
if (await pathExists(distBinCandidate))
|
|
176
|
-
return distBinCandidate;
|
|
177
189
|
const distDir = join(objDirAbs, 'dist');
|
|
178
|
-
|
|
190
|
+
const isMacos = process.platform === 'darwin';
|
|
191
|
+
async function probeMacAppBundle() {
|
|
192
|
+
if (!(await pathExists(distDir)))
|
|
193
|
+
return null;
|
|
194
|
+
let entries;
|
|
195
|
+
try {
|
|
196
|
+
entries = await readdir(distDir);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
for (const entry of entries) {
|
|
202
|
+
if (!entry.endsWith('.app'))
|
|
203
|
+
continue;
|
|
204
|
+
const candidate = join(distDir, entry, 'Contents', 'Resources', relativeAppdir);
|
|
205
|
+
if (await pathExists(candidate))
|
|
206
|
+
return candidate;
|
|
207
|
+
}
|
|
179
208
|
return null;
|
|
180
|
-
let entries;
|
|
181
|
-
try {
|
|
182
|
-
entries = await readdir(distDir);
|
|
183
209
|
}
|
|
184
|
-
|
|
210
|
+
if (isMacos) {
|
|
211
|
+
const appBundle = await probeMacAppBundle();
|
|
212
|
+
if (appBundle)
|
|
213
|
+
return appBundle;
|
|
214
|
+
if (await pathExists(distBinCandidate))
|
|
215
|
+
return distBinCandidate;
|
|
185
216
|
return null;
|
|
186
217
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const candidate = join(distDir, entry, 'Contents', 'Resources', relativeAppdir);
|
|
191
|
-
if (await pathExists(candidate))
|
|
192
|
-
return candidate;
|
|
193
|
-
}
|
|
194
|
-
return null;
|
|
218
|
+
if (await pathExists(distBinCandidate))
|
|
219
|
+
return distBinCandidate;
|
|
220
|
+
return probeMacAppBundle();
|
|
195
221
|
}
|
|
196
222
|
/**
|
|
197
223
|
* Top-level resolver. Walks every test path, reads the nearest
|
package/dist/src/errors/git.d.ts
CHANGED
|
@@ -39,3 +39,23 @@ export declare class GitIndexLockError extends GitError {
|
|
|
39
39
|
constructor(lockPath: string, ageMs?: number | undefined);
|
|
40
40
|
get userMessage(): string;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Error thrown when `git add` (monolithic or chunked) exceeds the
|
|
44
|
+
* configured timeout while indexing the Firefox source tree.
|
|
45
|
+
*
|
|
46
|
+
* 2026-04-24 eval Finding 10: a 140.10.0esr bump on a previously-working
|
|
47
|
+
* 140.9.0esr workspace aborted after ~854s with a generic
|
|
48
|
+
* `AbortError: The operation was aborted`. The root cause was the
|
|
49
|
+
* `git add` timeout firing, but the surfaced error was indistinguishable
|
|
50
|
+
* from any other AbortError and gave the operator no actionable
|
|
51
|
+
* direction. This typed error carries the elapsed budget and the
|
|
52
|
+
* environment-variable override so the recovery path is
|
|
53
|
+
* self-documenting.
|
|
54
|
+
*/
|
|
55
|
+
export declare class GitIndexingTimeoutError extends GitError {
|
|
56
|
+
readonly phase: 'monolithic' | 'chunked';
|
|
57
|
+
readonly timeoutMs: number;
|
|
58
|
+
readonly envVar: string;
|
|
59
|
+
constructor(phase: 'monolithic' | 'chunked', timeoutMs: number, envVar: string, cause?: Error);
|
|
60
|
+
get userMessage(): string;
|
|
61
|
+
}
|
package/dist/src/errors/git.js
CHANGED
|
@@ -96,4 +96,43 @@ export class GitIndexLockError extends GitError {
|
|
|
96
96
|
' 3. Re-run "fireforge download --force"');
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Error thrown when `git add` (monolithic or chunked) exceeds the
|
|
101
|
+
* configured timeout while indexing the Firefox source tree.
|
|
102
|
+
*
|
|
103
|
+
* 2026-04-24 eval Finding 10: a 140.10.0esr bump on a previously-working
|
|
104
|
+
* 140.9.0esr workspace aborted after ~854s with a generic
|
|
105
|
+
* `AbortError: The operation was aborted`. The root cause was the
|
|
106
|
+
* `git add` timeout firing, but the surfaced error was indistinguishable
|
|
107
|
+
* from any other AbortError and gave the operator no actionable
|
|
108
|
+
* direction. This typed error carries the elapsed budget and the
|
|
109
|
+
* environment-variable override so the recovery path is
|
|
110
|
+
* self-documenting.
|
|
111
|
+
*/
|
|
112
|
+
export class GitIndexingTimeoutError extends GitError {
|
|
113
|
+
phase;
|
|
114
|
+
timeoutMs;
|
|
115
|
+
envVar;
|
|
116
|
+
constructor(phase, timeoutMs, envVar, cause) {
|
|
117
|
+
super(`Git ${phase} indexing exceeded the ${Math.round(timeoutMs / 1000)}s timeout`, 'add -A', cause);
|
|
118
|
+
this.phase = phase;
|
|
119
|
+
this.timeoutMs = timeoutMs;
|
|
120
|
+
this.envVar = envVar;
|
|
121
|
+
}
|
|
122
|
+
get userMessage() {
|
|
123
|
+
const minutes = Math.max(1, Math.round(this.timeoutMs / 60_000));
|
|
124
|
+
const phaseDescription = this.phase === 'monolithic'
|
|
125
|
+
? 'the monolithic `git add -A` pass'
|
|
126
|
+
: 'one of the chunked `git add -- <dir>` passes';
|
|
127
|
+
return (`Git Error: ${phaseDescription} exceeded the ${minutes}-minute timeout while indexing the Firefox source tree.\n\n` +
|
|
128
|
+
'Common triggers:\n' +
|
|
129
|
+
' - Slow or loaded disk (an external volume, encrypted filesystem, or heavily-used SSD under load).\n' +
|
|
130
|
+
' - A Firefox source tree that has grown beyond what the default timeout accommodates.\n' +
|
|
131
|
+
' - A background process (antivirus, backup, indexing) holding the working directory.\n\n' +
|
|
132
|
+
'To recover:\n' +
|
|
133
|
+
` 1. Extend the timeout via the ${this.envVar} environment variable (milliseconds; e.g. "export ${this.envVar}=1800000" for 30 minutes).\n` +
|
|
134
|
+
' 2. Re-run "fireforge download --force" — the resume path resumes from the partial initialisation, so the repeat pass is not wasted work.\n' +
|
|
135
|
+
' 3. If the problem persists, check disk throughput and free space; Firefox source indexing on a cold SSD typically completes in 1–3 minutes.');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
99
138
|
//# sourceMappingURL=git.js.map
|
|
@@ -73,6 +73,29 @@ export interface PatchMetadata {
|
|
|
73
73
|
* renamed or removed.
|
|
74
74
|
*/
|
|
75
75
|
lintIgnore?: string[];
|
|
76
|
+
/**
|
|
77
|
+
* Optional per-patch threshold-tier override for the `large-patch-lines`
|
|
78
|
+
* rule. Exists for branding patches that must touch a small number of
|
|
79
|
+
* cross-cutting registration files alongside `browser/branding/<name>/`
|
|
80
|
+
* (notably `browser/moz.configure` to register the new branding flavor
|
|
81
|
+
* with the top-level configure). The narrow auto-detect allowlist in
|
|
82
|
+
* `isBrandingOnlyPatch` covers the canonical shape, but a fork whose
|
|
83
|
+
* branding patch also touches an unlisted sibling (for example a
|
|
84
|
+
* `browser/themes/<name>/` override or a vendor-specific icon
|
|
85
|
+
* resource) falls through to the general tier and trips the hard
|
|
86
|
+
* limit on what is legitimately one branding diff.
|
|
87
|
+
*
|
|
88
|
+
* Declaring `tier: "branding"` here forces the branding thresholds
|
|
89
|
+
* (notice 3000 / warning 8000 / error 20000) regardless of
|
|
90
|
+
* `filesAffected`. The tier is the weaker claim than test — a patch
|
|
91
|
+
* of all-tests still lands in the test tier even if this field is
|
|
92
|
+
* set, because the test-tier thresholds are already more permissive
|
|
93
|
+
* and a test that is also branding-shaped is vanishingly rare.
|
|
94
|
+
*
|
|
95
|
+
* Only `"branding"` is currently recognised. Unknown values are
|
|
96
|
+
* rejected by the manifest validator, not silently stripped.
|
|
97
|
+
*/
|
|
98
|
+
tier?: 'branding';
|
|
76
99
|
}
|
|
77
100
|
/**
|
|
78
101
|
* Schema for patches/patches.json file.
|
|
@@ -102,6 +102,15 @@ export interface FurnaceConfig {
|
|
|
102
102
|
tokenPrefix?: string;
|
|
103
103
|
/** Custom properties allowed even though they don't match tokenPrefix (e.g. ["--background-color-box"]) */
|
|
104
104
|
tokenAllowlist?: string[];
|
|
105
|
+
/**
|
|
106
|
+
* CSS custom-property prefixes that identify upstream / platform
|
|
107
|
+
* variables the fork does not own. `token coverage` counts matches
|
|
108
|
+
* as `allowlisted` rather than `unknown` so a copied upstream
|
|
109
|
+
* baseline doesn't drag fork-owned coverage percentages down.
|
|
110
|
+
* Defaults to `['--moz-']` when unset. Pass an explicit empty array
|
|
111
|
+
* to restore the pre-0.18.0 strict contract.
|
|
112
|
+
*/
|
|
113
|
+
platformPrefixes?: string[];
|
|
105
114
|
/**
|
|
106
115
|
* Custom properties used as runtime state channels — written and read by the
|
|
107
116
|
* component itself (e.g. per-frame camera/tile positions) rather than
|
|
@@ -76,6 +76,13 @@ export declare class ParsedRecord {
|
|
|
76
76
|
* @throws Error if the field is missing or not an array of strings
|
|
77
77
|
*/
|
|
78
78
|
stringArray(key: string): string[];
|
|
79
|
+
/**
|
|
80
|
+
* Extracts an optional array-of-strings field.
|
|
81
|
+
* @param key - Field name
|
|
82
|
+
* @returns The string array (fresh copy) or undefined when absent
|
|
83
|
+
* @throws Error if the field is present but not an array of strings
|
|
84
|
+
*/
|
|
85
|
+
optionalStringArray(key: string): string[] | undefined;
|
|
79
86
|
/**
|
|
80
87
|
* Extracts a required nested object field.
|
|
81
88
|
* @param key - Field name
|
package/dist/src/utils/parse.js
CHANGED
|
@@ -142,6 +142,21 @@ export class ParsedRecord {
|
|
|
142
142
|
}
|
|
143
143
|
return [...value];
|
|
144
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Extracts an optional array-of-strings field.
|
|
147
|
+
* @param key - Field name
|
|
148
|
+
* @returns The string array (fresh copy) or undefined when absent
|
|
149
|
+
* @throws Error if the field is present but not an array of strings
|
|
150
|
+
*/
|
|
151
|
+
optionalStringArray(key) {
|
|
152
|
+
const value = this.#data[key];
|
|
153
|
+
if (value === undefined)
|
|
154
|
+
return undefined;
|
|
155
|
+
if (!isArray(value) || !value.every(isString)) {
|
|
156
|
+
throw new Error(`${this.#label}.${key} must be an array of strings`);
|
|
157
|
+
}
|
|
158
|
+
return [...value];
|
|
159
|
+
}
|
|
145
160
|
/**
|
|
146
161
|
* Extracts a required nested object field.
|
|
147
162
|
* @param key - Field name
|