@hominis/fireforge 0.47.1 → 0.48.0

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 (82) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/bin/fireforge.js +5 -0
  3. package/dist/build-info.json +4 -4
  4. package/dist/src/commands/doctor-macos-sdk.d.ts +8 -0
  5. package/dist/src/commands/doctor-macos-sdk.js +156 -0
  6. package/dist/src/commands/doctor.js +2 -0
  7. package/dist/src/commands/download.js +64 -3
  8. package/dist/src/commands/import.js +5 -2
  9. package/dist/src/commands/lint-per-patch.js +44 -6
  10. package/dist/src/commands/lint-per-run-checkjs.d.ts +3 -0
  11. package/dist/src/commands/lint-per-run-checkjs.js +1 -0
  12. package/dist/src/commands/lint.js +10 -0
  13. package/dist/src/commands/re-export-lint.d.ts +17 -2
  14. package/dist/src/commands/re-export-lint.js +33 -4
  15. package/dist/src/commands/re-export.js +4 -2
  16. package/dist/src/commands/rebase/confirm.d.ts +12 -1
  17. package/dist/src/commands/rebase/confirm.js +6 -3
  18. package/dist/src/commands/rebase/index.js +77 -13
  19. package/dist/src/commands/run.js +43 -7
  20. package/dist/src/commands/status.js +5 -3
  21. package/dist/src/commands/typecheck.d.ts +5 -0
  22. package/dist/src/commands/typecheck.js +7 -1
  23. package/dist/src/core/build-prepare.d.ts +2 -0
  24. package/dist/src/core/build-prepare.js +67 -35
  25. package/dist/src/core/config-paths.d.ts +1 -1
  26. package/dist/src/core/config-paths.js +1 -0
  27. package/dist/src/core/config-validate.js +7 -0
  28. package/dist/src/core/discard-baseline.js +2 -1
  29. package/dist/src/core/forward-registrations.d.ts +33 -0
  30. package/dist/src/core/forward-registrations.js +16 -0
  31. package/dist/src/core/git-file-ops.d.ts +21 -0
  32. package/dist/src/core/git-file-ops.js +75 -0
  33. package/dist/src/core/git-status.d.ts +14 -0
  34. package/dist/src/core/git-status.js +41 -2
  35. package/dist/src/core/mach-mozconfig.d.ts +2 -1
  36. package/dist/src/core/mach-mozconfig.js +2 -0
  37. package/dist/src/core/patch-apply-fuzz.d.ts +12 -0
  38. package/dist/src/core/patch-apply-fuzz.js +27 -6
  39. package/dist/src/core/patch-apply.d.ts +13 -0
  40. package/dist/src/core/patch-apply.js +27 -2
  41. package/dist/src/core/patch-lint-checkjs-cache.d.ts +18 -0
  42. package/dist/src/core/patch-lint-checkjs-cache.js +45 -0
  43. package/dist/src/core/patch-lint-checkjs.d.ts +6 -0
  44. package/dist/src/core/patch-lint-checkjs.js +8 -1
  45. package/dist/src/core/patch-lint-cross.d.ts +7 -1
  46. package/dist/src/core/patch-lint-cross.js +13 -12
  47. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +38 -0
  48. package/dist/src/core/patch-lint-forward-registration-extended.js +240 -0
  49. package/dist/src/core/patch-lint-forward-registration.d.ts +18 -5
  50. package/dist/src/core/patch-lint-forward-registration.js +83 -37
  51. package/dist/src/core/patch-lint.d.ts +11 -0
  52. package/dist/src/core/patch-lint.js +20 -2
  53. package/dist/src/core/patch-transform.d.ts +14 -13
  54. package/dist/src/core/patch-transform.js +29 -17
  55. package/dist/src/core/rebase-dry-run.d.ts +48 -0
  56. package/dist/src/core/rebase-dry-run.js +105 -0
  57. package/dist/src/core/run-profile.d.ts +50 -0
  58. package/dist/src/core/run-profile.js +131 -0
  59. package/dist/src/core/status-binary.d.ts +1 -1
  60. package/dist/src/core/status-binary.js +3 -1
  61. package/dist/src/core/status-classify.js +29 -2
  62. package/dist/src/core/toolchain-preflight.d.ts +6 -0
  63. package/dist/src/core/toolchain-preflight.js +1 -1
  64. package/dist/src/core/typecheck.d.ts +11 -1
  65. package/dist/src/core/typecheck.js +85 -11
  66. package/dist/src/errors/rebase.d.ts +9 -0
  67. package/dist/src/errors/rebase.js +20 -0
  68. package/dist/src/index.d.ts +3 -1
  69. package/dist/src/index.js +2 -1
  70. package/dist/src/types/commands/options.d.ts +15 -0
  71. package/dist/src/types/config.d.ts +10 -0
  72. package/dist/src/utils/process-input.d.ts +27 -0
  73. package/dist/src/utils/process-input.js +49 -0
  74. package/dist/src/utils/process.d.ts +2 -0
  75. package/dist/src/utils/process.js +2 -1
  76. package/dist/src/utils/validation.d.ts +11 -0
  77. package/dist/src/utils/validation.js +43 -0
  78. package/docs/configuration.md +81 -0
  79. package/docs/exit-codes.md +1 -1
  80. package/docs/lifecycle-invariants.md +4 -3
  81. package/docs/patch-workflow.md +35 -1
  82. package/package.json +7 -3
