@hominis/fireforge 0.39.0 → 0.41.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 (279) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +35 -5
  3. package/dist/bin/fireforge.js +25 -4
  4. package/dist/build-info.json +9 -0
  5. package/dist/src/cli.js +27 -3
  6. package/dist/src/commands/bootstrap.js +5 -1
  7. package/dist/src/commands/discard.js +2 -22
  8. package/dist/src/commands/doctor/post-rebase-audit.js +27 -4
  9. package/dist/src/commands/doctor-check-core.d.ts +20 -0
  10. package/dist/src/commands/doctor-check-core.js +28 -0
  11. package/dist/src/commands/doctor-external-toolchains.js +2 -0
  12. package/dist/src/commands/doctor-furnace-manifest-sync.js +144 -46
  13. package/dist/src/commands/doctor-furnace.js +13 -11
  14. package/dist/src/commands/doctor-orphaned-harness.js +2 -1
  15. package/dist/src/commands/doctor.js +2 -2
  16. package/dist/src/commands/export-all.js +9 -1
  17. package/dist/src/commands/export-flow.js +27 -9
  18. package/dist/src/commands/export-placement-conflicts.d.ts +17 -0
  19. package/dist/src/commands/export-placement-conflicts.js +74 -0
  20. package/dist/src/commands/export-placement-gate.js +1 -1
  21. package/dist/src/commands/export-shared.d.ts +8 -1
  22. package/dist/src/commands/export-shared.js +30 -10
  23. package/dist/src/commands/export.js +2 -2
  24. package/dist/src/commands/furnace/apply.js +4 -3
  25. package/dist/src/commands/furnace/chrome-doc-remove.js +13 -2
  26. package/dist/src/commands/furnace/chrome-doc.js +13 -2
  27. package/dist/src/commands/furnace/create-browser-test.d.ts +5 -0
  28. package/dist/src/commands/furnace/create-browser-test.js +5 -0
  29. package/dist/src/commands/furnace/create.js +2 -2
  30. package/dist/src/commands/furnace/diff.js +2 -1
  31. package/dist/src/commands/furnace/init.js +4 -6
  32. package/dist/src/commands/furnace/override.js +2 -2
  33. package/dist/src/commands/furnace/remove-mutations.d.ts +40 -0
  34. package/dist/src/commands/furnace/remove-mutations.js +171 -0
  35. package/dist/src/commands/furnace/remove.js +21 -128
  36. package/dist/src/commands/furnace/rename-browser-test.js +54 -5
  37. package/dist/src/commands/furnace/scan.js +27 -36
  38. package/dist/src/commands/furnace/status.js +30 -28
  39. package/dist/src/commands/furnace/validate.js +25 -18
  40. package/dist/src/commands/import.js +6 -20
  41. package/dist/src/commands/lint-per-patch.js +73 -51
  42. package/dist/src/commands/lint-per-run-checkjs.d.ts +64 -0
  43. package/dist/src/commands/lint-per-run-checkjs.js +102 -0
  44. package/dist/src/commands/lint-report.d.ts +21 -0
  45. package/dist/src/commands/lint-report.js +62 -0
  46. package/dist/src/commands/lint.js +26 -22
  47. package/dist/src/commands/manifest.js +3 -1
  48. package/dist/src/commands/package.js +9 -16
  49. package/dist/src/commands/patch/compact.js +5 -5
  50. package/dist/src/commands/patch/delete.js +5 -5
  51. package/dist/src/commands/patch/lint-ignore.js +3 -2
  52. package/dist/src/commands/patch/move-files-create.js +22 -4
  53. package/dist/src/commands/patch/move-files-into.js +14 -10
  54. package/dist/src/commands/patch/move-files.js +6 -5
  55. package/dist/src/commands/patch/rename.d.ts +2 -2
  56. package/dist/src/commands/patch/rename.js +152 -51
  57. package/dist/src/commands/patch/reorder.d.ts +9 -0
  58. package/dist/src/commands/patch/reorder.js +14 -10
  59. package/dist/src/commands/patch/split-plan.d.ts +13 -6
  60. package/dist/src/commands/patch/split-plan.js +22 -12
  61. package/dist/src/commands/patch/split.d.ts +1 -1
  62. package/dist/src/commands/patch/split.js +23 -9
  63. package/dist/src/commands/patch/staged-dependency-validate.d.ts +15 -0
  64. package/dist/src/commands/patch/staged-dependency-validate.js +49 -0
  65. package/dist/src/commands/patch/staged-dependency.js +112 -14
  66. package/dist/src/commands/re-export-adjacent.d.ts +34 -0
  67. package/dist/src/commands/re-export-adjacent.js +105 -0
  68. package/dist/src/commands/re-export-bulk-scan.d.ts +13 -0
  69. package/dist/src/commands/re-export-bulk-scan.js +142 -2
  70. package/dist/src/commands/re-export-drift.d.ts +74 -0
  71. package/dist/src/commands/re-export-drift.js +234 -0
  72. package/dist/src/commands/re-export-files.js +5 -3
  73. package/dist/src/commands/re-export-lint.d.ts +97 -0
  74. package/dist/src/commands/re-export-lint.js +210 -0
  75. package/dist/src/commands/re-export-options.js +11 -0
  76. package/dist/src/commands/re-export-register.d.ts +18 -0
  77. package/dist/src/commands/re-export-register.js +61 -0
  78. package/dist/src/commands/re-export-scan.js +13 -7
  79. package/dist/src/commands/re-export-single.d.ts +20 -0
  80. package/dist/src/commands/re-export-single.js +261 -0
  81. package/dist/src/commands/re-export.d.ts +0 -4
  82. package/dist/src/commands/re-export.js +123 -289
  83. package/dist/src/commands/rebase/abort.js +13 -4
  84. package/dist/src/commands/rebase/continue.js +11 -4
  85. package/dist/src/commands/rebase/index.js +19 -29
  86. package/dist/src/commands/rebase/patch-loop.js +12 -0
  87. package/dist/src/commands/reset.js +4 -23
  88. package/dist/src/commands/resolve.js +2 -9
  89. package/dist/src/commands/run.js +29 -23
  90. package/dist/src/commands/source.js +3 -9
  91. package/dist/src/commands/status-check.d.ts +48 -0
  92. package/dist/src/commands/status-check.js +106 -0
  93. package/dist/src/commands/status-json.d.ts +31 -0
  94. package/dist/src/commands/status-json.js +76 -0
  95. package/dist/src/commands/status-output.d.ts +1 -0
  96. package/dist/src/commands/status-output.js +10 -1
  97. package/dist/src/commands/status-ownership.d.ts +52 -0
  98. package/dist/src/commands/status-ownership.js +57 -0
  99. package/dist/src/commands/status.js +190 -170
  100. package/dist/src/commands/test-build-phase.d.ts +19 -0
  101. package/dist/src/commands/test-build-phase.js +168 -0
  102. package/dist/src/commands/test-diagnose.js +30 -2
  103. package/dist/src/commands/test-modes.d.ts +9 -1
  104. package/dist/src/commands/test-modes.js +46 -13
  105. package/dist/src/commands/test-register.js +59 -27
  106. package/dist/src/commands/test-run.d.ts +29 -3
  107. package/dist/src/commands/test-run.js +59 -7
  108. package/dist/src/commands/test-verdict.d.ts +32 -0
  109. package/dist/src/commands/test-verdict.js +48 -0
  110. package/dist/src/commands/test.d.ts +9 -0
  111. package/dist/src/commands/test.js +100 -123
  112. package/dist/src/commands/token-coverage.js +4 -9
  113. package/dist/src/commands/token.js +4 -1
  114. package/dist/src/commands/tree.d.ts +19 -0
  115. package/dist/src/commands/tree.js +320 -0
  116. package/dist/src/commands/verify.js +2 -2
  117. package/dist/src/commands/watch.js +22 -21
  118. package/dist/src/commands/wire.js +1 -1
  119. package/dist/src/core/ast-utils.d.ts +11 -0
  120. package/dist/src/core/ast-utils.js +11 -0
  121. package/dist/src/core/build-audit-platform.js +6 -1
  122. package/dist/src/core/build-audit-registration.js +2 -0
  123. package/dist/src/core/build-audit-resolve.d.ts +13 -0
  124. package/dist/src/core/build-audit-resolve.js +34 -13
  125. package/dist/src/core/build-audit.js +4 -20
  126. package/dist/src/core/build-baseline-types.d.ts +29 -8
  127. package/dist/src/core/build-baseline-types.js +2 -1
  128. package/dist/src/core/build-baseline.d.ts +10 -2
  129. package/dist/src/core/build-baseline.js +39 -8
  130. package/dist/src/core/build-prepare.d.ts +4 -0
  131. package/dist/src/core/build-prepare.js +7 -1
  132. package/dist/src/core/config-paths.d.ts +1 -1
  133. package/dist/src/core/config-paths.js +2 -0
  134. package/dist/src/core/config-validate-test-toolchains.js +3 -2
  135. package/dist/src/core/config-validate.js +45 -30
  136. package/dist/src/core/config.js +16 -3
  137. package/dist/src/core/coverage-extend.d.ts +89 -0
  138. package/dist/src/core/coverage-extend.js +188 -0
  139. package/dist/src/core/destructive.js +3 -1
  140. package/dist/src/core/discard-baseline.js +18 -4
  141. package/dist/src/core/engine-precondition.d.ts +35 -0
  142. package/dist/src/core/engine-precondition.js +50 -0
  143. package/dist/src/core/engine-session-lock.d.ts +38 -1
  144. package/dist/src/core/engine-session-lock.js +78 -4
  145. package/dist/src/core/file-lock.d.ts +7 -0
  146. package/dist/src/core/file-lock.js +20 -30
  147. package/dist/src/core/firefox.js +5 -2
  148. package/dist/src/core/furnace-apply-helpers.d.ts +12 -1
  149. package/dist/src/core/furnace-apply-helpers.js +12 -3
  150. package/dist/src/core/furnace-apply-output.js +9 -0
  151. package/dist/src/core/furnace-apply-overwrite-warn.d.ts +63 -0
  152. package/dist/src/core/furnace-apply-overwrite-warn.js +123 -0
  153. package/dist/src/core/furnace-apply-undeploy-actions.d.ts +11 -0
  154. package/dist/src/core/furnace-apply-undeploy-actions.js +49 -0
  155. package/dist/src/core/furnace-apply.d.ts +16 -4
  156. package/dist/src/core/furnace-apply.js +39 -43
  157. package/dist/src/core/furnace-config.js +2 -0
  158. package/dist/src/core/furnace-css-fragments.js +2 -0
  159. package/dist/src/core/furnace-jsconfig.js +14 -7
  160. package/dist/src/core/furnace-registration-ast.js +8 -8
  161. package/dist/src/core/furnace-registration-remove.js +6 -3
  162. package/dist/src/core/furnace-registration.js +9 -12
  163. package/dist/src/core/furnace-rollback.js +1 -1
  164. package/dist/src/core/furnace-scanner.js +2 -1
  165. package/dist/src/core/furnace-stale-export.js +2 -1
  166. package/dist/src/core/furnace-state-persist.d.ts +4 -6
  167. package/dist/src/core/furnace-validate-compatibility.js +4 -7
  168. package/dist/src/core/furnace-validate-helpers.js +33 -1
  169. package/dist/src/core/furnace-validate-registration.js +16 -0
  170. package/dist/src/core/furnace-validate.js +4 -2
  171. package/dist/src/core/git-diff.js +37 -2
  172. package/dist/src/core/lint-cache.d.ts +15 -5
  173. package/dist/src/core/lint-cache.js +49 -14
  174. package/dist/src/core/mach-build-artifacts.d.ts +63 -1
  175. package/dist/src/core/mach-build-artifacts.js +108 -2
  176. package/dist/src/core/mach-resource-shim.js +1 -0
  177. package/dist/src/core/marionette-port.d.ts +16 -0
  178. package/dist/src/core/marionette-port.js +93 -8
  179. package/dist/src/core/marionette-preflight.js +6 -5
  180. package/dist/src/core/patch-apply.d.ts +20 -13
  181. package/dist/src/core/patch-apply.js +32 -33
  182. package/dist/src/core/patch-export.d.ts +2 -2
  183. package/dist/src/core/patch-export.js +4 -6
  184. package/dist/src/core/patch-lint-checkjs.d.ts +42 -2
  185. package/dist/src/core/patch-lint-checkjs.js +132 -8
  186. package/dist/src/core/patch-lint-chrome-jsdoc.js +9 -0
  187. package/dist/src/core/patch-lint-creators.d.ts +30 -0
  188. package/dist/src/core/patch-lint-creators.js +45 -0
  189. package/dist/src/core/patch-lint-cross.d.ts +1 -17
  190. package/dist/src/core/patch-lint-cross.js +16 -32
  191. package/dist/src/core/patch-lint-css.js +64 -16
  192. package/dist/src/core/patch-lint-diff.d.ts +8 -0
  193. package/dist/src/core/patch-lint-diff.js +31 -0
  194. package/dist/src/core/patch-lint-jsdoc.d.ts +3 -1
  195. package/dist/src/core/patch-lint-jsdoc.js +15 -5
  196. package/dist/src/core/patch-lint-module-registration.d.ts +15 -0
  197. package/dist/src/core/patch-lint-module-registration.js +88 -0
  198. package/dist/src/core/patch-lint-mozbuild.js +99 -11
  199. package/dist/src/core/patch-lint-ownership.d.ts +21 -0
  200. package/dist/src/core/patch-lint-ownership.js +30 -0
  201. package/dist/src/core/patch-lint-reexports.d.ts +2 -1
  202. package/dist/src/core/patch-lint-reexports.js +1 -0
  203. package/dist/src/core/patch-lint.d.ts +31 -0
  204. package/dist/src/core/patch-lint.js +114 -8
  205. package/dist/src/core/patch-lock.d.ts +12 -1
  206. package/dist/src/core/patch-lock.js +40 -4
  207. package/dist/src/core/patch-manifest-consistency.js +15 -3
  208. package/dist/src/core/patch-manifest.d.ts +1 -1
  209. package/dist/src/core/patch-parse.d.ts +8 -0
  210. package/dist/src/core/patch-parse.js +10 -0
  211. package/dist/src/core/patch-policy.d.ts +7 -0
  212. package/dist/src/core/patch-policy.js +4 -1
  213. package/dist/src/core/patch-transform.d.ts +10 -0
  214. package/dist/src/core/patch-transform.js +13 -2
  215. package/dist/src/core/rebase-session.d.ts +44 -2
  216. package/dist/src/core/rebase-session.js +114 -17
  217. package/dist/src/core/register-browser-content.js +2 -1
  218. package/dist/src/core/register-shared-css.js +2 -1
  219. package/dist/src/core/smoke-patterns.d.ts +24 -16
  220. package/dist/src/core/smoke-patterns.js +26 -14
  221. package/dist/src/core/status-binary.d.ts +55 -0
  222. package/dist/src/core/status-binary.js +124 -0
  223. package/dist/src/core/status-classify.d.ts +22 -1
  224. package/dist/src/core/status-classify.js +112 -70
  225. package/dist/src/core/test-harness-crash.d.ts +32 -1
  226. package/dist/src/core/test-harness-crash.js +59 -4
  227. package/dist/src/core/test-path-scope.js +2 -0
  228. package/dist/src/core/test-stale-check.js +1 -20
  229. package/dist/src/core/token-category.d.ts +40 -0
  230. package/dist/src/core/token-category.js +264 -0
  231. package/dist/src/core/token-manager.d.ts +7 -0
  232. package/dist/src/core/token-manager.js +48 -196
  233. package/dist/src/core/toolchain-preflight.js +24 -26
  234. package/dist/src/core/tree-cow.d.ts +18 -0
  235. package/dist/src/core/tree-cow.js +71 -0
  236. package/dist/src/core/tree-guard.d.ts +35 -0
  237. package/dist/src/core/tree-guard.js +179 -0
  238. package/dist/src/core/tree-store.d.ts +129 -0
  239. package/dist/src/core/tree-store.js +452 -0
  240. package/dist/src/core/typecheck-shim.d.ts +14 -0
  241. package/dist/src/core/typecheck-shim.js +41 -2
  242. package/dist/src/core/typecheck.js +2 -1
  243. package/dist/src/core/wire-destroy.js +14 -3
  244. package/dist/src/core/wire-init.js +37 -40
  245. package/dist/src/core/wire-subscript.js +11 -3
  246. package/dist/src/core/wire-utils.js +4 -4
  247. package/dist/src/core/xpcshell-appdir.js +3 -0
  248. package/dist/src/core/xpcshell-profile-dir.d.ts +12 -0
  249. package/dist/src/core/xpcshell-profile-dir.js +45 -0
  250. package/dist/src/errors/base.d.ts +13 -0
  251. package/dist/src/errors/base.js +13 -0
  252. package/dist/src/errors/git.d.ts +8 -0
  253. package/dist/src/errors/git.js +17 -0
  254. package/dist/src/errors/rebase.d.ts +13 -0
  255. package/dist/src/errors/rebase.js +17 -0
  256. package/dist/src/index.d.ts +2 -0
  257. package/dist/src/index.js +1 -0
  258. package/dist/src/types/commands/options.d.ts +119 -3
  259. package/dist/src/types/commands/patches.d.ts +8 -0
  260. package/dist/src/types/config.d.ts +19 -0
  261. package/dist/src/types/furnace.d.ts +7 -0
  262. package/dist/src/utils/build-info.d.ts +31 -0
  263. package/dist/src/utils/build-info.js +98 -0
  264. package/dist/src/utils/concurrency.d.ts +8 -0
  265. package/dist/src/utils/concurrency.js +21 -0
  266. package/dist/src/utils/errors.d.ts +39 -0
  267. package/dist/src/utils/errors.js +55 -0
  268. package/dist/src/utils/fs.js +5 -12
  269. package/dist/src/utils/logger.d.ts +14 -2
  270. package/dist/src/utils/logger.js +43 -13
  271. package/dist/src/utils/options.d.ts +21 -4
  272. package/dist/src/utils/options.js +26 -6
  273. package/dist/src/utils/package-root.js +7 -4
  274. package/dist/src/utils/process-group.js +13 -2
  275. package/dist/src/utils/stdio-drain.d.ts +28 -0
  276. package/dist/src/utils/stdio-drain.js +54 -0
  277. package/dist/src/utils/validation.d.ts +33 -2
  278. package/dist/src/utils/validation.js +57 -2
  279. package/package.json +13 -13
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Bounded stdio drain for delayed process exits (FORGE I1).
3
+ *
4
+ * `process.exit()` discards whatever is still queued in an async writable —
5
+ * and stdout IS async when it is a pipe, with a 64 KiB kernel buffer on
6
+ * macOS/Linux. The observed failure: `status --json --fail-on` writes a
7
+ * 176 KB payload, the refusal makes `bin/fireforge.ts` exit non-zero, and a
8
+ * piped consumer receives exactly 65 536 bytes. The bin entry point awaits
9
+ * this helper before every delayed exit; the helper itself never calls
10
+ * `process.exit` (the process-boundary test keeps that a bin-only right).
11
+ */
12
+ /**
13
+ * The structural slice of `Writable` the drain wait needs — accepted instead
14
+ * of `NodeJS.WriteStream` so unit tests can drive synthetic streams.
15
+ */
16
+ export interface DrainableStream {
17
+ readonly destroyed: boolean;
18
+ readonly writableFinished: boolean;
19
+ readonly writableLength: number;
20
+ on(event: 'drain' | 'error' | 'close', listener: () => void): unknown;
21
+ off(event: 'drain' | 'error' | 'close', listener: () => void): unknown;
22
+ }
23
+ /**
24
+ * Waits (bounded by `timeoutMs`) for every given stream — stdout and stderr
25
+ * by default — to drain. Resolution is unconditional; the caller exits
26
+ * either way, the bound only decides how much queued output survives.
27
+ */
28
+ export declare function waitForStdioDrain(timeoutMs: number, streams?: readonly DrainableStream[]): Promise<void>;
@@ -0,0 +1,54 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Bounded stdio drain for delayed process exits (FORGE I1).
4
+ *
5
+ * `process.exit()` discards whatever is still queued in an async writable —
6
+ * and stdout IS async when it is a pipe, with a 64 KiB kernel buffer on
7
+ * macOS/Linux. The observed failure: `status --json --fail-on` writes a
8
+ * 176 KB payload, the refusal makes `bin/fireforge.ts` exit non-zero, and a
9
+ * piped consumer receives exactly 65 536 bytes. The bin entry point awaits
10
+ * this helper before every delayed exit; the helper itself never calls
11
+ * `process.exit` (the process-boundary test keeps that a bin-only right).
12
+ */
13
+ /** True when nothing the process is about to lose remains queued. */
14
+ function isFlushed(stream) {
15
+ return stream.destroyed || stream.writableFinished || stream.writableLength === 0;
16
+ }
17
+ /**
18
+ * Resolves once `stream` has drained its queued writes, and always within
19
+ * `timeoutMs`. Never rejects: `'error'` and `'close'` (an EPIPE'd pipe —
20
+ * `installBrokenPipeHandler` already swallows the error itself) resolve
21
+ * immediately, because a destroyed pipe can never drain and must not stall
22
+ * the exit behind it.
23
+ */
24
+ function waitForStreamDrain(stream, timeoutMs) {
25
+ if (isFlushed(stream)) {
26
+ return Promise.resolve();
27
+ }
28
+ return new Promise((resolve) => {
29
+ const settle = () => {
30
+ clearTimeout(timer);
31
+ stream.off('drain', onDrain);
32
+ stream.off('error', settle);
33
+ stream.off('close', settle);
34
+ resolve();
35
+ };
36
+ const onDrain = () => {
37
+ if (isFlushed(stream)) {
38
+ settle();
39
+ }
40
+ };
41
+ const timer = setTimeout(settle, timeoutMs);
42
+ stream.on('drain', onDrain);
43
+ stream.on('error', settle);
44
+ stream.on('close', settle);
45
+ });
46
+ }
47
+ /**
48
+ * Waits (bounded by `timeoutMs`) for every given stream — stdout and stderr
49
+ * by default — to drain. Resolution is unconditional; the caller exits
50
+ * either way, the bound only decides how much queued output survives.
51
+ */
52
+ export async function waitForStdioDrain(timeoutMs, streams = [process.stdout, process.stderr]) {
53
+ await Promise.all(streams.map((stream) => waitForStreamDrain(stream, timeoutMs)));
54
+ }
@@ -1,3 +1,4 @@
1
+ import type { FirefoxProduct } from '../types/config.js';
1
2
  /**
2
3
  * Checks whether a value is a string.
3
4
  * @param value - Value to check
@@ -62,11 +63,31 @@ export declare function isValidFirefoxVersion(version: string): boolean;
62
63
  * Accepts formats like "build1", "build2", "build12" (no leading zero).
63
64
  */
