@hominis/fireforge 0.45.0 → 0.46.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 (359) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/bin/fireforge.js +6 -0
  3. package/dist/build-info.json +4 -4
  4. package/dist/src/cli-usage-error.d.ts +46 -0
  5. package/dist/src/cli-usage-error.js +81 -0
  6. package/dist/src/cli.d.ts +5 -1
  7. package/dist/src/cli.js +20 -21
  8. package/dist/src/commands/bootstrap.js +1 -2
  9. package/dist/src/commands/build.js +15 -6
  10. package/dist/src/commands/destructive-decision.d.ts +19 -0
  11. package/dist/src/commands/destructive-decision.js +31 -0
  12. package/dist/src/commands/doctor-check-core.d.ts +0 -6
  13. package/dist/src/commands/doctor-check-core.js +0 -8
  14. package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
  15. package/dist/src/commands/doctor-orphaned-harness.js +4 -12
  16. package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
  17. package/dist/src/commands/doctor.d.ts +2 -0
  18. package/dist/src/commands/doctor.js +7 -8
  19. package/dist/src/commands/download.js +35 -24
  20. package/dist/src/commands/export-all.js +21 -4
  21. package/dist/src/commands/export-flow.js +15 -45
  22. package/dist/src/commands/export-placement-conflicts.js +3 -3
  23. package/dist/src/commands/export-placement-gate.js +2 -8
  24. package/dist/src/commands/export-placement-policy.js +3 -5
  25. package/dist/src/commands/export-shared.d.ts +42 -23
  26. package/dist/src/commands/export-shared.js +4 -19
  27. package/dist/src/commands/export.js +10 -1
  28. package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
  29. package/dist/src/commands/furnace/authoring-config.js +19 -0
  30. package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
  31. package/dist/src/commands/furnace/create-browser-test.js +3 -8
  32. package/dist/src/commands/furnace/create-dry-run.js +2 -5
  33. package/dist/src/commands/furnace/create-templates.d.ts +17 -1
  34. package/dist/src/commands/furnace/create-templates.js +2 -1
  35. package/dist/src/commands/furnace/create.js +25 -19
  36. package/dist/src/commands/furnace/deploy.js +11 -2
  37. package/dist/src/commands/furnace/init.js +2 -1
  38. package/dist/src/commands/furnace/override.js +28 -19
  39. package/dist/src/commands/furnace/refresh.js +9 -1
  40. package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
  41. package/dist/src/commands/furnace/remove-mutations.js +0 -1
  42. package/dist/src/commands/furnace/remove.js +2 -6
  43. package/dist/src/commands/furnace/rename-browser-test.js +1 -7
  44. package/dist/src/commands/furnace/rename.js +29 -29
  45. package/dist/src/commands/furnace/status.js +12 -9
  46. package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
  47. package/dist/src/commands/furnace/test-file-name.js +39 -0
  48. package/dist/src/commands/furnace/validation-output.d.ts +21 -9
  49. package/dist/src/commands/furnace/validation-output.js +3 -8
  50. package/dist/src/commands/import.js +2 -9
  51. package/dist/src/commands/lint-per-patch.d.ts +2 -2
  52. package/dist/src/commands/lint-per-patch.js +19 -4
  53. package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
  54. package/dist/src/commands/lint.d.ts +2 -1
  55. package/dist/src/commands/lint.js +1 -1
  56. package/dist/src/commands/manifest.js +1 -1
  57. package/dist/src/commands/package.js +2 -4
  58. package/dist/src/commands/patch/compact.js +14 -15
  59. package/dist/src/commands/patch/delete.js +15 -25
  60. package/dist/src/commands/patch/entry-projection.d.ts +13 -0
  61. package/dist/src/commands/patch/entry-projection.js +24 -0
  62. package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
  63. package/dist/src/commands/patch/lint-ignore.js +19 -29
  64. package/dist/src/commands/patch/move-files-create.js +24 -10
  65. package/dist/src/commands/patch/move-files-into.js +38 -34
  66. package/dist/src/commands/patch/move-files.js +4 -13
  67. package/dist/src/commands/patch/rename.js +27 -41
  68. package/dist/src/commands/patch/reorder.js +25 -38
  69. package/dist/src/commands/patch/split-plan.d.ts +4 -5
  70. package/dist/src/commands/patch/split-plan.js +4 -8
  71. package/dist/src/commands/patch/split.js +45 -43
  72. package/dist/src/commands/patch/staged-dependency.js +16 -22
  73. package/dist/src/commands/patch/tier.js +15 -25
  74. package/dist/src/commands/patch-rename-map.d.ts +19 -0
  75. package/dist/src/commands/patch-rename-map.js +36 -0
  76. package/dist/src/commands/pending-resolution.d.ts +15 -0
  77. package/dist/src/commands/pending-resolution.js +25 -0
  78. package/dist/src/commands/re-export-adjacent.d.ts +24 -2
  79. package/dist/src/commands/re-export-adjacent.js +32 -16
  80. package/dist/src/commands/re-export-bulk-scan.js +6 -10
  81. package/dist/src/commands/re-export-drift.js +4 -4
  82. package/dist/src/commands/re-export-files.d.ts +2 -3
  83. package/dist/src/commands/re-export-files.js +38 -29
  84. package/dist/src/commands/re-export-lint.d.ts +3 -4
  85. package/dist/src/commands/re-export-lint.js +9 -2
  86. package/dist/src/commands/re-export-scan.js +5 -2
  87. package/dist/src/commands/re-export-single.d.ts +22 -3
  88. package/dist/src/commands/re-export-single.js +19 -11
  89. package/dist/src/commands/re-export.js +28 -11
  90. package/dist/src/commands/rebase/abort.js +3 -8
  91. package/dist/src/commands/rebase/continue.js +12 -12
  92. package/dist/src/commands/rebase/index.js +2 -2
  93. package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
  94. package/dist/src/commands/rebase/patch-loop.js +2 -7
  95. package/dist/src/commands/resolve.js +18 -10
  96. package/dist/src/commands/run.d.ts +0 -13
  97. package/dist/src/commands/run.js +12 -25
  98. package/dist/src/commands/setup-support.d.ts +16 -4
  99. package/dist/src/commands/setup-support.js +16 -16
  100. package/dist/src/commands/setup.js +3 -9
  101. package/dist/src/commands/source.js +8 -1
  102. package/dist/src/commands/status-check.js +2 -2
  103. package/dist/src/commands/status-output.js +2 -2
  104. package/dist/src/commands/status.js +3 -4
  105. package/dist/src/commands/test-build-phase.d.ts +2 -2
  106. package/dist/src/commands/test-build-phase.js +31 -7
  107. package/dist/src/commands/test-modes.d.ts +2 -2
  108. package/dist/src/commands/test-modes.js +0 -1
  109. package/dist/src/commands/test-register.js +5 -3
  110. package/dist/src/commands/test-run.js +25 -18
  111. package/dist/src/commands/test-stale-gate.js +4 -4
  112. package/dist/src/commands/test-verdict.d.ts +16 -2
  113. package/dist/src/commands/test-verdict.js +18 -3
  114. package/dist/src/commands/test.js +42 -5
  115. package/dist/src/commands/token-list.d.ts +26 -0
  116. package/dist/src/commands/token-list.js +153 -0
  117. package/dist/src/commands/token.js +17 -1
  118. package/dist/src/commands/tree.js +7 -2
  119. package/dist/src/commands/typecheck.js +9 -9
  120. package/dist/src/commands/wire.js +4 -3
  121. package/dist/src/core/branding.d.ts +0 -7
  122. package/dist/src/core/branding.js +3 -2
  123. package/dist/src/core/browser-wire.js +6 -3
  124. package/dist/src/core/build-audit-platform.d.ts +29 -0
  125. package/dist/src/core/build-audit-platform.js +140 -0
  126. package/dist/src/core/build-audit-registration.js +3 -2
  127. package/dist/src/core/build-audit-skip.d.ts +57 -0
  128. package/dist/src/core/build-audit-skip.js +112 -0
  129. package/dist/src/core/build-audit.d.ts +15 -2
  130. package/dist/src/core/build-audit.js +110 -10
  131. package/dist/src/core/build-baseline.d.ts +45 -28
  132. package/dist/src/core/build-baseline.js +6 -29
  133. package/dist/src/core/build-jar-escalation.js +3 -2
  134. package/dist/src/core/build-overwrite-guard.js +1 -1
  135. package/dist/src/core/build-prepare.d.ts +0 -8
  136. package/dist/src/core/build-prepare.js +7 -12
  137. package/dist/src/core/config-paths.d.ts +2 -6
  138. package/dist/src/core/config-paths.js +4 -2
  139. package/dist/src/core/config-state.d.ts +0 -6
  140. package/dist/src/core/config-state.js +1 -19
  141. package/dist/src/core/config-validate-build-audit.d.ts +8 -0
  142. package/dist/src/core/config-validate-build-audit.js +80 -0
  143. package/dist/src/core/config-validate-file-size.js +1 -1
  144. package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
  145. package/dist/src/core/config-validate-patch-policy.js +3 -3
  146. package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
  147. package/dist/src/core/config-validate-test-toolchains.js +4 -5
  148. package/dist/src/core/config-validate.js +14 -3
  149. package/dist/src/core/config.d.ts +2 -2
  150. package/dist/src/core/config.js +11 -15
  151. package/dist/src/core/coverage-extend.js +4 -4
  152. package/dist/src/core/discard-baseline.js +7 -7
  153. package/dist/src/core/engine-precondition.d.ts +1 -9
  154. package/dist/src/core/engine-precondition.js +3 -8
  155. package/dist/src/core/engine-session-lock.js +13 -17
  156. package/dist/src/core/export-moved-code-shape.js +2 -2
  157. package/dist/src/core/file-lock-owner.d.ts +103 -0
  158. package/dist/src/core/file-lock-owner.js +259 -0
  159. package/dist/src/core/file-lock.d.ts +2 -7
  160. package/dist/src/core/file-lock.js +40 -91
  161. package/dist/src/core/firefox-cache.d.ts +13 -1
  162. package/dist/src/core/firefox-cache.js +59 -34
  163. package/dist/src/core/firefox-download.d.ts +15 -0
  164. package/dist/src/core/firefox-download.js +37 -21
  165. package/dist/src/core/firefox-extract.d.ts +6 -5
  166. package/dist/src/core/firefox-extract.js +36 -18
  167. package/dist/src/core/firefox.d.ts +30 -32
  168. package/dist/src/core/firefox.js +5 -36
  169. package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
  170. package/dist/src/core/furnace-apply-dry-run.js +1 -7
  171. package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
  172. package/dist/src/core/furnace-apply-helpers.js +8 -19
  173. package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
  174. package/dist/src/core/furnace-apply.d.ts +1 -1
  175. package/dist/src/core/furnace-apply.js +30 -10
  176. package/dist/src/core/furnace-config.d.ts +1 -1
  177. package/dist/src/core/furnace-config.js +23 -36
  178. package/dist/src/core/furnace-constants.js +3 -2
  179. package/dist/src/core/furnace-dir-entry.d.ts +20 -0
  180. package/dist/src/core/furnace-dir-entry.js +21 -0
  181. package/dist/src/core/furnace-operation.d.ts +2 -0
  182. package/dist/src/core/furnace-operation.js +3 -1
  183. package/dist/src/core/furnace-refresh.d.ts +18 -6
  184. package/dist/src/core/furnace-refresh.js +3 -6
  185. package/dist/src/core/furnace-registration-ast.d.ts +0 -1
  186. package/dist/src/core/furnace-registration-ast.js +0 -3
  187. package/dist/src/core/furnace-registration.d.ts +2 -1
  188. package/dist/src/core/furnace-registration.js +22 -29
  189. package/dist/src/core/furnace-scanner.js +3 -6
  190. package/dist/src/core/furnace-state-persist.d.ts +2 -2
  191. package/dist/src/core/furnace-validate-registration.js +5 -7
  192. package/dist/src/core/git-base.d.ts +3 -1
  193. package/dist/src/core/git-base.js +25 -3
  194. package/dist/src/core/git-diff-new-file.d.ts +40 -0
  195. package/dist/src/core/git-diff-new-file.js +119 -0
  196. package/dist/src/core/git-diff.d.ts +9 -9
  197. package/dist/src/core/git-diff.js +88 -157
  198. package/dist/src/core/git-file-ops.d.ts +0 -7
  199. package/dist/src/core/git-file-ops.js +4 -28
  200. package/dist/src/core/git-readonly-index.d.ts +0 -2
  201. package/dist/src/core/git-readonly-index.js +0 -4
  202. package/dist/src/core/git-status.d.ts +7 -0
  203. package/dist/src/core/git-status.js +21 -9
  204. package/dist/src/core/git.d.ts +0 -6
  205. package/dist/src/core/git.js +14 -43
  206. package/dist/src/core/harness-orphans.js +8 -7
  207. package/dist/src/core/history-log.d.ts +19 -0
  208. package/dist/src/core/history-log.js +29 -0
  209. package/dist/src/core/mach-error-hints.d.ts +0 -5
  210. package/dist/src/core/mach-error-hints.js +1 -1
  211. package/dist/src/core/mach-known-noise-filter.d.ts +4 -3
  212. package/dist/src/core/mach-known-noise-filter.js +7 -9
  213. package/dist/src/core/mach-mozconfig.js +2 -1
  214. package/dist/src/core/mach-python.d.ts +2 -0
  215. package/dist/src/core/mach-python.js +12 -7
  216. package/dist/src/core/mach-resource-shim.d.ts +13 -1
  217. package/dist/src/core/mach-resource-shim.js +112 -9
  218. package/dist/src/core/mach.d.ts +39 -53
  219. package/dist/src/core/mach.js +13 -81
  220. package/dist/src/core/marionette-port.d.ts +0 -17
  221. package/dist/src/core/marionette-port.js +12 -58
  222. package/dist/src/core/marionette-preflight.js +9 -27
  223. package/dist/src/core/mochitest-server-port.js +3 -3
  224. package/dist/src/core/moz-manifest-rules.js +37 -33
  225. package/dist/src/core/patch-apply-fuzz.js +4 -4
  226. package/dist/src/core/patch-apply.d.ts +4 -5
  227. package/dist/src/core/patch-apply.js +6 -12
  228. package/dist/src/core/patch-export-coverage.d.ts +0 -9
  229. package/dist/src/core/patch-export-coverage.js +1 -30
  230. package/dist/src/core/patch-export-update.d.ts +18 -1
  231. package/dist/src/core/patch-export-update.js +4 -3
  232. package/dist/src/core/patch-export.d.ts +11 -21
  233. package/dist/src/core/patch-export.js +44 -66
  234. package/dist/src/core/patch-files.d.ts +0 -4
  235. package/dist/src/core/patch-files.js +0 -11
  236. package/dist/src/core/patch-lint-binary.d.ts +6 -12
  237. package/dist/src/core/patch-lint-cache.d.ts +17 -1
  238. package/dist/src/core/patch-lint-cache.js +3 -6
  239. package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
  240. package/dist/src/core/patch-lint-checkjs.js +37 -24
  241. package/dist/src/core/patch-lint-creators.d.ts +2 -6
  242. package/dist/src/core/patch-lint-cross.d.ts +16 -8
  243. package/dist/src/core/patch-lint-cross.js +49 -92
  244. package/dist/src/core/patch-lint-css.js +10 -6
  245. package/dist/src/core/patch-lint-file-size.js +3 -3
  246. package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
  247. package/dist/src/core/patch-lint-forward-registration.js +210 -0
  248. package/dist/src/core/patch-lint-jsdoc.js +36 -36
  249. package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
  250. package/dist/src/core/patch-lint-prettier.js +1 -1
  251. package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
  252. package/dist/src/core/patch-lint-queue-types.js +12 -0
  253. package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
  254. package/dist/src/core/patch-lint-staged-registration.js +90 -0
  255. package/dist/src/core/patch-lint.d.ts +14 -9
  256. package/dist/src/core/patch-lint.js +4 -3
  257. package/dist/src/core/patch-lock.js +3 -3
  258. package/dist/src/core/patch-manifest-io.js +8 -7
  259. package/dist/src/core/patch-parse.d.ts +19 -8
  260. package/dist/src/core/patch-parse.js +25 -0
  261. package/dist/src/core/patch-policy.js +15 -27
  262. package/dist/src/core/patch-source-metadata.d.ts +1 -3
  263. package/dist/src/core/patch-source-metadata.js +0 -4
  264. package/dist/src/core/patch-transform.d.ts +0 -8
  265. package/dist/src/core/patch-transform.js +0 -10
  266. package/dist/src/core/process-cpu-time.js +1 -1
  267. package/dist/src/core/rebase-session.d.ts +2 -8
  268. package/dist/src/core/rebase-session.js +5 -17
  269. package/dist/src/core/register-browser-content.js +3 -2
  270. package/dist/src/core/register-browser-test.js +2 -1
  271. package/dist/src/core/register-module.js +2 -1
  272. package/dist/src/core/register-scaffold.js +5 -4
  273. package/dist/src/core/register-shared-css.js +2 -1
  274. package/dist/src/core/run-log-redact.d.ts +38 -0
  275. package/dist/src/core/run-log-redact.js +69 -0
  276. package/dist/src/core/run-log.d.ts +13 -2
  277. package/dist/src/core/run-log.js +67 -18
  278. package/dist/src/core/signal-critical.js +2 -4
  279. package/dist/src/core/smoke-patterns.d.ts +0 -1
  280. package/dist/src/core/smoke-patterns.js +6 -11
  281. package/dist/src/core/state-file.d.ts +12 -1
  282. package/dist/src/core/state-file.js +14 -1
  283. package/dist/src/core/status-classify.js +2 -1
  284. package/dist/src/core/test-changed-prefs.js +26 -11
  285. package/dist/src/core/test-harness-crash.js +2 -2
  286. package/dist/src/core/test-stale-check.d.ts +0 -2
  287. package/dist/src/core/test-stale-check.js +2 -2
  288. package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
  289. package/dist/src/core/test-xpcshell-retry.js +14 -11
  290. package/dist/src/core/token-category.d.ts +33 -0
  291. package/dist/src/core/token-category.js +56 -48
  292. package/dist/src/core/token-dark-mode.js +2 -14
  293. package/dist/src/core/token-inventory.d.ts +56 -0
  294. package/dist/src/core/token-inventory.js +109 -0
  295. package/dist/src/core/token-manager.d.ts +1 -1
  296. package/dist/src/core/token-manager.js +1 -1
  297. package/dist/src/core/token-variant.js +21 -22
  298. package/dist/src/core/toolchain-preflight.js +1 -1
  299. package/dist/src/core/tree-cow.d.ts +0 -2
  300. package/dist/src/core/tree-cow.js +2 -2
  301. package/dist/src/core/tree-store.js +4 -5
  302. package/dist/src/core/typecheck.js +2 -2
  303. package/dist/src/core/wire-dom-fragment.js +6 -17
  304. package/dist/src/core/wire-utils.js +5 -17
  305. package/dist/src/core/xpcshell-appdir.js +2 -4
  306. package/dist/src/errors/base.d.ts +53 -1
  307. package/dist/src/errors/base.js +79 -11
  308. package/dist/src/errors/build.js +20 -28
  309. package/dist/src/errors/config.d.ts +0 -14
  310. package/dist/src/errors/config.js +13 -34
  311. package/dist/src/errors/download.d.ts +13 -0
  312. package/dist/src/errors/download.js +53 -24
  313. package/dist/src/errors/furnace.js +8 -9
  314. package/dist/src/errors/git.d.ts +8 -8
  315. package/dist/src/errors/git.js +20 -30
  316. package/dist/src/errors/patch.js +8 -10
  317. package/dist/src/errors/rebase.js +7 -7
  318. package/dist/src/types/commands/options.d.ts +77 -123
  319. package/dist/src/types/config.d.ts +34 -2
  320. package/dist/src/types/index.d.ts +0 -1
  321. package/dist/src/utils/assert.d.ts +3 -15
  322. package/dist/src/utils/assert.js +1 -18
  323. package/dist/src/utils/build-info.d.ts +1 -1
  324. package/dist/src/utils/child-env.d.ts +2 -1
  325. package/dist/src/utils/elapsed.js +10 -1
  326. package/dist/src/utils/fs.js +8 -13
  327. package/dist/src/utils/hash.d.ts +12 -0
  328. package/dist/src/utils/hash.js +16 -0
  329. package/dist/src/utils/line-dispatch.d.ts +15 -0
  330. package/dist/src/utils/line-dispatch.js +48 -0
  331. package/dist/src/utils/options.d.ts +1 -7
  332. package/dist/src/utils/options.js +6 -7
  333. package/dist/src/utils/package-root.js +3 -12
  334. package/dist/src/utils/platform.d.ts +0 -4
  335. package/dist/src/utils/process-group.d.ts +2 -1
  336. package/dist/src/utils/process-group.js +20 -19
  337. package/dist/src/utils/process.d.ts +40 -26
  338. package/dist/src/utils/process.js +115 -166
  339. package/dist/src/utils/ps-duration.js +3 -3
  340. package/dist/src/utils/sleep.d.ts +26 -0
  341. package/dist/src/utils/sleep.js +31 -0
  342. package/dist/src/utils/validation.d.ts +2 -38
  343. package/dist/src/utils/validation.js +7 -16
  344. package/docs/configuration.md +89 -1
  345. package/docs/exit-codes.md +31 -19
  346. package/docs/lifecycle-invariants.md +18 -12
  347. package/docs/machine-output.md +31 -8
  348. package/docs/patch-workflow.md +2 -1
  349. package/docs/run-logs.md +44 -4
  350. package/docs/testing.md +8 -1
  351. package/package.json +8 -7
  352. package/dist/src/commands/rebase.d.ts +0 -4
  353. package/dist/src/commands/rebase.js +0 -5
  354. package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
  355. package/dist/src/core/patch-artifact-normalize.js +0 -24
  356. package/dist/src/core/patch-lint-reexports.d.ts +0 -7
  357. package/dist/src/core/patch-lint-reexports.js +0 -8
  358. package/dist/src/core/wire-targets.d.ts +0 -7
  359. package/dist/src/core/wire-targets.js +0 -8
