@hominis/fireforge 0.44.4 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/dist/bin/fireforge.js +6 -0
  3. package/dist/build-info.json +4 -4
  4. package/dist/src/cli-usage-error.d.ts +46 -0
  5. package/dist/src/cli-usage-error.js +81 -0
  6. package/dist/src/cli.d.ts +5 -1
  7. package/dist/src/cli.js +20 -21
  8. package/dist/src/commands/bootstrap.js +1 -2
  9. package/dist/src/commands/build.js +15 -6
  10. package/dist/src/commands/destructive-decision.d.ts +19 -0
  11. package/dist/src/commands/destructive-decision.js +31 -0
  12. package/dist/src/commands/doctor-check-core.d.ts +0 -6
  13. package/dist/src/commands/doctor-check-core.js +0 -8
  14. package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
  15. package/dist/src/commands/doctor-orphaned-harness.js +4 -12
  16. package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
  17. package/dist/src/commands/doctor-source-pin.d.ts +29 -0
  18. package/dist/src/commands/doctor-source-pin.js +81 -0
  19. package/dist/src/commands/doctor.d.ts +2 -0
  20. package/dist/src/commands/doctor.js +9 -8
  21. package/dist/src/commands/download.js +35 -24
  22. package/dist/src/commands/export-all.js +21 -4
  23. package/dist/src/commands/export-flow.js +15 -60
  24. package/dist/src/commands/export-placement-conflicts.js +3 -3
  25. package/dist/src/commands/export-placement-gate.js +2 -8
  26. package/dist/src/commands/export-placement-policy.js +3 -5
  27. package/dist/src/commands/export-shared.d.ts +42 -23
  28. package/dist/src/commands/export-shared.js +4 -19
  29. package/dist/src/commands/export.js +10 -1
  30. package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
  31. package/dist/src/commands/furnace/authoring-config.js +19 -0
  32. package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
  33. package/dist/src/commands/furnace/create-browser-test.js +3 -8
  34. package/dist/src/commands/furnace/create-dry-run.js +2 -5
  35. package/dist/src/commands/furnace/create-templates.d.ts +17 -1
  36. package/dist/src/commands/furnace/create-templates.js +2 -1
  37. package/dist/src/commands/furnace/create.js +25 -19
  38. package/dist/src/commands/furnace/deploy.js +11 -2
  39. package/dist/src/commands/furnace/init.js +2 -1
  40. package/dist/src/commands/furnace/override.js +28 -19
  41. package/dist/src/commands/furnace/refresh.js +9 -1
  42. package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
  43. package/dist/src/commands/furnace/remove-mutations.js +0 -1
  44. package/dist/src/commands/furnace/remove.js +2 -6
  45. package/dist/src/commands/furnace/rename-browser-test.js +1 -7
  46. package/dist/src/commands/furnace/rename.js +29 -29
  47. package/dist/src/commands/furnace/status.js +12 -9
  48. package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
  49. package/dist/src/commands/furnace/test-file-name.js +39 -0
  50. package/dist/src/commands/furnace/validation-output.d.ts +21 -9
  51. package/dist/src/commands/furnace/validation-output.js +3 -8
  52. package/dist/src/commands/import.js +2 -9
  53. package/dist/src/commands/lint-per-patch.d.ts +2 -2
  54. package/dist/src/commands/lint-per-patch.js +19 -4
  55. package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
  56. package/dist/src/commands/lint.d.ts +2 -1
  57. package/dist/src/commands/lint.js +1 -1
  58. package/dist/src/commands/manifest.js +1 -1
  59. package/dist/src/commands/package.js +2 -4
  60. package/dist/src/commands/patch/compact.js +14 -15
  61. package/dist/src/commands/patch/delete.js +15 -25
  62. package/dist/src/commands/patch/entry-projection.d.ts +13 -0
  63. package/dist/src/commands/patch/entry-projection.js +24 -0
  64. package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
  65. package/dist/src/commands/patch/lint-ignore.js +19 -29
  66. package/dist/src/commands/patch/move-files-create.js +24 -10
  67. package/dist/src/commands/patch/move-files-into.js +38 -37
  68. package/dist/src/commands/patch/move-files.js +4 -13
  69. package/dist/src/commands/patch/rename.js +27 -41
  70. package/dist/src/commands/patch/reorder.js +25 -38
  71. package/dist/src/commands/patch/split-plan.d.ts +4 -5
  72. package/dist/src/commands/patch/split-plan.js +4 -11
  73. package/dist/src/commands/patch/split.js +45 -43
  74. package/dist/src/commands/patch/staged-dependency.js +16 -22
  75. package/dist/src/commands/patch/tier.js +15 -25
  76. package/dist/src/commands/patch-rename-map.d.ts +19 -0
  77. package/dist/src/commands/patch-rename-map.js +36 -0
  78. package/dist/src/commands/pending-resolution.d.ts +15 -0
  79. package/dist/src/commands/pending-resolution.js +25 -0
  80. package/dist/src/commands/re-export-adjacent.d.ts +32 -2
  81. package/dist/src/commands/re-export-adjacent.js +51 -10
  82. package/dist/src/commands/re-export-bulk-scan.js +6 -10
  83. package/dist/src/commands/re-export-drift.d.ts +8 -0
  84. package/dist/src/commands/re-export-drift.js +65 -4
  85. package/dist/src/commands/re-export-files.d.ts +2 -3
  86. package/dist/src/commands/re-export-files.js +38 -32
  87. package/dist/src/commands/re-export-lint.d.ts +3 -4
  88. package/dist/src/commands/re-export-lint.js +9 -2
  89. package/dist/src/commands/re-export-options.js +19 -5
  90. package/dist/src/commands/re-export-register.js +5 -1
  91. package/dist/src/commands/re-export-scan.js +8 -8
  92. package/dist/src/commands/re-export-single.d.ts +22 -3
  93. package/dist/src/commands/re-export-single.js +19 -11
  94. package/dist/src/commands/re-export.js +44 -27
  95. package/dist/src/commands/rebase/abort.js +3 -8
  96. package/dist/src/commands/rebase/continue.js +12 -12
  97. package/dist/src/commands/rebase/index.js +2 -2
  98. package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
  99. package/dist/src/commands/rebase/patch-loop.js +2 -7
  100. package/dist/src/commands/resolve.js +18 -10
  101. package/dist/src/commands/run.d.ts +0 -13
  102. package/dist/src/commands/run.js +12 -25
  103. package/dist/src/commands/setup-support.d.ts +16 -4
  104. package/dist/src/commands/setup-support.js +16 -16
  105. package/dist/src/commands/setup.js +3 -9
  106. package/dist/src/commands/source.js +8 -1
  107. package/dist/src/commands/status-check.js +2 -2
  108. package/dist/src/commands/status-output.js +2 -2
  109. package/dist/src/commands/status.js +3 -4
  110. package/dist/src/commands/test-build-phase.d.ts +2 -2
  111. package/dist/src/commands/test-build-phase.js +33 -8
  112. package/dist/src/commands/test-diagnose.js +14 -5
  113. package/dist/src/commands/test-modes.d.ts +2 -2
  114. package/dist/src/commands/test-modes.js +0 -1
  115. package/dist/src/commands/test-register.js +6 -3
  116. package/dist/src/commands/test-run.js +29 -21
  117. package/dist/src/commands/test-stale-gate.js +8 -5
  118. package/dist/src/commands/test-verdict.d.ts +16 -2
  119. package/dist/src/commands/test-verdict.js +18 -3
  120. package/dist/src/commands/test.js +51 -7
  121. package/dist/src/commands/token-list.d.ts +26 -0
  122. package/dist/src/commands/token-list.js +153 -0
  123. package/dist/src/commands/token.js +17 -1
  124. package/dist/src/commands/tree.js +7 -2
  125. package/dist/src/commands/typecheck.js +9 -9
  126. package/dist/src/commands/wire.js +4 -3
  127. package/dist/src/core/branding.d.ts +0 -7
  128. package/dist/src/core/branding.js +3 -2
  129. package/dist/src/core/browser-wire.js +6 -3
  130. package/dist/src/core/build-audit-platform.d.ts +29 -0
  131. package/dist/src/core/build-audit-platform.js +140 -0
  132. package/dist/src/core/build-audit-registration.js +3 -2
  133. package/dist/src/core/build-audit-skip.d.ts +57 -0
  134. package/dist/src/core/build-audit-skip.js +112 -0
  135. package/dist/src/core/build-audit.d.ts +15 -2
  136. package/dist/src/core/build-audit.js +154 -12
  137. package/dist/src/core/build-baseline.d.ts +45 -28
  138. package/dist/src/core/build-baseline.js +6 -29
  139. package/dist/src/core/build-jar-escalation.d.ts +64 -0
  140. package/dist/src/core/build-jar-escalation.js +128 -0
  141. package/dist/src/core/build-overwrite-guard.js +1 -1
  142. package/dist/src/core/build-prepare.d.ts +5 -2
  143. package/dist/src/core/build-prepare.js +34 -6
  144. package/dist/src/core/config-paths.d.ts +2 -6
  145. package/dist/src/core/config-paths.js +4 -2
  146. package/dist/src/core/config-state.d.ts +0 -6
  147. package/dist/src/core/config-state.js +1 -19
  148. package/dist/src/core/config-validate-build-audit.d.ts +8 -0
  149. package/dist/src/core/config-validate-build-audit.js +80 -0
  150. package/dist/src/core/config-validate-file-size.d.ts +12 -0
  151. package/dist/src/core/config-validate-file-size.js +65 -0
  152. package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
  153. package/dist/src/core/config-validate-patch-policy.js +3 -3
  154. package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
  155. package/dist/src/core/config-validate-test-toolchains.js +4 -5
  156. package/dist/src/core/config-validate.js +20 -3
  157. package/dist/src/core/config.d.ts +2 -2
  158. package/dist/src/core/config.js +11 -15
  159. package/dist/src/core/coverage-extend.js +4 -4
  160. package/dist/src/core/discard-baseline.js +7 -7
  161. package/dist/src/core/engine-precondition.d.ts +1 -9
  162. package/dist/src/core/engine-precondition.js +3 -8
  163. package/dist/src/core/engine-session-lock.js +13 -17
  164. package/dist/src/core/export-moved-code-shape.js +2 -2
  165. package/dist/src/core/file-lock-owner.d.ts +103 -0
  166. package/dist/src/core/file-lock-owner.js +259 -0
  167. package/dist/src/core/file-lock.d.ts +2 -7
  168. package/dist/src/core/file-lock.js +40 -91
  169. package/dist/src/core/firefox-cache.d.ts +13 -1
  170. package/dist/src/core/firefox-cache.js +59 -34
  171. package/dist/src/core/firefox-download.d.ts +15 -0
  172. package/dist/src/core/firefox-download.js +37 -21
  173. package/dist/src/core/firefox-extract.d.ts +6 -5
  174. package/dist/src/core/firefox-extract.js +36 -18
  175. package/dist/src/core/firefox.d.ts +30 -32
  176. package/dist/src/core/firefox.js +5 -36
  177. package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
  178. package/dist/src/core/furnace-apply-dry-run.js +1 -7
  179. package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
  180. package/dist/src/core/furnace-apply-helpers.js +8 -19
  181. package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
  182. package/dist/src/core/furnace-apply.d.ts +1 -1
  183. package/dist/src/core/furnace-apply.js +30 -10
  184. package/dist/src/core/furnace-config.d.ts +1 -1
  185. package/dist/src/core/furnace-config.js +23 -36
  186. package/dist/src/core/furnace-constants.js +3 -2
  187. package/dist/src/core/furnace-dir-entry.d.ts +20 -0
  188. package/dist/src/core/furnace-dir-entry.js +21 -0
  189. package/dist/src/core/furnace-operation.d.ts +2 -0
  190. package/dist/src/core/furnace-operation.js +3 -1
  191. package/dist/src/core/furnace-refresh.d.ts +18 -6
  192. package/dist/src/core/furnace-refresh.js +3 -6
  193. package/dist/src/core/furnace-registration-ast.d.ts +0 -1
  194. package/dist/src/core/furnace-registration-ast.js +0 -3
  195. package/dist/src/core/furnace-registration.d.ts +2 -1
  196. package/dist/src/core/furnace-registration.js +22 -29
  197. package/dist/src/core/furnace-scanner.js +3 -6
  198. package/dist/src/core/furnace-state-persist.d.ts +2 -2
  199. package/dist/src/core/furnace-validate-registration.js +5 -7
  200. package/dist/src/core/git-base.d.ts +3 -1
  201. package/dist/src/core/git-base.js +25 -3
  202. package/dist/src/core/git-diff-new-file.d.ts +40 -0
  203. package/dist/src/core/git-diff-new-file.js +119 -0
  204. package/dist/src/core/git-diff.d.ts +9 -9
  205. package/dist/src/core/git-diff.js +88 -157
  206. package/dist/src/core/git-file-ops.d.ts +0 -7
  207. package/dist/src/core/git-file-ops.js +4 -28
  208. package/dist/src/core/git-readonly-index.d.ts +0 -2
  209. package/dist/src/core/git-readonly-index.js +0 -4
  210. package/dist/src/core/git-status.d.ts +7 -0
  211. package/dist/src/core/git-status.js +21 -9
  212. package/dist/src/core/git.d.ts +0 -6
  213. package/dist/src/core/git.js +14 -43
  214. package/dist/src/core/harness-orphans.d.ts +73 -0
  215. package/dist/src/core/harness-orphans.js +187 -0
  216. package/dist/src/core/history-log.d.ts +19 -0
  217. package/dist/src/core/history-log.js +29 -0
  218. package/dist/src/core/mach-error-hints.d.ts +0 -5
  219. package/dist/src/core/mach-error-hints.js +1 -1
  220. package/dist/src/core/mach-known-noise-filter.d.ts +25 -3
  221. package/dist/src/core/mach-known-noise-filter.js +31 -10
  222. package/dist/src/core/mach-mozconfig.js +2 -1
  223. package/dist/src/core/mach-python.d.ts +2 -0
  224. package/dist/src/core/mach-python.js +12 -7
  225. package/dist/src/core/mach-resource-shim.d.ts +13 -1
  226. package/dist/src/core/mach-resource-shim.js +112 -9
  227. package/dist/src/core/mach.d.ts +46 -53
  228. package/dist/src/core/mach.js +31 -83
  229. package/dist/src/core/marionette-port.d.ts +0 -17
  230. package/dist/src/core/marionette-port.js +12 -58
  231. package/dist/src/core/marionette-preflight.js +9 -27
  232. package/dist/src/core/mochitest-server-port.js +3 -3
  233. package/dist/src/core/moz-manifest-rules.js +37 -33
  234. package/dist/src/core/patch-apply-fuzz.js +4 -4
  235. package/dist/src/core/patch-apply.d.ts +4 -5
  236. package/dist/src/core/patch-apply.js +55 -32
  237. package/dist/src/core/patch-export-coverage.d.ts +0 -9
  238. package/dist/src/core/patch-export-coverage.js +1 -30
  239. package/dist/src/core/patch-export-update.d.ts +18 -1
  240. package/dist/src/core/patch-export-update.js +4 -3
  241. package/dist/src/core/patch-export.d.ts +11 -21
  242. package/dist/src/core/patch-export.js +44 -66
  243. package/dist/src/core/patch-files.d.ts +0 -4
  244. package/dist/src/core/patch-files.js +0 -11
  245. package/dist/src/core/patch-lint-binary.d.ts +6 -12
  246. package/dist/src/core/patch-lint-cache.d.ts +17 -1
  247. package/dist/src/core/patch-lint-cache.js +3 -6
  248. package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
  249. package/dist/src/core/patch-lint-checkjs.js +43 -25
  250. package/dist/src/core/patch-lint-creators.d.ts +2 -6
  251. package/dist/src/core/patch-lint-cross.d.ts +16 -8
  252. package/dist/src/core/patch-lint-cross.js +49 -92
  253. package/dist/src/core/patch-lint-css.js +10 -6
  254. package/dist/src/core/patch-lint-file-size.d.ts +52 -0
  255. package/dist/src/core/patch-lint-file-size.js +53 -0
  256. package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
  257. package/dist/src/core/patch-lint-forward-registration.js +210 -0
  258. package/dist/src/core/patch-lint-jsdoc.js +36 -36
  259. package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
  260. package/dist/src/core/patch-lint-prettier.d.ts +36 -0
  261. package/dist/src/core/patch-lint-prettier.js +126 -0
  262. package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
  263. package/dist/src/core/patch-lint-queue-types.js +12 -0
  264. package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
  265. package/dist/src/core/patch-lint-staged-registration.js +90 -0
  266. package/dist/src/core/patch-lint-unmanaged-companion.d.ts +106 -0
  267. package/dist/src/core/patch-lint-unmanaged-companion.js +214 -0
  268. package/dist/src/core/patch-lint.d.ts +14 -9
  269. package/dist/src/core/patch-lint.js +27 -27
  270. package/dist/src/core/patch-lock.js +3 -3
  271. package/dist/src/core/patch-manifest-io.js +8 -7
  272. package/dist/src/core/patch-parse.d.ts +19 -8
  273. package/dist/src/core/patch-parse.js +25 -0
  274. package/dist/src/core/patch-policy.js +15 -27
  275. package/dist/src/core/patch-source-metadata.d.ts +1 -3
  276. package/dist/src/core/patch-source-metadata.js +0 -4
  277. package/dist/src/core/patch-transform.d.ts +21 -8
  278. package/dist/src/core/patch-transform.js +38 -10
  279. package/dist/src/core/process-cpu-time.js +1 -1
  280. package/dist/src/core/rebase-session.d.ts +2 -8
  281. package/dist/src/core/rebase-session.js +5 -17
  282. package/dist/src/core/register-browser-content.js +3 -2
  283. package/dist/src/core/register-browser-test.js +2 -1
  284. package/dist/src/core/register-module.js +2 -1
  285. package/dist/src/core/register-scaffold.js +5 -4
  286. package/dist/src/core/register-shared-css.js +2 -1
  287. package/dist/src/core/run-log-redact.d.ts +38 -0
  288. package/dist/src/core/run-log-redact.js +69 -0
  289. package/dist/src/core/run-log.d.ts +13 -2
  290. package/dist/src/core/run-log.js +67 -18
  291. package/dist/src/core/signal-critical.js +2 -4
  292. package/dist/src/core/smoke-patterns.d.ts +0 -1
  293. package/dist/src/core/smoke-patterns.js +18 -11
  294. package/dist/src/core/state-file.d.ts +12 -1
  295. package/dist/src/core/state-file.js +14 -1
  296. package/dist/src/core/status-classify.js +2 -1
  297. package/dist/src/core/test-changed-prefs.js +26 -11
  298. package/dist/src/core/test-harness-crash.js +61 -48
  299. package/dist/src/core/test-harness-verdict-notes.d.ts +55 -0
  300. package/dist/src/core/test-harness-verdict-notes.js +48 -0
  301. package/dist/src/core/test-stale-check.d.ts +23 -8
  302. package/dist/src/core/test-stale-check.js +77 -4
  303. package/dist/src/core/test-stall-triage.js +37 -19
  304. package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
  305. package/dist/src/core/test-xpcshell-retry.js +14 -11
  306. package/dist/src/core/token-category.d.ts +33 -0
  307. package/dist/src/core/token-category.js +56 -48
  308. package/dist/src/core/token-dark-mode.js +2 -14
  309. package/dist/src/core/token-inventory.d.ts +56 -0
  310. package/dist/src/core/token-inventory.js +109 -0
  311. package/dist/src/core/token-manager.d.ts +1 -1
  312. package/dist/src/core/token-manager.js +1 -1
  313. package/dist/src/core/token-variant.js +21 -22
  314. package/dist/src/core/toolchain-preflight.js +1 -1
  315. package/dist/src/core/tree-cow.d.ts +0 -2
  316. package/dist/src/core/tree-cow.js +2 -2
  317. package/dist/src/core/tree-store.js +4 -5
  318. package/dist/src/core/typecheck.js +2 -2
  319. package/dist/src/core/wire-dom-fragment.js +6 -17
  320. package/dist/src/core/wire-utils.js +5 -17
  321. package/dist/src/core/xpcshell-appdir.js +2 -4
  322. package/dist/src/errors/base.d.ts +53 -1
  323. package/dist/src/errors/base.js +79 -11
  324. package/dist/src/errors/build.d.ts +28 -0
  325. package/dist/src/errors/build.js +51 -10
  326. package/dist/src/errors/config.d.ts +0 -14
  327. package/dist/src/errors/config.js +13 -34
  328. package/dist/src/errors/download.d.ts +13 -0
  329. package/dist/src/errors/download.js +53 -24
  330. package/dist/src/errors/furnace.js +8 -9
  331. package/dist/src/errors/git.d.ts +8 -8
  332. package/dist/src/errors/git.js +20 -30
  333. package/dist/src/errors/patch.js +8 -10
  334. package/dist/src/errors/rebase.js +7 -7
  335. package/dist/src/types/commands/options.d.ts +96 -123
  336. package/dist/src/types/commands/patches.d.ts +17 -6
  337. package/dist/src/types/config.d.ts +75 -2
  338. package/dist/src/types/index.d.ts +0 -1
  339. package/dist/src/utils/assert.d.ts +3 -15
  340. package/dist/src/utils/assert.js +1 -18
  341. package/dist/src/utils/build-info.d.ts +1 -1
  342. package/dist/src/utils/child-env.d.ts +2 -1
  343. package/dist/src/utils/elapsed.js +10 -1
  344. package/dist/src/utils/fs.d.ts +4 -2
  345. package/dist/src/utils/fs.js +11 -14
  346. package/dist/src/utils/hash.d.ts +12 -0
  347. package/dist/src/utils/hash.js +16 -0
  348. package/dist/src/utils/line-dispatch.d.ts +15 -0
  349. package/dist/src/utils/line-dispatch.js +48 -0
  350. package/dist/src/utils/options.d.ts +1 -7
  351. package/dist/src/utils/options.js +6 -7
  352. package/dist/src/utils/package-root.js +3 -12
  353. package/dist/src/utils/platform.d.ts +0 -4
  354. package/dist/src/utils/process-group.d.ts +2 -1
  355. package/dist/src/utils/process-group.js +20 -19
  356. package/dist/src/utils/process.d.ts +40 -26
  357. package/dist/src/utils/process.js +115 -166
  358. package/dist/src/utils/ps-duration.js +3 -3
  359. package/dist/src/utils/sleep.d.ts +26 -0
  360. package/dist/src/utils/sleep.js +31 -0
  361. package/dist/src/utils/validation.d.ts +2 -38
  362. package/dist/src/utils/validation.js +7 -16
  363. package/docs/configuration.md +136 -1
  364. package/docs/exit-codes.md +34 -20
  365. package/docs/lifecycle-invariants.md +18 -12
  366. package/docs/machine-output.md +31 -8
  367. package/docs/patch-workflow.md +2 -1
  368. package/docs/run-logs.md +44 -4
  369. package/docs/testing.md +70 -11
  370. package/package.json +8 -7
  371. package/dist/src/commands/rebase.d.ts +0 -4
  372. package/dist/src/commands/rebase.js +0 -5
  373. package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
  374. package/dist/src/core/patch-artifact-normalize.js +0 -24
  375. package/dist/src/core/patch-lint-reexports.d.ts +0 -7
  376. package/dist/src/core/patch-lint-reexports.js +0 -8
  377. package/dist/src/core/wire-targets.d.ts +0 -7
  378. package/dist/src/core/wire-targets.js +0 -8
