@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,16 +1,30 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { spawn } from 'node:child_process';
3
3
  import { constants as osConstants } from 'node:os';
4
+ import { StringDecoder } from 'node:string_decoder';
5
+ import { ExecTimeoutError } from '../errors/base.js';
6
+ import { killProcessTree, sweepProcessGroup } from './process-group.js';
4
7
  // 50 MB cap per stream to prevent OOM — large toolchain builds (e.g. Firefox, Chromium)
5
8
  // can easily blow past this, so we truncate rather than let the buffer grow unbounded.
9
+ // Callers for whom truncation is unacceptable (e.g. the archive-safety preflight
10
+ // scanning a full `tar -tvf` listing) must check `wasTruncated` and fail hard.
6
11
  const MAX_OUTPUT_SIZE = 50 * 1024 * 1024;
7
12
  function createStreamCollector(mirror) {
8
13
  const chunks = [];
9
14
  let totalLength = 0;
10
15
  let truncated = false;
16
+ // A per-stream StringDecoder keeps multibyte UTF-8 characters split across
17
+ // pipe chunk boundaries intact. Per-chunk `Buffer.toString()` turned a
18
+ // straddling character into two U+FFFD replacement chars, which silently
19
+ // broke regex matching over captured output (smoke-run allowlists, mach
20
+ // error hints) whenever a non-ASCII path or localized message landed on a
21
+ // 64 KB chunk boundary.
22
+ const decoder = new StringDecoder('utf8');
11
23
  return {
12
24
  onData: (data) => {
13
- const chunk = data.toString();
25
+ const chunk = decoder.write(data);
26
+ if (chunk.length === 0)
27
+ return;
14
28
  mirror?.write(chunk);
15
29
  if (truncated)
16
30
  return;
@@ -27,6 +41,7 @@ function createStreamCollector(mirror) {
27
41
  }
28
42
  },
29
43
  getText: () => chunks.join(''),
44
+ wasTruncated: () => truncated,
30
45
  };
31
46
  }
