@hominis/fireforge 0.49.0 → 0.49.2
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 +2 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/furnace/create-templates.d.ts +8 -3
- package/dist/src/commands/furnace/create-templates.js +8 -23
- package/dist/src/core/objdir-maintenance.js +17 -1
- package/dist/src/core/patch-parse.js +8 -6
- package/dist/src/core/token-category.js +28 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
### Furnace and builds
|
|
6
6
|
|
|
7
7
|
- Safer Furnace deployment and objdir cloning protect component sources and peer checkouts from shared files and directory aliases. Failed copies finish before rollback; cloned build metadata is relocated and verified.
|
|
8
|
+
- `furnace create --localized` no longer scaffolds a `connectedCallback`/`disconnectedCallback` pair that calls `l10n.connectRoot(shadowRoot)` and `disconnectRoot`. `MozLitElement` already registers its shadow render root with `document.l10n`, so the generated pair registered the same root twice, which `DOMLocalization::ConnectRoot` asserts against in debug builds. The scaffold keeps the module-level `window.MozXULElement?.insertFTLIfNeeded(...)` call.
|
|
8
9
|
- Build and test preflight detect broken partial configuration, stale artifacts and invalid build baselines. Scoped builds retain coverage only while their inputs remain unchanged, and builds remove dangling test links.
|
|
9
10
|
|
|
10
11
|
### Test harness
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
### Tooling
|
|
29
30
|
|
|
30
31
|
- Full-Firefox integration preserves existing workspace files and Git index state, and fails on incomplete recovery. Build identity includes untracked contents and binary changes. Release checks enforce the pinned toolchain and additional module coverage floors.
|
|
32
|
+
- Dev tooling: `eslint-plugin-jsdoc` 65.0.1 and `@types/node` 26.6.4. Transitive `source-map-js` 1.2.2 (GHSA-68fv-2mgg-jv7q) and `smol-toml` 1.9.0 (GHSA-r4xh-jqrq-34v2) clear `npm audit`.
|
|
31
33
|
|
|
32
34
|
## 0.48.0
|
|
33
35
|
|
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.49.
|
|
4
|
-
"commit": "
|
|
5
|
-
"shortCommit": "
|
|
3
|
+
"version": "0.49.2",
|
|
4
|
+
"commit": "c707978869b62aba07d1e8f0955be00c223d5ce0",
|
|
5
|
+
"shortCommit": "c707978869b6",
|
|
6
6
|
"dirty": false,
|
|
7
7
|
"dirtyHash": null,
|
|
8
|
-
"buildTime": "2026-10-
|
|
8
|
+
"buildTime": "2026-10-06T21:50:15.508Z"
|
|
9
9
|
}
|
|
@@ -26,9 +26,14 @@ export interface GenerateMjsContentOptions {
|
|
|
26
26
|
* `MozXULElement`. Calling it from `connectedCallback` on a Lit-based
|
|
27
27
|
* component throws `TypeError: this.insertFTLIfNeeded is not a function` at
|
|
28
28
|
* every connect. Upstream Firefox components (e.g. `moz-input-folder.mjs`)
|
|
29
|
-
* solve this with a module-level guarded call on `window.MozXULElement
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* solve this with a module-level guarded call on `window.MozXULElement`, and
|
|
30
|
+
* the template mirrors that call so `--localized` produces functional code.
|
|
31
|
+
*
|
|
32
|
+
* The template emits no `connectedCallback`/`disconnectedCallback` pair for
|
|
33
|
+
* Fluent. `MozLitElement` already connects its shadow render root to
|
|
34
|
+
* `document.l10n` on connect and disconnects it on disconnect, so a second
|
|
35
|
+
* `l10n.connectRoot(shadowRoot)` registers the same root twice, which
|
|
36
|
+
* `DOMLocalization::ConnectRoot` asserts against in debug builds.
|
|
32
37
|
*
|
|
33
38
|
* Path resolution precedence (when `localized` is true):
|
|
34
39
|
* 1. `sharedFtl`: used verbatim. The caller has resolved it from
|
|
@@ -11,9 +11,14 @@
|
|
|
11
11
|
* `MozXULElement`. Calling it from `connectedCallback` on a Lit-based
|
|
12
12
|
* component throws `TypeError: this.insertFTLIfNeeded is not a function` at
|
|
13
13
|
* every connect. Upstream Firefox components (e.g. `moz-input-folder.mjs`)
|
|
14
|
-
* solve this with a module-level guarded call on `window.MozXULElement
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* solve this with a module-level guarded call on `window.MozXULElement`, and
|
|
15
|
+
* the template mirrors that call so `--localized` produces functional code.
|
|
16
|
+
*
|
|
17
|
+
* The template emits no `connectedCallback`/`disconnectedCallback` pair for
|
|
18
|
+
* Fluent. `MozLitElement` already connects its shadow render root to
|
|
19
|
+
* `document.l10n` on connect and disconnects it on disconnect, so a second
|
|
20
|
+
* `l10n.connectRoot(shadowRoot)` registers the same root twice, which
|
|
21
|
+
* `DOMLocalization::ConnectRoot` asserts against in debug builds.
|
|
17
22
|
*
|
|
18
23
|
* Path resolution precedence (when `localized` is true):
|
|
19
24
|
* 1. `sharedFtl`: used verbatim. The caller has resolved it from
|
|
@@ -45,25 +50,6 @@ export function generateMjsContent(options) {
|
|
|
45
50
|
const ftlModulePreamble = localized
|
|
46
51
|
? `
|
|
47
52
|
window.MozXULElement?.insertFTLIfNeeded(${JSON.stringify(ftlPath)});
|
|
48
|
-
`
|
|
49
|
-
: '';
|
|
50
|
-
const lifecycleHooks = localized
|
|
51
|
-
? `
|
|
52
|
-
connectedCallback() {
|
|
53
|
-
super.connectedCallback();
|
|
54
|
-
const { shadowRoot } = this;
|
|
55
|
-
if (shadowRoot) {
|
|
56
|
-
this.ownerDocument.l10n?.connectRoot(shadowRoot);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
disconnectedCallback() {
|
|
61
|
-
super.disconnectedCallback();
|
|
62
|
-
const { shadowRoot } = this;
|
|
63
|
-
if (shadowRoot) {
|
|
64
|
-
this.ownerDocument.l10n?.disconnectRoot(shadowRoot);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
53
|
`
|
|
68
54
|
: '';
|
|
69
55
|
return `${header}
|
|
@@ -80,7 +66,6 @@ class ${className} extends MozLitElement {
|
|
|
80
66
|
/** @type {Record<string, unknown>} */
|
|
81
67
|
static properties = {};
|
|
82
68
|
|
|
83
|
-
${lifecycleHooks}
|
|
84
69
|
render() {
|
|
85
70
|
return html\`
|
|
86
71
|
<link rel="stylesheet" href="chrome://global/content/elements/${name}.css" />
|
|
@@ -30,6 +30,15 @@ function replacePrefix(value, oldRoot, newRoot) {
|
|
|
30
30
|
? newRoot + value.slice(oldRoot.length)
|
|
31
31
|
: value;
|
|
32
32
|
}
|
|
33
|
+
/** Rewrites references under oldRoot, including the forward-slash spelling mozbuild writes on Windows. */
|
|
34
|
+
function rewriteRootReferences(content, oldRoot, newRoot) {
|
|
35
|
+
const rewritten = content.replaceAll(oldRoot + sep, newRoot + sep);
|
|
36
|
+
if (sep !== '\\')
|
|
37
|
+
return rewritten;
|
|
38
|
+
return rewritten
|
|
39
|
+
.replaceAll(oldRoot + '/', newRoot + '/')
|
|
40
|
+
.replaceAll(oldRoot.replaceAll('\\', '/') + '/', newRoot.replaceAll('\\', '/') + '/');
|
|
41
|
+
}
|
|
33
42
|
function isDependencyMetadata(path) {
|
|
34
43
|
return (/\.(?:pp|d)$/.test(path) ||
|
|
35
44
|
/[/\\]faster[/\\]/.test(path) ||
|
|
@@ -72,6 +81,13 @@ export async function relocateObjdirProducts(engineDir, objDir, oldRoot) {
|
|
|
72
81
|
try {
|
|
73
82
|
await rename(temp, path);
|
|
74
83
|
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// Windows refuses to rename over a directory link; replace it in two steps.
|
|
86
|
+
if (process.platform !== 'win32' || getNodeErrorCode(error) !== 'EPERM')
|
|
87
|
+
throw error;
|
|
88
|
+
await rm(path);
|
|
89
|
+
await rename(temp, path);
|
|
90
|
+
}
|
|
75
91
|
finally {
|
|
76
92
|
await rm(temp, { force: true });
|
|
77
93
|
}
|
|
@@ -79,7 +95,7 @@ export async function relocateObjdirProducts(engineDir, objDir, oldRoot) {
|
|
|
79
95
|
}
|
|
80
96
|
else if (isDependencyMetadata(path)) {
|
|
81
97
|
const content = await readFile(path, 'utf8');
|
|
82
|
-
const rewritten = content
|
|
98
|
+
const rewritten = rewriteRootReferences(content, oldRoot, newRoot);
|
|
83
99
|
if (content !== rewritten)
|
|
84
100
|
await writeText(path, rewritten);
|
|
85
101
|
}
|
|
@@ -142,12 +142,14 @@ export function parseDiffGitHeader(line) {
|
|
|
142
142
|
return { sourcePath: candidate, targetPath: candidate };
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
-
// Rename/copy (differing paths):
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
return
|
|
149
|
-
|
|
150
|
-
|
|
145
|
+
// Rename/copy (differing paths): split at the first ` b/` with a path on
|
|
146
|
+
// each side. indexOf keeps this linear where a lazy regex backtracks.
|
|
147
|
+
if (/[\n\r\u2028\u2029]/.test(rest))
|
|
148
|
+
return null;
|
|
149
|
+
const split = rest.indexOf(' b/', 1);
|
|
150
|
+
if (split === -1 || split + 3 >= rest.length)
|
|
151
|
+
return null;
|
|
152
|
+
return { sourcePath: rest.slice(0, split), targetPath: rest.slice(split + 3) };
|
|
151
153
|
}
|
|
152
154
|
/**
|
|
153
155
|
* Splits raw diff content into lines, tolerating CRLF-saved patch files.
|
|
@@ -209,6 +209,33 @@ export function findCategorySection(lines, category, tokensCssPath) {
|
|
|
209
209
|
}
|
|
210
210
|
return { categoryLine: section.categoryLine, sectionEnd: section.sectionEnd };
|
|
211
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Drops complete quoted strings from one CSS line in linear time. An
|
|
214
|
+
* unterminated quote is kept, and once one fails to close every later quote
|
|
215
|
+
* of that kind fails too, so no start position is ever rescanned.
|
|
216
|
+
*/
|
|
217
|
+
function stripCssStrings(line) {
|
|
218
|
+
let out = '';
|
|
219
|
+
const unterminated = new Set();
|
|
220
|
+
for (let i = 0; i < line.length; i++) {
|
|
221
|
+
const quote = line.charAt(i);
|
|
222
|
+
if ((quote !== '"' && quote !== "'") || unterminated.has(quote)) {
|
|
223
|
+
out += quote;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
let end = i + 1;
|
|
227
|
+
while (end < line.length && line.charAt(end) !== quote)
|
|
228
|
+
end += line.charAt(end) === '\\' ? 2 : 1;
|
|
229
|
+
if (end >= line.length) {
|
|
230
|
+
unterminated.add(quote);
|
|
231
|
+
out += quote;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
i = end;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
212
239
|
/** 0-based open/close line indices of the base `:root {` block. */
|
|
213
240
|
export function findBaseRootBounds(lines) {
|
|
214
241
|
const open = lines.findIndex((line) => /:root\s*\{/.test(line));
|
|
@@ -218,7 +245,7 @@ export function findBaseRootBounds(lines) {
|
|
|
218
245
|
let depth = 0;
|
|
219
246
|
for (let i = open; i < lines.length; i++) {
|
|
220
247
|
// Ignore braces in CSS strings (including data URLs).
|
|
221
|
-
const code = (masked[i] ?? '')
|
|
248
|
+
const code = stripCssStrings(masked[i] ?? '');
|
|
222
249
|
for (const char of code) {
|
|
223
250
|
if (char === '{')
|
|
224
251
|
depth++;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hominis/fireforge",
|
|
3
|
-
"version": "0.49.
|
|
3
|
+
"version": "0.49.2",
|
|
4
4
|
"description": "FireForge — a build tool for customizing Firefox",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|
|
@@ -72,13 +72,13 @@
|
|
|
72
72
|
"dpdm": "4.3.0",
|
|
73
73
|
"eslint": "^10.0.0",
|
|
74
74
|
"eslint-config-prettier": "^10.1.8",
|
|
75
|
-
"eslint-plugin-jsdoc": "^
|
|
75
|
+
"eslint-plugin-jsdoc": "^65.0.1",
|
|
76
76
|
"eslint-plugin-simple-import-sort": "^14.0.0",
|
|
77
77
|
"fast-check": "^4.6.0",
|
|
78
78
|
"husky": "^9.1.7",
|
|
79
|
-
"knip": "6.
|
|
79
|
+
"knip": "6.39.0",
|
|
80
80
|
"lint-staged": "^17.0.4",
|
|
81
|
-
"prettier": "3.9.
|
|
81
|
+
"prettier": "3.9.9",
|
|
82
82
|
"tsx": "^4.7.0",
|
|
83
83
|
"typescript": "~6.0.0",
|
|
84
84
|
"typescript-eslint": "^8.0.0",
|