@hominis/fireforge 0.43.1 → 0.44.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 (470) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +10 -1
  3. package/dist/bin/fireforge.d.ts +4 -9
  4. package/dist/bin/fireforge.js +42 -40
  5. package/dist/build-info.json +4 -4
  6. package/dist/src/cli.js +74 -8
  7. package/dist/src/commands/bootstrap.js +17 -20
  8. package/dist/src/commands/build.js +25 -32
  9. package/dist/src/commands/config.js +34 -41
  10. package/dist/src/commands/discard.js +8 -9
  11. package/dist/src/commands/doctor-check-core.d.ts +4 -18
  12. package/dist/src/commands/doctor-check-core.js +8 -35
  13. package/dist/src/commands/doctor-external-toolchains.js +2 -2
  14. package/dist/src/commands/doctor-furnace-config-sync.d.ts +18 -0
  15. package/dist/src/commands/{doctor-furnace-manifest-sync.js → doctor-furnace-config-sync.js} +27 -26
  16. package/dist/src/commands/doctor-furnace-jar.d.ts +7 -9
  17. package/dist/src/commands/doctor-furnace-jar.js +7 -9
  18. package/dist/src/commands/doctor-furnace.js +21 -24
  19. package/dist/src/commands/doctor-orphaned-harness.d.ts +16 -20
  20. package/dist/src/commands/doctor-orphaned-harness.js +18 -44
  21. package/dist/src/commands/doctor-post-rebase-audit.d.ts +2 -0
  22. package/dist/src/commands/{doctor/post-rebase-audit.js → doctor-post-rebase-audit.js} +3 -3
  23. package/dist/src/commands/doctor-working-tree.d.ts +10 -12
  24. package/dist/src/commands/doctor-working-tree.js +11 -18
  25. package/dist/src/commands/doctor.js +35 -53
  26. package/dist/src/commands/download.js +32 -48
  27. package/dist/src/commands/export-all.js +29 -33
  28. package/dist/src/commands/export-flow.js +6 -6
  29. package/dist/src/commands/export-placement-conflicts.js +10 -9
  30. package/dist/src/commands/export-placement-gate.js +5 -5
  31. package/dist/src/commands/export-shared.d.ts +30 -33
  32. package/dist/src/commands/export-shared.js +45 -43
  33. package/dist/src/commands/export.js +21 -27
  34. package/dist/src/commands/furnace/apply.js +14 -23
  35. package/dist/src/commands/furnace/chrome-doc-remove.js +14 -21
  36. package/dist/src/commands/furnace/chrome-doc-templates.d.ts +23 -23
  37. package/dist/src/commands/furnace/chrome-doc-templates.js +23 -23
  38. package/dist/src/commands/furnace/chrome-doc-tests.js +26 -29
  39. package/dist/src/commands/furnace/chrome-doc.d.ts +16 -17
  40. package/dist/src/commands/furnace/chrome-doc.js +31 -41
  41. package/dist/src/commands/furnace/create-browser-test.d.ts +4 -4
  42. package/dist/src/commands/furnace/create-browser-test.js +8 -8
  43. package/dist/src/commands/furnace/create-mochikit.d.ts +6 -7
  44. package/dist/src/commands/furnace/create-mochikit.js +8 -9
  45. package/dist/src/commands/furnace/create-readback.d.ts +12 -13
  46. package/dist/src/commands/furnace/create-readback.js +12 -13
  47. package/dist/src/commands/furnace/create-templates.d.ts +22 -28
  48. package/dist/src/commands/furnace/create-templates.js +35 -30
  49. package/dist/src/commands/furnace/create-xpcshell.js +2 -2
  50. package/dist/src/commands/furnace/create.js +33 -23
  51. package/dist/src/commands/furnace/deploy.js +16 -24
  52. package/dist/src/commands/furnace/diff.js +9 -9
  53. package/dist/src/commands/furnace/init.js +33 -36
  54. package/dist/src/commands/furnace/override.js +23 -25
  55. package/dist/src/commands/furnace/preview.js +47 -67
  56. package/dist/src/commands/furnace/refresh.js +12 -16
  57. package/dist/src/commands/furnace/remove-mutations.d.ts +3 -6
  58. package/dist/src/commands/furnace/remove-mutations.js +9 -16
  59. package/dist/src/commands/furnace/remove-state.js +1 -1
  60. package/dist/src/commands/furnace/remove.js +33 -38
  61. package/dist/src/commands/furnace/rename-xpcshell.d.ts +8 -11
  62. package/dist/src/commands/furnace/rename-xpcshell.js +8 -11
  63. package/dist/src/commands/furnace/rename.js +59 -69
  64. package/dist/src/commands/furnace/scan.js +19 -33
  65. package/dist/src/commands/furnace/status.js +12 -9
  66. package/dist/src/commands/furnace/sync.js +9 -16
  67. package/dist/src/commands/furnace/validate.js +20 -20
  68. package/dist/src/commands/import.js +42 -35
  69. package/dist/src/commands/lint-per-patch.js +15 -10
  70. package/dist/src/commands/lint-per-run-checkjs.d.ts +13 -13
  71. package/dist/src/commands/lint-per-run-checkjs.js +13 -13
  72. package/dist/src/commands/lint-report.js +6 -7
  73. package/dist/src/commands/lint.d.ts +15 -16
  74. package/dist/src/commands/lint.js +86 -102
  75. package/dist/src/commands/package.js +6 -11
  76. package/dist/src/commands/patch/compact.js +1 -1
  77. package/dist/src/commands/patch/delete.js +24 -33
  78. package/dist/src/commands/patch/index.d.ts +5 -6
  79. package/dist/src/commands/patch/index.js +10 -12
  80. package/dist/src/commands/patch/lint-ignore.js +14 -13
  81. package/dist/src/commands/patch/move-files-create.d.ts +6 -6
  82. package/dist/src/commands/patch/move-files-create.js +8 -8
  83. package/dist/src/commands/patch/move-files-into.d.ts +6 -6
  84. package/dist/src/commands/patch/move-files-into.js +8 -8
  85. package/dist/src/commands/patch/patch-context.d.ts +2 -3
  86. package/dist/src/commands/patch/patch-context.js +2 -3
  87. package/dist/src/commands/patch/rename.d.ts +10 -11
  88. package/dist/src/commands/patch/rename.js +11 -12
  89. package/dist/src/commands/patch/reorder.js +8 -9
  90. package/dist/src/commands/patch/split-plan.js +1 -1
  91. package/dist/src/commands/patch/split.d.ts +8 -10
  92. package/dist/src/commands/patch/split.js +9 -11
  93. package/dist/src/commands/patch/staged-dependency-validate.js +8 -8
  94. package/dist/src/commands/patch/staged-dependency.js +7 -3
  95. package/dist/src/commands/patch/tier.d.ts +9 -9
  96. package/dist/src/commands/patch/tier.js +18 -15
  97. package/dist/src/commands/re-export-adjacent.js +9 -8
  98. package/dist/src/commands/re-export-bulk-scan.d.ts +9 -9
  99. package/dist/src/commands/re-export-bulk-scan.js +22 -21
  100. package/dist/src/commands/re-export-drift.js +10 -10
  101. package/dist/src/commands/re-export-files.d.ts +6 -7
  102. package/dist/src/commands/re-export-files.js +34 -44
  103. package/dist/src/commands/re-export-lint.d.ts +10 -13
  104. package/dist/src/commands/re-export-lint.js +16 -14
  105. package/dist/src/commands/re-export-options.d.ts +6 -6
  106. package/dist/src/commands/re-export-options.js +6 -6
  107. package/dist/src/commands/re-export-register.d.ts +4 -10
  108. package/dist/src/commands/re-export-register.js +5 -11
  109. package/dist/src/commands/re-export-scan.js +1 -1
  110. package/dist/src/commands/re-export-single.d.ts +3 -3
  111. package/dist/src/commands/re-export-single.js +33 -25
  112. package/dist/src/commands/re-export.js +13 -21
  113. package/dist/src/commands/rebase/abort.js +3 -3
  114. package/dist/src/commands/rebase/confirm.js +2 -1
  115. package/dist/src/commands/rebase/continue.d.ts +1 -1
  116. package/dist/src/commands/rebase/continue.js +14 -9
  117. package/dist/src/commands/rebase/index.js +11 -11
  118. package/dist/src/commands/rebase/patch-loop.d.ts +7 -1
  119. package/dist/src/commands/rebase/patch-loop.js +60 -39
  120. package/dist/src/commands/rebase/summary.js +1 -1
  121. package/dist/src/commands/register.js +5 -7
  122. package/dist/src/commands/reset.js +2 -1
  123. package/dist/src/commands/resolve.d.ts +16 -13
  124. package/dist/src/commands/resolve.js +30 -33
  125. package/dist/src/commands/run.js +33 -35
  126. package/dist/src/commands/setup-support.js +15 -19
  127. package/dist/src/commands/setup.js +13 -9
  128. package/dist/src/commands/status-check.d.ts +6 -6
  129. package/dist/src/commands/status-json.d.ts +6 -6
  130. package/dist/src/commands/status-json.js +5 -0
  131. package/dist/src/commands/status-output.d.ts +5 -5
  132. package/dist/src/commands/status-output.js +6 -6
  133. package/dist/src/commands/status-ownership.d.ts +16 -14
  134. package/dist/src/commands/status-ownership.js +16 -14
  135. package/dist/src/commands/status.js +42 -34
  136. package/dist/src/commands/test-build-phase.js +26 -13
  137. package/dist/src/commands/test-diagnose.js +44 -44
  138. package/dist/src/commands/test-register.js +1 -0
  139. package/dist/src/commands/test-run.d.ts +14 -16
  140. package/dist/src/commands/test-run.js +21 -13
  141. package/dist/src/commands/test-stale-gate.d.ts +15 -16
  142. package/dist/src/commands/test-stale-gate.js +15 -16
  143. package/dist/src/commands/test-verdict.d.ts +20 -3
  144. package/dist/src/commands/test-verdict.js +30 -0
  145. package/dist/src/commands/test.js +83 -76
  146. package/dist/src/commands/token-coverage.js +3 -4
  147. package/dist/src/commands/token.js +16 -18
  148. package/dist/src/commands/tree.js +47 -31
  149. package/dist/src/commands/typecheck.d.ts +10 -12
  150. package/dist/src/commands/typecheck.js +24 -27
  151. package/dist/src/commands/verify.js +13 -15
  152. package/dist/src/commands/watch.js +21 -23
  153. package/dist/src/commands/wire.js +52 -62
  154. package/dist/src/core/ast-utils.d.ts +8 -7
  155. package/dist/src/core/ast-utils.js +8 -7
  156. package/dist/src/core/branding.d.ts +9 -14
  157. package/dist/src/core/branding.js +7 -11
  158. package/dist/src/core/browser-wire.d.ts +1 -1
  159. package/dist/src/core/browser-wire.js +14 -19
  160. package/dist/src/core/build-audit-platform.js +25 -33
  161. package/dist/src/core/build-audit-registration.js +17 -25
  162. package/dist/src/core/build-audit-resolve.d.ts +15 -17
  163. package/dist/src/core/build-audit-resolve.js +27 -33
  164. package/dist/src/core/build-audit-transforms.js +18 -19
  165. package/dist/src/core/build-audit.js +28 -42
  166. package/dist/src/core/build-baseline-types.d.ts +29 -35
  167. package/dist/src/core/build-baseline.d.ts +5 -4
  168. package/dist/src/core/build-baseline.js +14 -14
  169. package/dist/src/core/build-overwrite-guard.d.ts +7 -14
  170. package/dist/src/core/build-overwrite-guard.js +17 -27
  171. package/dist/src/core/build-prepare.d.ts +2 -2
  172. package/dist/src/core/build-prepare.js +8 -9
  173. package/dist/src/core/config-state.js +12 -4
  174. package/dist/src/core/config-validate.js +8 -5
  175. package/dist/src/core/config.d.ts +5 -8
  176. package/dist/src/core/config.js +7 -16
  177. package/dist/src/core/coverage-extend.d.ts +15 -13
  178. package/dist/src/core/coverage-extend.js +15 -13
  179. package/dist/src/core/destructive.d.ts +31 -9
  180. package/dist/src/core/destructive.js +29 -26
  181. package/dist/src/core/diff-hunks.js +10 -12
  182. package/dist/src/core/discard-baseline.d.ts +8 -8
  183. package/dist/src/core/discard-baseline.js +16 -15
  184. package/dist/src/core/display-state.d.ts +12 -14
  185. package/dist/src/core/display-state.js +12 -14
  186. package/dist/src/core/engine-changes.d.ts +2 -4
  187. package/dist/src/core/engine-changes.js +2 -4
  188. package/dist/src/core/engine-precondition.d.ts +28 -13
  189. package/dist/src/core/engine-precondition.js +25 -10
  190. package/dist/src/core/engine-session-lock.d.ts +47 -21
  191. package/dist/src/core/engine-session-lock.js +168 -33
  192. package/dist/src/core/export-moved-code-shape.d.ts +10 -10
  193. package/dist/src/core/export-moved-code-shape.js +10 -10
  194. package/dist/src/core/file-lock.d.ts +62 -5
  195. package/dist/src/core/file-lock.js +196 -59
  196. package/dist/src/core/firefox-cache.js +1 -3
  197. package/dist/src/core/firefox-extract.js +10 -14
  198. package/dist/src/core/firefox.d.ts +7 -5
  199. package/dist/src/core/firefox.js +4 -5
  200. package/dist/src/core/furnace-apply-dry-run.d.ts +2 -2
  201. package/dist/src/core/furnace-apply-dry-run.js +3 -2
  202. package/dist/src/core/furnace-apply-ftl.d.ts +7 -9
  203. package/dist/src/core/furnace-apply-ftl.js +12 -12
  204. package/dist/src/core/furnace-apply-helpers.d.ts +7 -7
  205. package/dist/src/core/furnace-apply-helpers.js +19 -16
  206. package/dist/src/core/furnace-apply-overwrite-warn.d.ts +8 -9
  207. package/dist/src/core/furnace-apply-overwrite-warn.js +8 -9
  208. package/dist/src/core/furnace-apply-undeploy-actions.d.ts +3 -4
  209. package/dist/src/core/furnace-apply-undeploy-actions.js +3 -4
  210. package/dist/src/core/furnace-apply.d.ts +5 -8
  211. package/dist/src/core/furnace-apply.js +19 -24
  212. package/dist/src/core/furnace-checksum-utils.d.ts +14 -0
  213. package/dist/src/core/furnace-checksum-utils.js +16 -0
  214. package/dist/src/core/furnace-config.d.ts +21 -30
  215. package/dist/src/core/furnace-config.js +32 -54
  216. package/dist/src/core/furnace-constants.d.ts +8 -0
  217. package/dist/src/core/furnace-constants.js +10 -2
  218. package/dist/src/core/furnace-css-fragments.d.ts +5 -5
  219. package/dist/src/core/furnace-css-fragments.js +5 -5
  220. package/dist/src/core/furnace-jsconfig.d.ts +11 -12
  221. package/dist/src/core/furnace-jsconfig.js +11 -12
  222. package/dist/src/core/furnace-marker.d.ts +7 -8
  223. package/dist/src/core/furnace-marker.js +7 -8
  224. package/dist/src/core/furnace-operation.d.ts +31 -0
  225. package/dist/src/core/furnace-operation.js +54 -17
  226. package/dist/src/core/furnace-precondition.d.ts +61 -0
  227. package/dist/src/core/furnace-precondition.js +67 -0
  228. package/dist/src/core/furnace-registration-remove.d.ts +5 -6
  229. package/dist/src/core/furnace-registration-remove.js +6 -7
  230. package/dist/src/core/furnace-registration-validate.d.ts +13 -0
  231. package/dist/src/core/furnace-registration-validate.js +20 -1
  232. package/dist/src/core/furnace-registration.d.ts +5 -6
  233. package/dist/src/core/furnace-registration.js +10 -12
  234. package/dist/src/core/furnace-rollback.js +7 -7
  235. package/dist/src/core/furnace-scanner.d.ts +1 -0
  236. package/dist/src/core/furnace-scanner.js +1 -2
  237. package/dist/src/core/furnace-stale-export.d.ts +10 -10
  238. package/dist/src/core/furnace-stale-export.js +10 -10
  239. package/dist/src/core/furnace-state-persist.d.ts +6 -8
  240. package/dist/src/core/furnace-state-persist.js +6 -8
  241. package/dist/src/core/furnace-validate-accessibility.d.ts +22 -0
  242. package/dist/src/core/furnace-validate-accessibility.js +310 -1
  243. package/dist/src/core/furnace-validate-compatibility.js +141 -1
  244. package/dist/src/core/furnace-validate-helpers.d.ts +5 -116
  245. package/dist/src/core/furnace-validate-helpers.js +0 -491
  246. package/dist/src/core/furnace-validate-registration.d.ts +7 -0
  247. package/dist/src/core/furnace-validate-registration.js +115 -41
  248. package/dist/src/core/furnace-validate.d.ts +16 -0
  249. package/dist/src/core/furnace-validate.js +188 -145
  250. package/dist/src/core/furnace-version-drift.d.ts +10 -11
  251. package/dist/src/core/furnace-version-drift.js +10 -11
  252. package/dist/src/core/git-base.d.ts +7 -9
  253. package/dist/src/core/git-base.js +7 -9
  254. package/dist/src/core/git-diff.d.ts +20 -9
  255. package/dist/src/core/git-diff.js +148 -63
  256. package/dist/src/core/git-file-ops.d.ts +19 -19
  257. package/dist/src/core/git-file-ops.js +19 -19
  258. package/dist/src/core/git-readonly-index.d.ts +53 -19
  259. package/dist/src/core/git-readonly-index.js +75 -31
  260. package/dist/src/core/git-status.d.ts +5 -6
  261. package/dist/src/core/git-status.js +5 -6
  262. package/dist/src/core/git.d.ts +9 -10
  263. package/dist/src/core/git.js +54 -70
  264. package/dist/src/core/license-headers.d.ts +17 -23
  265. package/dist/src/core/license-headers.js +17 -23
  266. package/dist/src/core/mach-build-artifacts.d.ts +9 -9
  267. package/dist/src/core/mach-build-artifacts.js +57 -19
  268. package/dist/src/core/mach-error-hints.js +42 -29
  269. package/dist/src/core/mach-known-noise-filter.d.ts +15 -16
  270. package/dist/src/core/mach-known-noise-filter.js +18 -20
  271. package/dist/src/core/mach-mozconfig.d.ts +14 -0
  272. package/dist/src/core/mach-mozconfig.js +30 -0
  273. package/dist/src/core/mach-resource-shim.d.ts +43 -58
  274. package/dist/src/core/mach-resource-shim.js +50 -67
  275. package/dist/src/core/mach.d.ts +37 -41
  276. package/dist/src/core/mach.js +50 -51
  277. package/dist/src/core/marionette-port.d.ts +36 -1
  278. package/dist/src/core/marionette-port.js +96 -31
  279. package/dist/src/core/marionette-preflight.d.ts +8 -12
  280. package/dist/src/core/marionette-preflight.js +86 -52
  281. package/dist/src/core/mochitest-server-port.d.ts +42 -0
  282. package/dist/src/core/mochitest-server-port.js +119 -0
  283. package/dist/src/core/module-resolution-preflight.d.ts +9 -11
  284. package/dist/src/core/module-resolution-preflight.js +9 -11
  285. package/dist/src/core/moz-manifest-helpers.d.ts +79 -0
  286. package/dist/src/core/moz-manifest-helpers.js +163 -0
  287. package/dist/src/core/{manifest-register.d.ts → moz-manifest-register.d.ts} +1 -1
  288. package/dist/src/core/{manifest-register.js → moz-manifest-register.js} +13 -28
  289. package/dist/src/core/{manifest-rules.d.ts → moz-manifest-rules.d.ts} +10 -1
  290. package/dist/src/core/{manifest-rules.js → moz-manifest-rules.js} +31 -24
  291. package/dist/src/core/{manifest-tokenizers.d.ts → moz-manifest-tokenizers.d.ts} +3 -5
  292. package/dist/src/core/{manifest-tokenizers.js → moz-manifest-tokenizers.js} +9 -12
  293. package/dist/src/core/ownership-table.d.ts +14 -16
  294. package/dist/src/core/ownership-table.js +14 -16
  295. package/dist/src/core/parser-fallback.d.ts +15 -1
  296. package/dist/src/core/parser-fallback.js +22 -3
  297. package/dist/src/core/patch-apply-fuzz.d.ts +10 -12
  298. package/dist/src/core/patch-apply-fuzz.js +15 -18
  299. package/dist/src/core/patch-apply.d.ts +13 -34
  300. package/dist/src/core/patch-apply.js +16 -76
  301. package/dist/src/core/patch-artifact-normalize.d.ts +13 -15
  302. package/dist/src/core/patch-artifact-normalize.js +13 -15
  303. package/dist/src/core/patch-export-metadata.d.ts +12 -2
  304. package/dist/src/core/patch-export-metadata.js +13 -4
  305. package/dist/src/core/patch-export-update.d.ts +13 -1
  306. package/dist/src/core/patch-export-update.js +25 -8
  307. package/dist/src/core/patch-export.d.ts +15 -8
  308. package/dist/src/core/patch-export.js +10 -11
  309. package/dist/src/core/patch-files.d.ts +5 -6
  310. package/dist/src/core/patch-files.js +5 -6
  311. package/dist/src/core/patch-identifier-suggest.d.ts +7 -12
  312. package/dist/src/core/patch-identifier-suggest.js +14 -19
  313. package/dist/src/core/patch-lint-binary.d.ts +51 -0
  314. package/dist/src/core/patch-lint-binary.js +39 -0
  315. package/dist/src/core/{lint-cache.d.ts → patch-lint-cache.d.ts} +5 -5
  316. package/dist/src/core/{lint-cache.js → patch-lint-cache.js} +21 -23
  317. package/dist/src/core/patch-lint-checkjs.d.ts +16 -16
  318. package/dist/src/core/patch-lint-checkjs.js +16 -16
  319. package/dist/src/core/patch-lint-chrome-jsdoc.d.ts +6 -9
  320. package/dist/src/core/patch-lint-chrome-jsdoc.js +10 -14
  321. package/dist/src/core/patch-lint-creators.d.ts +8 -8
  322. package/dist/src/core/patch-lint-creators.js +8 -8
  323. package/dist/src/core/patch-lint-cross.d.ts +17 -20
  324. package/dist/src/core/patch-lint-cross.js +16 -16
  325. package/dist/src/core/patch-lint-css.d.ts +3 -5
  326. package/dist/src/core/patch-lint-css.js +17 -22
  327. package/dist/src/core/patch-lint-diff-tag.d.ts +14 -16
  328. package/dist/src/core/patch-lint-diff-tag.js +14 -16
  329. package/dist/src/core/patch-lint-jsdoc.d.ts +7 -8
  330. package/dist/src/core/patch-lint-jsdoc.js +26 -19
  331. package/dist/src/core/patch-lint-mozbuild.js +20 -20
  332. package/dist/src/core/patch-lint-observer.d.ts +7 -7
  333. package/dist/src/core/patch-lint-observer.js +7 -7
  334. package/dist/src/core/patch-lint.d.ts +38 -29
  335. package/dist/src/core/patch-lint.js +89 -121
  336. package/dist/src/core/patch-lock.d.ts +1 -1
  337. package/dist/src/core/patch-lock.js +45 -9
  338. package/dist/src/core/patch-manifest-consistency.d.ts +2 -1
  339. package/dist/src/core/patch-manifest-consistency.js +25 -27
  340. package/dist/src/core/patch-manifest-io.js +14 -18
  341. package/dist/src/core/patch-manifest-resolve.d.ts +9 -12
  342. package/dist/src/core/patch-manifest-resolve.js +9 -12
  343. package/dist/src/core/patch-manifest-validate.d.ts +6 -1
  344. package/dist/src/core/patch-manifest-validate.js +25 -13
  345. package/dist/src/core/patch-parse.d.ts +24 -13
  346. package/dist/src/core/patch-parse.js +19 -14
  347. package/dist/src/core/patch-policy.d.ts +2 -2
  348. package/dist/src/core/patch-policy.js +2 -2
  349. package/dist/src/core/patch-registration-refs.d.ts +7 -8
  350. package/dist/src/core/patch-registration-refs.js +9 -9
  351. package/dist/src/core/process-cpu-time.d.ts +12 -0
  352. package/dist/src/core/process-cpu-time.js +52 -0
  353. package/dist/src/core/rebase-session.d.ts +38 -26
  354. package/dist/src/core/rebase-session.js +55 -28
  355. package/dist/src/core/register-browser-content.js +9 -74
  356. package/dist/src/core/{register-test-manifest.js → register-browser-test.js} +7 -42
  357. package/dist/src/core/register-module.js +10 -43
  358. package/dist/src/core/register-scaffold.d.ts +8 -8
  359. package/dist/src/core/register-scaffold.js +10 -10
  360. package/dist/src/core/register-shared-css.d.ts +9 -12
  361. package/dist/src/core/register-shared-css.js +17 -87
  362. package/dist/src/core/register-xpcshell-test.d.ts +6 -7
  363. package/dist/src/core/register-xpcshell-test.js +7 -8
  364. package/dist/src/core/shared-ftl.js +12 -4
  365. package/dist/src/core/state-file.d.ts +45 -0
  366. package/dist/src/core/state-file.js +34 -0
  367. package/dist/src/core/status-binary.d.ts +18 -11
  368. package/dist/src/core/status-binary.js +19 -12
  369. package/dist/src/core/status-classify.d.ts +30 -36
  370. package/dist/src/core/status-classify.js +21 -26
  371. package/dist/src/core/test-changed-prefs.d.ts +30 -0
  372. package/dist/src/core/test-changed-prefs.js +78 -0
  373. package/dist/src/core/test-harness-crash.d.ts +25 -63
  374. package/dist/src/core/test-harness-crash.js +121 -142
  375. package/dist/src/core/test-harness-signature.d.ts +18 -0
  376. package/dist/src/core/test-harness-signature.js +13 -0
  377. package/dist/src/core/test-path-scope.d.ts +12 -15
  378. package/dist/src/core/test-path-scope.js +15 -18
  379. package/dist/src/core/test-stale-check.d.ts +17 -17
  380. package/dist/src/core/test-stale-check.js +50 -43
  381. package/dist/src/core/test-stall-triage.d.ts +51 -0
  382. package/dist/src/core/test-stall-triage.js +126 -0
  383. package/dist/src/core/token-category.js +5 -7
  384. package/dist/src/core/token-coverage.js +9 -9
  385. package/dist/src/core/token-dark-mode.d.ts +9 -11
  386. package/dist/src/core/token-dark-mode.js +9 -11
  387. package/dist/src/core/token-docs.d.ts +2 -0
  388. package/dist/src/core/token-docs.js +2 -0
  389. package/dist/src/core/token-manager.d.ts +10 -2
  390. package/dist/src/core/token-manager.js +17 -8
  391. package/dist/src/core/token-scaffold.d.ts +7 -7
  392. package/dist/src/core/token-scaffold.js +7 -7
  393. package/dist/src/core/token-variant.d.ts +4 -4
  394. package/dist/src/core/token-variant.js +4 -4
  395. package/dist/src/core/toolchain-preflight.d.ts +20 -21
  396. package/dist/src/core/toolchain-preflight.js +28 -31
  397. package/dist/src/core/tree-guard.js +12 -2
  398. package/dist/src/core/tree-store.d.ts +41 -27
  399. package/dist/src/core/tree-store.js +63 -42
  400. package/dist/src/core/typecheck-shim.d.ts +23 -24
  401. package/dist/src/core/typecheck-shim.js +23 -24
  402. package/dist/src/core/typecheck.js +8 -8
  403. package/dist/src/core/wire-destroy.d.ts +3 -3
  404. package/dist/src/core/wire-destroy.js +7 -15
  405. package/dist/src/core/wire-dom-fragment.d.ts +9 -17
  406. package/dist/src/core/wire-dom-fragment.js +11 -44
  407. package/dist/src/core/wire-init.d.ts +2 -3
  408. package/dist/src/core/wire-init.js +8 -19
  409. package/dist/src/core/wire-subscript.d.ts +2 -3
  410. package/dist/src/core/wire-subscript.js +4 -13
  411. package/dist/src/core/wire-utils.d.ts +7 -8
  412. package/dist/src/core/wire-utils.js +7 -8
  413. package/dist/src/core/xpcshell-appdir.d.ts +38 -45
  414. package/dist/src/core/xpcshell-appdir.js +38 -45
  415. package/dist/src/errors/base.d.ts +37 -16
  416. package/dist/src/errors/base.js +37 -16
  417. package/dist/src/errors/build.d.ts +13 -1
  418. package/dist/src/errors/build.js +14 -2
  419. package/dist/src/errors/codes.d.ts +16 -0
  420. package/dist/src/errors/codes.js +16 -0
  421. package/dist/src/errors/furnace.d.ts +4 -4
  422. package/dist/src/errors/git.d.ts +6 -10
  423. package/dist/src/errors/git.js +6 -10
  424. package/dist/src/errors/patch.d.ts +7 -7
  425. package/dist/src/errors/patch.js +7 -7
  426. package/dist/src/errors/rebase.d.ts +4 -4
  427. package/dist/src/errors/rebase.js +4 -4
  428. package/dist/src/index.d.ts +7 -1
  429. package/dist/src/index.js +5 -0
  430. package/dist/src/types/commands/index.d.ts +2 -2
  431. package/dist/src/types/commands/options.d.ts +88 -70
  432. package/dist/src/types/commands/patches.d.ts +24 -34
  433. package/dist/src/types/commands/project.d.ts +5 -34
  434. package/dist/src/types/config.d.ts +25 -23
  435. package/dist/src/types/config.js +6 -1
  436. package/dist/src/types/furnace.d.ts +37 -13
  437. package/dist/src/types/furnace.js +19 -1
  438. package/dist/src/types/index.d.ts +3 -3
  439. package/dist/src/utils/build-info.d.ts +7 -8
  440. package/dist/src/utils/build-info.js +12 -11
  441. package/dist/src/utils/errors.d.ts +6 -8
  442. package/dist/src/utils/errors.js +6 -8
  443. package/dist/src/utils/logger.d.ts +27 -10
  444. package/dist/src/utils/logger.js +28 -16
  445. package/dist/src/utils/machine-output.d.ts +12 -0
  446. package/dist/src/utils/machine-output.js +33 -0
  447. package/dist/src/utils/options.d.ts +31 -14
  448. package/dist/src/utils/options.js +86 -30
  449. package/dist/src/utils/paths.d.ts +10 -8
  450. package/dist/src/utils/paths.js +10 -8
  451. package/dist/src/utils/platform.d.ts +0 -25
  452. package/dist/src/utils/platform.js +1 -42
  453. package/dist/src/utils/process-group.d.ts +9 -4
  454. package/dist/src/utils/process-group.js +19 -5
  455. package/dist/src/utils/process.d.ts +42 -7
  456. package/dist/src/utils/process.js +44 -17
  457. package/dist/src/utils/ps-duration.d.ts +31 -0
  458. package/dist/src/utils/ps-duration.js +70 -0
  459. package/dist/src/utils/regex.d.ts +9 -10
  460. package/dist/src/utils/regex.js +9 -10
  461. package/dist/src/utils/validation.d.ts +58 -28
  462. package/dist/src/utils/validation.js +60 -33
  463. package/package.json +2 -2
  464. package/dist/src/commands/doctor/post-rebase-audit.d.ts +0 -2
  465. package/dist/src/commands/doctor-furnace-manifest-sync.d.ts +0 -18
  466. package/dist/src/core/manifest-helpers.d.ts +0 -38
  467. package/dist/src/core/manifest-helpers.js +0 -120
  468. /package/dist/src/core/{lint-projection.d.ts → patch-lint-projection.d.ts} +0 -0
  469. /package/dist/src/core/{lint-projection.js → patch-lint-projection.js} +0 -0
  470. /package/dist/src/core/{register-test-manifest.d.ts → register-browser-test.d.ts} +0 -0
