@hominis/fireforge 0.42.0 → 0.43.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 (199) hide show
  1. package/CHANGELOG.md +76 -34
  2. package/dist/build-info.json +4 -4
  3. package/dist/src/cli.js +21 -2
  4. package/dist/src/commands/build.js +7 -1
  5. package/dist/src/commands/config.js +16 -27
  6. package/dist/src/commands/discard.js +1 -1
  7. package/dist/src/commands/export-flow.js +13 -3
  8. package/dist/src/commands/export-placement-conflicts.js +1 -1
  9. package/dist/src/commands/export-shared.d.ts +1 -1
  10. package/dist/src/commands/export-shared.js +4 -5
  11. package/dist/src/commands/furnace/chrome-doc-remove.js +3 -0
  12. package/dist/src/commands/furnace/chrome-doc.js +3 -0
  13. package/dist/src/commands/furnace/create.js +3 -0
  14. package/dist/src/commands/furnace/override.js +3 -0
  15. package/dist/src/commands/furnace/preview.js +3 -0
  16. package/dist/src/commands/furnace/refresh.js +3 -0
  17. package/dist/src/commands/furnace/remove.js +3 -0
  18. package/dist/src/commands/furnace/rename.js +3 -0
  19. package/dist/src/commands/furnace/scan.js +3 -0
  20. package/dist/src/commands/import.js +1 -1
  21. package/dist/src/commands/lint-per-patch.js +16 -8
  22. package/dist/src/commands/lint-per-run-checkjs.d.ts +4 -4
  23. package/dist/src/commands/lint-per-run-checkjs.js +4 -4
  24. package/dist/src/commands/lint-report.js +1 -1
  25. package/dist/src/commands/lint.js +2 -2
  26. package/dist/src/commands/patch/lint-ignore.js +1 -1
  27. package/dist/src/commands/patch/move-files-create.js +5 -3
  28. package/dist/src/commands/patch/move-files-into.d.ts +1 -1
  29. package/dist/src/commands/patch/move-files-into.js +5 -3
  30. package/dist/src/commands/patch/move-files.js +1 -1
  31. package/dist/src/commands/patch/rename.js +1 -1
  32. package/dist/src/commands/patch/reorder.d.ts +1 -2
  33. package/dist/src/commands/patch/reorder.js +1 -2
  34. package/dist/src/commands/patch/split.js +5 -3
  35. package/dist/src/commands/patch/staged-dependency-validate.js +1 -1
  36. package/dist/src/commands/patch/staged-dependency.js +4 -4
  37. package/dist/src/commands/re-export-adjacent.js +1 -1
  38. package/dist/src/commands/re-export-bulk-scan.d.ts +1 -1
  39. package/dist/src/commands/re-export-bulk-scan.js +5 -4
  40. package/dist/src/commands/re-export-drift.d.ts +4 -4
  41. package/dist/src/commands/re-export-drift.js +47 -8
  42. package/dist/src/commands/re-export-lint.d.ts +7 -5
  43. package/dist/src/commands/re-export-lint.js +9 -8
  44. package/dist/src/commands/re-export-single.d.ts +1 -1
  45. package/dist/src/commands/re-export-single.js +5 -4
  46. package/dist/src/commands/re-export.js +26 -14
  47. package/dist/src/commands/reset.js +1 -1
  48. package/dist/src/commands/run.js +1 -1
  49. package/dist/src/commands/source.js +6 -6
  50. package/dist/src/commands/status-check.d.ts +1 -1
  51. package/dist/src/commands/status-check.js +1 -1
  52. package/dist/src/commands/status-json.d.ts +2 -2
  53. package/dist/src/commands/status-json.js +1 -1
  54. package/dist/src/commands/status-output.d.ts +1 -1
  55. package/dist/src/commands/status-output.js +1 -1
  56. package/dist/src/commands/status.js +21 -7
  57. package/dist/src/commands/test-build-phase.d.ts +2 -2
  58. package/dist/src/commands/test-build-phase.js +9 -6
  59. package/dist/src/commands/test-diagnose.js +8 -1
  60. package/dist/src/commands/test-modes.d.ts +2 -2
  61. package/dist/src/commands/test-modes.js +3 -3
  62. package/dist/src/commands/test-register.js +7 -2
  63. package/dist/src/commands/test-run.d.ts +6 -0
  64. package/dist/src/commands/test-run.js +32 -3
  65. package/dist/src/commands/test-stale-gate.js +7 -2
  66. package/dist/src/commands/test.js +4 -4
  67. package/dist/src/commands/tree.js +2 -2
  68. package/dist/src/commands/typecheck.d.ts +2 -1
  69. package/dist/src/commands/typecheck.js +59 -3
  70. package/dist/src/commands/verify.js +26 -1
  71. package/dist/src/core/ast-utils.js +14 -2
  72. package/dist/src/core/browser-wire.js +9 -1
  73. package/dist/src/core/build-baseline-types.d.ts +1 -1
  74. package/dist/src/core/build-baseline.d.ts +1 -1
  75. package/dist/src/core/build-baseline.js +1 -1
  76. package/dist/src/core/build-overwrite-guard.d.ts +49 -0
  77. package/dist/src/core/build-overwrite-guard.js +165 -0
  78. package/dist/src/core/build-prepare.d.ts +13 -5
  79. package/dist/src/core/build-prepare.js +42 -9
  80. package/dist/src/core/config-mutate.d.ts +3 -2
  81. package/dist/src/core/config-mutate.js +5 -2
  82. package/dist/src/core/config.d.ts +3 -2
  83. package/dist/src/core/config.js +2 -0
  84. package/dist/src/core/coverage-extend.d.ts +1 -1
  85. package/dist/src/core/coverage-extend.js +1 -1
  86. package/dist/src/core/destructive.d.ts +23 -0
  87. package/dist/src/core/destructive.js +45 -4
  88. package/dist/src/core/diff-hunks.js +31 -17
  89. package/dist/src/core/discard-baseline.d.ts +1 -1
  90. package/dist/src/core/discard-baseline.js +1 -1
  91. package/dist/src/core/display-state.d.ts +52 -0
  92. package/dist/src/core/display-state.js +89 -0
  93. package/dist/src/core/engine-session-lock.d.ts +20 -0
  94. package/dist/src/core/engine-session-lock.js +84 -14
  95. package/dist/src/core/export-moved-code-shape.d.ts +60 -0
  96. package/dist/src/core/export-moved-code-shape.js +129 -0
  97. package/dist/src/core/file-lock.d.ts +44 -0
  98. package/dist/src/core/file-lock.js +157 -42
  99. package/dist/src/core/furnace-apply-helpers.d.ts +1 -1
  100. package/dist/src/core/furnace-apply-helpers.js +1 -1
  101. package/dist/src/core/furnace-apply-output.js +1 -1
  102. package/dist/src/core/furnace-apply-overwrite-warn.d.ts +2 -2
  103. package/dist/src/core/furnace-apply-overwrite-warn.js +2 -2
  104. package/dist/src/core/furnace-apply-undeploy-actions.d.ts +1 -1
  105. package/dist/src/core/furnace-apply-undeploy-actions.js +1 -1
  106. package/dist/src/core/furnace-apply.js +13 -1
  107. package/dist/src/core/furnace-config-custom.d.ts +2 -1
  108. package/dist/src/core/furnace-config-order.d.ts +2 -1
  109. package/dist/src/core/furnace-config-order.js +9 -4
  110. package/dist/src/core/furnace-config-validate.d.ts +4 -3
  111. package/dist/src/core/furnace-config-validate.js +3 -3
  112. package/dist/src/core/furnace-config.js +5 -0
  113. package/dist/src/core/furnace-operation.d.ts +12 -0
  114. package/dist/src/core/furnace-operation.js +125 -1
  115. package/dist/src/core/furnace-rollback.d.ts +17 -0
  116. package/dist/src/core/furnace-rollback.js +35 -1
  117. package/dist/src/core/git-base.js +7 -2
  118. package/dist/src/core/git-diff.js +11 -2
  119. package/dist/src/core/git-readonly-index.d.ts +59 -0
  120. package/dist/src/core/git-readonly-index.js +137 -0
  121. package/dist/src/core/license-headers.d.ts +1 -1
  122. package/dist/src/core/license-headers.js +1 -1
  123. package/dist/src/core/lint-cache.d.ts +15 -5
  124. package/dist/src/core/lint-cache.js +35 -8
  125. package/dist/src/core/mach-known-noise-filter.d.ts +21 -0
  126. package/dist/src/core/mach-known-noise-filter.js +23 -0
  127. package/dist/src/core/mach-mozconfig.js +12 -2
  128. package/dist/src/core/module-resolution-preflight.d.ts +51 -0
  129. package/dist/src/core/module-resolution-preflight.js +155 -0
  130. package/dist/src/core/patch-export.js +2 -1
  131. package/dist/src/core/patch-identifier-suggest.js +29 -3
  132. package/dist/src/core/patch-lint-checkjs.d.ts +5 -5
  133. package/dist/src/core/patch-lint-checkjs.js +6 -6
  134. package/dist/src/core/patch-lint-creators.d.ts +1 -1
  135. package/dist/src/core/patch-lint-creators.js +1 -1
  136. package/dist/src/core/patch-lint-cross.d.ts +1 -1
  137. package/dist/src/core/patch-lint-cross.js +4 -4
  138. package/dist/src/core/patch-lint-css.js +1 -1
  139. package/dist/src/core/patch-lint-diff.d.ts +1 -1
  140. package/dist/src/core/patch-lint-diff.js +1 -1
  141. package/dist/src/core/patch-lint-jsdoc.js +4 -2
  142. package/dist/src/core/patch-lint-module-registration.d.ts +13 -0
  143. package/dist/src/core/patch-lint-module-registration.js +13 -4
  144. package/dist/src/core/patch-lint-mozbuild.d.ts +1 -1
  145. package/dist/src/core/patch-lint-mozbuild.js +1 -1
  146. package/dist/src/core/patch-lint-ownership.d.ts +2 -2
  147. package/dist/src/core/patch-lint-ownership.js +2 -2
  148. package/dist/src/core/patch-lint.d.ts +2 -2
  149. package/dist/src/core/patch-lint.js +3 -3
  150. package/dist/src/core/patch-lock.d.ts +21 -0
  151. package/dist/src/core/patch-lock.js +29 -2
  152. package/dist/src/core/patch-manifest-io.d.ts +2 -1
  153. package/dist/src/core/patch-manifest-io.js +49 -0
  154. package/dist/src/core/patch-parse.d.ts +18 -0
  155. package/dist/src/core/patch-parse.js +24 -1
  156. package/dist/src/core/patch-policy.d.ts +2 -2
  157. package/dist/src/core/patch-policy.js +2 -2
  158. package/dist/src/core/smoke-patterns.d.ts +1 -1
  159. package/dist/src/core/status-binary.d.ts +1 -1
  160. package/dist/src/core/status-binary.js +1 -1
  161. package/dist/src/core/status-classify.d.ts +1 -1
  162. package/dist/src/core/status-classify.js +2 -2
  163. package/dist/src/core/test-harness-crash.d.ts +69 -3
  164. package/dist/src/core/test-harness-crash.js +174 -9
  165. package/dist/src/core/test-stale-check.d.ts +1 -1
  166. package/dist/src/core/test-stale-check.js +1 -1
  167. package/dist/src/core/token-category.js +1 -1
  168. package/dist/src/core/token-manager.d.ts +1 -1
  169. package/dist/src/core/token-manager.js +5 -5
  170. package/dist/src/core/tree-cow.js +1 -1
  171. package/dist/src/core/tree-guard.js +2 -2
  172. package/dist/src/core/tree-store.js +1 -1
  173. package/dist/src/core/typecheck-shim.d.ts +2 -2
  174. package/dist/src/core/typecheck-shim.js +2 -2
  175. package/dist/src/core/typecheck.js +10 -0
  176. package/dist/src/core/xpcshell-profile-dir.js +1 -1
  177. package/dist/src/errors/base.d.ts +22 -1
  178. package/dist/src/errors/base.js +32 -1
  179. package/dist/src/errors/codes.d.ts +10 -0
  180. package/dist/src/errors/codes.js +10 -0
  181. package/dist/src/types/commands/options.d.ts +27 -11
  182. package/dist/src/types/commands/patches.d.ts +1 -1
  183. package/dist/src/types/config.d.ts +2 -2
  184. package/dist/src/types/furnace.d.ts +1 -1
  185. package/dist/src/types/json.d.ts +22 -0
  186. package/dist/src/types/json.js +11 -0
  187. package/dist/src/utils/assert.d.ts +46 -0
  188. package/dist/src/utils/assert.js +83 -0
  189. package/dist/src/utils/build-info.d.ts +1 -1
  190. package/dist/src/utils/build-info.js +1 -1
  191. package/dist/src/utils/logger.d.ts +20 -0
  192. package/dist/src/utils/logger.js +26 -0
  193. package/dist/src/utils/options.d.ts +12 -0
  194. package/dist/src/utils/options.js +39 -0
  195. package/dist/src/utils/stdio-drain.d.ts +1 -1
  196. package/dist/src/utils/stdio-drain.js +1 -1
  197. package/dist/src/utils/validation.d.ts +12 -1
  198. package/dist/src/utils/validation.js +13 -1
  199. package/package.json +3 -3
