@hominis/fireforge 0.52.0 → 0.53.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 (243) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/build-info.json +4 -4
  3. package/dist/src/commands/bootstrap.js +2 -3
  4. package/dist/src/commands/build-prelock.d.ts +3 -3
  5. package/dist/src/commands/build-prelock.js +3 -3
  6. package/dist/src/commands/build.js +4 -0
  7. package/dist/src/commands/declarations-report.d.ts +9 -3
  8. package/dist/src/commands/declarations-report.js +18 -11
  9. package/dist/src/commands/discard.js +3 -3
  10. package/dist/src/commands/doctor-furnace-config-sync.js +4 -4
  11. package/dist/src/commands/doctor-furnace.js +5 -5
  12. package/dist/src/commands/export-adopt-check.d.ts +5 -6
  13. package/dist/src/commands/export-adopt-check.js +5 -6
  14. package/dist/src/commands/export-flow.js +6 -9
  15. package/dist/src/commands/export-staged-registration.d.ts +4 -4
  16. package/dist/src/commands/export-staged-registration.js +4 -4
  17. package/dist/src/commands/forward-import-remedy.d.ts +2 -3
  18. package/dist/src/commands/forward-import-remedy.js +2 -3
  19. package/dist/src/commands/furnace/create-dry-run.d.ts +2 -2
  20. package/dist/src/commands/furnace/deploy-fragment-includers.d.ts +2 -2
  21. package/dist/src/commands/furnace/deploy-fragment-includers.js +2 -2
  22. package/dist/src/commands/furnace/index.js +2 -2
  23. package/dist/src/commands/lint.js +4 -4
  24. package/dist/src/commands/patch/compact.js +1 -1
  25. package/dist/src/commands/patch/lint-ignore-from.d.ts +3 -4
  26. package/dist/src/commands/patch/lint-ignore-from.js +3 -4
  27. package/dist/src/commands/patch/move-files-adopt.d.ts +33 -0
  28. package/dist/src/commands/patch/move-files-adopt.js +65 -0
  29. package/dist/src/commands/patch/move-files-create.js +41 -20
  30. package/dist/src/commands/patch/move-files-into.d.ts +18 -0
  31. package/dist/src/commands/patch/move-files-into.js +125 -60
  32. package/dist/src/commands/patch/move-files-retire.d.ts +39 -0
  33. package/dist/src/commands/patch/move-files-retire.js +91 -0
  34. package/dist/src/commands/patch/move-files.d.ts +3 -0
  35. package/dist/src/commands/patch/move-files.js +42 -12
  36. package/dist/src/commands/patch/move-size-tier.d.ts +27 -0
  37. package/dist/src/commands/patch/move-size-tier.js +66 -0
  38. package/dist/src/commands/patch/rename.js +1 -1
  39. package/dist/src/commands/patch/reorder-projection.d.ts +17 -0
  40. package/dist/src/commands/patch/reorder-projection.js +49 -0
  41. package/dist/src/commands/patch/reorder-shift.d.ts +30 -0
  42. package/dist/src/commands/patch/reorder-shift.js +307 -0
  43. package/dist/src/commands/patch/reorder.d.ts +9 -15
  44. package/dist/src/commands/patch/reorder.js +52 -50
  45. package/dist/src/commands/patch/split-plan.d.ts +31 -3
  46. package/dist/src/commands/patch/split-plan.js +36 -12
  47. package/dist/src/commands/patch/split-staged.d.ts +2 -2
  48. package/dist/src/commands/patch/split-staged.js +2 -2
  49. package/dist/src/commands/patch/split.js +18 -5
  50. package/dist/src/commands/patch/staged-dependency-entries.d.ts +9 -1
  51. package/dist/src/commands/patch/staged-dependency-entries.js +32 -4
  52. package/dist/src/commands/patch/staged-dependency-from.js +15 -9
  53. package/dist/src/commands/patch/staged-dependency.js +13 -9
  54. package/dist/src/commands/patch/tier.js +2 -2
  55. package/dist/src/commands/patch-rename-map.d.ts +14 -3
  56. package/dist/src/commands/patch-rename-map.js +21 -3
  57. package/dist/src/commands/re-export-adjacent.d.ts +4 -5
  58. package/dist/src/commands/re-export-lint.d.ts +4 -3
  59. package/dist/src/commands/re-export-lint.js +4 -3
  60. package/dist/src/commands/re-export-preflight.js +1 -2
  61. package/dist/src/commands/re-export-purity-scope.d.ts +1 -1
  62. package/dist/src/commands/re-export-purity-scope.js +13 -7
  63. package/dist/src/commands/re-export-retired-declarations.d.ts +22 -0
  64. package/dist/src/commands/re-export-retired-declarations.js +113 -0
  65. package/dist/src/commands/re-export-single.js +2 -2
  66. package/dist/src/commands/re-export-stale-registrations.d.ts +27 -6
  67. package/dist/src/commands/re-export-stale-registrations.js +65 -21
  68. package/dist/src/commands/re-export.js +4 -0
  69. package/dist/src/commands/rebase/continue.js +3 -3
  70. package/dist/src/commands/register.js +5 -4
  71. package/dist/src/commands/status.js +1 -1
  72. package/dist/src/commands/test-build-furnace.d.ts +1 -2
  73. package/dist/src/commands/test-build-furnace.js +1 -2
  74. package/dist/src/commands/test-build-phase.d.ts +8 -0
  75. package/dist/src/commands/test-build-phase.js +42 -7
  76. package/dist/src/commands/test-control-restore.d.ts +40 -0
  77. package/dist/src/commands/test-control-restore.js +86 -0
  78. package/dist/src/commands/test-dispatch.js +68 -7
  79. package/dist/src/commands/test-multi-verdict.js +1 -0
  80. package/dist/src/commands/test-orchestrate.d.ts +6 -0
  81. package/dist/src/commands/test-orchestrate.js +28 -0
  82. package/dist/src/commands/test-perf-scope.d.ts +1 -1
  83. package/dist/src/commands/test-perf-scope.js +1 -1
  84. package/dist/src/commands/test-prelock.d.ts +7 -8
  85. package/dist/src/commands/test-prelock.js +15 -12
  86. package/dist/src/commands/test-register.js +2 -2
  87. package/dist/src/commands/test-results.d.ts +10 -1
  88. package/dist/src/commands/test-results.js +20 -3
  89. package/dist/src/commands/test-run.d.ts +6 -0
  90. package/dist/src/commands/test-run.js +23 -4
  91. package/dist/src/commands/test-shards.d.ts +7 -3
  92. package/dist/src/commands/test-shards.js +24 -7
  93. package/dist/src/commands/test-stale-gate.d.ts +6 -1
  94. package/dist/src/commands/test-stale-gate.js +40 -2
  95. package/dist/src/commands/test-verdict.d.ts +5 -3
  96. package/dist/src/commands/test.js +10 -0
  97. package/dist/src/commands/token.js +2 -2
  98. package/dist/src/commands/verify.js +1 -1
  99. package/dist/src/commands/watch.js +1 -1
  100. package/dist/src/commands/wire.js +1 -1
  101. package/dist/src/core/build-audit-resolve.d.ts +4 -5
  102. package/dist/src/core/build-audit-resolve.js +4 -5
  103. package/dist/src/core/build-audit-skip.d.ts +2 -2
  104. package/dist/src/core/build-audit.d.ts +3 -4
  105. package/dist/src/core/build-baseline-types.d.ts +24 -3
  106. package/dist/src/core/build-baseline-validate.d.ts +30 -1
  107. package/dist/src/core/build-baseline-validate.js +127 -35
  108. package/dist/src/core/build-baseline.d.ts +8 -2
  109. package/dist/src/core/build-baseline.js +25 -16
  110. package/dist/src/core/build-jar-escalation.d.ts +4 -4
  111. package/dist/src/core/build-jar-escalation.js +4 -4
  112. package/dist/src/core/build-prepare.js +4 -4
  113. package/dist/src/core/config-validate.js +4 -4
  114. package/dist/src/core/coverage-extend.d.ts +5 -3
  115. package/dist/src/core/coverage-extend.js +8 -3
  116. package/dist/src/core/coverage-records.d.ts +4 -5
  117. package/dist/src/core/coverage-records.js +4 -5
  118. package/dist/src/core/destructive.d.ts +2 -2
  119. package/dist/src/core/destructive.js +2 -2
  120. package/dist/src/core/discard-baseline.d.ts +2 -2
  121. package/dist/src/core/discard-baseline.js +3 -3
  122. package/dist/src/core/engine-session-lock.d.ts +1 -1
  123. package/dist/src/core/engine-session-lock.js +1 -1
  124. package/dist/src/core/file-lock-owner.d.ts +5 -5
  125. package/dist/src/core/file-lock-owner.js +5 -5
  126. package/dist/src/core/file-lock-queue.js +6 -8
  127. package/dist/src/core/file-lock-shared.d.ts +4 -4
  128. package/dist/src/core/file-lock-shared.js +4 -4
  129. package/dist/src/core/file-lock.js +7 -10
  130. package/dist/src/core/firefox.js +2 -2
  131. package/dist/src/core/furnace-apply-overwrite-warn.d.ts +1 -1
  132. package/dist/src/core/furnace-apply-overwrite-warn.js +1 -1
  133. package/dist/src/core/furnace-apply.js +3 -3
  134. package/dist/src/core/furnace-operation.js +5 -5
  135. package/dist/src/core/furnace-registration-jar-inplace.js +5 -6
  136. package/dist/src/core/furnace-registration-rename.d.ts +5 -5
  137. package/dist/src/core/furnace-registration-rename.js +5 -5
  138. package/dist/src/core/furnace-registration-validate.d.ts +2 -1
  139. package/dist/src/core/furnace-registration-validate.js +2 -1
  140. package/dist/src/core/furnace-registration.js +2 -2
  141. package/dist/src/core/git-base.js +4 -3
  142. package/dist/src/core/git-diff.js +1 -1
  143. package/dist/src/core/git.js +1 -1
  144. package/dist/src/core/harness-orphans.d.ts +6 -6
  145. package/dist/src/core/harness-orphans.js +6 -6
  146. package/dist/src/core/harness-port-reservation.d.ts +60 -0
  147. package/dist/src/core/harness-port-reservation.js +122 -0
  148. package/dist/src/core/history-log-file.d.ts +9 -9
  149. package/dist/src/core/history-log-file.js +15 -15
  150. package/dist/src/core/history-log.d.ts +3 -3
  151. package/dist/src/core/history-log.js +3 -3
  152. package/dist/src/core/launchable-build.d.ts +39 -0
  153. package/dist/src/core/launchable-build.js +85 -0
  154. package/dist/src/core/marionette-port.js +1 -3
  155. package/dist/src/core/moz-manifest-rules.js +3 -2
  156. package/dist/src/core/negative-control-compiled.d.ts +13 -3
  157. package/dist/src/core/negative-control-compiled.js +27 -5
  158. package/dist/src/core/parser-fallback.d.ts +5 -5
  159. package/dist/src/core/parser-fallback.js +5 -5
  160. package/dist/src/core/patch-identifier-suggest.d.ts +6 -0
  161. package/dist/src/core/patch-identifier-suggest.js +11 -4
  162. package/dist/src/core/patch-lint-checkjs.d.ts +1 -1
  163. package/dist/src/core/patch-lint-checkjs.js +1 -1
  164. package/dist/src/core/patch-lint-css.js +2 -2
  165. package/dist/src/core/patch-lint-file-size.d.ts +5 -7
  166. package/dist/src/core/patch-lint-file-size.js +5 -7
  167. package/dist/src/core/patch-lint-ignore.d.ts +3 -3
  168. package/dist/src/core/patch-lint-ignore.js +3 -3
  169. package/dist/src/core/patch-lint-prettier.d.ts +4 -6
  170. package/dist/src/core/patch-lint-prettier.js +4 -6
  171. package/dist/src/core/patch-lint-projection.d.ts +1 -1
  172. package/dist/src/core/patch-lint-projection.js +1 -1
  173. package/dist/src/core/patch-lint-queue-types.d.ts +2 -2
  174. package/dist/src/core/patch-lint-queue-types.js +2 -2
  175. package/dist/src/core/patch-manifest-consistency.d.ts +2 -2
  176. package/dist/src/core/patch-manifest-consistency.js +2 -2
  177. package/dist/src/core/patch-manifest-io.d.ts +5 -1
  178. package/dist/src/core/patch-manifest-io.js +12 -10
  179. package/dist/src/core/patch-manifest-key-order.d.ts +3 -4
  180. package/dist/src/core/patch-manifest-key-order.js +3 -4
  181. package/dist/src/core/patch-parse.d.ts +3 -3
  182. package/dist/src/core/patch-parse.js +3 -3
  183. package/dist/src/core/patch-policy.d.ts +1 -1
  184. package/dist/src/core/patch-policy.js +1 -1
  185. package/dist/src/core/patch-source-metadata.d.ts +1 -1
  186. package/dist/src/core/patch-source-metadata.js +1 -1
  187. package/dist/src/core/patch-subset.d.ts +5 -3
  188. package/dist/src/core/patch-subset.js +8 -7
  189. package/dist/src/core/patch-transform.d.ts +2 -2
  190. package/dist/src/core/patch-transform.js +2 -2
  191. package/dist/src/core/rebase-session.js +4 -6
  192. package/dist/src/core/register-xpcshell-test.d.ts +28 -7
  193. package/dist/src/core/register-xpcshell-test.js +76 -27
  194. package/dist/src/core/run-log.d.ts +8 -8
  195. package/dist/src/core/run-log.js +8 -8
  196. package/dist/src/core/status-classify.d.ts +1 -2
  197. package/dist/src/core/status-classify.js +1 -2
  198. package/dist/src/core/test-harness-crash.d.ts +5 -3
  199. package/dist/src/core/test-harness-crash.js +13 -14
  200. package/dist/src/core/test-harness-infra.d.ts +32 -0
  201. package/dist/src/core/test-harness-infra.js +69 -0
  202. package/dist/src/core/test-harness-signature.d.ts +6 -0
  203. package/dist/src/core/test-stale-check.d.ts +38 -0
  204. package/dist/src/core/test-stale-check.js +82 -1
  205. package/dist/src/core/test-stall-triage.js +5 -7
  206. package/dist/src/core/token-category.js +2 -2
  207. package/dist/src/core/token-inventory.js +3 -3
  208. package/dist/src/core/token-manager.d.ts +2 -2
  209. package/dist/src/core/token-manager.js +5 -7
  210. package/dist/src/core/token-variant.d.ts +6 -6
  211. package/dist/src/core/token-variant.js +7 -7
  212. package/dist/src/core/typecheck-shim.js +1 -1
  213. package/dist/src/core/wire-destroy.d.ts +1 -1
  214. package/dist/src/core/wire-destroy.js +1 -1
  215. package/dist/src/core/wire-dom-fragment.js +1 -1
  216. package/dist/src/core/wire-init.d.ts +1 -1
  217. package/dist/src/core/wire-init.js +1 -1
  218. package/dist/src/core/wire-subscript.d.ts +1 -1
  219. package/dist/src/core/wire-subscript.js +1 -1
  220. package/dist/src/core/wire-utils.d.ts +2 -2
  221. package/dist/src/core/wire-utils.js +3 -3
  222. package/dist/src/errors/base.d.ts +1 -1
  223. package/dist/src/errors/base.js +1 -1
  224. package/dist/src/errors/rebase.d.ts +1 -1
  225. package/dist/src/errors/rebase.js +1 -1
  226. package/dist/src/types/commands/options.d.ts +26 -3
  227. package/dist/src/types/config.d.ts +5 -7
  228. package/dist/src/utils/options.js +2 -2
  229. package/dist/src/utils/process.js +2 -2
  230. package/dist/src/utils/regex.d.ts +2 -2
  231. package/dist/src/utils/regex.js +3 -3
  232. package/dist/src/utils/sleep.d.ts +5 -6
  233. package/dist/src/utils/sleep.js +5 -6
  234. package/dist/src/utils/validation.js +2 -2
  235. package/docs/configuration.md +21 -23
  236. package/docs/exit-codes.md +8 -11
  237. package/docs/furnace.md +3 -3
  238. package/docs/lifecycle-invariants.md +36 -45
  239. package/docs/machine-output.md +41 -24
  240. package/docs/patch-workflow.md +129 -24
  241. package/docs/run-logs.md +8 -9
  242. package/docs/testing.md +105 -46
  243. package/package.json +1 -1
