@hominis/fireforge 0.15.1 → 0.15.3

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +39 -3
  2. package/README.md +76 -3
  3. package/dist/src/commands/build.js +41 -3
  4. package/dist/src/commands/furnace/chrome-doc-templates.d.ts +49 -0
  5. package/dist/src/commands/furnace/chrome-doc-templates.js +151 -0
  6. package/dist/src/commands/furnace/chrome-doc.d.ts +34 -0
  7. package/dist/src/commands/furnace/chrome-doc.js +168 -0
  8. package/dist/src/commands/furnace/create-mochikit.d.ts +30 -0
  9. package/dist/src/commands/furnace/create-mochikit.js +70 -0
  10. package/dist/src/commands/furnace/create-templates.d.ts +53 -0
  11. package/dist/src/commands/furnace/create-templates.js +118 -0
  12. package/dist/src/commands/furnace/create-xpcshell.d.ts +27 -0
  13. package/dist/src/commands/furnace/create-xpcshell.js +53 -0
  14. package/dist/src/commands/furnace/create.d.ts +17 -0
  15. package/dist/src/commands/furnace/create.js +59 -12
  16. package/dist/src/commands/furnace/index.d.ts +2 -1
  17. package/dist/src/commands/furnace/index.js +20 -2
  18. package/dist/src/commands/lint.d.ts +13 -1
  19. package/dist/src/commands/lint.js +33 -7
  20. package/dist/src/commands/setup.d.ts +1 -1
  21. package/dist/src/commands/setup.js +3 -2
  22. package/dist/src/core/build-audit.d.ts +46 -0
  23. package/dist/src/core/build-audit.js +251 -0
  24. package/dist/src/core/build-baseline.d.ts +59 -0
  25. package/dist/src/core/build-baseline.js +83 -0
  26. package/dist/src/core/build-prepare.d.ts +20 -1
  27. package/dist/src/core/build-prepare.js +94 -4
  28. package/dist/src/core/furnace-apply-helpers.d.ts +1 -1
  29. package/dist/src/core/furnace-config-tokens.d.ts +6 -0
  30. package/dist/src/core/furnace-config-tokens.js +15 -0
  31. package/dist/src/core/furnace-config.js +10 -4
  32. package/dist/src/core/furnace-operation.d.ts +2 -1
  33. package/dist/src/core/furnace-operation.js +13 -7
  34. package/dist/src/core/furnace-registration-ast.d.ts +2 -2
  35. package/dist/src/core/furnace-registration-ast.js +1 -1
  36. package/dist/src/core/furnace-validate-compatibility.js +18 -7
  37. package/dist/src/core/furnace-validate-helpers.d.ts +31 -1
  38. package/dist/src/core/furnace-validate-helpers.js +101 -18
  39. package/dist/src/core/furnace-validate-registration.d.ts +1 -1
  40. package/dist/src/core/furnace-validate-registration.js +1 -1
  41. package/dist/src/core/mach-error-hints.d.ts +29 -0
  42. package/dist/src/core/mach-error-hints.js +43 -0
  43. package/dist/src/core/mach.d.ts +5 -2
  44. package/dist/src/core/mach.js +31 -4
  45. package/dist/src/core/marionette-preflight.d.ts +14 -7
  46. package/dist/src/core/marionette-preflight.js +94 -44
  47. package/dist/src/core/patch-lint-cross.d.ts +1 -1
  48. package/dist/src/core/patch-lint-cross.js +1 -1
  49. package/dist/src/core/patch-lint-diff-tag.d.ts +33 -0
  50. package/dist/src/core/patch-lint-diff-tag.js +83 -0
  51. package/dist/src/core/patch-lint.js +29 -9
  52. package/dist/src/types/commands/options.d.ts +25 -0
  53. package/dist/src/types/commands/patches.d.ts +9 -0
  54. package/dist/src/types/config.d.ts +1 -1
  55. package/dist/src/types/furnace.d.ts +13 -2
  56. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,7 +5,7 @@
5
5
  ### Furnace registration
6
6
 
7
7
  - `furnace apply` idempotency check is marker-comment-tolerant. Previously the single-line substring match (`content.includes('["tag",')`) missed multi-line entries, and the standalone-line regex anchored on `\s*$`, which did not allow trailing `// <marker>:` comments an operator may have appended to a previously-written entry. A duplicate tag was then inserted on every re-apply, and the second `setElementCreationCallback` invocation threw `NotSupportedError: Operation is not supported` at every window-load. The idempotency check now matches on tag-name column 0 (both single- and multi-line array shapes) and tolerates trailing `//` comments on the line.
8
- - New optional fireforge.json field `markerComment` (e.g. `"HOMINIS"`) is appended as a ` // HOMINIS:` suffix to every line FireForge writes into `customElements.js`. Keeps fork modifications discoverable and re-applies idempotent without hand-tagging after each apply. The field is threaded through `applyCustomComponent` and `furnace deploy`, not just `furnace create`.
8
+ - New optional fireforge.json field `markerComment` (e.g. `"MYBROWSER"`) is appended as a ` // MYBROWSER:` suffix to every line FireForge writes into `customElements.js`. Keeps fork modifications discoverable and re-applies idempotent without hand-tagging after each apply. The field is threaded through `applyCustomComponent` and `furnace deploy`, not just `furnace create`.
9
9
  - `addCustomElementRegistration` and its regex fallback both accept the new marker as an optional parameter; the AST idempotency check and the regex-fallback idempotency check share a single helper (`isTagAlreadyRegistered`).
10
10
 
11
11
  ### Furnace `--localized`
@@ -17,19 +17,55 @@
17
17
 
18
18
  ### Furnace validate
19
19
 
