@hominis/fireforge 0.26.0 → 0.27.1

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 (41) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/dist/src/commands/doctor/post-rebase-audit.d.ts +2 -0
  4. package/dist/src/commands/doctor/post-rebase-audit.js +86 -0
  5. package/dist/src/commands/doctor.js +3 -0
  6. package/dist/src/commands/download.js +4 -1
  7. package/dist/src/commands/manifest.js +2 -0
  8. package/dist/src/commands/re-export-files.js +44 -26
  9. package/dist/src/commands/re-export.js +53 -14
  10. package/dist/src/commands/rebase/conflict-summary.d.ts +12 -0
  11. package/dist/src/commands/rebase/conflict-summary.js +38 -0
  12. package/dist/src/commands/rebase/patch-loop.js +24 -6
  13. package/dist/src/commands/setup-support.js +6 -2
  14. package/dist/src/commands/setup.js +1 -0
  15. package/dist/src/commands/source.d.ts +9 -0
  16. package/dist/src/commands/source.js +92 -0
  17. package/dist/src/commands/verify.js +27 -0
  18. package/dist/src/core/branding.js +54 -7
  19. package/dist/src/core/config-validate.js +1 -1
  20. package/dist/src/core/firefox-extract.d.ts +1 -1
  21. package/dist/src/core/firefox-extract.js +13 -1
  22. package/dist/src/core/firefox.d.ts +2 -1
  23. package/dist/src/core/firefox.js +2 -2
  24. package/dist/src/core/furnace-registration-ast.js +32 -4
  25. package/dist/src/core/furnace-registration-validate.d.ts +7 -0
  26. package/dist/src/core/furnace-registration-validate.js +48 -6
  27. package/dist/src/core/furnace-validate-registration.js +8 -17
  28. package/dist/src/core/git.js +46 -16
  29. package/dist/src/core/patch-artifact-normalize.d.ts +9 -0
  30. package/dist/src/core/patch-artifact-normalize.js +13 -0
  31. package/dist/src/core/patch-export-update.js +2 -1
  32. package/dist/src/core/patch-export.js +3 -2
  33. package/dist/src/core/status-classify.js +19 -4
  34. package/dist/src/types/commands/index.d.ts +1 -1
  35. package/dist/src/types/commands/options.d.ts +22 -1
  36. package/dist/src/types/config.d.ts +1 -1
  37. package/dist/src/utils/elapsed.d.ts +4 -0
  38. package/dist/src/utils/elapsed.js +15 -0
  39. package/dist/src/utils/validation.d.ts +2 -2
  40. package/dist/src/utils/validation.js +5 -5
  41. package/package.json +2 -2
@@ -6,6 +6,7 @@ import { pathExists, readText, removeFile, writeText } from '../utils/fs.js';
6
6
  import { warn } from '../utils/logger.js';
7
7
  import { PATCH_CATEGORIES } from '../utils/validation.js';
8
8
  import { discoverPatches, withPatchDirectoryLock } from './patch-apply.js';
9
+ import { normalizePatchArtifact } from './patch-artifact-normalize.js';
9
10
  import { findAllPatchesForFilesWithDetails, } from './patch-export-coverage.js';
10
11
  import { addPatchToManifest, loadPatchesManifest, PATCHES_MANIFEST, savePatchesManifest, } from './patch-manifest.js';
11
12
  import { allocatePolicyOrder, enforcePatchPolicy } from './patch-policy.js';
@@ -95,7 +96,7 @@ export async function commitExportedPatch(input) {
95
96
  }
96
97
  }