32
47
  function buildSignalFromTimeout(timeout) {
@@ -34,6 +49,19 @@ function buildSignalFromTimeout(timeout) {
34
49
  return undefined;
35
50
  return AbortSignal.timeout(timeout);
36
51
  }
52
+ /**
53
+ * Maps a child-process `error` event to the rejection the caller sees.
54
+ * When the caller set a `timeout` and the error is the AbortError produced
55
+ * by `AbortSignal.timeout` firing, rejects with a typed
56
+ * {@link ExecTimeoutError} naming the command and budget instead of Node's
57
+ * opaque `AbortError: The operation was aborted`.
58
+ */
59
+ function toExecRejection(error, command, args, timeout) {
60
+ if (timeout !== undefined && error.name === 'AbortError') {
61
+ return new ExecTimeoutError(command, args, timeout, error);
62
+ }
63
+ return error;
64
+ }
37
65
  function exitCodeFromClose(code, signal) {
38
66
  if (code !== null) {
39
67
  return code;
@@ -66,13 +94,15 @@ export async function exec(command, args, options = {}) {
66
94
  child.stdout.on('data', out.onData);
67
95
  child.stderr.on('data', err.onData);
68
96
  child.on('error', (error) => {
69
- reject(error);
97
+ reject(toExecRejection(error, command, args, options.timeout));
70
98
  });
71
99
  child.on('close', (code, signal) => {
72
100
  resolve({
73
101
  stdout: out.getText(),
74
102
  stderr: err.getText(),
75
103
  exitCode: exitCodeFromClose(code, signal),
104
+ stdoutTruncated: out.wasTruncated(),
105
+ stderrTruncated: err.wasTruncated(),
76
106
  });
77
107
  });
78
108
  });
@@ -86,26 +116,99 @@ export async function exec(command, args, options = {}) {
86
116
  */
87
117
  export async function execStream(command, args, options = {}) {
88
118
  return new Promise((resolve, reject) => {
119
+ const usesProcessGroup = options.processGroup === true && process.platform !== 'win32';
89
120
  const child = spawn(command, args, {
90
121
  cwd: options.cwd,
91
122
  env: { ...process.env, ...options.env },
92
123
  stdio: ['ignore', 'pipe', 'pipe'],
93
124
  signal: buildSignalFromTimeout(options.timeout),
125
+ detached: usesProcessGroup,
94
126
  });
127
+ const groupPid = usesProcessGroup ? child.pid : undefined;
128
+ const stdoutDecoder = new StringDecoder('utf8');
129
+ const stderrDecoder = new StringDecoder('utf8');
95
130
  child.stdout.on('data', (data) => {
96
- options.onStdout?.(data.toString());
131
+ const chunk = stdoutDecoder.write(data);
132
+ if (chunk.length > 0)
133
+ options.onStdout?.(chunk);
97
134
  });
98
135
  child.stderr.on('data', (data) => {
99
- options.onStderr?.(data.toString());
136
+ const chunk = stderrDecoder.write(data);
137
+ if (chunk.length > 0)
138
+ options.onStderr?.(chunk);
100
139
  });
140
+ // A detached group leader no longer receives the terminal's Ctrl+C, so
141
+ // the forwarder is mandatory (not just graceful-UX) when processGroup
142
+ // is set. Group-aware: kills route to the whole tree.
143
+ const forwarder = options.processGroup === true
144
+ ? installGracefulShutdownForwarder(child, 1500, (signal) => {
145
+ killProcessTree(child, signal, usesProcessGroup);
146
+ })
147
+ : undefined;
101
148
  child.on('error', (error) => {
102
- reject(error);
149
+ // Abort/startup failure: make sure a partially-started tree does not
150
+ // outlive the dispatch (a mach dying at startup used to strand
151
+ // multiprocessing workers).
152
+ if (options.processGroup === true) {
153
+ killProcessTree(child, 'SIGKILL', usesProcessGroup);
154
+ }
155
+ forwarder?.dispose();
156
+ reject(toExecRejection(error, command, args, options.timeout));
103
157
  });
104
158
  child.on('close', (code, signal) => {
105
- resolve(exitCodeFromClose(code, signal));
159
+ forwarder?.dispose();
160
+ const finish = () => {
161
+ resolve(exitCodeFromClose(code, signal));
162
+ };
163
+ if (groupPid !== undefined) {
164
+ sweepProcessGroup(groupPid).then(finish, finish);
165
+ }
166
+ else {
167
+ finish();
168
+ }
106
169
  });
107
170
  });
108
171
  }
172
+ /**
173
+ * Close-promises of children whose shutdown the CLI must wait for when a
174
+ * termination signal arrives. The bin signal handler used to `process.exit`
175
+ * within a microtask of the first Ctrl+C, which made `execInherit`'s
176
+ * documented SIGTERM → grace → SIGKILL escalation unreachable: the parent
177
+ * was gone before the 1500 ms grace timer could fire, so a hung Firefox
178
+ * was orphaned forever instead of being SIGKILLed and a healthy one lost
179
+ * its AsyncShutdown flush window.
180
+ */
181
+ const activeChildClosures = new Set();
182
+ function trackChildClosure() {
183
+ let resolveClosed;
184
+ const closed = new Promise((resolve) => {
185
+ resolveClosed = resolve;
186
+ });
187
+ activeChildClosures.add(closed);
188
+ return {
189
+ settle: () => {
190
+ resolveClosed?.();
191
+ activeChildClosures.delete(closed);
192
+ },
193
+ };
194
+ }
195
+ /**
196
+ * Waits (bounded) for every tracked child process to close. Called by the
197
+ * bin signal handler after forwarding SIGINT/SIGTERM, so the parent stays
198
+ * alive long enough for the grace-then-SIGKILL escalation to actually run.
199
+ * Resolves immediately when no children are active.
200
+ */
201
+ export async function waitForActiveChildShutdown(timeoutMs) {
202
+ if (activeChildClosures.size === 0)
203
+ return;
204
+ await Promise.race([
205
+ Promise.allSettled([...activeChildClosures]),
206
+ new Promise((resolve) => {
207
+ const timer = setTimeout(resolve, timeoutMs);
208
+ timer.unref();
209
+ }),
210
+ ]);
211
+ }
109
212
  /**
110
213
  * Executes a command and inherits stdio (shows output directly).
111
214
  *
@@ -124,21 +227,42 @@ export async function execStream(command, args, options = {}) {
124
227
  */
125
228
  export async function execInherit(command, args, options = {}) {
126
229
  return new Promise((resolve, reject) => {
230
+ const usesProcessGroup = options.processGroup === true && process.platform !== 'win32';
127
231
  const child = spawn(command, args, {
128
232
  cwd: options.cwd,
129
233
  env: { ...process.env, ...options.env },
130
234
  stdio: 'inherit',
131
235
  signal: buildSignalFromTimeout(options.timeout),
236
+ detached: usesProcessGroup,
132
237
  });
238
+ const groupPid = usesProcessGroup ? child.pid : undefined;
133
239
  const graceMs = options.shutdownGraceMs ?? 1500;
134
- const { dispose } = installGracefulShutdownForwarder(child, graceMs);
240
+ const { dispose } = installGracefulShutdownForwarder(child, graceMs, options.processGroup === true
241
+ ? (signal) => {
242
+ killProcessTree(child, signal, usesProcessGroup);
243
+ }
244
+ : undefined);
245
+ const closure = trackChildClosure();
135
246
  child.on('error', (error) => {
247
+ if (options.processGroup === true) {
248
+ killProcessTree(child, 'SIGKILL', usesProcessGroup);
249
+ }
136
250
  dispose();
137
- reject(error);
251
+ closure.settle();
252
+ reject(toExecRejection(error, command, args, options.timeout));
138
253
  });
139
254
  child.on('close', (code, signal) => {
140
255
  dispose();
141
- resolve(exitCodeFromClose(code, signal));
256
+ const finish = () => {
257
+ closure.settle();
258
+ resolve(exitCodeFromClose(code, signal));
259
+ };
260
+ if (groupPid !== undefined) {
261
+ sweepProcessGroup(groupPid).then(finish, finish);
262
+ }
263
+ else {
264
+ finish();
265
+ }
142
266
  });
143
267
  });
144
268
  }
@@ -150,14 +274,21 @@ export async function execInherit(command, args, options = {}) {
150
274
  * `close` and `error` handlers so the process does not accumulate signal
151
275
  * listeners across repeated spawns.
152
276
  */
153
- function installGracefulShutdownForwarder(child, graceMs) {
277
+ function installGracefulShutdownForwarder(child, graceMs, killTarget) {
154
278
  let graceTimer;
155
279
  const forwarded = new Set();
280
+ const sendKill = (signal) => {
281
+ if (killTarget) {
282
+ killTarget(signal);
283
+ return;
284
+ }
285
+ child.kill(signal);
286
+ };
156
287
  const escalate = () => {
157
288
  if (child.exitCode !== null || child.signalCode !== null)
158
289
  return;
159
290
  try {
160
- child.kill('SIGKILL');
291
+ sendKill('SIGKILL');
161
292
  }
162
293
  catch {
163
294
  // Child is already gone — nothing to do.
@@ -171,7 +302,7 @@ function installGracefulShutdownForwarder(child, graceMs) {
171
302
  }
172
303
  forwarded.add(signal);
173
304
  try {
174
- child.kill('SIGTERM');
305
+ sendKill('SIGTERM');
175
306
  }
176
307
  catch {
177
308
  // If the child can't accept SIGTERM (already dead), nothing to do.
@@ -208,29 +339,52 @@ function installGracefulShutdownForwarder(child, graceMs) {
208
339
  */
209
340
  export async function execInheritCapture(command, args, options = {}) {
210
341
  return new Promise((resolve, reject) => {
342
+ const usesProcessGroup = options.processGroup === true && process.platform !== 'win32';
211
343
  const child = spawn(command, args, {
212
344
  cwd: options.cwd,
213
345
  env: { ...process.env, ...options.env },
214
346
  stdio: ['inherit', 'pipe', 'pipe'],
215
347
  signal: buildSignalFromTimeout(options.timeout),
348
+ detached: usesProcessGroup,
216
349
  });
350
+ const groupPid = usesProcessGroup ? child.pid : undefined;
217
351
  const out = createStreamCollector(process.stdout);
218
352
  const err = createStreamCollector(process.stderr);
219
353
  child.stdout.on('data', out.onData);
220
354
  child.stderr.on('data', err.onData);
221
355
  const graceMs = options.shutdownGraceMs ?? 1500;
222
- const { dispose } = installGracefulShutdownForwarder(child, graceMs);
356
+ const { dispose } = installGracefulShutdownForwarder(child, graceMs, options.processGroup === true
357
+ ? (signal) => {
358
+ killProcessTree(child, signal, usesProcessGroup);
359
+ }
360
+ : undefined);
361
+ const closure = trackChildClosure();
223
362
  child.on('error', (error) => {
363
+ if (options.processGroup === true) {
364
+ killProcessTree(child, 'SIGKILL', usesProcessGroup);
365
+ }
224
366
  dispose();
225
- reject(error);
367
+ closure.settle();
368
+ reject(toExecRejection(error, command, args, options.timeout));
226
369
  });
227
370
  child.on('close', (code, signal) => {
228
371
  dispose();
229
- resolve({
230
- stdout: out.getText(),
231
- stderr: err.getText(),
232
- exitCode: exitCodeFromClose(code, signal),
233
- });
372
+ const finish = () => {
373
+ closure.settle();
374
+ resolve({
375
+ stdout: out.getText(),
376
+ stderr: err.getText(),
377
+ exitCode: exitCodeFromClose(code, signal),
378
+ stdoutTruncated: out.wasTruncated(),
379
+ stderrTruncated: err.wasTruncated(),
380
+ });
381
+ };
382
+ if (groupPid !== undefined) {
383
+ sweepProcessGroup(groupPid).then(finish, finish);
384
+ }
385
+ else {
386
+ finish();
387
+ }
234
388
  });
235
389
  });
236
390
  }
@@ -248,8 +402,12 @@ export async function execInheritCapture(command, args, options = {}) {
248
402
  * that inherited the group.
249
403
  *
250
404
  * Windows fallback: `detached: true` does not create an equivalent group
251
- * there, so we degrade to `child.kill()` and log a best-effort warning
252
- * via the `onStderrLine` callback if the caller wired one.
405
+ * there, so we kill the descendant tree with `taskkill /pid <pid> /T /F`
406
+ * instead. taskkill has no graceful-signal equivalent both the deadline
407
+ * and the grace re-invocation are forced kills, so Windows children get
408
+ * no shutdown window (best-effort; Windows is untested, see README).
409
+ * `child.kill()` is still sent as a direct-child fallback in case
410
+ * taskkill itself is unavailable.
253
411
  */
254
412
  export async function execSmokeRun(command, args, options) {
255
413
  return new Promise((resolve, reject) => {
@@ -261,45 +419,30 @@ export async function execSmokeRun(command, args, options) {
261
419
  // A new process group on POSIX lets us signal the whole descendant
262
420
  // tree at once, which is essential for mach → python → firefox →
263
421
  // content-process chains. On Windows spawn ignores this for our
264
- // purposes and we fall back to child.kill below.
422
+ // purposes and we fall back to a taskkill /T tree kill below.
265
423
  detached: usesProcessGroup,
266
424
  });
267
425
  const out = createStreamCollector(options.mirror?.stdout);
268
426
  const err = createStreamCollector(options.mirror?.stderr);
427
+ const stdoutLineDecoder = new StringDecoder('utf8');
428
+ const stderrLineDecoder = new StringDecoder('utf8');
269
429
  let stdoutBuffer = '';
270
430
  let stderrBuffer = '';
271
431
  child.stdout.on('data', (data) => {
272
432
  out.onData(data);
273
- stdoutBuffer += data.toString();
433
+ stdoutBuffer += stdoutLineDecoder.write(data);
274
434
  stdoutBuffer = dispatchCompleteLines(stdoutBuffer, options.onStdoutLine);
275
435
  });
276
436
  child.stderr.on('data', (data) => {
277
437
  err.onData(data);
278
- stderrBuffer += data.toString();
438
+ stderrBuffer += stderrLineDecoder.write(data);
279
439
  stderrBuffer = dispatchCompleteLines(stderrBuffer, options.onStderrLine);
280
440
  });
281
441
  let timedOut = false;
282
442
  let graceTimer;
443
+ let signalGraceTimer;
283
444
  const signalChildGroup = (signal) => {
284
- if (child.exitCode !== null || child.signalCode !== null)
285
- return;
286
- const targetPid = child.pid;
287
- if (targetPid === undefined)
288
- return;
289
- try {
290
- if (usesProcessGroup) {
291
- // Negative PID routes to the process group, killing the Python
292
- // wrapper, the firefox it forked, and every content process
293
- // that inherited the group.
294
- process.kill(-targetPid, signal);
295
- }
296
- else {
297
- child.kill(signal);
298
- }
299
- }
300
- catch {
301
- // Already gone. Nothing to do.
302
- }
445
+ killProcessTree(child, signal, usesProcessGroup);
303
446
  };
304
447
  const deadlineTimer = setTimeout(() => {
305
448
  timedOut = true;
@@ -310,16 +453,52 @@ export async function execSmokeRun(command, args, options) {
310
453
  graceTimer.unref();
311
454
  }, options.smokeTimeoutMs);
312
455
  deadlineTimer.unref();
456
+ // Forward parent SIGINT/SIGTERM to the whole child process group. The
457
+ // smoke child is a group leader (detached), so it does NOT receive the
458
+ // terminal's Ctrl+C SIGINT — without this forwarder, the parent exits
459
+ // and the entire mach → firefox tree is orphaned. Second signal (or
460
+ // grace expiry) escalates to a group SIGKILL.
461
+ const forwardedSignals = new Set();
462
+ const onParentSignal = (signal) => {
463
+ if (forwardedSignals.has(signal)) {
464
+ signalChildGroup('SIGKILL');
465
+ return;
466
+ }
467
+ forwardedSignals.add(signal);
468
+ signalChildGroup('SIGTERM');
469
+ signalGraceTimer = setTimeout(() => {
470
+ signalChildGroup('SIGKILL');
471
+ }, options.killGraceMs ?? 10000);
472
+ signalGraceTimer.unref();
473
+ };
474
+ const onSigint = () => {
475
+ onParentSignal('SIGINT');
476
+ };
477
+ const onSigterm = () => {
478
+ onParentSignal('SIGTERM');
479
+ };
480
+ process.on('SIGINT', onSigint);
481
+ process.on('SIGTERM', onSigterm);
482
+ const closure = trackChildClosure();
483
+ const cleanupSignalForwarding = () => {
484
+ process.off('SIGINT', onSigint);
485
+ process.off('SIGTERM', onSigterm);
486
+ if (signalGraceTimer)
487
+ clearTimeout(signalGraceTimer);
488
+ closure.settle();
489
+ };
313
490
  child.on('error', (error) => {
314
491
  clearTimeout(deadlineTimer);
315
492
  if (graceTimer)
316
493
  clearTimeout(graceTimer);
494
+ cleanupSignalForwarding();
317
495
  reject(error);
318
496
  });
319
497
  child.on('close', (code, signal) => {
320
498
  clearTimeout(deadlineTimer);
321
499
  if (graceTimer)
322
500
  clearTimeout(graceTimer);
501
+ cleanupSignalForwarding();
323
502
  // Flush any remaining partial line (child ended without a trailing newline).
324
503
  if (stdoutBuffer.length > 0)
325
504
  options.onStdoutLine?.(stdoutBuffer);
@@ -335,24 +514,45 @@ export async function execSmokeRun(command, args, options) {
335
514
  });
336
515
  }
337
516
  /**
338
- * Drains complete newline-terminated lines from `buffer`, dispatching each
339
- * trimmed-of-newline line to `cb`. Returns the remaining partial line that
340
- * has not yet been terminated callers should keep accumulating into it.
517
+ * Cap on the partial-line tail kept between chunks. A child emitting one
518
+ * enormous line with no terminator (minified JS dumped to stderr, a raw
519
+ * binary blob) previously grew the buffer without bound the 50 MB cap
520
+ * only guarded the collector, not these line buffers. When the tail
521
+ * exceeds the cap it is dispatched as a synthetic (oversized) line so the
522
+ * matchers still get to see its content, then the buffer resets.
523
+ */
524
+ const MAX_PARTIAL_LINE_SIZE = 1024 * 1024;
525
+ /**
526
+ * Drains complete lines from `buffer`, dispatching each to `cb`. Treats
527
+ * `\n`, `\r\n`, and lone `\r` as line terminators — the lone-`\r` case
528
+ * matters for progress-bar style output (mach, cargo) that repaints a line
529
+ * with carriage returns and never sends a newline; before `\r` counted as a
530
+ * terminator those updates accumulated indefinitely. A single trailing `\r`
531
+ * is held back since it may be the first half of a `\r\n` pair split across
532
+ * chunks. Returns the remaining partial line — callers keep accumulating
533
+ * into it.
341
534
  */
342
535
  function dispatchCompleteLines(buffer, cb) {
343
- if (!cb) {
344
- // No listener — still need to collapse accumulated newlines so the
345
- // buffer does not grow forever. Keep only the partial-line tail.
346
- const lastNewline = buffer.lastIndexOf('\n');
347
- if (lastNewline === -1)
348
- return buffer;
349
- return buffer.slice(lastNewline + 1);
536
+ let searchFrom = 0;
537
+ for (;;) {
538
+ const nl = buffer.indexOf('\n', searchFrom);
539
+ const cr = buffer.indexOf('\r', searchFrom);
540
+ const idx = nl === -1 ? cr : cr === -1 ? nl : Math.min(nl, cr);
541
+ if (idx === -1)
542
+ break;
543
+ if (buffer[idx] === '\r' && idx === buffer.length - 1) {
544
+ // Possible first half of a chunk-split \r\n — wait for the next chunk.
545
+ break;
546
+ }
547
+ const line = buffer.slice(0, idx);
548
+ const terminatorLength = buffer[idx] === '\r' && buffer[idx + 1] === '\n' ? 2 : 1;
549
+ buffer = buffer.slice(idx + terminatorLength);
550
+ searchFrom = 0;
551
+ cb?.(line);
350
552
  }
351
- let idx;
352
- while ((idx = buffer.indexOf('\n')) !== -1) {
353
- const line = buffer.slice(0, idx).replace(/\r$/, '');
354
- buffer = buffer.slice(idx + 1);
355
- cb(line);
553
+ if (buffer.length > MAX_PARTIAL_LINE_SIZE) {
554
+ cb?.(buffer);
555
+ return '';
356
556
  }
357
557
  return buffer;
358
558
  }
@@ -388,4 +588,3 @@ export async function executableExists(name) {
388
588
  const path = await findExecutable(name);
389
589
  return path !== undefined;
390
590
  }
391
- //# sourceMappingURL=process.js.map
@@ -7,9 +7,19 @@ export declare function escapeRegex(str: string): string;
7
7
  */
8
8
  export declare function hasRawCssColors(content: string): boolean;
9
9
  /**
10
- * Strips JS single-line and multi-line comments from source code, replacing them
11
- * with spaces of equal length to preserve character offsets. String literals
12
- * (single-quoted, double-quoted, and template) are preserved intact.
10
+ * Strips JS single-line and multi-line comments from source code, replacing
11
+ * comment bytes with spaces (newlines inside block comments are kept) so
12
+ * both character offsets AND line numbers are preserved. String literals
13
+ * are preserved intact.
14
+ *
15
+ * Uses acorn's tokenizer, which understands regex literals — the previous
16
+ * pure-regex implementation did not model them, so two adjacent slashes
17
+ * inside a regex (e.g. the tail of `/https?:\/\//`) were treated as a `//`
18
+ * line comment and the rest of the line was blanked, corrupting whatever
19
+ * lint pass consumed the stripped text. Firefox chrome sources contain
20
+ * preprocessor directives (`#ifdef`) and other constructs acorn cannot
21
+ * tokenize; those fall back to the legacy regex strip, which handles
22
+ * strings but NOT regex literals (documented limitation of the fallback).
13
23
  */
14
24
  export declare function stripJsComments(source: string): string;
15
25
  /**
@@ -1,4 +1,5 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
+ import { tokenizer as acornTokenizer, tokTypes as acornTokTypes } from 'acorn';
2
3
  /**
3
4
  * Escapes special regex characters in a string.
4
5
  */
@@ -22,11 +23,60 @@ export function hasRawCssColors(content) {
22
23
  return CSS_HEX_COLOR.test(content) || CSS_RGB_COLOR.test(content) || CSS_HSL_COLOR.test(content);
23
24
  }
24
25
  /**
25
- * Strips JS single-line and multi-line comments from source code, replacing them
26
- * with spaces of equal length to preserve character offsets. String literals
27
- * (single-quoted, double-quoted, and template) are preserved intact.
26
+ * Strips JS single-line and multi-line comments from source code, replacing
27
+ * comment bytes with spaces (newlines inside block comments are kept) so
28
+ * both character offsets AND line numbers are preserved. String literals
29
+ * are preserved intact.
30
+ *
31
+ * Uses acorn's tokenizer, which understands regex literals — the previous
32
+ * pure-regex implementation did not model them, so two adjacent slashes
33
+ * inside a regex (e.g. the tail of `/https?:\/\//`) were treated as a `//`
34
+ * line comment and the rest of the line was blanked, corrupting whatever
35
+ * lint pass consumed the stripped text. Firefox chrome sources contain
36
+ * preprocessor directives (`#ifdef`) and other constructs acorn cannot
37
+ * tokenize; those fall back to the legacy regex strip, which handles
38
+ * strings but NOT regex literals (documented limitation of the fallback).
28
39
  */
29
40
  export function stripJsComments(source) {
41
+ const comments = [];
42
+ try {
43
+ const t = acornTokenizer(source, {
44
+ ecmaVersion: 'latest',
45
+ sourceType: 'module',
46
+ allowHashBang: true,
47
+ onComment: (_isBlock, _text, start, end) => {
48
+ comments.push({ start, end });
49
+ },
50
+ });
51
+ // Drive the tokenizer to EOF; we only care about onComment callbacks.
52
+ for (;;) {
53
+ const token = t.getToken();
54
+ if (token.type === acornTokTypes.eof)
55
+ break;
56
+ }
57
+ }
58
+ catch {
59
+ return stripJsCommentsLegacy(source);
60
+ }
61
+ if (comments.length === 0)
62
+ return source;
63
+ const chars = source.split('');
64
+ for (const { start, end } of comments) {
65
+ for (let i = start; i < end; i++) {
66
+ if (chars[i] !== '\n' && chars[i] !== '\r') {
67
+ chars[i] = ' ';
68
+ }
69
+ }
70
+ }
71
+ return chars.join('');
72
+ }
73
+ /**
74
+ * Legacy pure-regex comment strip, used only when acorn cannot tokenize
75
+ * the source (preprocessor directives, syntax errors). Strings survive;
76
+ * regex literals are NOT modeled, so `//` inside a regex blanks the rest
77
+ * of the line.
78
+ */
79
+ function stripJsCommentsLegacy(source) {
30
80
  return source.replace(/\/\/.*$|\/\*[\s\S]*?\*\/|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`/gm, (match) => (match.startsWith('/') ? ' '.repeat(match.length) : match));
31
81
  }
32
82
  /**
@@ -37,4 +87,3 @@ export function countRawCssColors(content) {
37
87
  (content.match(CSS_RGB_COLOR_G)?.length ?? 0) +
38
88
  (content.match(CSS_HSL_COLOR_G)?.length ?? 0));
39
89
  }
40
- //# sourceMappingURL=regex.js.map
@@ -52,18 +52,6 @@ export declare function isObject(value: unknown): value is Record<string, unknow
52
52
  * @returns True if value is an array
53
53
  */
54
54
  export declare function isArray(value: unknown): value is unknown[];
55
- /**
56
- * Asserts that a value is a string or throws.
57
- * @param value - Value to check
58
- * @param name - Name of the field for error messages
59
- */
60
- export declare function assertString(value: unknown, name: string): asserts value is string;
61
- /**
62
- * Asserts that a value is a non-null object or throws.
63
- * @param value - Value to check
64
- * @param name - Name of the field for error messages
65
- */
66
- export declare function assertObject(value: unknown, name: string): asserts value is Record<string, unknown>;
67
55
  /**
68
56
  * Validates a Firefox version string.
69
57
  * Accepts formats like "140.9.0", "140.9.1", "140.9.0esr", "147.0b1"