20
- - `missing-token-link` now reads `tokenHostDocuments` from furnace.json and scans every configured chrome document for the tokens CSS link. Warning fires only when NONE of them link the tokens CSS; the warning enumerates the documents it actually checked. Previously the check was hardcoded to `browser/base/content/browser.xhtml`, which false-positived on forks that mount components in a different chrome document (e.g. `hominis.xhtml`). Defaults to `["browser/base/content/browser.xhtml"]` when omitted — behaviour is unchanged for projects that never set the field.
20
+ - `missing-token-link` now reads `tokenHostDocuments` from furnace.json and scans every configured chrome document for the tokens CSS link. Warning fires only when NONE of them link the tokens CSS; the warning enumerates the documents it actually checked. Previously the check was hardcoded to `browser/base/content/browser.xhtml`, which false-positived on forks that mount components in a different chrome document (e.g. `mybrowser.xhtml`). Defaults to `["browser/base/content/browser.xhtml"]` when omitted — behaviour is unchanged for projects that never set the field.
21
21
  - `no-keyboard-handler` no longer warns when `@click` sits on a native interactive element (`<button>`, `<a href>`, `<input>`, `<select>`, `<textarea>`, `<summary>`, `<details>`, or the Firefox `moz-button`/`moz-toggle`/`moz-checkbox`/`moz-radio`/`moz-menulist` widgets). Those elements dispatch `click` on Enter and Space via the platform, so a duplicate `@keydown`/`@keypress` handler would double-fire. The rule still fires for synthetic interactive markup (e.g. `<div @click>`) and for bare `<a>` without an `href` attribute, which are the real keyboard-a11y hazards.