@@ -2,8 +2,10 @@
2
2
  import { Command } from 'commander';
3
3
  import { isBrandingManagedPath } from '../core/branding.js';
4
4
  import { getProjectPaths, loadConfig } from '../core/config.js';
5
+ import { stdioIsInteractive } from '../core/destructive.js';
6
+ import { assertEngineGitReady } from '../core/engine-precondition.js';
5
7
  import { collectFurnaceManagedPrefixes, furnaceConfigExists, loadFurnaceConfig, } from '../core/furnace-config.js';
6
- import { hasChanges, isGitRepository } from '../core/git.js';
8
+ import { hasChanges } from '../core/git.js';
7
9
  import { getAllDiff, getDiffForFilesAgainstHead } from '../core/git-diff.js';
8
10
  import { expandUntrackedDirectoryEntries, getWorkingTreeStatus } from '../core/git-status.js';
9
11
  import { extractAffectedFiles } from '../core/patch-apply.js';
@@ -14,7 +16,7 @@ import { buildPatchSourceMetadata } from '../core/patch-source-metadata.js';
14
16
  import { GeneralError } from '../errors/base.js';
15
17
  import { ensureDir, pathExists } from '../utils/fs.js';
16
18
  import { info, intro, outro, spinner } from '../utils/logger.js';