64
65
  export declare function isValidFirefoxCandidate(candidate: string): boolean;
66
+ /**
67
+ * Valid Firefox product identifiers.
68
+ *
69
+ * `satisfies readonly FirefoxProduct[]` links the runtime list to the union at
70
+ * `types/config.ts:5` in ONE direction: it rejects an entry here that the union
71
+ * does not declare. It cannot see a union member missing from this list —
72
+ * widening a `readonly T[]` check never fails for being short. The reverse
73
+ * direction is covered by the exhaustive switch in
74
+ * `utils/__tests__/validation.test.ts`, which fails to compile when the union
75
+ * grows.
76
+ *
77
+ * Two further copies of this list existed before 0.41.0 — an inline literal
78
+ * inside the validator below and `SOURCE_PRODUCTS` in `commands/source.ts` —
79
+ * neither linked to the union.
80
+ */
81
+ export declare const FIREFOX_PRODUCTS: readonly ["firefox", "firefox-esr", "firefox-beta", "firefox-devedition"];
65
82
  /**
66
83
  * Validates a Firefox product string.
67
- * Accepts: firefox, firefox-esr, firefox-beta, firefox-devedition
84
+ *
85
+ * A type predicate, matching its siblings {@link isValidPatchCategory} and
86
+ * {@link isValidProjectLicense}. It returned plain `boolean` until 0.41.0,
87
+ * which forced `as FirefoxProduct` casts at both call sites even though the
88
+ * `.includes` check IS the runtime proof.
68
89
  */
