@hominis/fireforge 0.34.2 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +43 -5
  3. package/dist/bin/fireforge.d.ts +4 -0
  4. package/dist/bin/fireforge.js +25 -6
  5. package/dist/src/cli.js +22 -5
  6. package/dist/src/commands/bootstrap-checks.js +0 -1
  7. package/dist/src/commands/bootstrap.d.ts +2 -1
  8. package/dist/src/commands/bootstrap.js +16 -7
  9. package/dist/src/commands/build.js +17 -3
  10. package/dist/src/commands/config.js +30 -10
  11. package/dist/src/commands/discard.js +24 -5
  12. package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
  13. package/dist/src/commands/doctor-check-core.js +0 -1
  14. package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
  15. package/dist/src/commands/doctor-external-toolchains.js +54 -0
  16. package/dist/src/commands/doctor-furnace-jar.js +0 -1
  17. package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
  18. package/dist/src/commands/doctor-furnace.js +2 -2
  19. package/dist/src/commands/doctor-working-tree.js +0 -1
  20. package/dist/src/commands/doctor.js +4 -1
  21. package/dist/src/commands/download.js +51 -15
  22. package/dist/src/commands/export-all.js +5 -3
  23. package/dist/src/commands/export-flow.js +12 -7
  24. package/dist/src/commands/export-placement-gate.js +0 -1
  25. package/dist/src/commands/export-placement-policy.js +0 -1
  26. package/dist/src/commands/export-shared.d.ts +5 -1
  27. package/dist/src/commands/export-shared.js +18 -6
  28. package/dist/src/commands/export.js +15 -10
  29. package/dist/src/commands/furnace/apply.js +22 -6
  30. package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
  31. package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
  32. package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
  33. package/dist/src/commands/furnace/chrome-doc.js +0 -1
  34. package/dist/src/commands/furnace/create-browser-test.js +0 -1
  35. package/dist/src/commands/furnace/create-dry-run.js +0 -1
  36. package/dist/src/commands/furnace/create-features.js +0 -1
  37. package/dist/src/commands/furnace/create-mochikit.js +0 -1
  38. package/dist/src/commands/furnace/create-readback.js +0 -1
  39. package/dist/src/commands/furnace/create-templates.js +0 -1
  40. package/dist/src/commands/furnace/create-validation.js +0 -1
  41. package/dist/src/commands/furnace/create-xpcshell.js +0 -1
  42. package/dist/src/commands/furnace/create.js +0 -1
  43. package/dist/src/commands/furnace/deploy.js +7 -83
  44. package/dist/src/commands/furnace/diff.js +0 -1
  45. package/dist/src/commands/furnace/index.js +59 -25
  46. package/dist/src/commands/furnace/init.js +0 -1
  47. package/dist/src/commands/furnace/list.js +0 -1
  48. package/dist/src/commands/furnace/override.js +0 -1
  49. package/dist/src/commands/furnace/preview.js +3 -3
  50. package/dist/src/commands/furnace/refresh.js +0 -1
  51. package/dist/src/commands/furnace/remove-state.d.ts +15 -0
  52. package/dist/src/commands/furnace/remove-state.js +47 -1
  53. package/dist/src/commands/furnace/remove.js +3 -9
  54. package/dist/src/commands/furnace/rename-browser-test.js +1 -5
  55. package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
  56. package/dist/src/commands/furnace/rename-helpers.js +30 -1
  57. package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
  58. package/dist/src/commands/furnace/rename.js +15 -36
  59. package/dist/src/commands/furnace/scan.js +0 -1
  60. package/dist/src/commands/furnace/status.js +0 -1
  61. package/dist/src/commands/furnace/sync.js +76 -4
  62. package/dist/src/commands/furnace/validate.js +0 -1
  63. package/dist/src/commands/furnace/validation-output.js +0 -1
  64. package/dist/src/commands/import.js +35 -9
  65. package/dist/src/commands/lint-per-patch.js +19 -7
  66. package/dist/src/commands/lint.js +13 -3
  67. package/dist/src/commands/manifest.js +1 -2
  68. package/dist/src/commands/package.js +2 -1
  69. package/dist/src/commands/patch/compact.js +1 -1
  70. package/dist/src/commands/patch/delete.js +1 -1
  71. package/dist/src/commands/patch/index.js +1 -1
  72. package/dist/src/commands/patch/lint-ignore.js +21 -6
  73. package/dist/src/commands/patch/move-files.js +1 -1
  74. package/dist/src/commands/patch/patch-context.js +0 -1
  75. package/dist/src/commands/patch/rename.js +4 -4
  76. package/dist/src/commands/patch/reorder.js +3 -4
  77. package/dist/src/commands/patch/split-plan.js +2 -1
  78. package/dist/src/commands/patch/split.js +4 -4
  79. package/dist/src/commands/patch/staged-dependency.js +1 -1
  80. package/dist/src/commands/patch/tier.js +18 -5
  81. package/dist/src/commands/re-export-bulk-scan.js +0 -1
  82. package/dist/src/commands/re-export-files.js +1 -1
  83. package/dist/src/commands/re-export-options.js +0 -1
  84. package/dist/src/commands/re-export-scan.js +0 -1
  85. package/dist/src/commands/re-export.js +6 -4
  86. package/dist/src/commands/rebase/abort.js +2 -9
  87. package/dist/src/commands/rebase/confirm.js +0 -1
  88. package/dist/src/commands/rebase/conflict-summary.js +0 -1
  89. package/dist/src/commands/rebase/continue.js +0 -1
  90. package/dist/src/commands/rebase/index.d.ts +1 -1
  91. package/dist/src/commands/rebase/index.js +10 -15
  92. package/dist/src/commands/rebase/patch-loop.js +38 -7
  93. package/dist/src/commands/rebase/summary.js +2 -3
  94. package/dist/src/commands/rebase.js +0 -1
  95. package/dist/src/commands/register.js +1 -1
  96. package/dist/src/commands/reset.js +16 -9
  97. package/dist/src/commands/resolve.js +2 -2
  98. package/dist/src/commands/run.js +18 -6
  99. package/dist/src/commands/setup-support.js +0 -1
  100. package/dist/src/commands/setup.js +1 -2
  101. package/dist/src/commands/source.js +1 -2
  102. package/dist/src/commands/status-output.js +1 -12
  103. package/dist/src/commands/status.js +147 -133
  104. package/dist/src/commands/test-appdir.js +0 -1
  105. package/dist/src/commands/test-diagnose.js +18 -2
  106. package/dist/src/commands/test-modes.d.ts +15 -0
  107. package/dist/src/commands/test-modes.js +61 -0
  108. package/dist/src/commands/test-register.d.ts +4 -0
  109. package/dist/src/commands/test-register.js +64 -0
  110. package/dist/src/commands/test-run.d.ts +23 -7
  111. package/dist/src/commands/test-run.js +15 -14
  112. package/dist/src/commands/test.d.ts +0 -4
  113. package/dist/src/commands/test.js +95 -56
  114. package/dist/src/commands/token-coverage.js +0 -1
  115. package/dist/src/commands/token.js +1 -2
  116. package/dist/src/commands/typecheck.js +1 -1
  117. package/dist/src/commands/verify.js +3 -9
  118. package/dist/src/commands/watch.js +5 -1
  119. package/dist/src/commands/wire.js +1 -1
  120. package/dist/src/core/ast-utils.js +0 -1
  121. package/dist/src/core/brand-validation.js +0 -1
  122. package/dist/src/core/branding.js +0 -1
  123. package/dist/src/core/browser-wire.js +0 -1
  124. package/dist/src/core/build-audit-platform.js +0 -1
  125. package/dist/src/core/build-audit-registration.js +0 -1
  126. package/dist/src/core/build-audit-resolve.js +0 -1
  127. package/dist/src/core/build-audit-transforms.js +0 -1
  128. package/dist/src/core/build-audit.js +0 -1
  129. package/dist/src/core/build-baseline-types.js +0 -1
  130. package/dist/src/core/build-baseline.js +0 -1
  131. package/dist/src/core/build-prepare.js +21 -7
  132. package/dist/src/core/config-mutate.js +0 -1
  133. package/dist/src/core/config-paths.d.ts +2 -2
  134. package/dist/src/core/config-paths.js +6 -1
  135. package/dist/src/core/config-state.js +0 -1
  136. package/dist/src/core/config-validate-patch-policy.js +0 -1
  137. package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
  138. package/dist/src/core/config-validate-test-toolchains.js +105 -0
  139. package/dist/src/core/config-validate.js +3 -1
  140. package/dist/src/core/config.js +0 -1
  141. package/dist/src/core/destructive.js +0 -1
  142. package/dist/src/core/diff-hunks.js +13 -7
  143. package/dist/src/core/engine-changes.js +0 -1
  144. package/dist/src/core/engine-session-lock.d.ts +12 -0
  145. package/dist/src/core/engine-session-lock.js +45 -0
  146. package/dist/src/core/file-lock.d.ts +8 -0
  147. package/dist/src/core/file-lock.js +157 -48
  148. package/dist/src/core/firefox-archive.d.ts +4 -0
  149. package/dist/src/core/firefox-archive.js +6 -1
  150. package/dist/src/core/firefox-cache.js +67 -8
  151. package/dist/src/core/firefox-download.js +0 -1
  152. package/dist/src/core/firefox-extract.d.ts +21 -1
  153. package/dist/src/core/firefox-extract.js +178 -9
  154. package/dist/src/core/firefox-ignorefile.js +0 -1
  155. package/dist/src/core/firefox.d.ts +12 -0
  156. package/dist/src/core/firefox.js +64 -2
  157. package/dist/src/core/furnace-apply-dry-run.js +0 -1
  158. package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
  159. package/dist/src/core/furnace-apply-ftl.js +10 -5
  160. package/dist/src/core/furnace-apply-helpers.js +0 -1
  161. package/dist/src/core/furnace-apply-output.js +2 -2
  162. package/dist/src/core/furnace-apply.d.ts +7 -1
  163. package/dist/src/core/furnace-apply.js +47 -13
  164. package/dist/src/core/furnace-checksum-utils.js +0 -1
  165. package/dist/src/core/furnace-config-array-utils.js +0 -1
  166. package/dist/src/core/furnace-config-custom.js +0 -1
  167. package/dist/src/core/furnace-config-order.js +0 -1
  168. package/dist/src/core/furnace-config-tokens.js +0 -1
  169. package/dist/src/core/furnace-config-validate.js +0 -1
  170. package/dist/src/core/furnace-config.d.ts +11 -0
  171. package/dist/src/core/furnace-config.js +37 -1
  172. package/dist/src/core/furnace-constants.js +0 -1
  173. package/dist/src/core/furnace-css-fragments.js +0 -1
  174. package/dist/src/core/furnace-graph-utils.js +0 -1
  175. package/dist/src/core/furnace-jsconfig.d.ts +2 -3
  176. package/dist/src/core/furnace-jsconfig.js +25 -18
  177. package/dist/src/core/furnace-marker.js +0 -1
  178. package/dist/src/core/furnace-operation.js +0 -1
  179. package/dist/src/core/furnace-refresh.js +0 -1
  180. package/dist/src/core/furnace-registration-ast.d.ts +14 -0
  181. package/dist/src/core/furnace-registration-ast.js +1 -2
  182. package/dist/src/core/furnace-registration-remove.js +0 -1
  183. package/dist/src/core/furnace-registration-validate.js +1 -5
  184. package/dist/src/core/furnace-registration.js +0 -1
  185. package/dist/src/core/furnace-rollback.js +0 -1
  186. package/dist/src/core/furnace-scanner.js +0 -1
  187. package/dist/src/core/furnace-staleness.js +0 -1
  188. package/dist/src/core/furnace-state-persist.d.ts +72 -0
  189. package/dist/src/core/furnace-state-persist.js +118 -0
  190. package/dist/src/core/furnace-step-errors.d.ts +23 -0
  191. package/dist/src/core/furnace-step-errors.js +25 -0
  192. package/dist/src/core/furnace-stories.js +0 -1
  193. package/dist/src/core/furnace-validate-accessibility.js +0 -1
  194. package/dist/src/core/furnace-validate-checks.js +0 -1
  195. package/dist/src/core/furnace-validate-compatibility.js +0 -1
  196. package/dist/src/core/furnace-validate-helpers.js +0 -1
  197. package/dist/src/core/furnace-validate-registration.js +7 -7
  198. package/dist/src/core/furnace-validate-structure.d.ts +8 -0
  199. package/dist/src/core/furnace-validate-structure.js +11 -2
  200. package/dist/src/core/furnace-validate.js +0 -1
  201. package/dist/src/core/furnace-version-drift.d.ts +6 -3
  202. package/dist/src/core/furnace-version-drift.js +6 -4
  203. package/dist/src/core/git-base.js +0 -1
  204. package/dist/src/core/git-diff.js +0 -1
  205. package/dist/src/core/git-file-ops.js +0 -1
  206. package/dist/src/core/git-status.d.ts +13 -0
  207. package/dist/src/core/git-status.js +32 -2
  208. package/dist/src/core/git.d.ts +10 -1
  209. package/dist/src/core/git.js +65 -8
  210. package/dist/src/core/license-headers.d.ts +16 -0
  211. package/dist/src/core/license-headers.js +19 -1
  212. package/dist/src/core/lint-cache.js +0 -1
  213. package/dist/src/core/lint-projection.js +0 -1
  214. package/dist/src/core/mach-build-artifacts.js +0 -1
  215. package/dist/src/core/mach-error-hints.js +27 -1
  216. package/dist/src/core/mach-mozconfig.js +0 -1
  217. package/dist/src/core/mach-python.js +0 -1
  218. package/dist/src/core/mach-resource-shim.js +0 -1
  219. package/dist/src/core/mach.js +0 -1
  220. package/dist/src/core/manifest-helpers.js +0 -1
  221. package/dist/src/core/manifest-register.js +0 -1
  222. package/dist/src/core/manifest-rules.js +0 -1
  223. package/dist/src/core/manifest-tokenizers.js +0 -1
  224. package/dist/src/core/marionette-port.d.ts +8 -0
  225. package/dist/src/core/marionette-port.js +34 -3
  226. package/dist/src/core/marionette-preflight.js +0 -1
  227. package/dist/src/core/markdown-table.js +0 -1
  228. package/dist/src/core/ownership-table.js +2 -9
  229. package/dist/src/core/parser-fallback.js +0 -1
  230. package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
  231. package/dist/src/core/patch-apply-fuzz.js +71 -34
  232. package/dist/src/core/patch-apply.d.ts +30 -0
  233. package/dist/src/core/patch-apply.js +152 -39
  234. package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
  235. package/dist/src/core/patch-artifact-normalize.js +19 -15
  236. package/dist/src/core/patch-export-coverage.js +0 -1
  237. package/dist/src/core/patch-export-metadata.js +0 -1
  238. package/dist/src/core/patch-export-update.js +2 -3
  239. package/dist/src/core/patch-export.d.ts +2 -6
  240. package/dist/src/core/patch-export.js +24 -48
  241. package/dist/src/core/patch-files.js +0 -1
  242. package/dist/src/core/patch-identifier-suggest.js +0 -1
  243. package/dist/src/core/patch-lint-checkjs.js +0 -1
  244. package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
  245. package/dist/src/core/patch-lint-cross.js +0 -1
  246. package/dist/src/core/patch-lint-css.js +0 -1
  247. package/dist/src/core/patch-lint-diff-tag.js +0 -1
  248. package/dist/src/core/patch-lint-diff.d.ts +2 -1
  249. package/dist/src/core/patch-lint-diff.js +17 -32
  250. package/dist/src/core/patch-lint-imports.js +0 -1
  251. package/dist/src/core/patch-lint-jsdoc.js +0 -1
  252. package/dist/src/core/patch-lint-observer.js +0 -1
  253. package/dist/src/core/patch-lint-ownership.js +0 -1
  254. package/dist/src/core/patch-lint-reexports.js +0 -1
  255. package/dist/src/core/patch-lint.d.ts +37 -2
  256. package/dist/src/core/patch-lint.js +48 -24
  257. package/dist/src/core/patch-lock.js +0 -1
  258. package/dist/src/core/patch-manifest-consistency.js +9 -1
  259. package/dist/src/core/patch-manifest-io.d.ts +16 -0
  260. package/dist/src/core/patch-manifest-io.js +37 -12
  261. package/dist/src/core/patch-manifest-query.d.ts +6 -0
  262. package/dist/src/core/patch-manifest-query.js +27 -12
  263. package/dist/src/core/patch-manifest-resolve.js +0 -1
  264. package/dist/src/core/patch-manifest-validate.js +0 -1
  265. package/dist/src/core/patch-manifest.d.ts +1 -1
  266. package/dist/src/core/patch-manifest.js +1 -2
  267. package/dist/src/core/patch-parse.d.ts +59 -0
  268. package/dist/src/core/patch-parse.js +209 -92
  269. package/dist/src/core/patch-policy.js +0 -1
  270. package/dist/src/core/patch-registration-refs.js +16 -26
  271. package/dist/src/core/patch-source-metadata.js +0 -1
  272. package/dist/src/core/patch-transform.d.ts +4 -0
  273. package/dist/src/core/patch-transform.js +30 -38
  274. package/dist/src/core/rebase-session.js +0 -1
  275. package/dist/src/core/register-browser-content.js +0 -1
  276. package/dist/src/core/register-module.js +0 -1
  277. package/dist/src/core/register-result.js +0 -1
  278. package/dist/src/core/register-scaffold.js +0 -1
  279. package/dist/src/core/register-shared-css.js +0 -1
  280. package/dist/src/core/register-test-manifest.js +0 -1
  281. package/dist/src/core/register-xpcshell-test.js +0 -1
  282. package/dist/src/core/shared-ftl.js +0 -1
  283. package/dist/src/core/signal-critical.js +0 -1
  284. package/dist/src/core/smoke-patterns.js +0 -1
  285. package/dist/src/core/state-file.js +0 -1
  286. package/dist/src/core/status-classify.d.ts +16 -0
  287. package/dist/src/core/status-classify.js +24 -16
  288. package/dist/src/core/test-harness-crash.d.ts +64 -7
  289. package/dist/src/core/test-harness-crash.js +196 -15
  290. package/dist/src/core/test-harness-output.js +0 -1
  291. package/dist/src/core/test-path-scope.d.ts +62 -0
  292. package/dist/src/core/test-path-scope.js +148 -0
  293. package/dist/src/core/test-stale-check.js +0 -1
  294. package/dist/src/core/test-stale-symlink.js +0 -1
  295. package/dist/src/core/test-xpcshell-retry.js +0 -1
  296. package/dist/src/core/token-coverage.js +0 -1
  297. package/dist/src/core/token-dark-mode.js +0 -1
  298. package/dist/src/core/token-docs.js +0 -1
  299. package/dist/src/core/token-manager.js +0 -1
  300. package/dist/src/core/token-scaffold.js +0 -1
  301. package/dist/src/core/token-variant.js +0 -1
  302. package/dist/src/core/toolchain-preflight.d.ts +85 -0
  303. package/dist/src/core/toolchain-preflight.js +256 -0
  304. package/dist/src/core/typecheck-shim.d.ts +4 -1
  305. package/dist/src/core/typecheck-shim.js +61 -18
  306. package/dist/src/core/typecheck.js +0 -1
  307. package/dist/src/core/wire-destroy.js +0 -1
  308. package/dist/src/core/wire-dom-fragment.js +0 -1
  309. package/dist/src/core/wire-init.js +0 -1
  310. package/dist/src/core/wire-subscript.js +0 -1
  311. package/dist/src/core/wire-targets.js +0 -1
  312. package/dist/src/core/wire-utils.js +0 -1
  313. package/dist/src/core/xpcshell-appdir.js +1 -4
  314. package/dist/src/errors/base.d.ts +29 -0
  315. package/dist/src/errors/base.js +35 -1
  316. package/dist/src/errors/build.js +0 -1
  317. package/dist/src/errors/codes.d.ts +6 -0
  318. package/dist/src/errors/codes.js +6 -1
  319. package/dist/src/errors/config.js +0 -1
  320. package/dist/src/errors/download.js +2 -1
  321. package/dist/src/errors/furnace.d.ts +11 -1
  322. package/dist/src/errors/furnace.js +6 -5
  323. package/dist/src/errors/git.js +0 -1
  324. package/dist/src/errors/patch.d.ts +17 -0
  325. package/dist/src/errors/patch.js +29 -1
  326. package/dist/src/errors/rebase.js +0 -1
  327. package/dist/src/errors/run.js +1 -1
  328. package/dist/src/index.js +0 -1
  329. package/dist/src/types/cli.js +0 -1
  330. package/dist/src/types/commands/index.js +0 -1
  331. package/dist/src/types/commands/options.d.ts +20 -5
  332. package/dist/src/types/commands/options.js +0 -1
  333. package/dist/src/types/commands/patches.d.ts +8 -0
  334. package/dist/src/types/commands/patches.js +0 -1
  335. package/dist/src/types/commands/project.d.ts +11 -2
  336. package/dist/src/types/commands/project.js +0 -1
  337. package/dist/src/types/config.d.ts +32 -0
  338. package/dist/src/types/config.js +0 -1
  339. package/dist/src/types/furnace.d.ts +10 -0
  340. package/dist/src/types/furnace.js +0 -1
  341. package/dist/src/types/index.js +0 -1
  342. package/dist/src/types/typecheck.js +0 -1
  343. package/dist/src/utils/elapsed.js +0 -1
  344. package/dist/src/utils/errors.js +0 -1
  345. package/dist/src/utils/fs.js +37 -5
  346. package/dist/src/utils/logger.d.ts +7 -0
  347. package/dist/src/utils/logger.js +85 -3
  348. package/dist/src/utils/options.d.ts +15 -0
  349. package/dist/src/utils/options.js +25 -1
  350. package/dist/src/utils/package-root.js +11 -4
  351. package/dist/src/utils/parse.js +0 -1
  352. package/dist/src/utils/paths.js +0 -1
  353. package/dist/src/utils/platform.js +0 -1
  354. package/dist/src/utils/process.d.ts +33 -4
  355. package/dist/src/utils/process.js +183 -28
  356. package/dist/src/utils/regex.d.ts +13 -3
  357. package/dist/src/utils/regex.js +53 -4
  358. package/dist/src/utils/validation.d.ts +0 -12
  359. package/dist/src/utils/validation.js +13 -26
  360. package/package.json +8 -7