17
- import { pickDefined } from '../utils/options.js';
19
+ import { addWaitLockOption, pickDefined, resolveWaitLockSeconds } from '../utils/options.js';
18
20
  import { renderDryRunPreview } from './export-flow.js';
19
21
  import { autoFixLicenseHeaders, promptExportPatchMetadata, runPatchLint, runSupersedeAndOverlapGates, } from './export-shared.js';
20
22
  async function checkBrandingManagedFiles(paths, config) {
@@ -46,9 +48,7 @@ async function resolveFurnaceExclusionPolicy(paths, projectRoot, excludeFurnace)
46
48
  // Expand collapsed `?? dir/` entries before matching against Furnace
47
49
  // prefixes — otherwise a Furnace-introduced directory slips past the
48
50
  // filter and later lands in the non-Furnace path list that feeds the
49
- // aggregate diff, where `getDiffForFilesAgainstHead` crashes with
50
- // EISDIR (eval finding: export-all unusable on a fresh project with
51
- // Furnace scaffolding).
51
+ // aggregate diff, where `getDiffForFilesAgainstHead` crashes with EISDIR.
52
52
  const rawStatus = await getWorkingTreeStatus(paths.engine);
53
53
  const changedFiles = await expandUntrackedDirectoryEntries(paths.engine, rawStatus);
54
54
  const furnaceManagedFiles = changedFiles
@@ -66,20 +66,19 @@ async function resolveFurnaceExclusionPolicy(paths, projectRoot, excludeFurnace)
66
66
  }