69
- export declare function isValidFirefoxProduct(product: string): boolean;
90
+ export declare function isValidFirefoxProduct(product: string): product is FirefoxProduct;
70
91
  /**
71
92
  * Valid project license SPDX identifiers.
72
93
  */
@@ -128,6 +149,16 @@ export declare function validateTokenName(name: string): string | undefined;
128
149
  * @throws InvalidArgumentError if the resulting name is not a valid CSS custom property.
129
150
  */
130
151
  export declare function normalizeTokenName(name: string): string;
152
+ /**
153
+ * Normalizes a patch display name against its category (FORGE G13):
154
+ * strips a trailing `.patch` and any redundant `NNN-<category>-` /
155
+ * `<category>-` prefixes, case-insensitively and repeatedly, mirroring
156
+ * the filename slug pipeline's require-the-category-token rule — a bare
157
+ * leading number is never stripped, so names like `2-step-verification`
158
+ * survive intact. Falls back to the `.patch`-stripped stem when the
159
+ * strip would empty the name.
160
+ */
161
+ export declare function normalizePatchDisplayName(name: string, category: string): string;
131
162
  /**
132
163
  * Validates a patch name.
133
164
  * @param name - The patch name to validate
@@ -4,6 +4,7 @@
4
4
  * Used to safely narrow types from unknown values.
5
5
  */