22
+ - `token-prefix-violation` stops flagging component-owned runtime CSS custom properties. Previously every `var(--foo)` that did not match `tokenPrefix` was rejected as a design-token escape, even when the property was a per-frame state channel (`--cam-x`, `--tile-z`) both written and read by the component. Two relaxations ship together: (a) a new optional `runtimeVariables: string[]` field in `furnace.json` explicitly allowlists cross-component runtime channels (e.g. set in JS, read in the child's CSS); (b) variables that are both declared (`--foo: value;`) and consumed (`var(--foo)`) inside the same CSS file are auto-exempted — no config entry required. The same relaxations apply to the patch-stack lint. The violation message now calls out `runtimeVariables` as the third escape hatch alongside `tokenPrefix` and `tokenAllowlist`.
23
+ - `hardcoded-text` narrowed from a bare `>…<` scan to three context-aware probes: text inside Lit `` html`…` `` template literals, string literals assigned via `.textContent = "…"` / `.innerHTML = "…"`, and XUL-attribute values set via `setAttribute("label"|"title"|"tooltiptext", "…")`. Previously the rule also matched JS comparisons (`if (x > 0 && y < 100)`), long diagnostic strings (`console.error("…")`), and identifier literals passed to `querySelector`, producing noise that trained authors to ignore validator warnings. The file-wide `// furnace-ignore: hardcoded-text` escape hatch is preserved.
22
24
 
23
25
  ### Run / test
24
26
 
25
27
  - `fireforge run`, `fireforge watch`, `fireforge build`, and every other `mach` invocation launched with inherited stdio now forward parent `SIGINT`/`SIGTERM` to the child as `SIGTERM` and wait ~1.5 s before escalating to `SIGKILL`. A second Ctrl-C during the grace window escalates immediately (matches the usual "hit Ctrl-C twice to force-quit" UX). Previously the parent could exit before Gecko's `AsyncShutdown` / `profileBeforeChange` blockers finished flushing in-memory state, losing the last few seconds of edits. The grace window is configurable via a new `shutdownGraceMs` option on `execInherit` / `execInheritCapture`.
26
28
  - New `fireforge test --doctor` runs a short marionette handshake preflight before (optionally) invoking `mach test`. Spawns the built browser headless, opens a TCP socket to `127.0.0.1:2828`, waits for the handshake bytes, and reports PASS/FAIL with the tail of stderr on FAIL. When `--doctor` is supplied with no test paths, it exits after the preflight — a sub-minute way to tell "marionette wedged" apart from "test failed to discover" when `mach test` hangs for the full 360 s marionette timeout. When supplied with test paths, a FAIL preflight short-circuits before `mach test` runs.
29
+ - `fireforge test --doctor` is now a cascade of six layered probes (engine-present → mach-available → python-available → profile-creatable → browser-spawns → marionette-handshake) with tight per-layer budgets. Previously the single 30 s socket poll gave the same generic "socket did not respond" diagnostic whether mach was missing, Python was unavailable, `/tmp` was not writable, or the browser binary crashed at startup — so operators had no lead on where to start debugging. Each layer now short-circuits with a `[layer N/6: <name>]`-prefixed detail message so the first broken layer is surfaced immediately, and a crashing browser is caught by a short settle window at layer 5 instead of wasting the full budget waiting for bytes that never come.
30
+
31
+ ### Furnace build
32
+
33
+ - `fireforge build` already auto-applies Furnace components (via `prepareBuildEnvironment`) before the mach build step, but the behaviour was undocumented and silent — operators who edited `components/custom/` and then ran `fireforge build` could not distinguish "auto-apply wrote files" from "nothing changed". A loud `Furnace: source → engine sync wrote N component(s) before build (...)` banner now fires whenever apply wrote files, naming every component that was synced. The `fireforge build --help` description and help footer now call out that apply runs before the build step.
34
+
35
+ ### Furnace create
36
+
37
+ - New `furnace create --xpcshell` flag scaffolds an xpcshell test harness alongside (or instead of) the browser-chrome mochitest that `--with-tests` already produces. xpcshell runs headless without a `tabbrowser`, so storage-layer / observer-driven / module-loading code on forks that do not mount the upstream browser chrome (no `openLinkIn` → `URILoadingHelper`) can be covered without the harness complaining about a missing tab strip. Writes `test_<name>_module_loads.js` and an `xpcshell.toml` manifest into `engine/browser/base/content/test/<binary-name>-xpcshell/<component-name>/`. Registration in `XPCSHELL_TESTS_MANIFESTS` is left to the operator — the moz.build that should own the entry depends on where the component lives.
38
+
39
+ ### Build audit
40
+
41
+ - `fireforge build` now runs a warn-only post-build dist-tree audit after a successful mach build. The audit diffs engine-relative paths touched since the last successful build (stored as `.fireforge/last-build.json`) against the dist bundle, and warns per file that is packageable-by-convention (`.js`/`.mjs`/`.css`/`.ftl`/`.xhtml`/`app/profile/…`) but has no matching artifact, or whose dist mtime is older than the source. Surfaces the class of bug where a new pref file or widget is edited but never registered in `moz.build` / `jar.mn` / `package-manifest.in` — the 2026-04-18 `hominis.js` pref-file incident is the motivating case. The audit is warn-only and never fails a successful build.
42
+ - Ends every build with a `Packaged: N updated, M stale, K missing, S skipped` summary so operators can distinguish a fast-because-incremental build from a fast-because-silently-skipped one without a post-build `find`.
43
+ - `fireforge build` auto-runs `mach configure` before the mach build step when any `moz.build`, `moz.configure`, or `Makefile.in` changed since the last successful build. Prevents the stale-backend trap where an incremental build skips work against a recursive-make backend that no longer matches the source tree. Emits a `Backend config changed; running mach configure first...` banner so the extra step is visible, and continues the build even if configure exits non-zero.
44
+ - Mach build failures with known-cryptic mozbuild errors now print actionable hints. First entry in the table: `mozbuild.preprocessor.Preprocessor.Error: no preprocessor directives found` prints `Use JS_PREFERENCE_FILES instead, or add at least one #filter / #expand directive to the file.` The hint table lives in `src/core/mach-error-hints.ts` and is extensible — new cryptic errors we diagnose get one-line hints added without touching the build wrapper.
45
+
46
+ ### Lint
47
+
48
+ - `fireforge lint --since <git-rev>` tags each lint issue as `[introduced]` (file touched in the diff since `<git-rev>`) or `[cumulative]` (pre-existing patch-state drift). Output gains the tag prefix and the summary line splits counts (e.g. `Lint: 2 introduced error(s), 0 introduced warning(s); 5 cumulative error(s), 1 cumulative warning(s)`). Exit code semantics are unchanged — an introduced OR cumulative error still fails lint — but triage of "is the diff I just produced clean?" no longer requires mentally subtracting pre-existing noise from every report. Without `--since` the output is unchanged.
49
+
50
+ ### Furnace chrome-doc
51
+
52
+ - New `furnace chrome-doc create <name>` subcommand scaffolds a top-level chrome document (xhtml + js + css + ftl) plus the three jar.mn registrations (`browser/base/jar.mn`, `browser/themes/shared/jar.inc.mn`, `browser/locales/jar.mn`). Default emits titlebar-buttonbox markup and a `windowtype="navigator:browser"` shell; `--no-titlebar` produces a frameless overlay with the macOS `.titlebar-button { display: none }` carve-out. Mirrors the workflow for custom elements (`furnace create`) so hand-authoring mistakes — the `*` preprocessor flag, the startup-topic observer, the platform titlebar inheritance — are eliminated. All writes go through a rollback journal under the signal-handler pathway: a Ctrl+C mid-scaffold restores every touched file.
53
+
54
+ ### Furnace create
55
+
56
+ - New `--test-style <mochikit|browser-chrome|xpcshell>` option for `furnace create --with-tests`. `mochikit` (the new default when `--with-tests` is set alone) scaffolds a `chrome://mochikit/...` test under `engine/toolkit/content/tests/widgets/test_<tag>.html` that loads the component module directly and smoke-asserts `customElements.whenDefined(tag)`. Runs today against forks whose top-level chrome document lacks a `tabbrowser` (the class of bug that forced `--xpcshell` for storage-layer code) because the harness doesn't traverse `URILoadingHelper.openLinkIn`. `browser-chrome` is the former default and requires a working tabbrowser; `xpcshell` is equivalent to `--xpcshell`. Backwards-compat: `--xpcshell` alone still works; conflicting flag combinations are rejected up-front.
57
+
58
+ ### Run / signals
59
+
60
+ - `fireforge run` (and every other command that never registers a furnace mutation) no longer prints the alarming `Received SIGTERM; rolling back in-flight furnace mutations…` line when the CLI receives SIGINT or SIGTERM. The global signal handler now gates the rollback banner on the presence of at least one live mutation in the registry — plain launches exit silently with code 130/143 as they always did. A new regression test asserts `warn` is not called when no operations are registered.
27
61
 
28
62
  ### Internal
29
63
 
30
64
  - Extracted `furnace-apply-ftl.ts`, `furnace-config-tokens.ts`, and `create-templates.ts` to keep apply / config / scaffolding files under the per-file LOC budget after the new features landed. `parseStringArray` is now exported from `furnace-config.ts` for cross-module reuse.
31
65
  - New `src/core/marionette-preflight.ts` owns the `--doctor` probe and its teardown semantics.
32
66
  - Test mocks for `furnace-registration.js` now cover the new `addLocaleFtlJarMnEntry` / `removeLocaleFtlJarMnEntry` exports; `config.js` mocks in apply-batch tests now cover `loadConfig` because the apply path reads `markerComment` from fireforge.json.
67
+ - Repo-wide scrub of fork-example mentions (`hominis.xhtml`, `HOMINIS` marker-comment examples, fixture tag names) in favour of a generic `mybrowser` / `MYBROWSER` placeholder. FireForge reads as fork-agnostic in docs and fixtures; the npm identity (`@hominis/fireforge`) is unchanged. Closes a v0.15.0 slip-through (one `@hominis/fireforge` reference remained in `src/core/furnace-operation.ts` as a generic example alongside the npm-identity occurrences; the code example is now fork-neutral).
68
+ - New modules landed under coverage-gate protection: `src/core/mach-error-hints.ts`, `src/core/build-audit.ts`, `src/core/build-baseline.ts`, `src/core/patch-lint-diff-tag.ts`, `src/commands/furnace/chrome-doc.ts`, `src/commands/furnace/chrome-doc-templates.ts`, `src/commands/furnace/create-mochikit.ts`. Per-module thresholds added to `scripts/check-coverage-thresholds.mjs`.
33
69
 
34
70
  ## 0.14.0
35
71
 
@@ -249,7 +285,7 @@
249
285
  ### General improvements
250
286
 
251
287
  - getPackageRoot up to this point expected hardcoded `@hominis/fireforge`, was changed to just the package name for potential forks and more flexibility when changing project name.
252
- - Some test generators were derived from early Hominis Browser fork additions, the references to Hominis have been replaced with generic naming.
288
+ - Some test generators were derived from an early downstream fork; the fork-specific names have been replaced with generic naming so the templates apply to any Firefox fork.
253
289
 
254
290
  ### Build and Git reliability
255
291
 
package/README.md CHANGED
@@ -293,12 +293,46 @@ There are three component types:
293
293
  fireforge furnace scan # discover components in the engine
294
294
  fireforge furnace override moz-button -t css-only # fork with CSS-only restyle
295
295
  fireforge furnace create moz-my-widget # scaffold a new component
296
+ fireforge furnace chrome-doc create mybrowser # scaffold a top-level chrome document
296
297
  fireforge furnace deploy # apply to engine/ + validate
297
298
  fireforge furnace status # workspace vs engine drift
298
299
  fireforge furnace diff moz-button # unified diff against baseline
299
300
  ```
300
301
 
301
- `furnace deploy` validates components before applying. As always, errors block, warnings are advisory. `fireforge build` and `fireforge test --build` run apply automatically. Use `fireforge doctor --repair-furnace` if the engine gets out of sync.
302
+ `furnace deploy` validates components before applying. As always, errors block, warnings are advisory. `fireforge build` and `fireforge test --build` run apply automatically — when apply wrote files during a build, the build prints a `Furnace: source → engine sync wrote N component(s) …` banner naming every component that was synced, so it is obvious whether engine/ was freshly updated. Use `fireforge doctor --repair-furnace` if the engine gets out of sync.
303
+
304
+ ### Scaffolding top-level chrome documents
305
+
306
+ Custom elements live under `toolkit/content/widgets`, but a fork's top-level chrome document (`browser.xhtml` equivalents like `mybrowser.xhtml`, `about:*` panels, onboarding flows) lives at `browser/base/content/` and needs jar.mn, jar.inc.mn, and locales/jar.mn entries to reach the packaged bundle. `furnace chrome-doc create <name>` handles that boilerplate:
307
+
308
+ ```bash
309
+ fireforge furnace chrome-doc create mybrowser # full chrome (titlebar + windowtype)
310
+ fireforge furnace chrome-doc create overlay --no-titlebar # frameless overlay
311
+ ```
312
+
313
+ The command writes:
314
+
315
+ - `engine/browser/base/content/<name>.xhtml` — XHTML shell, optional titlebar-buttonbox, Fluent `<link>`.
316
+ - `engine/browser/base/content/<name>.js` — startup-topic observer fired on first idle.
317
+ - `engine/browser/themes/shared/<name>-chrome.css` — scoped CSS; emits the macOS `.titlebar-button { display: none }` carve-out under `--no-titlebar`.
318
+ - `engine/browser/locales/en-US/browser/<name>.ftl` — Fluent stub keyed on `<name>-window-title`.
319
+ - Appends the corresponding `jar.mn` / `jar.inc.mn` / `locales/jar.mn` entries.
320
+
321
+ Writes are transactional: a SIGINT mid-scaffold rolls back every touched file. Requires an existing engine — run `fireforge download` first.
322
+
323
+ ### Picking a test harness for `furnace create`
324
+
325
+ `furnace create --with-tests` defaults to a MochiKit test at `engine/toolkit/content/tests/widgets/test_<tag>.html`. MochiKit tests load the component module via `chrome://global/` and don't need a `tabbrowser`, so they run against any fork — including bespoke chrome documents (`mybrowser.xhtml`-class) that deliberately omit the upstream browser chrome.
326
+
327
+ Three styles are available via `--test-style`:
328
+
329
+ | Style | When to use |
330
+ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
331
+ | `mochikit` | Default. Pure-UI custom elements. Runs today against non-tabbrowser chrome. Emits `test_<tag>.html` under `toolkit/content/tests/widgets/`. |
332
+ | `browser-chrome` | Element talks to the browser window (open URLs, manipulate tabs). Requires a working `tabbrowser` in the chrome document. Emits the `browser_<bin>_<tag>.js` scaffold and registers it in `browser/base/moz.build`. |
333
+ | `xpcshell` | Storage-layer, observer-driven, or ESM-loading code. Headless, no tabbrowser. Emits `test_<name>_module_loads.js` + `xpcshell.toml` (registration in `XPCSHELL_TESTS_MANIFESTS` is left to the operator). |
334
+
335
+ `--xpcshell` is preserved as an alias for `--test-style=xpcshell`; conflicting flag combinations (`--xpcshell --test-style=mochikit`) are rejected.
302
336
 
303
337
  ## Additional Commands
304
338
 
@@ -348,6 +382,26 @@ fireforge watch
348
382
  fireforge token --name "--my-color" --value "light-dark(#fff, #000)"
349
383
  ```
350
384
 
385
+ ### Diff-scoped lint (`lint --since`)
386
+
387
+ `fireforge lint --since <git-rev>` tags each issue as `[introduced]` or `[cumulative]` based on whether its file changed since `<git-rev>`:
388
+
389
+ ```bash
390
+ fireforge lint --since HEAD~1 # just the current commit
391
+ fireforge lint --since main # everything since main
392
+ fireforge lint --since abc1234 # since a specific SHA
393
+ ```
394
+
395
+ The summary line splits counts — e.g. `Lint: 2 introduced error(s), 0 introduced warning(s); 5 cumulative error(s), 1 cumulative warning(s)` — so triage of "did my diff introduce any of these?" is a one-glance check on a large patch series. Exit code still fails on any error (introduced or cumulative); the flag is purely a display / audit tool. Without `--since`, output is unchanged.
396
+
397
+ ### Post-build audit and auto-configure
398
+
399
+ `fireforge build` is a transactional step: after a successful mach build it audits the dist bundle against engine-relative paths touched since the last successful build, and warns per file that is packageable-by-convention (`.js`/`.mjs`/`.css`/`.ftl`/`.xhtml`/`app/profile/…`) but has no matching artifact or whose dist mtime is older than the source. Ends every build with a `Packaged: N updated, M stale, K missing, S skipped` summary. The audit is warn-only — it never fails a build that mach reported green.
400
+
401
+ The build also auto-runs `mach configure` before the mach build step when any `moz.build`, `moz.configure`, or `Makefile.in` changed since the last successful build. Prevents incremental builds from silently skipping work against a stale recursive-make backend. Emits a `Backend config changed; running mach configure first...` banner when it fires.
402
+
403
+ Mach build failures with known-cryptic mozbuild errors now print actionable hints. Example: a `JS_PREFERENCE_PP_FILES` entry with no `#filter` / `#expand` directives now prints `Hint: ...use JS_PREFERENCE_FILES instead, or add at least one #filter / #expand directive to the file.` alongside the raw mach traceback.
404
+
351
405
  ## Configuration
352
406
 
353
407
  `fireforge.json` at your project root:
@@ -367,7 +421,7 @@ fireforge token --name "--my-color" --value "light-dark(#fff, #000)"
367
421
  "wire": { "subscriptDir": "browser/components/mybrowser" },
368
422
  "patchLint": {
369
423
  "checkJs": true,
370
- "rawColorAllowlist": ["hominis-tokens.css"]
424
+ "rawColorAllowlist": ["mybrowser-tokens.css"]
371
425
  },
372
426
  "markerComment": "MYBROWSER"
373
427
  }