67
67
  /**
68
68
  * Refuses the export when the resulting patch would register furnace
69
- * component source files it does not itself carry. 2026-04-24 eval
70
- * Finding 1: operators running `export-all --exclude-furnace` after
71
- * `furnace create --localized --with-tests` ended up with patches that
72
- * added `toolkit/content/widgets/moz-qa-panel/*` via jar.mn /
73
- * customElements.js / locale jar.mn but excluded the component source
74
- * files themselves. The resulting patch queue was structurally broken
75
- * and `fireforge verify` stayed silent. We now detect the condition
76
- * pre-write and ask the operator to either include the component
77
- * sources (skip `--exclude-furnace`) or revert the furnace changes
78
- * before exporting.
69
+ * component source files it does not itself carry.
70
+ *
71
+ * `export-all --exclude-furnace` after `furnace create --localized
72
+ * --with-tests` otherwise produces a patch that adds
73
+ * `toolkit/content/widgets/<tag>/*` via jar.mn / customElements.js / locale
74
+ * jar.mn while excluding the component source files themselves — a
75
+ * structurally broken queue that `fireforge verify` stays silent about. The
76
+ * operator is asked to either include the component sources (skip
77
+ * `--exclude-furnace`) or revert the furnace changes before exporting.
79
78
  *
80
79
  * The check runs against the synthesised patch body before
81
- * `commitExportedPatch` writes anything, so no broken patch is left on
82
- * disk when the refusal fires.
80
+ * `commitExportedPatch` writes anything, so no broken patch is left on disk
81
+ * when the refusal fires.
83
82
  */