6
6
  import { InvalidArgumentError } from '../errors/base.js';
7
+ import { escapeRegex } from './regex.js';
7
8
  /**
8
9
  * Checks whether a value is a string.
9
10
  * @param value - Value to check
@@ -94,12 +95,37 @@ export function isValidFirefoxVersion(version) {
94
95
  export function isValidFirefoxCandidate(candidate) {
95
96
  return /^build[1-9]\d{0,3}$/.test(candidate);
96
97
  }
98
+ /**
99
+ * Valid Firefox product identifiers.
100
+ *
101
+ * `satisfies readonly FirefoxProduct[]` links the runtime list to the union at
102
+ * `types/config.ts:5` in ONE direction: it rejects an entry here that the union
103
+ * does not declare. It cannot see a union member missing from this list —
104
+ * widening a `readonly T[]` check never fails for being short. The reverse
105
+ * direction is covered by the exhaustive switch in
106
+ * `utils/__tests__/validation.test.ts`, which fails to compile when the union
107
+ * grows.
108
+ *
109
+ * Two further copies of this list existed before 0.41.0 — an inline literal
110
+ * inside the validator below and `SOURCE_PRODUCTS` in `commands/source.ts` —
111
+ * neither linked to the union.
112
+ */
113
+ export const FIREFOX_PRODUCTS = [
114
+ 'firefox',
115
+ 'firefox-esr',
116
+ 'firefox-beta',
117
+ 'firefox-devedition',
118
+ ];
97
119
  /**
98
120
  * Validates a Firefox product string.
99
- * Accepts: firefox, firefox-esr, firefox-beta, firefox-devedition
121
+ *
122
+ * A type predicate, matching its siblings {@link isValidPatchCategory} and
123
+ * {@link isValidProjectLicense}. It returned plain `boolean` until 0.41.0,
124
+ * which forced `as FirefoxProduct` casts at both call sites even though the
125
+ * `.includes` check IS the runtime proof.
100
126
  */
