@hominis/fireforge 0.15.8 → 0.16.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 (44) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +16 -3
  3. package/dist/src/cli.d.ts +4 -1
  4. package/dist/src/cli.js +6 -3
  5. package/dist/src/commands/download.js +9 -0
  6. package/dist/src/commands/export-all.js +46 -0
  7. package/dist/src/commands/export-shared.d.ts +6 -1
  8. package/dist/src/commands/export-shared.js +7 -2
  9. package/dist/src/commands/export.js +10 -1
  10. package/dist/src/commands/furnace/diff.js +22 -2
  11. package/dist/src/commands/furnace/override.js +35 -12
  12. package/dist/src/commands/furnace/preview.js +33 -1
  13. package/dist/src/commands/furnace/rename.js +14 -3
  14. package/dist/src/commands/lint.d.ts +20 -0
  15. package/dist/src/commands/lint.js +167 -45
  16. package/dist/src/commands/package.js +16 -5
  17. package/dist/src/commands/re-export-files.js +6 -2
  18. package/dist/src/commands/re-export.js +62 -4
  19. package/dist/src/commands/register.js +2 -18
  20. package/dist/src/commands/run.js +23 -2
  21. package/dist/src/commands/status.js +25 -3
  22. package/dist/src/commands/test.js +6 -24
  23. package/dist/src/commands/token.js +14 -1
  24. package/dist/src/commands/watch.js +14 -2
  25. package/dist/src/core/branding.d.ts +23 -0
  26. package/dist/src/core/branding.js +39 -0
  27. package/dist/src/core/browser-wire.js +68 -23
  28. package/dist/src/core/mach-build-artifacts.d.ts +41 -0
  29. package/dist/src/core/mach-build-artifacts.js +70 -0
  30. package/dist/src/core/mach-error-hints.js +15 -0
  31. package/dist/src/core/mach-mozconfig.d.ts +25 -0
  32. package/dist/src/core/mach-mozconfig.js +66 -0
  33. package/dist/src/core/mach.d.ts +12 -1
  34. package/dist/src/core/mach.js +14 -1
  35. package/dist/src/core/manifest-rules.js +22 -1
  36. package/dist/src/core/patch-lint.d.ts +6 -1
  37. package/dist/src/core/patch-lint.js +14 -1
  38. package/dist/src/types/commands/options.d.ts +10 -0
  39. package/dist/src/types/commands/patches.d.ts +22 -0
  40. package/dist/src/utils/fs.d.ts +12 -0
  41. package/dist/src/utils/fs.js +12 -0
  42. package/dist/src/utils/paths.d.ts +19 -0
  43. package/dist/src/utils/paths.js +33 -0
  44. package/package.json +1 -1
@@ -16,7 +16,15 @@ export function getRules(binaryName) {
16
16
  extractArgs: (m) => [m[1] ?? ''],
17
17
  },