@@ -375,7 +429,7 @@ fireforge token --name "--my-color" --value "light-dark(#fff, #000)"
375
429
 
376
430
  **`markerComment`** (optional). Appended as a ` // <marker>:` suffix to every line FireForge writes into upstream Firefox source files (starting with `customElements.js`). Keeps fork modifications discoverable and makes re-apply idempotent without hand-tagging entries after each `furnace apply`. Reject list: empty strings, leading/trailing whitespace, newlines, `*/` (would close an enclosing block comment), control characters.
377
431
 
378
- **`furnace.json.tokenHostDocuments`** (optional). List of chrome XHTML documents the `missing-token-link` validator scans for the tokens CSS link. Forks with a second chrome host (e.g. `hominis.xhtml` alongside `browser.xhtml`) should list every document that may own the link — the rule fires only when NONE of them link the tokens CSS. Defaults to `["browser/base/content/browser.xhtml"]` when omitted.
432
+ **`furnace.json.tokenHostDocuments`** (optional). List of chrome XHTML documents the `missing-token-link` validator scans for the tokens CSS link. Forks with a second chrome host (e.g. `mybrowser.xhtml` alongside `browser.xhtml`) should list every document that may own the link — the rule fires only when NONE of them link the tokens CSS. Defaults to `["browser/base/content/browser.xhtml"]` when omitted.
379
433
 