@@ -2,7 +2,7 @@ import { type SmokeLineCallback, type SmokeRunResult } from '../utils/process.js
2
2
  import { type HarnessCrashSignature } from './test-harness-crash.js';
3
3
  export { attemptMozinfoRewrite, buildArtifactMismatchMessage, hasBuildArtifacts, hasRunnableBundle, } from './mach-build-artifacts.js';
4
4
  export { generateMozconfig } from './mach-mozconfig.js';
5
- export { ensurePython, resetResolvedPython } from './mach-python.js';
5
+ export { ensurePython } from './mach-python.js';
6
6
  /**
7
7
  * Ensures mach is available in the engine directory.
8
8
  * @param engineDir - Path to the engine directory
@@ -66,12 +66,6 @@ export declare function runMachCapture(args: string[], engineDir: string, option
66
66
  * capturing stdout/stderr for post-run diagnostics.
67
67
  */
68
68
  export declare function runMachInheritCapture(args: string[], engineDir: string, options?: Omit<MachOptions, 'inherit'>): Promise<MachCommandResult>;
69
- /**
70
- * Runs mach bootstrap to install build dependencies.
71
- * @param engineDir - Path to the engine directory
72
- * @returns Exit code
73
- */
74
- export declare function bootstrap(engineDir: string): Promise<number>;
75
69
  /**
76
70
  * Runs mach bootstrap while preserving stdin and capturing the emitted output.
77
71
  * @param engineDir - Path to the engine directory
@@ -197,29 +191,15 @@ export interface RunMachSmokeOptions {
197
191
  * the runner to chrome-specific pattern logic.
198
192
  */