@@ -85,6 +85,8 @@ export interface SetupOptions {
85
85
  export interface DownloadOptions {
86
86
  /** Force re-download, deleting existing engine/ */
87
87
  force?: boolean;
88
+ /** With `force`: skip the confirmation for an engine with changes to lose */
89
+ yes?: boolean;
88
90
  }
89
91
  /**
90
92
  * Options for the source command.
@@ -497,6 +499,13 @@ export interface RebaseOptions {
497
499
  export interface RunOptions {
498
500
  /** Additional arguments to pass to the browser */
499
501
  args?: string[];
502
+ /** Launch with this profile directory, passed to Firefox as `-profile`. */
503
+ profile?: string;
504
+ /**
505
+ * Launch with a fresh temporary profile, removed when the run ends.
506
+ * Implied by `smokeExit` unless `profile` is given.
507
+ */
508
+ tempProfile?: boolean;
500
509
  /**
501
510
  * Enable smoke-run mode. Launches the browser, streams the console,
502
511
  * sends SIGTERM to the whole process group after `smokeExit` seconds,
@@ -1115,4 +1124,10 @@ export interface LintCommandOptions {
1115
1124
  * {@link perPatch}.
1116
1125
  */
1117
1126
  report?: string;
1127
+ /**
1128
+ * With `--per-patch`: `'full'` (default) prints every NOTICE line;
1129
+ * `'summary'` prints one line per check with its count and patch count.
1130
+ * Errors and warnings always print in full.
1131
+ */
1132
+ notices?: 'summary' | 'full';
1118
1133
  }
