@hominis/fireforge 0.35.0 → 0.37.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 (369) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +34 -9
  3. package/dist/bin/fireforge.js +21 -6
  4. package/dist/src/cli.js +22 -5
  5. package/dist/src/commands/bootstrap-checks.js +0 -1
  6. package/dist/src/commands/bootstrap.d.ts +2 -1
  7. package/dist/src/commands/bootstrap.js +16 -7
  8. package/dist/src/commands/build.js +5 -4
  9. package/dist/src/commands/config.js +30 -10
  10. package/dist/src/commands/discard.js +18 -3
  11. package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
  12. package/dist/src/commands/doctor-check-core.js +0 -1
  13. package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
  14. package/dist/src/commands/doctor-external-toolchains.js +54 -0
  15. package/dist/src/commands/doctor-furnace-jar.js +0 -1
  16. package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
  17. package/dist/src/commands/doctor-furnace.js +2 -2
  18. package/dist/src/commands/doctor-orphaned-harness.d.ts +52 -0
  19. package/dist/src/commands/doctor-orphaned-harness.js +132 -0
  20. package/dist/src/commands/doctor-working-tree.js +0 -1
  21. package/dist/src/commands/doctor.js +6 -1
  22. package/dist/src/commands/download.js +28 -13
  23. package/dist/src/commands/export-all.js +5 -3
  24. package/dist/src/commands/export-flow.js +12 -7
  25. package/dist/src/commands/export-placement-gate.js +0 -1
  26. package/dist/src/commands/export-placement-policy.js +0 -1
  27. package/dist/src/commands/export-shared.d.ts +5 -1
  28. package/dist/src/commands/export-shared.js +18 -6
  29. package/dist/src/commands/export.js +22 -10
  30. package/dist/src/commands/furnace/apply.js +22 -6
  31. package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
  32. package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
  33. package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
  34. package/dist/src/commands/furnace/chrome-doc.js +0 -1
  35. package/dist/src/commands/furnace/create-browser-test.js +0 -1
  36. package/dist/src/commands/furnace/create-dry-run.js +0 -1
  37. package/dist/src/commands/furnace/create-features.js +0 -1
  38. package/dist/src/commands/furnace/create-mochikit.js +0 -1
  39. package/dist/src/commands/furnace/create-readback.js +0 -1
  40. package/dist/src/commands/furnace/create-templates.js +0 -1
  41. package/dist/src/commands/furnace/create-validation.js +0 -1
  42. package/dist/src/commands/furnace/create-xpcshell.js +0 -1
  43. package/dist/src/commands/furnace/create.js +0 -1
  44. package/dist/src/commands/furnace/deploy.js +7 -83
  45. package/dist/src/commands/furnace/diff.js +0 -1
  46. package/dist/src/commands/furnace/index.js +59 -25
  47. package/dist/src/commands/furnace/init.js +0 -1
  48. package/dist/src/commands/furnace/list.js +0 -1
  49. package/dist/src/commands/furnace/override.js +0 -1
  50. package/dist/src/commands/furnace/preview.js +3 -3
  51. package/dist/src/commands/furnace/refresh.js +0 -1
  52. package/dist/src/commands/furnace/remove-state.d.ts +15 -0
  53. package/dist/src/commands/furnace/remove-state.js +47 -1
  54. package/dist/src/commands/furnace/remove.js +3 -9
  55. package/dist/src/commands/furnace/rename-browser-test.js +1 -5
  56. package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
  57. package/dist/src/commands/furnace/rename-helpers.js +30 -1
  58. package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
  59. package/dist/src/commands/furnace/rename.js +15 -36
  60. package/dist/src/commands/furnace/scan.js +0 -1
  61. package/dist/src/commands/furnace/status.js +0 -1
  62. package/dist/src/commands/furnace/sync.js +76 -4
  63. package/dist/src/commands/furnace/validate.js +0 -1
  64. package/dist/src/commands/furnace/validation-output.js +0 -1
  65. package/dist/src/commands/import.js +35 -9
  66. package/dist/src/commands/lint-per-patch.js +19 -7
  67. package/dist/src/commands/lint.js +13 -3
  68. package/dist/src/commands/manifest.js +1 -2
  69. package/dist/src/commands/package.js +2 -1
  70. package/dist/src/commands/patch/compact.js +1 -1
  71. package/dist/src/commands/patch/delete.js +1 -1
  72. package/dist/src/commands/patch/index.js +1 -1
  73. package/dist/src/commands/patch/lint-ignore.js +21 -6
  74. package/dist/src/commands/patch/move-files.js +1 -1
  75. package/dist/src/commands/patch/patch-context.js +0 -1
  76. package/dist/src/commands/patch/rename.js +4 -4
  77. package/dist/src/commands/patch/reorder.js +3 -4
  78. package/dist/src/commands/patch/split-plan.js +2 -1
  79. package/dist/src/commands/patch/split.js +4 -4
  80. package/dist/src/commands/patch/staged-dependency.d.ts +1 -1
  81. package/dist/src/commands/patch/staged-dependency.js +114 -52
  82. package/dist/src/commands/patch/tier.js +18 -5
  83. package/dist/src/commands/re-export-bulk-scan.js +0 -1
  84. package/dist/src/commands/re-export-files.js +5 -1
  85. package/dist/src/commands/re-export-options.js +0 -1
  86. package/dist/src/commands/re-export-scan.js +0 -1
  87. package/dist/src/commands/re-export.js +14 -4
  88. package/dist/src/commands/rebase/abort.js +2 -9
  89. package/dist/src/commands/rebase/confirm.js +0 -1
  90. package/dist/src/commands/rebase/conflict-summary.js +0 -1
  91. package/dist/src/commands/rebase/continue.js +0 -1
  92. package/dist/src/commands/rebase/index.d.ts +1 -1
  93. package/dist/src/commands/rebase/index.js +10 -15
  94. package/dist/src/commands/rebase/patch-loop.js +38 -7
  95. package/dist/src/commands/rebase/summary.js +2 -3
  96. package/dist/src/commands/rebase.js +0 -1
  97. package/dist/src/commands/register.js +1 -1
  98. package/dist/src/commands/reset.js +16 -9
  99. package/dist/src/commands/resolve.js +2 -2
  100. package/dist/src/commands/run.js +4 -4
  101. package/dist/src/commands/setup-support.js +0 -1
  102. package/dist/src/commands/setup.js +1 -2
  103. package/dist/src/commands/source.js +1 -2
  104. package/dist/src/commands/status-output.js +1 -12
  105. package/dist/src/commands/status.js +147 -133
  106. package/dist/src/commands/test-appdir.js +0 -1
  107. package/dist/src/commands/test-diagnose.js +38 -3
  108. package/dist/src/commands/test-modes.d.ts +15 -0
  109. package/dist/src/commands/test-modes.js +61 -0
  110. package/dist/src/commands/test-register.d.ts +4 -0
  111. package/dist/src/commands/test-register.js +64 -0
  112. package/dist/src/commands/test-run.d.ts +23 -7
  113. package/dist/src/commands/test-run.js +15 -14
  114. package/dist/src/commands/test.d.ts +0 -4
  115. package/dist/src/commands/test.js +147 -96
  116. package/dist/src/commands/token-coverage.js +0 -1
  117. package/dist/src/commands/token.js +1 -2
  118. package/dist/src/commands/typecheck.js +1 -1
  119. package/dist/src/commands/verify.js +3 -9
  120. package/dist/src/commands/watch.js +5 -1
  121. package/dist/src/commands/wire.js +1 -1
  122. package/dist/src/core/ast-utils.js +0 -1
  123. package/dist/src/core/brand-validation.js +0 -1
  124. package/dist/src/core/branding.js +0 -1
  125. package/dist/src/core/browser-wire.js +0 -1
  126. package/dist/src/core/build-audit-platform.js +0 -1
  127. package/dist/src/core/build-audit-registration.js +0 -1
  128. package/dist/src/core/build-audit-resolve.js +0 -1
  129. package/dist/src/core/build-audit-transforms.js +0 -1
  130. package/dist/src/core/build-audit.js +0 -1
  131. package/dist/src/core/build-baseline-types.d.ts +24 -0
  132. package/dist/src/core/build-baseline-types.js +0 -1
  133. package/dist/src/core/build-baseline.d.ts +5 -2
  134. package/dist/src/core/build-baseline.js +5 -2
  135. package/dist/src/core/build-prepare.js +2 -2
  136. package/dist/src/core/config-mutate.js +0 -1
  137. package/dist/src/core/config-paths.d.ts +2 -2
  138. package/dist/src/core/config-paths.js +6 -1
  139. package/dist/src/core/config-state.js +0 -1
  140. package/dist/src/core/config-validate-patch-policy.js +0 -1
  141. package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
  142. package/dist/src/core/config-validate-test-toolchains.js +105 -0
  143. package/dist/src/core/config-validate.js +3 -1
  144. package/dist/src/core/config.js +0 -1
  145. package/dist/src/core/destructive.js +0 -1
  146. package/dist/src/core/diff-hunks.js +13 -7
  147. package/dist/src/core/engine-changes.js +0 -1
  148. package/dist/src/core/engine-session-lock.d.ts +12 -0
  149. package/dist/src/core/engine-session-lock.js +45 -0
  150. package/dist/src/core/file-lock.d.ts +8 -0
  151. package/dist/src/core/file-lock.js +157 -48
  152. package/dist/src/core/firefox-archive.d.ts +4 -0
  153. package/dist/src/core/firefox-archive.js +6 -1
  154. package/dist/src/core/firefox-cache.js +60 -4
  155. package/dist/src/core/firefox-download.js +0 -1
  156. package/dist/src/core/firefox-extract.js +75 -16
  157. package/dist/src/core/firefox-ignorefile.js +0 -1
  158. package/dist/src/core/firefox.d.ts +12 -0
  159. package/dist/src/core/firefox.js +64 -2
  160. package/dist/src/core/furnace-apply-dry-run.js +0 -1
  161. package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
  162. package/dist/src/core/furnace-apply-ftl.js +10 -5
  163. package/dist/src/core/furnace-apply-helpers.js +0 -1
  164. package/dist/src/core/furnace-apply-output.js +2 -2
  165. package/dist/src/core/furnace-apply.d.ts +7 -1
  166. package/dist/src/core/furnace-apply.js +47 -13
  167. package/dist/src/core/furnace-checksum-utils.js +0 -1
  168. package/dist/src/core/furnace-config-array-utils.js +0 -1
  169. package/dist/src/core/furnace-config-custom.js +10 -1
  170. package/dist/src/core/furnace-config-order.js +0 -1
  171. package/dist/src/core/furnace-config-tokens.js +0 -1
  172. package/dist/src/core/furnace-config-validate.js +0 -1
  173. package/dist/src/core/furnace-config.d.ts +11 -0
  174. package/dist/src/core/furnace-config.js +37 -1
  175. package/dist/src/core/furnace-constants.js +0 -1
  176. package/dist/src/core/furnace-css-fragments.js +0 -1
  177. package/dist/src/core/furnace-graph-utils.js +0 -1
  178. package/dist/src/core/furnace-jsconfig.d.ts +2 -3
  179. package/dist/src/core/furnace-jsconfig.js +25 -18
  180. package/dist/src/core/furnace-marker.js +0 -1
  181. package/dist/src/core/furnace-operation.js +0 -1
  182. package/dist/src/core/furnace-refresh.js +0 -1
  183. package/dist/src/core/furnace-registration-ast.d.ts +14 -0
  184. package/dist/src/core/furnace-registration-ast.js +1 -2
  185. package/dist/src/core/furnace-registration-remove.js +0 -1
  186. package/dist/src/core/furnace-registration-validate.js +1 -5
  187. package/dist/src/core/furnace-registration.js +0 -1
  188. package/dist/src/core/furnace-rollback.js +0 -1
  189. package/dist/src/core/furnace-scanner.js +0 -1
  190. package/dist/src/core/furnace-stale-export.d.ts +59 -0
  191. package/dist/src/core/furnace-stale-export.js +123 -0
  192. package/dist/src/core/furnace-staleness.js +0 -1
  193. package/dist/src/core/furnace-state-persist.d.ts +72 -0
  194. package/dist/src/core/furnace-state-persist.js +118 -0
  195. package/dist/src/core/furnace-step-errors.d.ts +23 -0
  196. package/dist/src/core/furnace-step-errors.js +25 -0
  197. package/dist/src/core/furnace-stories.js +0 -1
  198. package/dist/src/core/furnace-validate-accessibility.js +0 -1
  199. package/dist/src/core/furnace-validate-checks.js +0 -1
  200. package/dist/src/core/furnace-validate-compatibility.js +9 -3
  201. package/dist/src/core/furnace-validate-helpers.js +0 -1
  202. package/dist/src/core/furnace-validate-registration.js +7 -7
  203. package/dist/src/core/furnace-validate-structure.d.ts +8 -0
  204. package/dist/src/core/furnace-validate-structure.js +14 -4
  205. package/dist/src/core/furnace-validate.js +0 -1
  206. package/dist/src/core/furnace-version-drift.d.ts +6 -3
  207. package/dist/src/core/furnace-version-drift.js +6 -4
  208. package/dist/src/core/git-base.js +0 -1
  209. package/dist/src/core/git-diff.js +0 -1
  210. package/dist/src/core/git-file-ops.js +0 -1
  211. package/dist/src/core/git-status.d.ts +13 -0
  212. package/dist/src/core/git-status.js +32 -2
  213. package/dist/src/core/git.d.ts +10 -1
  214. package/dist/src/core/git.js +63 -6
  215. package/dist/src/core/license-headers.js +0 -1
  216. package/dist/src/core/lint-cache.js +0 -1
  217. package/dist/src/core/lint-projection.js +0 -1
  218. package/dist/src/core/mach-build-artifacts.js +0 -1
  219. package/dist/src/core/mach-error-hints.js +0 -1
  220. package/dist/src/core/mach-known-noise-filter.d.ts +60 -0
  221. package/dist/src/core/mach-known-noise-filter.js +193 -0
  222. package/dist/src/core/mach-mozconfig.js +0 -1
  223. package/dist/src/core/mach-python.js +0 -1
  224. package/dist/src/core/mach-resource-shim.js +0 -1
  225. package/dist/src/core/mach.d.ts +8 -0
  226. package/dist/src/core/mach.js +36 -4
  227. package/dist/src/core/manifest-helpers.js +0 -1
  228. package/dist/src/core/manifest-register.js +0 -1
  229. package/dist/src/core/manifest-rules.js +0 -1
  230. package/dist/src/core/manifest-tokenizers.js +0 -1
  231. package/dist/src/core/marionette-port.d.ts +8 -0
  232. package/dist/src/core/marionette-port.js +34 -3
  233. package/dist/src/core/marionette-preflight.js +0 -1
  234. package/dist/src/core/markdown-table.js +0 -1
  235. package/dist/src/core/ownership-table.js +2 -9
  236. package/dist/src/core/parser-fallback.js +0 -1
  237. package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
  238. package/dist/src/core/patch-apply-fuzz.js +71 -34
  239. package/dist/src/core/patch-apply.d.ts +30 -0
  240. package/dist/src/core/patch-apply.js +101 -18
  241. package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
  242. package/dist/src/core/patch-artifact-normalize.js +19 -15
  243. package/dist/src/core/patch-export-coverage.js +0 -1
  244. package/dist/src/core/patch-export-metadata.js +0 -1
  245. package/dist/src/core/patch-export-update.js +2 -3
  246. package/dist/src/core/patch-export.d.ts +2 -6
  247. package/dist/src/core/patch-export.js +24 -48
  248. package/dist/src/core/patch-files.js +0 -1
  249. package/dist/src/core/patch-identifier-suggest.js +0 -1
  250. package/dist/src/core/patch-lint-checkjs.js +0 -1
  251. package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
  252. package/dist/src/core/patch-lint-cross.js +80 -3
  253. package/dist/src/core/patch-lint-css.js +0 -1
  254. package/dist/src/core/patch-lint-diff-tag.js +0 -1
  255. package/dist/src/core/patch-lint-diff.d.ts +2 -1
  256. package/dist/src/core/patch-lint-diff.js +17 -32
  257. package/dist/src/core/patch-lint-imports.js +0 -1
  258. package/dist/src/core/patch-lint-jsdoc.js +0 -1
  259. package/dist/src/core/patch-lint-observer.js +0 -1
  260. package/dist/src/core/patch-lint-ownership.js +0 -1
  261. package/dist/src/core/patch-lint-reexports.js +0 -1
  262. package/dist/src/core/patch-lint.d.ts +37 -2
  263. package/dist/src/core/patch-lint.js +47 -34
  264. package/dist/src/core/patch-lock.js +0 -1
  265. package/dist/src/core/patch-manifest-consistency.js +9 -1
  266. package/dist/src/core/patch-manifest-io.d.ts +19 -2
  267. package/dist/src/core/patch-manifest-io.js +57 -21
  268. package/dist/src/core/patch-manifest-query.js +18 -10
  269. package/dist/src/core/patch-manifest-resolve.js +0 -1
  270. package/dist/src/core/patch-manifest-validate.js +24 -1
  271. package/dist/src/core/patch-manifest.d.ts +1 -1
  272. package/dist/src/core/patch-manifest.js +1 -2
  273. package/dist/src/core/patch-parse.d.ts +59 -0
  274. package/dist/src/core/patch-parse.js +209 -92
  275. package/dist/src/core/patch-policy.js +0 -1
  276. package/dist/src/core/patch-registration-refs.js +16 -26
  277. package/dist/src/core/patch-source-metadata.js +0 -1
  278. package/dist/src/core/patch-transform.d.ts +4 -0
  279. package/dist/src/core/patch-transform.js +30 -38
  280. package/dist/src/core/rebase-session.js +0 -1
  281. package/dist/src/core/register-browser-content.js +0 -1
  282. package/dist/src/core/register-module.js +0 -1
  283. package/dist/src/core/register-result.js +0 -1
  284. package/dist/src/core/register-scaffold.js +0 -1
  285. package/dist/src/core/register-shared-css.js +0 -1
  286. package/dist/src/core/register-test-manifest.js +0 -1
  287. package/dist/src/core/register-xpcshell-test.js +0 -1
  288. package/dist/src/core/shared-ftl.js +0 -1
  289. package/dist/src/core/signal-critical.js +0 -1
  290. package/dist/src/core/smoke-patterns.js +0 -1
  291. package/dist/src/core/state-file.js +0 -1
  292. package/dist/src/core/status-classify.d.ts +16 -0
  293. package/dist/src/core/status-classify.js +24 -16
  294. package/dist/src/core/test-harness-crash.d.ts +80 -7
  295. package/dist/src/core/test-harness-crash.js +251 -15
  296. package/dist/src/core/test-harness-output.js +0 -1
  297. package/dist/src/core/test-path-scope.d.ts +21 -13
  298. package/dist/src/core/test-path-scope.js +43 -28
  299. package/dist/src/core/test-stale-check.d.ts +29 -1
  300. package/dist/src/core/test-stale-check.js +59 -1
  301. package/dist/src/core/test-stale-symlink.js +0 -1
  302. package/dist/src/core/test-xpcshell-retry.js +0 -1
  303. package/dist/src/core/token-coverage.js +0 -1
  304. package/dist/src/core/token-dark-mode.js +0 -1
  305. package/dist/src/core/token-docs.js +0 -1
  306. package/dist/src/core/token-manager.js +0 -1
  307. package/dist/src/core/token-scaffold.js +0 -1
  308. package/dist/src/core/token-variant.js +0 -1
  309. package/dist/src/core/toolchain-preflight.d.ts +31 -10
  310. package/dist/src/core/toolchain-preflight.js +97 -36
  311. package/dist/src/core/typecheck-shim.js +13 -1
  312. package/dist/src/core/typecheck.js +0 -1
  313. package/dist/src/core/wire-destroy.js +0 -1
  314. package/dist/src/core/wire-dom-fragment.js +0 -1
  315. package/dist/src/core/wire-init.js +0 -1
  316. package/dist/src/core/wire-subscript.js +0 -1
  317. package/dist/src/core/wire-targets.js +0 -1
  318. package/dist/src/core/wire-utils.js +0 -1
  319. package/dist/src/core/xpcshell-appdir.js +1 -4
  320. package/dist/src/errors/base.d.ts +29 -0
  321. package/dist/src/errors/base.js +35 -1
  322. package/dist/src/errors/build.js +0 -1
  323. package/dist/src/errors/codes.d.ts +6 -0
  324. package/dist/src/errors/codes.js +6 -1
  325. package/dist/src/errors/config.js +0 -1
  326. package/dist/src/errors/download.js +0 -1
  327. package/dist/src/errors/furnace.d.ts +11 -1
  328. package/dist/src/errors/furnace.js +6 -5
  329. package/dist/src/errors/git.js +0 -1
  330. package/dist/src/errors/patch.d.ts +17 -0
  331. package/dist/src/errors/patch.js +29 -1
  332. package/dist/src/errors/rebase.js +0 -1
  333. package/dist/src/errors/run.js +1 -1
  334. package/dist/src/index.js +0 -1
  335. package/dist/src/types/cli.js +0 -1
  336. package/dist/src/types/commands/index.d.ts +1 -1
  337. package/dist/src/types/commands/index.js +0 -1
  338. package/dist/src/types/commands/options.d.ts +40 -9
  339. package/dist/src/types/commands/options.js +0 -1
  340. package/dist/src/types/commands/patches.d.ts +37 -0
  341. package/dist/src/types/commands/patches.js +0 -1
  342. package/dist/src/types/commands/project.d.ts +11 -2
  343. package/dist/src/types/commands/project.js +0 -1
  344. package/dist/src/types/config.d.ts +32 -0
  345. package/dist/src/types/config.js +0 -1
  346. package/dist/src/types/furnace.d.ts +23 -0
  347. package/dist/src/types/furnace.js +0 -1
  348. package/dist/src/types/index.js +0 -1
  349. package/dist/src/types/typecheck.js +0 -1
  350. package/dist/src/utils/elapsed.js +0 -1
  351. package/dist/src/utils/errors.js +0 -1
  352. package/dist/src/utils/fs.js +37 -5
  353. package/dist/src/utils/logger.d.ts +7 -0
  354. package/dist/src/utils/logger.js +85 -3
  355. package/dist/src/utils/options.d.ts +15 -0
  356. package/dist/src/utils/options.js +25 -1
  357. package/dist/src/utils/package-root.js +11 -4
  358. package/dist/src/utils/parse.js +0 -1
  359. package/dist/src/utils/paths.js +0 -1
  360. package/dist/src/utils/platform.js +0 -1
  361. package/dist/src/utils/process-group.d.ts +33 -0
  362. package/dist/src/utils/process-group.js +161 -0
  363. package/dist/src/utils/process.d.ts +48 -4
  364. package/dist/src/utils/process.js +258 -59
  365. package/dist/src/utils/regex.d.ts +13 -3
  366. package/dist/src/utils/regex.js +53 -4
  367. package/dist/src/utils/validation.d.ts +0 -12
  368. package/dist/src/utils/validation.js +13 -26
  369. package/package.json +9 -8
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { warn } from '../utils/logger.js';
1
3
  import { ensureGit, git } from './git-base.js';