84
83
  async function checkDanglingFurnaceRegistrations(projectRoot, diff, furnaceExcluded) {
85
84
  if (furnaceExcluded.size === 0)
@@ -171,14 +170,7 @@ export async function exportAllCommand(projectRoot, options = {}) {
171
170
  const isDryRun = options.dryRun === true;
172
171
  intro(isDryRun ? 'FireForge Export All (dry run)' : 'FireForge Export All');
173
172
  const paths = getProjectPaths(projectRoot);
174
- // Check if engine exists
175
- if (!(await pathExists(paths.engine))) {
176
- throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
177
- }
178
- // Check if it's a git repository
179
- if (!(await isGitRepository(paths.engine))) {
180
- throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
181
- }
173
+ await assertEngineGitReady(paths.engine);
182
174
  // Check for changes
183
175
  if (!(await hasChanges(paths.engine))) {
184
176
  info('No changes to export');
@@ -228,13 +220,13 @@ export async function exportAllCommand(projectRoot, options = {}) {
228
220
  // the aggregate would newly create, so it runs here instead of alongside
229
221
  // the branding / furnace guards that operate on the raw status list.
230
222
  await checkDuplicateNewFileCreations(paths, diff);
231
- // Dangling-furnace-registration preflight (Finding 1). Runs after the
232
- // diff is assembled so we can inspect the exact hunks the operator is
233
- // about to land; runs BEFORE any write so a refusal leaves the
234
- // patches directory untouched.
223
+ // Dangling-furnace-registration preflight. Runs after the diff is
224
+ // assembled so it can inspect the exact hunks the operator is about to
225
+ // land, and BEFORE any write so a refusal leaves the patches directory
226
+ // untouched.
235
227
  await checkDanglingFurnaceRegistrations(projectRoot, diff, furnaceExcluded);
236
228
  // Check for non-interactive mode
237
- const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
229
+ const isInteractive = stdioIsInteractive();
238
230
  // Auto-fix missing license headers on new files (interactive only;
239
231
  // report-only under --dry-run so the preview never mutates engine/)
240
232
  const headersAdded = await autoFixLicenseHeaders(paths.engine, diff, config, isInteractive, isDryRun);
@@ -308,6 +300,10 @@ export async function exportAllCommand(projectRoot, options = {}) {
308
300
  config,
309
301
  policyCommand: 'export-all',
310
302
  forceUnsafe: options.forceUnsafe === true,
303
+ lockOptions: {
304
+ waitLockSeconds: resolveWaitLockSeconds(options.waitLock),
305
+ command: 'export-all',
306
+ },
311
307
  });
312
308
  for (const oldPatch of superseded) {
313
309
  info(`Superseded: ${oldPatch.filename}`);
@@ -326,7 +322,7 @@ export async function exportAllCommand(projectRoot, options = {}) {
326
322
  }
327
323
  /** Registers the export-all command on the CLI program. */
328
324
  export function registerExportAll(program, { getProjectRoot, withErrorHandling }) {
329
- program
325
+ const command = program
330
326
  .command('export-all')
331
327
  .description('Export all changes as a patch')
332
328
  .option('--name <name>', 'Name for the patch')
@@ -337,8 +333,8 @@ export function registerExportAll(program, { getProjectRoot, withErrorHandling }
337
333
  .option('--exclude-furnace', 'Export the non-Furnace subset of the aggregate diff instead of refusing when Furnace-managed files are modified. Furnace-managed files are still deployed by "fireforge furnace apply"; this flag only changes whether export-all aborts or filters in their presence.')
338
334
  .option('--allow-overlap', 'Acknowledge cross-patch ownership overlap with non-superseded patches (the resulting queue fails verify). Does not bypass the new-file creation guard — two patches creating the same path is structurally unrecoverable, so that case still refuses regardless of this flag.')
339
335
  .option('--dry-run', 'Print the export-all plan (filename, metadata, files affected, supersede preview) without writing anything to patches/. Lint still runs so the operator sees the same lint output a real run would produce.')
340
- .option('--force-unsafe', 'Bypass force-mode patchPolicy refusals')
341
- .action(withErrorHandling(async (options) => {
336
+ .option('--force-unsafe', 'Bypass force-mode patchPolicy refusals');
337
+ addWaitLockOption(command).action(withErrorHandling(async (options) => {
342
338
  const { category, ...rest } = options;
343
339
  await exportAllCommand(getProjectRoot(), {
344
340
  ...pickDefined(rest),
@@ -195,12 +195,12 @@ export async function projectPlacementForLint(patchesDir, plan, diff, config) {
195
195
  return entry;
196
196
  return { ...entry, filename: rename.newFilename, order: rename.newOrder };
197
197
  });
198
- // Synthetic entry for the pending patch, populated with both its
199
- // new-file content AND its added-line content for files it modifies
200
- // so the forward-import rule can inspect imports the patch *itself*
201
- // authors — whether they live in a brand-new file or are added to an
202
- // existing file. Leaving either map empty lets a patch land before
203
- // one of its own dependencies and still pass the gate.
198
+ // Synthetic entry for the pending patch, populated with both its new-file
199
+ // content AND its added-line content for files it modifies, so the
200
+ // forward-import rule can inspect imports the patch *itself* authors —
201
+ // whether they live in a brand-new file or are added to an existing one.
202
+ // Leaving either map empty lets a patch land before one of its own
203
+ // dependencies and still pass the gate.
204
204
  projectedEntries.push({
205
205
  filename: plan.newFilename,
206
206
  order: plan.insertionOrder,
@@ -1,16 +1,17 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  /**
3
3
  * Attribution of projected placement lint errors, split out of
4
- * export-flow.ts for the line budget. A placement projection lints one
5
- * merged queue — the renumbered existing patches plus a synthetic entry
6
- * for the pending patch — so its errors used to render as one flat list
7
- * that read as though the NEW patch caused every one of them. A
8
- * renumbering that re-evaluates pre-existing staged relationships (455
9
- * importing a module created by 456, legal only at their current
10
- * ordinals) then sent the operator auditing a perfectly fine export.
4
+ * export-flow.ts for the line budget.
11
5
  *
12
- * Consumed by export-flow.ts; no top-level registrar is exported and none
13
- * is wanted.
6
+ * A placement projection lints one merged queue — the renumbered existing
7
+ * patches plus a synthetic entry for the pending patch — so a flat error
8
+ * list reads as though the NEW patch caused every one of them. A renumbering
9
+ * that re-evaluates pre-existing staged relationships (patch 455 importing a
10
+ * module created by 456, legal only at their current ordinals) then sends
11
+ * the operator auditing a perfectly fine export.
12
+ *
13
+ * Consumed by export-flow.ts; no top-level registrar is exported and none is
14
+ * wanted.
14
15
  */
15
16
  import { formatPatchLintIssue } from '../core/patch-lint.js';
16
17
  /**
@@ -70,10 +70,10 @@ export async function gatePlacementPlan(args) {
70
70
  const summary = placementSummary(placementPlan);
71
71
  const renameCount = placementPlan.renameMap.size;
72
72
  // Route through confirmDestructive when the operation is destructive
73
- // enough to warrant a prompt (more than one rename) OR when the user
74
- // asked for a dry-run. The dry-run branch must always print the
75
- // placement summary — previously, single-rename/no-rename dry-runs
76
- // exited silently with no filename or projected layout.
73
+ // enough to warrant a prompt (more than one rename) OR when the user asked
74
+ // for a dry-run. The dry-run branch must always print the placement
75
+ // summary; otherwise single-rename and no-rename dry-runs exit silently
76
+ // with no filename or projected layout.
77
77
  if (renameCount > 1 || isDryRun) {
78
78
  s.stop();
79
79
  const decision = await confirmDestructive({
@@ -89,7 +89,7 @@ export async function gatePlacementPlan(args) {
89
89
  outro('Dry run complete — no changes made');
90
90
  return 'stop';
91
91
  }
92
- if (decision === 'cancelled') {
92
+ if (decision === 'declined') {
93
93
  outro('Export cancelled');
94
94
  return 'stop';
95
95
  }
@@ -3,26 +3,26 @@ import type { ExportOptions, PatchCategory } from '../types/commands/index.js';
3
3
  import type { FireForgeConfig } from '../types/config.js';
4
4
  import type { SpinnerHandle } from '../utils/logger.js';
5
5
  /**
6
- * Runs the full patch lint pipeline and reports results.
7
- * Warnings are always displayed. Errors block the export unless skipLint is true.
6
+ * Runs the full patch lint pipeline and reports results. Warnings are always
7
+ * displayed. Errors block the export unless skipLint is true.
8
8
  *
9
9
  * @param engineDir - Engine root directory
10
10
  * @param filesAffected - Files touched by the patch
11
11
  * @param diffContent - Raw unified diff string
12
12
  * @param config - Project configuration
13
13
  * @param skipLint - If true, downgrade errors to warnings
14
- * @param patchQueueCtx - Optional cross-patch context for ownership resolution
14
+ * @param patchQueueCtx - Optional cross-patch context for ownership
15
+ * resolution
15
16
  * @param ignoreChecks - Optional per-patch set of `check` IDs to suppress
16
17
  * (threaded from `PatchMetadata.lintIgnore`). Surgical alternative to
17
18
  * `--skip-lint` when exactly one advisory rule does not apply to a
18
- * specific patch — e.g. `large-patch-lines` on a cohesive branding
19
- * bundle that genuinely cannot be split.
19
+ * specific patch — e.g. `large-patch-lines` on a cohesive branding bundle
20
+ * that genuinely cannot be split.
20
21
  * @param patchTier - Optional explicit tier override (threaded from
21
- * `PatchMetadata.tier`). Forces the branding-tier thresholds when
22
- * set, independent of the auto-detect allowlist. When the branding
23
- * tier is applied (either via this opt-in or the auto-detect), a
24
- * single `info()` line surfaces the choice so the tier decision is
25
- * visible rather than silent.
22
+ * `PatchMetadata.tier`). Forces the branding-tier thresholds when set,
23
+ * independent of the auto-detect allowlist. When the branding tier is
24
+ * applied, a single `info()` line surfaces the choice so the tier decision
25
+ * is visible rather than silent.
26
26
  */
27
27
  export declare function runPatchLint(engineDir: string, filesAffected: string[], diffContent: string, config: FireForgeConfig, skipLint?: boolean, patchQueueCtx?: import('../core/patch-lint-cross.js').PatchQueueContext, ignoreChecks?: ReadonlySet<string>, patchTier?: 'branding'): Promise<void>;
28
28
  /**
@@ -55,18 +55,18 @@ export declare function confirmSupersedePatches(patchesDir: string, filesAffecte
55
55
  /**
56
56
  * Detects new files missing license headers and offers to add them.
57
57
  *
58
- * In interactive mode the user is prompted before any files are modified.
59
- * In non-interactive mode the function is a no-op — the existing lint error
60
- * will block the export instead.
58
+ * In interactive mode the user is prompted before any files are modified. In
59
+ * non-interactive mode the function is a no-op — the existing lint error
60
+ * blocks the export instead.
61
61
  *
62
62
  * @param engineDir - Absolute path to engine directory
63
63
  * @param diffContent - Current unified diff
64
64
  * @param config - Project configuration
65
65
  * @param isInteractive - Whether interactive prompts are available
66
66
  * @param dryRun - When true, only REPORT missing headers, never prompt or
67
- * write. Dry-run must stay read-only: before this flag existed, an
68
- * interactive `export --dry-run` prompted (default Yes) and wrote license
69
- * headers into engine/ files, then closed with "no changes made".
67
+ * write. Dry-run must stay read-only: without this flag an interactive
68
+ * `export --dry-run` prompts (default Yes), writes license headers into
69
+ * engine/ files, and then closes with "no changes made".
70
70
  * @returns true if files were modified on disk (caller must regenerate diff)
71
71
  */
72
72
  export declare function autoFixLicenseHeaders(engineDir: string, diffContent: string, config: FireForgeConfig, isInteractive: boolean, dryRun?: boolean): Promise<boolean>;
@@ -83,19 +83,17 @@ export declare function autoFixLicenseHeaders(engineDir: string, diffContent: st
83
83
  export declare function findPartialOwnershipOverlap(manifest: PatchesManifest, filesAffected: string[], excludeFilenames: ReadonlySet<string>): Map<string, string[]>;
84
84
  /**
85
85
  * Gate that refuses the default export path when the new patch would
86
- * silently claim files that are already tracked by other non-superseded
87
- * patches. `findAllPatchesForFiles` already catches the full-coverage
88
- * supersede case — this helper fills the gap for partial overlap, which
89
- * was the eval finding #12 scenario (two patches both claiming
90
- * `browser/themes/shared/jar.inc.mn` after a second export with
91
- * `--before`).
86
+ * silently claim files already tracked by other non-superseded patches.
87
+ * `findAllPatchesForFiles` catches the full-coverage supersede case; this
88
+ * fills the gap for PARTIAL overlap — two patches both claiming e.g.
89
+ * `browser/themes/shared/jar.inc.mn` after a second export with `--before`.
92
90
  *
93
91
  * Proceeds silently when there is no overlap, or when the caller passed
94
92
  * `--allow-overlap`. In interactive mode the caller is prompted to
95
93
  * acknowledge the overlap (the proper fix path is `re-export --files` to
96
94
  * repartition ownership, so the prompt surfaces that pointer). In
97
- * non-interactive mode the function throws — better to fail fast than
98
- * let the queue fall out of sync with verify.
95
+ * non-interactive mode it throws — better to fail fast than let the queue
96
+ * fall out of sync with verify.
99
97
  */
100
98
  export declare function guardOwnershipOverlap(args: {
101
99
  patchesDir: string;
@@ -107,13 +105,12 @@ export declare function guardOwnershipOverlap(args: {
107
105
  }): Promise<boolean>;
108
106
  /**
109
107
  * Runs the two pre-commit gates shared by `export` and `export-all` in
110
- * order: the supersede confirmation, then the cross-patch ownership
111
- * overlap guard. The overlap guard receives the filenames of the patches
112
- * the export would fully supersede so it does not flag a file claimed by
113
- * a patch that is about to be removed (pre-0.16.0 `export` only caught
114
- * FULL-coverage supersedes, so a second export targeting a shared file
115
- * like `browser/themes/shared/jar.inc.mn` created a queue where two
116
- * patches both claimed the file and `verify` failed immediately).
108
+ * order: the supersede confirmation, then the cross-patch ownership overlap
109
+ * guard. The overlap guard receives the filenames of the patches the export
110
+ * would fully supersede so it does not flag a file claimed by a patch that
111
+ * is about to be removed. Catching only FULL-coverage supersedes leaves a
112
+ * second export targeting a shared file creating a queue where two patches
113
+ * both claim it and `verify` fails immediately.
117
114
  *
118
115
  * @param args - Gate inputs shared by both export commands
119
116
  * @param args.patchesDir - Absolute path of the patches directory
@@ -122,8 +119,8 @@ export declare function guardOwnershipOverlap(args: {
122
119
  * @param args.allowOverlap - The command's `--allow-overlap` flag
123
120
  * @param args.isInteractive - Whether prompting the operator is possible
124
121
  * @param args.s - Active spinner, stopped before any prompt
125
- * @returns `true` when both gates passed; `false` when the operator
126
- * declined (the caller returns without committing)
122
+ * @returns `true` when both gates passed; `false` when the operator declined
123
+ * (the caller returns without committing)
127
124
  */
128
125
  export declare function runSupersedeAndOverlapGates(args: {
129
126
  patchesDir: string;
@@ -10,36 +10,36 @@ import { getPatchPolicyCategories, isCategoryAllowedByConfig } from '../core/pat
10
10
  import { GeneralError, InvalidArgumentError } from '../errors/base.js';
11
11
  import { pathExists, readText } from '../utils/fs.js';
12
12
  import { cancel, info, isCancel, warn } from '../utils/logger.js';
13
- import { normalizePatchDisplayName, PATCH_CATEGORIES, validatePatchName, } from '../utils/validation.js';
13
+ import { describePatchNameProblem, normalizePatchDisplayName, PATCH_CATEGORIES, } from '../utils/validation.js';
14
14
  /**
15
- * Runs the full patch lint pipeline and reports results.
16
- * Warnings are always displayed. Errors block the export unless skipLint is true.
15
+ * Runs the full patch lint pipeline and reports results. Warnings are always
16
+ * displayed. Errors block the export unless skipLint is true.
17
17
  *
18
18
  * @param engineDir - Engine root directory
19
19
  * @param filesAffected - Files touched by the patch
20
20
  * @param diffContent - Raw unified diff string
21
21
  * @param config - Project configuration
22
22
  * @param skipLint - If true, downgrade errors to warnings
23
- * @param patchQueueCtx - Optional cross-patch context for ownership resolution
23
+ * @param patchQueueCtx - Optional cross-patch context for ownership
24
+ * resolution
24
25
  * @param ignoreChecks - Optional per-patch set of `check` IDs to suppress
25
26
  * (threaded from `PatchMetadata.lintIgnore`). Surgical alternative to
26
27
  * `--skip-lint` when exactly one advisory rule does not apply to a
27
- * specific patch — e.g. `large-patch-lines` on a cohesive branding
28
- * bundle that genuinely cannot be split.
28
+ * specific patch — e.g. `large-patch-lines` on a cohesive branding bundle
29
+ * that genuinely cannot be split.
29
30
  * @param patchTier - Optional explicit tier override (threaded from
30
- * `PatchMetadata.tier`). Forces the branding-tier thresholds when
31
- * set, independent of the auto-detect allowlist. When the branding
32
- * tier is applied (either via this opt-in or the auto-detect), a
33
- * single `info()` line surfaces the choice so the tier decision is
34
- * visible rather than silent.
31
+ * `PatchMetadata.tier`). Forces the branding-tier thresholds when set,
32
+ * independent of the auto-detect allowlist. When the branding tier is
33
+ * applied, a single `info()` line surfaces the choice so the tier decision
34
+ * is visible rather than silent.
35
35
  */
36
36
  export async function runPatchLint(engineDir, filesAffected, diffContent, config, skipLint, patchQueueCtx, ignoreChecks, patchTier) {
37
- // Compute the tier decision independently of the lint pipeline so the
38
- // decision can be surfaced even when the rule body emitted no issues
39
- // (e.g. a branding patch under the soft threshold still benefits from
40
- // operators knowing which tier governed the run). The same helper is
41
- // reused inside `lintPatchSize`, so the surfaced tier and the tier
42
- // that actually drove the thresholds never drift.
37
+ // Compute the tier decision independently of the lint pipeline so it can
38
+ // be surfaced even when the rule body emitted no issues — a branding patch
39
+ // under the soft threshold still benefits from operators knowing which
40
+ // tier governed the run. The same helper is reused inside `lintPatchSize`,
41
+ // so the surfaced tier and the tier that actually drove the thresholds
42
+ // never drift.
43
43
  const tierDecision = resolvePatchSizeTier(filesAffected, patchTier);
44
44
  if (tierDecision.tier === 'branding') {
45
45
  info(tierDecision.source === 'explicit'
@@ -54,7 +54,12 @@ export async function runPatchLint(engineDir, filesAffected, diffContent, config
54
54
  const checkJsReportScope = patchQueueCtx
55
55
  ? resolvePatchOwnedSysMjs(detectNewFilesInDiff(diffContent))
56
56
  : undefined;
57
- const issues = await lintExportedPatch(engineDir, filesAffected, diffContent, config, patchQueueCtx, ignoreChecks, patchTier, checkJsReportScope ? { checkJsReportScope } : undefined);
57
+ const issues = await lintExportedPatch(engineDir, filesAffected, diffContent, config, {
58
+ ...(patchQueueCtx ? { patchQueueCtx } : {}),
59
+ ...(ignoreChecks ? { ignoreChecks } : {}),
60
+ ...(patchTier ? { patchTier } : {}),
61
+ ...(checkJsReportScope ? { checkJsReportScope } : {}),
62
+ });
58
63
  reportPatchLintOutcome(issues, skipLint);
59
64
  }
60
65
  /**
@@ -115,7 +120,7 @@ export async function promptExportPatchMetadata(options, isInteractive, commandN
115
120
  // both name shapes.
116
121
  let patchName = options.name?.trim().replace(/\.patch$/i, '');
117
122
  if (patchName) {
118
- const validationError = validatePatchName(patchName);
123
+ const validationError = describePatchNameProblem(patchName);
119
124
  if (validationError) {
120
125
  throw new InvalidArgumentError(validationError, '--name');
121
126
  }
@@ -127,7 +132,7 @@ export async function promptExportPatchMetadata(options, isInteractive, commandN
127
132
  const nameResult = await text({
128
133
  message: 'Enter a name for this patch:',
129
134
  placeholder: commandName === 'export' ? 'my-change' : 'my-changes',
130
- validate: (value) => validatePatchName((value ?? '').trim()),
135
+ validate: (value) => describePatchNameProblem((value ?? '').trim()),
131
136
  });
132
137
  if (isCancel(nameResult)) {
133
138
  cancel('Export cancelled');
@@ -217,18 +222,18 @@ export async function confirmSupersedePatches(patchesDir, filesAffected, superse
217
222
  /**
218
223
  * Detects new files missing license headers and offers to add them.
219
224
  *
220
- * In interactive mode the user is prompted before any files are modified.
221
- * In non-interactive mode the function is a no-op — the existing lint error
222
- * will block the export instead.
225
+ * In interactive mode the user is prompted before any files are modified. In
226
+ * non-interactive mode the function is a no-op — the existing lint error
227
+ * blocks the export instead.
223
228
  *
224
229
  * @param engineDir - Absolute path to engine directory
225
230
  * @param diffContent - Current unified diff
226
231
  * @param config - Project configuration
227
232
  * @param isInteractive - Whether interactive prompts are available
228
233
  * @param dryRun - When true, only REPORT missing headers, never prompt or
229
- * write. Dry-run must stay read-only: before this flag existed, an
230
- * interactive `export --dry-run` prompted (default Yes) and wrote license
231
- * headers into engine/ files, then closed with "no changes made".
234
+ * write. Dry-run must stay read-only: without this flag an interactive
235
+ * `export --dry-run` prompts (default Yes), writes license headers into
236
+ * engine/ files, and then closes with "no changes made".
232
237
  * @returns true if files were modified on disk (caller must regenerate diff)
233
238
  */
234
239
  export async function autoFixLicenseHeaders(engineDir, diffContent, config, isInteractive, dryRun = false) {
@@ -324,19 +329,17 @@ export function findPartialOwnershipOverlap(manifest, filesAffected, excludeFile
324
329
  }
325
330
  /**
326
331
  * Gate that refuses the default export path when the new patch would
327
- * silently claim files that are already tracked by other non-superseded
328
- * patches. `findAllPatchesForFiles` already catches the full-coverage
329
- * supersede case — this helper fills the gap for partial overlap, which
330
- * was the eval finding #12 scenario (two patches both claiming
331
- * `browser/themes/shared/jar.inc.mn` after a second export with
332
- * `--before`).
332
+ * silently claim files already tracked by other non-superseded patches.
333
+ * `findAllPatchesForFiles` catches the full-coverage supersede case; this
334
+ * fills the gap for PARTIAL overlap — two patches both claiming e.g.
335
+ * `browser/themes/shared/jar.inc.mn` after a second export with `--before`.
333
336
  *
334
337
  * Proceeds silently when there is no overlap, or when the caller passed
335
338
  * `--allow-overlap`. In interactive mode the caller is prompted to
336
339
  * acknowledge the overlap (the proper fix path is `re-export --files` to
337
340
  * repartition ownership, so the prompt surfaces that pointer). In
338
- * non-interactive mode the function throws — better to fail fast than
339
- * let the queue fall out of sync with verify.
341
+ * non-interactive mode it throws — better to fail fast than let the queue
342
+ * fall out of sync with verify.
340
343
  */
341
344
  export async function guardOwnershipOverlap(args) {
342
345
  const { patchesDir, filesAffected, supersedingFilenames, allowOverlap, isInteractive, s } = args;
@@ -373,13 +376,12 @@ export async function guardOwnershipOverlap(args) {
373
376
  }
374
377
  /**
375
378
  * Runs the two pre-commit gates shared by `export` and `export-all` in
376
- * order: the supersede confirmation, then the cross-patch ownership
377
- * overlap guard. The overlap guard receives the filenames of the patches
378
- * the export would fully supersede so it does not flag a file claimed by
379
- * a patch that is about to be removed (pre-0.16.0 `export` only caught
380
- * FULL-coverage supersedes, so a second export targeting a shared file
381
- * like `browser/themes/shared/jar.inc.mn` created a queue where two
382
- * patches both claimed the file and `verify` failed immediately).
379
+ * order: the supersede confirmation, then the cross-patch ownership overlap
380
+ * guard. The overlap guard receives the filenames of the patches the export
381
+ * would fully supersede so it does not flag a file claimed by a patch that
382
+ * is about to be removed. Catching only FULL-coverage supersedes leaves a
383
+ * second export targeting a shared file creating a queue where two patches
384
+ * both claim it and `verify` fails immediately.
383
385
  *
384
386
  * @param args - Gate inputs shared by both export commands
385
387
  * @param args.patchesDir - Absolute path of the patches directory
@@ -388,8 +390,8 @@ export async function guardOwnershipOverlap(args) {
388
390
  * @param args.allowOverlap - The command's `--allow-overlap` flag
389
391
  * @param args.isInteractive - Whether prompting the operator is possible
390
392
  * @param args.s - Active spinner, stopped before any prompt
391
- * @returns `true` when both gates passed; `false` when the operator
392
- * declined (the caller returns without committing)
393
+ * @returns `true` when both gates passed; `false` when the operator declined
394
+ * (the caller returns without committing)
393
395
  */
394
396
  export async function runSupersedeAndOverlapGates(args) {
395
397
  const { patchesDir, filesAffected, supersede, allowOverlap, isInteractive, s } = args;
@@ -3,11 +3,12 @@ import { stat } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { Command, Option } from 'commander';
5
5
  import { getProjectPaths, loadConfig } from '../core/config.js';
6
- import { appendHistory } from '../core/destructive.js';
6
+ import { appendHistory, stdioIsInteractive } from '../core/destructive.js';
7
+ import { assertEngineGitReady } from '../core/engine-precondition.js';
7
8
  import { withEngineSessionLock } from '../core/engine-session-lock.js';
8
9
  import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
9
10
  import { enforceFreshFurnaceSources } from '../core/furnace-stale-export.js';
10
- import { getStatusWithCodes, isGitRepository } from '../core/git.js';
11
+ import { getStatusWithCodes } from '../core/git.js';
11
12
  import { generateBinaryFilePatch, generateFullFilePatch } from '../core/git-diff.js';
12
13
  import { isBinaryFile } from '../core/git-file-ops.js';
13
14
  import { getModifiedFilesInDir, getUntrackedFiles, getUntrackedFilesInDir, } from '../core/git-status.js';
@@ -33,11 +34,10 @@ async function collectExportFiles(paths, files) {
33
34
  let untrackedFiles;
34
35
  // Accept both repo-root-relative (`engine/browser/...`) and engine-relative
35
36
  // (`browser/...`) paths for every input, matching `register`/`test`/`lint`.
36
- // Previously, an `engine/`-prefixed path fell through to
37
- // `File "engine/..." has no changes to export.` because the status lookup
38
- // sees paths relative to `paths.engine` and the explicit prefix double-
39
- // rooted the candidate. `stripEnginePrefix` makes that user-facing form
40
- // a no-op for the lookup pipeline.
37
+ // Without the strip, an `engine/`-prefixed path falls through to
38
+ // `File "engine/..." has no changes to export.`: the status lookup sees
39
+ // paths relative to `paths.engine`, so the explicit prefix double-roots the
40
+ // candidate.
41
41
  for (const rawInputPath of files) {
42
42
  const inputPath = stripEnginePrefix(rawInputPath);
43
43
  const fullInputPath = join(paths.engine, inputPath);
@@ -88,13 +88,14 @@ async function collectExportFiles(paths, files) {
88
88
  return { files: [...collectedFiles].sort(), fromDirectory };
89
89
  }
90
90
  /**
91
- * Auto-excludes directory-derived files already owned by OTHER patches
92
- * (0.34.0 field report): a directory export used to plan files owned by
93
- * earlier patches into the new patch, and the duplicate-new-file-creation
94
- * refusal surfaced only at placement lint, suggesting --force-unsafe —
95
- * the wrong tool for "leave that file with its owner". Explicitly named
96
- * files are never excluded (the overlap gates still confront the operator
97
- * with those). Prints one notice per exclusion.
91
+ * Auto-excludes directory-derived files already owned by OTHER patches.
92
+ *
93
+ * A directory export otherwise plans files owned by earlier patches into the
94
+ * new patch, and the duplicate-new-file-creation refusal surfaces only at
95
+ * placement lint, suggesting `--force-unsafe` — the wrong tool for "leave
96
+ * that file with its owner". Explicitly named files are never excluded (the
97
+ * overlap gates still confront the operator with those). Prints one notice
98
+ * per exclusion.
98
99
  */
99
100
  async function excludeFilesOwnedByOtherPatches(patchesDir, collected) {
100
101
  if (collected.fromDirectory.size === 0)
@@ -169,14 +170,7 @@ async function prepareExport(projectRoot, files, options) {
169
170
  throw new InvalidArgumentError('--order, --before, and --after are mutually exclusive.', 'export placement');
170
171
  }
171
172
  const paths = getProjectPaths(projectRoot);
172
- // Check if engine exists
173
- if (!(await pathExists(paths.engine))) {
174
- throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
175
- }
176
- // Check if it's a git repository
177
- if (!(await isGitRepository(paths.engine))) {
178
- throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
179
- }
173
+ await assertEngineGitReady(paths.engine);
180
174
  const collected = await collectExportFiles(paths, files);
181
175
  let allFiles = await excludeFilesOwnedByOtherPatches(paths.patches, collected);
182
176
  const ownershipExclusions = collected.files.length - allFiles.length;
@@ -202,10 +196,10 @@ async function prepareExport(projectRoot, files, options) {
202
196
  throw new GeneralError(`Paths "${pathList}" have no changes to export.\n\n` +
203
197
  'Run "fireforge status" to see modified files.');
204
198
  }
205
- // Stale-furnace-source gate (0.37.0 item 4): the export captures deployed
206
- // engine copies; refuse (or warn under --allow-stale-furnace) when a
207
- // covered component's source changed since the last furnace apply. Runs
208
- // after --exclude-furnace so excluded furnace files never trigger it.
199
+ // Stale-furnace-source gate: the export captures deployed engine copies,
200
+ // so refuse (or warn under --allow-stale-furnace) when a covered
201
+ // component's source changed since the last furnace apply. Runs after
202
+ // --exclude-furnace so excluded furnace files never trigger it.
209
203
  await enforceFreshFurnaceSources(projectRoot, allFiles, options.allowStaleFurnace === true, 'export');
210
204
  let diff = await generatePatchDiff(paths.engine, allFiles);
211
205
  if (!diff.trim()) {
@@ -222,7 +216,7 @@ async function prepareExport(projectRoot, files, options) {
222
216
  await ensureDir(paths.patches);
223
217
  }
224
218
  const config = await loadConfig(projectRoot);
225
- const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
219
+ const isInteractive = stdioIsInteractive();
226
220
  // Auto-fix missing license headers on new files (interactive only;
227
221
  // report-only under --dry-run so the preview never mutates engine/)
228
222
  const headersAdded = await autoFixLicenseHeaders(paths.engine, diff, config, isInteractive, options.dryRun ?? false);