101
127
  export function isValidFirefoxProduct(product) {
102
- return ['firefox', 'firefox-esr', 'firefox-beta', 'firefox-devedition'].includes(product);
128
+ return FIREFOX_PRODUCTS.includes(product);
103
129
  }
104
130
  /**
105
131
  * Valid project license SPDX identifiers.
@@ -251,6 +277,35 @@ export function normalizeTokenName(name) {
251
277
  const stripped = name.replace(/^--/, '');
252
278
  return `--${stripped}`;
253
279
  }
280
+ /**
281
+ * Normalizes a patch display name against its category (FORGE G13):
282
+ * strips a trailing `.patch` and any redundant `NNN-<category>-` /
283
+ * `<category>-` prefixes, case-insensitively and repeatedly, mirroring
284
+ * the filename slug pipeline's require-the-category-token rule — a bare
285
+ * leading number is never stripped, so names like `2-step-verification`
286
+ * survive intact. Falls back to the `.patch`-stripped stem when the
287
+ * strip would empty the name.
288
+ */
289
+ export function normalizePatchDisplayName(name, category) {
290
+ const stem = name.trim().replace(/\.patch$/i, '');
291
+ const escaped = escapeRegex(category);
292
+ const prefixes = [
293
+ new RegExp(`^\\d+[-_ ]+${escaped}[-_ ]+`, 'i'),
294
+ new RegExp(`^${escaped}[-_ ]+`, 'i'),
295
+ ];
296
+ let stripped = stem;
297
+ let changed = true;
298
+ while (changed) {
299
+ changed = false;
300
+ for (const prefix of prefixes) {
301
+ if (prefix.test(stripped)) {
302
+ stripped = stripped.replace(prefix, '');
303
+ changed = true;
304
+ }
305
+ }
306
+ }
307
+ return stripped.length > 0 ? stripped : stem;
308
+ }
254
309
  /**
255
310
  * Validates a patch name.
256
311
  * @param name - The patch name to validate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hominis/fireforge",
3
- "version": "0.39.0",
3
+ "version": "0.41.0",
4
4
  "description": "FireForge — a build tool for customizing Firefox",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -18,11 +18,11 @@
18
18
  },
19
19
  "scripts": {
20
20
  "fireforge": "tsx bin/fireforge.ts",
21
- "build": "npm run clean && tsc -p tsconfig.build.json",
21
+ "build": "npm run clean && tsc -p tsconfig.build.json && node ./scripts/generate-build-info.mjs",
22
22
  "clean": "node ./scripts/clean.mjs",
23
- "lint": "eslint src/ bin/ scripts/ eslint.config.js vitest.config.ts vitest.pack.config.ts",
24
- "lint:ci": "eslint src/ bin/ scripts/ eslint.config.js vitest.config.ts vitest.pack.config.ts --max-warnings 0",
25
- "lint:fix": "eslint src/ bin/ scripts/ eslint.config.js vitest.config.ts vitest.pack.config.ts --fix",
23
+ "lint": "eslint src/ bin/ scripts/ eslint-rules/ eslint.config.js vitest.config.ts vitest.pack.config.ts",
24
+ "lint:ci": "eslint src/ bin/ scripts/ eslint-rules/ eslint.config.js vitest.config.ts vitest.pack.config.ts --max-warnings 0",
25
+ "lint:fix": "eslint src/ bin/ scripts/ eslint-rules/ eslint.config.js vitest.config.ts vitest.pack.config.ts --fix",
26
26
  "typecheck": "tsc --noEmit -p tsconfig.json",
27
27
  "deadcode:check": "knip",
28
28
  "cycles:check": "dpdm --no-output --no-tree --no-warning --exit-code circular:1 src/index.ts bin/fireforge.ts",
@@ -34,10 +34,10 @@
34
34
  "whitespace:check": "node ./scripts/check-worktree-whitespace.mjs",
35
35
  "pack:verify": "vitest run --config vitest.pack.config.ts",
36
36
  "pack:dry-run": "npm pack --dry-run --json --silent",
37
- "format": "prettier --write --ignore-unknown \"src/**/*.ts\" \"bin/**/*.ts\" \"scripts/**/*.mjs\" \"package.json\" \"eslint.config.js\" \"vitest.config.ts\" \"vitest.pack.config.ts\" \"tsconfig.json\" \"tsconfig.build.json\" \".lintstagedrc.json\" \".prettierignore\" \".prettierrc\" \".gitignore\" \"README.md\" \"CHANGELOG.md\" \"docs/**/*.md\" \"knip.json\" \"docs/**/*.md\" \"knip.json\"",
38
- "format:check": "prettier --check --ignore-unknown \"src/**/*.ts\" \"bin/**/*.ts\" \"scripts/**/*.mjs\" \"package.json\" \"eslint.config.js\" \"vitest.config.ts\" \"vitest.pack.config.ts\" \"tsconfig.json\" \"tsconfig.build.json\" \".lintstagedrc.json\" \".prettierignore\" \".prettierrc\" \".gitignore\" \"README.md\" \"CHANGELOG.md\" \"docs/**/*.md\" \"knip.json\" \"docs/**/*.md\" \"knip.json\"",
37
+ "format": "prettier --write --ignore-unknown \"src/**/*.ts\" \"bin/**/*.ts\" \"scripts/**/*.mjs\" \"eslint-rules/**/*.js\" \"package.json\" \"eslint.config.js\" \"vitest.config.ts\" \"vitest.pack.config.ts\" \"tsconfig.json\" \"tsconfig.build.json\" \".lintstagedrc.json\" \".prettierignore\" \".prettierrc\" \".gitignore\" \"README.md\" \"CHANGELOG.md\" \"docs/**/*.md\" \"knip.json\"",
38
+ "format:check": "prettier --check --ignore-unknown \"src/**/*.ts\" \"bin/**/*.ts\" \"scripts/**/*.mjs\" \"eslint-rules/**/*.js\" \"package.json\" \"eslint.config.js\" \"vitest.config.ts\" \"vitest.pack.config.ts\" \"tsconfig.json\" \"tsconfig.build.json\" \".lintstagedrc.json\" \".prettierignore\" \".prettierrc\" \".gitignore\" \"README.md\" \"CHANGELOG.md\" \"docs/**/*.md\" \"knip.json\"",
39
39
  "prepare": "node -e \"import('./scripts/prepare.mjs').catch((error) => { if (error && typeof error === 'object' && 'code' in error && error.code === 'ERR_MODULE_NOT_FOUND') process.exit(0); throw error; })\"",
