@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
@@ -22,7 +22,11 @@
22
22
  export interface RunLog {
23
23
  /** Absolute path to the log file. */
24
24
  path: string;
25
- /** Appends raw output. Silently drops on any stream error. */
25
+ /**
26
+ * Appends output. Complete lines pass through {@link redactRunLogText}
27
+ * before hitting disk; a trailing partial line is held until its newline
28
+ * arrives (or until `close`). Silently drops on any stream error.
29
+ */
26
30
  write: (chunk: string) => void;
27
31
  /** Flushes and closes. Resolves even when the stream already failed. */
28
32
  close: () => Promise<void>;
@@ -45,7 +49,14 @@ export declare function setActiveRunLog(log: RunLog | undefined): void;
45
49
  export declare function getActiveRunLogPath(): string | undefined;
46
50
  /** Appends to the active run log. A no-op when none is open. */
47
51
  export declare function writeToActiveRunLog(chunk: string): void;
48
- /** Closes and clears the active run log. Resolves even when none is open. */
52
+ /**
53
+ * Closes and clears the active run log. Resolves even when none is open.
54
+ *
55
+ * Also called from the entry point's SIGINT/SIGTERM path: the held partial
56
+ * line and anything queued in the stream would otherwise be lost across
57
+ * `process.exit`, and a killed run's last line is usually the one that
58
+ * says what it was doing.
59
+ */
49
60
  export declare function closeActiveRunLog(): Promise<void>;
50
61
  /**
51
62
  * A writable that forwards to `base` AND to the active run log.
@@ -23,8 +23,11 @@ import { createWriteStream } from 'node:fs';
23
23
  import { mkdir, readdir, unlink } from 'node:fs/promises';
24
24
  import { join } from 'node:path';
25
25
  import { Writable } from 'node:stream';
26
+ import { toError } from '../utils/errors.js';
26
27
  import { verbose } from '../utils/logger.js';
27
28
  import { FIREFORGE_DIR } from './config-paths.js';
29
+ import { redactRunLogText } from './run-log-redact.js';
30
+ import { fileSafeTimestamp } from './state-file.js';
28
31
  /** Directory under `.fireforge/` holding run logs. */
29
32
  const LOGS_DIRNAME = 'logs';
30
33
  /**
@@ -33,20 +36,34 @@ const LOGS_DIRNAME = 'logs';
33
36
  * most often needs to read together.
34
37
  */
35
38
  const RETAINED_LOGS_PER_COMMAND = 20;
39
+ /**
40
+ * Cap on the partial line held back between writes. A child emitting one
41
+ * enormous unterminated line (a minified dump on stderr) would otherwise
42
+ * grow the buffer without bound — and, because each write rebuilds
43
+ * `pending + chunk`, quadratically — with nothing reaching disk until
44
+ * close. Past the cap the tail is redacted and written as-is; a `KEY=value`
45
+ * split exactly at the cap boundary is the accepted cost. Same figure as
46
+ * `line-dispatch.ts` uses for the stream matchers.
47
+ */
48
+ const MAX_PENDING_LINE_SIZE = 1024 * 1024;
49
+ /**
50
+ * Index just past the last line terminator in `text`, or -1 when there is
51
+ * none. `\n` and lone `\r` both terminate — progress-bar output (mach,
52
+ * cargo) repaints a line with carriage returns and may never send a
53
+ * newline, which would otherwise pin the whole run in `pending`. A single
54
+ * trailing `\r` is NOT a terminator yet: it may be the first half of a
55
+ * `\r\n` split across chunks.
56
+ */
57
+ function lastLineEnd(text) {
58
+ const lastNewline = text.lastIndexOf('\n');
59
+ const lastReturn = text.lastIndexOf('\r');
60
+ const cut = lastReturn > lastNewline && lastReturn < text.length - 1 ? lastReturn : lastNewline;
61
+ return cut === -1 ? -1 : cut + 1;
62
+ }
36
63
  /** `.fireforge/logs` under `projectRoot`. */
37
64
  export function getRunLogDir(projectRoot) {
38
65
  return join(projectRoot, FIREFORGE_DIR, LOGS_DIRNAME);
39
66
  }
40
- /**
41
- * A filesystem-safe, sortable timestamp: `2026-08-28T14-32-05-123Z`.
42
- *
43
- * Colons are not portable in filenames (Windows rejects them outright), so
44
- * the ISO form is punctuated with dashes only. Lexical order still equals
45
- * chronological order, which is what the pruning below relies on.
46
- */
47
- function logTimestamp(now) {
48
- return now.toISOString().replace(/[:.]/g, '-');
49
- }
50
67
  /**
51
68
  * Makes room for one new log by deleting the oldest, so that at most
52
69
  * {@link RETAINED_LOGS_PER_COMMAND} logs for `command` exist once the new
@@ -76,7 +93,7 @@ async function pruneRunLogs(dir, command) {
76
93
  }));
77
94
  }
78
95
  catch (error) {
79
- verbose(`Run-log pruning skipped: ${String(error)}`);
96
+ verbose(`Run-log pruning skipped: ${toError(error).message}`);
80
97
  }
81
98
  }
82
99
  /**
@@ -91,14 +108,14 @@ async function pruneRunLogs(dir, command) {
91
108
  export async function openRunLog(projectRoot, command, now = new Date()) {
92
109
  const dir = getRunLogDir(projectRoot);
93
110
  let stream;
94
- const path = join(dir, `${command}-${logTimestamp(now)}.log`);
111
+ const path = join(dir, `${command}-${fileSafeTimestamp(now)}.log`);
95
112
  try {
96
113
  await mkdir(dir, { recursive: true });
97
114
  await pruneRunLogs(dir, command);
98
115
  stream = createWriteStream(path, { flags: 'a' });
99
116
  }
100
117
  catch (error) {
101
- verbose(`Run log unavailable (${path}): ${String(error)}`);
118
+ verbose(`Run log unavailable (${path}): ${toError(error).message}`);
102
119
  return undefined;
103
120
  }
104
121
  // A stream error after open (disk full, a removed directory) must not
@@ -109,19 +126,44 @@ export async function openRunLog(projectRoot, command, now = new Date()) {
109
126
  broken = true;
110
127
  verbose(`Run log write failed (${path}): ${error.message}`);
111
128
  });
129
+ // Redaction is per LINE, and chunks arrive at arbitrary byte boundaries,
130
+ // so a `KEY=value` split across two chunks would slip past a per-chunk
131
+ // pass. Hold the trailing partial line until its terminator arrives (or
132
+ // it outgrows MAX_PENDING_LINE_SIZE, or the log is closed).
133
+ let pending = '';
134
+ const emit = (text) => {
135
+ if (broken || text.length === 0)
136
+ return;
137
+ try {
138
+ stream.write(redactRunLogText(text));
139
+ }
140
+ catch {
141
+ broken = true;
142
+ }
143
+ };
112
144
  return {
113
145
  path,
114
146
  write: (chunk) => {
115
147
  if (broken)
116
148
  return;
117
- try {
118
- stream.write(chunk);
149
+ const buffered = pending + chunk;
150
+ const lineEnd = lastLineEnd(buffered);
151
+ if (lineEnd === -1) {
152
+ pending = buffered;
119
153
  }
120
- catch {
121
- broken = true;
154
+ else {
155
+ pending = buffered.slice(lineEnd);
156
+ emit(buffered.slice(0, lineEnd));
157
+ }
158
+ if (pending.length > MAX_PENDING_LINE_SIZE) {
159
+ emit(pending);
160
+ pending = '';
122
161
  }
123
162
  },
124
163
  close: async () => {
164
+ const tail = pending;
165
+ pending = '';
166
+ emit(tail);
125
167
  await new Promise((resolve) => {
126
168
  stream.end(() => {
127
169
  resolve();
@@ -155,7 +197,14 @@ export function getActiveRunLogPath() {
155
197
  export function writeToActiveRunLog(chunk) {
156
198
  activeRunLog?.write(chunk);
157
199
  }
158
- /** Closes and clears the active run log. Resolves even when none is open. */
200
+ /**
201
+ * Closes and clears the active run log. Resolves even when none is open.
202
+ *
203
+ * Also called from the entry point's SIGINT/SIGTERM path: the held partial
204
+ * line and anything queued in the stream would otherwise be lost across
205
+ * `process.exit`, and a killed run's last line is usually the one that
206
+ * says what it was doing.
207
+ */
159
208
  export async function closeActiveRunLog() {
160
209
  const log = activeRunLog;
161
210
  activeRunLog = undefined;
@@ -18,6 +18,7 @@
18
18
  * itself. The bin entry point is responsible for awaiting
19
19
  * `waitForActiveCriticalSections` before terminating.
20
20
  */
21
+ import { sleep } from '../utils/sleep.js';
21
22
  const activeSections = new Set();
22
23
  /**
23
24
  * Runs `fn` inside a signal-deferred critical section. The CLI entry point's
@@ -57,8 +58,5 @@ export async function waitForActiveCriticalSections(timeoutMs) {
57
58
  if (activeSections.size === 0)
58
59
  return;
59
60
  const snapshot = [...activeSections].map((s) => s.promise);
60
- await Promise.race([
61
- Promise.allSettled(snapshot).then(() => undefined),
62
- new Promise((resolve) => setTimeout(resolve, timeoutMs)),
63
- ]);
61
+ await Promise.race([Promise.allSettled(snapshot).then(() => undefined), sleep(timeoutMs)]);
64
62
  }
@@ -10,7 +10,6 @@
10
10
  * warning (e.g. `"pending JavaScript error cleanup"`) do not start with
11
11
  * the prefix and should not trip the scanner.
12
12
  */
13
- export declare const SMOKE_ERROR_PATTERNS: readonly RegExp[];
14
13
  /**
15
14
  * Returns `true` when `line` matches any pattern in
16
15
  * {@link SMOKE_ERROR_PATTERNS}. Does not consult the allowlist — that step
@@ -20,7 +20,7 @@
20
20
  * smoke run into noise for operators and every CI run into flake.
21
21
  */
22
22
  import { toError } from '../utils/errors.js';
23
- export const SMOKE_ERROR_PATTERNS = [
23
+ const SMOKE_ERROR_PATTERNS = [
24
24
  // Firefox chrome error lines — `JavaScript error: chrome://…, line N: TypeError: …`.
25
25
  /^\s*JavaScript error:/i,
26
26
  // Some log paths prefix browser-console `console.error(...)` with the literal label below.
@@ -63,12 +63,7 @@ export function matchesSmokeError(line) {
63
63
  * allowlist (always returns -1).
64
64
  */
65
65
  export function matchAllowlist(line, allow) {
66
- for (let i = 0; i < allow.length; i++) {
67
- if (allow[i]?.pattern.test(line)) {
68
- return i;
69
- }
70
- }
71
- return -1;
66
+ return allow.findIndex((entry) => entry.pattern.test(line));
72
67
  }
73
68
  /**
74
69
  * Parses a newline-delimited allowlist file body. Lines are trimmed; blank
@@ -88,12 +83,12 @@ export function compileAllowlistFromFile(body, sourcePath) {
88
83
  compiled.push({
89
84
  pattern: new RegExp(line),
90
85
  source: line,
91
- origin: `${sourcePath}:${String(index + 1)}`,
86
+ origin: `${sourcePath}:${index + 1}`,
92
87
  });
93
88
  }
94
89
  catch (error) {
95
90
  const message = toError(error).message;
96
- throw new Error(`Invalid allowlist regex at ${sourcePath}:${String(index + 1)}: ${message}`, {
91
+ throw new Error(`Invalid allowlist regex at ${sourcePath}:${index + 1}: ${message}`, {
97
92
  cause: error,
98
93
  });
99
94
  }
@@ -111,12 +106,12 @@ export function compileAllowlistFromStrings(sources) {
111
106
  compiled.push({
112
107
  pattern: new RegExp(source),
113
108
  source,
114
- origin: `--console-allow #${String(index + 1)}`,
109
+ origin: `--console-allow #${index + 1}`,
115
110
  });
116
111
  }
117
112
  catch (error) {
118
113
  const message = toError(error).message;
119
- throw new Error(`Invalid --console-allow regex at position ${String(index + 1)} ("${source}"): ${message}`, { cause: error });
114
+ throw new Error(`Invalid --console-allow regex at position ${index + 1} ("${source}"): ${message}`, { cause: error });
120
115
  }
121
116
  });
122
117
  return compiled;
@@ -1,5 +1,16 @@
1
1
  /** Runs an operation while holding the sidecar lock for a FireForge state file. */
2
2
  export declare function withStateFileLock<T>(statePath: string, operation: () => Promise<T>): Promise<T>;
3
+ /**
4
+ * A filesystem-safe, sortable timestamp: `2026-08-28T14-32-05-123Z`.
5
+ *
6
+ * Colons are not portable in filenames (Windows rejects them outright), so
7
+ * the ISO form is punctuated with dashes only. Lexical order still equals
8
+ * chronological order, which is what run-log pruning relies on.
9
+ *
10
+ * @param now - Instant to render
11
+ * @returns The ISO-8601 form with `:` and `.` replaced by `-`
12
+ */
13
+ export declare function fileSafeTimestamp(now: Date): string;
3
14
  /** Renames a state file out of the way while preserving it for later inspection. */
4
15
  export declare function quarantineStateFile(statePath: string, reason?: string): Promise<string | undefined>;
5
16
  /**
@@ -15,7 +26,7 @@ export declare function quarantineStateFile(statePath: string, reason?: string):
15
26
  * inside that tree.
16
27
  *
17
28
  * `furnace.json` is the pattern the rest should follow: it refuses a newer
18
- * version by name and tells the operator to upgrade (`migrateFurnaceConfig`
29
+ * version by name and tells the operator to upgrade (`validateConfigVersion`
19
30
  * in `furnace-config.ts`).
20
31
  */
21
32
  export type DocumentVersionCheck = {
@@ -12,12 +12,25 @@ export async function withStateFileLock(statePath, operation) {
12
12
  `(age: ${Math.round(ageMs / 1000)}s). A previous fireforge process may have crashed.`,
13
13
  });
14
14
  }
15
+ /**
16
+ * A filesystem-safe, sortable timestamp: `2026-08-28T14-32-05-123Z`.
17
+ *
18
+ * Colons are not portable in filenames (Windows rejects them outright), so
19
+ * the ISO form is punctuated with dashes only. Lexical order still equals
20
+ * chronological order, which is what run-log pruning relies on.
21
+ *
22
+ * @param now - Instant to render
23
+ * @returns The ISO-8601 form with `:` and `.` replaced by `-`
24
+ */
25
+ export function fileSafeTimestamp(now) {
26
+ return now.toISOString().replace(/[:.]/g, '-');
27
+ }
15
28
  /** Renames a state file out of the way while preserving it for later inspection. */
16
29
  export async function quarantineStateFile(statePath, reason = 'corrupt') {
17
30
  if (!(await pathExists(statePath))) {
18
31
  return undefined;
19
32
  }
20
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
33
+ const timestamp = fileSafeTimestamp(new Date());
21
34
  const quarantinedPath = `${statePath}.${reason}-${timestamp}`;
22
35
  await rename(statePath, quarantinedPath);
23
36
  return basename(quarantinedPath);
@@ -11,6 +11,7 @@ import { mapWithConcurrency } from '../utils/concurrency.js';
11
11
  import { toError } from '../utils/errors.js';
12
12
  import { readText } from '../utils/fs.js';
13
13
  import { verbose } from '../utils/logger.js';
14
+ import { normalizePathSlashes } from '../utils/paths.js';
14
15
  import { isBrandingManagedPath } from './branding.js';
15
16
  import { createPatchedContentContext } from './patch-apply.js';
16
17
  import { classifyBinaryOwnedFile } from './status-binary.js';
@@ -56,7 +57,7 @@ export function getPrimaryStatusCode(status) {
56
57
  * visible as unmanaged (the Assets.car precedent).
57
58
  */
58
59
  export function isGeneratedBrandingPath(file, binaryName) {
59
- const normalized = file.replace(/\\/g, '/');
60
+ const normalized = normalizePathSlashes(file);
60
61
  const brandingRoot = `browser/branding/${binaryName}`;
61
62
  return (normalized === 'browser/moz.configure' ||
62
63
  normalized === `${brandingRoot}/configure.sh` ||
@@ -46,6 +46,30 @@ const CHANGED_PREF_BLOCK_PATTERN = /pref(?:erence)?s?\b/i;
46
46
  * @returns Operator-facing explanation, or undefined
47
47
  */
48
48
  export function describeChangedPrefNoise(output) {
49
+ const namesSeen = matchedTimeDrivenPrefNames(output);
50
+ if (namesSeen === undefined)
51
+ return undefined;
52
+ return (`Every unexpected result in this run is a changed-preference check on ${[...namesSeen].join(' / ')}. ` +
53
+ 'Those preferences are TIME-DRIVEN: they move once a run is long enough to cross the ' +
54
+ 'cold-startup check interval, so they correlate with run LENGTH rather than with the ' +
55
+ 'change under test or with the build. The first probe is to halve the chunk size ' +
56
+ '(`--chunk 8` where a 40-row chunk tripped it) and see whether the same work passes; ' +
57
+ 'if it does, this was harness noise, not a regression. FireForge cannot forgive the ' +
58
+ 'check — it belongs to the harness — only tell you which kind of failure you are ' +
59
+ 'looking at.');
60
+ }
61
+ /** Failure blocks inspected before the changed-pref shape is abandoned. */
62
+ const CHANGED_PREF_BLOCK_LIMIT = 20;
63
+ /**
64
+ * The RECOGNIZER half of the shape, split from the formatting so a caller
65
+ * that only needs the yes/no answer does not build the ~500-character
66
+ * explanation to throw it away.
67
+ *
68
+ * @param output - Combined harness stdout+stderr
69
+ * @returns The time-driven pref names seen, or undefined when the shape does
70
+ * not apply
71
+ */
72
+ function matchedTimeDrivenPrefNames(output) {
49
73
  const blocks = collectUnexpectedFailureBlocks(output, CHANGED_PREF_BLOCK_LIMIT).filter((block) => !block.startsWith('…'));
50
74
  if (blocks.length === 0)
51
75
  return undefined;
@@ -59,20 +83,11 @@ export function describeChangedPrefNoise(output) {
59
83
  for (const name of matched)
60
84
  namesSeen.add(name);
61
85
  }
62
- return (`Every unexpected result in this run is a changed-preference check on ${[...namesSeen].join(' / ')}. ` +
63
- 'Those preferences are TIME-DRIVEN: they move once a run is long enough to cross the ' +
64
- 'cold-startup check interval, so they correlate with run LENGTH rather than with the ' +
65
- 'change under test or with the build. The first probe is to halve the chunk size ' +
66
- '(`--chunk 8` where a 40-row chunk tripped it) and see whether the same work passes; ' +
67
- 'if it does, this was harness noise, not a regression. FireForge cannot forgive the ' +
68
- 'check — it belongs to the harness — only tell you which kind of failure you are ' +
69
- 'looking at.');
86
+ return namesSeen;
70
87
  }
71
- /** Failure blocks inspected before the changed-pref shape is abandoned. */
72
- const CHANGED_PREF_BLOCK_LIMIT = 20;
73
88
  /** Short verdict-line note for the changed-preference-noise shape. */
74
89
  export function changedPrefNoiseVerdictNote(output) {
75
- return describeChangedPrefNoise(output) === undefined
90
+ return matchedTimeDrivenPrefNames(output) === undefined
76
91
  ? undefined
77
92
  : 'all unexpected results are time-driven changed-pref checks';
78
93
  }
@@ -589,13 +589,13 @@ function evaluateGreenTeardownOverride(output, counts, realFailures, requestedPa
589
589
  return reject('summary printed no "Unexpected results:" line');
590
590
  }
591
591
  if (counts.unexpected !== 0) {
592
- return reject(`summary reported unexpected=${String(counts.unexpected)}`);
592
+ return reject(`summary reported unexpected=${counts.unexpected}`);
593
593
  }
594
594
  if (NONZERO_UNEXPECTED_SUMMARY_PATTERN.test(output)) {
595
595
  return reject('a non-zero "Unexpected results:" line is present somewhere in the output');
596
596
  }
597
597
  if (realFailures.length > 0) {
598
- return reject(`${String(realFailures.length)} matched failure line(s), first: ${truncateEvidence(realFailures[0] ?? '')}`);
598
+ return reject(`${realFailures.length} matched failure line(s), first: ${truncateEvidence(realFailures[0] ?? '')}`);
599
599
  }
600
600
  const crashLine = findCrashMarkerLine(output);
601
601
  if (crashLine !== undefined)
@@ -1,6 +1,4 @@
1
- import { isXpcomManifestPath } from './build-audit.js';
2
1
  import type { BuildBaseline, TestPackagingCoverage } from './build-baseline-types.js';
3
- export { isXpcomManifestPath };
4
2
  /** Result of the stale-build preflight probe. */
5
3
  export interface StaleBuildResult {
6
4
  /** True when at least one packageable engine file changed since the baseline. */
@@ -24,11 +24,11 @@
24
24
  */
25
25
  import { toError } from '../utils/errors.js';
26
26
  import { verbose, warn } from '../utils/logger.js';
27
+ import { normalizePathSlashes } from '../utils/paths.js';
27
28
  import { isPackageablePath, isXpcomManifestPath } from './build-audit.js';
28
29
  import { readBuildBaseline } from './build-baseline.js';
29
30
  import { hashEngineFile } from './coverage-extend.js';
30
31
  import { collectChangedEnginePaths, dropPathsMatchingFingerprints } from './engine-changes.js';
31
- export { isXpcomManifestPath };
32
32
  /** Cap on the number of changed paths rendered inline. */
33
33
  const STALE_PATHS_LIMIT = 10;
34
34
  /**
@@ -138,7 +138,7 @@ function toManifestGranule(path) {
138
138
  }
139
139
  /** Normalizes a path for coverage comparison: forward slashes, no trailing slash. */
140
140
  function normalizeCoveragePath(path) {
141
- return path.trim().replace(/\\/g, '/').replace(/\/+$/, '');
141
+ return normalizePathSlashes(path.trim()).replace(/\/+$/, '');
142
142
  }
143
143
  /**
144
144
  * Formats the refusal shown when a non-`--build` run requests paths the
@@ -1,14 +1,33 @@
1
- import { type MachCommandResult } from './mach.js';
1
+ import { type MachCommandResult, type MachTestSuiteKind } from './mach.js';
2
2
  export interface XpcshellRetryClassification {
3
3
  xpcshell: readonly string[];
4
4
  nonXpcshell: readonly string[];
5
5
  }
6
- /** Dispatches a (possibly suite-specific) mach test run, mirroring `testWithOutput`. */
7
- export type TestDispatch = (engineDir: string, testPaths: string[], args: string[], env?: Record<string, string>, fullOutput?: boolean) => Promise<MachCommandResult>;
6
+ export interface XpcshellRetryOptions {
7
+ /** Engine checkout the run happened in. */
8
+ engineDir: string;
9
+ /** Object directory the run used, when one is configured. */
10
+ objDir: string | undefined;
11
+ /** Result of the run that may need repairing and retrying. */
12
+ result: MachCommandResult;
13
+ /** Which of the requested paths are xpcshell tests, and which are not. */
14
+ classification: XpcshellRetryClassification;
15
+ /** Test paths, normalized, as handed to mach. */
16
+ normalizedPaths: string[];
17
+ /** Extra mach arguments the run was started with. */
18
+ extraArgs: string[];
19
+ /** Environment overlay for the retried run. */
20
+ env?: Record<string, string> | undefined;
21
+ /** Mach command kind to retry with. Defaults to the generic `test`. */
22
+ kind?: MachTestSuiteKind | undefined;
23
+ /** Whether the run was started with full harness output. */
24
+ fullOutput?: boolean | undefined;
25
+ }
8
26
  /**
9
27
  * Removes a stale xpcshell install symlink and retries the focused mach test
10
- * once. The retry uses the same `dispatch` (suite-specific or generic) the
11
- * caller is already running on, so an xpcshell-suite run repairs and re-runs
12
- * via `mach xpcshell-test` rather than falling back to the generic command.
28
+ * once. The retry uses the same mach command `kind` (suite-specific or
29
+ * generic) the caller is already running on, so an xpcshell-suite run repairs
30
+ * and re-runs via `mach xpcshell-test` rather than falling back to the
31
+ * generic command.
13
32
  */
14
- export declare function retryAfterXpcshellSymlinkRepair(engineDir: string, objDir: string | undefined, result: MachCommandResult, classification: XpcshellRetryClassification, normalizedPaths: string[], extraArgs: string[], env?: Record<string, string>, dispatch?: TestDispatch, fullOutput?: boolean): Promise<MachCommandResult>;
33
+ export declare function retryAfterXpcshellSymlinkRepair(options: XpcshellRetryOptions): Promise<MachCommandResult>;
@@ -1,13 +1,15 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
- import { testWithOutput } from './mach.js';
2
+ import { runMachTestSuite } from './mach.js';
3
3
  import { tryRepairStaleXpcshellTestSymlink } from './test-stale-symlink.js';
4
4
  /**
5
5
  * Removes a stale xpcshell install symlink and retries the focused mach test
6
- * once. The retry uses the same `dispatch` (suite-specific or generic) the
7
- * caller is already running on, so an xpcshell-suite run repairs and re-runs
8
- * via `mach xpcshell-test` rather than falling back to the generic command.
6
+ * once. The retry uses the same mach command `kind` (suite-specific or
7
+ * generic) the caller is already running on, so an xpcshell-suite run repairs
8
+ * and re-runs via `mach xpcshell-test` rather than falling back to the
9
+ * generic command.
9
10
  */
10
- export async function retryAfterXpcshellSymlinkRepair(engineDir, objDir, result, classification, normalizedPaths, extraArgs, env, dispatch = testWithOutput, fullOutput) {
11
+ export async function retryAfterXpcshellSymlinkRepair(options) {
12
+ const { engineDir, objDir, result, classification, normalizedPaths, extraArgs, env, kind = 'test', fullOutput, } = options;
11
13
  if (result.exitCode !== 0 &&
12
14
  classification.xpcshell.length > 0 &&
13
15
  classification.nonXpcshell.length === 0) {
@@ -15,12 +17,13 @@ export async function retryAfterXpcshellSymlinkRepair(engineDir, objDir, result,
15
17
  if (repaired) {
16
18
  // The repaired re-run is the same logical invocation, so it keeps
17
19
  // the verbosity the run was started with.
18
- if (fullOutput === true) {
19
- return dispatch(engineDir, normalizedPaths, extraArgs, env, true);
20
- }
21
- return env
22
- ? dispatch(engineDir, normalizedPaths, extraArgs, env)
23
- : dispatch(engineDir, normalizedPaths, extraArgs);
20
+ return runMachTestSuite(kind, {
21
+ engineDir,
22
+ testPaths: normalizedPaths,
23
+ args: extraArgs,
24
+ env,
25
+ fullOutput,
26
+ });
24
27
  }
25
28
  }
26
29
  return result;
@@ -5,6 +5,32 @@
5
5
  * agree on what "exists" means.
6
6
  */
7
7
  export declare function categoryHeaderExists(lines: string[], category: string): boolean;
8
+ /**
9
+ * Scans a tokens CSS file for category header comments and returns the
10
+ * category names in document order. Used to enrich the "category not
11
+ * found" error body with concrete alternatives the operator can copy.
12
+ *
13
+ * Mirrors the shapes `findCategorySection` recognises:
14
+ * - Single-line: `/* = Foo = *\/`
15
+ * - Multi-line: `/* =====` opening, `Foo` on any of the next ~5 lines,
16
+ * closing `*\/`.
17
+ *
18
+ * This helper exists as a pure inspector; it never throws on malformed
19
+ * headers and silently skips shapes it cannot parse.
20
+ */
21
+ /**
22
+ * Category name declared by the banner STARTING at `index`, or `undefined`
23
+ * when that line opens no banner.
24
+ *
25
+ * The single reader for both banner shapes, so the inventory walk, the
26
+ * "available categories" error body and the section finder cannot drift on
27
+ * what counts as a header.
28
+ *
29
+ * @param lines - Tokens CSS split into lines
30
+ * @param index - 0-based line index to test
31
+ * @returns The declared category name, or undefined
32
+ */
33
+ export declare function categoryBannerNameAt(lines: string[], index: number): string | undefined;
8
34
  /**
9
35
  * Asserts the category banner exists (or that creation was requested).
10
36
  * Throws a GeneralError naming the available categories and the
@@ -23,6 +49,13 @@ export declare function findCategorySection(lines: string[], category: string, t
23
49
  categoryLine: number;
24
50
  sectionEnd: number;
25
51
  };
52
+ /** 0-based open/close line indices of the base `:root {` block. */
53
+ export declare function findBaseRootBounds(lines: string[]): {
54
+ open: number;
55
+ close: number;
56
+ } | undefined;
57
+ /** Masks block-comment content per line so declarations inside comments never match. */
58
+ export declare function maskCommentLines(lines: string[]): string[];
26
59
  /** Location of an existing base-`:root` token declaration. */
27
60
  export interface TokenDeclarationLocation {
28
61
  /** 1-based line number in the tokens CSS file. */