@@ -6,15 +6,25 @@
6
6
  * objdir links to. A C++ (or Rust, IDL, …) source edited at that point is
7
7
  * never compiled: the run tests the unmutated binary and reports
8
8
  * `control=missed`, which reads as a test that cannot fail. Such a target
9
- * is now refused without `--build`, and with `--build` the control is
9
+ * is refused without `--build`, and with `--build` the control is
10
10
  * applied before the build so the binary under test carries it. That build
11
11
  * is a full `mach build`: the pre-test build is otherwise `mach build
12
- * faster`, which compiles nothing, so the control still missed.
12
+ * faster`, which compiles nothing, so the control would still miss.
13
13
  */
14
+ /**
15
+ * Whether an engine-relative path has a compiled-source extension: a file
16
+ * the build compiles or code-generates into the binary. This is the
17
+ * extension test only. {@link findCompiledControlTargets} additionally
18
+ * drops non-header files an install manifest ships as-is.
19
+ *
20
+ * @param path - Engine-relative path
21
+ */
22
+ export declare function isCompiledSourcePath(path: string): boolean;
14
23
  /**
15
24
  * The engine-relative targets that reach the runtime only through a
16
25
  * compile: a compiled-source extension and no install-manifest entry that
17
- * ships the file itself.
26
+ * ships the file itself. Headers are always compiled, even when an
27
+ * `EXPORTS` manifest installs them into `dist/include`.
18
28
  *
19
29
  * @param engineDir - Absolute engine directory
20
30
  * @param enginePaths - The control's engine-relative targets
@@ -7,10 +7,10 @@
7
7
  * objdir links to. A C++ (or Rust, IDL, …) source edited at that point is
8
8
  * never compiled: the run tests the unmutated binary and reports
9
9
  * `control=missed`, which reads as a test that cannot fail. Such a target
10
- * is now refused without `--build`, and with `--build` the control is
10
+ * is refused without `--build`, and with `--build` the control is
11
11
  * applied before the build so the binary under test carries it. That build
12
12
  * is a full `mach build`: the pre-test build is otherwise `mach build