18
18
  {
19
- pattern: /^browser\/base\/content\/(.+\.(?:js|mjs|xhtml|css))$/,
19
+ // `.inc.xhtml` fragments under browser/base/content/ are deliberately
20
+ // excluded: they are consumed via `#include` from a registered chrome
21
+ // document (typically browser.xhtml) and do not get their own
22
+ // packaged chrome URI. Before this carve-out, `status` flagged every
23
+ // wired fragment as "potentially unregistered" and `register --dry-run`
24
+ // proposed a bogus jar.mn entry. The lookahead blocks the match so
25
+ // `getUnregistrableAdvice` gets a chance to emit the correct
26
+ // guidance for the `.inc.xhtml` case.
27
+ pattern: /^browser\/base\/content\/(?!.+\.inc\.xhtml$)(.+\.(?:js|mjs|xhtml|css))$/,
20
28
  isRegistered: (engineDir, fileName) => isBrowserContentRegistered(engineDir, fileName),
21
29
  register: (engineDir, after, dryRun, fileName) => registerBrowserContent(engineDir, fileName, after, undefined, dryRun),
22
30
  extractArgs: (m) => [m[1] ?? ''],
@@ -98,6 +106,19 @@ function getUnregistrableAdvice(filePath) {
98
106
  if (filePath.endsWith('.ftl')) {
99
107
  return "FTL locale files are auto-discovered via jar.mn glob patterns and don't need manual registration.";
100
108
  }
109
+ // `.inc.xhtml` fragments live under browser/base/content/ but are
110
+ // consumed via `#include` from a registered chrome document (browser.xhtml
111
+ // by default; a fork's custom top-level doc when `wire --dom-target` is
112
+ // set). The preprocessor resolves the include at packaging time, so the
113
+ // fragment never needs its own chrome URI entry in jar.mn. Give the
114
+ // operator the actionable `wire` path instead of letting the generic
115
+ // "unknown file pattern" message above fire.
116
+ if (/^browser\/base\/content\/.+\.inc\.xhtml$/.test(filePath)) {
117
+ return ('`.inc.xhtml` fragments are consumed via `#include` from a registered chrome document ' +
118
+ '(e.g. browser.xhtml). They do not need an independent jar.mn entry — run ' +
119
+ '"fireforge wire <name> --dom <path>" to insert the #include, or add the directive manually ' +
120
+ 'in the top-level chrome document.');
121
+ }
101
122
  const testMatch = filePath.match(/^browser\/base\/content\/test\/([^/]+)\/(?!browser\.toml$).+$/);
102
123
  if (testMatch) {
103
124
  const dir = testMatch[1];
@@ -89,6 +89,11 @@ export declare function lintModifiedFileHeaders(repoDir: string, affectedFiles:
89
89
  * @param diffContent - Raw unified diff string
90
90
  * @param config - Project configuration
91
91
  * @param patchQueueCtx - Optional cross-patch context for ownership resolution
92
+ * @param ignoreChecks - Optional set of per-patch `check` IDs to drop from the
93
+ * returned issues. Threaded from `PatchMetadata.lintIgnore` so a patch that
94
+ * is advisory-noisy by nature (a cohesive branding bundle, auto-generated
95
+ * manifest, etc.) can opt out of a specific rule without reaching for the
96
+ * blunt `--skip-lint` hammer. Not mutated by this function.
92
97
  * @returns Array of all lint issues found
93
98
  */
94
- export declare function lintExportedPatch(repoDir: string, affectedFiles: string[], diffContent: string, config: FireForgeConfig, patchQueueCtx?: import('./patch-lint-cross.js').PatchQueueContext): Promise<PatchLintIssue[]>;
99
+ export declare function lintExportedPatch(repoDir: string, affectedFiles: string[], diffContent: string, config: FireForgeConfig, patchQueueCtx?: import('./patch-lint-cross.js').PatchQueueContext, ignoreChecks?: ReadonlySet<string>): Promise<PatchLintIssue[]>;
@@ -455,9 +455,14 @@ export async function lintModifiedFileHeaders(repoDir, affectedFiles, newFiles)
455
455
  * @param diffContent - Raw unified diff string
456
456
  * @param config - Project configuration
457
457
  * @param patchQueueCtx - Optional cross-patch context for ownership resolution
458
+ * @param ignoreChecks - Optional set of per-patch `check` IDs to drop from the
459
+ * returned issues. Threaded from `PatchMetadata.lintIgnore` so a patch that
460
+ * is advisory-noisy by nature (a cohesive branding bundle, auto-generated
461
+ * manifest, etc.) can opt out of a specific rule without reaching for the
462
+ * blunt `--skip-lint` hammer. Not mutated by this function.
458
463
  * @returns Array of all lint issues found
459
464
  */
460
- export async function lintExportedPatch(repoDir, affectedFiles, diffContent, config, patchQueueCtx) {
465
+ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, config, patchQueueCtx, ignoreChecks) {
461
466
  const newFiles = detectNewFilesInDiff(diffContent);
462
467
  const { textLines: lineCount } = countNonBinaryDiffLines(diffContent);
463
468
  const patchOwnedFiles = resolvePatchOwnedSysMjs(newFiles, patchQueueCtx);
@@ -482,6 +487,14 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
482
487
  const checkJsIssues = await runCheckJs(repoDir, patchOwnedFiles);
483
488
  issues.push(...checkJsIssues);
484
489
  }
490
+ // Filter out ignored checks last so every rule still runs (keeps the
491
+ // implementation uniform) but suppressed rules do not surface. We do not
492
+ // reclassify severities — an ignored error simply drops, mirroring how
493
+ // inline `fireforge-ignore: <check>` markers work in the CSS and
494
+ // forward-import rules.
495
+ if (ignoreChecks && ignoreChecks.size > 0) {
496
+ return issues.filter((issue) => !ignoreChecks.has(issue.check));
497
+ }
485
498
  return issues;
486
499
  }
487
500
  //# sourceMappingURL=patch-lint.js.map
@@ -155,6 +155,16 @@ export interface ReExportOptions {
155
155
  yes?: boolean;
156
156
  /** Bypass cross-patch lint refusal on projected shrink state */
157
157
  forceUnsafe?: boolean;
158
+ /**
159
+ * After every selected patch re-exports cleanly, stamp each re-exported
160
+ * patch's `sourceEsrVersion` in `patches.json` to the current
161
+ * `firefox.version` from `fireforge.json`. Opt-in because the default
162
+ * contract of `re-export` is "refresh the patch body and filesAffected";
163
+ * version stamping is normally a `rebase` responsibility. Use this when
164
+ * re-exporting after a manual Firefox bump that did not go through
165
+ * `rebase`.
166
+ */
167
+ stamp?: boolean;
158
168
  }
159
169
  /**
160
170
  * Options for the rebase command.
@@ -51,6 +51,28 @@ export interface PatchMetadata {
51
51
  sourceEsrVersion: string;
52
52
  /** Array of file paths affected by this patch */
53
53
  filesAffected: string[];
54
+ /**
55
+ * Optional per-patch list of lint check IDs to suppress when this patch
56
+ * is the target of `export`, `export-all`, or `re-export`. Exists for
57
+ * the class of patch that is advisory-noisy by nature — a cohesive
58
+ * branding bundle, a localised-resource pack, an auto-generated
59
+ * manifest — where the generic `large-patch-lines` / `large-patch-files`
60
+ * thresholds do not apply but `--skip-lint` (which silences *all*
61
+ * errors, not just the one that does not apply) is too coarse a hammer.
62
+ *
63
+ * Previously the only escape hatches were `--skip-lint` (blunt) or the
64
+ * full `rebase` flow (refreshes the same patch through a code path that
65
+ * silently skips `runPatchLint` — an asymmetry that forced operators
66
+ * through a multi-minute Firefox source re-download just to refresh
67
+ * one patch body).
68
+ *
69
+ * Values are free-form check IDs (e.g. `"large-patch-lines"`,
70
+ * `"large-patch-files"`). Checks not listed here still run normally.
71
+ * An entry for an unknown check ID is a no-op — the patch metadata
72
+ * documents the *intent* to suppress even if the check is later
73
+ * renamed or removed.
74
+ */
75
+ lintIgnore?: string[];
54
76
  }
55
77
  /**
56
78
  * Schema for patches/patches.json file.
@@ -78,6 +78,18 @@ export declare function writeFileAtomic(path: string, content: string | Buffer):
78
78
  * @param dest - Destination directory path
79
79
  */
80
80
  export declare function copyDir(src: string, dest: string): Promise<void>;
81
+ /**
82
+ * Matches the atomic-temp-file shape emitted by `createAtomicTempPath`
83
+ * anywhere in a normalised (forward-slash) path. The `.fireforge-tmp-`
84
+ * marker plus a PID/UUID tail is unique to our own rename-based atomic
85
+ * writes, so callers (notably `status`) can filter these mid-flight
86
+ * artefacts out of their listings without racing the rename.
87
+ *
88
+ * Intentionally anchored so a legitimately-named backup file like
89
+ * `.notes.fireforge-tmp-backup` (no PID+UUID continuation) is NOT treated
90
+ * as one of our temps. The full shape is `.<filename>.fireforge-tmp-<pid>-<uuid>`.
91
+ */
92
+ export declare const FIREFORGE_TMP_PATH_PATTERN: RegExp;
81
93
  /**
82
94
  * Checks available disk space at a path and warns via the provided
83
95
  * callback when it falls below `minBytes`.
@@ -180,6 +180,18 @@ export async function copyDir(src, dest) {
180
180
  }
181
181
  }
182
182
  }
183
+ /**
184
+ * Matches the atomic-temp-file shape emitted by `createAtomicTempPath`
185
+ * anywhere in a normalised (forward-slash) path. The `.fireforge-tmp-`
186
+ * marker plus a PID/UUID tail is unique to our own rename-based atomic
187
+ * writes, so callers (notably `status`) can filter these mid-flight
188
+ * artefacts out of their listings without racing the rename.
189
+ *
190
+ * Intentionally anchored so a legitimately-named backup file like
191
+ * `.notes.fireforge-tmp-backup` (no PID+UUID continuation) is NOT treated
192
+ * as one of our temps. The full shape is `.<filename>.fireforge-tmp-<pid>-<uuid>`.
193
+ */
194
+ export const FIREFORGE_TMP_PATH_PATTERN = /(^|\/)\.[^/]+\.fireforge-tmp-\d+-[0-9a-f-]{36}$/i;
183
195
  /**
184
196
  * Generates a unique temp file path for atomic writes.
185
197
  *
@@ -1,5 +1,24 @@
1
1
  /** Converts Windows path separators to forward slashes for stable comparisons. */
2
2
  export declare function normalizePathSlashes(path: string): string;
3
+ /**
4
+ * Strips a leading `engine/` (or `engine\\`) segment from a user-supplied
5
+ * path so the same command invocation accepts both repo-root-relative paths
6
+ * (`engine/browser/base/content/foo.js`) and engine-relative paths
7
+ * (`browser/base/content/foo.js`). The match is case-insensitive because
8
+ * default macOS and Windows filesystems treat `Engine/` and `engine/` as
9
+ * the same directory; a literal lowercase-only check previously left `mach`
10
+ * / the manifest writers resolving against a wrongly-cased prefix. Leading
11
+ * whitespace is ignored so tab-completed inputs don't slip past the strip.
12
+ *
13
+ * The return value is trimmed of the same leading whitespace when the
14
+ * prefix matched, and otherwise passed through verbatim — callers that
15
+ * care about internal whitespace can trim on their side.
16
+ *
17
+ * @param filePath Path as provided by the user
18
+ * @returns Path relative to the engine directory (or the original when the
19
+ * prefix was absent)
20
+ */
21
+ export declare function stripEnginePrefix(filePath: string): string;
3
22
  /** Checks whether a path is explicitly absolute on either POSIX or Windows. */
4
23
  export declare function isExplicitAbsolutePath(path: string): boolean;
5
24
  /** Resolves a candidate path and returns whether it stays within the given root. */
@@ -2,10 +2,43 @@
2
2
  import { isAbsolute, relative, resolve } from 'node:path';
3
3
  const WINDOWS_ABSOLUTE_PATH = /^[a-zA-Z]:[\\/]/;
4
4
  const RELATIVE_PATH_ROOT = resolve('/__fireforge_path_root__');
5
+ /**
6
+ * Matches a leading `engine/` or `engine\\` segment (case-insensitive,
7
+ * tolerates leading whitespace). Shared between `register`, `test`, `lint`,
8
+ * and `export` so every command that takes an engine-relative path accepts
9
+ * both the repo-root form (`engine/browser/...`) and the engine-relative
10
+ * form (`browser/...`) without diverging.
11
+ */
12
+ const ENGINE_PREFIX_PATTERN = /^\s*engine[/\\]/i;
5
13
  /** Converts Windows path separators to forward slashes for stable comparisons. */
6
14
  export function normalizePathSlashes(path) {
7
15
  return path.replace(/\\/g, '/');
8
16
  }
17
+ /**
18
+ * Strips a leading `engine/` (or `engine\\`) segment from a user-supplied
19
+ * path so the same command invocation accepts both repo-root-relative paths
20
+ * (`engine/browser/base/content/foo.js`) and engine-relative paths
21
+ * (`browser/base/content/foo.js`). The match is case-insensitive because
22
+ * default macOS and Windows filesystems treat `Engine/` and `engine/` as
23
+ * the same directory; a literal lowercase-only check previously left `mach`
24
+ * / the manifest writers resolving against a wrongly-cased prefix. Leading
25
+ * whitespace is ignored so tab-completed inputs don't slip past the strip.
26
+ *
27
+ * The return value is trimmed of the same leading whitespace when the
28
+ * prefix matched, and otherwise passed through verbatim — callers that
29
+ * care about internal whitespace can trim on their side.
30
+ *
31
+ * @param filePath Path as provided by the user
32
+ * @returns Path relative to the engine directory (or the original when the
33
+ * prefix was absent)
34
+ */
35
+ export function stripEnginePrefix(filePath) {
36
+ const match = ENGINE_PREFIX_PATTERN.exec(filePath);
37
+ if (match) {
38
+ return filePath.slice(match[0].length);
39
+ }
40
+ return filePath;
41
+ }
9
42
  /** Checks whether a path is explicitly absolute on either POSIX or Windows. */
10
43
  export function isExplicitAbsolutePath(path) {
11
44
  return isAbsolute(path) || WINDOWS_ABSOLUTE_PATH.test(path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hominis/fireforge",
3
- "version": "0.15.8",
3
+ "version": "0.16.0",
4
4
  "description": "FireForge — a build tool for customizing Firefox",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",