@hominis/fireforge 0.44.2 → 0.44.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/build-info.json +4 -4
  3. package/dist/src/commands/build.js +9 -4
  4. package/dist/src/commands/furnace/create-browser-test.d.ts +9 -0
  5. package/dist/src/commands/furnace/create-browser-test.js +6 -6
  6. package/dist/src/commands/furnace/create-dry-run.d.ts +10 -0
  7. package/dist/src/commands/furnace/create-dry-run.js +16 -11
  8. package/dist/src/commands/furnace/create-xpcshell.js +2 -2
  9. package/dist/src/commands/furnace/create.js +5 -0
  10. package/dist/src/commands/test-build-phase.js +1 -1
  11. package/dist/src/commands/token.js +3 -1
  12. package/dist/src/core/build-audit.d.ts +20 -0
  13. package/dist/src/core/build-audit.js +25 -1
  14. package/dist/src/core/build-baseline-types.d.ts +28 -0
  15. package/dist/src/core/build-baseline.d.ts +16 -1
  16. package/dist/src/core/build-baseline.js +38 -2
  17. package/dist/src/core/build-prepare.js +32 -3
  18. package/dist/src/core/engine-changes.d.ts +18 -0
  19. package/dist/src/core/engine-changes.js +34 -0
  20. package/dist/src/core/furnace-constants.d.ts +21 -0
  21. package/dist/src/core/furnace-constants.js +26 -1
  22. package/dist/src/core/license-headers.d.ts +9 -5
  23. package/dist/src/core/license-headers.js +33 -7
  24. package/dist/src/core/test-stale-check.js +2 -13
  25. package/dist/src/core/token-variant.d.ts +12 -2
  26. package/dist/src/core/token-variant.js +106 -17
  27. package/docs/README.md +35 -0
  28. package/docs/configuration.md +116 -0
  29. package/docs/exit-codes.md +63 -0
  30. package/docs/furnace.md +57 -0
  31. package/docs/lifecycle-invariants.md +309 -0
  32. package/docs/machine-output.md +94 -0
  33. package/docs/patch-workflow.md +119 -0
  34. package/docs/run-logs.md +60 -0
  35. package/docs/testing.md +123 -0
  36. package/docs/verification-trees.md +48 -0
  37. package/package.json +2 -1