@@ -355,6 +355,16 @@ export interface PatchLintConfig {
355
355
  * scope for the per-patch tier.
356
356
  */
357
357
  prettier?: PatchLintSeverityGate;
358
+ /**
359
+ * Which carriers the `forward-registration` queue rule checks for a line
360
+ * that names a file only a later patch creates. `'support-files'` (the
361
+ * default) checks test-manifest `support-files`. `'extended'` also checks
362
+ * jar.mn pair lines, moz.build path tokens, test-manifest sections and
363
+ * `head`, and customElements.js chrome URLs resolved through the queue's
364
+ * own jar.mn lines. Opt-in: a queue that predates the wider rule can carry
365
+ * such edges, and a new error on install would red its gate.
366
+ */
367
+ forwardRegistration?: 'support-files' | 'extended';
358
368
  /** Enforce JSDoc on class-method exports in patch-owned .sys.mjs files. Default: 'off'. */
359
369
  jsdocClassMethods?: PatchLintSeverityGate;
360
370
  /** Require ≥1 assertion in any patch-touched browser_*.js test file (new or modified). Default: 'off'. */
@@ -0,0 +1,27 @@
1
+ /** Options for {@link execWithInput}. */
2
+ export interface ExecWithInputOptions {
3
+ /** Working directory for the command */
4
+ cwd?: string;
5
+ /** Environment variables merged over `process.env` */
6
+ env?: Record<string, string>;
7
+ }
8
+ /** Result of {@link execWithInput}: stdout stays raw bytes. */
9
+ export interface ExecBufferResult {
10
+ /** Standard output, uncapped and undecoded */
11
+ stdout: Buffer;
12
+ /** Standard error, decoded as UTF-8 */
13
+ stderr: string;
14
+ /** Process exit code */
15
+ exitCode: number;
16
+ }
17
+ /**
18
+ * Runs a command, writes `input` to its stdin, and returns stdout as raw
19
+ * bytes. For batch protocols (`git cat-file --batch`) whose framing counts
20
+ * bytes: the decoded, 50 MB-capped collector `exec` uses would corrupt
21
+ * both. The caller bounds the input, and so the output.
22
+ * @param command - Command to execute
23
+ * @param args - Command arguments
24
+ * @param input - Bytes written to stdin, which is then closed
25
+ * @param options - Execution options
26
+ */
27
+ export declare function execWithInput(command: string, args: string[], input: string | Buffer, options?: ExecWithInputOptions): Promise<ExecBufferResult>;
@@ -0,0 +1,49 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Stdin-fed command execution with byte-exact stdout.
4
+ *
5
+ * Its own module rather than a function in `process.ts` only because that
6
+ * file sits on a 500-line budget. The logic belongs to the exec layer.
7
+ */
8
+ import { spawn } from 'node:child_process';
9
+ import { buildChildEnv } from './child-env.js';
10
+ import { exitCodeFromClose } from './process.js';
11
+ /**
12
+ * Runs a command, writes `input` to its stdin, and returns stdout as raw
13
+ * bytes. For batch protocols (`git cat-file --batch`) whose framing counts
14
+ * bytes: the decoded, 50 MB-capped collector `exec` uses would corrupt
15
+ * both. The caller bounds the input, and so the output.
16
+ * @param command - Command to execute
17
+ * @param args - Command arguments
18
+ * @param input - Bytes written to stdin, which is then closed
19
+ * @param options - Execution options
20
+ */
21
+ export async function execWithInput(command, args, input, options = {}) {
22
+ return new Promise((resolve, reject) => {
23
+ const child = spawn(command, args, {
24
+ cwd: options.cwd,
25
+ env: buildChildEnv(options),
26
+ stdio: ['pipe', 'pipe', 'pipe'],
27
+ });
28
+ const out = [];
29
+ const err = [];
30
+ child.stdout.on('data', (chunk) => {
31
+ out.push(chunk);
32
+ });
33
+ child.stderr.on('data', (chunk) => {
34
+ err.push(chunk);
35
+ });
36
+ // A child that exits before reading all of stdin (a bad ref, say) makes
37
+ // the write fail with EPIPE. The close handler reports the real outcome.
38
+ child.stdin.on('error', () => undefined);
39
+ child.stdin.end(input);
40
+ child.on('error', reject);
41
+ child.on('close', (code, signal) => {
42
+ resolve({
43
+ stdout: Buffer.concat(out),
44
+ stderr: Buffer.concat(err).toString('utf8'),
45
+ exitCode: exitCodeFromClose(code, signal),
46
+ });
47
+ });
48
+ });
49
+ }
@@ -104,6 +104,8 @@ export interface ProcessGroupOptions {
104
104
  */
105
105
  postCloseSweep?: () => Promise<void>;
106
106
  }
107
+ /** A child's exit code, with a signal death mapped to 128 + its number. */
108
+ export declare function exitCodeFromClose(code: number | null, signal: NodeJS.Signals | null): number;
107
109
  /**
108
110
  * Executes a command and returns its output.
109
111
  * @param command - Command to execute
@@ -77,7 +77,8 @@ function collectedOutput(out, err) {
77
77
  truncated: out.wasTruncated() || err.wasTruncated(),
78
78
  };
79
79
  }
80
- function exitCodeFromClose(code, signal) {
80
+ /** A child's exit code, with a signal death mapped to 128 + its number. */
81
+ export function exitCodeFromClose(code, signal) {
81
82
  if (code !== null) {
82
83
  return code;
83
84
  }
@@ -68,6 +68,17 @@ export declare function isJsonObject(value: JsonValue | undefined): value is Jso
68
68
  * Accepts formats like "140.9.0", "140.9.1", "140.9.0esr", "147.0b1"
69
69
  */
70
70
  export declare function isValidFirefoxVersion(version: string): boolean;
71
+ /**
72
+ * Orders two Firefox version strings semantically: major, minor and patch
73
+ * numerically, with a beta (`153.0b7`) before the release it precedes
74
+ * (`153.0`). An `esr` suffix does not change the order: `153.3.0esr` and
75
+ * `153.3.0` compare equal, since the product says which line it is. A
76
+ * lexical sort, which this replaces in `rebase`, put `153.10.0esr` before
77
+ * `153.2.0esr` and `99.0` after `140.0esr`. A string that is not a Firefox
78
+ * version sorts after every one that is, then lexically.
79
+ * @returns Negative when `a` is older, positive when newer, 0 when equal
80
+ */
81
+ export declare function compareFirefoxVersions(a: string, b: string): number;
71
82
  /**
72
83
  * Validates a release-candidate build directory name.
73
84
  * Accepts formats like "build1", "build2", "build12" (no leading zero).
@@ -100,6 +100,49 @@ export function isValidFirefoxVersion(version) {
100
100
  // Beta: 147.0b1, 147.0b2
101
101
  return /^[1-9]\d{0,2}\.\d+(?:b[1-9]\d*|\.\d+(?:esr)?|esr)?$/.test(version);
102
102
  }
103
+ /**
104
+ * Orders two Firefox version strings semantically: major, minor and patch
105
+ * numerically, with a beta (`153.0b7`) before the release it precedes
106
+ * (`153.0`). An `esr` suffix does not change the order: `153.3.0esr` and
107
+ * `153.3.0` compare equal, since the product says which line it is. A
108
+ * lexical sort, which this replaces in `rebase`, put `153.10.0esr` before
109
+ * `153.2.0esr` and `99.0` after `140.0esr`. A string that is not a Firefox
110
+ * version sorts after every one that is, then lexically.
111
+ * @returns Negative when `a` is older, positive when newer, 0 when equal
112
+ */
113
+ export function compareFirefoxVersions(a, b) {
114
+ const left = parseFirefoxVersionOrder(a);
115
+ const right = parseFirefoxVersionOrder(b);
116
+ if (left === undefined || right === undefined) {
117
+ if (left !== undefined)
118
+ return -1;
119
+ if (right !== undefined)
120
+ return 1;
121
+ return a < b ? -1 : a > b ? 1 : 0;
122
+ }
123
+ for (let i = 0; i < left.length; i++) {
124
+ const delta = (left[i] ?? 0) - (right[i] ?? 0);
125
+ if (delta !== 0)
126
+ return delta;
127
+ }
128
+ return 0;
129
+ }
130
+ /**
131
+ * `[major, minor, patch, betaRank]`, where a release ranks above every beta
132
+ * of the same version.
133
+ */
134
+ function parseFirefoxVersionOrder(version) {
135
+ const match = /^(\d+)\.(\d+)(?:\.(\d+))?(?:b(\d+))?(?:esr)?$/i.exec(version.trim());
136
+ if (!match)
137
+ return undefined;
138
+ const beta = match[4];
139
+ return [
140
+ Number(match[1]),
141
+ Number(match[2]),
142
+ Number(match[3] ?? 0),
143
+ beta === undefined ? Number.MAX_SAFE_INTEGER : Number(beta),
144
+ ];
145
+ }
103
146
  /**
104
147
  * Validates a release-candidate build directory name.
105
148
  * Accepts formats like "build1", "build2", "build12" (no leading zero).
@@ -59,6 +59,19 @@ tool at
59
59
  `/Applications/Xcode.app/Contents/Applications/Icon Composer.app/Contents/Executables/ictool`,
60
60
  plus `actool` via `xcrun`, `sips`, and `iconutil`.
61
61
 
62
+ On macOS, `fireforge doctor` also checks that the bootstrapped clang
63
+ (`~/.mozbuild/clang`, or under `MOZBUILD_STATE_PATH`) can link against the
64
+ SDK the build will use. That is the SDK `engine/mozconfig` selects with
65
+ `--with-macos-sdk`, resolved by sourcing the mozconfig the way mach does,
66
+ or `xcrun --show-sdk-path` when it names none. The check links a trivial C
67
+ program through the toolchain's lld in a temp directory. When that fails, it
68
+ reports a warning with clang's first error and lists the SDKs installed
69
+ under the mozbuild state directory, the Command Line Tools and the active
70
+ Xcode. An Xcode update can ship an SDK the pinned toolchain cannot read, and
71
+ `mach configure` then dies at its linker probe with nothing but `config.log`
72
+ saying why. The check does nothing on other platforms and when no clang has
73
+ been bootstrapped.
74
+
62
75
  ## `patchLint`
63
76
 
64
77
  Per-patch lint type-checks plain Firefox JS against a built-in
@@ -84,6 +97,28 @@ Firefox-globals shim that tracks upstream WebIDL additions per release.
84
97
  project's, then `npx --no-install`. Left at `'off'`, formatting is out of
85
98
  scope for the per-patch tier, and no other tier checks `.sys.mjs`
86
99
  formatting either.
100
+ - `patchLint.forwardRegistration` (`'support-files'` or `'extended'`,
101
+ default `'support-files'`) picks which carriers the `forward-registration`
102
+ queue rule checks for a line naming a file that only a later patch
103
+ creates. The default checks test-manifest `support-files`. `'extended'`
104
+ also checks:
105
+ - jar.mn pair lines (the source in parentheses, relative to the jar.mn);
106
+ - moz.build path tokens (a quoted string with a `.` or `/`, relative to
107
+ the moz.build);
108
+ - test-manifest section headers and `head`;
109
+ - `customElements.js` `chrome://<pkg>/content/…` URLs, resolved through
110
+ the jar.mn lines the same queue adds.
111
+
112
+ A line whose target cannot be resolved without the build system is left
113
+ alone: a `%` or `@AB_CD@` substitution, `**`, `!`, a path that leaves the
114
+ tree, or a chrome URL no queue jar.mn line packages. Each finding prints
115
+ the same paste-and-run `patch staged-dependency --add --kind registration`
116
+ command as the `support-files` arm. The setting is opt-in because a queue
117
+ that predates it can carry many such edges, and a new error would red a
118
+ `--max-warnings 0` gate on install. The same census is on the public API as
119
+ `findForwardRegistrations(patchesDir, { scope })`, which defaults to
120
+ `'extended'`, for a consumer that keeps its own ratchet over these edges.
121
+
87
122
  - `patchLint.fileSizeThresholds` tunes the `file-too-large` line counts per
88
123
  file class:
89
124
 
@@ -105,6 +140,27 @@ Firefox-globals shim that tracks upstream WebIDL additions per release.
105
140
  is a hard failure rather than a soft limit, which is what these thresholds
106
141
  exist to let you move.
107
142
 
143
+ ## `typecheck`
144
+
145
+ `typecheck.projects` lists the jsconfig files `fireforge typecheck` checks
146
+ as whole projects. `typecheck.extraShim` and `typecheck.projectOverrides`
147
+ add declarations to the Firefox-globals shim, for every project or for one.
148
+ When `patchLint.checkJs` is on, the same command also runs the per-patch
149
+ checkJs pass that `export` enforces, so a green `typecheck` means
150
+ export-clean types.
151
+
152
+ Each project keeps incremental build info in `.fireforge/typecheck/`, one
153
+ file per project. A warm run re-checks only the files whose content changed
154
+ since the last run. The file name carries a key over everything outside the
155
+ program's own files that can change a verdict: the composed shim, the
156
+ TypeScript version, the compiler options, `typecheck.undefinedIdentifiers`
157
+ and the FireForge version. A change to any of them selects a new file, so a
158
+ stale verdict is never replayed, and the project's other build-info files are
159
+ deleted. `fireforge typecheck --no-cache` checks every project from scratch
160
+ and neither reads nor writes the build info. A jsconfig's own `incremental`
161
+ and `tsBuildInfoFile` are still ignored, so nothing is written under
162
+ `engine/`.
163
+
108
164
  ## `patchPolicy`
109
165
 
110
166
  `patchPolicy.ranges` makes `patch compact` range-aware, so closing ordinal
@@ -225,6 +281,31 @@ non-CI launches print a warning saying so. Exit codes 12 and 13 separate a
225
281
  console regression from a launch failure. See
226
282
  [`exit-codes.md`](exit-codes.md).
227
283
 
284
+ A smoke run starts on a fresh temporary profile by default (since 0.48.0).
285
+ The step exists to prove what the build does on a clean start. The
286
+ developer's `obj-*/tmp/profile-default` carries whatever the last session
287
+ left (a pre-reset database, a restored hand-made applet), and those reds
288
+ say nothing about the tree. FireForge creates the directory under the OS
289
+ temp dir, seeds the two prefs `mach run` writes into profiles it makes
290
+ itself (`browser.shell.checkDefaultBrowser` and
291
+ `browser.aboutConfig.showWarning`), and passes it to Firefox as
292
+ `-profile`. The directory is removed when the run ends. That includes a
293
+ failed run and a SIGINT/SIGTERM, which the signal handler removes after
294
+ the browser tree has shut down. A FireForge that was SIGKILLed cannot clean
295
+ up after itself, so the next `run` removes any temporary profile whose
296
+ owning process is gone.
297
+
298
+ - `--profile <path>` launches with a named profile directory and leaves it
299
+ in place. Under `--smoke-exit` it replaces the fresh default.
300
+ - `--temp-profile` gives a plain `run` the same fresh, removed-afterwards
301
+ profile.
302
+
303
+ A plain `run` without either flag keeps mach's `profile-default`. `mach run`'s
304
+ desktop parser has no `--profile` option (its `--profile/-P` belongs to the
305
+ Android parser), so the profile is always passed as a program parameter.
306
+ With an explicit profile, mach neither writes its `runprefs` from
307
+ `~/.mozbuild/machrc` nor accepts `--setpref`.
308
+
228
309
  The allowlist (`--console-allow-file`) is count-blind by default: a line that
229
310
  matches an entry never counts, however often it fires. A `# max-hits: N`
230
311
  comment directly above an entry (other comments may sit between) caps how
@@ -15,7 +15,7 @@ which error class produces each one.
15
15
  | 3 | `DOWNLOAD_ERROR` | Could not download or extract Firefox source. | `DownloadError` and subclasses |
16
16
  | 4 | `GIT_ERROR` | A git operation failed. | `GitError` and subclasses, except `GitNotFoundError` |
17
17
  | 5 | `BUILD_ERROR` | `mach build` failed, or a test suite went red. | `BuildError` and subclasses, `TestFailureError` |
18
- | 6 | `PATCH_ERROR` | A patch failed to apply, or the patch queue could not be mutated. | `PatchError`, `PatchManifestCorruptError`, `RebaseError` and subclasses |
18
+ | 6 | `PATCH_ERROR` | A patch failed to apply (including a patch `rebase --dry-run` predicts will reject), or the patch queue could not be mutated. | `PatchError`, `PatchManifestCorruptError`, `RebaseError` and subclasses |
19
19
  | 7 | `MISSING_DEPENDENCY` | A required tool (python3, git, tar) was not found. | `PythonNotFoundError`, `GitNotFoundError`, `MachNotFoundError` |
20
20
  | 8 | `INVALID_ARGUMENT` | The flags were wrong. A usage problem, not an environment one. | `InvalidArgumentError`, plus commander usage errors (unknown command or option, missing argument) |
21
21
  | 9 | `FURNACE_ERROR` | A Furnace component operation failed. | `FurnaceError` |
@@ -190,9 +190,10 @@ command, read the decision table at the bottom first.
190
190
  stages and unstages for real. Each of those commands therefore runs its
191
191
  whole body inside `withPrivateGitIndex`, which seeds a temp
192
192
  `GIT_INDEX_FILE` from the repository's own index so that every refresh
193
- lands there and is discarded. `typecheck` additionally forces
194
- `incremental: false` and drops `tsBuildInfoFile`, so no sidecar is written
195
- under `engine/`. The scope fails open, meaning an unresolvable git dir
193
+ lands there and is discarded. `typecheck` additionally ignores a
194
+ jsconfig's own `incremental` and `tsBuildInfoFile`, so no sidecar is
195
+ written under `engine/`. Its incremental build info (0.48.0) lives in the
196
+ project's `.fireforge/typecheck/`, outside the engine. The scope fails open, meaning an unresolvable git dir
196
197
  simply does not install it, and it is not reentrant. This exists so that
197
198
  invariant 8 stays evidential: a concurrent `fireforge test` must not be
198
199
  invalidated by FireForge's own read-only lane. Enforced by
@@ -81,11 +81,23 @@ slugs, and bare slugs.
81
81
  `lint --per-patch --max-warnings 0` is the warning-clean form for a release
82
82
  gate.
83
83
 
84
+ `lint --per-patch --notices summary` prints one line per check in place of
85
+ every NOTICE row, for example `NOTICE [large-patch-lines]: 180 across 172
86
+ patch(es), 150 of them suppressed by lintIgnore (see --report)`. On a green
87
+ queue those rows are reviewed waivers and under-threshold measurements, and
88
+ there can be hundreds of them. The default, `--notices full`, prints them
89
+ as before. Errors and warnings always print in full, and the `--report`
90
+ JSON is the same in both modes.
91
+
84
92
  `lint --per-patch --report <path>` writes a machine-readable JSON report with
85
93
  each patch's line count, tier, active size thresholds, issues, and issues
86
94
  suppressed by `lintIgnore`. The size metrics (`countNonBinaryDiffLines`,
87
95
  `resolvePatchSizeTier`, `getPatchSizeThresholds`) are also exported on the
88
- programmatic API.
96
+ programmatic API. So is `filesMeasuredByFileTooLarge(patchText)`, which
97
+ returns the files `file-too-large` measures in a patch: the JS files it
98
+ creates, and never a modified file or a new file of another kind. The rule
99
+ selects its files through that same function, so an audit that uses it
100
+ cannot drift from the rule.
89
101
 
90
102
  Per-patch lint type-checks plain Firefox JS against a built-in
91
103
  Firefox-globals shim that tracks upstream WebIDL additions per release. See
@@ -116,6 +128,28 @@ npx fireforge download --force
116
128
  npx fireforge rebase
117
129
  ```
118
130
 
131
+ `download --force` is the destructive step of the three, not `rebase`. It
132
+ replaces `engine/` wholesale, so applied patches, unexported edits and
133
+ commits made in `engine/` are gone once the new tree is active. When the
134
+ engine it is about to replace has changed paths against HEAD, or HEAD has
135
+ moved off the recorded base commit, it names the loss (how many changed
136
+ paths match the patch queue and how many do not) and asks first. A
137
+ non-interactive run refuses unless `--yes` is given. A clean engine is
138
+ replaced without a prompt.
139
+
140
+ `npx fireforge rebase --dry-run` replays the queue onto the engine's HEAD
141
+ without touching `engine/`. It seeds a private index from HEAD and applies
142
+ each patch there with `git apply --cached`, in queue order, walking the same
143
+ context-reduction ladder as the real run (exact, then `-C2`, `-C1`, `-C0`,
144
+ bounded by `--max-fuzz`). Each patch is reported as `clean`, `reduced
145
+ context` (with the step it needed) or `reject` (with the files git named).
146
+ It exits 6 when anything rejects. A reject after an earlier one is marked
147
+ as possibly cascading from it, since the replay continues without the
148
+ rejected patch. Run it after the download, when HEAD is the new source. The
149
+ header names the oldest patch stamp as "from", compared as versions rather
150
+ than strings, prints the spread when the queue carries several stamps, and
151
+ warns when `engine/` is not yet on the pinned version.
152
+
119
153
  If a patch fails, fix the reject inside `engine/`, then run
120
154
  `npx fireforge rebase --continue`. A rebase leaves the engine mutated on
121
155
  purpose, so its apply-and-persist pairs are held across a signal rather than
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hominis/fireforge",
3
- "version": "0.47.1",
3
+ "version": "0.48.0",
4
4
  "description": "FireForge — a build tool for customizing Firefox",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -75,9 +75,9 @@
75
75
  "eslint-plugin-simple-import-sort": "^14.0.0",
76
76
  "fast-check": "^4.6.0",
77
77
  "husky": "^9.1.7",
78
- "knip": "6.35.1",
78
+ "knip": "6.37.0",
79
79
  "lint-staged": "^17.0.4",
80
- "prettier": "3.9.6",
80
+ "prettier": "3.9.8",
81
81
  "tsx": "^4.7.0",
82
82
  "typescript": "~6.0.0",
83
83
  "typescript-eslint": "^8.0.0",
@@ -108,5 +108,9 @@
108
108
  "publishConfig": {
109
109
  "access": "public",
110
110
  "provenance": true
111
+ },
112
+ "allowScripts": {
113
+ "esbuild@0.28.1": true,
114
+ "fsevents@2.3.3": true
111
115
  }
112
116
  }