@hominis/fireforge 0.14.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -1
- package/README.md +41 -3
- package/dist/src/commands/build.js +12 -1
- package/dist/src/commands/furnace/create-templates.d.ts +47 -0
- package/dist/src/commands/furnace/create-templates.js +135 -0
- package/dist/src/commands/furnace/create-xpcshell.d.ts +27 -0
- package/dist/src/commands/furnace/create-xpcshell.js +53 -0
- package/dist/src/commands/furnace/create.js +81 -109
- package/dist/src/commands/furnace/deploy.js +3 -3
- package/dist/src/commands/furnace/index.js +1 -0
- package/dist/src/commands/setup.d.ts +1 -1
- package/dist/src/commands/setup.js +3 -2
- package/dist/src/commands/test.js +20 -0
- package/dist/src/core/build-prepare.js +6 -1
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +2 -0
- package/dist/src/core/config-validate.js +32 -0
- package/dist/src/core/furnace-apply-ftl.d.ts +33 -0
- package/dist/src/core/furnace-apply-ftl.js +102 -0
- package/dist/src/core/furnace-apply-helpers.d.ts +10 -1
- package/dist/src/core/furnace-apply-helpers.js +16 -12
- package/dist/src/core/furnace-apply.js +7 -4
- package/dist/src/core/furnace-config-tokens.d.ts +17 -0
- package/dist/src/core/furnace-config-tokens.js +43 -0
- package/dist/src/core/furnace-config.d.ts +6 -0
- package/dist/src/core/furnace-config.js +16 -3
- package/dist/src/core/furnace-constants.d.ts +20 -0
- package/dist/src/core/furnace-constants.js +32 -0
- package/dist/src/core/furnace-registration-ast.d.ts +13 -1
- package/dist/src/core/furnace-registration-ast.js +58 -25
- package/dist/src/core/furnace-registration.d.ts +27 -0
- package/dist/src/core/furnace-registration.js +96 -0
- package/dist/src/core/furnace-validate-accessibility.js +8 -2
- package/dist/src/core/furnace-validate-compatibility.js +18 -7
- package/dist/src/core/furnace-validate-helpers.d.ts +39 -1
- package/dist/src/core/furnace-validate-helpers.js +182 -18
- package/dist/src/core/furnace-validate-registration.d.ts +8 -2
- package/dist/src/core/furnace-validate-registration.js +34 -9
- package/dist/src/core/furnace-validate.js +2 -2
- package/dist/src/core/marionette-preflight.d.ts +46 -0
- package/dist/src/core/marionette-preflight.js +260 -0
- package/dist/src/core/patch-lint-cross.d.ts +1 -1
- package/dist/src/core/patch-lint-cross.js +1 -1
- package/dist/src/core/patch-lint.js +29 -9
- package/dist/src/types/commands/options.d.ts +16 -0
- package/dist/src/types/config.d.ts +7 -0
- package/dist/src/types/furnace.d.ts +19 -0
- package/dist/src/utils/process.d.ts +15 -2
- package/dist/src/utils/process.js +73 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.0
|
|
4
|
+
|
|
5
|
+
### Furnace registration
|
|
6
|
+
|
|
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. `"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
|
+
- `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
|
+
|
|
11
|
+
### Furnace `--localized`
|
|
12
|
+
|
|
13
|
+
- `furnace create --localized` now emits the Mozilla-idiomatic `MozLitElement` l10n pattern: a module-level `window.MozXULElement?.insertFTLIfNeeded("<chrome-uri>")` call and `this.ownerDocument.l10n?.connectRoot(this.shadowRoot)` / `disconnectRoot` in `connectedCallback` / `disconnectedCallback`. Previously the template called `this.insertFTLIfNeeded(...)` directly on a `MozLitElement` instance, which throws `TypeError: this.insertFTLIfNeeded is not a function` at every connect because that method lives on `MozXULElement`, not `MozLitElement`. The `--localized` path was silently non-functional.
|
|
14
|
+
- `furnace apply` now registers the scaffolded `.ftl` in the locale jar.mn (default `toolkit/locales/jar.mn`) so the chrome URI `insertFTLIfNeeded` expects actually resolves at runtime. Previously only the `.ftl` file itself was copied into the FTL tree, with no chrome registration. `furnace remove` and the workspace-delete codepath (`undeployCustomFiles`) drop the jar.mn entry symmetrically.
|
|
15
|
+
- The locale jar.mn write degrades gracefully — a missing target (non-standard fork tree) surfaces a structured step error rather than aborting apply, so a well-formed `.mjs`/`.css` is never blocked by a broken locale path.
|
|
16
|
+
- FTL chrome URIs are now derived from `furnace.json.ftlBasePath` via a pair of helpers (`resolveFtlChromeSubPath`, `resolveFtlLocaleJarMnPath`) so forks that customise the FTL tree get matching `insertFTLIfNeeded` and jar.mn output.
|
|
17
|
+
|
|
18
|
+
### Furnace validate
|
|
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. `mybrowser.xhtml`). Defaults to `["browser/base/content/browser.xhtml"]` when omitted — behaviour is unchanged for projects that never set the field.
|
|
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.
|
|
24
|
+
|
|
25
|
+
### Run / test
|
|
26
|
+
|
|
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`.
|
|
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
|
+
### Internal
|
|
40
|
+
|
|
41
|
+
- 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.
|
|
42
|
+
- New `src/core/marionette-preflight.ts` owns the `--doctor` probe and its teardown semantics.
|
|
43
|
+
- 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.
|
|
44
|
+
- 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.
|
|
45
|
+
|
|
3
46
|
## 0.14.0
|
|
4
47
|
|
|
5
48
|
### Concurrency and atomicity
|
|
@@ -218,7 +261,7 @@
|
|
|
218
261
|
### General improvements
|
|
219
262
|
|
|
220
263
|
- 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.
|
|
221
|
-
- Some test generators were derived from early
|
|
264
|
+
- 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.
|
|
222
265
|
|
|
223
266
|
### Build and Git reliability
|
|
224
267
|
|
package/README.md
CHANGED
|
@@ -298,7 +298,7 @@ fireforge furnace status # workspace vs engine drift
|
|
|
298
298
|
fireforge furnace diff moz-button # unified diff against baseline
|
|
299
299
|
```
|
|
300
300
|
|
|
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.
|
|
301
|
+
`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.
|
|
302
302
|
|
|
303
303
|
## Additional Commands
|
|
304
304
|
|
|
@@ -367,11 +367,49 @@ fireforge token --name "--my-color" --value "light-dark(#fff, #000)"
|
|
|
367
367
|
"wire": { "subscriptDir": "browser/components/mybrowser" },
|
|
368
368
|
"patchLint": {
|
|
369
369
|
"checkJs": true,
|
|
370
|
-
"rawColorAllowlist": ["
|
|
371
|
-
}
|
|
370
|
+
"rawColorAllowlist": ["mybrowser-tokens.css"]
|
|
371
|
+
},
|
|
372
|
+
"markerComment": "MYBROWSER"
|
|
372
373
|
}
|
|
373
374
|
```
|
|
374
375
|
|
|
376
|
+
**`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
|
+
|
|
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. `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
|
+
|
|
380
|
+
### `furnace create --localized` for `MozLitElement`
|
|
381
|
+
|
|
382
|
+
`fireforge furnace create <tag> --localized` scaffolds a Fluent-ready component. The generated `.mjs` uses the Mozilla-idiomatic `MozLitElement` pattern: a module-level `window.MozXULElement?.insertFTLIfNeeded("<chrome-uri>")` plus `this.ownerDocument.l10n?.connectRoot(this.shadowRoot)` / `disconnectRoot` in `connectedCallback` / `disconnectedCallback`. The chrome URI derives from `furnace.json.ftlBasePath` (default `toolkit/locales/en-US/toolkit/global` → `toolkit/global/<tag>.ftl`). `furnace apply` registers the `.ftl` in the matching locale jar.mn (default `toolkit/locales/jar.mn`) so the chrome URI resolves at runtime. If the locale jar.mn is missing in your fork (non-standard tree), apply surfaces a structured step error instead of aborting — the `.mjs`/`.css` still ship.
|
|
383
|
+
|
|
384
|
+
### `fireforge test --doctor`
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
# Sub-minute marionette handshake probe; bails out of mach test on FAIL
|
|
388
|
+
fireforge test --doctor
|
|
389
|
+
fireforge test --doctor browser/base/content/test/foo/browser_bar.js
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
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
|
+
|
|
394
|
+
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.
|
|
395
|
+
|
|
396
|
+
### Runtime CSS variables in Furnace
|
|
397
|
+
|
|
398
|
+
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:
|
|
399
|
+
|
|
400
|
+
- **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.
|
|
401
|
+
- **`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 `--`.
|
|
402
|
+
|
|
403
|
+
Both rules compose with the existing `tokenPrefix` / `tokenAllowlist` checks and apply to both component validation and patch-stack lint.
|
|
404
|
+
|
|
405
|
+
### Test harness options
|
|
406
|
+
|
|
407
|
+
`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.).
|
|
408
|
+
|
|
409
|
+
`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).
|
|
410
|
+
|
|
411
|
+
The two flags can be combined — `--with-tests --xpcshell` writes both harnesses.
|
|
412
|
+
|
|
375
413
|
## Roadmap
|
|
376
414
|
|
|
377
415
|
Planned but not yet implemented:
|
|
@@ -96,10 +96,21 @@ export async function buildCommand(projectRoot, options) {
|
|
|
96
96
|
export function registerBuild(program, { getProjectRoot, withErrorHandling }) {
|
|
97
97
|
program
|
|
98
98
|
.command('build')
|
|
99
|
-
.description('Build the browser')
|
|
99
|
+
.description('Build the browser (auto-applies Furnace components first)')
|
|
100
100
|
.option('--ui', 'Fast UI-only rebuild')
|
|
101
101
|
.option('-j, --jobs <n>', 'Number of parallel jobs', parseJobCount)
|
|
102
102
|
.option('--brand <name>', 'Build specific brand')
|
|
103
|
+
.addHelpText('after', [
|
|
104
|
+
'',
|
|
105
|
+
'Furnace apply runs automatically before the build step, so edits in',
|
|
106
|
+
'components/custom/ and components/overrides/ are propagated to the',
|
|
107
|
+
'engine/ tree every time. The command prints a banner listing the',
|
|
108
|
+
'components synced during the current invocation.',
|
|
109
|
+
'',
|
|
110
|
+
'If you want to preview the engine state without triggering a build,',
|
|
111
|
+
'run `fireforge furnace apply` directly. For source-change-driven',
|
|
112
|
+
'rebuild loops during development, use `fireforge watch`.',
|
|
113
|
+
].join('\n'))
|
|
103
114
|
.action(withErrorHandling(async (options) => {
|
|
104
115
|
await buildCommand(getProjectRoot(), pickDefined(options));
|
|
105
116
|
}));
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-content templates for `fireforge furnace create`. Extracted from the
|
|
3
|
+
* command entrypoint so the generator is unit-testable in isolation and the
|
|
4
|
+
* command file stays under the per-file LOC budget.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Generates the .mjs file content for a custom component.
|
|
8
|
+
*
|
|
9
|
+
* `MozLitElement` does NOT expose `insertFTLIfNeeded` — that method lives on
|
|
10
|
+
* `MozXULElement`. Calling it from `connectedCallback` on a Lit-based
|
|
11
|
+
* component throws `TypeError: this.insertFTLIfNeeded is not a function` at
|
|
12
|
+
* every connect. Upstream Firefox components (e.g. `moz-input-folder.mjs`)
|
|
13
|
+
* solve this with a module-level guarded call on `window.MozXULElement` and
|
|
14
|
+
* per-instance shadow-DOM Fluent attachment via `l10n.connectRoot`. We mirror
|
|
15
|
+
* that pattern here so `--localized` produces functional code.
|
|
16
|
+
*
|
|
17
|
+
* The FTL path mirrors the locale jar.mn entry that `furnace apply` writes:
|
|
18
|
+
* `<ftlChromeSubPath>/<name>.ftl`. For the default `toolkit/global` tree this
|
|
19
|
+
* yields `toolkit/global/<name>.ftl`, which matches the URI upstream toolkit
|
|
20
|
+
* widgets ship.
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateMjsContent(name: string, className: string, description: string, localized: boolean, header: string, ftlChromeSubPath: string | undefined): string;
|
|
23
|
+
/** Generates the .css file content for a custom component. */
|
|
24
|
+
export declare function generateCssContent(header: string): string;
|
|
25
|
+
/** Generates the .ftl file content for a custom component. */
|
|
26
|
+
export declare function generateFtlContent(name: string, header: string): string;
|
|
27
|
+
/** Returns the canonical xpcshell test file basename for a component. */
|
|
28
|
+
export declare function xpcshellTestFileName(name: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Generates an xpcshell test file for a custom component.
|
|
31
|
+
*
|
|
32
|
+
* xpcshell tests run headless without a `tabbrowser`, so they suit
|
|
33
|
+
* storage/observer/module-loading code in forks that do not mount the
|
|
34
|
+
* upstream browser chrome (and therefore lack `openLinkIn` →
|
|
35
|
+
* `URILoadingHelper`). The scaffold imports the component module via
|
|
36
|
+
* `ChromeUtils.importESModule` and asserts the module resolves — enough
|
|
37
|
+
* to catch registration regressions without touching DOM rendering paths
|
|
38
|
+
* that xpcshell cannot execute.
|
|
39
|
+
*/
|
|
40
|
+
export declare function generateXpcshellTestContent(name: string, header: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Generates an `xpcshell.toml` manifest for a custom component's test
|
|
43
|
+
* directory. Kept minimal — adding prefs, head.js, and support-files is
|
|
44
|
+
* left to the operator because those decisions depend on what the
|
|
45
|
+
* component actually touches (Services.storage, observer topics, etc.).
|
|
46
|
+
*/
|
|
47
|
+
export declare function generateXpcshellManifestContent(name: string, header: string): string;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* File-content templates for `fireforge furnace create`. Extracted from the
|
|
4
|
+
* command entrypoint so the generator is unit-testable in isolation and the
|
|
5
|
+
* command file stays under the per-file LOC budget.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Generates the .mjs file content for a custom component.
|
|
9
|
+
*
|
|
10
|
+
* `MozLitElement` does NOT expose `insertFTLIfNeeded` — that method lives on
|
|
11
|
+
* `MozXULElement`. Calling it from `connectedCallback` on a Lit-based
|
|
12
|
+
* component throws `TypeError: this.insertFTLIfNeeded is not a function` at
|
|
13
|
+
* every connect. Upstream Firefox components (e.g. `moz-input-folder.mjs`)
|
|
14
|
+
* solve this with a module-level guarded call on `window.MozXULElement` and
|
|
15
|
+
* per-instance shadow-DOM Fluent attachment via `l10n.connectRoot`. We mirror
|
|
16
|
+
* that pattern here so `--localized` produces functional code.
|
|
17
|
+
*
|
|
18
|
+
* The FTL path mirrors the locale jar.mn entry that `furnace apply` writes:
|
|
19
|
+
* `<ftlChromeSubPath>/<name>.ftl`. For the default `toolkit/global` tree this
|
|
20
|
+
* yields `toolkit/global/<name>.ftl`, which matches the URI upstream toolkit
|
|
21
|
+
* widgets ship.
|
|
22
|
+
*/
|
|
23
|
+
export function generateMjsContent(name, className, description, localized, header, ftlChromeSubPath) {
|
|
24
|
+
const ftlPath = ftlChromeSubPath !== undefined ? `${ftlChromeSubPath}/${name}.ftl` : `${name}.ftl`;
|
|
25
|
+
const ftlModulePreamble = localized
|
|
26
|
+
? `
|
|
27
|
+
window.MozXULElement?.insertFTLIfNeeded("${ftlPath}");
|
|
28
|
+
`
|
|
29
|
+
: '';
|
|
30
|
+
const lifecycleHooks = localized
|
|
31
|
+
? `
|
|
32
|
+
connectedCallback() {
|
|
33
|
+
super.connectedCallback();
|
|
34
|
+
this.ownerDocument.l10n?.connectRoot(this.shadowRoot);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
disconnectedCallback() {
|
|
38
|
+
super.disconnectedCallback();
|
|
39
|
+
this.ownerDocument.l10n?.disconnectRoot(this.shadowRoot);
|
|
40
|
+
}
|
|
41
|
+
`
|
|
42
|
+
: '';
|
|
43
|
+
return `${header}
|
|
44
|
+
|
|
45
|
+
import { html } from "chrome://global/content/vendor/lit.all.mjs";
|
|
46
|
+
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
|
47
|
+
${ftlModulePreamble}
|
|
48
|
+
/**
|
|
49
|
+
* ${description || name}
|
|
50
|
+
*
|
|
51
|
+
* @tagname ${name}
|
|
52
|
+
*/
|
|
53
|
+
class ${className} extends MozLitElement {
|
|
54
|
+
static properties = {};
|
|
55
|
+
|
|
56
|
+
constructor() {
|
|
57
|
+
super();
|
|
58
|
+
}
|
|
59
|
+
${lifecycleHooks}
|
|
60
|
+
render() {
|
|
61
|
+
return html\`
|
|
62
|
+
<link rel="stylesheet" href="chrome://global/content/elements/${name}.css" />
|
|
63
|
+
<slot></slot>
|
|
64
|
+
\`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
customElements.define("${name}", ${className});
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
/** Generates the .css file content for a custom component. */
|
|
71
|
+
export function generateCssContent(header) {
|
|
72
|
+
return `${header}
|
|
73
|
+
|
|
74
|
+
:host {
|
|
75
|
+
display: block;
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
/** Generates the .ftl file content for a custom component. */
|
|
80
|
+
export function generateFtlContent(name, header) {
|
|
81
|
+
return `${header}
|
|
82
|
+
|
|
83
|
+
## Strings for the ${name} component
|
|
84
|
+
`;
|
|
85
|
+
}
|
|
86
|
+
/** Returns the canonical xpcshell test file basename for a component. */
|
|
87
|
+
export function xpcshellTestFileName(name) {
|
|
88
|
+
return `test_${name.replace(/-/g, '_')}_module_loads.js`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Generates an xpcshell test file for a custom component.
|
|
92
|
+
*
|
|
93
|
+
* xpcshell tests run headless without a `tabbrowser`, so they suit
|
|
94
|
+
* storage/observer/module-loading code in forks that do not mount the
|
|
95
|
+
* upstream browser chrome (and therefore lack `openLinkIn` →
|
|
96
|
+
* `URILoadingHelper`). The scaffold imports the component module via
|
|
97
|
+
* `ChromeUtils.importESModule` and asserts the module resolves — enough
|
|
98
|
+
* to catch registration regressions without touching DOM rendering paths
|
|
99
|
+
* that xpcshell cannot execute.
|
|
100
|
+
*/
|
|
101
|
+
export function generateXpcshellTestContent(name, header) {
|
|
102
|
+
return `${header}
|
|
103
|
+
|
|
104
|
+
"use strict";
|
|
105
|
+
|
|
106
|
+
add_task(async function test_${name.replace(/-/g, '_')}_module_loads() {
|
|
107
|
+
// Module-load smoke check: resolves the ESM at its registered chrome URI.
|
|
108
|
+
// Replace or extend with storage-layer assertions as the component grows
|
|
109
|
+
// (Services.storage, observer topics, JSONFile, etc. are all available
|
|
110
|
+
// here without a tabbrowser).
|
|
111
|
+
const moduleUri = "chrome://global/content/elements/${name}.mjs";
|
|
112
|
+
const module = await ChromeUtils.importESModule(moduleUri);
|
|
113
|
+
Assert.ok(
|
|
114
|
+
module,
|
|
115
|
+
"${name}.mjs should load under xpcshell (storage-layer code path).",
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Generates an `xpcshell.toml` manifest for a custom component's test
|
|
122
|
+
* directory. Kept minimal — adding prefs, head.js, and support-files is
|
|
123
|
+
* left to the operator because those decisions depend on what the
|
|
124
|
+
* component actually touches (Services.storage, observer topics, etc.).
|
|
125
|
+
*/
|
|
126
|
+
export function generateXpcshellManifestContent(name, header) {
|
|
127
|
+
return `${header}
|
|
128
|
+
|
|
129
|
+
[DEFAULT]
|
|
130
|
+
head = ""
|
|
131
|
+
|
|
132
|
+
["${xpcshellTestFileName(name)}"]
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=create-templates.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xpcshell test-harness scaffolder for `fireforge furnace create --xpcshell`.
|
|
3
|
+
* Extracted from `create.ts` so the command entrypoint stays under the
|
|
4
|
+
* per-file LOC budget and the scaffolder is unit-testable in isolation.
|
|
5
|
+
*/
|
|
6
|
+
import { type RollbackJournal } from '../../core/furnace-rollback.js';
|
|
7
|
+
import type { ProjectLicense } from '../../types/config.js';
|
|
8
|
+
/**
|
|
9
|
+
* Scaffolds an xpcshell test harness for a newly created custom component.
|
|
10
|
+
*
|
|
11
|
+
* xpcshell is the appropriate harness for storage-layer code on forks
|
|
12
|
+
* without a `tabbrowser` (no `openLinkIn` → `URILoadingHelper`). Browser
|
|
13
|
+
* chrome mochitests require tabbrowser; xpcshell does not, so storage,
|
|
14
|
+
* observers, and ESM-loading logic can be covered headless.
|
|
15
|
+
*
|
|
16
|
+
* Writes `test_<name>_module_loads.js` and an `xpcshell.toml` manifest
|
|
17
|
+
* into `engine/browser/base/content/test/<binary-name>-xpcshell/
|
|
18
|
+
* <component-name>/`. moz.build registration is intentionally left to the
|
|
19
|
+
* operator — wiring an `XPCSHELL_TESTS_MANIFESTS` entry requires a
|
|
20
|
+
* deliberate choice about which moz.build should own it, and an
|
|
21
|
+
* auto-insertion that guessed wrong would be worse than a note.
|
|
22
|
+
*/
|
|
23
|
+
export declare function scaffoldXpcshellTestFiles(componentName: string, license: ProjectLicense, forgeConfig: {
|
|
24
|
+
binaryName: string;
|
|
25
|
+
}, paths: {
|
|
26
|
+
engine: string;
|
|
27
|
+
}, journal?: RollbackJournal): Promise<string[]>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* xpcshell test-harness scaffolder for `fireforge furnace create --xpcshell`.
|
|
4
|
+
* Extracted from `create.ts` so the command entrypoint stays under the
|
|
5
|
+
* per-file LOC budget and the scaffolder is unit-testable in isolation.
|
|
6
|
+
*/
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { recordCreatedDir, snapshotFile, } from '../../core/furnace-rollback.js';
|
|
9
|
+
import { getLicenseHeader } from '../../core/license-headers.js';
|
|
10
|
+
import { ensureDir, pathExists, writeText } from '../../utils/fs.js';
|
|
11
|
+
import { warn } from '../../utils/logger.js';
|
|
12
|
+
import { generateXpcshellManifestContent, generateXpcshellTestContent, xpcshellTestFileName, } from './create-templates.js';
|
|
13
|
+
/**
|
|
14
|
+
* Scaffolds an xpcshell test harness for a newly created custom component.
|
|
15
|
+
*
|
|
16
|
+
* xpcshell is the appropriate harness for storage-layer code on forks
|
|
17
|
+
* without a `tabbrowser` (no `openLinkIn` → `URILoadingHelper`). Browser
|
|
18
|
+
* chrome mochitests require tabbrowser; xpcshell does not, so storage,
|
|
19
|
+
* observers, and ESM-loading logic can be covered headless.
|
|
20
|
+
*
|
|
21
|
+
* Writes `test_<name>_module_loads.js` and an `xpcshell.toml` manifest
|
|
22
|
+
* into `engine/browser/base/content/test/<binary-name>-xpcshell/
|
|
23
|
+
* <component-name>/`. moz.build registration is intentionally left to the
|
|
24
|
+
* operator — wiring an `XPCSHELL_TESTS_MANIFESTS` entry requires a
|
|
25
|
+
* deliberate choice about which moz.build should own it, and an
|
|
26
|
+
* auto-insertion that guessed wrong would be worse than a note.
|
|
27
|
+
*/
|
|
28
|
+
export async function scaffoldXpcshellTestFiles(componentName, license, forgeConfig, paths, journal) {
|
|
29
|
+
const parentDirName = `${forgeConfig.binaryName}-xpcshell`;
|
|
30
|
+
const testDir = join(paths.engine, 'browser/base/content/test', parentDirName, componentName);
|
|
31
|
+
if (journal && !(await pathExists(testDir))) {
|
|
32
|
+
recordCreatedDir(journal, testDir);
|
|
33
|
+
}
|
|
34
|
+
await ensureDir(testDir);
|
|
35
|
+
const jsHeader = getLicenseHeader(license, 'js');
|
|
36
|
+
const hashHeader = getLicenseHeader(license, 'hash');
|
|
37
|
+
const testFiles = [];
|
|
38
|
+
const testFileName = xpcshellTestFileName(componentName);
|
|
39
|
+
const testFilePath = join(testDir, testFileName);
|
|
40
|
+
if (journal)
|
|
41
|
+
await snapshotFile(journal, testFilePath);
|
|
42
|
+
await writeText(testFilePath, generateXpcshellTestContent(componentName, jsHeader));
|
|
43
|
+
testFiles.push(testFileName);
|
|
44
|
+
const manifestPath = join(testDir, 'xpcshell.toml');
|
|
45
|
+
if (journal)
|
|
46
|
+
await snapshotFile(journal, manifestPath);
|
|
47
|
+
await writeText(manifestPath, generateXpcshellManifestContent(componentName, hashHeader));
|
|
48
|
+
testFiles.push('xpcshell.toml');
|
|
49
|
+
warn(`xpcshell scaffold written under browser/base/content/test/${parentDirName}/${componentName}/. ` +
|
|
50
|
+
'Add the directory to XPCSHELL_TESTS_MANIFESTS in the nearest moz.build to run it via "fireforge test".');
|
|
51
|
+
return testFiles;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=create-xpcshell.js.map
|