13
- * faster`, which compiles nothing, so the control still missed.
13
+ * faster`, which compiles nothing, so the control would still miss.
14
14
  */
15
15
  import { extname } from 'node:path';
16
16
  import { classifyRuntimeDelivery } from './objdir-install-manifests.js';
@@ -34,24 +34,46 @@ const COMPILED_EXTENSIONS = new Set([
34
34
  '.yaml',
35
35
  '.yml',
36
36
  ]);
37
+ /**
38
+ * Header extensions. An `EXPORTS` install manifest names many headers (it
39
+ * copies them into `dist/include`), but that copy only feeds other
40
+ * compiles: the runtime never reads a header, so a header edit reaches the
41
+ * binary through a compile whatever the manifests say.
42
+ */
43
+ const HEADER_EXTENSIONS = new Set(['.h', '.hh', '.hpp', '.ipdlh']);
44
+ /**
45
+ * Whether an engine-relative path has a compiled-source extension: a file
46
+ * the build compiles or code-generates into the binary. This is the
47
+ * extension test only. {@link findCompiledControlTargets} additionally
48
+ * drops non-header files an install manifest ships as-is.
49
+ *
50
+ * @param path - Engine-relative path
51
+ */
52
+ export function isCompiledSourcePath(path) {
53
+ return COMPILED_EXTENSIONS.has(extname(path));
54
+ }
37
55
  /**
38
56
  * The engine-relative targets that reach the runtime only through a
39
57
  * compile: a compiled-source extension and no install-manifest entry that
40
- * ships the file itself.
58
+ * ships the file itself. Headers are always compiled, even when an
59
+ * `EXPORTS` manifest installs them into `dist/include`.
41
60
  *
42
61
  * @param engineDir - Absolute engine directory
43
62
  * @param enginePaths - The control's engine-relative targets
44
63
  */
