@hominis/fireforge 0.43.0 → 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 +127 -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 +62 -19
  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
@@ -4,6 +4,7 @@ import { readdir, readFile } from 'node:fs/promises';
4
4
  import { join } from 'node:path';
5
5
  import { Command } from 'commander';
6
6
  import { getProjectPaths, loadConfig } from '../core/config.js';
7
+ import { assertEngineExists } from '../core/engine-precondition.js';
7
8
  import { warnIfFurnaceStale } from '../core/furnace-staleness.js';
8
9
  import { hasBuildArtifacts, hasRunnableBundle, run, runMachSmoke } from '../core/mach.js';
9
10
  import { assertBuildArtifacts } from '../core/mach-build-artifacts.js';
@@ -78,9 +79,7 @@ export async function runCommand(projectRoot, options = {}) {
78
79
  intro('FireForge Run');
79
80
  const paths = getProjectPaths(projectRoot);
80
81
  // Check if engine exists
81
- if (!(await pathExists(paths.engine))) {
82
- throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
83
- }
82
+ await assertEngineExists(paths.engine);
84
83
  const buildCheck = await hasBuildArtifacts(paths.engine);
85
84
  assertBuildArtifacts(paths.engine, buildCheck, {
86
85
  label: 'Run',
@@ -88,16 +87,14 @@ export async function runCommand(projectRoot, options = {}) {
88
87
  remediation: "Run 'fireforge build' first, then rerun 'fireforge run'.",
89
88
  requireExisting: true,
90
89
  });
91
- // `hasBuildArtifacts` only checks for an `obj-*/dist/` directory; a
92
- // build that configured but hasn't yet produced the launchable binary
93
- // (common in a long real Firefox compile that the operator stopped
94
- // and restarted) passes that check, and `mach run` then fails on the
95
- // missing binary path. `hasRunnableBundle` narrows the probe to the
96
- // actual executable so `fireforge run` refuses with a targeted
97
- // message before handing control to mach. `fireforge watch` stays
98
- // permissive and instead surfaces the same information as a banner
99
- // suffix; watch is supposed to drive rebuilds of partially-built
100
- // trees, so blocking there would defeat the feature.
90
+ // `hasBuildArtifacts` only checks for an `obj-*/dist/` directory; a build
91
+ // that configured but has not yet produced the launchable binary passes
92
+ // that check, and `mach run` then fails on the missing binary path.
93
+ // `hasRunnableBundle` narrows the probe to the actual executable so
94
+ // `fireforge run` refuses with a targeted message before handing control
95
+ // to mach. `fireforge watch` stays permissive and surfaces the same
96
+ // information as a banner suffix; watch exists to drive rebuilds of
97
+ // partially-built trees, so blocking there would defeat it.
101
98
  if (buildCheck.objDir) {
102
99
  const config = await loadConfig(projectRoot);
103
100
  const bundleCheck = await hasRunnableBundle(paths.engine, config.binaryName, buildCheck.objDir);
@@ -142,7 +139,7 @@ async function runSmokeExit(engineDir, options) {
142
139
  // SIGTERM the whole mach → python → firefox tree. Running through anyway
143
140
  // would only kill the top-level wrapper and orphan Firefox content
144
141
  // processes, so reject the flag up front to match the documented contract
145
- // in CHANGELOG.md / README.md.
142
+ // in the README.
146
143
  if (process.platform === 'win32') {
147
144
  throw new InvalidArgumentError('--smoke-exit is POSIX-only; process-group semantics do not map cleanly onto Windows.', 'smokeExit');
148
145
  }
@@ -187,14 +184,13 @@ async function runSmokeExit(engineDir, options) {
187
184
  const sink = stream === 'stdout' ? process.stdout : process.stderr;
188
185
  sink.write(`${line}\n`);
189
186
  // Count allowlist hits up-front, regardless of error-pattern match.
190
- // Pre-0.16.0 the counter only incremented when the line ALSO matched
191
- // an error pattern — so an allowlist regex that visibly matched
192
- // `console.warn: RSLoader:` still reported 0 hits because
193
- // `console.warn:` is not a smoke error class, confusing operators
194
- // who were tuning their allowlist. We now surface two numbers: the
195
- // total set of allowlisted lines (what the operator sees in the
196
- // console) and the subset that were error-class (what the smoke
197
- // exit contract cares about). The exit contract itself is unchanged.
187
+ // Incrementing only when the line ALSO matches an error pattern makes an
188
+ // allowlist regex that visibly matches `console.warn: RSLoader:` report 0
189
+ // hits, because `console.warn:` is not a smoke error class — confusing
190
+ // for operators tuning their allowlist. Two numbers are surfaced: the
191
+ // total set of allowlisted lines (what the operator sees in the console)
192
+ // and the subset that were error-class (what the smoke exit contract
193
+ // cares about). The exit contract itself is unchanged.
198
194
  const matchIndex = allowlist.length > 0 ? matchAllowlist(line, allowlist) : -1;
199
195
  const isAllowlisted = matchIndex !== -1;
200
196
  if (isAllowlisted) {
@@ -209,12 +205,12 @@ async function runSmokeExit(engineDir, options) {
209
205
  }
210
206
  findings.push({ stream, line });
211
207
  };
212
- // A headed smoke window on a developer desktop absorbs live input:
213
- // during the drill a human interacted with the window mid-run, the
214
- // password-manager import scan probed an unreadable Chrome profile
215
- // dir, and the resulting console errors failed the smoke run looking
216
- // like a product regression. CI hosts (CI env var set) are assumed
217
- // display-free/unattended, so the notice stays quiet there.
208
+ // A headed smoke window on a developer desktop absorbs live input: a human
209
+ // interacting with the window mid-run can trigger console errors — a
210
+ // password-manager import scan probing an unreadable profile dir, say —
211
+ // that fail the smoke run looking like a product regression. CI hosts (CI
212
+ // env var set) are assumed display-free and unattended, so the notice
213
+ // stays quiet there.
218
214
  if (!options.headless && !process.env['CI']) {
219
215
  warn('Headed smoke window: keyboard/mouse input during the window will contaminate the ' +
220
216
  'console capture and can fail the run. Pass --headless (or run on an unattended host) ' +
@@ -303,12 +299,11 @@ function reportSmokeSummary(args) {
303
299
  info('');
304
300
  info(`Smoke run complete: ${seconds}s elapsed of ${windowSeconds}s window${suffix}`);
305
301
  info(` Unallowed errors: ${String(args.findings.length)}`);
306
- // The "suppressed errors" count is what the exit contract cares about —
307
- // it is the subset of allowlisted hits that would otherwise have been
308
- // tallied as findings. The "all allowlisted lines" count answers the
309
- // operator's mental model ("my --console-allow pattern matched N
310
- // console lines"), which pre-0.16.0 was missing and led to 0-hit
311
- // reports on visibly matching regexes.
302
+ // The "suppressed errors" count is what the exit contract cares about — the
303
+ // subset of allowlisted hits that would otherwise have been tallied as
304
+ // findings. The "all allowlisted lines" count answers the operator's mental
305
+ // model ("my --console-allow pattern matched N console lines"), without
306
+ // which a visibly matching regex reports 0 hits.
312
307
  info(` Allowlisted error hits (suppressed): ${String(args.allowlistedErrorHits)}`);
313
308
  info(` Allowlisted lines total: ${String(args.allowlistedTotalHits)}`);
314
309
  info(` Child exit code: ${String(args.exitCode)}`);
@@ -351,7 +346,10 @@ export function registerRun(program, { getProjectRoot, withErrorHandling }) {
351
346
  .option('--console-allow-file <path>', 'Newline-delimited allowlist regex file. Blank lines and # comments are ignored.')
352
347
  .option('--capture-console <file>', 'Mirror captured console output to <file> for post-exit inspection.')
353
348
  .option('--headless', 'Launch the browser with --headless. Recommended for --smoke-exit on a shared desktop: input into a headed smoke window contaminates the console capture.')
354
- .action(withErrorHandling(async (options) => {
349
+ .action(withErrorHandling(
350
+ // `args` is the variadic positional, not a Commander flag, so it
351
+ // never appears in this object.
352
+ async (options) => {
355
353
  await runCommand(getProjectRoot(), pickDefined(options));
356
354
  }));
357
355
  }
@@ -8,7 +8,7 @@ import { CancellationError, InvalidArgumentError } from '../errors/base.js';
8
8
  import { ensureDir, pathExists, readText, writeText } from '../utils/fs.js';
9
9
  import { cancel } from '../utils/logger.js';
10
10
  import { getPackageRoot } from '../utils/package-root.js';
11
- import { inferProductFromVersion, isValidAppId, isValidFirefoxVersion, isValidProjectLicense, } from '../utils/validation.js';
11
+ import { FIREFOX_PRODUCTS, inferProductFromVersion, isValidAppId, isValidFirefoxProduct, isValidFirefoxVersion, isValidProjectLicense, } from '../utils/validation.js';
12
12
  function getTemplatesDir() {
13
13
  return join(getPackageRoot(), 'templates');
14
14
  }
@@ -19,13 +19,13 @@ function renderLicenseTemplate(license, template, vendor, now = new Date()) {
19
19
  return template.replace(/\[year\]/g, String(now.getFullYear())).replace(/\[fullname\]/g, vendor);
20
20
  }
21
21
  function resolveFirefoxProduct(value, field) {
22
- if (value === 'firefox' ||
23
- value === 'firefox-esr' ||
24
- value === 'firefox-beta' ||
25
- value === 'firefox-devedition') {
22
+ // Matches the sibling `resolveProjectLicense`, which already delegates to
23
+ // its guard. The `||` chain this replaced spelled the member list twice —
24
+ // once as the predicate and once in the message.
25
+ if (typeof value === 'string' && isValidFirefoxProduct(value)) {
26
26
  return value;
27
27
  }
28
- throw new InvalidArgumentError('Invalid product (use: firefox, firefox-esr, firefox-beta, firefox-devedition)', field);
28
+ throw new InvalidArgumentError(`Invalid product (use: ${FIREFOX_PRODUCTS.join(', ')})`, field);
29
29
  }
30
30
  function resolveProjectLicense(value, field) {
31
31
  if (typeof value === 'string' && isValidProjectLicense(value)) {
@@ -269,14 +269,11 @@ export function buildSetupConfig(inputs) {
269
269
  }
270
270
  /**
271
271
  * Creates or updates the root `package.json` so its `license` field matches
272
- * the project license selected during setup. When the file already exists we
273
- * ONLY touch the `license` field — preserving `name`, `description`,
274
- * `dependencies`, `scripts`, and every other author-editorial field the
275
- * operator may have added. Without this sync, a `fireforge setup --force`
276
- * that picked a new license left the old license in `package.json`, which
277
- * then disagreed with `fireforge.json` (the motivating eval finding:
278
- * setup rewrote fireforge.json but left the original package.json
279
- * untouched, so the two files described different projects).
272
+ * the project license selected during setup. When the file already exists
273
+ * ONLY the `license` field is touched — preserving `name`, `description`,
274
+ * `dependencies`, `scripts`, and every other author-editorial field. Without
275
+ * this sync, a `fireforge setup --force` that picks a new license leaves the
276
+ * old one in `package.json`, so the two files describe different projects.
280
277
  *
281
278
  * Preserves the file's trailing newline state so a hand-edited
282
279
  * `package.json` with a specific EOL convention is not silently
@@ -307,12 +304,11 @@ async function syncRootPackageJson(projectRoot, license) {
307
304
  if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
308
305
  return;
309
306
  }
310
- // Treat the object as a typed shape with only the one field we modify.
307
+ // Treat the object as a typed shape with only the one field modified.
311
308
  // Keeping it narrowly typed rather than `Record<string, unknown>` avoids
312
- // eslint's `dot-notation` / `noPropertyAccessFromIndexSignature`
313
- // friction, and the rest of the package.json body is preserved via
314
- // object spread at the write site so we don't lose author-editorial
315
- // fields.
309
+ // eslint's `dot-notation` / `noPropertyAccessFromIndexSignature` friction,
310
+ // and the rest of the package.json body is preserved via object spread at
311
+ // the write site.
316
312
  const packageJson = parsed;
317
313
  if (packageJson.license === license) {
318
314
  return;
@@ -3,10 +3,11 @@ import { resolve } from 'node:path';
3
3
  import { confirm } from '@clack/prompts';
4
4
  import { Command, Option } from 'commander';
5
5
  import { configExists } from '../core/config.js';
6
+ import { stdioIsInteractive } from '../core/destructive.js';
6
7
  import { ConfigError } from '../errors/config.js';
7
8
  import { cancel, intro, isCancel, note, outro, spinner } from '../utils/logger.js';
8
9
  import { pickDefined } from '../utils/options.js';
9
- import { PROJECT_LICENSES } from '../utils/validation.js';
10
+ import { FIREFOX_PRODUCTS, PROJECT_LICENSES } from '../utils/validation.js';
10
11
  import { buildSetupConfig, parseFirefoxProductOption, parseProjectLicenseOption, resolveSetupInputs, validateSetupOptions, writeSetupProjectFiles, } from './setup-support.js';
11
12
  /**
12
13
  * Runs the setup command.
@@ -17,7 +18,7 @@ export async function setupCommand(projectRoot, options = {}) {
17
18
  // Validate any CLI-provided options first
18
19
  validateSetupOptions(options);
19
20
  // Determine if we can run interactively
20
- const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
21
+ const isInteractive = stdioIsInteractive();
21
22
  intro('FireForge Setup');
22
23
  // Check if config already exists
23
24
  if (await configExists(projectRoot)) {
@@ -67,17 +68,20 @@ export function registerSetup(program, { withErrorHandling }) {
67
68
  .option('--app-id <appId>', 'Application ID (reverse-domain format)')
68
69
  .option('--binary-name <binaryName>', 'Binary name (executable name)')
69
70
  .option('--firefox-version <version>', 'Firefox version to base on')
70
- .addOption(new Option('--product <product>', 'Firefox product').choices([
71
- 'firefox',
72
- 'firefox-esr',
73
- 'firefox-beta',
74
- 'firefox-devedition',
75
- ]))
71
+ .addOption(new Option('--product <product>', 'Firefox product').choices([...FIREFOX_PRODUCTS]))
76
72
  .addOption(new Option('--license <license>', 'Project license').choices([...PROJECT_LICENSES]))
77
73
  .option('-f, --force', 'Overwrite existing configuration without prompting')
74
+ // `--yes` is a plain alias here: `setup`'s only bypass is the overwrite
75
+ // prompt, so the two flags mean exactly the same thing. Seventeen other
76
+ // commands spell this bypass `--yes`; accepting it means a scripted
77
+ // sequence can use one spelling throughout.
78
+ .option('-y, --yes', 'Alias for --force: skip the overwrite confirmation')
78
79
  .action(withErrorHandling(async (options) => {
79
- const { product, license, ...rest } = options;
80
+ const { product, license, yes, ...rest } = options;
80
81
  const setupOptions = { ...pickDefined(rest) };
82
+ // `--yes` is an alias, so either flag sets the same bypass.
83
+ if (yes === true)
84
+ setupOptions.force = true;
81
85
  if (product !== undefined) {
82
86
  const parsedProduct = parseFirefoxProductOption(product);
83
87
  if (parsedProduct !== undefined) {
@@ -1,11 +1,11 @@
1
1
  /**
2
- * `status --check` enforcement policy. Turns the classified
3
- * status view into a CI-enforceable non-zero exit: the default policy
4
- * fails on any classification outside {patch-backed, branding, furnace},
5
- * and `--fail-on <class,...>` replaces that set for finer control.
2
+ * `status --check` enforcement policy. Turns the classified status view into
3
+ * a CI-enforceable non-zero exit: the default policy fails on any
4
+ * classification outside {patch-backed, branding, furnace}, and
5
+ * `--fail-on <class,...>` replaces that set for finer control.
6
6
  *
7
- * Helper module consumed by status.ts (which sits at the max-lines
8
- * budget); no top-level registrar is exported and none is wanted.
7
+ * Helper module consumed by status.ts (which sits at the max-lines budget);
8
+ * no top-level registrar is exported and none is wanted.
9
9
  */
10
10
  import type { ClassifiedFile, FileClassification } from '../core/status-classify.js';
11
11
  /** Resolved `--check`/`--fail-on` policy for one status invocation. */
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * `status --json` payload rendering (schemaVersion 1). Helper module
3
- * consumed by status.ts (which sits at the max-lines budget); no
4
- * top-level registrar is exported and none is wanted.
3
+ * consumed by status.ts (which sits at the max-lines budget); no top-level
4
+ * registrar is exported and none is wanted.
5
5
  *
6
6
  * Two shapes share one counting pass:
7
7
  * - the full payload (`renderJsonStatus`) with the per-file `files[]` list;
8
- * - the `--summary` gate payload (`renderJsonSummaryStatus`),
9
- * which omits `files[]` entirely — engine-clean gates need the verdict,
10
- * per-class counts, and offender names, not a payload that grows with
11
- * the queue (175 KB+ observed on a 290-patch consumer).
8
+ * - the `--summary` gate payload (`renderJsonSummaryStatus`), which omits
9
+ * `files[]` entirely — engine-clean gates need the verdict, per-class
10
+ * counts, and offender names, not a payload that grows with the queue
11
+ * (175 KB+ on a large one).
12
12
  */
13
13
  import type { ClassifiedFile } from '../core/status-classify.js';
14
14
  import { type StatusCheckPolicy } from './status-check.js';
@@ -1,3 +1,4 @@
1
+ import { setStdoutSealed } from '../utils/logger.js';
1
2
  import { collectStatusCheckOffenders, } from './status-check.js';
2
3
  function countByClassification(classified) {
3
4
  const byClassification = {
@@ -43,6 +44,8 @@ export function renderJsonStatus(classified, ownership) {
43
44
  ...(ownership !== undefined ? { ownership } : {}),
44
45
  };
45
46
  process.stdout.write(JSON.stringify(output, null, 2) + '\n');
47
+ // Payload written: stdout is now spoken for. See docs/machine-output.md.
48
+ setStdoutSealed(true);
46
49
  }
47
50
  /**
48
51
  * Summary-only `--json --summary` payload: counts always; when the
@@ -73,4 +76,6 @@ export function renderJsonSummaryStatus(classified, policy, ownership) {
73
76
  output.ownership = ownership;
74
77
  }
75
78
  process.stdout.write(JSON.stringify(output, null, 2) + '\n');
79
+ // Payload written: stdout is now spoken for. See docs/machine-output.md.
80
+ setStdoutSealed(true);
76
81
  }
@@ -14,10 +14,10 @@ export declare function renderUnmanagedOnly(unmanagedFiles: ClassifiedFile[], to
14
14
  /** Renders the default classified status buckets. */
15
15
  export declare function renderDefaultStatus(totalModified: number, buckets: ClassifiedBuckets, projectRoot: string, binaryName: string): Promise<void>;
16
16
  /**
17
- * Renders `fireforge status --test-coverage`: a READ-ONLY view
18
- * of the last build baseline's test-packaging coverage. Before this
19
- * existed, the only way to learn the recorded coverage scope — which
20
- * concurrent sessions sharing one engine tree overwrite constantly — was
21
- * to trip the out-of-coverage refusal on a real test run.
17
+ * Renders `fireforge status --test-coverage`: a READ-ONLY view of the last
18
+ * build baseline's test-packaging coverage. Without it, the only way to
19
+ * learn the recorded coverage scope — which concurrent sessions sharing one
20
+ * engine tree overwrite constantly — is to trip the out-of-coverage refusal
21
+ * on a real test run.
22
22
  */
23
23
  export declare function renderTestCoverageStatus(baseline: BuildBaseline | undefined): void;
@@ -1,4 +1,4 @@
1
- import { isFileRegistered, matchesRegistrablePattern } from '../core/manifest-rules.js';
1
+ import { isFileRegistered, matchesRegistrablePattern } from '../core/moz-manifest-rules.js';
2
2
  import { getPrimaryStatusCode } from '../core/status-classify.js';
3
3
  import { GeneralError } from '../errors/base.js';
4
4
  import { info, outro, warn } from '../utils/logger.js';
@@ -191,11 +191,11 @@ export async function renderDefaultStatus(totalModified, buckets, projectRoot, b
191
191
  outro(parts.join(', '));
192
192
  }
193
193
  /**
194
- * Renders `fireforge status --test-coverage`: a READ-ONLY view
195
- * of the last build baseline's test-packaging coverage. Before this
196
- * existed, the only way to learn the recorded coverage scope — which
197
- * concurrent sessions sharing one engine tree overwrite constantly — was
198
- * to trip the out-of-coverage refusal on a real test run.
194
+ * Renders `fireforge status --test-coverage`: a READ-ONLY view of the last
195
+ * build baseline's test-packaging coverage. Without it, the only way to
196
+ * learn the recorded coverage scope — which concurrent sessions sharing one
197
+ * engine tree overwrite constantly — is to trip the out-of-coverage refusal
198
+ * on a real test run.
199
199
  */
200
200
  export function renderTestCoverageStatus(baseline) {
201
201
  if (baseline === undefined) {
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Ownership-table assembly for `status`, split out of `status.ts` (FORGE
3
- * L3) so the human `--ownership` mode and the additive `ownership` block on
4
- * the `--json` payload build the SAME rows from the SAME scan.
2
+ * Ownership-table assembly for `status`, split out of `status.ts` so the
3
+ * human `--ownership` mode and the additive `ownership` block on the
4
+ * `--json` payload build the SAME rows from the SAME scan.
5
5
  *
6
- * Before L3 the `--ownership` branch re-implemented the shared scan and
7
- * inlined a second copy of the classification call, then discarded
8
- * everything but each file's classification — three back-to-back `status`
9
- * invocations in a gate therefore paid three full worktree scans. Both
10
- * callers now hand this module the already-classified files.
6
+ * A separate `--ownership` branch re-implements the shared scan and inlines
7
+ * a second copy of the classification call, then discards everything but
8
+ * each file's classification — so three back-to-back `status` invocations in
9
+ * a gate pay three full worktree scans. Both callers hand this module the
10
+ * already-classified files instead.
11
11
  *
12
12
  * No renderer lives here: `renderOwnershipTable` (human) and the JSON
13
13
  * serializer stay with their respective output layers.
@@ -29,13 +29,15 @@ export interface OwnershipJsonBlock {
29
29
  /**
30
30
  * Builds the flat path→owning-patch rows. Sources are the manifest's
31
31
  * `filesAffected`, worktree drift, and the cross-patch
32
- * duplicate-new-file-creation map produced by walking each patch body —
33
- * the last being the alignment fix between `status --ownership` and
34
- * `fireforge verify` (see `buildOwnershipTable`'s header).
35
- * @param patchesDir - Path to the patches directory
36
- * @param manifestPatches - Patch rows from patches.json (empty when absent)
37
- * @param files - Scanned worktree entries, temp-filtered
32
+ * duplicate-new-file-creation map produced by walking each patch body — the
33
+ * last being what keeps `status --ownership` aligned with `fireforge verify`
34
+ * (see `buildOwnershipTable`'s header).
35
+ *
36
+ * @param patchesDir - Absolute path to the patches directory
37
+ * @param manifestPatches - Manifest rows, each with its `filesAffected`
38
+ * @param files - Raw worktree status entries
38
39
  * @param classified - The same entries, already classified
40
+ * @returns The flat ownership rows
39
41
  */
40
42
  export declare function collectOwnershipRows(patchesDir: string, manifestPatches: readonly PatchMetadata[], files: StatusFile[], classified: readonly ClassifiedFile[]): Promise<OwnershipRow[]>;
41
43
  /**
@@ -1,14 +1,14 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  /**
3
- * Ownership-table assembly for `status`, split out of `status.ts` (FORGE
4
- * L3) so the human `--ownership` mode and the additive `ownership` block on
5
- * the `--json` payload build the SAME rows from the SAME scan.
3
+ * Ownership-table assembly for `status`, split out of `status.ts` so the
4
+ * human `--ownership` mode and the additive `ownership` block on the
5
+ * `--json` payload build the SAME rows from the SAME scan.
6
6
  *
7
- * Before L3 the `--ownership` branch re-implemented the shared scan and
8
- * inlined a second copy of the classification call, then discarded
9
- * everything but each file's classification — three back-to-back `status`
10
- * invocations in a gate therefore paid three full worktree scans. Both
11
- * callers now hand this module the already-classified files.
7
+ * A separate `--ownership` branch re-implements the shared scan and inlines
8
+ * a second copy of the classification call, then discards everything but
9
+ * each file's classification — so three back-to-back `status` invocations in
10
+ * a gate pay three full worktree scans. Both callers hand this module the
11
+ * already-classified files instead.
12
12
  *
13
13
  * No renderer lives here: `renderOwnershipTable` (human) and the JSON
14
14
  * serializer stay with their respective output layers.
@@ -19,13 +19,15 @@ import { pathExists } from '../utils/fs.js';
19
19
  /**
20
20
  * Builds the flat path→owning-patch rows. Sources are the manifest's
21
21
  * `filesAffected`, worktree drift, and the cross-patch
22
- * duplicate-new-file-creation map produced by walking each patch body —
23
- * the last being the alignment fix between `status --ownership` and
24
- * `fireforge verify` (see `buildOwnershipTable`'s header).
25
- * @param patchesDir - Path to the patches directory
26
- * @param manifestPatches - Patch rows from patches.json (empty when absent)
27
- * @param files - Scanned worktree entries, temp-filtered
22
+ * duplicate-new-file-creation map produced by walking each patch body — the
23
+ * last being what keeps `status --ownership` aligned with `fireforge verify`
24
+ * (see `buildOwnershipTable`'s header).
25
+ *
26
+ * @param patchesDir - Absolute path to the patches directory
27
+ * @param manifestPatches - Manifest rows, each with its `filesAffected`
28
+ * @param files - Raw worktree status entries
28
29
  * @param classified - The same entries, already classified
30
+ * @returns The flat ownership rows
29
31
  */
30
32
  export async function collectOwnershipRows(patchesDir, manifestPatches, files, classified) {
31
33
  // Only walk patch bodies when the directory actually exists: a fresh
@@ -2,17 +2,19 @@
2
2
  import { Command } from 'commander';
3
3
  import { readBuildBaseline } from '../core/build-baseline.js';
4
4
  import { getProjectPaths, loadConfig } from '../core/config.js';
5
+ import { assertEngineExists } from '../core/engine-precondition.js';
5
6
  import { formatEngineSessionLockStatus, readEngineSessionLockStatus, } from '../core/engine-session-lock.js';
6
7
  import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
7
8
  import { getHead, getStatusWithCodes, isGitRepository, isMissingHeadError } from '../core/git.js';
8
9
  import { getUntrackedFilesInDir, resolveMaxUntrackedFilesPerDir } from '../core/git-status.js';
9
10
  import { renderOwnershipTable } from '../core/ownership-table.js';
10
11
  import { loadPatchesManifest } from '../core/patch-manifest.js';
12
+ import { readProcessCpuSeconds } from '../core/process-cpu-time.js';
11
13
  import { classifyFiles } from '../core/status-classify.js';
12
- import { CommandError, GeneralError, InvalidArgumentError } from '../errors/base.js';
13
- import { ExitCode } from '../errors/codes.js';
14
+ import { GeneralError, InvalidArgumentError } from '../errors/base.js';
14
15
  import { FIREFORGE_TMP_PATH_PATTERN, pathExists } from '../utils/fs.js';
15
16
  import { info, intro, isMachineOutputMode, outro, setMachineOutputMode, warn, } from '../utils/logger.js';
17
+ import { emitMachineError } from '../utils/machine-output.js';
16
18
  import { resolveStatusCheckPolicy, runStatusCheck } from './status-check.js';
17
19
  import { renderJsonStatus, renderJsonSummaryStatus } from './status-json.js';
18
20
  import { renderDefaultStatus, renderTestCoverageStatus, renderUnmanagedOnly, } from './status-output.js';
@@ -96,12 +98,13 @@ async function classifyStatusFiles(files, paths, projectRoot, binaryName) {
96
98
  /**
97
99
  * The one worktree scan every classifying mode shares: porcelain status,
98
100
  * collapsed-directory expansion, atomic-temp-file filtering, and the
99
- * truncation banner. `--ownership` used to carry its own copy of this
100
- * block; a non-git engine degrades to an empty list there,
101
- * which is why the git probe is a parameter rather than a hard guard.
101
+ * truncation banner. A non-git engine degrades to an empty list for
102
+ * `--ownership`, which is why the git probe is a parameter rather than a
103
+ * hard guard.
104
+ *
102
105
  * @param engineDir - Path to the engine directory
103
106
  * @param requireGitRepository - When false, a non-git engine yields `[]`
104
- * instead of being probed (the historical `--ownership` behavior)
107
+ * instead of being probed
105
108
  */
106
109
  async function scanEngineStatusFiles(engineDir, requireGitRepository) {
107
110
  if (!requireGitRepository && !(await isGitRepository(engineDir))) {
@@ -112,12 +115,13 @@ async function scanEngineStatusFiles(engineDir, requireGitRepository) {
112
115
  return filterFireForgeTempFiles(entries);
113
116
  }
114
117
  /**
115
- * Emits the `--json` payload (full or `--summary`) and applies the
116
- * `--check` policy, which stays the SOLE exit driver on this path.
118
+ * Emits the `--json` payload (full or `--summary`) and applies the `--check`
119
+ * policy, which stays the SOLE exit driver on this path.
120
+ *
121
+ * `--include-ownership` is a MODIFIER, not a mode: it appends an ownership
122
+ * block without touching exit semantics, so an ownership conflict still
123
+ * fails only the human `--ownership` mode.
117
124
  *
118
- * `--include-ownership` is a MODIFIER, not a mode: it appends an
119
- * ownership block without touching exit semantics, so an ownership conflict
120
- * still fails only the human `--ownership` mode.
121
125
  * @param classified - Classified worktree entries
122
126
  * @param files - The same entries pre-classification, for ownership rows
123
127
  * @param paths - Resolved project paths
@@ -187,8 +191,8 @@ export async function statusCommand(projectRoot, options = {}) {
187
191
  }
188
192
  // On a throw, machine mode deliberately stays ENGAGED: withErrorHandling
189
193
  // routes the styled error to stderr while the mode is on and resets it
190
- // centrally (a mid-throw restore here put the refusal on stdout after the
191
- // JSON payload, breaking the 0.40.0 stderr promise).
194
+ // centrally. A mid-throw restore here would put the refusal on stdout
195
+ // after the JSON payload.
192
196
  await runStatusCommandBody(projectRoot, options);
193
197
  if (isMachineOutputMode() !== previousMachineOutputMode) {
194
198
  setMachineOutputMode(previousMachineOutputMode);
@@ -229,7 +233,11 @@ async function runStatusCommandBody(projectRoot, options) {
229
233
  // concurrent command is busy with, and that answer must not depend on
230
234
  // engine/ being readable.
231
235
  if (options.lock === true) {
232
- for (const line of formatEngineSessionLockStatus(await readEngineSessionLockStatus(projectRoot))) {
236
+ const snapshot = await readEngineSessionLockStatus(projectRoot);
237
+ const holderCpuSeconds = snapshot.holder?.alive === true
238
+ ? await readProcessCpuSeconds(snapshot.holder.pid)
239
+ : undefined;
240
+ for (const line of formatEngineSessionLockStatus(snapshot, holderCpuSeconds)) {
233
241
  info(line);
234
242
  }
235
243
  outro('Lock status');
@@ -243,24 +251,22 @@ async function runStatusCommandBody(projectRoot, options) {
243
251
  }
244
252
  const paths = getProjectPaths(projectRoot);
245
253
  const config = await loadConfig(projectRoot);
246
- const emitJsonError = (code, message) => {
247
- process.stdout.write(JSON.stringify({ schemaVersion: 1, error: message, code }) + '\n');
248
- throw new CommandError(ExitCode.GENERAL_ERROR);
249
- };
254
+ // Shared envelope: see src/utils/machine-output.ts and
255
+ // docs/machine-output.md. `status` was the only command that had one.
256
+ const emitJsonError = emitMachineError;
250
257
  // Ownership mode is a flat file→patch table; sources are the manifest's
251
258
  // filesAffected, any worktree drift, and the cross-patch
252
- // duplicate-new-file-creation map produced by walking each patch
253
- // body. The latter is the alignment fix between `status --ownership`
254
- // and `fireforge verify` — see buildOwnershipTable's header comment.
255
- // Runs before the default classify path so we can short-circuit
256
- // without computing patch-backed state.
259
+ // duplicate-new-file-creation map produced by walking each patch body. The
260
+ // last is what keeps `status --ownership` aligned with `fireforge verify`
261
+ // — see buildOwnershipTable's header. Runs before the default classify
262
+ // path so it can short-circuit without computing patch-backed state.
257
263
  if (options.ownership) {
258
- if (!(await pathExists(paths.engine))) {
259
- throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
260
- }
264
+ // Rung 1 only, deliberately — see the note below on why this branch
265
+ // does not take the full assertEngineGitReady ladder.
266
+ await assertEngineExists(paths.engine);
261
267
  const manifest = await loadPatchesManifest(paths.patches);
262
- // Same scan and the SAME classification pass as every other mode
263
- //. This branch keeps its historical guard shape on purpose:
268
+ // Same scan and the SAME classification pass as every other mode.
269
+ // This branch keeps its historical guard shape on purpose:
264
270
  // no baseline-commit assertion, and a non-git engine degrades to an
265
271
  // empty table rather than the recovery banner.
266
272
  const rawFilesOwnership = await scanEngineStatusFiles(paths.engine, false);
@@ -280,7 +286,10 @@ async function runStatusCommandBody(projectRoot, options) {
280
286
  }
281
287
  return;
282
288
  }
283
- // Check if engine exists
289
+ // Not routed through assertEngineExists: the --json contract requires a
290
+ // machine-readable payload on stdout BEFORE the throw, so scripted
291
+ // consumers get a parseable refusal instead of a bare non-zero exit. The
292
+ // message is kept identical to the helper's on purpose.
284
293
  if (!(await pathExists(paths.engine))) {
285
294
  if (options.json) {
286
295
  emitJsonError('engine-missing', 'Firefox source not found. Run "fireforge download" first.');
@@ -297,11 +306,10 @@ async function runStatusCommandBody(projectRoot, options) {
297
306
  await assertEngineHasBaselineCommit(paths.engine, options);
298
307
  const files = await scanEngineStatusFiles(paths.engine, true);
299
308
  // `--json` callers expect machine-parseable output on every invocation,
300
- // including the clean-tree case. Before this ordering fix a clean tree
301
- // printed "No modified files" / "Working tree clean" via the human
302
- // branch below and `--json` was silently ignored, so scripts that piped
303
- // the output through a JSON parser broke precisely when there was
304
- // nothing to report. Emit `[]` here and return before the human fallback.
309
+ // including the clean-tree case. Falling through to the human branch below
310
+ // silently ignores `--json` and breaks scripts that pipe the output
311
+ // through a JSON parser precisely when there is nothing to report. Emit
312
+ // `[]` here and return before the human fallback.
305
313
  if (options.json) {
306
314
  const classified = await classifyStatusFiles(files, paths, projectRoot, config.binaryName);
307
315
  await renderJsonMode(classified, files, paths, options, checkPolicy);