380
434
  ### `furnace create --localized` for `MozLitElement`
381
435
 
@@ -391,6 +445,25 @@ fireforge test --doctor browser/base/content/test/foo/browser_bar.js
391
445
 
392
446
  Spawns the built browser headless, waits for a marionette handshake on `127.0.0.1:2828`, and reports PASS/FAIL with the tail of the browser's stderr on FAIL. Distinguishes "marionette wedged" (socket silent) from "mach test discovery failed" — both otherwise surface as a silent 360-second hang followed by `Passed: 0, Failed: 0`. Useful as a prefix on routine `fireforge test` invocations when marionette has been flaky.
393
447
 
448
+ The probe is a cascade of six layered checks — engine-present → mach-available → python-available → profile-creatable → browser-spawns → marionette-handshake. Each failure is tagged `[layer N/6: <name>]` so the first broken layer is surfaced immediately instead of the whole cascade blocking on the final socket poll. When the browser binary crashes at startup (missing dylib, wrong CPU arch, corrupt profile) the cascade fails at layer 5 within the settle window, not after the full socket timeout.
449
+
450
+ ### Runtime CSS variables in Furnace
451
+
452
+ Design tokens imported from the fork's palette are enforced by `tokenPrefix`, but some components write and read CSS custom properties as runtime state channels (`--cam-x` per frame, `--tile-z` from a hit-test observer). Two escape hatches exist:
453
+
454
+ - **Auto-exempt** — a variable that is both declared (`--foo: 0;`) and consumed (`var(--foo)`) inside the same component's CSS file is recognised as a component-local runtime channel. No config entry required.
455
+ - **`furnace.json.runtimeVariables`** — explicit allowlist for names that are _written_ in JS and _read_ in a different file's CSS (cross-component runtime channels that the CSS-only auto-exempt cannot see). Entries must start with `--`.
456
+
457
+ Both rules compose with the existing `tokenPrefix` / `tokenAllowlist` checks and apply to both component validation and patch-stack lint.
458
+
459
+ ### Test harness options
460
+
461
+ `fireforge furnace create --with-tests` scaffolds a **browser-chrome mochitest**. Use this when the component renders UI that depends on the tab strip (`openLinkIn` → `URILoadingHelper`, `gBrowser`, etc.).
462
+
463
+ `fireforge furnace create --xpcshell` scaffolds an **xpcshell test harness** instead. Use this when the component's code path is storage-only, observer-driven, or module-loading logic that does not touch a `tabbrowser`. xpcshell runs headless without browser chrome, so forks without an upstream tab strip can still cover these paths. The scaffolder writes `test_<name>_module_loads.js` + `xpcshell.toml` into `engine/browser/base/content/test/<binary-name>-xpcshell/<component-name>/` and prints a note: registration in `XPCSHELL_TESTS_MANIFESTS` is the operator's call (the moz.build that should own the entry depends on where the component actually lives).
464
+
465
+ The two flags can be combined — `--with-tests --xpcshell` writes both harnesses.
466
+
394
467
  ## Roadmap