45
64
  export async function findCompiledControlTargets(engineDir, enginePaths) {
46
- const candidates = enginePaths.filter((path) => COMPILED_EXTENSIONS.has(extname(path)));
65
+ const candidates = enginePaths.filter(isCompiledSourcePath);
47
66
  if (candidates.length === 0)
48
67
  return [];
68
+ if (candidates.every((path) => HEADER_EXTENSIONS.has(extname(path))))
69
+ return candidates;
49
70
  const index = await loadEngineInstallIndex(engineDir);
50
71
  if (index === undefined)
51
72
  return candidates;
52
73
  const compiled = [];
53
74
  for (const path of candidates) {
54
- if ((await classifyRuntimeDelivery(index, engineDir, path)) === 'unlisted') {
75
+ if (HEADER_EXTENSIONS.has(extname(path)) ||
76
+ (await classifyRuntimeDelivery(index, engineDir, path)) === 'unlisted') {
55
77
  compiled.push(path);
56
78
  }
57
79
  }
@@ -13,12 +13,12 @@ export declare function consumeParserFallbackEvents(): ParserFallbackEvent[];
13
13
  * The `rethrowIf` predicate every AST-primary caller wants.
14
14
  *
15
15
  * Rethrows internal-invariant `GeneralError`s ("Unexpected empty …array"):
16
- * those are programming bugs, and retrying the legacy scanner cannot fix a
16
+ * those are programming bugs, and retrying the fallback scanner cannot fix a
17
17
  * broken invariant, it just buries the stack. Everything else still falls
18
18
  * back, which matters: the AST path raises a raw acorn SyntaxError
19
19
  * on chrome sources acorn cannot parse (preprocessor directives), and
20
20
  * BuildError when the file's shape is unexpected. Both are exactly what the
21
- * legacy scanner is here to handle.
21
+ * fallback scanner is here to handle.
22
22
  *
23
23
  * @param error - The error the primary parser threw
24
24
  * @returns True when the error must propagate instead of falling back
@@ -33,15 +33,15 @@ export interface ParserResult<T> {
33
33
  fallbackReason?: string;
34
34
  }
35
35
  /**
36
- * Wraps a primary (AST/tokenizer) parser with a legacy (regex/line-based)
36
+ * Wraps a primary (AST/tokenizer) parser with a regex/line-based
37
37
  * fallback. If the primary throws, a warning is logged and the fallback is
38
38
  * used instead.
39
39
  *
40
40
  * Returns a {@link ParserResult} so callers (and tests) can detect which
41
41
  * code path was taken and surface it in logs or assertions.
42
42
  *
43
- * @param primary - The modern parser implementation
44
- * @param fallback - The legacy fallback implementation
43
+ * @param primary - The primary parser implementation
44
+ * @param fallback - The regex/line-based fallback implementation
45
45
  * @param context - File/context name used in the warning message
46
46
  * @param rethrowIf - Optional predicate. If it returns true for the caught
47
47
  * error the error is re-thrown instead of falling back (useful for
@@ -18,12 +18,12 @@ export function consumeParserFallbackEvents() {
18
18
  * The `rethrowIf` predicate every AST-primary caller wants.
19
19
  *
20
20
  * Rethrows internal-invariant `GeneralError`s ("Unexpected empty …array"):
21
- * those are programming bugs, and retrying the legacy scanner cannot fix a
21
+ * those are programming bugs, and retrying the fallback scanner cannot fix a
22
22
  * broken invariant, it just buries the stack. Everything else still falls
23
23
  * back, which matters: the AST path raises a raw acorn SyntaxError
24
24
  * on chrome sources acorn cannot parse (preprocessor directives), and
25
25
  * BuildError when the file's shape is unexpected. Both are exactly what the
26
- * legacy scanner is here to handle.
26
+ * fallback scanner is here to handle.
27
27
  *
28
28
  * @param error - The error the primary parser threw
29
29
  * @returns True when the error must propagate instead of falling back
@@ -32,15 +32,15 @@ export function isInternalInvariantFailure(error) {
32
32
  return error instanceof GeneralError;
33
33
  }
34
34
  /**
35
- * Wraps a primary (AST/tokenizer) parser with a legacy (regex/line-based)
35
+ * Wraps a primary (AST/tokenizer) parser with a regex/line-based
36
36
  * fallback. If the primary throws, a warning is logged and the fallback is
37
37
  * used instead.
38
38
  *
39
39
  * Returns a {@link ParserResult} so callers (and tests) can detect which
40
40
  * code path was taken and surface it in logs or assertions.
41
41
  *
42
- * @param primary - The modern parser implementation
43
- * @param fallback - The legacy fallback implementation
42
+ * @param primary - The primary parser implementation
43
+ * @param fallback - The regex/line-based fallback implementation
44
44
  * @param context - File/context name used in the warning message
45
45
  * @param rethrowIf - Optional predicate. If it returns true for the caught
46
46
  * error the error is re-thrown instead of falling back (useful for
@@ -18,3 +18,9 @@ import type { PatchMetadata } from '../types/commands/index.js';
18
18
  * `InvalidArgumentError` body, never the full queue enumeration.
19
19
  */
20
20
  export declare function formatPatchNotFoundError(identifier: string, patches: readonly PatchMetadata[]): string;
21
+ /**
22
+ * The nearest-match half of {@link formatPatchNotFoundError}: the closest
23
+ * accepted identifiers and the queue size, never the full queue. For
24
+ * callers that word their own lead, such as the `--patches` subset.
25
+ */
26
+ export declare function formatPatchSuggestions(identifier: string, patches: readonly PatchMetadata[]): string;
@@ -118,11 +118,18 @@ function rankSuggestions(identifier, candidates) {
118
118
  * `InvalidArgumentError` body, never the full queue enumeration.
119
119
  */
120
120
  export function formatPatchNotFoundError(identifier, patches) {
121
- const accepted = collectAcceptedIdentifiers(patches);
122
- const suggestions = rankSuggestions(identifier, accepted);
123
121
  const lead = `Patch "${identifier}" not found. Accepted identifiers: ordinal (e.g. 2), filename (e.g. 002-ui-foo.patch), or manifest name (e.g. ui-foo).`;
122
+ return `${lead} ${formatPatchSuggestions(identifier, patches)}`;
123
+ }
124
+ /**
125
+ * The nearest-match half of {@link formatPatchNotFoundError}: the closest
126
+ * accepted identifiers and the queue size, never the full queue. For
127
+ * callers that word their own lead, such as the `--patches` subset.
128
+ */
129
+ export function formatPatchSuggestions(identifier, patches) {
130
+ const suggestions = rankSuggestions(identifier, collectAcceptedIdentifiers(patches));
124
131
  if (suggestions.length > 0) {
125
- return `${lead} Did you mean: ${suggestions.join(', ')}? (${patches.length} patches in queue — run "fireforge patch list" for the full list.)`;
132
+ return `Did you mean: ${suggestions.join(', ')}? (${patches.length} patches in queue — run "fireforge patch list" for the full list.)`;
126
133
  }
127
- return `${lead} No close match found among ${patches.length} patches in the queue. Run "fireforge patch list" to see them.`;
134
+ return `No close match found among ${patches.length} patches in the queue. Run "fireforge patch list" to see them.`;
128
135
  }
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Separated from `patch-lint.ts` to keep both files within the
11
11
  * project's per-file line budget. The shim itself and the suppressed
12
- * diagnostic code list now live in `typecheck-shim.ts` and are shared
12
+ * diagnostic code list live in `typecheck-shim.ts` and are shared
13
13
  * with the whole-project `fireforge typecheck` command, keeping a
14
14
  * single source of truth for the Firefox-globals coverage.
15
15
  * `patchLint.checkJsStrict` only tightens `strict` / `noImplicitAny`
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * Separated from `patch-lint.ts` to keep both files within the
12
12
  * project's per-file line budget. The shim itself and the suppressed
13
- * diagnostic code list now live in `typecheck-shim.ts` and are shared
13
+ * diagnostic code list live in `typecheck-shim.ts` and are shared
14
14
  * with the whole-project `fireforge typecheck` command, keeping a
15
15
  * single source of truth for the Firefox-globals coverage.
16
16
  * `patchLint.checkJsStrict` only tightens `strict` / `noImplicitAny`
@@ -65,7 +65,7 @@ const RAW_COLOR_IGNORE_MARKER = 'fireforge-ignore: raw-color-value';
65
65
  * comment spanning the context/added boundary suppresses its hex, then
66
66
  * only the patch's added line numbers are scanned. Returns undefined
67
67
  * when any added line number falls outside the on-disk file (patch not
68
- * applied / drifted). The caller then falls back to the legacy
68
+ * applied / drifted). The caller then falls back to the
69
69
  * joined-added-lines scan rather than misattributing lines.
70
70
  */
71
71
  function buildAddedLinesScanSource(rawCss, addedNumbers) {
@@ -100,7 +100,7 @@ function checkRawColorValues(input) {
100
100
  suppressedContent = buildAddedLinesScanSource(rawCss, addedNumbers);
101
101
  }
102
102
  if (suppressedContent === undefined) {
103
- // Legacy scan: full file (no diff), or the joined added lines when
103
+ // Fallback scan: full file (no diff), or the joined added lines when
104
104
  // the diff and on-disk file disagree. Waiver lines are filtered
105
105
  // against the pre-strip text so the CSS comment marker is present.
106
106
  const sourceForSuppression = addedLinesByFile
@@ -6,13 +6,11 @@
6
6
  * `patch-lint.ts` from `config-validate.ts` for a constant would draw an
7
7
  * import edge (and a cycle) between the rule bodies and config parsing.
8
8
  *
9
- * On the vocabulary: the bands were `notice` / `warning` / `error` with the
10
- * middle one described as a "soft limit". That description is false under
11
- * the posture FireForge itself recommends for a release gate,
12
- * `--max-warnings 0`, where a warning is a hard failure, so a file at 751
13
- * lines failed a gate with a message calling 750 soft. The message below
14
- * says what the band is (a warning) and what a zero-warning gate does with
15
- * it, and the thresholds are now tunable, so a project whose file-size
9
+ * On the vocabulary: the bands are `notice` / `warning` / `error`, and the
10
+ * middle one is not a "soft limit". Under the posture FireForge itself
11
+ * recommends for a release gate, `--max-warnings 0`, a warning is a hard
12
+ * failure. The message below therefore says what the band is (a warning)
13
+ * and what a zero-warning gate does with it, and the thresholds are tunable, so a project whose file-size
16
14
  * policy differs has a dial instead of an argument. The notice band is the
17
15
  * design target: advisory, and its message says so rather than borrowing
18
16
  * the warning band's gate sentence.
@@ -7,13 +7,11 @@
7
7
  * `patch-lint.ts` from `config-validate.ts` for a constant would draw an
8
8
  * import edge (and a cycle) between the rule bodies and config parsing.
9
9
  *
10
- * On the vocabulary: the bands were `notice` / `warning` / `error` with the
11
- * middle one described as a "soft limit". That description is false under
12
- * the posture FireForge itself recommends for a release gate,
13
- * `--max-warnings 0`, where a warning is a hard failure, so a file at 751
14
- * lines failed a gate with a message calling 750 soft. The message below
15
- * says what the band is (a warning) and what a zero-warning gate does with
16
- * it, and the thresholds are now tunable, so a project whose file-size
10
+ * On the vocabulary: the bands are `notice` / `warning` / `error`, and the
11
+ * middle one is not a "soft limit". Under the posture FireForge itself
12
+ * recommends for a release gate, `--max-warnings 0`, a warning is a hard
13
+ * failure. The message below therefore says what the band is (a warning)
14
+ * and what a zero-warning gate does with it, and the thresholds are tunable, so a project whose file-size
17
15
  * policy differs has a dial instead of an argument. The notice band is the
18
16
  * design target: advisory, and its message says so rather than borrowing
19
17
  * the warning band's gate sentence.
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * An entry is either a bare check ID, which suppresses that rule at any
5
5
  * size, or a ceiling `{ rule, max, reason }`, which suppresses a size rule
6
- * only while its measurement is at or below `max`. The bare form let a
7
- * waived file grow without bound: nothing re-examined the waiver once it
8
- * was written, so a 950-line file waived at review time was still waived at
6
+ * only while its measurement is at or below `max`. The bare form lets a
7
+ * waived file grow without bound: nothing re-examines the waiver once it
8
+ * is written, so a 950-line file waived at review time is still waived at
9
9
  * 2,000 lines. The ceiling makes growth past the reviewed size fire again,
10
10
  * and lets a gate say when the waiver is no longer needed at all.
11
11
  *
@@ -4,9 +4,9 @@
4
4
  *
5
5
  * An entry is either a bare check ID, which suppresses that rule at any
6
6
  * size, or a ceiling `{ rule, max, reason }`, which suppresses a size rule
7
- * only while its measurement is at or below `max`. The bare form let a
8
- * waived file grow without bound: nothing re-examined the waiver once it
9
- * was written, so a 950-line file waived at review time was still waived at
7
+ * only while its measurement is at or below `max`. The bare form lets a
8
+ * waived file grow without bound: nothing re-examines the waiver once it
9
+ * is written, so a 950-line file waived at review time is still waived at
10
10
  * 2,000 lines. The ceiling makes growth past the reviewed size fire again,
11
11
  * and lets a gate say when the waiver is no longer needed at all.
12
12
  *
@@ -1,14 +1,12 @@
1
1
  /**
2
2
  * Optional Prettier pass over patch-owned `.sys.mjs` modules.
3
3
  *
4
- * Until 0.45.0 no tier of the chain ran Prettier on `.sys.mjs` at all, so
5
- * module formatting drift was caught by nothing and survived to a human
6
- * reviewer. Worse, the obvious manual check was misleading in the same
7
- * direction: a fork's root `.prettierignore` typically excludes `engine/`,
4
+ * Without this pass no tier of the chain runs Prettier on `.sys.mjs`, so
5
+ * module formatting drift survives to a human reviewer. The obvious manual
6
+ * check is misleading in the same direction: a fork's root `.prettierignore` typically excludes `engine/`,
8
7
  * so `prettier --check --config engine/.prettierrc.js <engine files>` run
9
8
  * from the repo root reports "all files use Prettier code style" for files
10
- * that fail the identical check run from inside `engine/`. A root-level
11
- * spot-check falsely refuted a correct reviewer finding on exactly this.
9
+ * that fail the identical check run from inside `engine/`.
12
10
  *
13
11
  * This pass therefore runs prettier with `cwd` set to the engine directory,
14
12
  * so the engine's own `.prettierrc*` and `.prettierignore` resolve the way
@@ -2,14 +2,12 @@
2
2
  /**
3
3
  * Optional Prettier pass over patch-owned `.sys.mjs` modules.
4
4
  *
5
- * Until 0.45.0 no tier of the chain ran Prettier on `.sys.mjs` at all, so
6
- * module formatting drift was caught by nothing and survived to a human
7
- * reviewer. Worse, the obvious manual check was misleading in the same
8
- * direction: a fork's root `.prettierignore` typically excludes `engine/`,
5
+ * Without this pass no tier of the chain runs Prettier on `.sys.mjs`, so
6
+ * module formatting drift survives to a human reviewer. The obvious manual
7
+ * check is misleading in the same direction: a fork's root `.prettierignore` typically excludes `engine/`,
9
8
  * so `prettier --check --config engine/.prettierrc.js <engine files>` run
10
9
  * from the repo root reports "all files use Prettier code style" for files
11
- * that fail the identical check run from inside `engine/`. A root-level
12
- * spot-check falsely refuted a correct reviewer finding on exactly this.
10
+ * that fail the identical check run from inside `engine/`.
13
11
  *
14
12
  * This pass therefore runs prettier with `cwd` set to the engine directory,
15
13
  * so the engine's own `.prettierrc*` and `.prettierignore` resolve the way
@@ -8,7 +8,7 @@ import type { PatchLintIssue } from '../types/commands/index.js';
8
8
  * whose message text can drift between otherwise-equivalent runs (for
9
9
  * example because the message embeds later-owner filenames or positional
10
10
  * detail). Falling back to the full tuple keeps the helper conservative
11
- * for older/non-fingerprinted rules: if their message changes, we would
11
+ * for non-fingerprinted rules: if their message changes, we would
12
12
  * rather surface a potential regression than silently swallow it.
13
13
  *
14
14
  * Consumption order within the projected list is stable (the input
@@ -7,7 +7,7 @@
7
7
  * whose message text can drift between otherwise-equivalent runs (for
8
8
  * example because the message embeds later-owner filenames or positional
9
9
  * detail). Falling back to the full tuple keeps the helper conservative
10
- * for older/non-fingerprinted rules: if their message changes, we would
10
+ * for non-fingerprinted rules: if their message changes, we would
11
11
  * rather surface a potential regression than silently swallow it.
12
12
  *
13
13
  * Consumption order within the projected list is stable (the input
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * A leaf module by design: it imports nothing, so the individual rule
5
5
  * modules can describe the slice of `PatchQueueContext` they read without
6
- * importing `patch-lint-cross.ts` (which imports them back). Previously each
7
- * rule re-declared its own private `{ entries }` interface. Sharing them here
6
+ * importing `patch-lint-cross.ts` (which imports them back). Sharing them
7
+ * here instead of re-declaring a private `{ entries }` interface per rule
8
8
  * keeps the dependency edge one-way and `dpdm` clean while stating the shape
9
9
  * once.
10
10
  */
@@ -4,8 +4,8 @@
4
4
  *
5
5
  * A leaf module by design: it imports nothing, so the individual rule
6
6
  * modules can describe the slice of `PatchQueueContext` they read without
7
- * importing `patch-lint-cross.ts` (which imports them back). Previously each
8
- * rule re-declared its own private `{ entries }` interface. Sharing them here
7
+ * importing `patch-lint-cross.ts` (which imports them back). Sharing them
8
+ * here instead of re-declaring a private `{ entries }` interface per rule
9
9
  * keeps the dependency edge one-way and `dpdm` clean while stating the shape
10
10
  * once.
11
11
  */
@@ -20,7 +20,7 @@ export declare function validatePatchesManifestConsistency(patchesDir: string):
20
20
  *
21
21
  * Drift that is only in `filesAffected` is a derived value disagreeing with
22
22
  * the diff it describes, and the narrow repair fixes exactly that. Naming the
23
- * whole-manifest rebuild for it (as this hint used to, unconditionally) puts
23
+ * whole-manifest rebuild for it puts
24
24
  * an operator one keystroke from rewriting every row in the manifest to
25
25
  * correct one list.
26
26
  */
@@ -80,7 +80,7 @@ export interface RebuildPatchesManifestOptions extends PatchDirectoryLockOptions
80
80
  * becomes a whole-manifest reinvention. Pass `allowMetadataLoss` to accept it.
81
81
  *
82
82
  * @param patchesDir - Path to the patches directory
83
- * @param fallbackSourceEsrVersion - source version to use for recovered legacy entries
83
+ * @param fallbackSourceEsrVersion - source version to use for recovered entries that carry none
84
84
  * @param options - Lock options plus `allowMetadataLoss` / `dryRun`
85
85
  * @returns {@link RebuildPatchesManifestResult}: the rebuilt manifest, the
86
86
  * filenames that were reconstructed from generic defaults, and the rows
@@ -87,7 +87,7 @@ export async function validatePatchesManifestConsistency(patchesDir) {
87
87
  *
88
88
  * Drift that is only in `filesAffected` is a derived value disagreeing with
89
89
  * the diff it describes, and the narrow repair fixes exactly that. Naming the
90
- * whole-manifest rebuild for it (as this hint used to, unconditionally) puts
90
+ * whole-manifest rebuild for it puts
91
91
  * an operator one keystroke from rewriting every row in the manifest to
92
92
  * correct one list.
93
93
  */
@@ -115,7 +115,7 @@ export function recommendManifestRepair(issues) {
115
115
  * becomes a whole-manifest reinvention. Pass `allowMetadataLoss` to accept it.
116
116
  *
117
117
  * @param patchesDir - Path to the patches directory
118
- * @param fallbackSourceEsrVersion - source version to use for recovered legacy entries
118
+ * @param fallbackSourceEsrVersion - source version to use for recovered entries that carry none
119
119
  * @param options - Lock options plus `allowMetadataLoss` / `dryRun`
120
120
  * @returns {@link RebuildPatchesManifestResult}: the rebuilt manifest, the
121
121
  * filenames that were reconstructed from generic defaults, and the rows
@@ -65,10 +65,14 @@ export declare function loadPatchesManifestForWrite(patchesDir: string): Promise
65
65
  */
66
66
  export declare function savePatchesManifest(patchesDir: string, manifest: PatchesManifest): Promise<void>;
67
67
  /**
68
- * Mutates selected manifest rows while preserving the raw JSON shape of every
68
+ * Mutates selected manifest rows while preserving the raw JSON fields of every
69
69
  * untouched row. This avoids serializing reader-only fallback fields, such as
70
70
  * `sourceVersion` derived from legacy `sourceEsrVersion`, into unrelated patch
71
71
  * entries during partial metadata updates.
72
+ *
73
+ * Every row's keys (and the manifest's own) are written in canonical order,
74
+ * not only the changed rows', so the working copy matches what `patch stage`
75
+ * and full saves write and no commit flips untouched rows' key order.
72
76
  */
73
77
  export declare function mutatePatchRowsInManifest(patchesDir: string, filenames: readonly string[], mutator: (existing: PatchMetadata, rawExisting: Readonly<JsonObject>) => PatchManifestRowMutation | null): Promise<PatchManifestRowMutationResult[] | null>;
74
78
  /**
@@ -19,7 +19,7 @@ import { pathExistsStrict, readJson, removeFile, writeJson } from '../utils/fs.j
19
19
  import { warn } from '../utils/logger.js';
20
20
  import { isArray, isObject } from '../utils/validation.js';
21
21
  import { assertPatchDirectoryLockHeld } from './patch-lock.js';
22
- import { canonicalizeManifest, canonicalizePatchRow } from './patch-manifest-key-order.js';
22
+ import { canonicalizeManifest } from './patch-manifest-key-order.js';
23
23
  import { validatePatchesManifest } from './patch-manifest-validate.js';
24
24
  import { manifestWithRetiredSlots } from './patch-retired.js';
25
25
  /** Filename for the patches manifest */
@@ -90,10 +90,14 @@ export async function savePatchesManifest(patchesDir, manifest) {
90
90
  await writeJson(manifestPath, canonicalizeManifest(manifest));
91
91
  }
92
92
  /**
93
- * Mutates selected manifest rows while preserving the raw JSON shape of every
93
+ * Mutates selected manifest rows while preserving the raw JSON fields of every
94
94
  * untouched row. This avoids serializing reader-only fallback fields, such as
95
95
  * `sourceVersion` derived from legacy `sourceEsrVersion`, into unrelated patch
96
96
  * entries during partial metadata updates.
97
+ *
98
+ * Every row's keys (and the manifest's own) are written in canonical order,
99
+ * not only the changed rows', so the working copy matches what `patch stage`
100
+ * and full saves write and no commit flips untouched rows' key order.
97
101
  */
98
102
  export async function mutatePatchRowsInManifest(patchesDir, filenames, mutator) {
99
103
  const manifestPath = join(patchesDir, PATCHES_MANIFEST);
@@ -140,16 +144,14 @@ export async function mutatePatchRowsInManifest(patchesDir, filenames, mutator)
140
144
  }
141
145
  if (changedIndexes.length === 0)
142
146
  return [];
143
- // Only the rows this call changed are put in canonical order, so a key
144
- // it sets lands where every other writer puts it; untouched rows keep
145
- // their raw shape.
146
- for (const index of changedIndexes) {
147
- rawPatches[index] = canonicalizePatchRow(rawPatches[index]);
148
- }
149
- const nextRawManifest = {
147
+ // Every row is put in canonical key order, not only the changed ones:
148
+ // `patch stage` and full saves canonicalize the whole manifest, so a
149
+ // row left in any other order flips back and forth between the
150
+ // working copy and the index. Reordering keys adds no field.
151
+ const nextRawManifest = canonicalizeManifest({
150
152
  ...rawManifest,
151
153
  patches: rawPatches,
152
- };
154
+ });
153
155
  const afterManifest = validatePatchesManifest(nextRawManifest);
154
156
  await writeJson(manifestPath, nextRawManifest);
155
157
  return changedIndexes.map((index) => ({
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * The one key order every `patches.json` writer uses for a patch row and
3
- * for the manifest itself. Writers used to disagree: `export` spread
4
- * `sourceProduct` before `sourceVersion` while a full-manifest save put it
5
- * after `filesAffected`, so a no-op rewrite left a key-order-only hunk in
6
- * the manifest.
3
+ * for the manifest itself, so that every writer (`export`, full-manifest
4
+ * saves, `patch stage`) agrees and a no-op rewrite leaves no key-order-only
5
+ * hunk in the manifest.
7
6
  */
8
7
  /**
9
8
  * Returns a copy of a patch row with its keys in canonical order.
@@ -1,10 +1,9 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  /**
3
3
  * The one key order every `patches.json` writer uses for a patch row and
4
- * for the manifest itself. Writers used to disagree: `export` spread
5
- * `sourceProduct` before `sourceVersion` while a full-manifest save put it
6
- * after `filesAffected`, so a no-op rewrite left a key-order-only hunk in
7
- * the manifest.
4
+ * for the manifest itself, so that every writer (`export`, full-manifest
5
+ * saves, `patch stage`) agrees and a no-op rewrite leaves no key-order-only
6
+ * hunk in the manifest.
8
7
  */
9
8
  /** Patch-row keys in the order of the `PatchMetadata` interface. */
10
9
  const PATCH_ROW_KEY_ORDER = [
@@ -34,10 +34,10 @@ export declare function extractOrder(filename: string): number;
34
34
  * The write-side counterpart to {@link extractOrder}. That one answers
35
35
  * `Infinity` for a prefix-less filename, which sorts such a patch last and
36
36
  * is the right answer for a read: an unparseable name should not crash a
37
- * status listing. Persisting `Infinity` (or the `0` some call sites used to
38
- * fall back to) is a different matter: it puts an order in the manifest
37
+ * status listing. Persisting `Infinity` (or a fallback `0`) is a different
38
+ * matter: it puts an order in the manifest
39
39
  * that no filename on disk agrees with, and the queue then applies in an
40
- * order the operator cannot see. Callers here have always just built or
40
+ * order the operator cannot see. Callers here have just built or
41
41
  * looked up a real prefixed filename, so absence is a bug, not input.
42
42
  *
43
43
  * @param filename - Patch filename carrying a numeric order prefix
@@ -56,10 +56,10 @@ export function extractOrder(filename) {
56
56
  * The write-side counterpart to {@link extractOrder}. That one answers
57
57
  * `Infinity` for a prefix-less filename, which sorts such a patch last and
58
58
  * is the right answer for a read: an unparseable name should not crash a
59
- * status listing. Persisting `Infinity` (or the `0` some call sites used to
60
- * fall back to) is a different matter: it puts an order in the manifest
59
+ * status listing. Persisting `Infinity` (or a fallback `0`) is a different
60
+ * matter: it puts an order in the manifest
61
61
  * that no filename on disk agrees with, and the queue then applies in an
62
- * order the operator cannot see. Callers here have always just built or
62
+ * order the operator cannot see. Callers here have just built or
63
63
  * looked up a real prefixed filename, so absence is a bug, not input.
64
64
  *
65
65
  * @param filename - Patch filename carrying a numeric order prefix
@@ -42,7 +42,7 @@ export interface PatchPolicyEnforcementInput {
42
42
  }
43
43
  /** Returns valid categories for prompts and CLI validation under the config. */
44
44
  export declare function getPatchPolicyCategories(config: FireForgeConfig): string[];
45
- /** Checks whether a category is accepted by legacy defaults or the policy ranges. */
45
+ /** Checks whether a category is accepted by the built-in defaults or the policy ranges. */
46
46
  export declare function isCategoryAllowedByConfig(config: FireForgeConfig, category: string): boolean;
47
47
  /**
48
48
  * Human-readable label for a category's configured ranges, e.g. `300-399`.
@@ -28,7 +28,7 @@ export function getPatchPolicyCategories(config) {
28
28
  return [...PATCH_CATEGORIES];
29
29
  return Array.from(new Set(cfg.ranges.map((range) => range.category))).sort((a, b) => a.localeCompare(b));
30
30
  }
31
- /** Checks whether a category is accepted by legacy defaults or the policy ranges. */
31
+ /** Checks whether a category is accepted by the built-in defaults or the policy ranges. */
32
32
  export function isCategoryAllowedByConfig(config, category) {
33
33
  if (!/^[a-z][a-z0-9-]*$/.test(category))
34
34
  return false;
@@ -2,5 +2,5 @@ import type { PatchMetadata } from '../types/commands/index.js';
2
2
  import type { FirefoxConfig } from '../types/config.js';
3
3
  /** Metadata fields stamped on new or refreshed patch entries. */
4
4
  export declare function buildPatchSourceMetadata(firefox: Pick<FirefoxConfig, 'product' | 'version'>): Pick<PatchMetadata, 'sourceEsrVersion' | 'sourceProduct' | 'sourceVersion'>;
5
- /** Backward-compatible source version reader for legacy manifests. */
5
+ /** Source version reader that also accepts rows carrying only `sourceEsrVersion`. */
6
6
  export declare function getPatchSourceVersion(patch: Pick<PatchMetadata, 'sourceEsrVersion' | 'sourceVersion'>): string;
@@ -6,7 +6,7 @@ export function buildPatchSourceMetadata(firefox) {
6
6
  sourceVersion: firefox.version,
7
7
  };
8
8
  }
9
- /** Backward-compatible source version reader for legacy manifests. */
9
+ /** Source version reader that also accepts rows carrying only `sourceEsrVersion`. */
10
10
  export function getPatchSourceVersion(patch) {
11
11
  return patch.sourceVersion ?? patch.sourceEsrVersion;
12
12
  }
@@ -3,12 +3,14 @@ import type { PatchesManifest, PatchMetadata } from '../types/commands/index.js'
3
3
  * Resolves a `--patches <name…>` subset against the manifest, matching each
4
4
  * requested name tolerantly (exact filename, filename ± `.patch`, the bare
5
5
  * order number, the manifest `name` field, or the slug). Accepts repeated
6
- * values and comma lists. Throws listing the available patches when a
6
+ * values and lists separated by commas or whitespace, so a list passed as
7
+ * one shell word (`--patches "$P"` under zsh) still splits. Throws when a
7
8
  * requested name matches none, so a typo fails loud rather than silently
8
- * judging nothing.
9
+ * judging nothing; the refusal names that name and its nearest matches,
10
+ * not the whole queue.
9
11
  *
10
12
  * @param manifest - The loaded patch manifest
11
- * @param requested - Raw `--patches` values (each may be a comma list)
13
+ * @param requested - Raw `--patches` values (each may be a comma or whitespace list)
12
14
  * @param refusalHint - Sentence inserted into the no-match refusal, naming
13
15
  * what the caller's flag selects
14
16
  * @returns The matching manifest rows, de-duplicated, in request order