199
193
  export declare function runMachSmoke(args: string[], engineDir: string, options: RunMachSmokeOptions): Promise<SmokeRunResult>;
200
- /**
201
- * Creates a distribution package.
202
- * @param engineDir - Path to the engine directory
203
- * @returns Exit code
204
- */
205
- export declare function machPackage(engineDir: string): Promise<number>;
206
194
  /**
207
195
  * Creates a distribution package while streaming output to the terminal
208
- * and capturing the stderr tail for post-run diagnostics. Callers that
209
- * want to consult {@link explainMachError} on failure should use this
210
- * variant; the inherit-only `machPackage` above remains for callers that
211
- * just need an exit code.
196
+ * and capturing the stderr tail for post-run diagnostics, so callers can
197
+ * consult {@link explainMachError} on failure.
212
198
  *
213
199
  * @param engineDir - Path to the engine directory
214
200
  * @returns Captured mach result (stdout tail, stderr tail, exit code)
215
201
  */
216
202
  export declare function machPackageCapture(engineDir: string): Promise<MachCommandResult>;
217
- /**
218
- * Runs mach watch for auto-rebuilding.
219
- * @param engineDir - Path to the engine directory
220
- * @returns Exit code
221
- */
222
- export declare function watch(engineDir: string): Promise<number>;
223
203
  /**
224
204
  * Runs mach watch while preserving stdin and capturing emitted output.
225
205
  *
@@ -238,38 +218,44 @@ export declare function watchWithOutput(engineDir: string, options?: {
238
218
  env?: Record<string, string>;
239
219
  }): Promise<MachCommandResult>;
240
220
  /**
241
- * Runs mach test with the given test paths.
242
- * @param engineDir - Path to the engine directory
243
- * @param testPaths - Test file or directory paths (relative to engine)
244
- * @param args - Additional arguments to pass to mach test
245
- * @returns Exit code
246
- */
247
- export declare function test(engineDir: string, testPaths?: string[], args?: string[]): Promise<number>;
248
- /**
249
- * Runs mach test while capturing streamed output for better diagnostics.
221
+ * Which mach test command a capturing test dispatch runs.
250
222
  *
251
- * @param engineDir - Absolute path to the engine checkout
252
- * @param testPaths - Test paths to pass to `mach test`; empty runs the default set
253
- * @param args - Extra `mach test` arguments appended after the paths
254
- * @param env - Optional extra environment variables for the mach process
255
- * (merged over `process.env` by the exec layer). Used by
256
- * `fireforge test --perf-samples` to publish the artifact-path contract.
223
+ * - `test`: the generic `mach test` (mixed/all-tests runs, or the
224
+ * `--generic-mach-test` opt-out).
225
+ * - `xpcshell-test` / `mochitest`: the suite-specific commands. Unlike the
226
+ * generic `mach test`, these degrade a broken mozlog resource monitor to a
227
+ * warning instead of crashing at startup, so `fireforge test` dispatches
228
+ * single-suite runs to them to stay resilient to the host psutil failure.
257
229
  */