@@ -9,8 +9,8 @@ import { join } from 'node:path';
9
9
  import { pipeline } from 'node:stream/promises';
10
10
  import { ChecksumMismatchError } from '../errors/download.js';
11
11
  import { toError } from '../utils/errors.js';
12
- import { pathExists, readJson, removeFile, writeJson } from '../utils/fs.js';
13
- import { verbose } from '../utils/logger.js';
12
+ import { pathExistsStrict, readJson, removeFile, writeJson } from '../utils/fs.js';
13
+ import { verbose, warn } from '../utils/logger.js';
14
14
  import { createSiblingLockPath, withFileLock } from './file-lock.js';
15
15
  import { validateArchiveMetadata } from './firefox-archive.js';
16
16
  import { downloadFile } from './firefox-download.js';
@@ -24,6 +24,39 @@ async function sha256File(filePath) {
24
24
  await pipeline(stream, hash);
25
25
  return hash.digest('hex');
26
26
  }
27
+ /**
28
+ * Fetches Mozilla's published SHA256SUMS for the release and extracts the
29
+ * digest for this archive. Returns null when the checksum file cannot be
30
+ * fetched or does not list the archive — the caller warns loudly and
31
+ * continues (offline mirrors and staging hosts legitimately lack it), while
32
+ * a FETCHED-but-mismatching digest always fails closed.
33
+ *
34
+ * Line format (both variants appear in the wild):
35
+ * `<64-hex-digest> <release-relative-path>`
36
+ * `<64-hex-digest> *<release-relative-path>`
37
+ */
38
+ async function fetchPublishedSha256(archive) {
39
+ try {
40
+ const response = await fetch(archive.checksumsUrl);
41
+ if (!response.ok) {
42
+ verbose(`SHA256SUMS fetch returned HTTP ${String(response.status)} for ${archive.checksumsUrl}`);
43
+ return null;
44
+ }
45
+ const body = await response.text();
46
+ for (const line of body.split('\n')) {
47
+ const match = /^([0-9a-f]{64})\s+\*?(.+)$/.exec(line.trim());
48
+ if (match?.[1] && match[2] === archive.pathInChecksums) {
49
+ return match[1];
50
+ }
51
+ }
52
+ verbose(`SHA256SUMS at ${archive.checksumsUrl} does not list ${archive.pathInChecksums}`);
53
+ return null;
54
+ }
55
+ catch (error) {
56
+ verbose(`SHA256SUMS fetch failed for ${archive.checksumsUrl}: ${toError(error).message}`);
57
+ return null;
58
+ }
59
+ }
27
60
  /**
28
61
  * Ensures a valid cached archive exists, downloading it if needed.
29
62
  * @param archive - Resolved archive descriptor
@@ -49,8 +82,8 @@ export async function ensureCachedArchive(archive, cacheDir, onProgress, expecte
49
82
  });
50
83
  }
51
84
  async function cacheEntryExists(archive, cacheDir) {
52
- return ((await pathExists(join(cacheDir, archive.filename))) ||
53
- (await pathExists(join(cacheDir, archive.metadataFilename))));
85
+ return ((await pathExistsStrict(join(cacheDir, archive.filename))) ||
86
+ (await pathExistsStrict(join(cacheDir, archive.metadataFilename))));
54
87
  }
55
88
  /**
56
89
  * Validates a cached archive using sidecar metadata and SHA-256 checksum.
@@ -61,7 +94,10 @@ async function cacheEntryExists(archive, cacheDir) {
61
94
  async function validateCachedArchive(archive, cacheDir, expectedSha256) {
62
95
  const tarballPath = join(cacheDir, archive.filename);
63
96
  const metadataPath = join(cacheDir, archive.metadataFilename);
64
- if (!(await pathExists(tarballPath)) || !(await pathExists(metadataPath))) {
97
+ // Deliberately outside the try/catch below: a permission error probing the
98
+ // cache must propagate instead of reading as "invalid cache" and triggering
99
+ // a re-download into a directory we cannot write anyway.
100
+ if (!(await pathExistsStrict(tarballPath)) || !(await pathExistsStrict(metadataPath))) {
65
101
  return false;
66
102
  }
67
103
  try {
@@ -114,8 +150,32 @@ async function downloadToCache(archive, cacheDir, onProgress, expectedSha256, on
114
150
  promotedTarball = true;
115
151
  onCacheProgress?.(`Calculating source archive SHA-256 for ${archive.filename}...`);
116
152
  const sha256 = await sha256File(tarballPath);
117
- if (expectedSha256 && sha256 !== expectedSha256) {
118
- throw new ChecksumMismatchError(archive.product, expectedSha256, sha256, archive.url);
153
+ if (expectedSha256) {
154
+ // Operator-pinned digest (firefox.sha256 config): takes precedence
155
+ // and always fails closed.
156
+ if (sha256 !== expectedSha256) {
157
+ throw new ChecksumMismatchError(archive.product, expectedSha256, sha256, archive.url);
158
+ }
159
+ }
160
+ else {
161
+ // Default integrity check: verify against Mozilla's published
162
+ // SHA256SUMS. TLS alone is thin trust for the artifact that becomes
163
+ // the git baseline every patch is built on — a compromised CDN
164
+ // response would otherwise be trusted with no signal. Mismatch fails
165
+ // closed (the catch below deletes the artifact); an unfetchable
166
+ // SHA256SUMS degrades to a loud warning so offline/mirror workflows
167
+ // keep working (pin firefox.sha256 in fireforge.json to fail closed
168
+ // even then).
169
+ onCacheProgress?.(`Verifying archive against published SHA256SUMS...`);
170
+ const publishedSha256 = await fetchPublishedSha256(archive);
171
+ if (publishedSha256 === null) {
172
+ warn(`Could not verify ${archive.filename} against Mozilla's published SHA256SUMS ` +
173
+ `(${archive.checksumsUrl} unavailable). The download is trusted on TLS alone — ` +
174
+ 'set firefox.sha256 in fireforge.json to require checksum verification.');
175
+ }
176
+ else if (sha256 !== publishedSha256) {
177
+ throw new ChecksumMismatchError(archive.product, publishedSha256, sha256, archive.url);
178
+ }
119
179
  }
120
180
  onCacheProgress?.(`Writing source archive cache metadata for ${archive.metadataFilename}...`);
121
181
  await writeJson(metadataPath, {
@@ -164,4 +224,3 @@ async function removeArchivePartFiles(archive, cacheDir) {
164
224
  // Cache dir may not exist yet — that's fine.
165
225
  }
166
226
  }
167
- //# sourceMappingURL=firefox-cache.js.map
@@ -134,4 +134,3 @@ export async function downloadFile(url, destPath, onProgress) {
134
134
  }
135
135
  return totalSize > 0 ? totalSize : undefined;
136
136
  }
137
- //# sourceMappingURL=firefox-download.js.map
@@ -2,7 +2,27 @@
2
2
  * Firefox source extraction and installed version detection.
3
3
  */