@@ -66,6 +66,14 @@ export interface BuildOptions {
66
66
  * for any mismatch the rewriter cannot prove safe.
67
67
  */
68
68
  rewriteMozinfo?: boolean;
69
+ /**
70
+ * Refuse the build (rather than warn) when it would overwrite engine
71
+ * content recorded in neither a patch body nor the pristine baseline
72
+ *. The default is a loud warning, because an interactive
73
+ * operator often means to discard the drift; a scripted gate on a
74
+ * multi-session checkout wants the hard stop.
75
+ */
76
+ refuseUnexportedDrift?: boolean;
69
77
  /**
70
78
  * Parsed `--wait-lock [seconds]` value (`true` for the bare flag, meaning
71
79
  * 60). Consumed at the CLI layer to bound the engine-session-lock wait;
@@ -163,7 +171,7 @@ export interface DiscardOptions {
163
171
  /**
164
172
  * Restore to pristine upstream (HEAD) instead of the patch-applied
165
173
  * baseline; deletes patch-created files (the pre-0.39.0 semantics —
166
- * FORGE F1).
174
+ *).
167
175
  */
168
176
  toUpstream?: boolean;
169
177
  /** Show what would be discarded without doing it */
@@ -293,7 +301,7 @@ export interface ReExportOptions {
293
301
  * Bypass per-patch lint result cache reads AND writes for this
294
302
  * invocation (`--no-cache`). The cache is what makes a repeat
295
303
  * re-export of an unchanged patch skip the checkJs program build
296
- * entirely (FORGE J1).
304
+ * entirely.
297
305
  */
298
306
  noCache?: boolean;
299
307
  /**
@@ -309,7 +317,7 @@ export interface ReExportOptions {
309
317
  * Engine-relative files whose drift is EXPECTED (`--expect`, repeatable;
310
318
  * requires `--refuse-foreign-drift`). The content-based drift detector
311
319
  * cannot tell the exporting session's own slice edits from another
312
- * session's (FORGE L6), so a real slice export under the refusal flag
320
+ * session's, so a real slice export under the refusal flag
313
321
  * names its intended files here: drift confined to expected files
314
322
  * proceeds, drift anywhere else still refuses.
315
323
  */
@@ -518,12 +526,12 @@ export interface TestOptions {
518
526
  * MIXED harnesses allowed) and exit without dispatching tests. One
519
527
  * ~10-minute build then covers both an xpcshell and a browser-chrome
520
528
  * half of a slice; run each harness afterwards without `--build`
521
- * (FORGE J9). Emits `FIREFORGE-VERDICT: PASS` on success.
529
+ *. Emits `FIREFORGE-VERDICT: PASS` on success.
522
530
  */
523
531
  buildOnly?: boolean;
524
532
  /**
525
533
  * Union the paths this build packages onto the recorded coverage claim
526
- * instead of REPLACING it (FORGE L1), so successive scoped builds
534
+ * instead of REPLACING it, so successive scoped builds
527
535
  * accumulate and an earlier slice's build-less runs stay covered. Valid
528
536
  * only with `--build`/`--build-only` and explicit paths. Refused
529
537
  * fail-closed unless the build anchor is unchanged: same engine HEAD,
@@ -871,6 +879,14 @@ export interface PatchCompactOptions {
871
879
  */
872
880
  export interface StatusOptions {
873
881
  raw?: boolean;
882
+ /**
883
+ * Report the engine-session lock instead of file status:
884
+ * holder pid, the command it is running, how long it has held the lock,
885
+ * and how many waiters are queued behind it. Read-only — it never
886
+ * acquires the lock, so it is safe to run while a build or test holds
887
+ * it, which is exactly when an operator asks.
888
+ */
889
+ lock?: boolean;
874
890
  unmanaged?: boolean;
875
891
  /**
876
892
  * Render a flat file→owning-patch ownership table instead of the three-
@@ -882,7 +898,7 @@ export interface StatusOptions {
882
898
  /**
883
899
  * Print the recorded test-packaging coverage of the last build baseline
884
900
  * (scope, timestamp, recording invocation) and exit. Read-only — the
885
- * counterpart to the out-of-coverage test refusal (FORGE F11).
901
+ * counterpart to the out-of-coverage test refusal.
886
902
  */
887
903
  testCoverage?: boolean;
888
904
  /** Output machine-readable JSON instead of human-readable text. */
@@ -891,7 +907,7 @@ export interface StatusOptions {
891
907
  * With `--json`: emit only the summary counts (plus the offending
892
908
  * files per fail-set classification when the `--check`/`--fail-on`
893
909
  * policy is active), omitting the per-file `files[]` payload that
894
- * grows with the queue (FORGE K8). Refused without `--json`.
910
+ * grows with the queue. Refused without `--json`.
895
911
  */
896
912
  summary?: boolean;
897
913
  /**
@@ -899,7 +915,7 @@ export interface StatusOptions {
899
915
  * `ownership` block — the flat path→owning-patch rows plus
900
916
  * managed/unmanaged/conflict counts — so a gate reads ownership,
901
917
  * classification, and the check verdict from ONE scan instead of three
902
- * back-to-back `status` invocations (FORGE L3). A modifier, not a mode:
918
+ * back-to-back `status` invocations. A modifier, not a mode:
903
919
  * exit semantics are unchanged, so ownership conflicts still fail only
904
920
  * the human `--ownership` mode. Refused without `--json`.
905
921
  */
@@ -908,7 +924,7 @@ export interface StatusOptions {
908
924
  * Exit non-zero when any classification in the fail policy is
909
925
  * non-empty (default policy: unmanaged, patch-owned-drift, conflict).
910
926
  * Composes with the default view and `--json`; refused alongside
911
- * `--raw`/`--unmanaged`/`--ownership`/`--test-coverage` (FORGE G1).
927
+ * `--raw`/`--unmanaged`/`--ownership`/`--test-coverage`.
912
928
  */
913
929
  check?: boolean;
914
930
  /**
@@ -1010,7 +1026,7 @@ export interface LintCommandOptions {
1010
1026
  *
1011
1027
  * Positional file arguments change meaning under this flag: they are
1012
1028
  * PATCH selectors resolved like {@link patches} entries, not engine
1013
- * file paths (FORGE G14).
1029
+ * file paths.
1014
1030
  */
1015
1031
  perPatch?: boolean;
1016
1032
  /**
@@ -1035,7 +1051,7 @@ export interface LintCommandOptions {
1035
1051
  /**
1036
1052
  * With `--per-patch`, write a machine-readable JSON report (schemaVersion
1037
1053
  * 1: per-patch lineCount, filesAffected, tier, thresholds, issues, and
1038
- * lintIgnore-suppressed issues) to this path (FORGE G9/G10). Requires
1054
+ * lintIgnore-suppressed issues) to this path. Requires
1039
1055
  * {@link perPatch}.
1040
1056
  */
1041
1057
  report?: string;
@@ -217,7 +217,7 @@ export interface PatchLintIssue {
217
217
  * patch, or every creator for a duplicate-creation clash). Used by the
218
218
  * export placement gate to attribute projected errors to the exported
219
219
  * patch vs pre-existing patches without parsing messages or the
220
- * rename-sensitive fingerprint (FORGE K9).
220
+ * rename-sensitive fingerprint.
221
221
  */
222
222
  patches?: string[];
223
223
  /** Severity: errors block export, warnings are advisory, notices are informational (not counted) */
@@ -184,7 +184,7 @@ export interface TypecheckConfig {
184
184
  /**
185
185
  * How to report undefined free identifiers (TS2304/TS2552). These were
186
186
  * historically suppressed as shim-gap noise, which also hid genuine
187
- * missing imports until they crashed at runtime (FORGE F12). Default
187
+ * missing imports until they crashed at runtime. Default
188
188
  * `'warning'`: visible without failing the gate. `'error'` makes them
189
189
  * blocking; `'off'` restores the historical suppression.
190
190
  */
@@ -292,7 +292,7 @@ export interface PatchLintConfig {
292
292
  /**
293
293
  * How the checkJs pass reports undefined free identifiers
294
294
  * (TS2304/TS2552). Same semantics as `typecheck.undefinedIdentifiers`
295
- * (FORGE F12); the two flows share the suppression policy so a patch
295
+ *; the two flows share the suppression policy so a patch
296
296
  * cannot pass one and fail the other. Default: 'warning'.
297
297
  */
298
298
  undefinedIdentifiers?: PatchLintSeverityGate;
@@ -263,7 +263,7 @@ export interface ApplyResult {
263
263
  /**
264
264
  * Operator-facing warnings, printed by `logApplyResult`. Populated when
265
265
  * apply overwrote a deployed, patch-owned file whose engine bytes
266
- * differed from the component source (FORGE J6) — the case where a
266
+ * differed from the component source — the case where a
267
267
  * shipped engine-only fix is silently reverted by the copy.
268
268
  */
269
269
  warnings?: string[];
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Domain types for raw JSON documents (fireforge.json, furnace.json).
3
+ *
4
+ * FireForge deliberately reads and writes some documents in their raw,
5
+ * unvalidated form — e.g. `fireforge config --force` round-trips keys that
6
+ * the validated `FireForgeConfig` schema would strip. These types give that
7
+ * pipeline a concrete value contract: every value is JSON data, never a
8
+ * function, class instance, symbol, or other non-serializable runtime value.
9
+ */
10
+ /**
11
+ * Any value representable in a JSON document.
12
+ */
13
+ export type JsonValue = string | number | boolean | null | JsonValue[] | JsonObject;
14
+ /**
15
+ * A JSON object node. `undefined` is admitted as a property value (not a
16
+ * `JsonValue`) so in-memory documents can be built field-by-field from
17
+ * optional inputs; `JSON.stringify` drops such properties on write, exactly
18
+ * like an absent key.
19
+ */
20
+ export interface JsonObject {
21
+ [key: string]: JsonValue | undefined;
22
+ }
@@ -0,0 +1,11 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Domain types for raw JSON documents (fireforge.json, furnace.json).
4
+ *
5
+ * FireForge deliberately reads and writes some documents in their raw,
6
+ * unvalidated form — e.g. `fireforge config --force` round-trips keys that
7
+ * the validated `FireForgeConfig` schema would strip. These types give that
8
+ * pipeline a concrete value contract: every value is JSON data, never a
9
+ * function, class instance, symbol, or other non-serializable runtime value.
10
+ */
11
+ export {};
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A failure description, or a thunk producing one.
3
+ *
4
+ * The thunk form exists so a hot call site pays nothing on the passing
5
+ * path — `assert(ok, () => \`bad state: ${JSON.stringify(x)}\`)` builds
6
+ * the string only when the assertion actually fails, where a plain
7
+ * template literal would build it on every call.
8
+ */
9
+ export type AssertionMessage = string | (() => string);
10
+ /**
11
+ * Asserts that an internal invariant holds.
12
+ *
13
+ * @param condition - The invariant. Falsy means it was violated.
14
+ * @param message - What was supposed to be true, phrased as the invariant
15
+ * rather than the symptom (e.g. "rollback journal registered before
16
+ * first mutation", not "journal is undefined"). Pass a thunk on a hot
17
+ * path.
18
+ * @throws {@link InternalInvariantError} when `condition` is falsy.
19
+ */
20
+ export declare function assert(condition: unknown, message: AssertionMessage): asserts condition;
21
+ /**
22
+ * Asserts that a value is neither `null` nor `undefined`, narrowing it in
23
+ * place for the statements that follow.
24
+ *
25
+ * Use this in statement position, where the value is already bound to a
26
+ * name. Use {@link expectDefined} in expression position, where it is not.
27
+ *
28
+ * @param value - The value that should be present
29
+ * @param message - What was supposed to be present, and why
30
+ * @throws {@link InternalInvariantError} when `value` is `null` or `undefined`.
31
+ */
32
+ export declare function assertDefined<T>(value: T, message: AssertionMessage): asserts value is NonNullable<T>;
33
+ /**
34
+ * Returns a value that must be present, throwing if it is not.
35
+ *
36
+ * The expression-position counterpart to {@link assertDefined}: it checks
37
+ * and returns in one step, so an indexed read can be asserted inline
38
+ * (`push({ content: expectDefined(lines[k], …) })`) without first binding
39
+ * it to a local.
40
+ *
41
+ * @param value - The value that should be present
42
+ * @param message - What was supposed to be present, and why
43
+ * @returns `value`, narrowed to exclude `null` and `undefined`
44
+ * @throws {@link InternalInvariantError} when `value` is `null` or `undefined`.
45
+ */
46
+ export declare function expectDefined<T>(value: T, message: AssertionMessage): NonNullable<T>;
@@ -0,0 +1,83 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Runtime assertions for FireForge's own internal invariants.
4
+ *
5
+ * These check state that FireForge itself established — a journal was
6
+ * registered before the first mutation, a lock is held before a write,
7
+ * manifest ordering is contiguous after a renumber. They are NOT input
8
+ * validation: anything derived from user input, the filesystem, or a
9
+ * subprocess belongs in a typed error from `src/errors/` with a
10
+ * `userMessage` that tells the operator what to do. An assertion failure
11
+ * says the opposite — that nothing the operator could have done would
12
+ * have prevented it.
13
+ *
14
+ * Every check routes through a helper here rather than being open-coded
15
+ * as `if (!condition) throw`. That is deliberate: an inline throw adds a
16
+ * permanently-uncovered branch to every function it appears in, and the
17
+ * per-module coverage floors in `scripts/check-coverage-thresholds.mjs`
18
+ * would pay for it in ~40 places. Routed through a call, the branch lives
19
+ * here once and is covered here once, and the `complexity` ceiling in
20
+ * `eslint.config.js` does not see it at all.
21
+ *
22
+ * This module is a leaf: it imports the error class and nothing else, per
23
+ * the dependency-direction note in `src/utils/errors.ts`.
24
+ */
25
+ import { InternalInvariantError } from '../errors/base.js';
26
+ /**
27
+ * Resolves an {@link AssertionMessage} at failure time.
28
+ * @param message - Literal description, or a thunk producing one
29
+ * @returns The description text
30
+ */
31
+ function resolveMessage(message) {
32
+ return typeof message === 'function' ? message() : message;
33
+ }
34
+ /**
35
+ * Asserts that an internal invariant holds.
36
+ *
37
+ * @param condition - The invariant. Falsy means it was violated.
38
+ * @param message - What was supposed to be true, phrased as the invariant
39
+ * rather than the symptom (e.g. "rollback journal registered before
40
+ * first mutation", not "journal is undefined"). Pass a thunk on a hot
41
+ * path.
42
+ * @throws {@link InternalInvariantError} when `condition` is falsy.
43
+ */
44
+ export function assert(condition, message) {
45
+ if (!condition) {
46
+ throw new InternalInvariantError(resolveMessage(message));
47
+ }
48
+ }
49
+ /**
50
+ * Asserts that a value is neither `null` nor `undefined`, narrowing it in
51
+ * place for the statements that follow.
52
+ *
53
+ * Use this in statement position, where the value is already bound to a
54
+ * name. Use {@link expectDefined} in expression position, where it is not.
55
+ *
56
+ * @param value - The value that should be present
57
+ * @param message - What was supposed to be present, and why
58
+ * @throws {@link InternalInvariantError} when `value` is `null` or `undefined`.
59
+ */
60
+ export function assertDefined(value, message) {
61
+ if (value === null || value === undefined) {
62
+ throw new InternalInvariantError(resolveMessage(message));
63
+ }
64
+ }
65
+ /**
66
+ * Returns a value that must be present, throwing if it is not.
67
+ *
68
+ * The expression-position counterpart to {@link assertDefined}: it checks
69
+ * and returns in one step, so an indexed read can be asserted inline
70
+ * (`push({ content: expectDefined(lines[k], …) })`) without first binding
71
+ * it to a local.
72
+ *
73
+ * @param value - The value that should be present
74
+ * @param message - What was supposed to be present, and why
75
+ * @returns `value`, narrowed to exclude `null` and `undefined`
76
+ * @throws {@link InternalInvariantError} when `value` is `null` or `undefined`.
77
+ */
78
+ export function expectDefined(value, message) {
79
+ if (value === null || value === undefined) {
80
+ throw new InternalInvariantError(resolveMessage(message));
81
+ }
82
+ return value;
83
+ }
@@ -4,7 +4,7 @@ export interface BuildIdentity {
4
4
  dirty: boolean;
5
5
  /**
6
6
  * Content hash of the uncommitted diff, present only for a DIRTY
7
- * installed build (FORGE L8). Two packs from the same HEAD with
7
+ * installed build. Two packs from the same HEAD with
8
8
  * different uncommitted content share `shortCommit` and the `.dirty`
9
9
  * marker, so without this they report byte-identical `--version`
10
10
  * strings — which is what made a field report against
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  /**
3
- * Build identity for `--version` (FORGE K2/K3/L8). Three distinct builds
3
+ * Build identity for `--version`. Three distinct builds
4
4
  * shipped as plain "0.41.0" — consumers had to fingerprint tarballs by
5
5
  * sha256 to know which build a claim was verified against. `--version`
6
6
  * now reports `<semver>+g<short-sha>[.dirty[.<content-hash>]]`:
@@ -38,6 +38,26 @@ export declare function info(message: string): void;
38
38
  export declare function success(message: string): void;
39
39
  /** Logs a warning message. */
40
40
  export declare function warn(message: string): void;
41
+ /**
42
+ * Prefix that marks a line as FireForge's own explanatory output rather
43
+ * than a defect. Kept greppable so downstream filters can whitelist it.
44
+ */
45
+ export declare const NOTICE_PREFIX = "[FireForge] NOTICE:";
46
+ /**
47
+ * Logs one of FireForge's own "why this is happening / why this is slow"
48
+ * explanations at WARNING severity.
49
+ *
50
+ * These lines are not warnings about the operator's code — they explain a
51
+ * decision FireForge just made (escalating an incremental build to a full
52
+ * one, sharding a run, skipping a step). Emitted through `info` they were
53
+ * silently dropped by agent-facing output filters that keep only warnings
54
+ * and errors, leaving a multi-minute build with no explanation and reading
55
+ * as a hang. There are very few such lines and they are exactly the ones a
56
+ * non-interactive operator needs, so they ride the warning channel and
57
+ * carry {@link NOTICE_PREFIX} so a reader can still tell them apart from a
58
+ * real warning.
59
+ */
60
+ export declare function notice(message: string): void;
41
61
  /** Logs an error message. */
42
62
  export declare function error(message: string): void;
43
63
  /** Logs an in-progress step message. */
@@ -117,6 +117,32 @@ export function warn(message) {
117
117
  }
118
118
  p.log.warn(message);
119
119
  }
120
+ /**
121
+ * Prefix that marks a line as FireForge's own explanatory output rather
122
+ * than a defect. Kept greppable so downstream filters can whitelist it.
123
+ */
124
+ export const NOTICE_PREFIX = '[FireForge] NOTICE:';
125
+ /**
126
+ * Logs one of FireForge's own "why this is happening / why this is slow"
127
+ * explanations at WARNING severity.
128
+ *
129
+ * These lines are not warnings about the operator's code — they explain a
130
+ * decision FireForge just made (escalating an incremental build to a full
131
+ * one, sharding a run, skipping a step). Emitted through `info` they were
132
+ * silently dropped by agent-facing output filters that keep only warnings
133
+ * and errors, leaving a multi-minute build with no explanation and reading
134
+ * as a hang. There are very few such lines and they are exactly the ones a
135
+ * non-interactive operator needs, so they ride the warning channel and
136
+ * carry {@link NOTICE_PREFIX} so a reader can still tell them apart from a
137
+ * real warning.
138
+ */
139
+ export function notice(message) {
140
+ if (routeToStderr()) {
141
+ writeDiagnostic('warning: ', `${NOTICE_PREFIX} ${message}`);
142
+ return;
143
+ }
144
+ p.log.warn(`${NOTICE_PREFIX} ${message}`);
145
+ }
120
146
  /** Logs an error message. */
121
147
  export function error(message) {
122
148
  if (routeToStderr()) {
@@ -49,6 +49,18 @@ export declare function stringListOption(): [(value: string, previous: string[])
49
49
  * invalid-argument channel).
50
50
  */
51
51
  export declare function resolveWaitLockSeconds(value: string | number | boolean | undefined): number | undefined;
52
+ /**
53
+ * True when `command` already declares `--wait-lock` itself. Used by the
54
+ * CLI wiring to add the accept-and-ignore variant only where the honoring
55
+ * one is absent.
56
+ */
57
+ export declare function hasWaitLockOption(command: Command): boolean;
58
+ /**
59
+ * Recursively gives every command in the tree a `--wait-lock` flag: the
60
+ * honoring registration where one already exists, the accept-and-ignore
61
+ * one everywhere else.
62
+ */
63
+ export declare function ensureWaitLockOptionEverywhere(command: Command): void;
52
64
  /**
53
65
  * Registers the shared `--wait-lock [seconds]` flag on a lock-taking
54
66
  * command (the engine session lock for engine-mutating commands, the patch
@@ -86,6 +86,45 @@ export function resolveWaitLockSeconds(value) {
86
86
  }
87
87
  return n;
88
88
  }
89
+ /**
90
+ * Registers `--wait-lock [seconds]` on a command that takes NO lock, so a
91
+ * scripted sequence can blanket-append the flag without hitting a usage
92
+ * error.
93
+ *
94
+ * `export` / `re-export` / `build` / `test` / `patch move-files` all
95
+ * accept the flag; `status` and `patch staged-dependency` used to reject
96
+ * it with "unknown option", which killed the whole sequence with a usage
97
+ * error instead of a lock message. The flag is accepted and ignored here —
98
+ * never silently repurposed — and the help text says so, so nobody reads
99
+ * its presence as evidence that this command waits for anything.
100
+ *
101
+ * The value is parsed and validated exactly as the honoring variant does:
102
+ * `--wait-lock nonsense` must still be a usage error everywhere, or the
103
+ * uniformity would be a lie.
104
+ */
105
+ function addAcceptedWaitLockOption(command) {
106
+ return command.option('--wait-lock [seconds]', 'Accepted for scripting uniformity and ignored: this command takes no FireForge lock', commanderArgParser((raw) => resolveWaitLockSeconds(raw)));
107
+ }
108
+ /**
109
+ * True when `command` already declares `--wait-lock` itself. Used by the
110
+ * CLI wiring to add the accept-and-ignore variant only where the honoring
111
+ * one is absent.
112
+ */
113
+ export function hasWaitLockOption(command) {
114
+ return command.options.some((option) => option.long === '--wait-lock');
115
+ }
116
+ /**
117
+ * Recursively gives every command in the tree a `--wait-lock` flag: the
118
+ * honoring registration where one already exists, the accept-and-ignore
119
+ * one everywhere else.
120
+ */
121
+ export function ensureWaitLockOptionEverywhere(command) {
122
+ for (const sub of command.commands) {
123
+ if (!hasWaitLockOption(sub))
124
+ addAcceptedWaitLockOption(sub);
125
+ ensureWaitLockOptionEverywhere(sub);
126
+ }
127
+ }
89
128
  /**
90
129
  * Registers the shared `--wait-lock [seconds]` flag on a lock-taking
91
130
  * command (the engine session lock for engine-mutating commands, the patch
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Bounded stdio drain for delayed process exits (FORGE I1).
2
+ * Bounded stdio drain for delayed process exits.
3
3
  *
4
4
  * `process.exit()` discards whatever is still queued in an async writable —
5
5
  * and stdout IS async when it is a pipe, with a 64 KiB kernel buffer on
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  /**
3
- * Bounded stdio drain for delayed process exits (FORGE I1).
3
+ * Bounded stdio drain for delayed process exits.
4
4
  *
5
5
  * `process.exit()` discards whatever is still queued in an async writable —
6
6
  * and stdout IS async when it is a pipe, with a 64 KiB kernel buffer on
@@ -1,4 +1,5 @@
1
1
  import type { FirefoxProduct } from '../types/config.js';
2
+ import type { JsonObject, JsonValue } from '../types/json.js';
2
3
  /**
3
4
  * Checks whether a value is a string.
4
5
  * @param value - Value to check
@@ -53,6 +54,16 @@ export declare function isObject(value: unknown): value is Record<string, unknow
53
54
  * @returns True if value is an array
54
55
  */
55
56
  export declare function isArray(value: unknown): value is unknown[];
57
+ /**
58
+ * Narrows a JSON value to a JSON object node (excludes arrays and `null`).
59
+ *
60
+ * Unlike {@link isObject} this accepts only values already known to be JSON
61
+ * data, so the narrowed `JsonObject` keeps its concrete value contract —
62
+ * use it when walking a `JsonValue` tree rather than at `unknown` boundaries.
63
+ * @param value - JSON value to check
64
+ * @returns True if value is a JSON object node
65
+ */
66
+ export declare function isJsonObject(value: JsonValue | undefined): value is JsonObject;
56
67
  /**
57
68
  * Validates a Firefox version string.
58
69
  * Accepts formats like "140.9.0", "140.9.1", "140.9.0esr", "147.0b1"
@@ -150,7 +161,7 @@ export declare function validateTokenName(name: string): string | undefined;
150
161
  */
151
162
  export declare function normalizeTokenName(name: string): string;
152
163
  /**
153
- * Normalizes a patch display name against its category (FORGE G13):
164
+ * Normalizes a patch display name against its category:
154
165
  * strips a trailing `.patch` and any redundant `NNN-<category>-` /
155
166
  * `<category>-` prefixes, case-insensitively and repeatedly, mirroring
156
167
  * the filename slug pipeline's require-the-category-token rule — a bare
@@ -79,6 +79,18 @@ export function isObject(value) {
79
79
  export function isArray(value) {
80
80
  return Array.isArray(value);
81
81
  }
82
+ /**
83
+ * Narrows a JSON value to a JSON object node (excludes arrays and `null`).
84
+ *
85
+ * Unlike {@link isObject} this accepts only values already known to be JSON
86
+ * data, so the narrowed `JsonObject` keeps its concrete value contract —
87
+ * use it when walking a `JsonValue` tree rather than at `unknown` boundaries.
88
+ * @param value - JSON value to check
89
+ * @returns True if value is a JSON object node
90
+ */
91
+ export function isJsonObject(value) {
92
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
93
+ }
82
94
  /**
83
95
  * Validates a Firefox version string.
84
96
  * Accepts formats like "140.9.0", "140.9.1", "140.9.0esr", "147.0b1"
@@ -278,7 +290,7 @@ export function normalizeTokenName(name) {
278
290
  return `--${stripped}`;
279
291
  }
280
292
  /**
281
- * Normalizes a patch display name against its category (FORGE G13):
293
+ * Normalizes a patch display name against its category:
282
294
  * strips a trailing `.patch` and any redundant `NNN-<category>-` /
283
295
  * `<category>-` prefixes, case-insensitively and repeatedly, mirroring
284
296
  * the filename slug pipeline's require-the-category-token rule — a bare
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hominis/fireforge",
3
- "version": "0.42.0",
3
+ "version": "0.43.1",
4
4
  "description": "FireForge — a build tool for customizing Firefox",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -69,11 +69,11 @@
69
69
  "dpdm": "4.3.0",
70
70
  "eslint": "^10.0.0",
71
71
  "eslint-config-prettier": "^10.1.8",
72
- "eslint-plugin-jsdoc": "^63.0.0",
72
+ "eslint-plugin-jsdoc": "^64.1.0",
73
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.32.0",
76
+ "knip": "6.32.2",
77
77
  "lint-staged": "^17.0.4",
78
78
  "prettier": "3.9.6",
79
79
  "tsx": "^4.7.0",