@hominis/fireforge 0.34.2 → 0.36.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 (360) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +43 -5
  3. package/dist/bin/fireforge.d.ts +4 -0
  4. package/dist/bin/fireforge.js +25 -6
  5. package/dist/src/cli.js +22 -5
  6. package/dist/src/commands/bootstrap-checks.js +0 -1
  7. package/dist/src/commands/bootstrap.d.ts +2 -1
  8. package/dist/src/commands/bootstrap.js +16 -7
  9. package/dist/src/commands/build.js +17 -3
  10. package/dist/src/commands/config.js +30 -10
  11. package/dist/src/commands/discard.js +24 -5
  12. package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
  13. package/dist/src/commands/doctor-check-core.js +0 -1
  14. package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
  15. package/dist/src/commands/doctor-external-toolchains.js +54 -0
  16. package/dist/src/commands/doctor-furnace-jar.js +0 -1
  17. package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
  18. package/dist/src/commands/doctor-furnace.js +2 -2
  19. package/dist/src/commands/doctor-working-tree.js +0 -1
  20. package/dist/src/commands/doctor.js +4 -1
  21. package/dist/src/commands/download.js +51 -15
  22. package/dist/src/commands/export-all.js +5 -3
  23. package/dist/src/commands/export-flow.js +12 -7
  24. package/dist/src/commands/export-placement-gate.js +0 -1
  25. package/dist/src/commands/export-placement-policy.js +0 -1
  26. package/dist/src/commands/export-shared.d.ts +5 -1
  27. package/dist/src/commands/export-shared.js +18 -6
  28. package/dist/src/commands/export.js +15 -10
  29. package/dist/src/commands/furnace/apply.js +22 -6
  30. package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
  31. package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
  32. package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
  33. package/dist/src/commands/furnace/chrome-doc.js +0 -1
  34. package/dist/src/commands/furnace/create-browser-test.js +0 -1
  35. package/dist/src/commands/furnace/create-dry-run.js +0 -1
  36. package/dist/src/commands/furnace/create-features.js +0 -1
  37. package/dist/src/commands/furnace/create-mochikit.js +0 -1
  38. package/dist/src/commands/furnace/create-readback.js +0 -1
  39. package/dist/src/commands/furnace/create-templates.js +0 -1
  40. package/dist/src/commands/furnace/create-validation.js +0 -1
  41. package/dist/src/commands/furnace/create-xpcshell.js +0 -1
  42. package/dist/src/commands/furnace/create.js +0 -1
  43. package/dist/src/commands/furnace/deploy.js +7 -83
  44. package/dist/src/commands/furnace/diff.js +0 -1
  45. package/dist/src/commands/furnace/index.js +59 -25
  46. package/dist/src/commands/furnace/init.js +0 -1
  47. package/dist/src/commands/furnace/list.js +0 -1
  48. package/dist/src/commands/furnace/override.js +0 -1
  49. package/dist/src/commands/furnace/preview.js +3 -3
  50. package/dist/src/commands/furnace/refresh.js +0 -1
  51. package/dist/src/commands/furnace/remove-state.d.ts +15 -0
  52. package/dist/src/commands/furnace/remove-state.js +47 -1
  53. package/dist/src/commands/furnace/remove.js +3 -9
  54. package/dist/src/commands/furnace/rename-browser-test.js +1 -5
  55. package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
  56. package/dist/src/commands/furnace/rename-helpers.js +30 -1
  57. package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
  58. package/dist/src/commands/furnace/rename.js +15 -36
  59. package/dist/src/commands/furnace/scan.js +0 -1
  60. package/dist/src/commands/furnace/status.js +0 -1
  61. package/dist/src/commands/furnace/sync.js +76 -4
  62. package/dist/src/commands/furnace/validate.js +0 -1
  63. package/dist/src/commands/furnace/validation-output.js +0 -1
  64. package/dist/src/commands/import.js +35 -9
  65. package/dist/src/commands/lint-per-patch.js +19 -7
  66. package/dist/src/commands/lint.js +13 -3
  67. package/dist/src/commands/manifest.js +1 -2
  68. package/dist/src/commands/package.js +2 -1
  69. package/dist/src/commands/patch/compact.js +1 -1
  70. package/dist/src/commands/patch/delete.js +1 -1
  71. package/dist/src/commands/patch/index.js +1 -1
  72. package/dist/src/commands/patch/lint-ignore.js +21 -6
  73. package/dist/src/commands/patch/move-files.js +1 -1
  74. package/dist/src/commands/patch/patch-context.js +0 -1
  75. package/dist/src/commands/patch/rename.js +4 -4
  76. package/dist/src/commands/patch/reorder.js +3 -4
  77. package/dist/src/commands/patch/split-plan.js +2 -1
  78. package/dist/src/commands/patch/split.js +4 -4
  79. package/dist/src/commands/patch/staged-dependency.js +1 -1
  80. package/dist/src/commands/patch/tier.js +18 -5
  81. package/dist/src/commands/re-export-bulk-scan.js +0 -1
  82. package/dist/src/commands/re-export-files.js +1 -1
  83. package/dist/src/commands/re-export-options.js +0 -1
  84. package/dist/src/commands/re-export-scan.js +0 -1
  85. package/dist/src/commands/re-export.js +6 -4
  86. package/dist/src/commands/rebase/abort.js +2 -9
  87. package/dist/src/commands/rebase/confirm.js +0 -1
  88. package/dist/src/commands/rebase/conflict-summary.js +0 -1
  89. package/dist/src/commands/rebase/continue.js +0 -1
  90. package/dist/src/commands/rebase/index.d.ts +1 -1
  91. package/dist/src/commands/rebase/index.js +10 -15
  92. package/dist/src/commands/rebase/patch-loop.js +38 -7
  93. package/dist/src/commands/rebase/summary.js +2 -3
  94. package/dist/src/commands/rebase.js +0 -1
  95. package/dist/src/commands/register.js +1 -1
  96. package/dist/src/commands/reset.js +16 -9
  97. package/dist/src/commands/resolve.js +2 -2
  98. package/dist/src/commands/run.js +18 -6
  99. package/dist/src/commands/setup-support.js +0 -1
  100. package/dist/src/commands/setup.js +1 -2
  101. package/dist/src/commands/source.js +1 -2
  102. package/dist/src/commands/status-output.js +1 -12
  103. package/dist/src/commands/status.js +147 -133
  104. package/dist/src/commands/test-appdir.js +0 -1
  105. package/dist/src/commands/test-diagnose.js +18 -2
  106. package/dist/src/commands/test-modes.d.ts +15 -0
  107. package/dist/src/commands/test-modes.js +61 -0
  108. package/dist/src/commands/test-register.d.ts +4 -0
  109. package/dist/src/commands/test-register.js +64 -0
  110. package/dist/src/commands/test-run.d.ts +23 -7
  111. package/dist/src/commands/test-run.js +15 -14
  112. package/dist/src/commands/test.d.ts +0 -4
  113. package/dist/src/commands/test.js +95 -56
  114. package/dist/src/commands/token-coverage.js +0 -1
  115. package/dist/src/commands/token.js +1 -2
  116. package/dist/src/commands/typecheck.js +1 -1
  117. package/dist/src/commands/verify.js +3 -9
  118. package/dist/src/commands/watch.js +5 -1
  119. package/dist/src/commands/wire.js +1 -1
  120. package/dist/src/core/ast-utils.js +0 -1
  121. package/dist/src/core/brand-validation.js +0 -1
  122. package/dist/src/core/branding.js +0 -1
  123. package/dist/src/core/browser-wire.js +0 -1
  124. package/dist/src/core/build-audit-platform.js +0 -1
  125. package/dist/src/core/build-audit-registration.js +0 -1
  126. package/dist/src/core/build-audit-resolve.js +0 -1
  127. package/dist/src/core/build-audit-transforms.js +0 -1
  128. package/dist/src/core/build-audit.js +0 -1
  129. package/dist/src/core/build-baseline-types.js +0 -1
  130. package/dist/src/core/build-baseline.js +0 -1
  131. package/dist/src/core/build-prepare.js +21 -7
  132. package/dist/src/core/config-mutate.js +0 -1
  133. package/dist/src/core/config-paths.d.ts +2 -2
  134. package/dist/src/core/config-paths.js +6 -1
  135. package/dist/src/core/config-state.js +0 -1
  136. package/dist/src/core/config-validate-patch-policy.js +0 -1
  137. package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
  138. package/dist/src/core/config-validate-test-toolchains.js +105 -0
  139. package/dist/src/core/config-validate.js +3 -1
  140. package/dist/src/core/config.js +0 -1
  141. package/dist/src/core/destructive.js +0 -1
  142. package/dist/src/core/diff-hunks.js +13 -7
  143. package/dist/src/core/engine-changes.js +0 -1
  144. package/dist/src/core/engine-session-lock.d.ts +12 -0
  145. package/dist/src/core/engine-session-lock.js +45 -0
  146. package/dist/src/core/file-lock.d.ts +8 -0
  147. package/dist/src/core/file-lock.js +157 -48
  148. package/dist/src/core/firefox-archive.d.ts +4 -0
  149. package/dist/src/core/firefox-archive.js +6 -1
  150. package/dist/src/core/firefox-cache.js +67 -8
  151. package/dist/src/core/firefox-download.js +0 -1
  152. package/dist/src/core/firefox-extract.d.ts +21 -1
  153. package/dist/src/core/firefox-extract.js +178 -9
  154. package/dist/src/core/firefox-ignorefile.js +0 -1
  155. package/dist/src/core/firefox.d.ts +12 -0
  156. package/dist/src/core/firefox.js +64 -2
  157. package/dist/src/core/furnace-apply-dry-run.js +0 -1
  158. package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
  159. package/dist/src/core/furnace-apply-ftl.js +10 -5
  160. package/dist/src/core/furnace-apply-helpers.js +0 -1
  161. package/dist/src/core/furnace-apply-output.js +2 -2
  162. package/dist/src/core/furnace-apply.d.ts +7 -1
  163. package/dist/src/core/furnace-apply.js +47 -13
  164. package/dist/src/core/furnace-checksum-utils.js +0 -1
  165. package/dist/src/core/furnace-config-array-utils.js +0 -1
  166. package/dist/src/core/furnace-config-custom.js +0 -1
  167. package/dist/src/core/furnace-config-order.js +0 -1
  168. package/dist/src/core/furnace-config-tokens.js +0 -1
  169. package/dist/src/core/furnace-config-validate.js +0 -1
  170. package/dist/src/core/furnace-config.d.ts +11 -0
  171. package/dist/src/core/furnace-config.js +37 -1
  172. package/dist/src/core/furnace-constants.js +0 -1
  173. package/dist/src/core/furnace-css-fragments.js +0 -1
  174. package/dist/src/core/furnace-graph-utils.js +0 -1
  175. package/dist/src/core/furnace-jsconfig.d.ts +2 -3
  176. package/dist/src/core/furnace-jsconfig.js +25 -18
  177. package/dist/src/core/furnace-marker.js +0 -1
  178. package/dist/src/core/furnace-operation.js +0 -1
  179. package/dist/src/core/furnace-refresh.js +0 -1
  180. package/dist/src/core/furnace-registration-ast.d.ts +14 -0
  181. package/dist/src/core/furnace-registration-ast.js +1 -2
  182. package/dist/src/core/furnace-registration-remove.js +0 -1
  183. package/dist/src/core/furnace-registration-validate.js +1 -5
  184. package/dist/src/core/furnace-registration.js +0 -1
  185. package/dist/src/core/furnace-rollback.js +0 -1
  186. package/dist/src/core/furnace-scanner.js +0 -1
  187. package/dist/src/core/furnace-staleness.js +0 -1
  188. package/dist/src/core/furnace-state-persist.d.ts +72 -0
  189. package/dist/src/core/furnace-state-persist.js +118 -0
  190. package/dist/src/core/furnace-step-errors.d.ts +23 -0
  191. package/dist/src/core/furnace-step-errors.js +25 -0
  192. package/dist/src/core/furnace-stories.js +0 -1
  193. package/dist/src/core/furnace-validate-accessibility.js +0 -1
  194. package/dist/src/core/furnace-validate-checks.js +0 -1
  195. package/dist/src/core/furnace-validate-compatibility.js +0 -1
  196. package/dist/src/core/furnace-validate-helpers.js +0 -1
  197. package/dist/src/core/furnace-validate-registration.js +7 -7
  198. package/dist/src/core/furnace-validate-structure.d.ts +8 -0
  199. package/dist/src/core/furnace-validate-structure.js +11 -2
  200. package/dist/src/core/furnace-validate.js +0 -1
  201. package/dist/src/core/furnace-version-drift.d.ts +6 -3
  202. package/dist/src/core/furnace-version-drift.js +6 -4
  203. package/dist/src/core/git-base.js +0 -1
  204. package/dist/src/core/git-diff.js +0 -1
  205. package/dist/src/core/git-file-ops.js +0 -1
  206. package/dist/src/core/git-status.d.ts +13 -0
  207. package/dist/src/core/git-status.js +32 -2
  208. package/dist/src/core/git.d.ts +10 -1
  209. package/dist/src/core/git.js +65 -8
  210. package/dist/src/core/license-headers.d.ts +16 -0
  211. package/dist/src/core/license-headers.js +19 -1
  212. package/dist/src/core/lint-cache.js +0 -1
  213. package/dist/src/core/lint-projection.js +0 -1
  214. package/dist/src/core/mach-build-artifacts.js +0 -1
  215. package/dist/src/core/mach-error-hints.js +27 -1
  216. package/dist/src/core/mach-mozconfig.js +0 -1
  217. package/dist/src/core/mach-python.js +0 -1
  218. package/dist/src/core/mach-resource-shim.js +0 -1
  219. package/dist/src/core/mach.js +0 -1
  220. package/dist/src/core/manifest-helpers.js +0 -1
  221. package/dist/src/core/manifest-register.js +0 -1
  222. package/dist/src/core/manifest-rules.js +0 -1
  223. package/dist/src/core/manifest-tokenizers.js +0 -1
  224. package/dist/src/core/marionette-port.d.ts +8 -0
  225. package/dist/src/core/marionette-port.js +34 -3
  226. package/dist/src/core/marionette-preflight.js +0 -1
  227. package/dist/src/core/markdown-table.js +0 -1
  228. package/dist/src/core/ownership-table.js +2 -9
  229. package/dist/src/core/parser-fallback.js +0 -1
  230. package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
  231. package/dist/src/core/patch-apply-fuzz.js +71 -34
  232. package/dist/src/core/patch-apply.d.ts +30 -0
  233. package/dist/src/core/patch-apply.js +152 -39
  234. package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
  235. package/dist/src/core/patch-artifact-normalize.js +19 -15
  236. package/dist/src/core/patch-export-coverage.js +0 -1
  237. package/dist/src/core/patch-export-metadata.js +0 -1
  238. package/dist/src/core/patch-export-update.js +2 -3
  239. package/dist/src/core/patch-export.d.ts +2 -6
  240. package/dist/src/core/patch-export.js +24 -48
  241. package/dist/src/core/patch-files.js +0 -1
  242. package/dist/src/core/patch-identifier-suggest.js +0 -1
  243. package/dist/src/core/patch-lint-checkjs.js +0 -1
  244. package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
  245. package/dist/src/core/patch-lint-cross.js +0 -1
  246. package/dist/src/core/patch-lint-css.js +0 -1
  247. package/dist/src/core/patch-lint-diff-tag.js +0 -1
  248. package/dist/src/core/patch-lint-diff.d.ts +2 -1
  249. package/dist/src/core/patch-lint-diff.js +17 -32
  250. package/dist/src/core/patch-lint-imports.js +0 -1
  251. package/dist/src/core/patch-lint-jsdoc.js +0 -1
  252. package/dist/src/core/patch-lint-observer.js +0 -1
  253. package/dist/src/core/patch-lint-ownership.js +0 -1
  254. package/dist/src/core/patch-lint-reexports.js +0 -1
  255. package/dist/src/core/patch-lint.d.ts +37 -2
  256. package/dist/src/core/patch-lint.js +48 -24
  257. package/dist/src/core/patch-lock.js +0 -1
  258. package/dist/src/core/patch-manifest-consistency.js +9 -1
  259. package/dist/src/core/patch-manifest-io.d.ts +16 -0
  260. package/dist/src/core/patch-manifest-io.js +37 -12
  261. package/dist/src/core/patch-manifest-query.d.ts +6 -0
  262. package/dist/src/core/patch-manifest-query.js +27 -12
  263. package/dist/src/core/patch-manifest-resolve.js +0 -1
  264. package/dist/src/core/patch-manifest-validate.js +0 -1
  265. package/dist/src/core/patch-manifest.d.ts +1 -1
  266. package/dist/src/core/patch-manifest.js +1 -2
  267. package/dist/src/core/patch-parse.d.ts +59 -0
  268. package/dist/src/core/patch-parse.js +209 -92
  269. package/dist/src/core/patch-policy.js +0 -1
  270. package/dist/src/core/patch-registration-refs.js +16 -26
  271. package/dist/src/core/patch-source-metadata.js +0 -1
  272. package/dist/src/core/patch-transform.d.ts +4 -0
  273. package/dist/src/core/patch-transform.js +30 -38
  274. package/dist/src/core/rebase-session.js +0 -1
  275. package/dist/src/core/register-browser-content.js +0 -1
  276. package/dist/src/core/register-module.js +0 -1
  277. package/dist/src/core/register-result.js +0 -1
  278. package/dist/src/core/register-scaffold.js +0 -1
  279. package/dist/src/core/register-shared-css.js +0 -1
  280. package/dist/src/core/register-test-manifest.js +0 -1
  281. package/dist/src/core/register-xpcshell-test.js +0 -1
  282. package/dist/src/core/shared-ftl.js +0 -1
  283. package/dist/src/core/signal-critical.js +0 -1
  284. package/dist/src/core/smoke-patterns.js +0 -1
  285. package/dist/src/core/state-file.js +0 -1
  286. package/dist/src/core/status-classify.d.ts +16 -0
  287. package/dist/src/core/status-classify.js +24 -16
  288. package/dist/src/core/test-harness-crash.d.ts +64 -7
  289. package/dist/src/core/test-harness-crash.js +196 -15
  290. package/dist/src/core/test-harness-output.js +0 -1
  291. package/dist/src/core/test-path-scope.d.ts +62 -0
  292. package/dist/src/core/test-path-scope.js +148 -0
  293. package/dist/src/core/test-stale-check.js +0 -1
  294. package/dist/src/core/test-stale-symlink.js +0 -1
  295. package/dist/src/core/test-xpcshell-retry.js +0 -1
  296. package/dist/src/core/token-coverage.js +0 -1
  297. package/dist/src/core/token-dark-mode.js +0 -1
  298. package/dist/src/core/token-docs.js +0 -1
  299. package/dist/src/core/token-manager.js +0 -1
  300. package/dist/src/core/token-scaffold.js +0 -1
  301. package/dist/src/core/token-variant.js +0 -1
  302. package/dist/src/core/toolchain-preflight.d.ts +85 -0
  303. package/dist/src/core/toolchain-preflight.js +256 -0
  304. package/dist/src/core/typecheck-shim.d.ts +4 -1
  305. package/dist/src/core/typecheck-shim.js +61 -18
  306. package/dist/src/core/typecheck.js +0 -1
  307. package/dist/src/core/wire-destroy.js +0 -1
  308. package/dist/src/core/wire-dom-fragment.js +0 -1
  309. package/dist/src/core/wire-init.js +0 -1
  310. package/dist/src/core/wire-subscript.js +0 -1
  311. package/dist/src/core/wire-targets.js +0 -1
  312. package/dist/src/core/wire-utils.js +0 -1
  313. package/dist/src/core/xpcshell-appdir.js +1 -4
  314. package/dist/src/errors/base.d.ts +29 -0
  315. package/dist/src/errors/base.js +35 -1
  316. package/dist/src/errors/build.js +0 -1
  317. package/dist/src/errors/codes.d.ts +6 -0
  318. package/dist/src/errors/codes.js +6 -1
  319. package/dist/src/errors/config.js +0 -1
  320. package/dist/src/errors/download.js +2 -1
  321. package/dist/src/errors/furnace.d.ts +11 -1
  322. package/dist/src/errors/furnace.js +6 -5
  323. package/dist/src/errors/git.js +0 -1
  324. package/dist/src/errors/patch.d.ts +17 -0
  325. package/dist/src/errors/patch.js +29 -1
  326. package/dist/src/errors/rebase.js +0 -1
  327. package/dist/src/errors/run.js +1 -1
  328. package/dist/src/index.js +0 -1
  329. package/dist/src/types/cli.js +0 -1
  330. package/dist/src/types/commands/index.js +0 -1
  331. package/dist/src/types/commands/options.d.ts +20 -5
  332. package/dist/src/types/commands/options.js +0 -1
  333. package/dist/src/types/commands/patches.d.ts +8 -0
  334. package/dist/src/types/commands/patches.js +0 -1
  335. package/dist/src/types/commands/project.d.ts +11 -2
  336. package/dist/src/types/commands/project.js +0 -1
  337. package/dist/src/types/config.d.ts +32 -0
  338. package/dist/src/types/config.js +0 -1
  339. package/dist/src/types/furnace.d.ts +10 -0
  340. package/dist/src/types/furnace.js +0 -1
  341. package/dist/src/types/index.js +0 -1
  342. package/dist/src/types/typecheck.js +0 -1
  343. package/dist/src/utils/elapsed.js +0 -1
  344. package/dist/src/utils/errors.js +0 -1
  345. package/dist/src/utils/fs.js +37 -5
  346. package/dist/src/utils/logger.d.ts +7 -0
  347. package/dist/src/utils/logger.js +85 -3
  348. package/dist/src/utils/options.d.ts +15 -0
  349. package/dist/src/utils/options.js +25 -1
  350. package/dist/src/utils/package-root.js +11 -4
  351. package/dist/src/utils/parse.js +0 -1
  352. package/dist/src/utils/paths.js +0 -1
  353. package/dist/src/utils/platform.js +0 -1
  354. package/dist/src/utils/process.d.ts +33 -4
  355. package/dist/src/utils/process.js +183 -28
  356. package/dist/src/utils/regex.d.ts +13 -3
  357. package/dist/src/utils/regex.js +53 -4
  358. package/dist/src/utils/validation.d.ts +0 -12
  359. package/dist/src/utils/validation.js +13 -26
  360. package/package.json +8 -7