4
4
  /**
5
- * Extracts a tar.xz archive.
5
+ * Validates entry names from a `tar -tf` listing.
6
+ * @param names - Listing lines (one member name per line)
7
+ * @returns The first unsafe member name, or null when all are safe
8
+ */
9
+ export declare function findUnsafeArchiveEntryName(names: readonly string[]): string | null;
10
+ /**
11
+ * Validates symlink and hardlink targets from a `tar -tvf` listing.
12
+ *
13
+ * A relative link target without `..` segments can only resolve inside the
14
+ * extraction root, so only absolute or `..`-containing targets are rejected.
15
+ * Symlinks are `l`-typed lines with a ` -> target` suffix (the LAST arrow is
16
+ * taken, so a link whose own name contains ` -> ` still parses to its real
17
+ * target); hardlinks print ` link to target` on GNU tar and bsdtar alike.
18
+ *
19
+ * @param verboseLines - `tar -tvf` listing lines
20
+ * @returns A description of the first unsafe link found, or null
21
+ */
22
+ export declare function findUnsafeArchiveLink(verboseLines: readonly string[]): string | null;
23
+ /**
24
+ * Extracts a tar.xz archive after a listing preflight that rejects
25
+ * path-traversal member names and escaping link targets.
6
26
  * @param archivePath - Path to the archive
7
27
  * @param destDir - Destination directory
8
28
  */
