@hominis/fireforge 0.15.6 → 0.15.8
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 +78 -0
- package/README.md +158 -15
- 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 +38 -0
- package/dist/src/commands/furnace/create-dry-run.js +100 -0
- package/dist/src/commands/furnace/create-features.d.ts +24 -0
- package/dist/src/commands/furnace/create-features.js +56 -0
- package/dist/src/commands/furnace/create-templates.d.ts +9 -5
- package/dist/src/commands/furnace/create-templates.js +28 -6
- package/dist/src/commands/furnace/create.js +62 -63
- package/dist/src/commands/furnace/index.js +4 -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/run.d.ts +15 -1
- package/dist/src/commands/run.js +202 -7
- package/dist/src/commands/test.js +113 -3
- 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 +107 -7
- package/dist/src/core/furnace-apply-ftl.d.ts +5 -3
- package/dist/src/core/furnace-apply-ftl.js +6 -2
- package/dist/src/core/furnace-apply-helpers.js +14 -4
- package/dist/src/core/furnace-config-custom.d.ts +14 -0
- package/dist/src/core/furnace-config-custom.js +64 -0
- package/dist/src/core/furnace-config.js +2 -39
- package/dist/src/core/furnace-validate-accessibility.d.ts +9 -2
- package/dist/src/core/furnace-validate-accessibility.js +17 -3
- package/dist/src/core/furnace-validate-helpers.d.ts +13 -1
- package/dist/src/core/furnace-validate-helpers.js +19 -0
- 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/furnace-validate-structure.js +6 -2
- package/dist/src/core/furnace-validate.js +6 -3
- 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 +27 -1
- package/dist/src/core/mach.js +26 -2
- package/dist/src/core/shared-ftl.d.ts +28 -0
- package/dist/src/core/shared-ftl.js +42 -0
- package/dist/src/core/smoke-patterns.d.ts +45 -0
- package/dist/src/core/smoke-patterns.js +100 -0
- package/dist/src/core/test-stale-check.d.ts +42 -0
- package/dist/src/core/test-stale-check.js +114 -0
- package/dist/src/core/xpcshell-appdir.d.ts +143 -0
- package/dist/src/core/xpcshell-appdir.js +273 -0
- package/dist/src/errors/codes.d.ts +13 -0
- package/dist/src/errors/codes.js +13 -0
- package/dist/src/errors/run.d.ts +16 -0
- package/dist/src/errors/run.js +22 -0
- package/dist/src/types/commands/options.d.ts +64 -0
- package/dist/src/types/furnace.d.ts +39 -0
- package/dist/src/utils/process.d.ts +63 -0
- package/dist/src/utils/process.js +122 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,84 @@
|
|
|
2
2
|
|
|
3
3
|
## 0.15.0
|
|
4
4
|
|
|
5
|
+
### Test — xpcshell appdir auto-injection
|
|
6
|
+
|
|
7
|
+
- `fireforge test` now auto-injects `--app-path=<absolute>` into mach test invocations whose nearest `xpcshell.toml` sets `firefox-appdir = "browser"` on a rebranded fork (appname != `firefox`). Without this, every `resource:///modules/<name>.sys.mjs` import inside the harness throws because the upstream xpcshell harness reads the appdir override under the appname-keyed manifest field (`<appname>-appdir`) — the literal `firefox-appdir = "browser"` directive is silently ignored when `appname` is anything other than `firefox`, so `appPath` falls back to `xrePath` (one level above the real app root). The resolver lives in the new `src/core/xpcshell-appdir.ts`: it walks each test path to the nearest `xpcshell.toml`, reads `mozinfo.json` for the active appname, prefers any `<appname>-appdir` already in the manifest (so an operator who already migrated is not overridden), and otherwise probes `<objDir>/dist/bin/<value>` and `<objDir>/dist/<bundle>.app/Contents/Resources/<value>` for the absolute target. Operator overrides via `--mach-arg=--app-path=…` always win and the resolver is skipped silently when one is detected. Mismatches across multiple test paths (different manifests resolving to different app dirs) and unresolvable manifest values (no candidate under `dist/`) are surfaced as warnings rather than guessed at, so triage can reach the underlying cause instead of debugging a wrong path.
|
|
8
|
+
- The `xpcshell appdir` diagnostic that fires when the injection did not prevent the symptom now branches its "Likely triggers" copy on whether the auto-injection ran. When it ran and the failure persists, the message says so and points at packing or layout mismatches; when it did not run (no manifest found, or appname=`firefox`), the original appname-key explanation is preserved. Adds a fourth troubleshooting option — adding `<appname>-appdir = "browser"` alongside `firefox-appdir = "browser"` in the manifest — so operators can adopt the durable fix instead of relying on injection on every run.
|
|
9
|
+
- `fireforge test` was previously diagnostic-only for this class of failure: it detected the symptom and surfaced a hint suggesting `--mach-arg` overrides. The hint remains as a fallback for cases the resolver cannot fix, but no manual flag is needed for the common case.
|
|
10
|
+
|
|
11
|
+
### Run — `--smoke-exit` for unattended chrome smoke checks
|
|
12
|
+
|
|
13
|
+
- New `fireforge run --smoke-exit <seconds>` launches the real built browser with the dev profile, streams the merged console line-by-line, sends `SIGTERM` to the entire child process group at the deadline, and exits non-zero when any `JavaScript error:` / `console.error:` / `[JavaScript Error]` / `###!!! [Parent]` line surfaces inside the smoke window without matching the operator-supplied allowlist. Closes the headless-vs-real-chrome gap that previously forced agents to choose between `fireforge run` (no exit hook, hangs on a human) and `fireforge run --headless` (does not load `browser.xhtml`/`<custom>.xhtml`, so chrome-window constructor errors stay invisible). The motivating case was `TypeError: lazy.HominisEvents.HOMINIS_TOPICS is undefined` thrown from a chrome-document constructor on every window-open, which `--headless` never observed because the main document was never instantiated.
|
|
14
|
+
- Allowlist surface lives in the new `src/core/smoke-patterns.ts`: regex error patterns (`SMOKE_ERROR_PATTERNS`) anchored on the start of each line so embedded mentions of the same prefix inside descriptive prose do not trip the scanner, plus `compileAllowlistFromFile` and `compileAllowlistFromStrings` for `--console-allow-file <path>` and repeatable `--console-allow <regex>` inputs respectively. Allowlisted hits still count toward the summary so operators can see how many lines were suppressed; only unallowed errors drive the exit code. A blank or `#`-prefixed line in an allowlist file is skipped, and a malformed regex throws fast at CLI parse time rather than silently matching nothing.
|
|
15
|
+
- New `--capture-console <file>` mirrors the captured stream to a file path so post-exit inspection has the raw log without re-running. Mirroring is inline with line dispatch and the file is fsync'd on close.
|
|
16
|
+
- Smoke-run exit codes are wired distinctly from `BUILD_ERROR`: `SMOKE_EXIT_FAILURE = 12` (unallowed console errors observed inside the window), `SMOKE_LAUNCH_FAILURE = 13` (browser exited non-clean before the window elapsed — crashes before console wiring, missing profile, etc.). CI pipelines can route these distinctly from compile/config failures and from the existing FireForge exit codes. Carried by the new `SmokeRunError` in `src/errors/run.ts` so the throw site signals both the message and the exit code in one type. The `runMachSmoke()` wrapper in `src/core/mach.ts` handles the deadline-driven SIGTERM/SIGKILL escalation (default 10 s grace between SIGTERM and SIGKILL because Firefox's `AsyncShutdown` and `profileBeforeChange` blockers can take ~5–10 s to flush in-memory state; a shorter grace risks corrupting the dev profile mid-quit). POSIX-only — process-group semantics do not map cleanly onto Windows, so the flag is rejected up-front there. A smoke window shorter than 30 s also surfaces a one-line warning recommending a longer interval, since cold-start time alone can consume that budget on a debug build.
|
|
17
|
+
|
|
18
|
+
### Furnace create — `--shared-ftl` for feature-scoped Fluent bundles
|
|
19
|
+
|
|
20
|
+
- `furnace create <tag> --localized --shared-ftl <chrome-uri>` now scaffolds a component that participates in a pre-existing feature-scoped `.ftl` bundle (e.g. `browser/hominis-dock.ftl`) instead of getting its own per-component stub. Without the flag, every `--localized` create produced a `<tag>.ftl` under `components/custom/<tag>/` plus an `insertFTLIfNeeded("toolkit/global/<tag>.ftl")` call in the `.mjs`; on a feature like a dock with eight components sharing one bundle, this stranded seven empty per-component stubs in the workspace and required hand-editing every `.mjs` to point `insertFTLIfNeeded` at the shared file (and accepting that the seven empty stubs would still get packaged into `Resources/localization/en-US/toolkit/global/`). The flag short-circuits all of that: the `.mjs` is generated with the shared path baked in via `generateMjsContent`, the per-component `.ftl` is not written, and the `furnace.json` `custom` entry carries a new `sharedFtl` field so subsequent runs (and validators) know the component participates in a shared bundle.
|
|
21
|
+
- `--shared-ftl` implies `--localized`; combining `--no-localized` with `--shared-ftl` is rejected fast-fail with an actionable message rather than letting the cross-field check inside the config parser surface the same error later. The interactive features prompt is suppressed when `--shared-ftl` is set so the operator is not asked to flip a flag we are about to enforce.
|
|
22
|
+
- `furnace apply` and `furnace remove` honour `sharedFtl` symmetrically: apply does NOT copy a per-component `.ftl` into the FTL tree nor add a locale `jar.mn` entry (the shared file is registered by whoever owns the feature bundle), and remove early-returns from `removeCustomFtlJarMnEntry` so dropping our component's reference does not orphan every other participant in the shared bundle. The dry-run path mirrors apply, so a `--dry-run` plan accurately previews the actual file set.
|
|
23
|
+
- `furnace validate`'s `missing-ftl` structural check is skipped for `sharedFtl` components — there is no per-component `<tag>.ftl` to require. The remaining error message also points at `sharedFtl` as the third option ("Create the file, set localized: false in furnace.json, or switch to sharedFtl") so an operator who hits the warning sees the structured way out alongside the existing two.
|
|
24
|
+
- The `sharedFtl` value is validated in one place (`src/core/shared-ftl.ts`) by both the CLI flag path and the `furnace.json` parser, so the two entry points cannot drift. Backticks, backslashes, and `${` are rejected because the value is interpolated verbatim into the generated `.mjs` template literal — without that check, an unsafe input would either close the template or open an executable expression. `--no-localized + --shared-ftl` and `--shared-ftl=""` (empty after trim) are rejected with structured reasons. The custom-component schema parser was extracted from `furnace-config.ts` into `src/core/furnace-config-custom.ts` to keep the main config module under the per-file LOC budget as the schema continues to gain opt-in fields (`composes`, `keyboardCovered`, `sharedFtl`).
|
|
25
|
+
|
|
26
|
+
### Furnace validate — `no-keyboard-handler` composition awareness
|
|
27
|
+
|
|
28
|
+
- `no-keyboard-handler` no longer warns when `@click` sits on a custom-element host whose `composes` entry names a native-interactive child (e.g. `moz-button`, `moz-toggle`, `moz-checkbox`, `moz-radio`, `moz-menulist`). The wrapper's `@click` handler catches keyboard activation transitively — `<moz-button>` itself dispatches `click` on Enter/Space via the platform — so a duplicate `@keydown`/`@keypress` handler on the wrapper would either no-op or double-fire alongside the child's built-in keyboard path. Previously a `hominis-pin-section` rendering a row of `<hominis-dock-button @click=…>` instances flagged `[no-keyboard-handler] Interactive element has @click but no keyboard event handler` on every validate, even though the entire row was fully keyboard-accessible.
|
|
29
|
+
- New optional `keyboardCovered: true` field on a custom component's `furnace.json` entry forces the same skip when the wrapped inner element is hand-authored (e.g. a vendored `<button>`) or is a non-stock `moz-*` widget that does not appear in `composes`. Operator-asserted: setting this to `true` does not re-check the component, so it can be used to silence a genuine finding. The check's docstring calls out the preferred path of adding the wrapped tag to `composes` when that field applies, since `composes` carries semantic value beyond a11y.
|
|
30
|
+
- Unchanged: synthetic interactive markup (`<div @click>`) and bare `<a>` without an `href` attribute still fire the warning. Those are the real keyboard-a11y hazards the rule was written for; the new branches are narrowly scoped to the composition cases that previously trained authors to ignore validator output.
|
|
31
|
+
|
|
32
|
+
### Test — diagnostics for harness failure modes
|
|
33
|
+
|
|
34
|
+
- `fireforge test` now detects the `MochitestDesktop.http3Server` AttributeError that crashes the mochitest cleanup path and rewrites it as an actionable hint: branding registration is the lazy-init chain that initialises `http3Server`, so the AttributeError at teardown is almost always a downstream symptom of `chrome://branding` not registering correctly in the fork. The hint enumerates the three concrete checks (branding listed in `browser/branding/moz.build`, `chrome://branding/locale/brand.properties` resolves at runtime, `BROWSER_CHROME_MANIFESTS` registers the chrome.manifest) so operators do not chase the AttributeError as if it were the root cause. Surfaces as a `GeneralError` so the operator sees the diagnostic instead of the generic `BuildError("Tests failed")`. The crash itself remains an upstream Firefox harness bug — FireForge can only diagnose it.
|
|
35
|
+
- New `fireforge test --mach-arg <arg>` (repeatable) forwards a single argument verbatim to `mach test`, after FireForge-managed flags. Escape valve for upstream xpcshell/mochitest flags FireForge does not model directly. Used by the xpcshell appdir hint as the operator's escape valve when auto-injection is the wrong fix; also useful for `--keep-going`, `--verbose`, or `--debugger` while iterating on a flaky test.
|
|
36
|
+
|
|
37
|
+
### Furnace create
|
|
38
|
+
|
|
39
|
+
- `furnace create` now accepts `--dry-run` so operators can preview the planned file set, test scaffold, and `furnace.json` mutation without touching the workspace. Previously the only way to preview a create was `fireforge furnace create --help` followed by running the real command and inspecting the result after the fact — a workflow that stranded a component directory, a furnace.json entry, and (with `--with-tests`) test sources under `engine/` behind every aborted preview. The dry-run path runs every validation the real command would run (name shape, name conflicts, engine pre-existence, `--compose` target existence + cycle detection, prefix warning) BEFORE emitting the plan, so a preview that fails matches a real run that would fail. A new `src/commands/furnace/create-dry-run.ts` owns the plan formatter and the success-note formatter so the two renderings stay in lock-step when the scaffolded layout changes.
|
|
40
|
+
|
|
41
|
+
### Furnace validate — `missing-token-link` auto-detection
|
|
42
|
+
|
|
43
|
+
- `missing-token-link` now auto-detects chrome host documents that mount a component without requiring the operator to configure `tokenHostDocuments`. The validator scans `browser/base/content/*.xhtml` for any document that references the component's tag name and adds those to the scan set alongside the configured (or default) host documents. Previously a fork that mounted `moz-mybrowser-canvas` from its own `mybrowser.xhtml` chrome document had to either configure `tokenHostDocuments` explicitly or ignore every `missing-token-link` warning for Hominis-hosted components; the warning false-fired on `browser.xhtml` (the upstream default) while the component's actual host document linked the tokens CSS correctly. The warning message now reads "none of the scanned chrome host documents" to reflect that the set can include both configured and auto-detected entries. Auto-detected documents that are already in `tokenHostDocuments` are deduplicated so they never render twice in the warning list.
|
|
44
|
+
|
|
45
|
+
### Build audit — known packaging transforms
|
|
46
|
+
|
|
47
|
+
- The post-build audit now applies known source→chrome packaging transforms after jar.mn registration lookup and before the similarity scorer. Motivating case: `engine/browser/base/content/hominis.js` packages to `chrome/browser/content/browser/hominis.js` under dist/, but an unrelated patch placed a `browser/defaults/preferences/hominis.js` pref file in the same build. The source's jar.mn entry has no `(source)` annotation — just a bare target line — so the registration-aware resolver could not anchor the match. The scorer then tied both candidates at score=10 (every intermediate segment of the source is in the "generic" list so no bonus applies), `resolveBestArtifact` picked whichever the directory walk hit first, and the structural-relation check rejected every candidate as unrelated. The audit reported "missing" on the correctly-packaged chrome resource every build. The new `src/core/build-audit-transforms.ts` maps well-known source prefixes (`browser/base/content/`, `toolkit/content/widgets/`, `toolkit/content/`) to their upstream chrome suffixes and picks the first dist candidate whose absolute path ends with the expected suffix — treating the match as high-confidence (bypasses the structural-relation check). Rules are intentionally narrow to the subtrees whose packaging target is stable across every fork we know about; a fork that reroutes a known subtree can still win via `(source)` annotations in its own jar.mn.
|
|
48
|
+
|
|
49
|
+
### Register
|
|
50
|
+
|
|
51
|
+
- `fireforge register <path>` now accepts both repo-root-relative paths (e.g. `engine/browser/base/content/foo.xhtml`) and engine-relative paths (e.g. `browser/base/content/foo.xhtml`). Previously passing the former — a form operators commonly produce by copying paths from `git status` or shell tab completion — failed with `Invalid Argument: File not found in engine: engine/browser/base/content/foo.xhtml`, which named a doubled path that gave no hint the fix was to drop the `engine/` prefix. A leading `engine/` segment is now stripped before the existence check and before the manifest writer runs; the `[dry-run] Would register`, `Already registered`, and `Registered` log lines all render the normalised engine-relative path so the operator sees exactly what the manifest writer received.
|
|
52
|
+
|
|
53
|
+
### Test — stale-build preflight
|
|
54
|
+
|
|
55
|
+
- `fireforge test` now runs a stale-build preflight when `--build` was NOT passed. The probe diffs engine HEAD (and the workdir) against the last-build baseline (`.fireforge/last-build.json`), filters to paths that imply packaging, and — when any match — emits a single up-front warning naming the changed files and pointing at `fireforge test --build`. Previously the mismatch only surfaced as a cryptic `NS_ERROR_FILE_NOT_FOUND` against a `chrome://browser/content/…` URI AFTER xpcshell or mach test launched; the motivating case was scaffolding a new top-level chrome document plus a BrowserGlue-style xpcshell test, where the test file existed, the manifests were registered, but `obj-*/dist/` still held the pre-edit bundle and chrome URIs resolved to nothing. The preflight is warn-only (never blocks) because a fork that rebuilt out-of-band — a direct `./mach build` invocation, an IDE plugin, a separate CI stage — can legitimately have a fresh `dist/` with no FireForge-recorded baseline update. Lives in the new `src/core/test-stale-check.ts`.
|
|
56
|
+
|
|
57
|
+
### Lint — `--only-introduced` exit-code scope
|
|
58
|
+
|
|
59
|
+
- New `fireforge lint --only-introduced` flag scopes the exit code to issues tagged `[introduced]` by `--since`. Cumulative pre-existing queue errors still print (the operator retains full visibility into queue state), but do not fail lint — so a branch whose own diff is clean passes CI even when the repo carries unrelated `raw-color` / license-header errors from older patches. Requires `--since`; without a revision there is no introduced-vs-cumulative distinction and the combination is rejected up-front with an actionable message rather than silently treating every error as cumulative. The failure message reports the count of cumulative errors suppressed by the flag (`N cumulative error(s) suppressed by --only-introduced`) so a branch that turned clean only by virtue of the flag still tells the operator what was hidden. Pre-flag behaviour — any error fails lint — is unchanged when the flag is absent.
|
|
60
|
+
|
|
61
|
+
### Furnace create — xpcshell chrome-URI documentation
|
|
62
|
+
|
|
63
|
+
- `furnace create --xpcshell` scaffolds a generated test that now carries an inline explanation of the xpcshell chrome-URI boundary: toolkit chrome (`chrome://global/*`) IS registered and resolvable from the harness, but browser chrome (`chrome://browser/*`) is NOT registered even when `firefox-appdir = "browser"` is set, and the manifest set xpcshell loads lags what the real browser loads. The comment points at `furnace create --test-style=browser-chrome` as the correct harness for tests that need browser chrome. The `--xpcshell` help text picks up the same note so operators reading `fireforge furnace create --help` see the constraint before they scaffold. Previously the scaffolded test and the help text only described xpcshell as "headless, no tabbrowser", which read as "it just won't render UI" — not "it can't fetch browser chrome URIs". Motivating case: `test_browserGlue_hominis_startup.js` trying to fetch `chrome://browser/content/hominis.xhtml` via `NetUtil.asyncFetch` and hitting `NS_ERROR_FILE_NOT_FOUND` despite the file being present under `obj-*/dist/`.
|
|
64
|
+
|
|
65
|
+
### Furnace chrome-doc — platform-module compatibility + packaging verification
|
|
66
|
+
|
|
67
|
+
- Every `furnace chrome-doc create`-scaffolded root element now carries a `data-furnace-chrome-doc="<name>"` sentinel attribute. Fork-side patches to upstream platform modules that observe `browser-delayed-startup-finished` and walk INTO the window assuming `browser.xhtml`'s DOM — `DevToolsStartup`, `PageActions`, `SessionStore`, `DownloadsButton`, and the growing set of modules that treat every `navigator:browser` window as a main browser window — can guard on this attribute cheaply via `document.documentElement.hasAttribute("data-furnace-chrome-doc")` to skip the walk on a custom chrome doc. The attribute name is fork-neutral so a fork upgrading across FireForge versions does not have to rewrite every guard; the name carried in the value distinguishes multiple chrome docs when a patch needs finer-grained routing. Motivating case: Hominis' `hominis.xhtml` launch fired `TypeError: can't access property "addEventListener", menu is null` from `DevToolsStartup.sys.mjs` and `TypeError: can't access property "placeAllActionsInUrlbar", bpa is undefined` from `PageActions.sys.mjs` on every window-open, because both modules assumed structure that only `browser.xhtml` provides. Both fire as non-fatal Browser Console noise today, but the set of walking modules grows each upstream release, and chasing them with per-element stubs (`<xul:keyset id="mainKeyset"/>`, `<menubar>` placeholders, …) is whack-a-mole compared to a single guard attribute. Exported as `FURNACE_CHROME_DOC_SENTINEL` so test code and external checks can reference the exact name without hardcoding the string.
|
|
68
|
+
- New `furnace chrome-doc create --with-tests` flag scaffolds an xpcshell packaging-verification test (`test_<name>_packaging.js` + `xpcshell.toml`) under `engine/browser/base/content/test/<binary>-xpcshell/<name>/`. The generated test probes the packaged app directory via `Services.dirsvc.get("XCurProcD", Ci.nsIFile)` and navigates to `<AppDir>/chrome/browser/content/browser/<name>.xhtml` (and the corresponding `skin/classic/browser/<name>-chrome.css`), asserting each file exists and is non-empty. Crucially the test does NOT go through `chrome://` URI resolution — the xpcshell harness's browser-chrome manifest set lags the real browser's even with `firefox-appdir = "browser"` set, so `NetUtil.asyncFetch` on a packaged chrome URI returns `NS_ERROR_FILE_NOT_FOUND` against a file that IS correctly packaged (the Hominis startup-verification failure mode). Direct filesystem probing sidesteps the chrome-registration gap entirely. The generated test also carries an inline comment flagging the omni.ja-packed-build limitation so an operator running a packed-tree configuration sees that the scaffold assumes an unpacked `mach build` layout before it fails. `XPCSHELL_TESTS_MANIFESTS` registration is left to the operator because the owning `moz.build` depends on the fork's layout. Lives in the new `src/commands/furnace/chrome-doc-tests.ts`. Writes go through the same rollback journal as the chrome-doc scaffolder itself, so a SIGINT mid-scaffold restores both the source files and the test scaffold.
|
|
69
|
+
- `furnace chrome-doc create`'s "Next steps" note now calls out the platform-module-compatibility sentinel explicitly and points at the README's "Platform module compatibility" section, plus the manual registration step when `--with-tests` is set, so operators see the relevant context before they run `fireforge build`.
|
|
70
|
+
|
|
71
|
+
### Build audit — registration-aware resolution
|
|
72
|
+
|
|
73
|
+
- `fireforge build`'s post-build audit now anchors artifact resolution to the `(source)` references in `jar.mn`. When a source file is claimed by a `jar.mn` entry, the audit walks the registration to compute the expected target path (e.g. `content/browser/mybrowser.js`) and probes the dist tree for a candidate whose absolute path ends with that suffix — rather than basename-similarity scoring, which could not distinguish a correctly-registered chrome resource from an unrelated same-basename file elsewhere in the tree. Motivating case: a fork that added `engine/browser/base/content/mybrowser.js` (registered in `browser/base/jar.mn`, packaged to `chrome/browser/content/browser/mybrowser.js`) alongside an unrelated `browser/defaults/preferences/mybrowser.js` pref from an earlier patch. The heuristic locked onto whichever the directory walk hit first, awarded both candidates an equal trailing-overlap score (basename only), and reported the chrome resource as "missing" in the `Packaged: ...` summary — even though packaging had landed it correctly. Registration-aware resolution in the new `src/core/build-audit-registration.ts` picks the correct artifact without consulting the scorer; the similarity heuristic only runs when no `jar.mn` registration is found (moz.build-registered sources such as `FINAL_TARGET_FILES` and `JS_PREFERENCE_FILES`).
|
|
74
|
+
- When a source IS registered in `jar.mn` but the registered target is absent from dist, the audit now reports "missing" with a warning that names the `jar.mn` entry (`is registered in engine/browser/base/jar.mn as "content/browser/foo.js (content/foo.js)" but no packaged artifact ending in "/content/browser/foo.js" was found under dist/`). This is distinguishable from an unregistered miss and tells the operator "registration is intact, packaging dropped the file" — not "check the jar.mn again".
|
|
75
|
+
- When the heuristic fallback downgrades to `missing` (unrelated same-basename hit, no structural relation to the source), the warning now enumerates EVERY same-basename candidate in dist/ up to five entries, then truncates with a `(+N more)` tail. Previously only the scorer's single pick was surfaced, which often misled operators by naming a file that had nothing to do with the source; the full set lets triage see the real artifact alongside the confounders at a glance.
|
|
76
|
+
|
|
77
|
+
### Build preflight — `--rewrite-mozinfo` for safe relocations
|
|
78
|
+
|
|
79
|
+
- New `fireforge build --rewrite-mozinfo` option handles the class of problem where a workspace was simply moved to a new path and `obj-*/mozinfo.json` still records the old `topsrcdir` / `topobjdir`. Without the flag, the stale-objdir preflight aborts with a "delete and rebuild" instruction; the full rebuild takes ~20 minutes and discards ~14 GB of otherwise-intact obj artefacts. With the flag, FireForge patches `mozinfo.json`'s recorded paths in place and runs `mach configure` so the recursive-make backend regenerates against the corrected paths — no obj-\* scrub, no fresh compile.
|
|
80
|
+
- The rewriter refuses any change that is not a pure prefix-move: mozinfo must record both `topsrcdir` and `topobjdir`, `topobjdir` must resolve to `<topsrcdir>/<objDir>` (out-of-tree builds are rejected), and the detected obj-\* directory name must match the one mozinfo recorded. On any refusal the command falls back to the original clean-rebuild guidance with the refusal reason appended (`mozinfo rewrite refused: …`), so an unsafe relocation is never silently misrepaired. An external mozconfig (one that lives outside the old topsrcdir — e.g. a shared `$HOME/configs/shared-mozconfig`) is left untouched by the rewriter; a relocated workspace that also moved its mozconfig still falls back to clean-rebuild.
|
|
81
|
+
- The `buildArtifactMismatchMessage` copy now points operators at the new flag: "If the workspace was simply moved (same tree, different prefix), "fireforge build --rewrite-mozinfo" will patch mozinfo.json paths in place and run mach configure instead of scrubbing the whole tree." A failed `mach configure` after a successful rewrite surfaces as a `BuildError` so the operator sees "rewrote mozinfo but configure failed" distinctly from "rewrite refused".
|
|
82
|
+
|
|
5
83
|
### Furnace registration
|
|
6
84
|
|
|
7
85
|
- `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.
|
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ mkdir mybrowser && cd mybrowser
|
|
|
44
44
|
npm init -y
|
|
45
45
|
npm install --save-dev @hominis/fireforge
|
|
46
46
|
|
|
47
|
-
npx fireforge setup
|
|
47
|
+
npx fireforge setup # interactive project init
|
|
48
48
|
npx fireforge download # fetch Firefox source (~1 GB)
|
|
49
49
|
npx fireforge bootstrap # install build deps (may need sudo)
|
|
50
50
|
npx fireforge import # apply your patches (if any exist)
|
|
@@ -56,22 +56,25 @@ Your project now has `fireforge.json`, an `engine/` directory with Firefox sourc
|
|
|
56
56
|
|
|
57
57
|
### Workflow Overview
|
|
58
58
|
|
|
59
|
+
1. Make changes inside the `engine/` directory.
|
|
60
|
+
2. Export your changes as a patch:
|
|
61
|
+
|
|
59
62
|
```bash
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
npx fireforge export browser/base/content/browser.js --name "custom-toolbar" --category ui
|
|
64
|
+
```
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
npx fireforge export browser/base/content/browser.js \
|
|
65
|
-
--name "custom-toolbar" --category ui
|
|
66
|
+
3. Your patch is now in `patches/`.
|
|
66
67
|
|
|
67
|
-
#
|
|
68
|
-
# with metadata tracked in patches/patches.json
|
|
68
|
+
# 4. Reset and import to verify everything applies cleanly:
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
```bash
|
|
71
71
|
npx fireforge reset --yes
|
|
72
72
|
npx fireforge import # --dry-run to preview without applying
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
5. When Mozilla releases a new version, update fireforge.json, re-download and rebase:
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
```bash
|
|
75
78
|
npx fireforge download --force
|
|
76
79
|
npx fireforge rebase
|
|
77
80
|
```
|
|
@@ -246,8 +249,10 @@ Every destructive command defaults to an interactive confirmation with a change
|
|
|
246
249
|
# Wire a subscript with init/destroy lifecycle
|
|
247
250
|
fireforge wire my-widget --init "MyWidget.init()" --destroy "MyWidget.destroy()"
|
|
248
251
|
|
|
249
|
-
# Register a file in the correct build manifest
|
|
252
|
+
# Register a file in the correct build manifest (engine-relative or
|
|
253
|
+
# repo-root-relative — a leading `engine/` segment is stripped)
|
|
250
254
|
fireforge register browser/modules/mybrowser/MyStore.sys.mjs
|
|
255
|
+
fireforge register engine/browser/modules/mybrowser/MyStore.sys.mjs
|
|
251
256
|
|
|
252
257
|
# Both support --dry-run to preview changes
|
|
253
258
|
```
|
|
@@ -308,6 +313,7 @@ Custom elements live under `toolkit/content/widgets`, but a fork's top-level chr
|
|
|
308
313
|
```bash
|
|
309
314
|
fireforge furnace chrome-doc create mybrowser # full chrome (titlebar + windowtype)
|
|
310
315
|
fireforge furnace chrome-doc create overlay --no-titlebar # frameless overlay
|
|
316
|
+
fireforge furnace chrome-doc create mybrowser --with-tests # + xpcshell packaging-verification test
|
|
311
317
|
```
|
|
312
318
|
|
|
313
319
|
The command writes:
|
|
@@ -317,9 +323,37 @@ The command writes:
|
|
|
317
323
|
- `engine/browser/themes/shared/<name>-chrome.css` — scoped CSS; emits the macOS `.titlebar-button { display: none }` carve-out under `--no-titlebar`.
|
|
318
324
|
- `engine/browser/locales/en-US/browser/<name>.ftl` — Fluent stub keyed on `<name>-window-title`.
|
|
319
325
|
- Appends the corresponding `jar.mn` / `jar.inc.mn` / `locales/jar.mn` entries.
|
|
326
|
+
- When `--with-tests` is set, also scaffolds an xpcshell test + `xpcshell.toml` under `engine/browser/base/content/test/<binary>-xpcshell/<name>/` that probes the packaged app directory (`Services.dirsvc.get("XCurProcD")/chrome/browser/...`) directly rather than going through `chrome://` URI resolution — see "Platform module compatibility" and the xpcshell chrome-URI note further down for why direct filesystem probing is the reliable way to verify chrome-doc packaging. Registration in `XPCSHELL_TESTS_MANIFESTS` is left to the operator because the owning moz.build depends on the fork layout.
|
|
320
327
|
|
|
321
328
|
Writes are transactional: a SIGINT mid-scaffold rolls back every touched file. Requires an existing engine — run `fireforge download` first.
|
|
322
329
|
|
|
330
|
+
#### Platform module compatibility
|
|
331
|
+
|
|
332
|
+
A custom chrome document with `windowtype="navigator:browser"` is treated as a main browser window by every upstream platform module that observes `browser-delayed-startup-finished` — `DevToolsStartup`, `PageActions`, `SessionStore`, `DownloadsButton`, Sync UI, and more. Those modules walk INTO the window assuming `browser.xhtml`'s DOM (`<menu>` entries, `window.BrowserPageActions`, the `cmd_*` command set, the tabbrowser, …) and throw a `TypeError` on anything else. The errors are non-fatal but noisy, and the matrix of "which modules walk in" grows with every Firefox release.
|
|
333
|
+
|
|
334
|
+
Every `furnace chrome-doc create`-scaffolded root element now carries a `data-furnace-chrome-doc="<name>"` sentinel attribute. Fork-side patches to the offending platform modules can guard on this attribute cheaply:
|
|
335
|
+
|
|
336
|
+
```js
|
|
337
|
+
// DevToolsStartup.sys.mjs (fork patch)
|
|
338
|
+
observe(subject, topic) {
|
|
339
|
+
if (topic === "browser-delayed-startup-finished") {
|
|
340
|
+
const win = subject.QueryInterface(Ci.nsIDOMWindow);
|
|
341
|
+
if (win.document.documentElement.hasAttribute("data-furnace-chrome-doc")) {
|
|
342
|
+
return; // fork's custom chrome doc — skip DevTools menubar wiring
|
|
343
|
+
}
|
|
344
|
+
// ... upstream body ...
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
The sentinel is fork-neutral (the attribute name is stable across projects) so a fork upgrading from one FireForge version to the next does not have to rewrite every guard. The name carried in the attribute value distinguishes multiple chrome docs in the same fork when a patch needs finer-grained routing.
|
|
350
|
+
|
|
351
|
+
#### Harness matrix gap
|
|
352
|
+
|
|
353
|
+
`furnace chrome-doc create` generates a top-level chrome document, but neither of the two existing test harnesses (`furnace create --test-style=mochikit`, `--test-style=browser-chrome`) covers it well: mochikit targets widgets loaded via `chrome://global/` (no tabbrowser, but also no chrome-doc-level interactive behaviors like titlebar drag / focus-ring / window sizing), and browser-chrome mochitest requires a working `tabbrowser` which a fork-authored chrome document that replaces `browser.xhtml` deliberately does not carry. Running a tabbrowser-less window through the mochikit harness crashes the harness itself (on `URILoadingHelper.openLinkIn`), not the chrome doc.
|
|
354
|
+
|
|
355
|
+
The packaging-verification test that `--with-tests` scaffolds is what FireForge can offer cleanly from inside the current harness matrix: it asserts the packaged files landed, not that they behave correctly at runtime. Interactive assertions (dot-grid background painting, titlebar drag region, focus ring) are out of scope for this scaffold and require manual verification against a built browser until the upstream harness matrix catches up.
|
|
356
|
+
|
|
323
357
|
### Picking a test harness for `furnace create`
|
|
324
358
|
|
|
325
359
|
`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.
|
|
@@ -334,6 +368,8 @@ Three styles are available via `--test-style`:
|
|
|
334
368
|
|
|
335
369
|
`--xpcshell` is preserved as an alias for `--test-style=xpcshell`; conflicting flag combinations (`--xpcshell --test-style=mochikit`) are rejected.
|
|
336
370
|
|
|
371
|
+
`furnace create --dry-run` previews the planned file set, test scaffold, and `furnace.json` mutation without writing anything. Every validation the real command runs (tag-name shape, name conflicts, engine pre-existence of the component, `--compose` target existence + cycle detection) fires BEFORE the plan is emitted, so a failed preview matches a failed real run.
|
|
372
|
+
|
|
337
373
|
## Additional Commands
|
|
338
374
|
|
|
339
375
|
The commands below cover project configuration, patch queue management, build packaging and development utilities. Run `fireforge <command> --help` for full option details.
|
|
@@ -392,17 +428,26 @@ fireforge lint --since main # everything since main
|
|
|
392
428
|
fireforge lint --since abc1234 # since a specific SHA
|
|
393
429
|
```
|
|
394
430
|
|
|
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)
|
|
431
|
+
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) unless `--only-introduced` is set; without `--since`, output is unchanged.
|
|
432
|
+
|
|
433
|
+
Pass `--only-introduced` together with `--since` to scope the exit code to issues the current diff introduced. Cumulative pre-existing errors still print, but do not fail lint — useful in CI when a branch's own diff is clean but the repo already carries unrelated errors from older patches:
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
fireforge lint --since main --only-introduced
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
The failure message reports how many cumulative errors were suppressed by the flag so a branch that passed only because of the flag still tells the operator what was hidden. Without `--since`, `--only-introduced` is rejected up-front — there is no introduced-vs-cumulative distinction to scope to.
|
|
396
440
|
|
|
397
441
|
### Post-build audit and auto-configure
|
|
398
442
|
|
|
399
443
|
`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
444
|
|
|
401
|
-
The audit applies
|
|
445
|
+
The audit applies seven routing rules to suppress false positives that previously trained operators to ignore its warnings:
|
|
402
446
|
|
|
447
|
+
- **jar.mn registrations are authoritative.** When the source under audit is claimed by a `(source)` reference in an ancestor `jar.mn`, the audit walks the registration to compute the expected target path (e.g. `content/browser/mybrowser.js`) and probes the dist tree for a candidate whose absolute path ends with that suffix. Picking the correct artifact from a same-basename collision no longer depends on path-similarity scoring. If the registration target is missing from dist, the warning names the `jar.mn` entry so "registration is intact, packaging dropped the file" is distinguishable from "source is unregistered". This is the fix for the class of false positive where `engine/browser/base/content/<name>.js` (registered in `browser/base/jar.mn`) collided with an unrelated `browser/defaults/preferences/<name>.js` added by a separate patch; the heuristic could not distinguish them, so the audit falsely reported the correctly-packaged chrome resource as missing.
|
|
403
448
|
- **Build inputs are excluded.** `jar.mn`, `moz.build`, `moz.configure`, `Makefile.in`, and `mozbuild.in` are consumed by the build to produce chrome registrations / make targets but never themselves ship. They are skipped before the dist lookup, so editing them no longer fires a "missing packaged artifact" warning.
|
|
404
|
-
- **Same-basename collisions in `dist/` are disambiguated by trailing-segment overlap.** A branding override at `engine/browser/branding/<name>/content/aboutDialog.css` ships at `chrome/<area>/content/branding/aboutDialog.css`. A naive basename match would tie that against the unrelated upstream `chrome/<area>/content/browser/aboutDialog.css`; the audit now scores candidates by trailing path-segment match plus a small bonus for non-generic source segments (`branding`, the branding directory name) appearing in the candidate path, so re-rooted artifacts win over coincidentally-named ones.
|
|
405
|
-
- **Unrelated same-basename hits never surface as "stale".** When the best-scoring candidate shares only the basename with the source and no meaningful intermediate segment (common on sparsely-populated `_tests/` trees where an upstream helper like `head.js` is the only same-basename file left from a prior build), the audit classifies the file as `missing` rather than emitting a misleading stale-comparison warning against the unrelated candidate. The warning
|
|
449
|
+
- **Same-basename collisions in `dist/` are disambiguated by trailing-segment overlap.** A branding override at `engine/browser/branding/<name>/content/aboutDialog.css` ships at `chrome/<area>/content/branding/aboutDialog.css`. A naive basename match would tie that against the unrelated upstream `chrome/<area>/content/browser/aboutDialog.css`; the audit now scores candidates by trailing path-segment match plus a small bonus for non-generic source segments (`branding`, the branding directory name) appearing in the candidate path, so re-rooted artifacts win over coincidentally-named ones. Applies only to sources that are not registered in jar.mn (registration-aware lookup runs first).
|
|
450
|
+
- **Unrelated same-basename hits never surface as "stale".** When the best-scoring candidate shares only the basename with the source and no meaningful intermediate segment (common on sparsely-populated `_tests/` trees where an upstream helper like `head.js` is the only same-basename file left from a prior build), the audit classifies the file as `missing` rather than emitting a misleading stale-comparison warning against the unrelated candidate. The warning enumerates every same-basename hit so the operator can see the full set of confounders at a glance — not just the scorer's pick.
|
|
406
451
|
- **Test sources are looked up under `_tests/`, not `dist/`.** Anything under `/test(s)/` directories, plus `browser_*.js` / `test_*.js` / `xpcshell.toml` / `browser.ini`, is resolved against the `_tests/` tree under the active `obj-*` directory. Mochitest and xpcshell harnesses copy registered tests there, never into the packaged bundle. Misses still warn — but they point at `_tests/`, directing the operator to `BROWSER_CHROME_MANIFESTS` / `XPCSHELL_TESTS_MANIFESTS` instead of `package-manifest.in`.
|
|
407
452
|
- **Test-path audits are gated on `_tests/all-tests.json`.** Plain `mach build` populates a partial `_tests/` subtree and stops — full test packaging only runs under `mach package-tests` / `mach test <target>` (or `fireforge test <name>`). The audit now checks for the `all-tests.json` marker written by the packaged-tests make target and silently skips test-path sources when the marker is absent, so every registered mochitest / xpcshell source no longer false-flags as "missing" on the common build-only path. Run `cd engine && ./mach package-tests` (or a scoped `fireforge test`) after a build to green-check test registrations.
|
|
408
453
|
- **Files inside an `if CONFIG[…]:` block in their owning `moz.build` are skipped on hosts where the gate is off.** Windows-only stubinstaller CSS on a macOS build, Darwin-only artwork on Linux, etc. The detection walks up to the closest `moz.build`, scans for the basename inside a Python-style indented `if CONFIG[…]:` block, and matches the gate against the host platform. Negation expressions are conservatively NOT treated as single-OS gates so a warning is never wrongly suppressed for a file that should ship on the current host. Subtrees packaged through platform-specific `Makefile.in` recipes that live outside the `moz.build` graph — `/stubinstaller/` (NSIS), `browser/installer/windows/`, `browser/installer/macosx/`, `browser/installer/linux/` — are also gated by path convention so branding stubinstaller CSS no longer warns on every non-Windows build.
|
|
@@ -411,6 +456,21 @@ The build also auto-runs `mach configure` before the mach build step when any `m
|
|
|
411
456
|
|
|
412
457
|
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.
|
|
413
458
|
|
|
459
|
+
### Relocated workspaces: `fireforge build --rewrite-mozinfo`
|
|
460
|
+
|
|
461
|
+
When a workspace is moved to a new path (e.g. the project directory was renamed or relocated on disk), `obj-*/mozinfo.json` still records the old `topsrcdir` / `topobjdir`. The pre-flight detects the mismatch and aborts with a "delete and rebuild" instruction — correct but expensive; a fresh clean build typically runs ~20 minutes and discards ~14 GB of intact obj artefacts on a moved checkout.
|
|
462
|
+
|
|
463
|
+
`fireforge build --rewrite-mozinfo` offers a shortcut for the pure path-relocation case. The rewriter patches `topsrcdir` / `topobjdir` / `mozconfig` inside `mozinfo.json` to match the current checkout, then runs `mach configure` so the recursive-make backend regenerates against the corrected paths. No obj-\* scrubbing, no fresh compile.
|
|
464
|
+
|
|
465
|
+
The rewriter refuses any change it cannot prove safe:
|
|
466
|
+
|
|
467
|
+
- `mozinfo.json` must record both `topsrcdir` and `topobjdir`.
|
|
468
|
+
- `topobjdir` must resolve to `<topsrcdir>/<objDir>` — out-of-tree builds are rejected.
|
|
469
|
+
- The detected `obj-*` directory name must match the one recorded in mozinfo — if the objdir name itself changed, the configure shape changed and a full rebuild is required.
|
|
470
|
+
- `mozinfo.json` must be valid JSON describing an object.
|
|
471
|
+
|
|
472
|
+
On any refusal the command falls back to the original clean-rebuild guidance with the refusal reason appended, so an unsafe relocation is never silently misrepaired.
|
|
473
|
+
|
|
414
474
|
## Configuration
|
|
415
475
|
|
|
416
476
|
`fireforge.json` at your project root:
|
|
@@ -471,8 +531,91 @@ Both rules compose with the existing `tokenPrefix` / `tokenAllowlist` checks and
|
|
|
471
531
|
|
|
472
532
|
`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).
|
|
473
533
|
|
|
534
|
+
xpcshell has a chrome-URI boundary that is worth knowing before writing assertions: `chrome://global/*` (toolkit chrome) IS registered and resolvable from the harness, but `chrome://browser/*` (browser chrome) is NOT — even when `firefox-appdir = "browser"` is set in the xpcshell.toml, the manifest set xpcshell loads lags what the real browser loads, so `NetUtil.asyncFetch("chrome://browser/content/…")` can still fail with `NS_ERROR_FILE_NOT_FOUND` against an artifact that IS present in `obj-*/dist/`. Assertions that need browser chrome URIs belong in a browser-chrome mochitest (`furnace create --test-style=browser-chrome`). The scaffolded xpcshell test carries the same note inline so the constraint is obvious before the operator extends the test.
|
|
535
|
+
|
|
474
536
|
The two flags can be combined — `--with-tests --xpcshell` writes both harnesses.
|
|
475
537
|
|
|
538
|
+
### Stale-build preflight on `fireforge test`
|
|
539
|
+
|
|
540
|
+
`fireforge test <path>` (without `--build`) now runs a preflight that diffs engine HEAD and the workdir against the last successful `fireforge build` (recorded at `.fireforge/last-build.json`). When packageable engine files have changed since that baseline, the command prints a single up-front warning naming the paths and pointing at `fireforge test --build`. This catches the class of failure where a newly scaffolded chrome resource or pref file is registered correctly but `obj-*/dist/` still holds the pre-edit bundle, so the test reads stale packaged artifacts and errors out with a cryptic `NS_ERROR_FILE_NOT_FOUND` inside xpcshell / mach test. The preflight is warn-only — a fork that rebuilt out-of-band (direct `./mach build`, IDE plugin, separate CI stage) is not blocked. Passing `--build` skips the preflight because the rebuild just refreshed the bundle.
|
|
541
|
+
|
|
542
|
+
### xpcshell appdir auto-injection on rebranded forks
|
|
543
|
+
|
|
544
|
+
`fireforge test` auto-resolves and injects `--app-path=<absolute>` into the underlying `mach test` invocation when the nearest `xpcshell.toml` sets `firefox-appdir = "browser"` and the active build's `appname` is anything other than `firefox`. Without this, every `resource:///modules/<name>.sys.mjs` import inside the harness throws `Failed to load resource:///modules/…` because the upstream xpcshell harness reads the appdir override under the appname-keyed manifest field (`<appname>-appdir`) — the literal `firefox-appdir = "browser"` directive is silently ignored on rebranded forks, `appPath` falls back to `xrePath`, and `resource:///` resolves one level above the real app root. The resolver walks each test path to its nearest manifest, reads `mozinfo.json` for the active appname, prefers any `<appname>-appdir` already in the manifest, and otherwise probes `<objDir>/dist/bin/<value>` and `<objDir>/dist/<bundle>.app/Contents/Resources/<value>` for the absolute target. Operator overrides via `--mach-arg=--app-path=…` always win and skip the resolver silently. Mismatches across multiple test paths and unresolvable manifest values surface as warnings rather than guesses, so triage reaches the underlying cause.
|
|
545
|
+
|
|
546
|
+
The durable fix is to add `<appname>-appdir = "browser"` alongside `firefox-appdir = "browser"` in the manifest — the harness then reads the appname-keyed value directly without auto-injection. The xpcshell appdir hint that fires when the symptom persists despite injection lists this option first.
|
|
547
|
+
|
|
548
|
+
### Smoke-run mode (`fireforge run --smoke-exit`)
|
|
549
|
+
|
|
550
|
+
`fireforge run --smoke-exit <seconds>` launches the real built browser, streams the merged console line-by-line, sends `SIGTERM` to the entire child process group at the deadline, and exits non-zero when any `JavaScript error:` / `console.error:` / `[JavaScript Error]` / `###!!! [Parent]` line surfaces inside the smoke window without matching an allowlist. Closes the headless-vs-real-chrome gap that previously forced agents to choose between `fireforge run` (no exit hook, hangs on a human) and `--headless` (does not load the chrome document, so chrome-window constructor errors stay invisible).
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
# Launch, wait 60s, exit 0 unless an unallowed error fired
|
|
554
|
+
fireforge run --smoke-exit 60
|
|
555
|
+
|
|
556
|
+
# Same, but ignore a known async-shutdown blocker we've already triaged
|
|
557
|
+
fireforge run --smoke-exit 60 --console-allow 'AsyncShutdown blocker timed out'
|
|
558
|
+
|
|
559
|
+
# Allowlist file (one regex per line, # comments and blanks skipped) + capture
|
|
560
|
+
fireforge run --smoke-exit 60 --console-allow-file scripts/smoke-allow.txt --capture-console smoke.log
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
Exit codes are wired distinct from `BUILD_ERROR`:
|
|
564
|
+
|
|
565
|
+
| Code | Meaning |
|
|
566
|
+
| ---- | ---------------------------------------------------------------------- |
|
|
567
|
+
| 0 | Smoke window elapsed cleanly (or only allowlisted errors fired). |
|
|
568
|
+
| 12 | One or more unallowed console errors fired inside the window. |
|
|
569
|
+
| 13 | Browser exited non-clean before the window elapsed (launch-side fail). |
|
|
570
|
+
|
|
571
|
+
POSIX only — process-group semantics do not map cleanly onto Windows. A smoke window shorter than 30 s warns up-front because cold-start time alone can consume that budget on a debug build; `--capture-console <file>` mirrors the captured stream so post-exit inspection has the raw log without re-running.
|
|
572
|
+
|
|
573
|
+
### Furnace `--shared-ftl` for feature-scoped Fluent bundles
|
|
574
|
+
|
|
575
|
+
A feature with multiple components (e.g. an eight-component dock) typically wants one shared `.ftl` per feature rather than eight per-component stubs. `furnace create <tag> --localized --shared-ftl <chrome-uri>` participates in an existing feature-scoped bundle:
|
|
576
|
+
|
|
577
|
+
```bash
|
|
578
|
+
fireforge furnace create hominis-dock-button --localized --shared-ftl browser/hominis-dock.ftl
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
The generated `.mjs` calls `insertFTLIfNeeded("browser/hominis-dock.ftl")` instead of the per-component path. No `<tag>.ftl` stub is written. The `furnace.json` `custom` entry carries a new `sharedFtl` field so apply, validate, and remove all honour the participation:
|
|
582
|
+
|
|
583
|
+
- `furnace apply` does not copy a per-component `.ftl` into the FTL tree nor add a locale `jar.mn` entry — the shared file is registered by whoever owns the feature bundle.
|
|
584
|
+
- `furnace remove` early-returns from the locale `jar.mn` cleanup, so dropping our component's reference does not orphan the bundle for every other participant.
|
|
585
|
+
- `furnace validate`'s `missing-ftl` structural check is skipped — there is no per-component `.ftl` to require.
|
|
586
|
+
|
|
587
|
+
`--shared-ftl` implies `--localized`. `--no-localized + --shared-ftl` is rejected fast-fail. The value is interpolated verbatim into the generated template literal, so backticks, backslashes, and `${` are rejected at parse time. Setting `sharedFtl` does not auto-migrate previous per-component FTL state — flipping an existing component leaves the prior per-component entry in the engine tree and locale `jar.mn` until cleaned up explicitly.
|
|
588
|
+
|
|
589
|
+
### Furnace `keyboardCovered` for composed-button wrappers
|
|
590
|
+
|
|
591
|
+
`furnace validate`'s `no-keyboard-handler` rule is automatically suppressed when `@click` sits on a custom-element host whose `composes` lists a native-interactive child (e.g. `moz-button`, `moz-toggle`). The wrapper's click handler catches keyboard activation transitively because the inner element dispatches `click` on Enter/Space via the platform; a duplicate `@keydown` on the wrapper would either no-op or double-fire alongside the child's built-in path.
|
|
592
|
+
|
|
593
|
+
When the wrapped inner element is hand-authored or is a non-stock `moz-*` widget that does not appear in `composes`, the explicit `keyboardCovered: true` field on the component's `furnace.json` entry forces the same skip:
|
|
594
|
+
|
|
595
|
+
```json
|
|
596
|
+
"hominis-dock-button": {
|
|
597
|
+
"description": "Dock button wrapper",
|
|
598
|
+
"targetPath": "components/custom/hominis-dock-button",
|
|
599
|
+
"register": true,
|
|
600
|
+
"localized": false,
|
|
601
|
+
"composes": ["moz-button"],
|
|
602
|
+
"keyboardCovered": true
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
`keyboardCovered` is operator-asserted — it does not re-check the component, so it can be used to silence a genuine finding. Prefer adding the wrapped tag to `composes` when that field applies (it carries semantic value beyond a11y).
|
|
607
|
+
|
|
608
|
+
### Test escape valves
|
|
609
|
+
|
|
610
|
+
`fireforge test --mach-arg <arg>` (repeatable) forwards a single argument verbatim to `mach test` after FireForge-managed flags. Escape valve for upstream xpcshell/mochitest options FireForge does not model directly:
|
|
611
|
+
|
|
612
|
+
```bash
|
|
613
|
+
fireforge test browser/base/content/test/foo --mach-arg=--keep-going --mach-arg=--verbose
|
|
614
|
+
fireforge test browser/components/tests/unit/test_x.js --mach-arg=--app-path=/abs/override
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
Operator overrides for `--app-path` always win over the auto-injection described above.
|
|
618
|
+
|
|
476
619
|
## Roadmap
|
|
477
620
|
|
|
478
621
|
Planned but not yet implemented:
|
|
@@ -5,12 +5,12 @@ import { auditBuildArtifacts } from '../core/build-audit.js';
|
|
|
5
5
|
import { readBuildBaseline, writeBuildBaseline } from '../core/build-baseline.js';
|
|
6
6
|
import { prepareBuildEnvironment } from '../core/build-prepare.js';
|
|
7
7
|
import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
8
|
-
import { build, buildArtifactMismatchMessage, buildUI, hasBuildArtifacts } from '../core/mach.js';
|
|
8
|
+
import { attemptMozinfoRewrite, build, buildArtifactMismatchMessage, buildUI, hasBuildArtifacts, runMach, } from '../core/mach.js';
|
|
9
9
|
import { GeneralError } from '../errors/base.js';
|
|
10
10
|
import { AmbiguousBuildArtifactsError, BuildError } from '../errors/build.js';
|
|
11
11
|
import { toError } from '../utils/errors.js';
|
|
12
12
|
import { checkDiskSpace, pathExists } from '../utils/fs.js';
|
|
13
|
-
import { error, info, intro, outro, verbose, warn } from '../utils/logger.js';
|
|
13
|
+
import { error, info, intro, outro, spinner, verbose, warn } from '../utils/logger.js';
|
|
14
14
|
import { pickDefined } from '../utils/options.js';
|
|
15
15
|
import { isPositiveInteger } from '../utils/validation.js';
|
|
16
16
|
function parseJobCount(value) {
|
|
@@ -20,6 +20,45 @@ function parseJobCount(value) {
|
|
|
20
20
|
}
|
|
21
21
|
return parsed;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Patches `mozinfo.json` under the active obj-* directory so its recorded
|
|
25
|
+
* paths match the current engine checkout, then runs `mach configure` to
|
|
26
|
+
* regenerate every other generated path alongside it. Throws with the
|
|
27
|
+
* original mismatch guidance when the rewriter refuses to proceed — that
|
|
28
|
+
* always covers the unsafe cases, so the operator still gets the correct
|
|
29
|
+
* fallback recovery instruction.
|
|
30
|
+
*/
|
|
31
|
+
async function rewriteAndReconfigure(engineDir, objDir, mismatchMessage) {
|
|
32
|
+
const rewriteSpinner = spinner('Rewriting mozinfo.json paths...');
|
|
33
|
+
let rewrite;
|
|
34
|
+
try {
|
|
35
|
+
rewrite = await attemptMozinfoRewrite(engineDir, objDir);
|
|
36
|
+
}
|
|
37
|
+
catch (rewriteError) {
|
|
38
|
+
rewriteSpinner.error('mozinfo rewrite failed');
|
|
39
|
+
throw new GeneralError(`${mismatchMessage}\n\nmozinfo rewrite failed: ${toError(rewriteError).message}`);
|
|
40
|
+
}
|
|
41
|
+
if (!rewrite.rewritten) {
|
|
42
|
+
rewriteSpinner.error('mozinfo rewrite refused');
|
|
43
|
+
throw new GeneralError(`${mismatchMessage}\n\nmozinfo rewrite refused: ${rewrite.reason ?? 'unspecified reason'}`);
|
|
44
|
+
}
|
|
45
|
+
rewriteSpinner.stop(`mozinfo.json patched (topsrcdir → ${rewrite.newTopsrcdir})`);
|
|
46
|
+
const configureSpinner = spinner('Running mach configure against rewritten mozinfo.json...');
|
|
47
|
+
let exitCode;
|
|
48
|
+
try {
|
|
49
|
+
exitCode = await runMach(['configure'], engineDir);
|
|
50
|
+
}
|
|
51
|
+
catch (configureError) {
|
|
52
|
+
configureSpinner.error('mach configure failed');
|
|
53
|
+
throw new BuildError('mach configure failed after mozinfo rewrite', 'mach configure', configureError instanceof Error ? configureError : undefined);
|
|
54
|
+
}
|
|
55
|
+
if (exitCode !== 0) {
|
|
56
|
+
configureSpinner.error('mach configure exited non-zero');
|
|
57
|
+
throw new BuildError(`mach configure exited non-zero (${exitCode}) after mozinfo rewrite; a clean rebuild is required.`, 'mach configure');
|
|
58
|
+
}
|
|
59
|
+
configureSpinner.stop('mach configure regenerated the backend');
|
|
60
|
+
info('Backend path-rewrite complete; continuing with the build.');
|
|
61
|
+
}
|
|
23
62
|
function resolveJobCount(options, configJobs) {
|
|
24
63
|
const jobs = options.jobs ?? configJobs;
|
|
25
64
|
if (jobs === undefined) {
|
|
@@ -55,7 +94,17 @@ export async function buildCommand(projectRoot, options) {
|
|
|
55
94
|
}
|
|
56
95
|
const mismatchMessage = buildArtifactMismatchMessage(paths.engine, buildCheck, 'Build');
|
|
57
96
|
if (mismatchMessage) {
|
|
58
|
-
|
|
97
|
+
if (options.rewriteMozinfo && buildCheck.objDir) {
|
|
98
|
+
// Safe-relocation rewrite path: patch mozinfo.json paths in place so
|
|
99
|
+
// `mach configure` can regenerate the backend without scrubbing the
|
|
100
|
+
// whole obj tree. The rewriter refuses anything that is not a pure
|
|
101
|
+
// prefix-move; on refusal we surface the refusal reason alongside
|
|
102
|
+
// the original mismatch guidance and abort.
|
|
103
|
+
await rewriteAndReconfigure(paths.engine, buildCheck.objDir, mismatchMessage);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
throw new GeneralError(mismatchMessage);
|
|
107
|
+
}
|
|
59
108
|
}
|
|
60
109
|
// Log brand info if specified
|
|
61
110
|
if (options.brand) {
|
|
@@ -127,6 +176,7 @@ export function registerBuild(program, { getProjectRoot, withErrorHandling }) {
|
|
|
127
176
|
.option('--ui', 'Fast UI-only rebuild')
|
|
128
177
|
.option('-j, --jobs <n>', 'Number of parallel jobs', parseJobCount)
|
|
129
178
|
.option('--brand <name>', 'Build specific brand')
|
|
179
|
+
.option('--rewrite-mozinfo', 'On a mozinfo path mismatch, patch mozinfo.json paths in place and run mach configure instead of aborting with a clean-rebuild instruction. Refuses anything that is not a pure prefix-move.')
|
|
130
180
|
.addHelpText('after', [
|
|
131
181
|
'',
|
|
132
182
|
'Furnace apply runs automatically before the build step, so edits in',
|
|
@@ -137,6 +187,13 @@ export function registerBuild(program, { getProjectRoot, withErrorHandling }) {
|
|
|
137
187
|
'If you want to preview the engine state without triggering a build,',
|
|
138
188
|
'run `fireforge furnace apply` directly. For source-change-driven',
|
|
139
189
|
'rebuild loops during development, use `fireforge watch`.',
|
|
190
|
+
'',
|
|
191
|
+
'--rewrite-mozinfo: when a workspace is moved to a new path, mozinfo.json',
|
|
192
|
+
'still records the old topsrcdir/topobjdir and the build aborts with a',
|
|
193
|
+
'delete-and-rebuild instruction. This flag patches those paths in place',
|
|
194
|
+
'and runs mach configure — preserving up to ~20 GB of obj-* artefacts on',
|
|
195
|
+
'a relocation. The rewriter refuses any change that is not a pure prefix',
|
|
196
|
+
'move, in which case a clean rebuild is still required.',
|
|
140
197
|
].join('\n'))
|
|
141
198
|
.action(withErrorHandling(async (options) => {
|
|
142
199
|
await buildCommand(getProjectRoot(), pickDefined(options));
|
|
@@ -7,6 +7,19 @@
|
|
|
7
7
|
* upstream Firefox (browser.xhtml, privatebrowsing/aboutPrivateBrowsing.html,
|
|
8
8
|
* etc.) minus the fork-specific wiring. A fork author fills in the body.
|
|
9
9
|
*/
|
|
10
|
+
/**
|
|
11
|
+
* Sentinel attribute emitted on every `furnace chrome-doc create`-scaffolded
|
|
12
|
+
* root element. Platform modules (`DevToolsStartup`, `PageActions`,
|
|
13
|
+
* `SessionStore`, `DownloadsButton`, …) that observe
|
|
14
|
+
* `browser-delayed-startup-finished` and walk INTO the window assume the
|
|
15
|
+
* `browser.xhtml` DOM and throw on anything else. A fork-authored patch
|
|
16
|
+
* to such a module can use `hasAttribute(...)` against this sentinel as
|
|
17
|
+
* a cheap, fork-neutral guard to skip the walk on a custom chrome doc.
|
|
18
|
+
*
|
|
19
|
+
* Exposed as a named constant so test code and external checks can
|
|
20
|
+
* reference the exact attribute name without hardcoding the string.
|
|
21
|
+
*/
|
|
22
|
+
export declare const FURNACE_CHROME_DOC_SENTINEL = "data-furnace-chrome-doc";
|
|
10
23
|
/**
|
|
11
24
|
* XHTML shell for a top-level chrome document.
|
|
12
25
|
*
|
|
@@ -17,6 +30,10 @@
|
|
|
17
30
|
* platform-native window controls render.
|
|
18
31
|
* - Links the per-document CSS at `chrome://browser/content/<name>-chrome.css`
|
|
19
32
|
* and the Fluent bundle `browser/<name>.ftl`.
|
|
33
|
+
* - Carries the `data-furnace-chrome-doc="<name>"` sentinel so fork-side
|
|
34
|
+
* patches to upstream platform modules (DevToolsStartup, PageActions, …)
|
|
35
|
+
* that assume `browser.xhtml`'s DOM can guard against it cheaply. See
|
|
36
|
+
* the README "Platform module compatibility" section for the pattern.
|
|
20
37
|
*/
|
|
21
38
|
export declare function generateChromeDocXhtml(name: string, withTitlebar: boolean, license: string): string;
|
|
22
39
|
/**
|
|
@@ -8,6 +8,19 @@
|
|
|
8
8
|
* upstream Firefox (browser.xhtml, privatebrowsing/aboutPrivateBrowsing.html,
|
|
9
9
|
* etc.) minus the fork-specific wiring. A fork author fills in the body.
|
|
10
10
|
*/
|
|
11
|
+
/**
|
|
12
|
+
* Sentinel attribute emitted on every `furnace chrome-doc create`-scaffolded
|
|
13
|
+
* root element. Platform modules (`DevToolsStartup`, `PageActions`,
|
|
14
|
+
* `SessionStore`, `DownloadsButton`, …) that observe
|
|
15
|
+
* `browser-delayed-startup-finished` and walk INTO the window assume the
|
|
16
|
+
* `browser.xhtml` DOM and throw on anything else. A fork-authored patch
|
|
17
|
+
* to such a module can use `hasAttribute(...)` against this sentinel as
|
|
18
|
+
* a cheap, fork-neutral guard to skip the walk on a custom chrome doc.
|
|
19
|
+
*
|
|
20
|
+
* Exposed as a named constant so test code and external checks can
|
|
21
|
+
* reference the exact attribute name without hardcoding the string.
|
|
22
|
+
*/
|
|
23
|
+
export const FURNACE_CHROME_DOC_SENTINEL = 'data-furnace-chrome-doc';
|
|
11
24
|
/**
|
|
12
25
|
* XHTML shell for a top-level chrome document.
|
|
13
26
|
*
|
|
@@ -18,6 +31,10 @@
|
|
|
18
31
|
* platform-native window controls render.
|
|
19
32
|
* - Links the per-document CSS at `chrome://browser/content/<name>-chrome.css`
|
|
20
33
|
* and the Fluent bundle `browser/<name>.ftl`.
|
|
34
|
+
* - Carries the `data-furnace-chrome-doc="<name>"` sentinel so fork-side
|
|
35
|
+
* patches to upstream platform modules (DevToolsStartup, PageActions, …)
|
|
36
|
+
* that assume `browser.xhtml`'s DOM can guard against it cheaply. See
|
|
37
|
+
* the README "Platform module compatibility" section for the pattern.
|
|
21
38
|
*/
|
|
22
39
|
export function generateChromeDocXhtml(name, withTitlebar, license) {
|
|
23
40
|
const windowAttr = withTitlebar ? ' windowtype="navigator:browser"' : '';
|
|
@@ -34,6 +51,7 @@ export function generateChromeDocXhtml(name, withTitlebar, license) {
|
|
|
34
51
|
xmlns="http://www.w3.org/1999/xhtml"
|
|
35
52
|
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
|
36
53
|
id="${name}-window"${windowAttr}
|
|
54
|
+
${FURNACE_CHROME_DOC_SENTINEL}="${name}"
|
|
37
55
|
data-l10n-id="${name}-window-title"
|
|
38
56
|
role="application">
|
|
39
57
|
<head>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the canonical xpcshell test basename for a chrome doc packaging
|
|
3
|
+
* check (`test_<name>_packaging.js`). Hyphens in `<name>` are preserved —
|
|
4
|
+
* xpcshell permits them in file basenames even though the derived task
|
|
5
|
+
* function names replace them with underscores.
|
|
6
|
+
*/
|
|
7
|
+
export declare function chromeDocPackagingTestFileName(name: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Emits an xpcshell test that verifies the scaffolded chrome doc's
|
|
10
|
+
* `.xhtml`, `-chrome.css`, and `.ftl` are all present under the packaged
|
|
11
|
+
* app directory. Each assertion carries the exact probed path in its
|
|
12
|
+
* failure message so an operator reading a red CI run knows which
|
|
13
|
+
* jar.mn entry or build step dropped the file.
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateChromeDocPackagingTest(name: string, header: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Emits the `xpcshell.toml` manifest for the packaging test directory.
|
|
18
|
+
* Sets `firefox-appdir = "browser"` so XCurProcD resolves to the browser
|
|
19
|
+
* subdir rather than the generic gecko runtime dir — without that, the
|
|
20
|
+
* path probe walks the wrong tree on every fork whose app directory is
|
|
21
|
+
* not the default.
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateChromeDocPackagingManifest(name: string, header: string): string;
|