2
4
  /**
3
5
  * Parses NUL-delimited porcelain status output.
@@ -43,22 +45,51 @@ export async function getWorkingTreeStatus(repoDir) {
43
45
  await ensureGit();
44
46
  return parsePorcelainStatus(await git(['status', '--porcelain=v1', '-z'], repoDir));
45
47
  }
48
+ /** Default per-directory cap for untracked-directory expansion. */
49
+ const DEFAULT_MAX_UNTRACKED_FILES_PER_DIR = 5000;
50
+ /**
51
+ * Resolves the per-directory expansion cap, honoring the same
52
+ * FIREFORGE_MAX_UNTRACKED_FILES override the status command documents.
53
+ */
54
+ export function resolveMaxUntrackedFilesPerDir() {
55
+ const raw = process.env['FIREFORGE_MAX_UNTRACKED_FILES'];
56
+ if (raw === undefined || raw.length === 0)
57
+ return DEFAULT_MAX_UNTRACKED_FILES_PER_DIR;
58
+ const parsed = Number.parseInt(raw, 10);
59
+ if (!Number.isFinite(parsed) || parsed <= 0) {
60
+ warn(`Ignoring FIREFORGE_MAX_UNTRACKED_FILES="${raw}" — expected a positive integer. Falling back to ${String(DEFAULT_MAX_UNTRACKED_FILES_PER_DIR)}.`);
61
+ return DEFAULT_MAX_UNTRACKED_FILES_PER_DIR;
62
+ }
63
+ return parsed;
64
+ }
46
65
  /**
47
66
  * Expands collapsed untracked directory entries into individual file entries.
48
67
  * Git status may report "?? dir/" instead of listing each file underneath.
68
+ *
69
+ * Expansion is capped per directory (FIREFORGE_MAX_UNTRACKED_FILES, default
70
+ * 5000) with a warning on overflow. The cap used to exist only in the
71
+ * status command's private expander; this shared one — used by reset,
72
+ * discard, verify, lint, export-all — was unbounded, so e.g. `reset
73
+ * --dry-run` after an interrupted download (unborn HEAD, the entire ~300k
74
+ * file tree untracked) enumerated and printed every file.
75
+ *
49
76
  * @param repoDir - Repository directory
50
77
  * @param entries - Parsed status entries
51
78
  * @returns Status entries with untracked directories expanded to individual files
52
79
  */
