@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,128 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Decides whether a changed `jar.mn` really forces the pre-test build to
4
+ * escalate from `mach build faster` to a full `mach build`.
5
+ *
6
+ * The 0.41.0 rule was `any changed jar.mn ⇒ full build`, which costs ~10
7
+ * minutes against ~20 s for a chrome-only slice and is paid TWICE per
8
+ * green/no-change pair on a Furnace-touching slice. A downstream experiment
9
+ * (two clean runs, 2026-08-29, against 0.44.4) showed the rule is over-broad
10
+ * for the common case: a jar-only registration of a NEW content file added
11
+ * to an EXISTING `dist/bin` manifest was installed by a plain
12
+ * `fireforge build --ui` both times — `faster/install_dist_bin_browser` named
13
+ * the destination, the file landed in `dist/bin/…/content/browser/` and in
14
+ * the `.app` bundle, and a plain `fireforge test` fetched it over
15
+ * `chrome://` at runtime. Neither run escalated, and neither needed to.
16
+ *
17
+ * What the experiment did NOT exercise, and what therefore still escalates:
18
+ *
19
+ * - a NEW `jar.mn` file. The install manifest for a manifest the backend
20
+ * has never seen does not exist yet, and `mach build faster` consumes
21
+ * install manifests rather than regenerating the backend that writes
22
+ * them.
23
+ * - a manifest whose jar declaration carries a bracketed base-directory
24
+ * prefix (`[some/dir] name.jar:`), which redirects the install
25
+ * destination away from the default chrome root. That redirect is the
26
+ * "non-dist/bin target" half nobody has run.
27
+ *
28
+ * Everything else about the rule is unchanged, and every failure to read or
29
+ * parse a manifest escalates: this narrows a conservative rule, so its
30
+ * error direction stays the slow-but-correct one.
31
+ */
32
+ import { join } from 'node:path';
33
+ import { toError } from '../utils/errors.js';
34
+ import { readText } from '../utils/fs.js';
35
+ import { verbose } from '../utils/logger.js';
36
+ import { isJarManifestPath } from './build-audit.js';
37
+ import { getUntrackedFilesInDir } from './git-status.js';
38
+ /**
39
+ * A jar declaration line: an optional bracketed base-directory prefix, then
40
+ * the jar name. Matched at column zero because entry lines inside a section
41
+ * are indented — an indented `foo.jar:` is content, not a declaration.
42
+ */
43
+ const JAR_DECLARATION_PATTERN = /^(\[[^\]]*\]\s*)?[^\s[\]]+\.jar:\s*$/;
44
+ /** True when the jar declaration redirects the install base directory. */
45
+ function declaresNonDefaultBase(line) {
46
+ const match = JAR_DECLARATION_PATTERN.exec(line);
47
+ if (!match)
48
+ return false;
49
+ return match[1] !== undefined;
50
+ }
51
+ /**
52
+ * True when a changed `jar.mn` is NEW to the last successful build.
53
+ *
54
+ * Untracked in the engine repo is the reliable tell: a fork's engine tree is
55
+ * a checkout of upstream with patches applied to the WORKTREE, so a manifest
56
+ * a patch created is untracked and one that shipped upstream is tracked. The
57
+ * baseline's fingerprints cannot answer this on their own — they record only
58
+ * the paths that were DIRTY at the last build, so a clean pre-existing
59
+ * manifest has no entry either.
60
+ */
61
+ async function isNewJarManifest(engineDir, path) {
62
+ const untracked = await getUntrackedFilesInDir(engineDir, path);
63
+ return untracked.includes(path);
64
+ }
65
+ /**
66
+ * Evaluates every changed `jar.mn` against the narrowed rule.
67
+ *
68
+ * @param engineDir - Absolute engine directory
69
+ * @param changedPaths - Engine-relative paths changed since the last
70
+ * successful build (non-`jar.mn` entries are ignored)
71
+ * @param baseline - Last successful build's baseline (reserved: the
72
+ * fingerprint map already gated which paths reach here)
73
+ * @returns The escalation decision with per-manifest causes
74
+ */
75
+ export async function evaluateJarManifestEscalation(engineDir, changedPaths, baseline) {
76
+ void baseline;
77
+ const causes = [];
78
+ const cleared = [];
79
+ for (const path of changedPaths.filter(isJarManifestPath)) {
80
+ let isNew;
81
+ try {
82
+ isNew = await isNewJarManifest(engineDir, path);
83
+ }
84
+ catch (error) {
85
+ // Fail closed: an unanswerable "is this new?" keeps the old rule.
86
+ verbose(`jar escalation: could not classify engine/${path} (${toError(error).message}); escalating.`);
87
+ causes.push({ path, reason: 'could not determine whether the manifest is new' });
88
+ continue;
89
+ }
90
+ if (isNew) {
91
+ causes.push({ path, reason: 'new jar.mn (no install manifest exists for it yet)' });
92
+ continue;
93
+ }
94
+ let body;
95
+ try {
96
+ body = await readText(join(engineDir, path));
97
+ }
98
+ catch (error) {
99
+ verbose(`jar escalation: could not read engine/${path} (${toError(error).message}); escalating.`);
100
+ causes.push({ path, reason: 'manifest could not be read' });
101
+ continue;
102
+ }
103
+ const redirected = body
104
+ .split(/\r?\n/)
105
+ .filter((line) => declaresNonDefaultBase(line))
106
+ .map((line) => line.trim());
107
+ if (redirected.length > 0) {
108
+ causes.push({
109
+ path,
110
+ reason: `jar declaration redirects the install base directory (${redirected[0] ?? ''})`,
111
+ });
112
+ continue;
113
+ }
114
+ cleared.push(path);
115
+ }
116
+ return { escalate: causes.length > 0, causes, cleared };
117
+ }
118
+ /**
119
+ * Renders the operator-facing escalation notice. Names the manifest AND the
120
+ * reason: "a jar.mn changed" is the sentence that made the previous rule
121
+ * impossible to argue with from outside.
122
+ */
123
+ export function formatJarEscalationNotice(decision) {
124
+ const detail = decision.causes.map((c) => `engine/${c.path} (${c.reason})`).join('; ');
125
+ return (`Escalating this pre-test build to a full mach build so new install-manifest destinations ` +
126
+ `are created: ${detail}. A changed jar.mn that merely adds entries to an existing ` +
127
+ `dist/bin manifest no longer escalates.`);
128
+ }
@@ -143,7 +143,7 @@ export function formatUnexportedDriftWarning(files) {
143
143
  const owner = entry.owner !== undefined ? ` (owned by ${entry.owner})` : '';
144
144
  return ` ${entry.file} [${entry.classification}]${owner}`;
145
145
  });