258
- export declare function testWithOutput(engineDir: string, testPaths?: string[], args?: string[], env?: Record<string, string>, fullOutput?: boolean): Promise<MachCommandResult>;
230
+ export type MachTestSuiteKind = 'test' | 'xpcshell-test' | 'mochitest';
231
+ /** Options for {@link runMachTestSuite}. */
232
+ export interface MachTestSuiteOptions {
233
+ /** Absolute path to the engine checkout. */
234
+ engineDir: string;
235
+ /** Test paths to pass to the mach command; empty runs the default set. */
236
+ testPaths?: string[];
237
+ /** Extra mach arguments appended after the paths. */
238
+ args?: string[];
239
+ /**
240
+ * Extra environment variables for the mach process (merged over
241
+ * `process.env` by the exec layer). Used by `fireforge test
242
+ * --perf-samples` to publish the artifact-path contract.
243
+ */
244
+ env?: Record<string, string> | undefined;
245
+ /**
246
+ * Pass full mozbuild verbosity through by unsetting the coding-agent
247
+ * markers that quiet it. See {@link CODING_AGENT_ENV_MARKERS}.
248
+ */
249
+ fullOutput?: boolean | undefined;
250
+ }
259
251
  /**
260
- * Runs `mach xpcshell-test` (the suite-specific xpcshell command) while
261
- * capturing output. Unlike the generic `mach test`, the suite-specific
262
- * commands degrade a broken mozlog resource monitor to a warning instead of
263
- * crashing at startup, so `fireforge test` dispatches single-suite runs here
264
- * to stay resilient to the host psutil failure.
252
+ * Runs one mach test command (`mach test` / `mach xpcshell-test` / `mach
253
+ * mochitest`) while capturing streamed output for better diagnostics. The
254
+ * three commands share every other detail of the dispatch — resource guard,
255
+ * verbosity env, teardown-noise annotation — so they share one entry point
256
+ * keyed by `kind` rather than three copies that drift.
265
257
  *
266
- * Signature mirrors {@link testWithOutput} so the two are interchangeable in
267
- * the dispatch path.
268
- */
269
- export declare function xpcshellTestWithOutput(engineDir: string, testPaths?: string[], args?: string[], env?: Record<string, string>, fullOutput?: boolean): Promise<MachCommandResult>;
270
- /**
271
- * Runs `mach mochitest` (covers browser-chrome / mochitest flavors) while
272
- * capturing output. The suite-specific counterpart to {@link testWithOutput}
273
- * for non-xpcshell single-suite runs — see {@link xpcshellTestWithOutput}.
258
+ * @param kind - Which mach command to run
259
+ * @param options - Engine checkout, test paths, extra args, env, verbosity
274
260
  */