@@ -0,0 +1,112 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Skip classification for the post-build packaging audit.
4
+ *
5
+ * The audit is warn-only, which makes a false positive cheap individually
6
+ * and expensive in aggregate: an operator who learns that `Audit:` lines are
7
+ * usually wrong stops reading the one that is right. Every classifier here
8
+ * exists to remove a warning whose "missing packaged artifact" claim is
9
+ * structurally impossible, and each carries its own reason so the
10
+ * `Packaged:` summary can say what it skipped and why — a run reporting
11
+ * `4 missing` with zero real misses is what this replaces.
12
+ */
13
+ import { readText } from '../utils/fs.js';
14
+ import { normalizePathSlashes } from '../utils/paths.js';
15
+ import { escapeRegex } from '../utils/regex.js';
16
+ import { extractWithBrandingPath } from './mach-mozconfig.js';
17
+ /** Render order for the `Packaged:` summary's per-class breakdown. */
18
+ export const AUDIT_SKIP_REASONS = [
19
+ 'not-packageable',
20
+ 'platform-gated',
21
+ 'platform-gated-ancestor',
22
+ 'tests-not-packaged',
23
+ 'source-unreadable',
24
+ 'branding-not-selected',
25
+ 'storybook-story',
26
+ 'declared-unpackaged',
27
+ ];
28
+ /**
29
+ * Suffixes of Storybook story modules.
30
+ *
31
+ * Firefox's in-tree Storybook renders these through its own Vite config;
32
+ * no `jar.mn` or `moz.build` entry packages one, so "no packaged artifact"
33
+ * is the expected state rather than a missing registration. Kept as a
34
+ * CLOSED list on purpose: generalising to "anything with a dotted infix"
35
+ * would swallow `*.worker.js` and `*.sys.mjs`, which are packaged.
36
+ */
37
+ const STORYBOOK_SUFFIXES = ['.stories.mjs', '.stories.js'];
38
+ /** True for a Storybook story module. */
39
+ export function isStorybookStoryPath(sourcePath) {
40
+ const normalised = normalizePathSlashes(sourcePath);
41
+ return STORYBOOK_SUFFIXES.some((suffix) => normalised.endsWith(suffix));
42
+ }
43
+ /** Prefix under which every branding tree lives. */
44
+ const BRANDING_ROOT = 'browser/branding/';
45
+ /**
46
+ * Reads the branding directory this objdir was configured with.
47
+ *
48
+ * FireForge generates `engine/mozconfig` and writes the
49
+ * `ac_add_options --with-branding=…` line into it, so the selected branding
50
+ * is knowable from the tree. Returns undefined when the mozconfig cannot be
51
+ * read or carries no directive — the caller must then keep warning, because
52
+ * a skip that cannot name its evidence is a masked warning.
53
+ *
54
+ * @param engineDir - Absolute path to the engine root
55
+ * @returns Engine-relative branding path, or undefined
56
+ */
57
+ export async function resolveSelectedBranding(engineDir) {
58
+ try {
59
+ const content = await readText(`${engineDir.replace(/[/\\]+$/, '')}/mozconfig`);
60
+ const selected = extractWithBrandingPath(content);
61
+ return selected === undefined ? undefined : normalizePathSlashes(selected).replace(/\/+$/, '');
62
+ }
63
+ catch {
64
+ return undefined;
65
+ }
66
+ }
67
+ /**
68
+ * True when `sourcePath` lives under a branding tree that is NOT the one
69
+ * this objdir was configured with.
70
+ *
71
+ * An unselected branding directory is not an input to this build at all, so
72
+ * its files can never appear in `dist/`. Worse, the basename walker matched
73
+ * them against the SELECTED branding's same-named artifact and reported the
74
+ * unselected file as "newer than its packaged artifact" — a stale warning
75
+ * about a comparison that has no meaning.
76
+ *
77
+ * @param sourcePath - Engine-relative POSIX path
78
+ * @param selectedBranding - Engine-relative selected branding path
79
+ */
80
+ export function isUnselectedBrandingPath(sourcePath, selectedBranding) {
81
+ if (selectedBranding === undefined)
82
+ return false;
83
+ const normalised = normalizePathSlashes(sourcePath);
84
+ if (!normalised.startsWith(BRANDING_ROOT))
85
+ return false;
86
+ const selected = `${selectedBranding}/`;
87
+ return !normalised.startsWith(selected);
88
+ }
89
+ /**
90
+ * Finds the declaration admitting `sourcePath`, if any.
91
+ *
92
+ * Exact match or a `*`-within-one-segment glob, matching the
93
+ * `support-files` matcher's deliberate narrowness: `**` and other
94
+ * cross-directory shapes are not accepted, because a carve-out that
95
+ * silently widens is exactly the failure this feature is warned against.
96
+ *
97
+ * @param sourcePath - Engine-relative POSIX path
98
+ * @param declarations - The project's `buildAudit.unpackaged` entries
99
+ */
100
+ export function matchUnpackagedDeclaration(sourcePath, declarations) {
101
+ const normalised = normalizePathSlashes(sourcePath);
102
+ return declarations.find((declaration) => {
103
+ const pattern = normalizePathSlashes(declaration.path);
104
+ if (!pattern.includes('*'))
105
+ return pattern === normalised;
106
+ const regex = new RegExp(`^${pattern
107
+ .split('*')
108
+ .map((part) => escapeRegex(part))
109
+ .join('[^/]*')}$`);
110
+ return regex.test(normalised);
111
+ });
112
+ }
@@ -1,3 +1,4 @@
1
+ import { type AuditSkipReason, type UnpackagedDeclaration } from './build-audit-skip.js';
1
2
  import type { BuildBaseline } from './build-baseline-types.js';