146
- return (`This build is about to rewrite ${String(files.length)} engine file(s) from FireForge-owned ` +
146
+ return (`This build is about to rewrite ${files.length} engine file(s) from FireForge-owned ` +
147
147
  'sources, and their current content matches NEITHER a patch body NOR the pristine ' +
148
148
  'baseline — so the edits below are recorded nowhere and the build will destroy them:\n' +
149
149
  `${rows.join('\n')}\n\n` +
@@ -14,6 +14,11 @@ export interface BuildPreparation {
14
14
  reconfigured: boolean;
15
15
  /** A changed packaging manifest cannot be safely handled by `mach build faster`. */
16
16
  fullBuildRequired?: boolean;
17
+ /**
18
+ * Operator-facing explanation for {@link fullBuildRequired}, naming the
19
+ * manifest and why it still escalates. Absent when no escalation applies.
20
+ */
21
+ fullBuildReason?: string;
17
22
  }
18
23
  /** Options for {@link prepareBuildEnvironment}. */
19
24
  export interface PrepareBuildOptions {
@@ -31,8 +36,6 @@ export interface PrepareBuildOptions {
31
36
  */
32
37
  refuseUnexportedDrift?: boolean | undefined;
33
38
  }
34
- /** Packaging manifests whose graph/destination directories need a full build. */
35
- export declare function requiresFullBuildForIncrementalTest(path: string): boolean;
36
39
  /**
37
40
  * Describes an exit code in the shell's 128+N signal convention. Truncated
38
41
  * configure/build logs with a signal-shaped exit (e.g. 144 = 128+16, SIGURG
@@ -12,6 +12,7 @@ import { pathExists } from '../utils/fs.js';
12
12
  import { info, notice, spinner, verbose, warn } from '../utils/logger.js';
13
13
  import { isBrandingSetup, setupBranding } from './branding.js';
14
14
  import { isBuildInputPath, isJarManifestPath } from './build-audit.js';
15
+ import { evaluateJarManifestEscalation, formatJarEscalationNotice, } from './build-jar-escalation.js';
15
16
  import { findUnexportedDriftAtRisk, formatUnexportedDriftWarning, } from './build-overwrite-guard.js';
16
17
  import { collectChangedEnginePaths, dropPathsMatchingFingerprints } from './engine-changes.js';
17
18
  import { applyAllComponents } from './furnace-apply.js';
@@ -23,10 +24,6 @@ import { generateMozconfig, runMachCapture } from './mach.js';
23
24
  import { explainMachError } from './mach-error-hints.js';
24
25
  /** Path fragments of files whose edits invalidate the recursive-make backend. */
25
26
  const BACKEND_INVALIDATING_SUFFIXES = ['moz.build', 'moz.configure', 'Makefile.in'];
26
- /** Packaging manifests whose graph/destination directories need a full build. */
27
- export function requiresFullBuildForIncrementalTest(path) {
28
- return isJarManifestPath(path);
29
- }
30
27
  /**
31
28
  * Build-input manifests changed since the last SUCCESSFUL build — not
32
29
  * merely dirty against HEAD. `collectChangedEnginePaths` unions the
@@ -107,6 +104,29 @@ function buildConfigureFailureError(captured) {
107
104
  (hints.length > 0 ? `\n\n${hints.map((hint) => `Hint: ${hint}`).join('\n')}` : '') +
108
105
  '\n\nBuild stopped because continuing would hide the real configure failure.', 'mach configure');
109
106
  }
107
+ /**
108
+ * Runs the narrowed `jar.mn` escalation rule over this run's changed build
109
+ * inputs and returns the operator-facing reason when a full build is still
110
+ * required, or undefined when the narrowing cleared every changed manifest.
111
+ *
112
+ * Extracted so `prepareBuildEnvironment` stays under the complexity budget.
113
+ *
114
+ * @param engineDir - Absolute engine directory
115
+ * @param changed - Build inputs changed since the last successful build
116
+ * @param baseline - Last successful build's baseline
117
+ */
118
+ async function decideJarEscalation(engineDir, changed, baseline) {
119
+ // Packaging manifests whose graph/destination directories MAY need a full
120
+ // build. Path-shape only: `evaluateJarManifestEscalation` decides whether a
121
+ // given changed manifest actually forces one (a new manifest, or one that
122
+ // redirects its install base directory), so an entry added to an existing
123
+ // `dist/bin` manifest no longer costs a ~10-minute build.
124
+ const decision = await evaluateJarManifestEscalation(engineDir, changed.filter(isJarManifestPath), baseline);
125
+ if (decision.cleared.length > 0) {
126
+ verbose(`jar escalation: ${decision.cleared.length} changed jar.mn add entries to an existing dist/bin manifest; not escalating for ${decision.cleared.join(', ')}.`);
127
+ }
128
+ return decision.escalate ? formatJarEscalationNotice(decision) : undefined;
129
+ }
110
130
  /**
111
131
  * Returns true when the file path matches a pattern that forces
112
132
  * `mach configure` to regenerate the backend. Exported for testing.
@@ -177,10 +197,13 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
177
197
  // work against a stale recursive-make backend.
178
198
  let reconfigured = false;
179
199
  let fullBuildRequired = false;
200
+ let fullBuildReason;
180
201
  if (options.previousBaseline) {
181
202
  const changed = await collectChangedBuildInputs(paths.engine, options.previousBaseline);
182
203
  const invalidating = changed.filter(isBackendInvalidatingFile);
183
- fullBuildRequired = changed.some(requiresFullBuildForIncrementalTest);
204
+ const jarEscalation = await decideJarEscalation(paths.engine, changed, options.previousBaseline);
205
+ fullBuildRequired = jarEscalation !== undefined;
206
+ fullBuildReason = jarEscalation;
184
207
  if (invalidating.length > 0) {
185
208
  // Warning severity: this is the "why is this run slow" explanation,
186
209
  // and an info-level line does not survive an agent output filter that
@@ -300,5 +323,10 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
300
323
  mozconfigSpinner.error('Failed to generate mozconfig');
301
324
  throw error;
302
325
  }
303
- return { furnaceApplied, reconfigured, fullBuildRequired };
326
+ return {
327
+ furnaceApplied,
328
+ reconfigured,
329
+ fullBuildRequired,
330
+ ...(fullBuildReason !== undefined ? { fullBuildReason } : {}),
331
+ };
304
332
  }
@@ -2,16 +2,12 @@
2
2
  * Project path derivation from a root directory.
3
3
  */
4
4
  import type { ProjectPaths } from '../types/config.js';
5
- /** Name of the configuration file */
6
- export declare const CONFIG_FILENAME = "fireforge.json";
7
5
  /** Name of the fireforge data directory */
8
6
  export declare const FIREFORGE_DIR = ".fireforge";
9
- /** Name of the state file */
10
- export declare const STATE_FILENAME = "state.json";
11
7
  /** Supported top-level fireforge.json keys backed by the current schema. */
12
- export declare const SUPPORTED_CONFIG_ROOT_KEYS: readonly ["name", "vendor", "appId", "binaryName", "firefox", "build", "test", "externalToolchains", "license", "wire", "patchLint", "patchPolicy", "typecheck", "markerComment"];
8
+ export declare const SUPPORTED_CONFIG_ROOT_KEYS: readonly ["name", "vendor", "appId", "binaryName", "firefox", "build", "buildAudit", "test", "externalToolchains", "license", "wire", "patchLint", "patchPolicy", "typecheck", "markerComment"];
13
9
  /** Supported config paths that can be read or set without --force. */
14
- export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "firefox.sha256", "firefox.candidate", "build", "build.jobs", "test", "test.canaryPath", "test.canaryTimeoutSeconds", "externalToolchains", "wire", "wire.subscriptDir", "patchLint", "patchLint.checkJs", "patchLint.checkJsStrict", "patchLint.checkJsCompilerOptions", "patchLint.checkJsExtraShim", "patchLint.checkJsTestFiles", "patchLint.checkJsTestShim", "patchLint.rawColorAllowlist", "patchLint.jsdocClassMethods", "patchLint.testAssertionFloor", "patchLint.chromeScriptJsDoc", "patchPolicy", "typecheck", "typecheck.projects", "typecheck.extraShim", "markerComment"];
10
+ export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "firefox.sha256", "firefox.candidate", "firefox.allowUnverifiedDownload", "build", "build.jobs", "test", "test.canaryPath", "test.canaryTimeoutSeconds", "externalToolchains", "wire", "wire.subscriptDir", "patchLint", "patchLint.checkJs", "patchLint.checkJsStrict", "patchLint.checkJsCompilerOptions", "patchLint.checkJsExtraShim", "patchLint.checkJsTestFiles", "patchLint.checkJsTestShim", "patchLint.rawColorAllowlist", "patchLint.jsdocClassMethods", "patchLint.testAssertionFloor", "patchLint.chromeScriptJsDoc", "patchPolicy", "typecheck", "typecheck.projects", "typecheck.extraShim", "markerComment"];
15
11
  /**
16
12
  * Gets all project paths based on a root directory.
17
13
  * @param root - Root directory of the project
@@ -4,11 +4,11 @@
4
4
  */
5
5
  import { join } from 'node:path';
6
6
  /** Name of the configuration file */
7
- export const CONFIG_FILENAME = 'fireforge.json';
7
+ const CONFIG_FILENAME = 'fireforge.json';
8
8
  /** Name of the fireforge data directory */
9
9
  export const FIREFORGE_DIR = '.fireforge';
10
10
  /** Name of the state file */
11
- export const STATE_FILENAME = 'state.json';
11
+ const STATE_FILENAME = 'state.json';
12
12
  /** Name of the engine directory */
13
13
  const ENGINE_DIR = 'engine';
14
14
  /** Name of the patches directory */
@@ -25,6 +25,7 @@ export const SUPPORTED_CONFIG_ROOT_KEYS = [
25
25
  'binaryName',
26
26
  'firefox',
27
27
  'build',
28
+ 'buildAudit',
28
29
  'test',
29
30
  'externalToolchains',
30
31
  'license',
@@ -46,6 +47,7 @@ export const SUPPORTED_CONFIG_PATHS = [
46
47
  'firefox.product',
47
48
  'firefox.sha256',
48
49
  'firefox.candidate',
50
+ 'firefox.allowUnverifiedDownload',
49
51
  'build',
50
52
  'build.jobs',
51
53
  'test',
@@ -8,12 +8,6 @@ import type { FireForgeState } from '../types/config.js';
8
8
  * @returns FireForge state
9
9
  */
10
10
  export declare function loadState(root: string): Promise<FireForgeState>;
11
- /**
12
- * Saves the fireforge state.
13
- * @param root - Root directory of the project
14
- * @param state - State to save
15
- */
16
- export declare function saveState(root: string, state: FireForgeState): Promise<void>;
17
11
  /**
18
12
  * Updates specific fields in the fireforge state.
19
13
  * @param root - Root directory of the project
@@ -24,13 +24,7 @@ function sanitizeProjectState(data) {
24
24
  const state = {};
25
25
  const issues = [];
26
26
  const recoveredFields = [];
27
- const stringFields = [
28
- 'brand',
29
- 'buildMode',
30
- 'lastBuild',
31
- 'downloadedVersion',
32
- 'baseCommit',
33
- ];
27
+ const stringFields = ['brand', 'buildMode', 'downloadedVersion', 'baseCommit'];
34
28
  for (const key of stringFields) {
35
29
  const value = data[key];
36
30
  if (value === undefined) {
@@ -132,18 +126,6 @@ export async function loadState(root) {
132
126
  const paths = getProjectPaths(root);
133
127
  return loadStateFromPath(paths.state);
134
128
  }
135
- /**
136
- * Saves the fireforge state.
137
- * @param root - Root directory of the project
138
- * @param state - State to save
139
- */
140
- export async function saveState(root, state) {
141
- const paths = getProjectPaths(root);
142
- const validatedState = validateFireForgeState(state);
143
- await withStateFileLock(paths.state, async () => {
144
- await writeJson(paths.state, validatedState);
145
- });
146
- }
147
129
  /**
148
130
  * Updates specific fields in the fireforge state.
149
131
  * @param root - Root directory of the project
@@ -0,0 +1,8 @@
1
+ import type { BuildAuditConfig } from '../types/config.js';
2
+ /**
3
+ * Parses the `buildAudit` block.
4
+ *
5
+ * @param raw - The raw `buildAudit` value from fireforge.json
6
+ * @returns The validated block
7
+ */
8
+ export declare function parseBuildAuditConfig(raw: unknown): BuildAuditConfig;
@@ -0,0 +1,80 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Validation for the `buildAudit` block.
4
+ *
5
+ * Split out of `config-validate.ts`, which is at its per-file line budget,
6
+ * following the `config-validate-file-size.ts` precedent.
7
+ *
8
+ * The rules here are stricter than the schema strictly needs, and
9
+ * deliberately so: every entry is a standing suppression of a warning the
10
+ * audit would otherwise raise. A malformed one must fail loudly at config
11
+ * load rather than quietly admit nothing (leaving the operator believing a
12
+ * path is carved out) or quietly admit everything.
13
+ */
14
+ import { ConfigError } from '../errors/config.js';
15
+ import { isContainedRelativePath } from '../utils/paths.js';
16
+ import { isObject } from '../utils/validation.js';
17
+ /** Parses one `buildAudit.unpackaged[]` entry. */
18
+ function parseUnpackagedEntry(raw, field) {
19
+ if (!isObject(raw)) {
20
+ throw new ConfigError(`Config field "${field}" must be a plain object with "path" and "reason"`);
21
+ }
22
+ for (const key of Object.keys(raw)) {
23
+ if (key !== 'path' && key !== 'reason') {
24
+ throw new ConfigError(`Config field "${field}" has unknown key "${key}"`);
25
+ }
26
+ }
27
+ const path = raw['path'];
28
+ if (typeof path !== 'string' || path.trim().length === 0) {
29
+ throw new ConfigError(`Config field "${field}.path" must be a non-empty string`);
30
+ }
31
+ if (!isContainedRelativePath(path)) {
32
+ throw new ConfigError(`Config field "${field}.path" must be an engine-relative path`);
33
+ }
34
+ if (path.includes('**')) {
35
+ // A `**` carve-out admits an entire subtree, which is how a reviewed
36
+ // exception quietly becomes a blanket one. `*` inside a single segment
37
+ // is enough for the shapes this exists for.
38
+ throw new ConfigError(`Config field "${field}.path" must not use "**" — a carve-out may glob within one path segment only`);
39
+ }
40
+ const reason = raw['reason'];
41
+ if (typeof reason !== 'string' || reason.trim().length === 0) {
42
+ // Required on purpose: this is the one audit class FireForge cannot
43
+ // derive from the tree, so the declaration IS the evidence.
44
+ throw new ConfigError(`Config field "${field}.reason" must be a non-empty string explaining why the file is never packaged`);
45
+ }
46
+ return { path, reason };
47
+ }
48
+ /**
49
+ * Parses the `buildAudit` block.
50
+ *
51
+ * @param raw - The raw `buildAudit` value from fireforge.json
52
+ * @returns The validated block
53
+ */
54
+ export function parseBuildAuditConfig(raw) {
55
+ if (!isObject(raw)) {
56
+ throw new ConfigError('Config field "buildAudit" must be a plain object');
57
+ }
58
+ for (const key of Object.keys(raw)) {
59
+ if (key !== 'unpackaged') {
60
+ throw new ConfigError(`Config field "buildAudit" has unknown key "${key}"`);
61
+ }
62
+ }
63
+ const unpackaged = raw['unpackaged'];
64
+ if (unpackaged === undefined)
65
+ return {};
66
+ if (!Array.isArray(unpackaged)) {
67
+ throw new ConfigError('Config field "buildAudit.unpackaged" must be an array');
68
+ }
69
+ const entries = unpackaged.map((entry, index) => parseUnpackagedEntry(entry, `buildAudit.unpackaged[${String(index)}]`));
70
+ const seen = new Set();
71
+ for (const entry of entries) {
72
+ if (seen.has(entry.path)) {
73
+ // Two rows for one path means one of the two reasons is not the
74
+ // operative one, and nothing says which.
75
+ throw new ConfigError(`Config field "buildAudit.unpackaged" declares "${entry.path}" more than once`);
76
+ }
77
+ seen.add(entry.path);
78
+ }
79
+ return { unpackaged: entries };
80
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Validation for `patchLint.fileSizeThresholds`.
3
+ *
4
+ * Split out of `config-validate.ts`, which is at its per-file line budget.
5
+ */
6
+ import type { PatchLintFileSizeThresholds } from '../types/config.js';
7
+ /**
8
+ * Parses the `patchLint.fileSizeThresholds` block. Ordering is validated
9
+ * against the MERGED triple (overrides on top of the defaults), so setting
10
+ * only `warning` cannot land below the default `notice`.
11
+ */
12
+ export declare function parsePatchLintFileSizeThresholds(raw: unknown): PatchLintFileSizeThresholds;
@@ -0,0 +1,65 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Validation for `patchLint.fileSizeThresholds`.
4
+ *
5
+ * Split out of `config-validate.ts`, which is at its per-file line budget.
6
+ */
7
+ import { ConfigError } from '../errors/config.js';
8
+ import { isObject } from '../utils/validation.js';
9
+ import { DEFAULT_FILE_SIZE_THRESHOLDS } from './patch-lint-file-size.js';
10
+ /**
11
+ * Parses one `patchLint.fileSizeThresholds.<tier>` triple. Every field is
12
+ * optional and merges over the built-in default; values must be positive
13
+ * integers and stay ordered `notice <= warning <= error`, because an
14
+ * out-of-order triple would silently disable a band instead of failing.
15
+ */
16
+ function parseFileSizeTier(raw, field) {
17
+ if (!isObject(raw)) {
18
+ throw new ConfigError(`Config field "${field}" must be a plain object`);
19
+ }
20
+ const out = {};
21
+ for (const key of ['notice', 'warning', 'error']) {
22
+ const value = raw[key];
23
+ if (value === undefined)
24
+ continue;
25
+ if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
26
+ throw new ConfigError(`Config field "${field}.${key}" must be a positive integer`);
27
+ }
28
+ out[key] = value;
29
+ }
30
+ for (const key of Object.keys(raw)) {
31
+ if (!['notice', 'warning', 'error'].includes(key)) {
32
+ throw new ConfigError(`Config field "${field}" has unknown key "${key}"`);
33
+ }
34
+ }
35
+ return out;
36
+ }
37
+ /**
38
+ * Parses the `patchLint.fileSizeThresholds` block. Ordering is validated
39
+ * against the MERGED triple (overrides on top of the defaults), so setting
40
+ * only `warning` cannot land below the default `notice`.
41
+ */
42
+ export function parsePatchLintFileSizeThresholds(raw) {
43
+ if (!isObject(raw)) {
44
+ throw new ConfigError('Config field "patchLint.fileSizeThresholds" must be a plain object');
45
+ }
46
+ const out = {};
47
+ for (const tier of ['general', 'test']) {
48
+ const value = raw[tier];
49
+ if (value === undefined)
50
+ continue;
51
+ const parsed = parseFileSizeTier(value, `patchLint.fileSizeThresholds.${tier}`);
52
+ const merged = { ...DEFAULT_FILE_SIZE_THRESHOLDS[tier], ...parsed };
53
+ if (merged.notice > merged.warning || merged.warning > merged.error) {
54
+ throw new ConfigError(`Config field "patchLint.fileSizeThresholds.${tier}" must satisfy notice <= warning <= error ` +
55
+ `(resolved: ${merged.notice}/${merged.warning}/${merged.error})`);
56
+ }
57
+ out[tier] = parsed;
58
+ }
59
+ for (const key of Object.keys(raw)) {
60
+ if (key !== 'general' && key !== 'test') {
61
+ throw new ConfigError(`Config field "patchLint.fileSizeThresholds" has unknown key "${key}"`);
62
+ }
63
+ }
64
+ return out;
65
+ }
@@ -2,6 +2,6 @@
2
2
  * Validation helpers for `fireforge.json#patchPolicy`.
3
3
  */
4
4
  import type { PatchPolicyConfig } from '../types/config.js';
5
- import { parseObject } from '../utils/parse.js';
5
+ import { type ParsedRecord } from '../utils/parse.js';
6
6
  /** Parses and validates the optional patch policy config block. */
7
- export declare function parsePatchPolicyBlock(rec: ReturnType<typeof parseObject>): PatchPolicyConfig;
7
+ export declare function parsePatchPolicyBlock(rec: ParsedRecord): PatchPolicyConfig;
@@ -108,7 +108,7 @@ function parsePatchPolicyReservedRange(raw, label) {
108
108
  return {
109
109
  from,
110
110
  to,
111
- allowed: allowedRaw.map((entry, index) => parseReservedAllowedPatch(entry, `${label}.allowed[${String(index)}]`)),
111
+ allowed: allowedRaw.map((entry, index) => parseReservedAllowedPatch(entry, `${label}.allowed[${index}]`)),
112
112
  };
113
113
  }
114
114
  function assertPolicyRangesDoNotOverlap(ranges, label) {
@@ -160,14 +160,14 @@ export function parsePatchPolicyBlock(rec) {
160
160
  if (!Array.isArray(rangesRaw) || rangesRaw.length === 0) {
161
161
  throw new ConfigError('Config field "patchPolicy.ranges" must be a non-empty array');
162
162
  }
163
- out.ranges = rangesRaw.map((entry, index) => parsePatchPolicyRange(entry, `patchPolicy.ranges[${String(index)}]`));
163
+ out.ranges = rangesRaw.map((entry, index) => parsePatchPolicyRange(entry, `patchPolicy.ranges[${index}]`));
164
164
  assertPolicyRangesDoNotOverlap(out.ranges, 'patchPolicy.ranges');
165
165
  const reservedRangesRaw = rec.raw('reservedRanges');
166
166
  if (reservedRangesRaw !== undefined) {
167
167
  if (!Array.isArray(reservedRangesRaw)) {
168
168
  throw new ConfigError('Config field "patchPolicy.reservedRanges" must be an array');
169
169
  }
170
- out.reservedRanges = reservedRangesRaw.map((entry, index) => parsePatchPolicyReservedRange(entry, `patchPolicy.reservedRanges[${String(index)}]`));
170
+ out.reservedRanges = reservedRangesRaw.map((entry, index) => parsePatchPolicyReservedRange(entry, `patchPolicy.reservedRanges[${index}]`));
171
171
  assertPolicyRangesDoNotOverlap(out.reservedRanges, 'patchPolicy.reservedRanges');
172
172
  }
173
173
  return out;
@@ -1,6 +1,6 @@
1
1
  import type { FireForgeConfig } from '../types/config.js';
2
- import { parseObject } from '../utils/parse.js';
3
- type ConfigRecord = ReturnType<typeof parseObject>;
2
+ import type { ParsedRecord } from '../utils/parse.js';
3
+ type ConfigRecord = ParsedRecord;
4
4
  /** Parses the optional `test` block. */
5
5
  export declare function parseTestBlock(rec: ConfigRecord, config: FireForgeConfig): void;
6
6
  /** Parses optional project-specific external toolchain doctor probes. */
@@ -1,6 +1,5 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { ConfigError } from '../errors/config.js';
3
- import { parseObject } from '../utils/parse.js';
4
3
  import { isContainedRelativePath } from '../utils/paths.js';
5
4
  import { isObject } from '../utils/validation.js';
6
5
  function optionalConfigString(rec, key, label) {
@@ -58,16 +57,16 @@ export function parseExternalToolchainsBlock(rec, config) {
58
57
  }
59
58
  config.externalToolchains = raw.map((entry, index) => {
60
59
  if (!isObject(entry)) {
61
- throw new ConfigError(`Config field "externalToolchains[${String(index)}]" must be an object`);
60
+ throw new ConfigError(`Config field "externalToolchains[${index}]" must be an object`);
62
61
  }
63
62
  const item = entry;
64
63
  const name = item['name'];
65
64
  if (typeof name !== 'string' || name.trim() === '') {
66
- throw new ConfigError(`Config field "externalToolchains[${String(index)}].name" must be a non-empty string`);
65
+ throw new ConfigError(`Config field "externalToolchains[${index}].name" must be a non-empty string`);
67
66
  }
68
67
  const tools = item['tools'];
69
68
  if (!Array.isArray(tools) || tools.length === 0) {
70
- throw new ConfigError(`Config field "externalToolchains[${String(index)}].tools" must be a non-empty array`);
69
+ throw new ConfigError(`Config field "externalToolchains[${index}].tools" must be a non-empty array`);
71
70
  }
72
71
  return {
73
72
  name,
@@ -76,7 +75,7 @@ export function parseExternalToolchainsBlock(rec, config) {
76
75
  });
77
76
  }
78
77
  function parseExternalToolRequirement(tool, index, toolIndex) {
79
- const label = `externalToolchains[${String(index)}].tools[${String(toolIndex)}]`;
78
+ const label = `externalToolchains[${index}].tools[${toolIndex}]`;
80
79
  if (!isObject(tool)) {
81
80
  throw new ConfigError(`Config field "${label}" must be an object`);
82
81
  }