275
- export declare function mochitestWithOutput(engineDir: string, testPaths?: string[], args?: string[], env?: Record<string, string>, fullOutput?: boolean): Promise<MachCommandResult>;
261
+ export declare function runMachTestSuite(kind: MachTestSuiteKind, options: MachTestSuiteOptions): Promise<MachCommandResult>;
@@ -20,7 +20,7 @@ import { detectHarnessCrashSignature } from './test-harness-crash.js';
20
20
  // that mocks `mach.js`.
21
21
  export { attemptMozinfoRewrite, buildArtifactMismatchMessage, hasBuildArtifacts, hasRunnableBundle, } from './mach-build-artifacts.js';
22
22
  export { generateMozconfig } from './mach-mozconfig.js';
23
- export { ensurePython, resetResolvedPython } from './mach-python.js';
23
+ export { ensurePython } from './mach-python.js';
24
24
  /**
25
25
  * Ensures mach is available in the engine directory.
26
26
  * @param engineDir - Path to the engine directory
@@ -149,14 +149,6 @@ export async function runMachInheritCapture(args, engineDir, options = {}) {
149
149
  processGroup: true,
150
150
  });
151
151
  }
152
- /**
153
- * Runs mach bootstrap to install build dependencies.
154
- * @param engineDir - Path to the engine directory
155
- * @returns Exit code
156
- */
157
- export async function bootstrap(engineDir) {
158
- return runMach(['bootstrap', '--application-choice', 'browser'], engineDir, { inherit: true });
159
- }
160
152
  /**
161
153
  * Runs mach bootstrap while preserving stdin and capturing the emitted output.
162
154
  * @param engineDir - Path to the engine directory
@@ -351,20 +343,10 @@ export async function runMachSmoke(args, engineDir, options) {
351
343
  ...(options.mirror ? { mirror: options.mirror } : {}),
352
344
  });
353
345
  }
354
- /**
355
- * Creates a distribution package.
356
- * @param engineDir - Path to the engine directory
357
- * @returns Exit code
358
- */
359
- export async function machPackage(engineDir) {
360
- return runMach(['package'], engineDir, { inherit: true });
361
- }
362
346
  /**
363
347
  * Creates a distribution package while streaming output to the terminal
364
- * and capturing the stderr tail for post-run diagnostics. Callers that
365
- * want to consult {@link explainMachError} on failure should use this
366
- * variant; the inherit-only `machPackage` above remains for callers that
367
- * just need an exit code.
348
+ * and capturing the stderr tail for post-run diagnostics, so callers can
349
+ * consult {@link explainMachError} on failure.
368
350
  *
369
351
  * @param engineDir - Path to the engine directory
370
352
  * @returns Captured mach result (stdout tail, stderr tail, exit code)
@@ -372,14 +354,6 @@ export async function machPackage(engineDir) {
372
354
  export async function machPackageCapture(engineDir) {
373
355
  return runMachCapture(['package'], engineDir);
374
356
  }
375
- /**
376
- * Runs mach watch for auto-rebuilding.
377
- * @param engineDir - Path to the engine directory
378
- * @returns Exit code
379
- */
380
- export async function watch(engineDir) {
381
- return runMach(['watch'], engineDir, { inherit: true });
382
- }
383
357
  /**
384
358
  * Runs mach watch while preserving stdin and capturing emitted output.
385
359
  *
@@ -397,16 +371,6 @@ export async function watch(engineDir) {
397
371
  export async function watchWithOutput(engineDir, options = {}) {
398
372
  return runMachInheritCapture(['watch'], engineDir, options.env ? { env: options.env } : {});
399
373
  }
400
- /**
401
- * Runs mach test with the given test paths.
402
- * @param engineDir - Path to the engine directory
403
- * @param testPaths - Test file or directory paths (relative to engine)
404
- * @param args - Additional arguments to pass to mach test
405
- * @returns Exit code
406
- */
407
- export async function test(engineDir, testPaths = [], args = []) {
408
- return runMach(['test', ...testPaths, ...args], engineDir, { inherit: true });
409
- }
410
374
  /**
411
375
  * Environment markers mozbuild reads to decide it is running under a coding
412
376
  * agent (`mozbuild.util.is_running_under_coding_agent`), which quiets
@@ -433,52 +397,20 @@ function testVerbosityEnvUnset(fullOutput) {
433
397
  return fullOutput === true ? CODING_AGENT_ENV_MARKERS : undefined;
434
398
  }
435
399
  /**
436
- * Runs mach test while capturing streamed output for better diagnostics.
400
+ * Runs one mach test command (`mach test` / `mach xpcshell-test` / `mach
401
+ * mochitest`) while capturing streamed output for better diagnostics. The
402
+ * three commands share every other detail of the dispatch — resource guard,
403
+ * verbosity env, teardown-noise annotation — so they share one entry point
404
+ * keyed by `kind` rather than three copies that drift.
437
405
  *
438
- * @param engineDir - Absolute path to the engine checkout
439
- * @param testPaths - Test paths to pass to `mach test`; empty runs the default set
440
- * @param args - Extra `mach test` arguments appended after the paths
441
- * @param env - Optional extra environment variables for the mach process
442
- * (merged over `process.env` by the exec layer). Used by
443
- * `fireforge test --perf-samples` to publish the artifact-path contract.
444
- */
445
- export async function testWithOutput(engineDir, testPaths = [], args = [], env, fullOutput) {
446
- const guard = await installMachResourceGuard(engineDir);
447
- const envUnset = testVerbosityEnvUnset(fullOutput);
448
- return runMachCapture(['test', ...testPaths, ...args], engineDir, {
449
- env: { ...guard.env, ...env },
450
- ...(envUnset ? { envUnset } : {}),
451
- annotateKnownTeardownNoise: true,
452
- });
453
- }
454
- /**
455
- * Runs `mach xpcshell-test` (the suite-specific xpcshell command) while
456
- * capturing output. Unlike the generic `mach test`, the suite-specific
457
- * commands degrade a broken mozlog resource monitor to a warning instead of
458
- * crashing at startup, so `fireforge test` dispatches single-suite runs here
459
- * to stay resilient to the host psutil failure.
460
- *
461
- * Signature mirrors {@link testWithOutput} so the two are interchangeable in
462
- * the dispatch path.
463
- */
464
- export async function xpcshellTestWithOutput(engineDir, testPaths = [], args = [], env, fullOutput) {
465
- const guard = await installMachResourceGuard(engineDir);
466
- const envUnset = testVerbosityEnvUnset(fullOutput);
467
- return runMachCapture(['xpcshell-test', ...testPaths, ...args], engineDir, {
468
- env: { ...guard.env, ...env },
469
- ...(envUnset ? { envUnset } : {}),
470
- annotateKnownTeardownNoise: true,
471
- });
472
- }
473
- /**
474
- * Runs `mach mochitest` (covers browser-chrome / mochitest flavors) while
475
- * capturing output. The suite-specific counterpart to {@link testWithOutput}
476
- * for non-xpcshell single-suite runs — see {@link xpcshellTestWithOutput}.
406
+ * @param kind - Which mach command to run
407
+ * @param options - Engine checkout, test paths, extra args, env, verbosity
477
408
  */