2
3
  /**
3
4
  * True for the build-input manifests the build consumes but never packages
@@ -34,9 +35,17 @@ export interface AuditEntry {
34
35
  * updated: an artifact exists and is at least as new as the source.
35
36
  * stale: artifact exists but is older than the source (probable packaging drop).
36
37
  * missing: no artifact with a matching basename was found anywhere under dist/.
37
- * skipped: the file extension / path does not imply packaging; not counted.
38
+ * skipped: the file was not audited; `skipReason` says why.
38
39
  */
39
40
  status: 'updated' | 'stale' | 'missing' | 'skipped';
41
+ /**
42
+ * Why a `skipped` entry was skipped. Present only for `skipped`; the
43
+ * reasons used to exist only as verbose log strings, so the summary line
44
+ * could report `4 missing` on a run with zero real misses and nothing in
45
+ * the counts distinguished a structural non-input from an unregistered
46
+ * file an operator must act on.
47
+ */
48
+ skipReason?: AuditSkipReason;
40
49
  }
41
50
  /** Summary counts for the "Packaged:" end-of-build line. */
42
51
  export interface AuditSummary {
@@ -44,6 +53,8 @@ export interface AuditSummary {
44
53
  stale: number;
45
54
  missing: number;
46
55
  skipped: number;
56
+ /** Per-class skip counts, so the summary can name what it dismissed. */
57
+ skippedByReason: Record<AuditSkipReason, number>;
47
58
  entries: AuditEntry[];
48
59
  }
49
60
  /**
@@ -78,4 +89,6 @@ export declare function isXpcomManifestPath(sourcePath: string): boolean;
78
89
  * @param baseline Optional previous-build baseline marker.
79
90
  * @returns Summary of artifact status counts.
80
91
  */
81
- export declare function auditBuildArtifacts(projectRoot: string, engineDir: string, baseline: BuildBaseline | undefined): Promise<AuditSummary>;
92
+ export declare function auditBuildArtifacts(projectRoot: string, engineDir: string, baseline: BuildBaseline | undefined, options?: {
93
+ unpackaged?: readonly UnpackagedDeclaration[];
94
+ }): Promise<AuditSummary>;
@@ -35,14 +35,18 @@
35
35
  */
36
36
  import { readdir, stat } from 'node:fs/promises';
37
37
  import { basename, join } from 'node:path';
38
+ import { toError } from '../utils/errors.js';
38
39
  import { pathExists } from '../utils/fs.js';
39
40
  import { info, verbose, warn } from '../utils/logger.js';
40
41
  import { normalizePathSlashes } from '../utils/paths.js';
41
- import { detectPlatformGate } from './build-audit-platform.js';
42
+ import { detectAncestorDirsGate, detectPlatformGate } from './build-audit-platform.js';
42
43
  import { collectSameBasenameCandidates, findRegisteredTarget, resolveArtifactByRegistration, } from './build-audit-registration.js';
43
44
  import { countTrailingSegmentMatches, GENERIC_PATH_SEGMENTS, isTestPath, resolveBestArtifact, } from './build-audit-resolve.js';
45
+ import { AUDIT_SKIP_REASONS, isStorybookStoryPath, isUnselectedBrandingPath, matchUnpackagedDeclaration, resolveSelectedBranding, } from './build-audit-skip.js';
44
46
  import { resolveArtifactByKnownTransform } from './build-audit-transforms.js';
45
47
  import { collectChangedEnginePaths } from './engine-changes.js';
48
+ import { loadPatchesManifest } from './patch-manifest-io.js';
49
+ import { buildPatchClaims } from './status-classify.js';
46
50
  /** Path extensions that are conventionally packaged into the Firefox bundle. */
47
51
  const PACKAGEABLE_EXTENSIONS = [
48
52
  '.js',
@@ -98,6 +102,10 @@ export function isBuildInputPath(sourcePath) {
98
102
  export function isJarManifestPath(sourcePath) {
99
103
  return sourcePath === 'jar.mn' || sourcePath.endsWith('/jar.mn');
100
104
  }
105
+ /** Zeroed per-class skip counters. */
106
+ function emptySkipCounts() {
107
+ return Object.fromEntries(AUDIT_SKIP_REASONS.map((reason) => [reason, 0]));
108
+ }
101
109
  /**
102
110
  * Decides whether a source path should be packaged. Returns true for paths
103
111
  * whose extension or directory fragment matches a known-packaged pattern,
@@ -144,7 +152,7 @@ export function isPackageablePath(sourcePath) {
144
152
  * @returns True when the path is a `components.conf` manifest.
145
153
  */
146
154
  export function isXpcomManifestPath(sourcePath) {
147
- return basename(sourcePath.replace(/\\/g, '/')) === 'components.conf';
155
+ return basename(normalizePathSlashes(sourcePath)) === 'components.conf';
148
156
  }
149
157
  /*
150
158
  * Finds the unique obj-star directory with a dist subtree, or undefined
@@ -270,6 +278,55 @@ function isConfidentMatch(source, candidate) {
270
278
  }
271
279
  return false;
272
280
  }
281
+ /**
282
+ * Applies a `buildAudit.unpackaged` carve-out to a classified result.
283
+ *
284
+ * Two behaviours, both mirroring the `--expect-unmanaged` model:
285
+ *
286
+ * - An admitted path is LISTED, not silenced. A carve-out nobody can see
287
+ * is how one quietly widens, so the run says which paths it admitted and
288
+ * on what stated reason.
289
+ * - An admitted path that DOES resolve to a packaged artifact is a STALE
290
+ * carve-out: the declaration asserts a fact about the tree that is no
291
+ * longer true, and suppressing on it would hide a real packaging change.
292
+ * That is reported as a warning, not accepted.
293
+ *
294
+ * A declaration matching nothing that changed this run says nothing —
295
+ * unlike `--expect-unmanaged`, whose unseen entries are reported. The
296
+ * difference is deliberate: that is a per-invocation flag list, where an
297
+ * unmet entry is probably a typo in the command just typed. This is a
298
+ * standing config list checked against only the files that happened to
299
+ * change, so "not met" is the normal case and reporting it would put a
300
+ * warning on every build.
301
+ */
302
+ function applyUnpackagedCarveOut(result, ctx) {
303
+ // Already skipped for a more specific structural reason — the carve-out
304
+ // is redundant there and reporting it would be noise.
305
+ if (result.status === 'skipped')
306
+ return result;
307
+ const declaration = matchUnpackagedDeclaration(result.source, ctx.unpackaged);
308
+ if (declaration === undefined)
309
+ return result;
310
+ if (result.artifact !== undefined) {
311
+ return {
312
+ source: result.source,
313
+ artifact: result.artifact,
314
+ status: 'skipped',
315
+ skipReason: 'declared-unpackaged',
316
+ warning: `Audit: engine/${result.source} is declared unpackaged in fireforge.json ` +
317
+ `("${declaration.reason}") but a packaged artifact exists at ${result.artifact}. ` +
318
+ 'The declaration is stale — remove it, or correct its path.',
319
+ };
320
+ }
321
+ return {
322
+ source: result.source,
323
+ artifact: undefined,
324
+ status: 'skipped',
325
+ skipReason: 'declared-unpackaged',
326
+ notice: `Audit: engine/${result.source} admitted as unpackaged by ` +
327
+ `buildAudit.unpackaged "${declaration.path}" — ${declaration.reason}`,
328
+ };
329
+ }
273
330
  /**
274
331
  * Audits one engine source path and returns its entry. Pure orchestration
275
332
  * helper kept separate so `auditBuildArtifacts` stays under the per-function
@@ -277,12 +334,30 @@ function isConfidentMatch(source, candidate) {
277
334
  */
278
335
  async function auditSinglePath(source, ctx) {
279
336
  if (!isPackageablePath(source)) {
280
- return { source, artifact: undefined, status: 'skipped' };
337
+ return { source, artifact: undefined, status: 'skipped', skipReason: 'not-packageable' };
338
+ }
339
+ if (isStorybookStoryPath(source)) {
340
+ verbose(`Audit: skipping engine/${source} — Storybook story, never packaged.`);
341
+ return { source, artifact: undefined, status: 'skipped', skipReason: 'storybook-story' };
342
+ }
343
+ if (isUnselectedBrandingPath(source, ctx.selectedBranding)) {
344
+ verbose(`Audit: skipping engine/${source} — branding tree is not the selected ${ctx.selectedBranding ?? '?'}, so it is not an input to this objdir.`);
345
+ return { source, artifact: undefined, status: 'skipped', skipReason: 'branding-not-selected' };
281
346
  }
282
347
  const gate = await detectPlatformGate(ctx.engineDir, source);
283
348
  if (gate.gatedOff) {
284
349
  verbose(`Audit: skipping engine/${source} — gated off by "${gate.gateExpression ?? '?'}".`);
285
- return { source, artifact: undefined, status: 'skipped' };
350
+ return { source, artifact: undefined, status: 'skipped', skipReason: 'platform-gated' };
351
+ }
352
+ const ancestorGate = await detectAncestorDirsGate(ctx.engineDir, source);
353
+ if (ancestorGate.gatedOff) {
354
+ verbose(`Audit: skipping engine/${source} — its directory is reached through a DIRS entry in engine/${ancestorGate.gateFile ?? '?'} gated by "${ancestorGate.gateExpression ?? '?'}".`);
355
+ return {
356
+ source,
357
+ artifact: undefined,
358
+ status: 'skipped',
359
+ skipReason: 'platform-gated-ancestor',
360
+ };
286
361
  }
287
362
  // Tests only end up under `_tests/` after `mach package-tests` (or a
288
363
  // test-run that invokes the target) has executed. A plain `mach build`
@@ -291,7 +366,7 @@ async function auditSinglePath(source, ctx) {
291
366
  // Skip audit for test sources when no packaged-tests marker is present.
292
367
  if (isTestPath(source) && !ctx.testsPackaged) {
293
368
  verbose(`Audit: skipping engine/${source} — _tests/${PACKAGED_TESTS_MARKER} not present; full test packaging has not run for this build.`);
294
- return { source, artifact: undefined, status: 'skipped' };
369
+ return { source, artifact: undefined, status: 'skipped', skipReason: 'tests-not-packaged' };
295
370
  }
296
371
  const sourcePath = join(ctx.engineDir, source);
297
372
  let sourceMtime;
@@ -301,7 +376,7 @@ async function auditSinglePath(source, ctx) {
301
376
  }
302
377
  catch {
303
378
  // Deletion that didn't propagate — distinct class of bug, not audited yet.
304
- return { source, artifact: undefined, status: 'skipped' };
379
+ return { source, artifact: undefined, status: 'skipped', skipReason: 'source-unreadable' };
305
380
  }
306
381
  const roots = searchRootsFor(source, ctx.distRoot, ctx.testsRoot);
307
382
  // Registration-aware resolution first: a `jar.mn` entry whose `(source)`
@@ -442,6 +517,39 @@ function relativeManifestPath(engineDir, manifest) {
442
517
  }
443
518
  return manifest;
444
519
  }
520
+ /**
521
+ * Path → owning patch filenames, from the patches manifest's
522
+ * `filesAffected`. Best-effort: a project with no queue (or an unreadable
523
+ * manifest) yields an empty map and the notices render exactly as before.
524
+ */
525
+ async function resolveAuditOwnership(projectRoot) {
526
+ try {
527
+ const patchesDir = join(projectRoot, 'patches');
528
+ const manifest = await loadPatchesManifest(patchesDir);
529
+ if (!manifest)
530
+ return new Map();
531
+ return buildPatchClaims(manifest.patches);
532
+ }
533
+ catch (error) {
534
+ verbose(`Audit: ownership lookup unavailable (${toError(error).message}).`);
535
+ return new Map();
536
+ }
537
+ }
538
+ /**
539
+ * Renders the ownership suffix for one audit notice: the owning patch, or
540
+ * an explicit `unmanaged` mark. An empty map (no queue, unreadable
541
+ * manifest) renders nothing rather than claiming everything is unmanaged —
542
+ * "unmanaged" must mean "checked and unowned", never "could not check".
543
+ */
544
+ function describeOwnership(ownersByPath, source) {
545
+ if (ownersByPath.size === 0)
546
+ return '';
547
+ const owners = ownersByPath.get(source);
548
+ if (owners === undefined || owners.length === 0) {
549
+ return ' Ownership: unmanaged (no patch claims this path).';
550
+ }
551
+ return ` Ownership: ${owners.join(', ')}.`;
552
+ }
445
553
  /**
446
554
  * Runs the post-build audit. Emits per-file warnings for missing or
447
555
  * stale artifacts and a summary info line at the end. Always returns
@@ -452,13 +560,13 @@ function relativeManifestPath(engineDir, manifest) {
452
560
  * @param baseline Optional previous-build baseline marker.
453
561
  * @returns Summary of artifact status counts.
454
562
  */
455
- export async function auditBuildArtifacts(projectRoot, engineDir, baseline) {
456
- void projectRoot;
563
+ export async function auditBuildArtifacts(projectRoot, engineDir, baseline, options = {}) {
457
564
  const summary = {
458
565
  updated: 0,
459
566
  stale: 0,
460
567
  missing: 0,
461
568
  skipped: 0,
569
+ skippedByReason: emptySkipCounts(),
462
570
  entries: [],
463
571
  };
464
572
  const distRoot = await resolveDistRoot(engineDir);
@@ -472,18 +580,52 @@ export async function auditBuildArtifacts(projectRoot, engineDir, baseline) {
472
580
  if (changed.length === 0) {
473
581
  return summary;
474
582
  }
475
- const ctx = { engineDir, distRoot, testsRoot, testsPackaged };
583
+ // Ownership, resolved once for the whole audit. The notices key on "was
584
+ // touched", which on a shared checkout means any concurrent session's
585
+ // dirty files — so an operator gets a registration-shaped warning about a
586
+ // file they never edited and has to census ownership by hand to dismiss
587
+ // it. FireForge already resolves this for `status --ownership`; carrying
588
+ // it here turns a three-line mystery into three dismissible lines.
589
+ const ownersByPath = await resolveAuditOwnership(projectRoot);
590
+ const ctx = {
591
+ engineDir,
592
+ distRoot,
593
+ testsRoot,
594
+ testsPackaged,
595
+ selectedBranding: await resolveSelectedBranding(engineDir),
596
+ unpackaged: options.unpackaged ?? [],
597
+ };
476
598
  for (const source of changed) {
477
- const result = await auditSinglePath(source, ctx);
599
+ const result = applyUnpackagedCarveOut(await auditSinglePath(source, ctx), ctx);
478
600
  summary[result.status] += 1;
601
+ if (result.status === 'skipped' && result.skipReason !== undefined) {
602
+ summary.skippedByReason[result.skipReason] += 1;
603
+ }
479
604
  summary.entries.push({
480
605
  source: result.source,
481
606
  artifact: result.artifact,
482
607
  status: result.status,
608
+ ...(result.skipReason === undefined ? {} : { skipReason: result.skipReason }),
483
609
  });
484
610
  if (result.warning)
485
- warn(result.warning);
611
+ warn(`${result.warning}${describeOwnership(ownersByPath, result.source)}`);
612
+ if (result.notice)
613
+ info(result.notice);
486
614
  }
487
- info(`Packaged: ${summary.updated} updated, ${summary.stale} stale, ${summary.missing} missing, ${summary.skipped} skipped`);
615
+ info(`Packaged: ${summary.updated} updated, ${summary.stale} stale, ${summary.missing} missing, ` +
616
+ `${summary.skipped} skipped${describeSkipBreakdown(summary)}`);
488
617
  return summary;
489
618
  }
619
+ /**
620
+ * Renders the per-class skip breakdown appended to the `Packaged:` line.
621
+ *
622
+ * Without it the summary reported one undifferentiated `skipped` count, so
623
+ * a run that dismissed four unselected-branding files and one
624
+ * ancestor-gated directory looked identical to one that dismissed five
625
+ * unregistered sources — and the four false `missing` entries downstream
626
+ * reported had no counterpart in the counts an operator could check.
627
+ */
628
+ function describeSkipBreakdown(summary) {
629
+ const parts = AUDIT_SKIP_REASONS.filter((reason) => summary.skippedByReason[reason] > 0).map((reason) => `${reason} ${String(summary.skippedByReason[reason])}`);
630
+ return parts.length === 0 ? '' : ` (${parts.join(', ')})`;
631
+ }
@@ -25,8 +25,6 @@ import { type BuildBaseline, type TestPackagingCoverage } from './build-baseline
25
25
  * `'full'`.
26
26
  */
27
27
  export type BaselineBuildKind = 'full' | 'faster';
28
- /** Name of the last-build marker file under `.fireforge/`. */
29
- export declare const BUILD_BASELINE_FILENAME = "last-build.json";
30
28
  /**
31
29
  * Resolves the on-disk path of the build baseline marker.
32
30
  * @param projectRoot - Root directory of the project
@@ -40,36 +38,55 @@ export declare function getBuildBaselinePath(projectRoot: string): string;
40
38
  * @param projectRoot - Root directory of the project
41
39
  */
42
40
  export declare function readBuildBaseline(projectRoot: string): Promise<BuildBaseline | undefined>;
41
+ export interface WriteBuildBaselineOptions {
42
+ /** Root directory of the project. */
43
+ projectRoot: string;
44
+ /** Path to the engine directory. */
45
+ engineDir: string;
46
+ /** Current `binaryName` from fireforge.json. */
47
+ binaryName: string;
48
+ /**
49
+ * Coverage claim of the packaged test runtime this build produced
50
+ * (`'full'`, or the scoped request paths of a `test --build` invocation).
51
+ * Omitted → field left off the marker.
52
+ */
53
+ testPackagingCoverage?: TestPackagingCoverage | undefined;
54
+ /**
55
+ * The baseline this write replaces, when the caller has it. A scoped
56
+ * (non-`'full'`) write carries its `staticComponentsBaseline` forward
57
+ * verbatim — `mach build faster` does not rebake `components.conf`
58
+ * registrations, so the last FULL build stays the honest anchor for the
59
+ * compiled StaticComponents table.
60
+ */
61
+ previousBaseline?: BuildBaseline | undefined;
62
+ /** Invocation shape that produced this baseline. */
63
+ recordedBy?: string | undefined;
64
+ /**
65
+ * `'auto'` (default) refreshes the static-components anchor whenever the
66
+ * coverage claim is `'full'` or absent. `'refresh'` records it even for a
67
+ * scoped coverage claim whose implementation escalated to a full build.
68
+ * `'carry-forward'` always keeps the previous anchor: needed by
69
+ * `--extend-coverage`, whose union can EVALUATE to `'full'` while the
70
+ * build that produced it was still a scoped `mach build faster` that did
71
+ * not rebake the compiled table.
72
+ */
73
+ staticComponentsHandling?: 'auto' | 'refresh' | 'carry-forward' | undefined;
74
+ /**
75
+ * Which mach build produced this baseline (default `'full'`). A
76
+ * `'faster'` write carries the previous record's `jar.mn` fingerprints
77
+ * forward instead of refreshing them — see
78
+ * {@link BuildBaseline.buildInputFingerprints}. Every caller that ran
79
+ * `mach build faster` MUST say so, or the next pre-test build skips an
80
+ * escalation no full build has honoured.
81
+ */
82
+ buildKind?: BaselineBuildKind | undefined;
83
+ }
43
84
  /**
44
85
  * Records a successful build by writing a fresh baseline marker. Captures
45
86
  * engine HEAD SHA (or an empty string when the engine has no HEAD yet) and
46
87
  * the current binaryName. Caller is responsible for only invoking this
47
88
  * after the build exit code was zero.
48
89
  *
49
- * @param projectRoot - Root directory of the project
50
- * @param engineDir - Path to the engine directory
51
- * @param binaryName - Current `binaryName` from fireforge.json
52
- * @param testPackagingCoverage - Coverage claim of the packaged test
53
- * runtime this build produced (`'full'`, or the scoped request paths of
54
- * a `test --build` invocation). Omitted → field left off the marker.
55
- * @param previousBaseline - The baseline this write replaces, when the
56
- * caller has it. A scoped (non-`'full'`) write carries its
57
- * `staticComponentsBaseline` forward verbatim — `mach build faster`
58
- * does not rebake `components.conf` registrations, so the last FULL
59
- * build stays the honest anchor for the compiled StaticComponents table.
60
- * @param recordedBy - Invocation shape that produced this baseline.
61
- * @param staticComponentsHandling - `'auto'` (default) refreshes the
62
- * static-components anchor whenever the coverage claim is `'full'` or
63
- * absent. `'refresh'` records it even for a scoped coverage claim whose
64
- * implementation escalated to a full build. `'carry-forward'` always
65
- * keeps the previous anchor: needed by `--extend-coverage`, whose union
66
- * can EVALUATE to `'full'` while the build that produced it was still a
67
- * scoped `mach build faster` that did not rebake the compiled table.
68
- * @param buildKind - Which mach build produced this baseline (default
69
- * `'full'`). A `'faster'` write carries the previous record's `jar.mn`
70
- * fingerprints forward instead of refreshing them — see
71
- * {@link BuildBaseline.buildInputFingerprints}. Every caller that ran
72
- * `mach build faster` MUST say so, or the next pre-test build skips an
73
- * escalation no full build has honoured.
90
+ * @param options - See {@link WriteBuildBaselineOptions}
74
91
  */
75
- export declare function writeBuildBaseline(projectRoot: string, engineDir: string, binaryName: string, testPackagingCoverage?: TestPackagingCoverage, previousBaseline?: BuildBaseline, recordedBy?: string, staticComponentsHandling?: 'auto' | 'refresh' | 'carry-forward', buildKind?: BaselineBuildKind): Promise<void>;
92
+ export declare function writeBuildBaseline(options: WriteBuildBaselineOptions): Promise<void>;
@@ -16,12 +16,12 @@
16
16
  * on successful build completion; a failed build does not update it, so a
17
17
  * subsequent run still audits against the last known-good tree.
18
18
  */
19
- import { createHash } from 'node:crypto';
20
19
  import { readFile } from 'node:fs/promises';
21
20
  import { join } from 'node:path';
22
21
  import { mapWithConcurrency } from '../utils/concurrency.js';
23
22
  import { getNodeErrorCode, toError } from '../utils/errors.js';
24
23
  import { pathExists, readJson, writeJson } from '../utils/fs.js';
24
+ import { sha256Hex } from '../utils/hash.js';
25
25
  import { verbose } from '../utils/logger.js';
26
26
  import { isBuildInputPath, isJarManifestPath, isPackageablePath, isXpcomManifestPath, } from './build-audit.js';
27
27
  import { DELETED_FILE_FINGERPRINT, } from './build-baseline-types.js';
@@ -36,7 +36,7 @@ import { getUntrackedFiles } from './git-status.js';
36
36
  */
37
37
  const FINGERPRINT_IO_CONCURRENCY = 16;
38
38
  /** Name of the last-build marker file under `.fireforge/`. */
39
- export const BUILD_BASELINE_FILENAME = 'last-build.json';
39
+ const BUILD_BASELINE_FILENAME = 'last-build.json';
40
40
  /**
41
41
  * Resolves the on-disk path of the build baseline marker.
42
42
  * @param projectRoot - Root directory of the project
@@ -71,33 +71,10 @@ export async function readBuildBaseline(projectRoot) {
71
71
  * the current binaryName. Caller is responsible for only invoking this
72
72
  * after the build exit code was zero.
73
73
  *
74
- * @param projectRoot - Root directory of the project
75
- * @param engineDir - Path to the engine directory
76
- * @param binaryName - Current `binaryName` from fireforge.json
77
- * @param testPackagingCoverage - Coverage claim of the packaged test
78
- * runtime this build produced (`'full'`, or the scoped request paths of
79
- * a `test --build` invocation). Omitted → field left off the marker.
80
- * @param previousBaseline - The baseline this write replaces, when the
81
- * caller has it. A scoped (non-`'full'`) write carries its
82
- * `staticComponentsBaseline` forward verbatim — `mach build faster`
83
- * does not rebake `components.conf` registrations, so the last FULL
84
- * build stays the honest anchor for the compiled StaticComponents table.
85
- * @param recordedBy - Invocation shape that produced this baseline.
86
- * @param staticComponentsHandling - `'auto'` (default) refreshes the
87
- * static-components anchor whenever the coverage claim is `'full'` or
88
- * absent. `'refresh'` records it even for a scoped coverage claim whose
89
- * implementation escalated to a full build. `'carry-forward'` always
90
- * keeps the previous anchor: needed by `--extend-coverage`, whose union
91
- * can EVALUATE to `'full'` while the build that produced it was still a
92
- * scoped `mach build faster` that did not rebake the compiled table.
93
- * @param buildKind - Which mach build produced this baseline (default
94
- * `'full'`). A `'faster'` write carries the previous record's `jar.mn`
95
- * fingerprints forward instead of refreshing them — see
96
- * {@link BuildBaseline.buildInputFingerprints}. Every caller that ran
97
- * `mach build faster` MUST say so, or the next pre-test build skips an
98
- * escalation no full build has honoured.
74
+ * @param options - See {@link WriteBuildBaselineOptions}
99
75
  */
100
- export async function writeBuildBaseline(projectRoot, engineDir, binaryName, testPackagingCoverage, previousBaseline, recordedBy, staticComponentsHandling = 'auto', buildKind = 'full') {
76
+ export async function writeBuildBaseline(options) {
77
+ const { projectRoot, engineDir, binaryName, testPackagingCoverage, previousBaseline, recordedBy, staticComponentsHandling = 'auto', buildKind = 'full', } = options;
101
78
  let engineHeadSha = '';
102
79
  try {
103
80
  engineHeadSha = await getHead(engineDir);
@@ -224,7 +201,7 @@ async function collectDirtyFingerprints(engineDir, includePath, contextLabel) {
224
201
  const entries = await mapWithConcurrency(included, FINGERPRINT_IO_CONCURRENCY, async (relPath) => {
225
202
  try {
226
203
  const buffer = await readFile(join(engineDir, relPath));
227
- return [relPath, createHash('sha256').update(buffer).digest('hex')];
204
+ return [relPath, sha256Hex(buffer)];
228
205
  }
229
206
  catch (fileError) {
230
207
  if (getNodeErrorCode(fileError) === 'ENOENT') {
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Decides whether a changed `jar.mn` really forces the pre-test build to
3
+ * escalate from `mach build faster` to a full `mach build`.
4
+ *
5
+ * The 0.41.0 rule was `any changed jar.mn ⇒ full build`, which costs ~10
6
+ * minutes against ~20 s for a chrome-only slice and is paid TWICE per
7
+ * green/no-change pair on a Furnace-touching slice. A downstream experiment
8
+ * (two clean runs, 2026-08-29, against 0.44.4) showed the rule is over-broad
9
+ * for the common case: a jar-only registration of a NEW content file added
10
+ * to an EXISTING `dist/bin` manifest was installed by a plain
11
+ * `fireforge build --ui` both times — `faster/install_dist_bin_browser` named
12
+ * the destination, the file landed in `dist/bin/…/content/browser/` and in
13
+ * the `.app` bundle, and a plain `fireforge test` fetched it over
14
+ * `chrome://` at runtime. Neither run escalated, and neither needed to.
15
+ *
16
+ * What the experiment did NOT exercise, and what therefore still escalates:
17
+ *
18
+ * - a NEW `jar.mn` file. The install manifest for a manifest the backend
19
+ * has never seen does not exist yet, and `mach build faster` consumes
20
+ * install manifests rather than regenerating the backend that writes
21
+ * them.
22
+ * - a manifest whose jar declaration carries a bracketed base-directory
23
+ * prefix (`[some/dir] name.jar:`), which redirects the install
24
+ * destination away from the default chrome root. That redirect is the
25
+ * "non-dist/bin target" half nobody has run.
26
+ *
27
+ * Everything else about the rule is unchanged, and every failure to read or
28
+ * parse a manifest escalates: this narrows a conservative rule, so its
29
+ * error direction stays the slow-but-correct one.
30
+ */
31
+ import type { BuildBaseline } from './build-baseline-types.js';
32
+ /** Why one changed `jar.mn` still forces a full build. */
33
+ export interface JarEscalationCause {
34
+ /** Engine-relative manifest path. */
35
+ path: string;
36
+ /** Operator-facing reason, printed with the escalation notice. */
37
+ reason: string;
38
+ }
39
+ /** Outcome of {@link evaluateJarManifestEscalation}. */
40
+ export interface JarEscalationDecision {
41
+ /** True when at least one changed manifest still needs a full build. */
42
+ escalate: boolean;
43
+ /** One entry per manifest that forces the escalation. */
44
+ causes: JarEscalationCause[];
45
+ /** Manifests the narrowing cleared; kept for the verbose explanation. */
46
+ cleared: string[];
47
+ }
48
+ /**
49
+ * Evaluates every changed `jar.mn` against the narrowed rule.
50
+ *
51
+ * @param engineDir - Absolute engine directory
52
+ * @param changedPaths - Engine-relative paths changed since the last
53
+ * successful build (non-`jar.mn` entries are ignored)
54
+ * @param baseline - Last successful build's baseline (reserved: the
55
+ * fingerprint map already gated which paths reach here)
56
+ * @returns The escalation decision with per-manifest causes
57
+ */
58
+ export declare function evaluateJarManifestEscalation(engineDir: string, changedPaths: readonly string[], baseline: BuildBaseline | undefined): Promise<JarEscalationDecision>;
59
+ /**
60
+ * Renders the operator-facing escalation notice. Names the manifest AND the
61
+ * reason: "a jar.mn changed" is the sentence that made the previous rule
62
+ * impossible to argue with from outside.
63
+ */
64
+ export declare function formatJarEscalationNotice(decision: JarEscalationDecision): string;