97
98
  try {
98
- await writeText(patchPath, input.diff);
99
+ await writeText(patchPath, normalizePatchArtifact(input.diff));
99
100
  await addPatchToManifest(input.patchesDir, plan.metadata, plan.supersededPatches.map((p) => p.filename));
100
101
  for (const oldPatch of plan.supersededPatches) {
101
102
  await removeFile(oldPatch.path);
@@ -195,7 +196,7 @@ export async function findExistingPatchForFile(patchesDir, filePath) {
195
196
  * @param newContent - New patch content
196
197
  */
197
198
  export async function updatePatch(patchPath, newContent) {
198
- await writeText(patchPath, newContent);
199
+ await writeText(patchPath, normalizePatchArtifact(newContent));
199
200
  }
200
201
  /**
201
202
  * Deletes a patch file and removes it from the manifest.
@@ -25,6 +25,14 @@ function getPrimaryStatusCode(status) {
25
25
  }
26
26
  return status;
27
27
  }
28
+ function isGeneratedBrandingPath(file, binaryName) {
29
+ const normalized = file.replace(/\\/g, '/');
30
+ const brandingRoot = `browser/branding/${binaryName}`;
31
+ return (normalized === 'browser/moz.configure' ||
32
+ normalized === `${brandingRoot}/configure.sh` ||
33
+ normalized === `${brandingRoot}/locales/en-US/brand.properties` ||
34
+ normalized === `${brandingRoot}/locales/en-US/brand.ftl`);
35
+ }
28
36
  /**
29
37
  * Classifies files into patch-backed, unmanaged, branding, furnace, or
30
38
  * conflict buckets.
@@ -63,8 +71,17 @@ export async function classifyFiles(files, engineDir, patchesDir, binaryName, fu
63
71
  }
64
72
  const results = [];
65
73
  for (const entry of files) {
66
- // Branding check first
67
- if (isBrandingManagedPath(entry.file, binaryName)) {
74
+ const owners = patchClaims.get(entry.file);
75
+ const primaryCode = getPrimaryStatusCode(entry.status);
76
+ // Branding paths are tool-managed for generated edits, but a brand-new
77
+ // unowned branding asset must not disappear from `status --unmanaged`.
78
+ // The Hominis Firefox 152 side-grade added Assets.car under the active
79
+ // branding tree; classifying every branding path before checking
80
+ // ownership hid that new patch candidate as "branding" even though no
81
+ // patch claimed it yet.
82
+ const isUnownedNewFile = owners === undefined && (primaryCode === '?' || primaryCode === 'A');
83
+ if (isBrandingManagedPath(entry.file, binaryName) &&
84
+ (!isUnownedNewFile || isGeneratedBrandingPath(entry.file, binaryName))) {
68
85
  results.push({ ...entry, classification: 'branding' });
69
86
  continue;
70
87
  }
@@ -82,7 +99,6 @@ export async function classifyFiles(files, engineDir, patchesDir, binaryName, fu
82
99
  continue;
83
100
  }
84
101
  }
85
- const owners = patchClaims.get(entry.file);
86
102
  // Multiple patches claim this file — surface the cross-patch
87
103
  // ownership conflict regardless of whether the current content
88
104
  // matches any single claim. `--ownership` reports the same state
@@ -102,7 +118,6 @@ export async function classifyFiles(files, engineDir, patchesDir, binaryName, fu
102
118
  continue;
103
119
  }
104
120
  // File is claimed by exactly one patch — compare content.
105
- const primaryCode = getPrimaryStatusCode(entry.status);
106
121
  if (primaryCode === 'D') {
107
122
  // Deleted file: patch-backed only if patch expects deletion
108
123
  const expected = await computePatchedContent(patchesDir, engineDir, entry.file);
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Re-exports all command-related types from focused sub-modules.
3
3
  */
4
- export type { BuildOptions, DiscardOptions, DoctorOptions, DownloadOptions, ExportOptions, FurnaceApplyOptions, FurnaceCreateOptions, FurnaceDeployOptions, FurnaceOverrideOptions, FurnacePreviewOptions, FurnaceRefreshOptions, FurnaceRemoveOptions, FurnaceSyncOptions, FurnaceValidateOptions, GlobalOptions, ImportOptions, PackageOptions, PatchCompactOptions, PatchDeleteOptions, PatchLintIgnoreOptions, PatchMoveFilesOptions, PatchRenameOptions, PatchReorderOptions, PatchStagedDependencyOptions, PatchTierOptions, RebaseOptions, ReExportOptions, RegisterOptions, ResetOptions, RunOptions, SetupOptions, StatusOptions, TestOptions, TokenAddOptions, WireOptions, } from './options.js';
4
+ export type { BuildOptions, DiscardOptions, DoctorOptions, DownloadOptions, ExportOptions, FurnaceApplyOptions, FurnaceCreateOptions, FurnaceDeployOptions, FurnaceOverrideOptions, FurnacePreviewOptions, FurnaceRefreshOptions, FurnaceRemoveOptions, FurnaceSyncOptions, FurnaceValidateOptions, GlobalOptions, ImportOptions, PackageOptions, PatchCompactOptions, PatchDeleteOptions, PatchLintIgnoreOptions, PatchMoveFilesOptions, PatchRenameOptions, PatchReorderOptions, PatchStagedDependencyOptions, PatchTierOptions, RebaseOptions, ReExportOptions, RegisterOptions, ResetOptions, RunOptions, SetupOptions, SourceSetOptions, StatusOptions, TestOptions, TokenAddOptions, WireOptions, } from './options.js';
5
5
  export type { ImportSummary, PatchCategory, PatchesManifest, PatchInfo, PatchLintIssue, PatchMetadata, PatchResult, PatchStagedDependencies, PatchStagedForwardImport, } from './patches.js';
6
6
  export type { DoctorCheck, ProjectStatus, TokenCoverageFileEntry, TokenCoverageReport, } from './project.js';
@@ -17,7 +17,7 @@ export interface SetupOptions {
17
17
  binaryName?: string;
18
18
  /** Firefox version to base on */
19
19
  firefoxVersion?: string;
20
- /** Firefox product type (firefox, firefox-esr, firefox-beta) */
20
+ /** Firefox product type (firefox, firefox-esr, firefox-beta, firefox-devedition) */
21
21
  product?: FirefoxProduct;
22
22
  /** Overwrite existing configuration without prompting */
23
23
  force?: boolean;
@@ -31,6 +31,19 @@ export interface DownloadOptions {
31
31
  /** Force re-download, deleting existing engine/ */
32
32
  force?: boolean;
33
33
  }
34
+ /**
35
+ * Options for the source command.
36
+ */
37
+ export interface SourceSetOptions {
38
+ /** Firefox version to set */
39
+ version: string;
40
+ /** Firefox product type */
41
+ product: FirefoxProduct;
42
+ /** Optional pinned SHA-256 for the resolved source archive */
43
+ sha256?: string;
44
+ /** Clear any existing pinned SHA-256 */
45
+ clearSha256?: boolean;
46
+ }
34
47
  /**
35
48
  * Options for the build command.
36
49
  */
@@ -184,6 +197,12 @@ export interface ReExportOptions {
184
197
  skipLint?: boolean;
185
198
  /** Skip confirmation prompt on shrink (required for non-TTY) */
186
199
  yes?: boolean;
200
+ /**
201
+ * Explicitly allow `--files` to remove paths that are currently owned by
202
+ * the patch. Without this acknowledgement, non-dry-run shrinks are refused
203
+ * before the interactive/`--yes` confirmation path.
204
+ */
205
+ allowShrink?: boolean;
187
206
  /** Bypass cross-patch lint refusal on projected shrink state */
188
207
  forceUnsafe?: boolean;
189
208
  /**
@@ -645,6 +664,8 @@ export interface DoctorOptions {
645
664
  * and side-effect-free.
646
665
  */
647
666
  repairFurnace?: boolean;
667
+ /** Run extra post-rebase checks for common Firefox registration surfaces. */
668
+ postRebaseAudit?: boolean;
648
669
  }
649
670
  /**
650
671
  * Global CLI options available to all commands.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Firefox product type for downloads.
3
3
  */
4
- export type FirefoxProduct = 'firefox' | 'firefox-esr' | 'firefox-beta';
4
+ export type FirefoxProduct = 'firefox' | 'firefox-esr' | 'firefox-beta' | 'firefox-devedition';
5
5
  /**
6
6
  * Firefox version configuration.
7
7
  */
@@ -0,0 +1,4 @@
1
+ /** Formats elapsed milliseconds for progress messages. */
2
+ export declare function formatElapsed(ms: number): string;
3
+ /** Returns formatted elapsed time since a start timestamp from Date.now(). */
4
+ export declare function elapsedSince(startedAt: number): string;
@@ -0,0 +1,15 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /** Formats elapsed milliseconds for progress messages. */
3
+ export function formatElapsed(ms) {
4
+ const totalSeconds = Math.max(0, Math.round(ms / 1000));
5
+ const minutes = Math.floor(totalSeconds / 60);
6
+ const seconds = totalSeconds % 60;
7
+ if (minutes === 0)
8
+ return `${seconds}s`;
9
+ return `${minutes}m ${seconds}s`;
10
+ }
11
+ /** Returns formatted elapsed time since a start timestamp from Date.now(). */
12
+ export function elapsedSince(startedAt) {
13
+ return formatElapsed(Date.now() - startedAt);
14
+ }
15
+ //# sourceMappingURL=elapsed.js.map
@@ -71,7 +71,7 @@ export declare function assertObject(value: unknown, name: string): asserts valu
71
71
  export declare function isValidFirefoxVersion(version: string): boolean;
72
72
  /**
73
73
  * Validates a Firefox product string.
74
- * Accepts: firefox, firefox-esr, firefox-beta
74
+ * Accepts: firefox, firefox-esr, firefox-beta, firefox-devedition
75
75
  */
76
76
  export declare function isValidFirefoxProduct(product: string): boolean;
77
77
  /**
@@ -108,7 +108,7 @@ export declare function inferProductFromVersion(version: string): 'firefox' | 'f
108
108
  *
109
109
  * Rules:
110
110
  * - `firefox-esr` requires an ESR version (e.g. "140.9.0esr", "128.0.1esr").
111
- * - `firefox-beta` requires a beta version (e.g. "147.0b1").
111
+ * - `firefox-beta` and `firefox-devedition` require a beta version (e.g. "147.0b1").
112
112
  * - `firefox` (stable) rejects both ESR and beta version strings.
113
113
  *
114
114
  * @returns An error message if incompatible, or undefined if valid.
@@ -106,10 +106,10 @@ export function isValidFirefoxVersion(version) {
106
106
  }
107
107
  /**
108
108
  * Validates a Firefox product string.
109
- * Accepts: firefox, firefox-esr, firefox-beta
109
+ * Accepts: firefox, firefox-esr, firefox-beta, firefox-devedition
110
110
  */
111
111
  export function isValidFirefoxProduct(product) {
112
- return ['firefox', 'firefox-esr', 'firefox-beta'].includes(product);
112
+ return ['firefox', 'firefox-esr', 'firefox-beta', 'firefox-devedition'].includes(product);
113
113
  }
114
114
  /**
115
115
  * Valid project license SPDX identifiers.
@@ -161,7 +161,7 @@ export function inferProductFromVersion(version) {
161
161
  *
162
162
  * Rules:
163
163
  * - `firefox-esr` requires an ESR version (e.g. "140.9.0esr", "128.0.1esr").
164
- * - `firefox-beta` requires a beta version (e.g. "147.0b1").
164
+ * - `firefox-beta` and `firefox-devedition` require a beta version (e.g. "147.0b1").
165
165
  * - `firefox` (stable) rejects both ESR and beta version strings.
166
166
  *
167
167
  * @returns An error message if incompatible, or undefined if valid.
@@ -177,9 +177,9 @@ export function validateFirefoxProductVersionCompatibility(version, product) {
177
177
  }
178
178
  break;
179
179
  case 'firefox-beta':
180
+ case 'firefox-devedition':
180
181
  if (!versionIsBeta) {
181
- return (`Product "firefox-beta" requires a beta version (e.g. "147.0b1"), ` +
182
- `but got "${version}"`);
182
+ return (`Product "${product}" requires a beta version (e.g. "147.0b1"), ` + `but got "${version}"`);
183
183
  }
184
184
  break;
185
185
  case 'firefox':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hominis/fireforge",
3
- "version": "0.26.0",
3
+ "version": "0.27.1",
4
4
  "description": "FireForge — a build tool for customizing Firefox",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -53,7 +53,7 @@
53
53
  "dependencies": {
54
54
  "@clack/prompts": "^1.2.0",
55
55
  "acorn": "^8.14.0",
56
- "commander": "^14.0.0",
56
+ "commander": "^15.0.0",
57
57
  "estree-walker": "^3.0.3",
58
58
  "magic-string": "^0.30.17",
59
59
  "picocolors": "^1.1.0"