395
468
 
396
469
  Planned but not yet implemented:
@@ -1,11 +1,14 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { InvalidArgumentError as CommanderInvalidArgumentError } from 'commander';
3
3
  import { validateBrandOverride } from '../core/brand-validation.js';
4
+ import { auditBuildArtifacts } from '../core/build-audit.js';
5
+ import { readBuildBaseline, writeBuildBaseline } from '../core/build-baseline.js';
4
6
  import { prepareBuildEnvironment } from '../core/build-prepare.js';
5
7
  import { getProjectPaths, loadConfig } from '../core/config.js';
6
8
  import { build, buildArtifactMismatchMessage, buildUI, hasBuildArtifacts } from '../core/mach.js';
7
9
  import { GeneralError } from '../errors/base.js';
8
10
  import { AmbiguousBuildArtifactsError, BuildError } from '../errors/build.js';
11
+ import { toError } from '../utils/errors.js';
9
12
  import { checkDiskSpace, pathExists } from '../utils/fs.js';
10
13
  import { error, info, intro, outro, verbose, warn } from '../utils/logger.js';
11
14
  import { pickDefined } from '../utils/options.js';
@@ -60,8 +63,13 @@ export async function buildCommand(projectRoot, options) {
60
63
  // Future: Load brand-specific config from fireforge.json brands section
61
64
  info(`Brand: ${options.brand}`);
62
65
  }
63
- // Shared pre-flight: branding, Furnace, mozconfig
64
- await prepareBuildEnvironment(projectRoot, paths, config);
66
+ // Read the previous build baseline BEFORE prepareBuildEnvironment so the
67
+ // auto-configure step there can detect moz.build-family changes since the
68
+ // last successful build. The post-build audit below reuses the same
69
+ // baseline to diff engine changes against dist artifacts.
70
+ const previousBaseline = await readBuildBaseline(projectRoot);
71
+ // Shared pre-flight: branding, Furnace, mozconfig, auto-configure
72
+ await prepareBuildEnvironment(projectRoot, paths, config, { previousBaseline });
65
73
  const jobs = resolveJobCount(options, config.build?.jobs);
66
74
  // Run build
67
75
  info(`Starting ${buildType.toLowerCase()} build...`);
@@ -90,16 +98,46 @@ export async function buildCommand(projectRoot, options) {
90
98
  error(`Build failed after ${timeStr}`);
91
99
  throw new BuildError(`Build failed with exit code ${exitCode}`, options.ui ? 'mach build faster' : 'mach build');
92
100
  }
101
+ // Warn-only post-build audit: surfaces silent packaging drops (files
102
+ // edited in engine/ but never registered for packaging) against the
103
+ // previous-build baseline. Never fails the build; the worst case is a
104
+ // warning an operator chooses to investigate.
105
+ try {
106
+ await auditBuildArtifacts(projectRoot, paths.engine, previousBaseline);
107
+ }
108
+ catch (auditError) {
109
+ verbose(`Audit skipped: ${toError(auditError).message}`);
110
+ }
111
+ // Record a fresh baseline only on clean success so the next run audits
112
+ // against this build's HEAD. A failed build keeps the prior baseline so
113
+ // the next attempt still catches long-standing packaging drops.
114
+ try {
115
+ await writeBuildBaseline(projectRoot, paths.engine, config.binaryName);
116
+ }
117
+ catch (baselineError) {
118
+ verbose(`Could not persist build baseline: ${toError(baselineError).message}`);
119
+ }
93
120
  outro(`Build completed in ${timeStr}!`);
94
121
  }
95
122
  /** Registers the build command on the CLI program. */