@@ -6,9 +6,173 @@ import { join } from 'node:path';
6
6
  import { ExtractionError } from '../errors/download.js';
7
7
  import { elapsedSince } from '../utils/elapsed.js';
8
8
  import { ensureDir, pathExists } from '../utils/fs.js';
9
- import { exec, executableExists } from '../utils/process.js';
9
+ import { exec, execStream, executableExists } from '../utils/process.js';
10
10
  /**
11
- * Extracts a tar.xz archive.
11
+ * Returns true when an archive member path could land outside the
12
+ * extraction root: absolute (POSIX or Windows drive/backslash rooted) or
13
+ * containing a `..` segment.
14
+ */
15
+ function isUnsafeArchivePath(path) {
16
+ if (path.startsWith('/') || path.startsWith('\\'))
17
+ return true;
18
+ if (/^[A-Za-z]:[\\/]/.test(path))
19
+ return true;
20
+ return path.split(/[\\/]/).includes('..');
21
+ }
22
+ /**
23
+ * Validates entry names from a `tar -tf` listing.
24
+ * @param names - Listing lines (one member name per line)
25
+ * @returns The first unsafe member name, or null when all are safe
26
+ */
27
+ export function findUnsafeArchiveEntryName(names) {
28
+ for (const raw of names) {
29
+ const name = raw.trim();
30
+ if (name.length === 0)
31
+ continue;
32
+ if (isUnsafeArchivePath(name))
33
+ return name;
34
+ }
35
+ return null;
36
+ }
37
+ /**
38
+ * Validates symlink and hardlink targets from a `tar -tvf` listing.
39
+ *
40
+ * A relative link target without `..` segments can only resolve inside the
41
+ * extraction root, so only absolute or `..`-containing targets are rejected.
42
+ * Symlinks are `l`-typed lines with a ` -> target` suffix (the LAST arrow is
43
+ * taken, so a link whose own name contains ` -> ` still parses to its real
44
+ * target); hardlinks print ` link to target` on GNU tar and bsdtar alike.
45
+ *
46
+ * @param verboseLines - `tar -tvf` listing lines
47
+ * @returns A description of the first unsafe link found, or null
48
+ */
49
+ export function findUnsafeArchiveLink(verboseLines) {
50
+ for (const line of verboseLines) {
51
+ if (line.startsWith('l')) {
52
+ const arrow = line.lastIndexOf(' -> ');
53
+ if (arrow !== -1) {
54
+ const target = line.slice(arrow + 4).trim();
55
+ if (isUnsafeArchivePath(target))
56
+ return `symlink target: ${target}`;
57
+ continue;
58
+ }
59
+ }
60
+ const hardlink = line.lastIndexOf(' link to ');
61
+ if (hardlink !== -1) {
62
+ const target = line.slice(hardlink + 9).trim();
63
+ if (isUnsafeArchivePath(target))
64
+ return `hardlink target: ${target}`;
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+ /**
70
+ * Lists the archive and rejects members that could escape the extraction
71
+ * root before anything is written to disk: absolute names, `..` traversal,
72
+ * and symlink/hardlink targets that are absolute or `..`-escaping. Modern
73
+ * GNU tar / bsdtar refuse most of these at extraction time; the preflight
74
+ * makes the guarantee explicit and independent of the host tar's defaults.
75
+ *
76
+ * Two listings are needed because they answer different questions and
77
+ * neither is safe to derive from the other: member names come from `-tf`,
78
+ * where the whole line IS the name, while link targets only appear in
79
+ * `-tvf`. Names are deliberately NOT parsed out of the `-tvf` columns — the
80
+ * adjacent uname/gname fields are attacker-controlled in a crafted archive,
81
+ * so a date-shaped owner name could shift the column boundary and hide an
82
+ * absolute member name from the check. Each listing pass costs one full
83
+ * decompression, so the two run concurrently and overlap almost perfectly
84
+ * (they are CPU-bound on separate cores). Measured on a real Firefox ESR
85
+ * 140.9 source tarball (601 MB, bsdtar 3.5.3, macOS): -tf 21.7 s, -tvf
86
+ * 22.1 s, both concurrent 22 s wall, extraction itself 58 s (file-creation
87
+ * syscalls dominate it, not decompression) — so the preflight adds ~22 s
88
+ * (~38%) to this one-time extraction step, versus ~44 s if run
89
+ * sequentially.
90
+ */
91
+ /**
92
+ * Incremental line splitter for streamed listings. Keeps only the current
93
+ * partial line in memory, so a 350k-entry Firefox listing costs O(longest
94
+ * line), not O(listing).
95
+ */
96
+ function createLineScanner(onLine) {
97
+ let buffer = '';
98
+ return {
99
+ push(chunk) {
100
+ buffer += chunk;
101
+ let idx;
102
+ while ((idx = buffer.indexOf('\n')) !== -1) {
103
+ onLine(buffer.slice(0, idx));
104
+ buffer = buffer.slice(idx + 1);
105
+ }
106
+ },
107
+ flush() {
108
+ if (buffer.length > 0) {
109
+ onLine(buffer);
110
+ buffer = '';
111
+ }
112
+ },
113
+ };
114
+ }
115
+ /**
116
+ * Runs one tar listing pass, streaming lines through `checkLine` and
117
+ * returning the first unsafe finding (or null) plus the exit code.
118
+ *
119
+ * Streaming matters for safety, not just memory: the buffered `exec`
120
+ * collector silently truncates at 50 MB, and a full Firefox `-tvf` listing
121
+ * already sits in the 40–50 MB range — so the buffered implementation could
122
+ * scan only the head of the listing while reporting the archive safe. A
123
+ * crafted archive could exploit exactly that by padding benign entries
124
+ * first and hiding a traversal name or absolute link target past the cap.
125
+ * With per-line streaming EVERY entry is scanned, with bounded memory.
126
+ */
127
+ async function runListingScan(archivePath, tarFlag, checkLine) {
128
+ // LC_ALL=C keeps the -tvf column format stable for the link parse.
129
+ const listEnv = { LC_ALL: 'C', LANG: 'C' };
130
+ let unsafe = null;
131
+ let stderrTail = '';
132
+ const scanner = createLineScanner((line) => {
133
+ if (unsafe === null) {
134
+ unsafe = checkLine(line);
135
+ }
136
+ });
137
+ const exitCode = await execStream('tar', [tarFlag, archivePath], {
138
+ env: listEnv,
139
+ onStdout: (chunk) => {
140
+ scanner.push(chunk);
141
+ },
142
+ onStderr: (chunk) => {
143
+ // Keep a small diagnostic tail; the listing itself is the big stream.
144
+ stderrTail = (stderrTail + chunk).slice(-4096);
145
+ },
146
+ });
147
+ scanner.flush();
148
+ return { unsafe, exitCode, stderrTail };
149
+ }
150
+ async function preflightArchiveEntries(archivePath) {
151
+ const [nameScan, linkScan] = await Promise.all([
152
+ runListingScan(archivePath, '-tf', (line) => {
153
+ const name = line.trim();
154
+ if (name.length === 0)
155
+ return null;
156
+ return isUnsafeArchivePath(name) ? name : null;
157
+ }),
158
+ runListingScan(archivePath, '-tvf', (line) => findUnsafeArchiveLink([line])),
159
+ ]);
160
+ if (nameScan.exitCode !== 0) {
161
+ throw new ExtractionError(archivePath, new Error(`tar -tf preflight exited with code ${nameScan.exitCode}:\n${nameScan.stderrTail}`));
162
+ }
163
+ if (nameScan.unsafe !== null) {
164
+ throw new ExtractionError(archivePath, new Error(`Archive rejected: member name could escape the extraction root: ${nameScan.unsafe}`));
165
+ }
166
+ if (linkScan.exitCode !== 0) {
167
+ throw new ExtractionError(archivePath, new Error(`tar -tvf preflight exited with code ${linkScan.exitCode}:\n${linkScan.stderrTail}`));
168
+ }
169
+ if (linkScan.unsafe !== null) {
170
+ throw new ExtractionError(archivePath, new Error(`Archive rejected: link could escape the extraction root (${linkScan.unsafe})`));
171
+ }
172
+ }
173
+ /**
174
+ * Extracts a tar.xz archive after a listing preflight that rejects
175
+ * path-traversal member names and escaping link targets.
12
176
  * @param archivePath - Path to the archive
13
177
  * @param destDir - Destination directory
14
178
  */
@@ -18,18 +182,24 @@ export async function extractTarXz(archivePath, destDir, onProgress) {
18
182
  }
19
183
  await ensureDir(destDir);
20
184
  const startedAt = Date.now();
21
- onProgress?.(`Extracting source archive (${elapsedSince(startedAt)} elapsed)...`);
185
+ onProgress?.(`Validating source archive entries (${elapsedSince(startedAt)} elapsed)...`);
22
186
  const heartbeat = onProgress
23
187
  ? setInterval(() => {
24
188
  onProgress(`Extracting source archive (${elapsedSince(startedAt)} elapsed)...`);
25
189
  }, 15_000)
26
190
  : null;
27
191
  heartbeat?.unref();
28
- const result = await exec('tar', ['-xf', archivePath, '-C', destDir]);
29
- if (heartbeat)
30
- clearInterval(heartbeat);
31
- if (result.exitCode !== 0) {
32
- throw new ExtractionError(archivePath, new Error(`tar exited with code ${result.exitCode}:\n${result.stderr}`));
192
+ try {
193
+ await preflightArchiveEntries(archivePath);
194
+ onProgress?.(`Extracting source archive (${elapsedSince(startedAt)} elapsed)...`);
195
+ const result = await exec('tar', ['-xf', archivePath, '-C', destDir]);
196
+ if (result.exitCode !== 0) {
197
+ throw new ExtractionError(archivePath, new Error(`tar exited with code ${result.exitCode}:\n${result.stderr}`));
198
+ }
199
+ }
200
+ finally {
201
+ if (heartbeat)
202
+ clearInterval(heartbeat);
33
203
  }
34
204
  onProgress?.(`Source archive extracted (${elapsedSince(startedAt)} elapsed)`);
35
205
  }
@@ -62,4 +232,3 @@ export function formatBytes(bytes) {
62
232
  }
63
233
  return `${size.toFixed(1)} ${units[unitIndex]}`;
64
234
  }
65
- //# sourceMappingURL=firefox-extract.js.map
@@ -28,4 +28,3 @@ export async function ensureFirefoxIgnorefileCompatibility(engineDir) {
28
28
  await writeText(hgignorePath, await readText(gitignorePath));
29
29
  return 'created';
30
30
  }
31
- //# sourceMappingURL=firefox-ignorefile.js.map
@@ -35,6 +35,18 @@ export type FirefoxSourcePhase = 'download' | 'extract';
35
35
  /** Callback fired at phase transitions during {@link downloadFirefoxSource}. */
36
36
  export type FirefoxSourcePhaseCallback = (phase: FirefoxSourcePhase) => void;
37
37
  export type FirefoxSourceProgressCallback = (message: string) => void;
38
+ /**
39
+ * Sweeps orphaned working directories left behind by interrupted downloads
40
+ * (`<engine>.tmp-*` from extraction, `<engine>.replacement-*` from --force
41
+ * replacement). Both hold multi-GB partial trees, are worthless after the
42
+ * owning process died, and nothing else ever reclaimed them. Deliberately
43
+ * does NOT touch `<engine>.backup-*`: a backup may hold the operator's
44
+ * previous engine after a failed forced replacement and is their recovery
45
+ * copy — callers should surface its existence instead.
46
+ *
47
+ * @returns names of the directories that were removed
48
+ */
49
+ export declare function sweepOrphanedEngineWorkDirs(destDir: string): Promise<string[]>;
38
50
  /**
39
51
  * Downloads and extracts Firefox source.
40
52
  * @param version - Firefox version to download
@@ -33,6 +33,63 @@ export function getDownloadUrl(version, product = 'firefox') {
33
33
  export function getTarballFilename(version, product = 'firefox') {
34
34
  return resolveArchive(version, product).filename;
35
35
  }
36
+ /**
37
+ * Classifies an extraction failure as archive corruption (invalidate the
38
+ * cache and re-download) versus an environmental problem (keep the cached
39
+ * archive; the retry can reuse it). Environmental signatures are the
40
+ * conservative list — anything unrecognized is treated as corruption so a
41
+ * genuinely bad archive can never survive in the cache.
42
+ */
43
+ function isLikelyArchiveCorruptionError(error) {
44
+ const message = error instanceof Error ? error.message : String(error);
45
+ const code = error instanceof Error ? error.code : undefined;
46
+ if (code === 'ENOSPC' || code === 'EACCES' || code === 'EPERM' || code === 'EDQUOT') {
47
+ return false;
48
+ }
49
+ if (/No space left on device|Permission denied|Disk quota exceeded/i.test(message)) {
50
+ return false;
51
+ }
52
+ // Missing tar binary / preflight refusing an unsafe archive both carry
53
+ // ExtractionError text that names the condition; a missing tar is
54
+ // environmental, an unsafe-entry rejection indicts the archive.
55
+ if (/"tar" command was not found/i.test(message)) {
56
+ return false;
57
+ }
58
+ return true;
59
+ }
60
+ /**
61
+ * Sweeps orphaned working directories left behind by interrupted downloads
62
+ * (`<engine>.tmp-*` from extraction, `<engine>.replacement-*` from --force
63
+ * replacement). Both hold multi-GB partial trees, are worthless after the
64
+ * owning process died, and nothing else ever reclaimed them. Deliberately
65
+ * does NOT touch `<engine>.backup-*`: a backup may hold the operator's
66
+ * previous engine after a failed forced replacement and is their recovery
67
+ * copy — callers should surface its existence instead.
68
+ *
69
+ * @returns names of the directories that were removed
70
+ */
71
+ export async function sweepOrphanedEngineWorkDirs(destDir) {
72
+ const { readdir } = await import('node:fs/promises');
73
+ const { basename, dirname } = await import('node:path');
74
+ const parent = dirname(destDir);
75
+ const enginePrefixTmp = `${basename(destDir)}.tmp-`;
76
+ const enginePrefixReplacement = `${basename(destDir)}.replacement-`;
77
+ let entries;
78
+ try {
79
+ entries = await readdir(parent);
80
+ }
81
+ catch {
82
+ return [];
83
+ }
84
+ const removed = [];
85
+ for (const name of entries) {
86
+ if (name.startsWith(enginePrefixTmp) || name.startsWith(enginePrefixReplacement)) {
87
+ await removeDir(join(parent, name));
88
+ removed.push(name);
89
+ }
90
+ }
91
+ return removed;
92
+ }
36
93
  /**
37
94
  * Downloads and extracts Firefox source.
38
95
  * @param version - Firefox version to download
@@ -60,7 +117,13 @@ export async function downloadFirefoxSource(version, product, destDir, cacheDir,
60
117
  }
61
118
  catch (error) {
62
119
  await removeDir(tempDir);
63
- await invalidateArchiveCache(archive, cacheDir);
120
+ // Only throw away the ~600 MB cached archive when the failure indicts
121
+ // the ARCHIVE, not the environment. ENOSPC, a permission problem in
122
+ // destDir, or tar disappearing mid-run used to invalidate a perfectly
123
+ // good download and force a full re-fetch on retry.
124
+ if (isLikelyArchiveCorruptionError(error)) {
125
+ await invalidateArchiveCache(archive, cacheDir);
126
+ }
64
127
  throw error;
65
128
  }
66
129
  // Firefox source extracts to a subdirectory (e.g., firefox-140.0/)
@@ -80,4 +143,3 @@ export async function downloadFirefoxSource(version, product, destDir, cacheDir,
80
143
  await rename(tempDir, destDir);
81
144
  }
82
145
  }
83
- //# sourceMappingURL=firefox.js.map
@@ -102,4 +102,3 @@ export async function buildCustomDryRunActions(name, componentDir, engineDir, co
102
102
  }
103
103
  return { actions, stepErrors };
104
104
  }
105
- //# sourceMappingURL=furnace-apply-dry-run.js.map
@@ -4,9 +4,13 @@
4
4
  * per-file LOC budget.
5
5
  *
6
6
  * Every helper here degrades gracefully: if the locale jar.mn is missing or
7
- * the FTL tree is non-standard, apply logs a `stepError` rather than
8
- * aborting the whole command. Missing jar.mn on a fork without a locale
9
- * package should not block a working `.mjs`/`.css` from shipping.
7
+ * the FTL tree is non-standard, apply logs an ADVISORY `stepError`
8
+ * (`advisory: true`) rather than aborting the whole command. Advisory step
9
+ * errors are reported as warnings and never trigger rollback — missing
10
+ * jar.mn on a fork without a locale package should not block a working
11
+ * `.mjs`/`.css` from shipping (the blocking/advisory split exists because
12
+ * this contract used to be contradicted: any FTL step error rolled back
13
+ * the whole apply).
10
14
  */
11
15
  import type { CustomComponentConfig, DryRunAction, StepError } from '../types/furnace.js';
12
16
  import { type RollbackJournal } from './furnace-rollback.js';
@@ -5,9 +5,13 @@
5
5
  * per-file LOC budget.
6
6
  *
7
7
  * Every helper here degrades gracefully: if the locale jar.mn is missing or
8
- * the FTL tree is non-standard, apply logs a `stepError` rather than
9
- * aborting the whole command. Missing jar.mn on a fork without a locale
10
- * package should not block a working `.mjs`/`.css` from shipping.
8
+ * the FTL tree is non-standard, apply logs an ADVISORY `stepError`
9
+ * (`advisory: true`) rather than aborting the whole command. Advisory step
10
+ * errors are reported as warnings and never trigger rollback — missing
11
+ * jar.mn on a fork without a locale package should not block a working
12
+ * `.mjs`/`.css` from shipping (the blocking/advisory split exists because
13
+ * this contract used to be contradicted: any FTL step error rolled back
14
+ * the whole apply).
11
15
  */
12
16
  import { join, relative } from 'node:path';
13
17
  import { toError } from '../utils/errors.js';
@@ -86,7 +90,7 @@ export async function applySharedFtlPrune(engineDir, name, ftlDir, config, affec
86
90
  affectedPaths.push(prunedPath);
87
91
  }
88
92
  catch (error) {
89
- stepErrors.push({ step: 'locale jar.mn prune', error: toError(error).message });
93
+ stepErrors.push({ step: 'locale jar.mn prune', error: toError(error).message, advisory: true });
90
94
  }
91
95
  }
92
96
  /**
@@ -139,6 +143,7 @@ export async function applyCustomFtlFile(engineDir, name, componentDir, ftlDir,
139
143
  stepErrors.push({
140
144
  step: 'locale jar.mn registration',
141
145
  error: `Locale jar.mn not found at ${localeJarRel}; component "${name}" ships without a chrome URI for ${ftlFile}. Add the file manually or set furnace.json "ftlBasePath" to a tree that owns a jar.mn.`,
146
+ advisory: true,
142
147
  });
143
148
  return;
144
149
  }
@@ -155,6 +160,7 @@ export async function applyCustomFtlFile(engineDir, name, componentDir, ftlDir,
155
160
  stepErrors.push({
156
161
  step: 'locale jar.mn registration',
157
162
  error: toError(error).message,
163
+ advisory: true,
158
164
  });
159
165
  }
160
166
  }
@@ -199,4 +205,3 @@ export async function removeCustomFtlJarMnEntry(engineDir, fileName, ftlDir, con
199
205
  }
200
206
  await removeLocaleFtlJarMnEntry(engineDir, localeJarRel, tagName, chromeSubPath);
201
207
  }
202
- //# sourceMappingURL=furnace-apply-ftl.js.map
@@ -421,4 +421,3 @@ export async function applyOverrideComponent(engineDir, name, componentDir, conf
421
421
  return { affectedPaths };
422
422
  }
423
423
  export { diffDeletedFiles, extractComponentChecksums, prefixChecksums, } from './furnace-checksum-utils.js';
424
- //# sourceMappingURL=furnace-apply-helpers.js.map
@@ -45,7 +45,8 @@ export function logApplyResult(result, isDryRun) {
45
45
  for (const applied of result.applied) {
46
46
  if (applied.stepErrors && applied.stepErrors.length > 0) {
47
47
  for (const stepErr of applied.stepErrors) {
48
- warn(`${applied.name}: [${stepErr.step}] ${stepErr.error}`);
48
+ const advisoryPrefix = stepErr.advisory === true ? '(advisory) ' : '';
49
+ warn(`${applied.name}: ${advisoryPrefix}[${stepErr.step}] ${stepErr.error}`);
49
50
  }
50
51
  }
51
52
  }
@@ -54,4 +55,3 @@ export function logApplyResult(result, isDryRun) {
54
55
  error(`${err.name} — ${err.error}`);
55
56
  }
56
57
  }
57
- //# sourceMappingURL=furnace-apply-output.js.map
@@ -31,4 +31,10 @@ export declare function applyAllComponents(root: string, dryRun?: boolean, optio
31
31
  }): Promise<ApplyResult & {
32
32
  actions?: DryRunAction[];
33
33
  rollbackJournal?: RollbackJournal;
34
- }>;
34
+ }>; /**
35
+ * Shared context threaded through the batch apply loops. Bundles the state
36
+ * both loops mutate (result/actions/checksums) with the immutable run
37
+ * parameters — the previous 12–13 positional parameters let call sites
38
+ * drift (the named-apply state-wipe bug) and made every new parameter a
39
+ * two-signature change.
40
+ */