40
- "prepack": "npm run build",
40
+ "prepack": "npm run build && node ./scripts/generate-build-info.mjs --loud",
41
41
  "release:check": "npm run format:check && npm run whitespace:check && npm run lint:ci && npm run typecheck && npm run deadcode:check && npm run cycles:check && npm run test:coverage && npm run pack:verify && npm run pack:dry-run",
42
42
  "prepublishOnly": "npm run release:check"
43
43
  },
@@ -58,7 +58,7 @@
58
58
  "commander": "^15.0.0",
59
59
  "estree-walker": "^3.0.3",
60
60
  "jsonc-parser": "^3.3.1",
61
- "magic-string": "^0.30.17",
61
+ "magic-string": "^1.1.0",
62
62
  "picocolors": "^1.1.0"
63
63
  },
64
64
  "devDependencies": {
@@ -66,16 +66,16 @@
66
66
  "@types/estree": "^1.0.8",
67
67
  "@types/node": "^26.0.0",
68
68
  "@vitest/coverage-v8": "^4.1.2",
69
- "dpdm": "4.2.0",
69
+ "dpdm": "4.3.0",
70
70
  "eslint": "^10.0.0",
71
71
  "eslint-config-prettier": "^10.1.8",
72
72
  "eslint-plugin-jsdoc": "^63.0.0",
73
- "eslint-plugin-simple-import-sort": "^13.0.0",
73
+ "eslint-plugin-simple-import-sort": "^14.0.0",
74
74
  "fast-check": "^4.6.0",
75
75
  "husky": "^9.1.7",
76
- "knip": "6.26.0",
76
+ "knip": "6.32.0",
77
77
  "lint-staged": "^17.0.4",
78
- "prettier": "3.9.5",
78
+ "prettier": "3.9.6",
79
79
  "tsx": "^4.7.0",
80
80
  "typescript": "~6.0.0",
81
81
  "typescript-eslint": "^8.0.0",