@@ -0,0 +1,119 @@
1
+ <!-- SPDX-License-Identifier: EUPL-1.2 -->
2
+
3
+ # Patch workflow
4
+
5
+ The day-to-day loop: change code inside `engine/`, export it, keep the queue
6
+ applying cleanly. `fireforge --help` remains the authority on flags; this file
7
+ covers the behaviour that a flag list cannot state.
8
+
9
+ ## The loop
10
+
11
+ ```bash
12
+ npx fireforge status
13
+ npx fireforge export browser/base/content/browser.js --name custom-toolbar --category ui
14
+ npx fireforge re-export custom-toolbar
15
+ npx fireforge lint --per-patch
16
+ npx fireforge verify
17
+ ```
18
+
19
+ ## Exporting
20
+
21
+ Patch names are normalized on export and rename: `window-chrome-tests`,
22
+ `ui-window-chrome-tests`, and `235-ui-window-chrome-tests` all resolve to the
23
+ same `<order>-ui-window-chrome-tests.patch` shape.
24
+
25
+ For large generated asset batches, `re-export --scan --scan-files <manifest>`
26
+ assigns files to their owner patches without broad directory scanning. The
27
+ manifest is JSON:
28
+
29
+ ```json
30
+ {
31
+ "assignments": [
32
+ { "patch": "002-branding-runtime-icons.patch", "files": ["browser/branding/hominis/icon.svg"] }
33
+ ]
34
+ }
35
+ ```
36
+
37
+ The command is dry-runnable, refuses ambiguous ownership, and reports each
38
+ file-to-patch assignment before refreshing the patch.
39
+
40
+ Scan-less `re-export` previews drift; `--refuse-foreign-drift --expect <path>`
41
+ restricts it to intended files.
42
+
43
+ ### Deletions
44
+
45
+ A deleted upstream file is captured like any other change: a path that is
46
+ tracked in HEAD but absent from disk stays in the diff scope, and the emitted
47
+ patch carries a `deleted file mode` section. A captured deletion keeps its
48
+ `filesAffected` entry — the patch is what performs the deletion, so pruning
49
+ the entry would leave the body deleting a file the manifest says the patch has
50
+ nothing to do with — and it still requires the confirmation, because the patch
51
+ will remove those files wherever it is applied.
52
+
53
+ An **untracked** absent path is refused rather than warned about: a patch
54
+ cannot express it either way. The `--files` path also refuses when any
55
+ requested path produces no hunk, checked against the full requested list
56
+ rather than the post-filter subset, so nothing dropped upstream can pass as a
57
+ silent success.
58
+
59
+ ## Queue maintenance
60
+
61
+ Under `fireforge patch`:
62
+
63
+ - `patch compact` closes ordinal gaps (range-aware when `patchPolicy.ranges`
64
+ is configured).
65
+ - `patch reorder` moves a patch.
66
+ - `patch split <source> --files <paths...> --name <name>` carves files out of
67
+ a patch into a new one as a single transaction — including staged-dependency
68
+ owner rewrites — with `--dry-run` support.
69
+ - `patch rename` also supports `--category` and `--order`.
70
+
71
+ Every queue-mutating patch command accepts `--wait-lock [seconds]`; see
72
+ [`configuration.md`](configuration.md#lock-waits) for how the budget resolves.
73
+
74
+ ## Linting the queue
75
+
76
+ `lint --per-patch --patches` accepts repeated flags, comma lists, full
77
+ filenames/stems, manifest names, category-prefixed slugs, and bare slugs.
78
+
79
+ `lint --per-patch --max-warnings 0` is the warning-clean form for a release
80
+ gate.
81
+
82
+ `lint --per-patch --report <path>` writes a machine-readable JSON report with
83
+ each patch's line count, tier, active size thresholds, issues, and
84
+ `lintIgnore`-suppressed issues. The size metrics
85
+ (`countNonBinaryDiffLines`, `resolvePatchSizeTier`, `getPatchSizeThresholds`)
86
+ are also exported on the programmatic API.
87
+
88
+ Per-patch lint type-checks plain Firefox JS against a built-in Firefox-globals
89
+ shim that tracks upstream WebIDL additions per release. See
90
+ [`configuration.md`](configuration.md#patchlint) for the project hooks
91
+ (`checkJsExtraShim`, `checkJsTestFiles`, `checkJsTestShim`).
92
+
93
+ For release whitespace checks, use `npm run whitespace:check`; it still checks
94
+ source diffs while excluding generated `patches/*.patch` diff syntax.
95
+
96
+ ## CI enforcement
97
+
98
+ `fireforge status --check` exits non-zero when any unmanaged, drifted, or
99
+ conflicted file exists. `--fail-on <class,...>` tunes the policy set, and
100
+ `--json` composes — the JSON stays parseable and its `files[]` entries name
101
+ the owning `patch`. `--include-ownership` adds ownership rows to JSON status
102
+ output.
103
+
104
+ The JSON shapes are specified in [`machine-output.md`](machine-output.md), and
105
+ the codes a script should branch on in [`exit-codes.md`](exit-codes.md).
106
+
107
+ ## Rebasing onto a new Firefox source release
108
+
109
+ ```bash
110
+ npx fireforge source set --version 145.0.0esr --product firefox-esr --sha256 <archive-sha256>
111
+ npx fireforge download --force
112
+ npx fireforge rebase
113
+ ```
114
+
115
+ If a patch fails, fix the reject inside `engine/`, then run
116
+ `npx fireforge rebase --continue`. A rebase deliberately leaves the engine
117
+ mutated, so its apply-and-persist pairs are held across a signal rather than
118
+ rolled back — see invariant 4 in
119
+ [`lifecycle-invariants.md`](lifecycle-invariants.md).
@@ -0,0 +1,60 @@
1
+ <!-- SPDX-License-Identifier: EUPL-1.2 -->
2
+
3
+ # Run logs (internal)
4
+
5
+ `fireforge test` and `fireforge build` write their own complete output to
6
+ `.fireforge/logs/<command>-<timestamp>.log` as they stream. The module is
7
+ `src/core/run-log.ts`.
8
+
9
+ ## Why the tool holds the log, not the operator
10
+
11
+ A run's diagnosis used to exist in exactly one place: the terminal. The
12
+ ergonomic default when output is long — `fireforge test … 2>&1 | tail -40` —
13
+ keeps the summary and discards the `TEST-UNEXPECTED-FAIL` lines that say WHAT
14
+ broke, and it launders the exit code besides. Nothing else held the output:
15
+ `runMachCapture` keeps a 2 MB in-memory tail that dies with the process, and
16
+ the shard retry loop discards earlier attempts outright.
17
+
18
+ The operator rule ("never pipe a run") was written down three times downstream
19
+ and broken after each writing. That is the tell that it is not an operator
20
+ problem — the tool is the only party that can make the log survive the
21
+ mistake.
22
+
23
+ ## The contract
24
+
25
+ - **Raw bytes, never the echo filter's collapsed form.** The artifact exists
26
+ to be re-read by whoever is diagnosing the run; shortening it would
27
+ reintroduce the loss it prevents.
28
+ - **Opened before any preflight**, so a refusal is logged too. Those are
29
+ exactly the runs whose only output a `tail` throws away.
30
+ - **Retained 20 deep per command kind.** Per-kind, so a busy `test` loop
31
+ cannot evict the `build` log an operator needs to read beside it. Pruning
32
+ runs before the new file is opened, and orders by the filename's
33
+ timestamp rather than by stat-ing every entry.
34
+ - **Timestamps are filesystem-safe**: the ISO form with `:` and `.` replaced
35
+ by `-`, which Windows accepts and which still sorts chronologically.
36
+ - **Best-effort throughout.** A log that cannot be opened, written or pruned
37
+ degrades to no log. A run must never fail over a diagnostic, so `openRunLog`
38
+ returns `undefined` on failure, a post-open stream error latches `broken`
39
+ and drops every later write, and pruning failures are logged verbosely
40
+ only.
41
+
42
+ ## Where the path is announced
43
+
44
+ `test` publishes it as an additive `log=<path>` key on the
45
+ `FIREFORGE-VERDICT:` line. That is forced, not chosen: the verdict must stay
46
+ the run's LAST stdout write ([`machine-output.md`](machine-output.md)), so a
47
+ separate announcement after it would break the contract, and one before it is
48
+ the first thing a `tail` cuts.
49
+
50
+ `build` prints no verdict line, so it announces the path directly
51
+ (`Full build output: <path>`).
52
+
53
+ ## Where the tee sits
54
+
55
+ At the two mach capture funnels in `src/core/mach.ts`, not at the commands.
56
+ That covers every dispatch: the three test dispatchers, `package`, and the
57
+ stdio-inheriting path `mach build` uses — which rides the collectors' existing
58
+ `mirror` hook rather than growing a second one. A new dispatcher added at the
59
+ funnels is logged by construction; one added beside them is not, which is the
60
+ reason to keep going through them.
@@ -0,0 +1,123 @@
1
+ <!-- SPDX-License-Identifier: EUPL-1.2 -->
2
+
3
+ # Running tests
4
+
5
+ `fireforge test` wraps `mach test`. Most of what follows exists because
6
+ mach's own defaults are wrong for a fork that must know exactly what ran.
7
+
8
+ ## Scope is exact
9
+
10
+ `fireforge test <directory>` runs exactly that directory: FireForge enumerates
11
+ the directory's test files and passes the explicit file list to mach in one
12
+ invocation, so mach's prefix-based path matching cannot silently sweep in
13
+ sibling directories sharing the name prefix. Excluded siblings are echoed with
14
+ their test-file counts.
15
+
16
+ Multiple path arguments run as **sequential shards** by default — one browser
17
+ instance per argument, with a directory argument keeping its files together —
18
+ announced with a notice, since isolated instances do not exercise
19
+ cross-argument state. `--no-shard` restores one combined invocation.
20
+
21
+ ## Pathless runs must choose a mode
22
+
23
+ - `--auto` forwards mach's own auto-selection.
24
+ - `--doctor` runs the Marionette preflight only.
25
+ - `--canary [path]` runs one short browser-chrome canary
26
+ (`test.canaryPath` / `test.canaryTimeoutSeconds` in `fireforge.json`
27
+ provide defaults).
28
+
29
+ ## Build freshness
30
+
31
+ When packageable engine files changed since the last successful FireForge
32
+ build, `test` fails before launching stale artifacts. Use `--build` to
33
+ refresh, or `--allow-stale-build` only for intentional out-of-band rebuilds.
34
+
35
+ `--build-only` packages mixed-harness paths once, then each harness half can
36
+ run without `--build`. `--extend-coverage` safely retains prior scoped
37
+ coverage instead of replacing it; it is refused when the build anchor moved
38
+ (engine HEAD, `engine/mozconfig`, or a previously fingerprinted packageable
39
+ file).
40
+
41
+ ### What a pre-test build costs
42
+
43
+ `test --build` runs `mach build faster`, with two escalations decided by
44
+ comparing build inputs against the **last successful build** recorded in
45
+ `.fireforge/last-build.json` — not against engine HEAD, since a fork's
46
+ worktree is permanently dirty (imported patches, Furnace-applied
47
+ components):
48
+
49
+ - a changed `moz.build` / `moz.configure` / `Makefile.in` runs
50
+ `mach configure` first;
51
+ - a changed `jar.mn` escalates the whole build to a full `mach build`
52
+ (minutes rather than seconds), so that new install-manifest destinations
53
+ exist before the run.
54
+
55
+ Each escalation is paid once per actual content change: a `jar.mn` that is
56
+ dirty against HEAD but byte-identical to what the last full build consumed
57
+ does not escalate again. `fireforge build --ui` is itself a
58
+ `mach build faster` and never escalates; the `jar.mn` record it writes is
59
+ carried forward from the previous full build, so a registration made
60
+ between a `build --ui` and the next `test --build` still triggers the full
61
+ build once.
62
+
63
+ Whether a full build is really required for an entry added to an existing
64
+ `jar.mn` under `dist/bin` is an open question, recorded in the 0.44.0
65
+ changelog: mozbuild regenerates its backend from a newer `jar.mn` on any
66
+ `mach build`, `faster` included, and the FasterMake backend consumes
67
+ `dist/bin` jar manifests. The escalation stays until that is demonstrated
68
+ end to end against a real objdir (`build --ui` after a jar-only
69
+ registration, then a plain `test`), because relaxing a stale-artifact guard
70
+ on source reading alone trades a slow build for a silently wrong test.
71
+
72
+ ## Resilience
73
+
74
+ - Recognized harness crashes retry up to `--harness-retries <n>` times
75
+ (default 2).
76
+ - `--kill-stale-marionette` terminates recognized stale browsers.
77
+ - `--perf-samples <path>` publishes a perf-sample artifact path to the
78
+ harness (exported as `<BINARYNAME>_PERF_SAMPLE_JSON`).
79
+
80
+ ## The verdict line
81
+
82
+ Every test run ends with one machine-readable line:
83
+
84
+ ```
85
+ FIREFORGE-VERDICT: PASS|FAIL reason=… [log=<path>]
86
+ ```
87
+
88
+ Automation should branch on it rather than on the raw process code, and must
89
+ treat a **missing** verdict as failure. The closed set of `reason=` values and
90
+ the stdout rules around the line are in
91
+ [`machine-output.md`](machine-output.md); `log=` names the run's own complete
92
+ output, specified in [`run-logs.md`](run-logs.md).
93
+
94
+ Exit code 14 (`INCONCLUSIVE`) is not red: it means `engine/` moved while the
95
+ harness ran and the result was thrown away. Exit 15 (`LOCK_TIMEOUT`) means the
96
+ run never started. See [`exit-codes.md`](exit-codes.md).
97
+
98
+ ## Output verbosity
99
+
100
+ mozbuild quiets terminal output to warnings and errors when it detects a
101
+ coding agent (`is_running_under_coding_agent()` keys on `CLAUDECODE`). The
102
+ build half of that quieting is useful; the test half removes `TEST_START` and
103
+ console INFO — the lines a hang or stall diagnosis needs, and the ones
104
+ FireForge's own classifier reads (`Ran N checks`, `Unexpected results:`,
105
+ `TEST-UNEXPECTED-*`), so suppression pushes a run toward `reason=no-tests`.
106
+
107
+ `fireforge test --full-output` unsets the marker for **test dispatches only**;
108
+ the build path stays quiet. It is opt-in rather than automatic because it
109
+ changes how much a third party prints, and an operator who wants the quieting
110
+ should keep it.
111
+
112
+ ## Concurrency
113
+
114
+ A test run holds the engine-session lock and snapshots the engine git
115
+ generation before and after the harness run, failing the verdict as invalid if
116
+ the tree changed mid-run. To verify beside a busy primary checkout instead,
117
+ use a [verification tree](verification-trees.md).
118
+
119
+ ## Dev-build gotcha
120
+
121
+ In dev builds, files under `obj-*/dist/bin` may be symlinks back into the
122
+ source tree (notably prefs), so edit source prefs directly and keep a backup
123
+ before bisection experiments.
@@ -0,0 +1,48 @@
1
+ <!-- SPDX-License-Identifier: EUPL-1.2 -->
2
+
3
+ # Verification trees
4
+
5
+ For concurrent read-mostly verification beside a busy primary checkout,
6
+ `fireforge tree create <name>` snapshots the project — applied engine tree
7
+ included, `obj-*` excluded — into `.fireforge/trees/<name>` using filesystem
8
+ copy-on-write (APFS `clonefile`, btrfs/XFS reflink). Roughly 3 s for a 156 MB
9
+ applied browser tree.
10
+
11
+ ```bash
12
+ npx fireforge tree create verify
13
+ npx fireforge tree exec verify -- npx fireforge lint --per-patch
14
+ npx fireforge tree list
15
+ npx fireforge tree remove verify
16
+ ```
17
+
18
+ ## What works inside a tree
19
+
20
+ Read-only commands and `export`/`re-export` dry runs. **Mutations are
21
+ refused** — a default-deny verdict table, not a blocklist, so a newly added
22
+ mutating command is refused until it is classified. That is invariant 7 in
23
+ [`lifecycle-invariants.md`](lifecycle-invariants.md).
24
+
25
+ `--with-objdir` adds a safely relocated build, which makes build-less `test`
26
+ possible inside the tree.
27
+
28
+ ## Lifecycle
29
+
30
+ - `tree list` reports staleness (`--json`; see
31
+ [`machine-output.md`](machine-output.md)).
32
+ - `tree remove` deletes, refusing an active lock and a lock of unknown
33
+ ownership.
34
+ - `tree exec <name> -- <cmd>` runs a command inside a tree, sealing its stdout
35
+ so exactly one verdict line survives, last.
36
+
37
+ ## Filesystems without CoW
38
+
39
+ Refused unless `--force-copy` explicitly accepts a full physical copy. The
40
+ refusal is the point: a silent full copy of an applied browser tree is not
41
+ the operation anyone asked for.
42
+
43
+ ## The tree marker
44
+
45
+ `.fireforge/tree.json` is tri-state: absent (primary), valid (snapshot), or
46
+ **corrupt**. A corrupt marker refuses every command, because an unreadable
47
+ marker leaves it unknown whether this is a snapshot or the primary tree.
48
+ `--ignore-corrupt-tree-marker` overrides that deliberately.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hominis/fireforge",
3
- "version": "0.44.2",
3
+ "version": "0.44.4",
4
4
  "description": "FireForge — a build tool for customizing Firefox",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -48,6 +48,7 @@
48
48
  "!dist/**/test-utils/",
49
49
  "!dist/**/*.js.map",
50
50
  "templates/",
51
+ "docs/",
51
52
  "README.md",
52
53
  "CHANGELOG.md",
53
54
  "LICENSE.md"