53
80
  export async function expandUntrackedDirectoryEntries(repoDir, entries) {
54
81
  const expanded = [];
82
+ const maxPerDir = resolveMaxUntrackedFilesPerDir();
55
83
  for (const entry of entries) {
56
84
  if (!entry.isUntracked || !entry.file.endsWith('/')) {
57
85
  expanded.push(entry);
58
86
  continue;
59
87
  }
60
88
  const individualFiles = await getUntrackedFilesInDir(repoDir, entry.file);
61
- for (const file of individualFiles) {
89
+ if (individualFiles.length > maxPerDir) {
90
+ warn(`Untracked directory ${entry.file} contains ${String(individualFiles.length)} files — only the first ${String(maxPerDir)} are listed. Add a .gitignore entry or clean the directory.`);
91
+ }
92
+ for (const file of individualFiles.slice(0, maxPerDir)) {
62
93
  expanded.push({
63
94
  status: '??',
64
95
  indexStatus: '?',
@@ -125,4 +156,3 @@ export async function getDirtyFiles(repoDir, files) {
125
156
  const untracked = untrackedOutput.split('\n').filter((line) => line.trim().length > 0);
126
157
  return [...new Set([...tracked, ...untracked])].sort();
127
158
  }
128
- //# sourceMappingURL=git-status.js.map
@@ -53,12 +53,21 @@ export declare function applyPatch(patchPath: string, repoDir: string, options?:
53
53
  * Applies a patch idempotently using reverse-forward pattern.
54
54
  * First tries to reverse the patch (in case it's already applied),
55
55
  * then applies it forward.
56
+ *
56
57
  * @param patchPath - Path to the patch file
57
58
  * @param repoDir - Repository directory
58
- * @param options - Application options
59
+ * @param options.reject - Fall back to `git apply --reject`
60
+ * @param options.protectedFiles - Files that must NOT be reset to HEAD by
61
+ * the recovery step. Callers applying a patch QUEUE pass the files
62
+ * already touched by previously applied patches in the same run: two
63
+ * overlapping patches (an `--allow-overlap` queue) share files, and the
64
+ * blanket `checkout HEAD` used to wipe the earlier patch's changes from
65
+ * the shared file before applying the later one — leaving the engine in
66
+ * a hybrid state the summary never described.
59
67
  */
60
68
  export declare function applyPatchIdempotent(patchPath: string, repoDir: string, options?: {
61
69
  reject?: boolean;
70
+ protectedFiles?: ReadonlySet<string>;
62
71
  }): Promise<void>;
63
72
  /**
64
73
  * Reverses a previously applied patch.
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { readdir, stat } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
+ import { ExecTimeoutError } from '../errors/base.js';
4
5
  import { GitError, GitIndexingTimeoutError, GitIndexLockError, PatchApplyError, } from '../errors/git.js';
5
6
  import { elapsedSince } from '../utils/elapsed.js';
6
7
  import { toError } from '../utils/errors.js';
@@ -8,7 +9,7 @@ import { pathExists, pathExistsStrict, removeFile } from '../utils/fs.js';
8
9
  import { verbose } from '../utils/logger.js';
9
10
  import { exec } from '../utils/process.js';
10
11
  import { ensureFirefoxIgnorefileCompatibility } from './firefox-ignorefile.js';
11
- import { configureGitPerformance, ensureGit, git, GIT_ADD_CHUNK_TIMEOUT_ENV_VAR, GIT_ADD_CHUNK_TIMEOUT_MS, GIT_ADD_TIMEOUT_MS, } from './git-base.js';
12
+ import { chunkPathspecs, configureGitPerformance, ensureGit, git, GIT_ADD_CHUNK_TIMEOUT_ENV_VAR, GIT_ADD_CHUNK_TIMEOUT_MS, GIT_ADD_TIMEOUT_MS, } from './git-base.js';
12
13
  import { getWorkingTreeStatus } from './git-status.js';
13
14
  // ── Functions that remain in this file ──
14
15
  /**
@@ -47,6 +48,11 @@ const GIT_ADD_ENV = { GIT_INDEX_THREADS: '0' };
47
48
  * detection here needs to recognise that shape too.
48
49
  */
49
50
  function isTimeoutError(error) {
51
+ // `exec` now rejects with a typed ExecTimeoutError when its `timeout`
52
+ // option fires (the raw AbortError check below is kept for defence in
53
+ // depth against other abort paths).
54
+ if (error instanceof ExecTimeoutError)
55
+ return true;
50
56
  if (error instanceof Error && error.name === 'AbortError')
51
57
  return true;
52
58
  if (!(error instanceof GitError))
@@ -410,9 +416,17 @@ export async function applyPatch(patchPath, repoDir, options = {}) {
410
416
  * Applies a patch idempotently using reverse-forward pattern.
411
417
  * First tries to reverse the patch (in case it's already applied),
412
418
  * then applies it forward.
419
+ *
413
420
  * @param patchPath - Path to the patch file
414
421
  * @param repoDir - Repository directory
415
- * @param options - Application options
422
+ * @param options.reject - Fall back to `git apply --reject`
423
+ * @param options.protectedFiles - Files that must NOT be reset to HEAD by
424
+ * the recovery step. Callers applying a patch QUEUE pass the files
425
+ * already touched by previously applied patches in the same run: two
426
+ * overlapping patches (an `--allow-overlap` queue) share files, and the
427
+ * blanket `checkout HEAD` used to wipe the earlier patch's changes from
428
+ * the shared file before applying the later one — leaving the engine in
429
+ * a hybrid state the summary never described.
416
430
  */
417
431
  export async function applyPatchIdempotent(patchPath, repoDir, options = {}) {
418
432
  await ensureGit();
@@ -428,15 +442,59 @@ export async function applyPatchIdempotent(patchPath, repoDir, options = {}) {
428
442
  const touchedFiles = listResult.stdout
429
443
  .split('\n')
430
444
  .map((line) => line.split('\t')[2])
431
- .filter((f) => !!f);
445
+ .filter((f) => !!f)
446
+ .filter((f) => !(options.protectedFiles?.has(f) ?? false));
432
447
  if (touchedFiles.length > 0) {
433
- // Restore only the files the patch touches
434
- await exec('git', ['checkout', 'HEAD', '--', ...touchedFiles], { cwd: repoDir });
448
+ await restoreFilesToHead(repoDir, patchPath, touchedFiles);
435
449
  }
436
450
  }
437
451
  // Apply forward
438
452
  await applyPatch(patchPath, repoDir, options);
439
453
  }
454
+ /**
455
+ * Restores `files` to their HEAD state, deleting files the patch would
456
+ * CREATE (present on disk from a partial apply but absent in HEAD — for
457
+ * those, `git checkout HEAD --` fails, and the historical code ignored
458
+ * that failure entirely, leaving stray files that made the subsequent
459
+ * forward apply die with "already exists" and no hint why).
460
+ *
461
+ * Every git invocation is chunked via {@link chunkPathspecs} so a very
462
+ * large patch cannot hit `E2BIG`, and every exit code is checked — a
463
+ * silent failed restore is precisely the confusing state this recovery
464
+ * step exists to prevent.
465
+ */
466
+ async function restoreFilesToHead(repoDir, patchPath, files) {
467
+ // Partition into tracked-in-HEAD (restore via checkout) and absent-in-HEAD
468
+ // (patch-created leftovers: delete from the worktree).
469
+ const trackedInHead = new Set();
470
+ for (const chunk of chunkPathspecs(files)) {
471
+ const lsTree = await exec('git', ['ls-tree', '-r', '--name-only', 'HEAD', '--', ...chunk], {
472
+ cwd: repoDir,
473
+ });
474
+ if (lsTree.exitCode !== 0) {
475
+ throw new PatchApplyError(patchPath, new Error(`git ls-tree failed while preparing recovery: ${lsTree.stderr.trim()}`));
476
+ }
477
+ for (const line of lsTree.stdout.split('\n')) {
478
+ if (line.length > 0)
479
+ trackedInHead.add(line);
480
+ }
481
+ }
482
+ const toCheckout = files.filter((f) => trackedInHead.has(f));
483
+ const toDelete = files.filter((f) => !trackedInHead.has(f));
484
+ for (const chunk of chunkPathspecs(toCheckout)) {
485
+ const checkout = await exec('git', ['checkout', 'HEAD', '--', ...chunk], { cwd: repoDir });
486
+ if (checkout.exitCode !== 0) {
487
+ throw new PatchApplyError(patchPath, new Error(`Could not restore patch-touched files to HEAD before applying: ${checkout.stderr.trim()}`));
488
+ }
489
+ }
490
+ for (const file of toDelete) {
491
+ const target = join(repoDir, file);
492
+ if (await pathExists(target)) {
493
+ await removeFile(target);
494
+ verbose(`Removed stray patch-created file before apply: ${file}`);
495
+ }
496
+ }
497
+ }
440
498
  /**
441
499
  * Reverses a previously applied patch.
442
500
  * @param patchPath - Path to the patch file
@@ -524,4 +582,3 @@ export async function getStatusWithCodes(repoDir) {
524
582
  file: entry.file,
525
583
  }));
526
584
  }
527
- //# sourceMappingURL=git.js.map
@@ -193,4 +193,3 @@ export async function addLicenseHeaderToFile(filePath, license, style) {
193
193
  await writeText(filePath, header + '\n' + content);
194
194
  return true;
195
195
  }
196
- //# sourceMappingURL=license-headers.js.map
@@ -174,4 +174,3 @@ export function setCachedPerPatchLintIssues(cache, patchFilename, key, issues) {
174
174
  updatedAt: new Date().toISOString(),
175
175
  };
176
176
  }
177
- //# sourceMappingURL=lint-cache.js.map
@@ -41,4 +41,3 @@ export function computeProjectedLintRegressions(baseline, projected) {
41
41
  }
42
42
  return regressions;
43
43
  }
44
- //# sourceMappingURL=lint-projection.js.map
@@ -285,4 +285,3 @@ export async function attemptMozinfoRewrite(engineDir, objDir) {
285
285
  ...(newMozconfig ? { newMozconfig } : {}),
286
286
  };
287
287
  }
288
- //# sourceMappingURL=mach-build-artifacts.js.map
@@ -120,4 +120,3 @@ export function explainMachError(stderr) {
120
120
  }
121
121
  return hits;
122
122
  }
123
- //# sourceMappingURL=mach-error-hints.js.map
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Terminal-echo filter for the KNOWN upstream mozsystemmonitor teardown
3
+ * traceback (0.37.0 item 8).
4
+ *
5
+ * Every headless test run against the 153-beta engine ends with an
6
+ * `AttributeError: 'SystemResourceMonitor' object has no attribute
7
+ * 'stop_time'` traceback at harness teardown — upstream noise that sits
8
+ * exactly where a reader looks for the failure summary. 0.36.0 already made
9
+ * real failure lines beat this traceback in CLASSIFICATION; this filter
10
+ * closes the PRESENTATION gap by collapsing the echoed traceback to one
11
+ * labeled line.
12
+ *
13
+ * Scope is deliberately narrow:
14
+ * - Only the terminal ECHO is filtered. The captured stdout/stderr strings
15
+ * stay raw — the classifier (`test-harness-crash.ts`) depends on the
16
+ * raw traceback for its green-summary override and secondary-noise
17
+ * detection.
18
+ * - Only the exact documented incident is collapsed, and every condition
19
+ * must hold: an `AttributeError` on `SystemResourceMonitor` naming one
20
+ * of the two known attributes (`stop_time`, `poll_interval` — the
21
+ * 0.34.0 guard family), AND a `mozsystemmonitor/resourcemonitor.py`
22
+ * stack frame, AND a previously-seen SUITE_END shutdown marker (shared
23
+ * across the run's stdout/stderr filter instances — the marker usually
24
+ * lands on stdout while the traceback lands on stderr). A novel
25
+ * attribute, a novel exception type in resourcemonitor.py, or a
26
+ * pre-shutdown occurrence is echoed verbatim, always.
27
+ * - The hold buffer is bounded; on overflow the block is flushed verbatim
28
+ * and the filter returns to pass-through, so output is never lost.
29
+ */
30
+ /**
31
+ * Shared across the stdout and stderr filter instances of ONE mach run:
32
+ * SUITE_END typically arrives on stdout while the teardown traceback lands
33
+ * on stderr, so the shutdown-seen flag must be visible to both. The two
34
+ * pipes are independent, so a traceback can theoretically beat the marker
35
+ * through — the block then prints verbatim, the correct failure direction.
36
+ */
37
+ export interface TeardownNoiseContext {
38
+ shutdownSeen: boolean;
39
+ }
40
+ /** Fresh per-run context — hand the same instance to both stream filters. */
41
+ export declare function createTeardownNoiseContext(): TeardownNoiseContext;
42
+ /** One line replaces the whole recognized traceback block in the echo. */
43
+ export declare const KNOWN_TEARDOWN_NOISE_ANNOTATION: string;
44
+ /** Chunk-safe, line-buffered echo filter. See module doc. */
45
+ export interface KnownTeardownNoiseFilter {
46
+ /** Feed a raw chunk; returns the text to echo now (possibly empty). */
47
+ transform(chunk: string): string;
48
+ /** Flush any buffered residue verbatim (call after the stream closes). */
49
+ flush(): string;
50
+ }
51
+ /**
52
+ * Creates a stateful filter for one output stream. Complete lines outside a
53
+ * traceback pass straight through (only the trailing partial line is held
54
+ * back); a `Traceback (most recent call last)` header switches to hold mode
55
+ * until the block ends, then either the one-line annotation (recognized
56
+ * signature after a seen shutdown marker) or the verbatim block (anything
57
+ * else) is emitted. Pass the run's shared {@link TeardownNoiseContext} so
58
+ * both stream filters see the same shutdown flag.
59
+ */
60
+ export declare function createKnownTeardownNoiseFilter(context?: TeardownNoiseContext): KnownTeardownNoiseFilter;
@@ -0,0 +1,193 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Terminal-echo filter for the KNOWN upstream mozsystemmonitor teardown
4
+ * traceback (0.37.0 item 8).
5
+ *
6
+ * Every headless test run against the 153-beta engine ends with an
7
+ * `AttributeError: 'SystemResourceMonitor' object has no attribute
8
+ * 'stop_time'` traceback at harness teardown — upstream noise that sits
9
+ * exactly where a reader looks for the failure summary. 0.36.0 already made
10
+ * real failure lines beat this traceback in CLASSIFICATION; this filter
11
+ * closes the PRESENTATION gap by collapsing the echoed traceback to one
12
+ * labeled line.
13
+ *
14
+ * Scope is deliberately narrow:
15
+ * - Only the terminal ECHO is filtered. The captured stdout/stderr strings
16
+ * stay raw — the classifier (`test-harness-crash.ts`) depends on the
17
+ * raw traceback for its green-summary override and secondary-noise
18
+ * detection.
19
+ * - Only the exact documented incident is collapsed, and every condition
20
+ * must hold: an `AttributeError` on `SystemResourceMonitor` naming one
21
+ * of the two known attributes (`stop_time`, `poll_interval` — the
22
+ * 0.34.0 guard family), AND a `mozsystemmonitor/resourcemonitor.py`
23
+ * stack frame, AND a previously-seen SUITE_END shutdown marker (shared
24
+ * across the run's stdout/stderr filter instances — the marker usually
25
+ * lands on stdout while the traceback lands on stderr). A novel
26
+ * attribute, a novel exception type in resourcemonitor.py, or a
27
+ * pre-shutdown occurrence is echoed verbatim, always.
28
+ * - The hold buffer is bounded; on overflow the block is flushed verbatim
29
+ * and the filter returns to pass-through, so output is never lost.
30
+ */
31
+ const TRACEBACK_HEADER_PATTERN = /^Traceback \(most recent call last\)/;
32
+ const CHAINED_EXCEPTION_CONNECTOR_PATTERN = /^(?:During handling of the above exception, another exception occurred:|The above exception was the direct cause of the following exception:)\s*$/;
33
+ /**
34
+ * Closed allowlist of the documented teardown family's attributes:
35
+ * `stop_time` (the 0.37.0 item-8 incident) and `poll_interval` (the same
36
+ * mozsystemmonitor init failure the 0.34.0 resource-guard family covers —
37
+ * `test-harness-crash.ts` already classifies it as recognized noise). Any
38
+ * other missing attribute is a NEW upstream defect and must print verbatim.
39
+ */
40
+ const KNOWN_TEARDOWN_ATTRIBUTE_ERROR_PATTERN = /AttributeError: 'SystemResourceMonitor' object has no attribute '(?:stop_time|poll_interval)'/;
41
+ const RESOURCEMONITOR_FRAME_PATTERN = /mozsystemmonitor[/\\]resourcemonitor\.py/;
42
+ /**
43
+ * Shutdown marker: the harness's SUITE_END line. Matches the shape
44
+ * `test-harness-crash.ts` keys its summary parsing on (module-private
45
+ * there; duplicated here with this cross-reference rather than exported).
46
+ */
47
+ const SHUTDOWN_MARKER_PATTERN = /\bSUITE_END\b/;
48
+ /** Whole-block recognition — every signal must hold (see module doc). */
49
+ function isRecognizedTeardownNoise(block) {
50
+ return (KNOWN_TEARDOWN_ATTRIBUTE_ERROR_PATTERN.test(block) && RESOURCEMONITOR_FRAME_PATTERN.test(block));
51
+ }
52
+ /** Fresh per-run context — hand the same instance to both stream filters. */
53
+ export function createTeardownNoiseContext() {
54
+ return { shutdownSeen: false };
55
+ }
56
+ /** One line replaces the whole recognized traceback block in the echo. */
57
+ export const KNOWN_TEARDOWN_NOISE_ANNOTATION = '[FireForge] Known upstream mozsystemmonitor teardown noise (SystemResourceMonitor ' +
58
+ 'AttributeError at harness shutdown) — not a test failure. See FireForge docs.\n';
59
+ /** Bounds on the held traceback block before flushing verbatim. */
60
+ const HOLD_LINE_LIMIT = 100;
61
+ const HOLD_BYTE_LIMIT = 16 * 1024;
62
+ /**
63
+ * Creates a stateful filter for one output stream. Complete lines outside a
64
+ * traceback pass straight through (only the trailing partial line is held
65
+ * back); a `Traceback (most recent call last)` header switches to hold mode
66
+ * until the block ends, then either the one-line annotation (recognized
67
+ * signature after a seen shutdown marker) or the verbatim block (anything
68
+ * else) is emitted. Pass the run's shared {@link TeardownNoiseContext} so
69
+ * both stream filters see the same shutdown flag.
70
+ */
71
+ export function createKnownTeardownNoiseFilter(context = createTeardownNoiseContext()) {
72
+ /** Partial (no trailing newline yet) input line. */
73
+ let partial = '';
74
+ /** Held traceback lines (each WITH its newline) while in hold mode. */
75
+ let held = [];
76
+ let heldBytes = 0;
77
+ /**
78
+ * State machine:
79
+ * 'pass' — outside any traceback; lines echo through.
80
+ * 'inside' — between a Traceback header and its closing exception line.
81
+ * 'closed' — saw the closing `SomeError: …` line; still holding in
82
+ * case a chained-exception connector continues the block
83
+ * (the real fixture chains two tracebacks — the whole
84
+ * chain must be evaluated as ONE block or the second half
85
+ * would print raw after the annotation).
86
+ */
87
+ let state = 'pass';
88
+ const resetHold = () => {
89
+ const block = held.join('');
90
+ held = [];
91
+ heldBytes = 0;
92
+ state = 'pass';
93
+ return block;
94
+ };
95
+ const releaseHeld = () => {
96
+ const block = resetHold();
97
+ if (block.length === 0)
98
+ return '';
99
+ return context.shutdownSeen && isRecognizedTeardownNoise(block)
100
+ ? KNOWN_TEARDOWN_NOISE_ANNOTATION
101
+ : block;
102
+ };
103
+ const hold = (line) => {
104
+ held.push(line);
105
+ heldBytes += line.length;
106
+ if (held.length > HOLD_LINE_LIMIT || heldBytes > HOLD_BYTE_LIMIT) {
107
+ // Pathological block: flush verbatim rather than risk withholding
108
+ // output; do not attempt recognition on oversized blocks.
109
+ return { out: resetHold(), overflowed: true };
110
+ }
111
+ return { out: '', overflowed: false };
112
+ };
113
+ const processLine = (line) => {
114
+ const content = line.replace(/\r?\n$/, '');
115
+ if (state === 'pass') {
116
+ if (TRACEBACK_HEADER_PATTERN.test(content)) {
117
+ state = 'inside';
118
+ return hold(line).out;
119
+ }
120
+ if (SHUTDOWN_MARKER_PATTERN.test(content))
121
+ context.shutdownSeen = true;
122
+ return line;
123
+ }
124
+ if (state === 'inside') {
125
+ const isContinuation = content.trim().length === 0 ||
126
+ /^[ \t]/.test(content) ||
127
+ TRACEBACK_HEADER_PATTERN.test(content);
128
+ const { out, overflowed } = hold(line);
129
+ // The first unindented line is the closing `SomeError: …` line; keep
130
+ // holding in case a chained connector extends the block.
131
+ if (!overflowed && !isContinuation) {
132
+ state = 'closed';
133
+ }
134
+ return out;
135
+ }
136
+ // state === 'closed': only a blank line, a chained-exception connector,
137
+ // or another Traceback header continues the block.
138
+ if (content.trim().length === 0 || CHAINED_EXCEPTION_CONNECTOR_PATTERN.test(content)) {
139
+ return hold(line).out;
140
+ }
141
+ if (TRACEBACK_HEADER_PATTERN.test(content)) {
142
+ const { out, overflowed } = hold(line);
143
+ if (!overflowed)
144
+ state = 'inside';
145
+ return out;
146
+ }
147
+ return releaseHeld() + processLineInPass(line);
148
+ };
149
+ // Re-dispatch a line through pass-mode handling after a block release —
150
+ // the line that ended the block may itself start a new traceback.
151
+ const processLineInPass = (line) => {
152
+ const content = line.replace(/\r?\n$/, '');
153
+ if (TRACEBACK_HEADER_PATTERN.test(content)) {
154
+ state = 'inside';
155
+ return hold(line).out;
156
+ }
157
+ if (SHUTDOWN_MARKER_PATTERN.test(content))
158
+ context.shutdownSeen = true;
159
+ return line;
160
+ };
161
+ return {
162
+ transform(chunk) {
163
+ let out = '';
164
+ let buffer = partial + chunk;
165
+ partial = '';
166
+ let newlineIndex = buffer.indexOf('\n');
167
+ while (newlineIndex !== -1) {
168
+ const line = buffer.slice(0, newlineIndex + 1);
169
+ buffer = buffer.slice(newlineIndex + 1);
170
+ out += processLine(line);
171
+ newlineIndex = buffer.indexOf('\n');
172
+ }
173
+ partial = buffer;
174
+ return out;
175
+ },
176
+ flush() {
177
+ let out = '';
178
+ if (state !== 'pass') {
179
+ // A stream that closes mid-block: fold the trailing partial line in
180
+ // and evaluate — the final exception line is often the last thing
181
+ // printed, without a trailing newline.
182
+ if (partial.length > 0) {
183
+ held.push(partial);
184
+ partial = '';
185
+ }
186
+ out += releaseHeld();
187
+ }
188
+ out += partial;
189
+ partial = '';
190
+ return out;
191
+ },
192
+ };
193
+ }
@@ -113,4 +113,3 @@ export async function generateMozconfig(configsDir, engineDir, config) {
113
113
  // the single source of truth for both the render and the sanity-check.
114
114
  await assertBrandingMozconfigAgreement(engineDir, outputPath, config);
115
115
  }
116
- //# sourceMappingURL=mach-mozconfig.js.map
@@ -123,4 +123,3 @@ export async function getPython(engineDir) {
123
123
  }
124
124
  return pythonCache.python;
125
125
  }
126
- //# sourceMappingURL=mach-python.js.map
@@ -549,4 +549,3 @@ export async function installMachResourceGuard(engineDir) {
549
549
  const env = machResourceShimEnv(await ensureSitecustomizeFallback());
550
550
  return { env, sitePackagesDirs };
551
551
  }
552
- //# sourceMappingURL=mach-resource-shim.js.map
@@ -17,6 +17,14 @@ export interface MachOptions {
17
17
  env?: Record<string, string>;
18
18
  /** Whether to inherit stdio (show output directly) */
19
19
  inherit?: boolean;
20
+ /**
21
+ * Collapse the KNOWN mozsystemmonitor teardown traceback to one labeled
22
+ * line in the terminal ECHO (capture-only option). The captured
23
+ * stdout/stderr stay raw — the harness classifier depends on the raw
24
+ * traceback. Unrecognized tracebacks always echo verbatim. Opted into by
25
+ * the test dispatchers only.
26
+ */
27
+ annotateKnownTeardownNoise?: boolean;
20
28
  }
21
29
  /**
22
30
  * Result of running a mach command while capturing streamed output.
@@ -7,6 +7,7 @@ import { exec, execInherit, execInheritCapture, execSmokeRun, execStream, } from
7
7
  import { createSiblingLockPath, withFileLock } from './file-lock.js';
8
8
  import { ensureFirefoxIgnorefileCompatibility } from './firefox-ignorefile.js';
9
9
  import { explainMachError } from './mach-error-hints.js';
10
+ import { createKnownTeardownNoiseFilter, createTeardownNoiseContext, } from './mach-known-noise-filter.js';
10
11
  import { getPython } from './mach-python.js';
11
12
  import { installMachResourceGuard } from './mach-resource-shim.js';
12
13
  import { detectHarnessCrashSignature } from './test-harness-crash.js';
@@ -42,7 +43,12 @@ export async function runMach(args, engineDir, options = {}) {
42
43
  ...(options.env ? { env: options.env } : {}),
43
44
  };
44
45
  if (options.inherit) {
45
- return execInherit(python, [machPath, ...args], execOptions);
46
+ // Group-reap every long-lived inherited mach dispatch (bootstrap, run,
47
+ // watch): a mach dying at startup must not strand multiprocessing
48
+ // workers. Short-lived metadata queries below stay on plain exec() —
49
+ // adding groups there would touch every non-mach exec consumer's path
50
+ // for no reaping benefit.
51
+ return execInherit(python, [machPath, ...args], { ...execOptions, processGroup: true });
46
52
  }
47
53
  const result = await exec(python, [machPath, ...args], execOptions);
48
54
  return result.exitCode;
@@ -67,24 +73,47 @@ export async function runMachCapture(args, engineDir, options = {}) {
67
73
  const machPath = join(engineDir, 'mach');
68
74
  let stdout = '';
69
75
  let stderr = '';
76
+ // Echo-path filters only: the captured strings above accumulate RAW so the
77
+ // classifier still sees the full teardown traceback. When the option is
78
+ // off, the filters are absent and echo is a straight write (unchanged).
79
+ // One shutdown context per run, shared by both stream filters: the
80
+ // SUITE_END marker usually arrives on stdout while the teardown traceback
81
+ // lands on stderr, so the shutdown-seen flag must span the pair.
82
+ const noiseContext = options.annotateKnownTeardownNoise === true ? createTeardownNoiseContext() : undefined;
83
+ const stdoutFilter = noiseContext && createKnownTeardownNoiseFilter(noiseContext);
84
+ const stderrFilter = noiseContext && createKnownTeardownNoiseFilter(noiseContext);
70
85
  const exitCode = await execStream(python, [machPath, ...args], {
71
86
  cwd: engineDir,
72
87
  ...(options.env ? { env: options.env } : {}),
88
+ // Every capture dispatch (test suites, protected builds, package,
89
+ // storybook) runs as a process-group leader and is group-reaped on
90
+ // exit/abort — see ExecOptions.processGroup (0.37.0 item 9a).
91
+ processGroup: true,
73
92
  onStdout: (data) => {
74
93
  stdout += data;
75
94
  if (stdout.length > CAPTURE_TAIL_LIMIT) {
76
95
  stdout = stdout.slice(-CAPTURE_TAIL_LIMIT);
77
96
  }
78
- process.stdout.write(data);
97
+ process.stdout.write(stdoutFilter ? stdoutFilter.transform(data) : data);
79
98
  },
80
99
  onStderr: (data) => {
81
100
  stderr += data;
82
101
  if (stderr.length > CAPTURE_TAIL_LIMIT) {
83
102
  stderr = stderr.slice(-CAPTURE_TAIL_LIMIT);
84
103
  }
85
- process.stderr.write(data);
104
+ process.stderr.write(stderrFilter ? stderrFilter.transform(data) : data);
86
105
  },
87
106
  });
107
+ if (stdoutFilter) {
108
+ const residue = stdoutFilter.flush();
109
+ if (residue.length > 0)
110
+ process.stdout.write(residue);
111
+ }
112
+ if (stderrFilter) {
113
+ const residue = stderrFilter.flush();
114
+ if (residue.length > 0)
115
+ process.stderr.write(residue);
116
+ }
88
117
  return { stdout, stderr, exitCode };
89
118
  }
90
119
  /**
@@ -99,6 +128,7 @@ export async function runMachInheritCapture(args, engineDir, options = {}) {
99
128
  return execInheritCapture(python, [machPath, ...args], {
100
129
  cwd: engineDir,
101
130
  ...(options.env ? { env: options.env } : {}),
131
+ processGroup: true,
102
132
  });
103
133
  }
104
134
  /**
@@ -363,6 +393,7 @@ export async function testWithOutput(engineDir, testPaths = [], args = [], env)
363
393
  const guard = await installMachResourceGuard(engineDir);
364
394
  return runMachCapture(['test', ...testPaths, ...args], engineDir, {
365
395
  env: { ...guard.env, ...env },
396
+ annotateKnownTeardownNoise: true,
366
397
  });
367
398
  }
368
399
  /**
@@ -379,6 +410,7 @@ export async function xpcshellTestWithOutput(engineDir, testPaths = [], args = [
379
410
  const guard = await installMachResourceGuard(engineDir);
380
411
  return runMachCapture(['xpcshell-test', ...testPaths, ...args], engineDir, {
381
412
  env: { ...guard.env, ...env },
413
+ annotateKnownTeardownNoise: true,
382
414
  });
383
415
  }
384
416
  /**
@@ -390,6 +422,6 @@ export async function mochitestWithOutput(engineDir, testPaths = [], args = [],
390
422
  const guard = await installMachResourceGuard(engineDir);
391
423
  return runMachCapture(['mochitest', ...testPaths, ...args], engineDir, {
392
424
  env: { ...guard.env, ...env },
425
+ annotateKnownTeardownNoise: true,
393
426
  });
394
427
  }
395
- //# sourceMappingURL=mach.js.map
@@ -118,4 +118,3 @@ export function findAlphabeticalMozBuildPosition(tokens, newKey) {
118
118
  }
119
119
  return { insertIndex: insertLineIndex, previousEntry };
120
120
  }
121
- //# sourceMappingURL=manifest-helpers.js.map
@@ -64,4 +64,3 @@ export async function registerToolkitWidget(engineDir, tagName, fileName, dryRun
64
64
  }
65
65
  return { manifest, entry, previousEntry, skipped: false };
66
66
  }
67
- //# sourceMappingURL=manifest-register.js.map
@@ -250,4 +250,3 @@ export async function registerFile(root, filePath, dryRun = false, after, option
250
250
  }
251
251
  throw new InvalidArgumentError(buildUnknownPatternMessage(normalizedPath, config.binaryName), 'path');
252
252
  }
253
- //# sourceMappingURL=manifest-rules.js.map
@@ -109,4 +109,3 @@ export function tokenizeMozBuildList(lines, listPattern) {
109
109
  return null;
110
110
  return { tokens, startLine, endLine };
111
111
  }
112
- //# sourceMappingURL=manifest-tokenizers.js.map