@@ -63,35 +63,36 @@ const SHARD_STATUS_LABEL = {
63
63
  'no-tests': 'NO-TESTS',
64
64
  };
65
65
  /**
66
- * Runs each requested path as its own sequential harness invocation and
67
- * prints an aggregate report. Per-shard failures are diagnosed via
68
- * `diagnoseShardFailure` (which receives the throwing diagnosis chain from
69
- * the command layer) but downgraded to warnings so every shard runs; a
70
- * single aggregate error is thrown at the end when any shard did not pass.
66
+ * Runs each requested path argument as its own sequential harness
67
+ * invocation (a directory argument's enumerated files stay together in
68
+ * one invocation see {@link ShardGroup}) and prints an aggregate
69
+ * report. Per-shard failures are diagnosed via `diagnoseShardFailure`
70
+ * (which receives the throwing diagnosis chain from the command layer)
71
+ * but downgraded to warnings so every shard runs; a single aggregate
72
+ * error is thrown at the end when any shard did not pass.
71
73
  */
72
- export async function runShardedTests(ctx, paths, diagnoseShardFailure) {
74
+ export async function runShardedTests(ctx, groups, diagnoseShardFailure) {
73
75
  const shards = [];
74
- for (const [index, path] of paths.entries()) {
75
- info(`— Shard ${index + 1}/${paths.length}: ${path}`);
76
- const outcome = await runTestsWithRetries(ctx, [path]);
77
- shards.push({ path, outcome });
76
+ for (const [index, group] of groups.entries()) {
77
+ info(`— Shard ${index + 1}/${groups.length}: ${group.label}`);
78
+ const outcome = await runTestsWithRetries(ctx, group.paths);
79
+ shards.push({ label: group.label, outcome });
78
80
  if (outcome.verdict.kind === 'harness-crash' && outcome.verdict.signature) {
79
81
  warn(buildHarnessCrashMessage(outcome.verdict.signature, outcome.attempts));
80
82
  }
81
83
  else if (outcome.verdict.kind !== 'tests-ran-ok') {
82
- const diagnosis = diagnoseShardFailure(outcome, path);
84
+ const diagnosis = diagnoseShardFailure(outcome, group.label);
83
85
  if (diagnosis)
84
86
  warn(diagnosis);
85
87
  }
86
88
  }
87
- const lines = shards.map(({ path, outcome }) => `${SHARD_STATUS_LABEL[outcome.verdict.kind].padEnd(8)} ${path}` +
89
+ const lines = shards.map(({ label, outcome }) => `${SHARD_STATUS_LABEL[outcome.verdict.kind].padEnd(8)} ${label}` +
88
90
  (outcome.attempts > 1 ? ` (${outcome.attempts} attempts)` : ''));
89
91
  const failing = shards.filter(({ outcome }) => outcome.verdict.kind !== 'tests-ran-ok');
90
92
  note(`${lines.join('\n')}\n\n${shards.length - failing.length}/${shards.length} shard(s) passed`, 'Sharded Test Summary');
91
93
  if (failing.length > 0) {
92
94
  throw new BuildError(`${failing.length} of ${shards.length} sharded test run(s) did not pass: ` +
93
- `${failing.map(({ path }) => path).join(', ')}. ` +
95
+ `${failing.map(({ label }) => label).join(', ')}. ` +
94
96
  'See the per-shard diagnosis above. Use --no-shard to reproduce the combined single-invocation behaviour.', 'mach test');
95
97
  }
96
98
  }
97
- //# sourceMappingURL=test-run.js.map
@@ -1,5 +1,3 @@
1
- import { Command } from 'commander';
2
- import type { CommandContext } from '../types/cli.js';
3
1
  import type { TestOptions } from '../types/commands/index.js';
4
2
  /**
5
3
  * Runs the test command to execute mach tests.
@@ -8,5 +6,3 @@ import type { TestOptions } from '../types/commands/index.js';
8
6
  * @param options - Test options
9
7
  */
10
8
  export declare function testCommand(projectRoot: string, testPaths: string[], options?: TestOptions): Promise<void>;
11
- /** Registers the test command on the CLI program. */
12
- export declare function registerTest(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
@@ -3,20 +3,22 @@ import { join, resolve } from 'node:path';
3
3
  import { readBuildBaseline } from '../core/build-baseline.js';
4
4
  import { prepareBuildEnvironment } from '../core/build-prepare.js';
5
5
  import { getProjectPaths, loadConfig } from '../core/config.js';
6
+ import { assertEngineGenerationUnchanged, snapshotEngineGeneration, } from '../core/engine-session-lock.js';
6
7
  import { buildArtifactMismatchMessage, hasBuildArtifacts, hasRunnableBundle, runProtectedMachBuild, withBuildLock, } from '../core/mach.js';
7
- import { assertMarionettePortAvailable, extractForwardedMarionettePort, forwardedMachArgsIncludeMarionetteClient, shouldAutoForwardMarionettePortToMach, } from '../core/marionette-port.js';
8
+ import { assertMarionettePortAvailable, ensureMarionettePortAvailable, extractForwardedMarionettePort, forwardedMachArgsIncludeMarionetteClient, shouldAutoForwardMarionettePortToMach, } from '../core/marionette-port.js';
8
9
  import { formatMarionettePreflightLine, reportMarionettePreflight, runMarionettePreflight, } from '../core/marionette-preflight.js';
9
10
  import { buildHarnessCrashMessage } from '../core/test-harness-crash.js';
10
11
  import { createPostRebuildFailureContext } from '../core/test-harness-output.js';
12
+ import { analyzeTestPathScopes, formatScopeNotice, } from '../core/test-path-scope.js';
11
13
  import { checkStaleBuildForTest, formatStaleBuildWarning } from '../core/test-stale-check.js';
12
14
  import { findNearestXpcshellManifest } from '../core/xpcshell-appdir.js';
13
15
  import { GeneralError } from '../errors/base.js';
14
16
  import { AmbiguousBuildArtifactsError, BuildError } from '../errors/build.js';
15
17
  import { pathExists } from '../utils/fs.js';
16
18
  import { info, intro, outro, spinner, success, warn } from '../utils/logger.js';
17
- import { pickDefined } from '../utils/options.js';
18
19
  import { stripEnginePrefix } from '../utils/paths.js';
19
20
  import { diagnoseShardOutcome, finalizeSingleRunOutcome } from './test-diagnose.js';
21
+ import { assertPathlessTestMode, assertTestModeCombinations, canaryTimeoutSeconds, reportCanaryOutcome, resolveCanaryPath, } from './test-modes.js';
20
22
  import { DEFAULT_HARNESS_RETRIES, runShardedTests, runTestsWithRetries, } from './test-run.js';
21
23
  async function assertTestPathsExist(engineDir, testPaths) {
22
24
  const missingPaths = [];
@@ -134,9 +136,12 @@ async function runPreTestBuild(projectRoot, paths, projectConfig, harnessRetries
134
136
  : 'Pre-test build failed', 'mach build faster');
135
137
  });
136
138
  }
137
- function logTestSelection(normalizedPaths) {
138
- if (normalizedPaths.length > 0) {
139
- info(`Running tests: ${normalizedPaths.join(', ')}`);
139
+ function logTestSelection(scopes) {
140
+ if (scopes.length > 0) {
141
+ const labels = scopes.map((scope) => scope.isDirectory && scope.testFileCount > 0
142
+ ? `${scope.requestedPath} (${scope.testFileCount} test file${scope.testFileCount === 1 ? '' : 's'}, passed explicitly)`
143
+ : scope.requestedPath);
144
+ info(`Running tests: ${labels.join(', ')}`);
140
145
  }
141
146
  else {
142
147
  info('Running all tests...');
@@ -254,6 +259,13 @@ function appendMarionetteForwardingArgs(extraArgs, options, forwardedPort) {
254
259
  }
255
260
  }
256
261
  }
262
+ async function ensureTestMarionettePortAvailable(port, binaryName, killStaleBrowser) {
263
+ if (killStaleBrowser) {
264
+ await ensureMarionettePortAvailable(port, { binaryName, killStaleBrowser: true });
265
+ return;
266
+ }
267
+ await assertMarionettePortAvailable(port, { binaryName });
268
+ }
257
269
  /**
258
270
  * Runs the test command to execute mach tests.
259
271
  * @param projectRoot - Root directory of the project
@@ -267,11 +279,14 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
267
279
  if (!(await pathExists(paths.engine))) {
268
280
  throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
269
281
  }
282
+ assertPathlessTestMode(testPaths, options);
270
283
  const buildCheck = await assertTestBuildArtifacts(paths.engine);
271
284
  // Load the project config once so both the build and the port
272
285
  // probe have access to `binaryName` (the port probe uses it to
273
286
  // recognise a fork-branded browser holding the Marionette port).
274
287
  const projectConfig = await loadConfig(projectRoot);
288
+ const canaryPath = resolveCanaryPath(options, projectConfig);
289
+ assertTestModeCombinations(testPaths, options, canaryPath);
275
290
  // `hasBuildArtifacts` only confirms `obj-*/dist/` exists; a partial
276
291
  // build (linker failed, packaging step interrupted, etc.) can satisfy
277
292
  // that check without ever writing the launchable binary the marionette
@@ -290,15 +305,19 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
290
305
  else {
291
306
  // Stale-build preflight — when --build was NOT requested, detect
292
307
  // packageable engine edits since the last successful `fireforge build`
293
- // and warn UP-FRONT. Without this, edits to chrome / packaged resources
294
- // surface only as a cryptic `NS_ERROR_FILE_NOT_FOUND` inside xpcshell
295
- // after mach test has already launched (see motivating case in
296
- // `core/test-stale-check.ts`). The check is warn-only so a fork that
297
- // rebuilt out-of-band (no FireForge-recorded baseline update) is not
298
- // blocked from running tests.
308
+ // and fail UP-FRONT unless the operator explicitly accepts the stale
309
+ // package risk.
299
310
  const stale = await checkStaleBuildForTest(projectRoot, paths.engine);
300
311
  if (stale.stale) {
301
- warn(formatStaleBuildWarning(stale));
312
+ const message = `${formatStaleBuildWarning(stale)}\n\n` +
313
+ 'Run `fireforge test --build` to refresh the packaged runtime first, or pass ' +
314
+ '`--allow-stale-build` if you intentionally rebuilt out-of-band and accept the risk.';
315
+ if (options.allowStaleBuild === true) {
316
+ warn(message);
317
+ }
318
+ else {
319
+ throw new GeneralError(message);
320
+ }
302
321
  }
303
322
  }
304
323
  // Resolve the effective Marionette port. Operator precedence:
@@ -323,7 +342,7 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
323
342
  // generic bind failure. 2026-04-21 eval (Finding #20): a stale
324
343
  // `-marionette` process from `fresh/` poisoned a later test run in
325
344
  // the sibling `mybrowser/` workspace.
326
- await assertMarionettePortAvailable(effectivePort, { binaryName: projectConfig.binaryName });
345
+ await ensureTestMarionettePortAvailable(effectivePort, projectConfig.binaryName, options.killStaleMarionette === true);
327
346
  if (options.doctor) {
328
347
  const doctorOutcome = await runDoctorPreflight({
329
348
  engineDir: paths.engine,
@@ -336,7 +355,8 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
336
355
  if (doctorOutcome === 'stop')
337
356
  return;
338
357
  }
339
- const normalizedPaths = testPaths.map((p) => stripEnginePrefix(p).trim());
358
+ const requestedPaths = canaryPath !== undefined ? [canaryPath] : testPaths;
359
+ const normalizedPaths = requestedPaths.map((p) => stripEnginePrefix(p).trim());
340
360
  await assertTestPathsExist(paths.engine, normalizedPaths);
341
361
  const classification = await classifyTestHarnesses(paths.engine, normalizedPaths);
342
362
  if (classification.xpcshell.length > 0 && classification.nonXpcshell.length > 0) {
@@ -350,6 +370,12 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
350
370
  if (options.headless) {
351
371
  extraArgs.push('--headless');
352
372
  }
373
+ if (options.auto === true) {
374
+ extraArgs.push('--auto');
375
+ }
376
+ if (canaryPath !== undefined) {
377
+ extraArgs.push(`--timeout=${String(canaryTimeoutSeconds(projectConfig))}`);
378
+ }
353
379
  // --mach-arg is a verbatim passthrough for upstream mach/xpcshell/mochitest
354
380
  // flags FireForge does not model directly (see the xpcshell appdir hint
355
381
  // above for the motivating case). Appended AFTER --headless so mach sees
@@ -359,11 +385,33 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
359
385
  extraArgs.push(...forwardedMachArgs);
360
386
  }
361
387
  appendMarionetteForwardingArgs(extraArgs, options, forwardedPort);
388
+ // Directory arguments mean EXACTLY that directory: mozbuild's test
389
+ // resolver matches paths by string prefix, so a bare directory arg
390
+ // silently swept in prefix-named siblings (152.0b7 → 153.0b8 drill:
391
+ // `…/test/hominis` also ran `…/test/hominis-tiles`, 1224 tests instead
392
+ // of ~200, with no indication the scope widened). Each directory
393
+ // argument dispatches as its enumerated explicit test-file list — a
394
+ // file list cannot prefix-match a sibling (0.35.0's trailing-`/`
395
+ // normalization turned out to be cosmetic on Firefox 153's mach; field
396
+ // verification showed the sibling still ran while the echo claimed it
397
+ // was excluded). Any prefix-siblings are echoed so the narrowed scope
398
+ // is visible. Classification above intentionally used the raw
399
+ // argument forms; only the mach dispatch needs the exact-match shape.
400
+ const scopes = await analyzeTestPathScopes(paths.engine, normalizedPaths);
401
+ const dispatchGroups = scopes.map((scope) => ({
402
+ label: scope.requestedPath,
403
+ paths: scope.dispatchPaths,
404
+ }));
405
+ for (const scope of scopes) {
406
+ const notice = formatScopeNotice(scope);
407
+ if (notice)
408
+ info(notice);
409
+ }
362
410
  // xpcshell appdir auto-injection happens per harness invocation inside
363
411
  // `runTestsWithRetries` (src/commands/test-run.ts) so sharded runs probe
364
412
  // the manifest for each file individually. See src/core/xpcshell-appdir.ts
365
413
  // for the full motivation.
366
- logTestSelection(normalizedPaths);
414
+ logTestSelection(scopes);
367
415
  const perfSampleEnv = buildPerfSampleEnv(projectRoot, projectConfig.binaryName, options.perfSamples);
368
416
  const runCtx = {
369
417
  engineDir: paths.engine,
@@ -377,21 +425,46 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
377
425
  const postRebuildContext = options.build
378
426
  ? createPostRebuildFailureContext('fireforge test --build', normalizedPaths)
379
427
  : undefined;
380
- // Multi-file requests shard into sequential single-file harness runs by
381
- // default (field report C3): one shared mochitest profile across files
382
- // bleeds pref/media-query state into later files. --no-shard restores
383
- // the combined invocation.
384
- if (normalizedPaths.length > 1 && options.shard !== false) {
385
- await runShardedTests(runCtx, normalizedPaths, (outcome, path) => diagnoseShardOutcome(outcome, path, projectConfig.binaryName, postRebuildContext));
428
+ // Multi-argument requests shard into sequential harness runs by default
429
+ // (field report C3): one shared mochitest profile across files bleeds
430
+ // pref/media-query state into later files. Sharding is per path
431
+ // ARGUMENT a directory argument keeps its enumerated files together
432
+ // in one invocation, preserving the one-browser-instance semantics of
433
+ // a directory run. --no-shard restores the combined invocation. The
434
+ // default must not be SILENT (drill finding: a two-file cross-file
435
+ // pollution repro "passed" because the headed comparison run was
436
+ // sharded without saying so, briefly misattributing a suite-context
437
+ // bug to an upstream headless regression), so a one-line notice states
438
+ // what sharding does and does not exercise.
439
+ if (canaryPath === undefined && dispatchGroups.length > 1 && options.shard !== false) {
440
+ info(`Sharding: running ${dispatchGroups.length} test path arguments in isolated browser instances ` +
441
+ '(one mach invocation per argument; a directory argument keeps its files in one instance). ' +
442
+ 'Cross-argument state is NOT exercised — pass --no-shard for a combined single-instance run.');
443
+ const generationBefore = await snapshotEngineGeneration(paths.engine);
444
+ try {
445
+ await runShardedTests(runCtx, dispatchGroups, (outcome, label) => diagnoseShardOutcome(outcome, label, projectConfig.binaryName, postRebuildContext));
446
+ }
447
+ finally {
448
+ await assertEngineGenerationUnchanged(paths.engine, generationBefore);
449
+ }
386
450
  return;
387
451
  }
452
+ const combinedDispatchPaths = dispatchGroups.flatMap((group) => group.paths);
388
453
  let outcome;
454
+ const generationBefore = await snapshotEngineGeneration(paths.engine);
389
455
  try {
390
- outcome = await runTestsWithRetries(runCtx, normalizedPaths);
456
+ outcome = await runTestsWithRetries(runCtx, combinedDispatchPaths);
391
457
  }
392
458
  catch (error) {
393
459
  throw new BuildError('Test process failed to start', 'mach test', error instanceof Error ? error : undefined);
394
460
  }
461
+ finally {
462
+ await assertEngineGenerationUnchanged(paths.engine, generationBefore);
463
+ }
464
+ if (canaryPath !== undefined) {
465
+ reportCanaryOutcome(outcome);
466
+ return;
467
+ }
395
468
  finalizeSingleRunOutcome(outcome, normalizedPaths, projectConfig.binaryName, postRebuildContext);
396
469
  }
397
470
  /**
@@ -407,37 +480,3 @@ function buildPerfSampleEnv(projectRoot, binaryName, perfSamples) {
407
480
  info(`Perf sample contract: ${envName}=${artifactPath}`);
408
481
  return { [envName]: artifactPath };
409
482
  }
410
- /** Registers the test command on the CLI program. */
411
- export function registerTest(program, { getProjectRoot, withErrorHandling }) {
412
- program
413
- .command('test [paths...]')
414
- .description('Run tests via mach test')
415
- .option('--headless', 'Run tests in headless mode')
416
- .option('--build', 'Run incremental UI build before testing')
417
- .option('--doctor', 'Run a marionette handshake preflight before tests (exit 1 on FAIL). With no paths, runs the preflight only.')
418
- .option('--mach-arg <arg>', 'Forward this argument verbatim to `mach test` (repeatable). Escape valve for upstream xpcshell/mochitest flags FireForge does not model.', (value, acc) => {
419
- acc.push(value);
420
- return acc;
421
- }, [])
422
- .option('--harness-retries <n>', `Retry budget for recognized harness crashes (resource-monitor tracebacks, pre-test hangs, post-green shutdown re-entry). 0 disables retries. Default: ${String(DEFAULT_HARNESS_RETRIES)}.`, (raw) => {
423
- const n = Number.parseInt(raw, 10);
424
- if (!Number.isFinite(n) || n < 0 || n > 10) {
425
- throw new GeneralError(`--harness-retries must be an integer in 0..10 (got "${raw}")`);
426
- }
427
- return n;
428
- })
429
- .option('--generic-mach-test', 'Force dispatch through generic `mach test` instead of the suite-specific `mach xpcshell-test` / `mach mochitest` a single-suite run auto-selects (the suite-specific commands skip the mozlog resource monitor that crashes `mach test` on some hosts).')
430
- .option('--no-shard', 'Run multiple test paths in one combined mach invocation instead of sequential per-file shards')
431
- .option('--perf-samples <path>', 'Publish a perf-sample artifact path to the harness via <BINARYNAME>_PERF_SAMPLE_JSON (resolved against the project root)')
432
- .option('--marionette-port <port>', 'Override the Marionette control port (default 2828) for the stale-browser probe, the --doctor preflight, and (unless --mach-arg includes --flavor=xpcshell) auto-forwarded mach args: --setpref=marionette.port=<n> (browser listener) and --marionette=127.0.0.1:<n> (mochitest client). Omits the client flag when --mach-arg already sets --marionette. Use when 2828 is busy or CI assigns another port.', (raw) => {
433
- const n = Number.parseInt(raw, 10);
434
- if (!Number.isFinite(n) || n < 1 || n > 65535) {
435
- throw new GeneralError(`--marionette-port must be an integer in 1..65535 (got "${raw}")`);
436
- }
437
- return n;
438
- })
439
- .action(withErrorHandling(async (paths, options) => {
440
- await testCommand(getProjectRoot(), paths, pickDefined(options));
441
- }));
442
- }
443
- //# sourceMappingURL=test.js.map
@@ -174,4 +174,3 @@ async function collectFurnaceCustomCssFiles(projectRoot, engineDir, tokensCssPat
174
174
  }
175
175
  return results;
176
176
  }
177
- //# sourceMappingURL=token-coverage.js.map
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
- import { Option } from 'commander';
2
+ import { Command, Option } from 'commander';
3
3
  import { loadConfig } from '../core/config.js';
4
4
  import { furnaceConfigExists, loadFurnaceConfig } from '../core/furnace-config.js';
5
5
  import { addToken, getTokensCssPath, validateTokenAdd, } from '../core/token-manager.js';
@@ -183,4 +183,3 @@ export function registerToken(program, { getProjectRoot, withErrorHandling }) {
183
183
  await tokenCoverageCommand(getProjectRoot());
184
184
  }));
185
185
  }
186
- //# sourceMappingURL=token.js.map
@@ -15,6 +15,7 @@
15
15
  * Exits non-zero on any error-severity diagnostic. Warnings print
16
16
  * but do not fail. Designed for CI use.
17
17
  */
18
+ import { Command } from 'commander';
18
19
  import { getProjectPaths, loadConfig } from '../core/config.js';
19
20
  import { furnaceConfigExists, loadFurnaceConfig } from '../core/furnace-config.js';
20
21
  import { findJsconfigPathsDrift, syncFurnaceJsconfigPaths } from '../core/furnace-jsconfig.js';
@@ -147,4 +148,3 @@ export function registerTypecheck(program, { getProjectRoot, withErrorHandling }
147
148
  await typecheckCommand(getProjectRoot(), opts);
148
149
  }));
149
150
  }
150
- //# sourceMappingURL=typecheck.js.map
@@ -15,6 +15,7 @@
15
15
  * treat the output as pass/fail.
16
16
  */
17
17
  import { join } from 'node:path';
18
+ import { Command } from 'commander';
18
19
  import { getProjectPaths, loadConfig } from '../core/config.js';
19
20
  import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
20
21
  import { isGitRepository } from '../core/git.js';
@@ -23,6 +24,7 @@ import { buildPatchQueueContext, lintPatchQueue } from '../core/patch-lint.js';
23
24
  import { loadPatchesManifest, validatePatchesManifestConsistency } from '../core/patch-manifest.js';
24
25
  import { evaluatePatchPolicy } from '../core/patch-policy.js';
25
26
  import { collectPatchRegistrationReferences } from '../core/patch-registration-refs.js';
27
+ import { buildPatchClaims } from '../core/status-classify.js';
26
28
  import { classifyFiles } from '../core/status-classify.js';
27
29
  import { GeneralError } from '../errors/base.js';
28
30
  import { pathExists, readText } from '../utils/fs.js';
@@ -88,14 +90,7 @@ async function detectDanglingRegistrations(patchesDir, engineDir, patches) {
88
90
  return issues;
89
91
  }
90
92
  function detectCrossPatchFileClaims(manifestPatches) {
91
- const claims = new Map();
92
- for (const patch of manifestPatches) {
93
- for (const file of patch.filesAffected) {
94
- const existing = claims.get(file) ?? [];
95
- existing.push(patch.filename);
96
- claims.set(file, existing);
97
- }
98
- }
93
+ const claims = buildPatchClaims(manifestPatches);
99
94
  const results = [];
100
95
  for (const [path, filenames] of claims) {
101
96
  if (filenames.length > 1) {
@@ -284,4 +279,3 @@ export function registerVerify(program, { getProjectRoot, withErrorHandling }) {
284
279
  await verifyCommand(getProjectRoot());
285
280
  }));
286
281
  }
287
- //# sourceMappingURL=verify.js.map
@@ -1,5 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { delimiter, dirname } from 'node:path';
3
+ import { Command } from 'commander';
3
4
  import { getProjectPaths, loadConfig } from '../core/config.js';
4
5
  import { warnIfFurnaceStale } from '../core/furnace-staleness.js';
5
6
  import { buildArtifactMismatchMessage, generateMozconfig, hasBuildArtifacts, hasRunnableBundle, watchWithOutput, } from '../core/mach.js';
@@ -195,6 +196,10 @@ export async function watchCommand(projectRoot) {
195
196
  catch (error) {
196
197
  throw new BuildError('Watch process failed to start', 'mach watch', error instanceof Error ? error : undefined);
197
198
  }
199
+ // On Ctrl+C the bin signal handler now waits for the mach child to shut
200
+ // down (waitForActiveChildShutdown) before exiting, so this continuation
201
+ // can run and whitelist the SIGINT exit code — though the bin may still
202
+ // win the race and terminate first; both outcomes are acceptable.
198
203
  if (result.exitCode !== 0 && result.exitCode !== 130) {
199
204
  const combinedOutput = `${result.stdout}\n${result.stderr}`;
200
205
  if (hasConfigureTimeWatchmanFailure(combinedOutput)) {
@@ -214,4 +219,3 @@ export function registerWatch(program, { getProjectRoot, withErrorHandling }) {
214
219
  await watchCommand(getProjectRoot());
215
220
  }));
216
221
  }
217
- //# sourceMappingURL=watch.js.map
@@ -1,5 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { join, relative } from 'node:path';
3
+ import { Command } from 'commander';
3
4
  import { DEFAULT_BROWSER_SUBSCRIPT_DIR, wireSubscript } from '../core/browser-wire.js';
4
5
  import { getProjectPaths, loadConfig } from '../core/config.js';
5
6
  import { furnaceConfigExists as checkFurnaceConfigExists, loadFurnaceConfig, } from '../core/furnace-config.js';
@@ -356,4 +357,3 @@ export function registerWire(program, { getProjectRoot, withErrorHandling }) {
356
357
  await wireCommand(getProjectRoot(), name, pickDefined(options));
357
358
  }));
358
359
  }
359
- //# sourceMappingURL=wire.js.map
@@ -79,4 +79,3 @@ export function detectIndent(content, position) {
79
79
  export function getNodeSource(content, node) {
80
80
  return content.slice(node.start, node.end);
81
81
  }
82
- //# sourceMappingURL=ast-utils.js.map
@@ -12,4 +12,3 @@ export function validateBrandOverride(configuredBrand, requestedBrand) {
12
12
  }
13
13
  throw new InvalidArgumentError(`Brand override "${requestedBrand}" is not supported yet. FireForge currently operates only on the configured brand "${configuredBrand}".`, 'brand');
14
14
  }
15
- //# sourceMappingURL=brand-validation.js.map
@@ -356,4 +356,3 @@ export function isBrandingManagedPath(file, binaryName) {
356
356
  normalized === `${brandingRoot}/` ||
357
357
  normalized.startsWith(`${brandingRoot}/`));
358
358
  }
359
- //# sourceMappingURL=branding.js.map
@@ -125,4 +125,3 @@ export async function wireSubscript(root, name, options = {}) {
125
125
  throw error;
126
126
  }
127
127
  }
128
- //# sourceMappingURL=browser-wire.js.map
@@ -234,4 +234,3 @@ export async function detectPlatformGate(engineDir, sourcePath) {
234
234
  }
235
235
  return { gatedOff: false };
236
236
  }
237
- //# sourceMappingURL=build-audit-platform.js.map
@@ -184,4 +184,3 @@ export async function collectSameBasenameCandidates(source, searchRoots) {
184
184
  }
185
185
  return out;
186
186
  }
187
- //# sourceMappingURL=build-audit-registration.js.map
@@ -215,4 +215,3 @@ export async function resolveBestArtifact(sourcePath, searchRoots) {
215
215
  }
216
216
  return best;
217
217
  }
218
- //# sourceMappingURL=build-audit-resolve.js.map
@@ -91,4 +91,3 @@ export async function resolveArtifactByKnownTransform(source, searchRoots) {
91
91
  }
92
92
  return undefined;
93
93
  }
94
- //# sourceMappingURL=build-audit-transforms.js.map
@@ -475,4 +475,3 @@ export async function auditBuildArtifacts(projectRoot, engineDir, baseline) {
475
475
  info(`Packaged: ${summary.updated} updated, ${summary.stale} stale, ${summary.missing} missing, ${summary.skipped} skipped`);
476
476
  return summary;
477
477
  }
478
- //# sourceMappingURL=build-audit.js.map
@@ -7,4 +7,3 @@
7
7
  * into it would make the dependency graph cyclic.
8
8
  */
9
9
  export {};
10
- //# sourceMappingURL=build-baseline-types.js.map
@@ -140,4 +140,3 @@ async function collectPackageableFingerprints(engineDir) {
140
140
  return undefined;
141
141
  }
142
142
  }
143
- //# sourceMappingURL=build-baseline.js.map
@@ -13,8 +13,10 @@ import { collectChangedEnginePaths } from './engine-changes.js';
13
13
  import { applyAllComponents } from './furnace-apply.js';
14
14
  import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, loadFurnaceState, } from './furnace-config.js';
15
15
  import { runFurnaceMutation } from './furnace-operation.js';
16
+ import { countEntriesWithBlockingStepErrors } from './furnace-step-errors.js';
16
17
  import { cleanStories } from './furnace-stories.js';
17
18
  import { generateMozconfig, runMachCapture } from './mach.js';
19
+ import { explainMachError } from './mach-error-hints.js';
18
20
  /** Path fragments of files whose edits invalidate the recursive-make backend. */
19
21
  const BACKEND_INVALIDATING_SUFFIXES = ['moz.build', 'moz.configure', 'Makefile.in'];
20
22
  /**
@@ -31,6 +33,21 @@ function extractMachConfigureError(result) {
31
33
  return '';
32
34
  return combined.split('\n').slice(-40).join('\n').trim();
33
35
  }
36
+ /**
37
+ * Builds the `BuildError` for a non-zero auto-configure exit: the output
38
+ * tail (so the underlying mozbuild failure survives), any matched
39
+ * mach-error hints (so e.g. a toolchain minimum that moved with a source
40
+ * hop names `fireforge bootstrap` on this path too, exactly like the
41
+ * protected build dispatch), and the stop rationale.
42
+ */
43
+ function buildConfigureFailureError(captured) {
44
+ const detail = extractMachConfigureError(captured);
45
+ const hints = explainMachError(`${captured.stderr}\n${captured.stdout}`);
46
+ return new BuildError(`Backend regeneration failed: mach configure exited with code ${captured.exitCode}.` +
47
+ (detail ? `\n\nmach configure output (tail):\n${detail}` : '') +
48
+ (hints.length > 0 ? `\n\n${hints.map((hint) => `Hint: ${hint}`).join('\n')}` : '') +
49
+ '\n\nBuild stopped because continuing would hide the real configure failure.', 'mach configure');
50
+ }
34
51
  /**
35
52
  * Returns true when the file path matches a pattern that forces
36
53
  * `mach configure` to regenerate the backend. Exported for testing.
@@ -84,11 +101,9 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
84
101
  if (exitCode !== 0) {
85
102
  configureSpinner.error(`mach configure failed with exit code ${exitCode}`);
86
103
  // Surface the underlying mozbuild error (e.g. UnsortedError) instead
87
- // of a bare exit code — the generic message hid the actual cause.
88
- const detail = extractMachConfigureError(captured);
89
- throw new BuildError(`Backend regeneration failed: mach configure exited with code ${exitCode}.` +
90
- (detail ? `\n\nmach configure output (tail):\n${detail}` : '') +
91
- '\n\nBuild stopped because continuing would hide the real configure failure.', 'mach configure');
104
+ // of a bare exit code — the generic message hid the actual cause
105
+ // plus any matched mach-error hints (see the helper).
106
+ throw buildConfigureFailureError(captured);
92
107
  }
93
108
  else {
94
109
  configureSpinner.stop('Backend regenerated successfully (mach configure exit code 0)');
@@ -155,7 +170,7 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
155
170
  // These are distinct from `result.errors`, which captures
156
171
  // components that failed before reaching the applied list at all.
157
172
  // The sum of the two is the total count of failed components.
158
- const appliedWithStepErrorsCount = result.applied.filter((entry) => (entry.stepErrors?.length ?? 0) > 0).length;
173
+ const appliedWithStepErrorsCount = countEntriesWithBlockingStepErrors(result.applied);
159
174
  const totalApplyFailures = result.errors.length + appliedWithStepErrorsCount;
160
175
  if (totalApplyFailures > 0) {
161
176
  furnaceSpinner.error('Failed to apply Furnace components');
@@ -196,4 +211,3 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
196
211
  }
197
212
  return { furnaceApplied, reconfigured };
198
213
  }
199
- //# sourceMappingURL=build-prepare.js.map
@@ -70,4 +70,3 @@ export function mutateConfig(config, key, value, skipValidation = false) {
70
70
  }
71
71
  return raw;
72
72
  }
73
- //# sourceMappingURL=config-mutate.js.map
@@ -9,9 +9,9 @@ export declare const FIREFORGE_DIR = ".fireforge";
9
9
  /** Name of the state file */
10
10
  export declare const STATE_FILENAME = "state.json";
11
11
  /** Supported top-level fireforge.json keys backed by the current schema. */
12
- export declare const SUPPORTED_CONFIG_ROOT_KEYS: readonly ["name", "vendor", "appId", "binaryName", "firefox", "build", "license", "wire", "patchLint", "patchPolicy", "typecheck", "markerComment"];
12
+ export declare const SUPPORTED_CONFIG_ROOT_KEYS: readonly ["name", "vendor", "appId", "binaryName", "firefox", "build", "test", "externalToolchains", "license", "wire", "patchLint", "patchPolicy", "typecheck", "markerComment"];
13
13
  /** Supported config paths that can be read or set without --force. */
14
- export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "firefox.sha256", "build", "build.jobs", "wire", "wire.subscriptDir", "patchLint", "patchLint.checkJs", "patchLint.checkJsStrict", "patchLint.checkJsCompilerOptions", "patchLint.checkJsExtraShim", "patchLint.rawColorAllowlist", "patchLint.jsdocClassMethods", "patchLint.testAssertionFloor", "patchLint.chromeScriptJsDoc", "patchPolicy", "typecheck", "typecheck.projects", "typecheck.extraShim", "markerComment"];
14
+ export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "firefox.sha256", "build", "build.jobs", "test", "test.canaryPath", "test.canaryTimeoutSeconds", "externalToolchains", "wire", "wire.subscriptDir", "patchLint", "patchLint.checkJs", "patchLint.checkJsStrict", "patchLint.checkJsCompilerOptions", "patchLint.checkJsExtraShim", "patchLint.rawColorAllowlist", "patchLint.jsdocClassMethods", "patchLint.testAssertionFloor", "patchLint.chromeScriptJsDoc", "patchPolicy", "typecheck", "typecheck.projects", "typecheck.extraShim", "markerComment"];
15
15
  /**
16
16
  * Gets all project paths based on a root directory.
17
17
  * @param root - Root directory of the project
@@ -25,6 +25,8 @@ export const SUPPORTED_CONFIG_ROOT_KEYS = [
25
25
  'binaryName',
26
26
  'firefox',
27
27
  'build',
28
+ 'test',
29
+ 'externalToolchains',
28
30
  'license',
29
31
  'wire',
30
32
  'patchLint',
@@ -45,6 +47,10 @@ export const SUPPORTED_CONFIG_PATHS = [
45
47
  'firefox.sha256',
46
48
  'build',
47
49
  'build.jobs',
50
+ 'test',
51
+ 'test.canaryPath',
52
+ 'test.canaryTimeoutSeconds',
53
+ 'externalToolchains',
48
54
  'wire',
49
55
  'wire.subscriptDir',
50
56
  'patchLint',
@@ -81,4 +87,3 @@ export function getProjectPaths(root) {
81
87
  componentsDir: join(root, 'components'),
82
88
  };
83
89
  }
84
- //# sourceMappingURL=config-paths.js.map
@@ -149,4 +149,3 @@ export async function updateState(root, updates) {
149
149
  await writeJson(paths.state, validateFireForgeState(nextState));
150
150
  });
151
151
  }
152
- //# sourceMappingURL=config-state.js.map
@@ -172,4 +172,3 @@ export function parsePatchPolicyBlock(rec) {
172
172
  }
173
173
  return out;
174
174
  }
175
- //# sourceMappingURL=config-validate-patch-policy.js.map
@@ -0,0 +1,8 @@
1
+ import type { FireForgeConfig } from '../types/config.js';
2
+ import { parseObject } from '../utils/parse.js';
3
+ type ConfigRecord = ReturnType<typeof parseObject>;
4
+ /** Parses the optional `test` block. */
5
+ export declare function parseTestBlock(rec: ConfigRecord, config: FireForgeConfig): void;
6
+ /** Parses optional project-specific external toolchain doctor probes. */
7
+ export declare function parseExternalToolchainsBlock(rec: ConfigRecord, config: FireForgeConfig): void;
8
+ export {};