478
- export async function mochitestWithOutput(engineDir, testPaths = [], args = [], env, fullOutput) {
409
+ export async function runMachTestSuite(kind, options) {
410
+ const { engineDir, testPaths = [], args = [], env, fullOutput } = options;
479
411
  const guard = await installMachResourceGuard(engineDir);
480
412
  const envUnset = testVerbosityEnvUnset(fullOutput);
481
- return runMachCapture(['mochitest', ...testPaths, ...args], engineDir, {
413
+ return runMachCapture([kind, ...testPaths, ...args], engineDir, {
482
414
  env: { ...guard.env, ...env },
483
415
  ...(envUnset ? { envUnset } : {}),
484
416
  annotateKnownTeardownNoise: true,
@@ -147,20 +147,3 @@ export declare function hasExplicitXpcshellFlavor(machArgs: string[]): boolean;
147
147
  * Marionette-driven browser).
148
148
  */
149
149
  export declare function shouldAutoForwardMarionettePortToMach(machArgs: string[]): boolean;
150
- /**
151
- * Heuristic: do the test paths or forwarded mach args indicate a flavour
152
- * that actually launches a Marionette-driven browser? Browser-chrome and
153
- * mochitest do; xpcshell does not. A no-paths invocation (the default "run
154
- * all tests" shape) is treated as marionette-relevant since it includes
155
- * browser-chrome.
156
- *
157
- * Note: `fireforge test` auto-forward of `--marionette-port` to mach uses
158
- * {@link shouldAutoForwardMarionettePortToMach} (mach-arg flavor gate) rather
159
- * than this function alone, so toolkit paths without `/mochitest/` still get
160
- * the listener pref and harness `--marionette=127.0.0.1:<n>` when appropriate.
161
- *
162
- * @param testPaths - Engine-relative paths after `stripEnginePrefix`.
163
- * @param machArgs - Forwarded mach args (post-`--mach-arg`).
164
- * @returns `true` when the run is likely to bind a Marionette listener.
165
- */
166
- export declare function isMarionetteFlavor(testPaths: string[], machArgs: string[]): boolean;
@@ -24,7 +24,7 @@
24
24
  * installed the probe returns `{ inUse: false }` rather than failing the
25
25
  * test run — it is a best-effort friendliness check, not a prerequisite.
26
26
  */
27
- import { GeneralError } from '../errors/base.js';
27
+ import { PreflightRefusalError } from '../errors/base.js';
28
28
  import { toError } from '../utils/errors.js';
29
29
  import { getPlatform } from '../utils/platform.js';
30
30
  import { exec } from '../utils/process.js';
@@ -133,10 +133,10 @@ export async function ensureLaunchableBrowserNotRunning(launchableBinary, option
133
133
  return;
134
134
  }
135
135
  catch (error) {
136
- throw new GeneralError(`A browser from this objdir is still running (PID ${holder.pid}), but FireForge could not terminate it: ${toError(error).message}`);
136
+ throw new PreflightRefusalError(`A browser from this objdir is still running (PID ${holder.pid}), but FireForge could not terminate it: ${toError(error).message}`, 'stale-browser-kill-failed');
137
137
  }
138
138
  }
139
- throw new GeneralError(describeRunningBundleRefusal(holder));
139
+ throw new PreflightRefusalError(describeRunningBundleRefusal(holder), 'stale-browser');
140
140
  }
141
141
  /**
142
142
  * True when the process was launched BY a test harness — its command line
@@ -349,18 +349,18 @@ export async function assertMarionettePortAvailable(port = DEFAULT_MARIONETTE_PO
349
349
  const killHint = process.platform === 'win32'
350
350
  ? `Stop-Process -Id ${holder.pid} -Force`
351
351
  : `kill ${holder.pid} # or "kill -9 ${holder.pid}" if it doesn't exit`;
352
- throw new GeneralError(`Marionette port ${port} is already in use by ${holder.command} (PID ${holder.pid}). ` +
352
+ throw new PreflightRefusalError(`Marionette port ${port} is already in use by ${holder.command} (PID ${holder.pid}). ` +
353
353
  `This is usually a browser left running by a previously interrupted "fireforge test" run. ` +
354
354
  `Kill it with "${killHint}", then retry. ` +
355
355
  `(If you expected ${holder.command} to be running on ${port}, stop it manually or pass ` +
356
- `"--marionette-port <port>" to launch mach test on a different port.)`);
356
+ `"--marionette-port <port>" to launch mach test on a different port.)`, 'marionette-port-busy');
357
357
  }
358
358
  // Non-browser holder: mach test will still fail to bind, but the
359
359
  // cause is not a stale FireForge-launched browser. Flag it
360
360
  // explicitly so the operator can decide what to do instead of
361
361
  // getting mach's bind error with no FireForge context.
362
- throw new GeneralError(`Marionette port ${port} is already in use by ${holder.command} (PID ${holder.pid}). ` +
363
- `This is not a FireForge-launched browser; stop the holder process or free the port before rerunning.`);
362
+ throw new PreflightRefusalError(`Marionette port ${port} is already in use by ${holder.command} (PID ${holder.pid}). ` +
363
+ `This is not a FireForge-launched browser; stop the holder process or free the port before rerunning.`, 'marionette-port-busy');
364
364
  }
365
365
  /**
366
366
  * Ensures the Marionette port is usable, optionally terminating a stale
@@ -390,8 +390,8 @@ export async function ensureMarionettePortAvailable(port = DEFAULT_MARIONETTE_PO
390
390
  }
391
391
  }
392
392
  catch (error) {
393
- throw new GeneralError(`Marionette port ${port} is held by stale browser ${holder.command} (PID ${holder.pid}), ` +
394
- `but FireForge could not terminate it: ${toError(error).message}`);
393
+ throw new PreflightRefusalError(`Marionette port ${port} is held by stale browser ${holder.command} (PID ${holder.pid}), ` +
394
+ `but FireForge could not terminate it: ${toError(error).message}`, 'stale-browser-kill-failed');
395
395
  }
396
396
  }
397
397
  /**
@@ -410,10 +410,7 @@ export async function ensureMarionettePortAvailable(port = DEFAULT_MARIONETTE_PO
410
410
  * `undefined`.
411
411
  */
412
412
  export function extractForwardedMarionettePort(machArgs) {
413
- for (let i = 0; i < machArgs.length; i++) {
414
- const arg = machArgs[i];
415
- if (arg === undefined)
416
- continue;
413
+ for (const [i, arg] of machArgs.entries()) {
417
414
  // `--marionette-port=NNNN`
418
415
  let match = /^--marionette-port=(\d+)$/.exec(arg);
419
416
  if (match?.[1]) {
@@ -452,10 +449,7 @@ export function extractForwardedMarionettePort(machArgs) {
452
449
  */
453
450
  export function forwardedMachArgsIncludeMarionetteClient(machArgs) {
454
451
  const valueLooksLikeHostPort = (token) => /:[0-9]+$/.test(token) || /^\[[^]]+\]:[0-9]+$/.test(token);
455
- for (let i = 0; i < machArgs.length; i++) {
456
- const arg = machArgs[i];
457
- if (arg === undefined)
458
- continue;
452
+ for (const [i, arg] of machArgs.entries()) {
459
453
  if (arg.startsWith('--marionette=') && !arg.startsWith('--marionette-port=')) {
460
454
  return true;
461
455
  }
@@ -473,8 +467,7 @@ export function forwardedMachArgsIncludeMarionetteClient(machArgs) {
473
467
  * for runs where the pref is ignored anyway.
474
468
  */
475
469
  export function hasExplicitXpcshellFlavor(machArgs) {
476
- for (let i = 0; i < machArgs.length; i += 1) {
477
- const arg = machArgs[i] ?? '';
470
+ for (const [i, arg] of machArgs.entries()) {
478
471
  if (/^--flavor=xpcshell\b/.test(arg) || arg === '--flavor=xpcshell-tests')
479
472
  return true;
480
473
  if (arg === '--flavor' && /^xpcshell(?:-tests)?$/.test(machArgs[i + 1] ?? ''))
@@ -493,42 +486,3 @@ export function hasExplicitXpcshellFlavor(machArgs) {
493
486
  export function shouldAutoForwardMarionettePortToMach(machArgs) {
494
487
  return !hasExplicitXpcshellFlavor(machArgs);
495
488
  }
496
- /**
497
- * Heuristic: do the test paths or forwarded mach args indicate a flavour
498
- * that actually launches a Marionette-driven browser? Browser-chrome and
499
- * mochitest do; xpcshell does not. A no-paths invocation (the default "run
500
- * all tests" shape) is treated as marionette-relevant since it includes
501
- * browser-chrome.
502
- *
503
- * Note: `fireforge test` auto-forward of `--marionette-port` to mach uses
504
- * {@link shouldAutoForwardMarionettePortToMach} (mach-arg flavor gate) rather
505
- * than this function alone, so toolkit paths without `/mochitest/` still get
506
- * the listener pref and harness `--marionette=127.0.0.1:<n>` when appropriate.
507
- *
508
- * @param testPaths - Engine-relative paths after `stripEnginePrefix`.
509
- * @param machArgs - Forwarded mach args (post-`--mach-arg`).
510
- * @returns `true` when the run is likely to bind a Marionette listener.
511
- */
512
- export function isMarionetteFlavor(testPaths, machArgs) {
513
- if (hasExplicitXpcshellFlavor(machArgs))
514
- return false;
515
- for (const arg of machArgs) {
516
- if (/^--flavor=(browser-chrome|mochitest|chrome|a11y)\b/.test(arg))
517
- return true;
518
- }
519
- if (testPaths.length === 0)
520
- return true;
521
- for (const path of testPaths) {
522
- const base = path.split('/').pop() ?? path;
523
- if (/^browser_.+\.(js|ini|toml)$/.test(base))
524
- return true;
525
- if (path.includes('/mochitest/') || path.startsWith('mochitest/'))
526
- return true;
527
- if (path.includes('/browser-chrome/') || path.startsWith('browser-chrome/'))
528
- return true;
529
- if (path.includes('toolkit/content/tests/') && !path.includes('/tests/xpcshell/')) {
530
- return true;
531
- }
532
- }
533
- return false;
534
- }
@@ -22,6 +22,7 @@ import { pathExists } from '../utils/fs.js';
22
22
  import { info, verbose, warn } from '../utils/logger.js';
23
23
  import { installGracefulShutdownForwarder, trackChildClosure } from '../utils/process.js';
24
24
  import { killProcessTree, sweepProcessGroup } from '../utils/process-group.js';
25
+ import { sleep } from '../utils/sleep.js';
25
26
  import { ensureMach } from './mach.js';
26
27
  import { getPython } from './mach-python.js';
27
28
  /** Marionette's default TCP port when the browser is launched with `--marionette`. */
@@ -143,7 +144,7 @@ export async function runMarionettePreflight(engineDir, options = {}) {
143
144
  const spawnedChild = child;
144
145
  closure = trackChildClosure();
145
146
  forwarder = installGracefulShutdownForwarder(spawnedChild, 500, (signal) => {
146
- killProcessGroup(spawnedChild, signal);
147
+ killProcessTree(spawnedChild, signal, process.platform !== 'win32');
147
148
  });
148
149
  child.stderr?.on('data', (data) => {
149
150
  const chunk = data.toString();
@@ -155,10 +156,10 @@ export async function runMarionettePreflight(engineDir, options = {}) {
155
156
  // never come.
156
157
  const settleDeadline = Math.min(spawnSettleMs, Math.max(0, timeoutMs - elapsed()));
157
158
  if (settleDeadline > 0) {
158
- await delay(settleDeadline);
159
+ await sleep(settleDeadline, { unref: true });
159
160
  }
160
161
  if (hasChildExited(spawnedChild)) {
161
- return fail('browser-spawns', `Browser process exited during spawn (exit code ${String(spawnedChild.exitCode)}, signal ${spawnedChild.signalCode ?? 'none'}). ` +
162
+ return fail('browser-spawns', `Browser process exited during spawn (exit code ${spawnedChild.exitCode}, signal ${spawnedChild.signalCode ?? 'none'}). ` +
162
163
  `stderr tail: ${stderrTail.trim().slice(-2_000) || '(empty)'}`, elapsed());
163
164
  }
164
165
  // Layer 6: marionette handshake within the remaining budget.
@@ -175,7 +176,7 @@ export async function runMarionettePreflight(engineDir, options = {}) {
175
176
  // Child may have exited before the socket was ever ready — surface that
176
177
  // distinctly from "socket never answered" so the operator has a lead.
177
178
  if (hasChildExited(spawnedChild)) {
178
- return fail('marionette-handshake', `Browser process exited before marionette handshake (exit code ${String(spawnedChild.exitCode)}, signal ${spawnedChild.signalCode ?? 'none'}). ` +
179
+ return fail('marionette-handshake', `Browser process exited before marionette handshake (exit code ${spawnedChild.exitCode}, signal ${spawnedChild.signalCode ?? 'none'}). ` +
179
180
  `stderr tail: ${stderrTail.trim().slice(-2_000) || '(empty)'}`, elapsed());
180
181
  }
181
182
  return fail('marionette-handshake', `Marionette socket on 127.0.0.1:${port} did not respond within ${timeoutMs}ms. ` +
@@ -203,12 +204,12 @@ async function teardownPreflightChild(child, forwarder, closure, profileDir) {
203
204
  forwarder?.dispose();
204
205
  const groupPid = child?.pid;
205
206
  if (child && !hasChildExited(child)) {
206
- killProcessGroup(child, 'SIGTERM');
207
+ killProcessTree(child, 'SIGTERM', process.platform !== 'win32');
207
208
  // Small escalation: if the child doesn't honour SIGTERM quickly, SIGKILL
208
209
  // so we don't leave a ghost mach process around after a failed probe.
209
- await delay(500);
210
+ await sleep(500, { unref: true });
210
211
  if (!hasChildExited(child)) {
211
- killProcessGroup(child, 'SIGKILL');
212
+ killProcessTree(child, 'SIGKILL', process.platform !== 'win32');
212
213
  }
213
214
  }
214
215
  // Reap anything that outlived the group signal. The file's own comments
@@ -237,19 +238,6 @@ async function teardownPreflightChild(child, forwarder, closure, profileDir) {
237
238
  warn(`Could not clean up marionette preflight profile: ${toError(error).message}`);
238
239
  }
239
240
  }
240
- /**
241
- * Sends `signal` to the child's whole process group when possible, falling
242
- * back to a direct `child.kill` for environments that don't support
243
- * `detached: true` (Windows in particular: Node still returns a pid, but
244
- * `kill(-pid, …)` is not a supported kernel primitive).
245
- */
246
- function killProcessGroup(child, signal) {
247
- // Delegates to the shared implementation, which is a strict superset of
248
- // the hand-rolled one this replaced: same POSIX `kill(-pid)` with the same
249
- // fall-through to a direct `child.kill`, plus a Windows `taskkill /T /F`
250
- // branch and an already-exited early return.
251
- killProcessTree(child, signal, process.platform !== 'win32');
252
- }
253
241
  function fail(layer, message, durationMs) {
254
242
  return {
255
243
  ok: false,
@@ -267,7 +255,7 @@ async function waitForMarionetteSocket(port, connectFn, keepTrying) {
267
255
  if (result.ok) {
268
256
  return { ok: true };
269
257
  }
270
- await delay(400);
258
+ await sleep(400, { unref: true });
271
259
  }
272
260
  return { ok: false };
273
261
  }
@@ -312,12 +300,6 @@ function attemptMarionetteConnect(port, connectFn) {
312
300
  });
313
301
  });
314
302
  }
315
- function delay(ms) {
316
- return new Promise((resolve) => {
317
- const timer = setTimeout(resolve, ms);
318
- timer.unref();
319
- });
320
- }
321
303
  /** Renders a PASS/FAIL banner to the CLI using the shared logger helpers. */
322
304
  export function reportMarionettePreflight(result) {
323
305
  if (result.ok) {
@@ -100,8 +100,8 @@ export function describeMochitestServerRefusal(port, holder, recognized) {
100
100
  const killHint = process.platform === 'win32'
101
101
  ? `Stop-Process -Id ${holder.pid} -Force`
102
102
  : `kill -9 ${holder.pid}`;
103
- const evidence = `The mochitest server port ${String(port)} is already held by ${holder.command} ` +
104
- `(PID ${String(holder.pid)}).\n` +
103
+ const evidence = `The mochitest server port ${port} is already held by ${holder.command} ` +
104
+ `(PID ${holder.pid}).\n` +
105
105
  ` command: ${holder.commandLine}\n` +
106
106
  " A browser launched now would connect to THAT server, which cannot serve this run's " +
107
107
  'manifest — the run would stall before TEST_START and die on the no-output timeout with ' +
@@ -115,5 +115,5 @@ export function describeMochitestServerRefusal(port, holder, recognized) {
115
115
  return (evidence +
116
116
  " This is NOT the mochitest harness's server.js, so FireForge will not offer to kill it. " +
117
117
  `Stop the holder yourself with "${killHint}" if it is yours, or free the port, then retry. ` +
118
- `Diagnose with "lsof -nP -iTCP:${String(port)}".`);
118
+ `Diagnose with "lsof -nP -iTCP:${port}".`);
119
119
  }