96
123
  export function registerBuild(program, { getProjectRoot, withErrorHandling }) {
97
124
  program
98
125
  .command('build')
99
- .description('Build the browser')
126
+ .description('Build the browser (auto-applies Furnace components first)')
100
127
  .option('--ui', 'Fast UI-only rebuild')
101
128
  .option('-j, --jobs <n>', 'Number of parallel jobs', parseJobCount)
102
129
  .option('--brand <name>', 'Build specific brand')
130
+ .addHelpText('after', [
131
+ '',
132
+ 'Furnace apply runs automatically before the build step, so edits in',
133
+ 'components/custom/ and components/overrides/ are propagated to the',
134
+ 'engine/ tree every time. The command prints a banner listing the',
135
+ 'components synced during the current invocation.',
136
+ '',
137
+ 'If you want to preview the engine state without triggering a build,',
138
+ 'run `fireforge furnace apply` directly. For source-change-driven',
139
+ 'rebuild loops during development, use `fireforge watch`.',
140
+ ].join('\n'))
103
141
  .action(withErrorHandling(async (options) => {
104
142
  await buildCommand(getProjectRoot(), pickDefined(options));
105
143
  }));
@@ -0,0 +1,49 @@
1
+ /**
2
+ * File-content templates for `fireforge furnace chrome-doc create`.
3
+ * Extracted so the command entrypoint stays under the per-file LOC budget
4
+ * and each template can be exercised in isolation.
5
+ *
6
+ * The templates here mirror the shape of top-level chrome documents in
7
+ * upstream Firefox (browser.xhtml, privatebrowsing/aboutPrivateBrowsing.html,
8
+ * etc.) minus the fork-specific wiring. A fork author fills in the body.
9
+ */
10
+ /**
11
+ * XHTML shell for a top-level chrome document.
12
+ *
13
+ * The emitted document:
14
+ * - Declares `windowtype="navigator:browser"` when `withTitlebar` is true
15
+ * so chrome-wide stylesheets that target the browser window still apply.
16
+ * - Emits a titlebar-buttonbox placeholder when `withTitlebar` is true so
17
+ * platform-native window controls render.
18
+ * - Links the per-document CSS at `chrome://browser/content/<name>-chrome.css`
19
+ * and the Fluent bundle `browser/<name>.ftl`.
20
+ */
21
+ export declare function generateChromeDocXhtml(name: string, withTitlebar: boolean, license: string): string;
22
+ /**
23
+ * ESM bootstrap script for the chrome document.
24
+ *
25
+ * The generated script fires a startup observer topic on the first idle
26
+ * callback so other chrome code can wait on the document being ready.
27
+ * Mirrors the pattern FireForge-built forks use to coordinate per-window
28
+ * init across multiple top-level documents.
29
+ */
30
+ export declare function generateChromeDocJs(name: string, licenseHeader: string): string;
31
+ /**
32
+ * Scoped CSS for a chrome document. When `withTitlebar` is false the
33
+ * macOS `.titlebar-button { display: none }` carve-out is emitted so
34
+ * frameless overlay-style documents don't inherit the platform window
35
+ * controls that `global.css` applies by default.
36
+ */
37
+ export declare function generateChromeDocCss(name: string, withTitlebar: boolean, licenseHeader: string): string;
38
+ /** Fluent stub — one placeholder message keyed to the window title. */
39
+ export declare function generateChromeDocFtl(name: string, licenseHeader: string): string;
40
+ /**
41
+ * Single-line jar.mn entry that registers an xhtml + js pair under
42
+ * `content/browser/`. Emits the `*` preprocessor flag so both files flow
43
+ * through `#filter substitution` for FireForge brand-name substitution.
44
+ */
45
+ export declare function jarMnEntriesForChromeDoc(name: string): string[];
46
+ /** jar.inc.mn entry that registers the scoped CSS under `content/browser/`. */
47
+ export declare function jarIncMnEntryForChromeDoc(name: string): string;
48
+ /** locales/jar.mn entry that registers the `.ftl` under the browser locale bundle. */
49
+ export declare function localeJarMnEntryForChromeDoc(name: string): string;
@@ -0,0 +1,151 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * File-content templates for `fireforge furnace chrome-doc create`.
4
+ * Extracted so the command entrypoint stays under the per-file LOC budget
5
+ * and each template can be exercised in isolation.
6
+ *
7
+ * The templates here mirror the shape of top-level chrome documents in
8
+ * upstream Firefox (browser.xhtml, privatebrowsing/aboutPrivateBrowsing.html,
9
+ * etc.) minus the fork-specific wiring. A fork author fills in the body.
10
+ */
11
+ /**
12
+ * XHTML shell for a top-level chrome document.
13
+ *
14
+ * The emitted document:
15
+ * - Declares `windowtype="navigator:browser"` when `withTitlebar` is true
16
+ * so chrome-wide stylesheets that target the browser window still apply.
17
+ * - Emits a titlebar-buttonbox placeholder when `withTitlebar` is true so
18
+ * platform-native window controls render.
19
+ * - Links the per-document CSS at `chrome://browser/content/<name>-chrome.css`
20
+ * and the Fluent bundle `browser/<name>.ftl`.
21
+ */
22
+ export function generateChromeDocXhtml(name, withTitlebar, license) {
23
+ const windowAttr = withTitlebar ? ' windowtype="navigator:browser"' : '';
24
+ const titlebarMarkup = withTitlebar
25
+ ? `
26
+ <hbox class="titlebar-buttonbox-container">
27
+ <hbox class="titlebar-buttonbox"></hbox>
28
+ </hbox>`
29
+ : '';
30
+ return `<?xml version="1.0"?>
31
+ <!-- SPDX-License-Identifier: ${license} -->
32
+ <!DOCTYPE window>
33
+ <window
34
+ xmlns="http://www.w3.org/1999/xhtml"
35
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
36
+ id="${name}-window"${windowAttr}
37
+ data-l10n-id="${name}-window-title"
38
+ role="application">
39
+ <head>
40
+ <meta charset="utf-8" />
41
+ <title data-l10n-id="${name}-window-title"></title>
42
+ <link rel="localization" href="browser/${name}.ftl" />
43
+ <link rel="stylesheet" href="chrome://global/skin/global.css" />
44
+ <link rel="stylesheet" href="chrome://browser/content/${name}-chrome.css" />
45
+ <script src="chrome://browser/content/${name}.js"></script>
46
+ </head>
47
+ <body>${titlebarMarkup}
48
+ <main id="${name}-main"></main>
49
+ </body>
50
+ </window>
51
+ `;
52
+ }
53
+ /**
54
+ * ESM bootstrap script for the chrome document.
55
+ *
56
+ * The generated script fires a startup observer topic on the first idle
57
+ * callback so other chrome code can wait on the document being ready.
58
+ * Mirrors the pattern FireForge-built forks use to coordinate per-window
59
+ * init across multiple top-level documents.
60
+ */
61
+ export function generateChromeDocJs(name, licenseHeader) {
62
+ const topic = `${name}-startup`;
63
+ return `${licenseHeader}
64
+
65
+ "use strict";
66
+
67
+ // Fire a startup topic on first idle so subscribers can defer their own
68
+ // init until this document is ready. Observers see the document element as
69
+ // the subject; use \`aSubject instanceof Ci.nsIDOMWindow\` if you need the
70
+ // containing window instead.
71
+ window.addEventListener(
72
+ "DOMContentLoaded",
73
+ () => {
74
+ window.requestIdleCallback?.(() => {
75
+ try {
76
+ Services.obs.notifyObservers(document.documentElement, "${topic}");
77
+ } catch (error) {
78
+ // Observer notifications should never block document init — log
79
+ // but don't throw so a missing observer service (headless / test
80
+ // harness) still leaves the document usable.
81
+ console.error("Failed to fire ${topic} observer:", error);
82
+ }
83
+ });
84
+ },
85
+ { once: true }
86
+ );
87
+ `;
88
+ }
89
+ /**
90
+ * Scoped CSS for a chrome document. When `withTitlebar` is false the
91
+ * macOS `.titlebar-button { display: none }` carve-out is emitted so
92
+ * frameless overlay-style documents don't inherit the platform window
93
+ * controls that `global.css` applies by default.
94
+ */
95
+ export function generateChromeDocCss(name, withTitlebar, licenseHeader) {
96
+ const titlebarOverrides = withTitlebar
97
+ ? ''
98
+ : `
99
+
100
+ /* Frameless overlay — suppress the platform titlebar buttons that
101
+ global.css inherits on macOS. Without this carve-out the traffic-light
102
+ controls render even though the document has no titlebar-buttonbox. */
103
+ :root[windowtype="navigator:browser"] .titlebar-button {
104
+ display: none;
105
+ }
106
+ `;
107
+ return `${licenseHeader}
108
+
109
+ :root {
110
+ --${name}-padding: 16px;
111
+ }
112
+
113
+ #${name}-window {
114
+ display: flex;
115
+ flex-direction: column;
116
+ min-height: 100vh;
117
+ }
118
+
119
+ #${name}-main {
120
+ flex: 1;
121
+ padding: var(--${name}-padding);
122
+ }${titlebarOverrides}
123
+ `;
124
+ }
125
+ /** Fluent stub — one placeholder message keyed to the window title. */
126
+ export function generateChromeDocFtl(name, licenseHeader) {
127
+ return `${licenseHeader}
128
+
129
+ ${name}-window-title = ${name}
130
+ `;
131
+ }
132
+ /**
133
+ * Single-line jar.mn entry that registers an xhtml + js pair under
134
+ * `content/browser/`. Emits the `*` preprocessor flag so both files flow
135
+ * through `#filter substitution` for FireForge brand-name substitution.
136
+ */
137
+ export function jarMnEntriesForChromeDoc(name) {
138
+ return [
139
+ `* content/browser/${name}.xhtml (content/${name}.xhtml)`,
140
+ ` content/browser/${name}.js (content/${name}.js)`,
141
+ ];
142
+ }
143
+ /** jar.inc.mn entry that registers the scoped CSS under `content/browser/`. */
144
+ export function jarIncMnEntryForChromeDoc(name) {
145
+ return ` content/browser/${name}-chrome.css (shared/${name}-chrome.css)`;
146
+ }
147
+ /** locales/jar.mn entry that registers the `.ftl` under the browser locale bundle. */
148
+ export function localeJarMnEntryForChromeDoc(name) {
149
+ return ` locale/browser/${name}.ftl (%${name}.ftl)`;
150
+ }
151
+ //# sourceMappingURL=chrome-doc-templates.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `fireforge furnace chrome-doc create <name>` — scaffolds a top-level
3
+ * chrome document (xhtml + js + css + ftl + jar.mn registrations).
4
+ *
5
+ * Motivation: `furnace create` covers custom elements under
6
+ * `toolkit/content/widgets/`, but top-level chrome documents (the
7
+ * `mybrowser.xhtml`-class entry points a fork adds alongside or instead
8
+ * of `browser.xhtml`) are today hand-authored with error-prone jar.mn +
9
+ * jar.inc.mn + locales/jar.mn glue. The `*` preprocessor flag, the
10
+ * macOS titlebar-button carve-out, the startup-topic observer, and the
11
+ * Fluent linkage each have silent-break failure modes.
12
+ *
13
+ * This command writes the four source files and appends three jar.mn
14
+ * entries under a rollback journal identical in shape to `furnace create`.
15
+ * A SIGINT mid-scaffold restores every touched file; a successful run
16
+ * leaves the tree ready for `fireforge build`.
17
+ */
18
+ /** Options for `furnace chrome-doc create`. */
19
+ export interface FurnaceChromeDocCreateOptions {
20
+ /**
21
+ * Emit the titlebar-buttonbox markup and leave platform window controls
22
+ * visible. Defaults to `true` because the common case is a full chrome
23
+ * document (not a frameless overlay). Frameless callers pass
24
+ * `--no-titlebar`.
25
+ */
26
+ titlebar?: boolean;
27
+ }
28
+ /**
29
+ * Runs `furnace chrome-doc create <name>`.
30
+ * @param projectRoot Root directory of the project.
31
+ * @param name Chrome-doc name (e.g. `mybrowser`, `aboutonboarding`).
32
+ * @param options CLI-provided options.
33
+ */
34
+ export declare function furnaceChromeDocCreateCommand(projectRoot: string, name: string, options?: FurnaceChromeDocCreateOptions): Promise<void>;