@hominis/fireforge 0.49.2 → 0.50.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 (334) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/README.md +1 -1
  3. package/dist/bin/fireforge.js +6 -0
  4. package/dist/build-info.json +4 -4
  5. package/dist/src/commands/build-prelock.d.ts +20 -0
  6. package/dist/src/commands/build-prelock.js +50 -0
  7. package/dist/src/commands/build.d.ts +3 -1
  8. package/dist/src/commands/build.js +13 -6
  9. package/dist/src/commands/check-report.d.ts +28 -0
  10. package/dist/src/commands/check-report.js +78 -0
  11. package/dist/src/commands/check-run.d.ts +28 -0
  12. package/dist/src/commands/check-run.js +104 -0
  13. package/dist/src/commands/check-steps.d.ts +67 -0
  14. package/dist/src/commands/check-steps.js +175 -0
  15. package/dist/src/commands/check.d.ts +32 -0
  16. package/dist/src/commands/check.js +160 -0
  17. package/dist/src/commands/config.js +5 -6
  18. package/dist/src/commands/doctor-engine-eslint.js +6 -1
  19. package/dist/src/commands/doctor-source-pin.js +4 -1
  20. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  21. package/dist/src/commands/export-adopt-check.js +98 -0
  22. package/dist/src/commands/export-flow.js +29 -14
  23. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  24. package/dist/src/commands/export-shared.d.ts +2 -1
  25. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  26. package/dist/src/commands/export-staged-registration.js +91 -0
  27. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  28. package/dist/src/commands/forward-import-remedy.js +63 -0
  29. package/dist/src/commands/furnace/apply.js +3 -3
  30. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  31. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  32. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  33. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  34. package/dist/src/commands/furnace/create.js +2 -2
  35. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  36. package/dist/src/commands/furnace/deploy-named.js +107 -0
  37. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  38. package/dist/src/commands/furnace/deploy.js +50 -86
  39. package/dist/src/commands/furnace/diff.js +26 -3
  40. package/dist/src/commands/furnace/index.js +66 -41
  41. package/dist/src/commands/furnace/init.js +13 -6
  42. package/dist/src/commands/furnace/override.js +4 -2
  43. package/dist/src/commands/furnace/preview.js +4 -3
  44. package/dist/src/commands/furnace/refresh.js +2 -2
  45. package/dist/src/commands/furnace/remove.js +4 -3
  46. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  47. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  48. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  49. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  50. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  51. package/dist/src/commands/furnace/rename-registrations.js +93 -0
  52. package/dist/src/commands/furnace/rename.d.ts +2 -1
  53. package/dist/src/commands/furnace/rename.js +28 -66
  54. package/dist/src/commands/furnace/scan.d.ts +9 -4
  55. package/dist/src/commands/furnace/scan.js +11 -9
  56. package/dist/src/commands/furnace/sync.js +3 -2
  57. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  58. package/dist/src/commands/furnace/validate-report.js +91 -0
  59. package/dist/src/commands/furnace/validate.d.ts +1 -1
  60. package/dist/src/commands/furnace/validate.js +127 -114
  61. package/dist/src/commands/furnace/validation-output.js +14 -3
  62. package/dist/src/commands/lint-per-patch.js +31 -60
  63. package/dist/src/commands/lint-report.d.ts +8 -4
  64. package/dist/src/commands/lint-report.js +15 -5
  65. package/dist/src/commands/lint.js +15 -34
  66. package/dist/src/commands/manifest.js +2 -0
  67. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  68. package/dist/src/commands/patch/delete.d.ts +1 -1
  69. package/dist/src/commands/patch/delete.js +13 -98
  70. package/dist/src/commands/patch/index.d.ts +1 -1
  71. package/dist/src/commands/patch/index.js +4 -2
  72. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  73. package/dist/src/commands/patch/lint-ignore.js +65 -28
  74. package/dist/src/commands/patch/move-files-create.js +26 -17
  75. package/dist/src/commands/patch/move-files-into.js +5 -3
  76. package/dist/src/commands/patch/move-files.js +3 -1
  77. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  78. package/dist/src/commands/patch/removal-dependents.js +133 -0
  79. package/dist/src/commands/patch/rename.js +7 -0
  80. package/dist/src/commands/patch/retire.d.ts +34 -0
  81. package/dist/src/commands/patch/retire.js +128 -0
  82. package/dist/src/commands/patch/split-plan.d.ts +7 -7
  83. package/dist/src/commands/patch/split-plan.js +45 -28
  84. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  85. package/dist/src/commands/patch/split-staged.js +202 -0
  86. package/dist/src/commands/patch/split.d.ts +5 -0
  87. package/dist/src/commands/patch/split.js +51 -15
  88. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  89. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  90. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  91. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  92. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  93. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  94. package/dist/src/commands/patch/staged-dependency.js +14 -41
  95. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  96. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  97. package/dist/src/commands/re-export-adjacent.d.ts +45 -2
  98. package/dist/src/commands/re-export-adjacent.js +156 -37
  99. package/dist/src/commands/re-export-drift.d.ts +36 -8
  100. package/dist/src/commands/re-export-drift.js +73 -49
  101. package/dist/src/commands/re-export-files.js +11 -8
  102. package/dist/src/commands/re-export-lint.d.ts +2 -1
  103. package/dist/src/commands/re-export-lint.js +23 -10
  104. package/dist/src/commands/re-export-options.js +45 -4
  105. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  106. package/dist/src/commands/re-export-preflight.js +160 -0
  107. package/dist/src/commands/re-export-register.js +42 -17
  108. package/dist/src/commands/re-export-removed.d.ts +12 -1
  109. package/dist/src/commands/re-export-removed.js +41 -11
  110. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  111. package/dist/src/commands/re-export-scan-owner.js +116 -0
  112. package/dist/src/commands/re-export-scan.d.ts +17 -9
  113. package/dist/src/commands/re-export-scan.js +56 -23
  114. package/dist/src/commands/re-export-single.js +22 -6
  115. package/dist/src/commands/re-export-summary.d.ts +28 -0
  116. package/dist/src/commands/re-export-summary.js +36 -0
  117. package/dist/src/commands/re-export.js +37 -24
  118. package/dist/src/commands/rebase/index.js +17 -17
  119. package/dist/src/commands/run.js +16 -2
  120. package/dist/src/commands/status-check.d.ts +55 -16
  121. package/dist/src/commands/status-check.js +92 -37
  122. package/dist/src/commands/status-json.d.ts +22 -9
  123. package/dist/src/commands/status-json.js +47 -32
  124. package/dist/src/commands/status-output.js +3 -1
  125. package/dist/src/commands/status-scope.d.ts +37 -0
  126. package/dist/src/commands/status-scope.js +54 -0
  127. package/dist/src/commands/status.js +66 -23
  128. package/dist/src/commands/test-browser-preflight.js +19 -3
  129. package/dist/src/commands/test-build-furnace.d.ts +21 -0
  130. package/dist/src/commands/test-build-furnace.js +48 -0
  131. package/dist/src/commands/test-build-phase.js +47 -41
  132. package/dist/src/commands/test-checkjs.js +7 -0
  133. package/dist/src/commands/test-diagnose.js +7 -0
  134. package/dist/src/commands/test-modes.d.ts +16 -1
  135. package/dist/src/commands/test-modes.js +24 -1
  136. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  137. package/dist/src/commands/test-multi-verdict.js +111 -0
  138. package/dist/src/commands/test-orchestrate.d.ts +43 -0
  139. package/dist/src/commands/test-orchestrate.js +222 -0
  140. package/dist/src/commands/test-perf-scope.d.ts +29 -0
  141. package/dist/src/commands/test-perf-scope.js +83 -0
  142. package/dist/src/commands/test-prelock.d.ts +51 -0
  143. package/dist/src/commands/test-prelock.js +177 -0
  144. package/dist/src/commands/test-register.js +54 -10
  145. package/dist/src/commands/test-run.js +4 -1
  146. package/dist/src/commands/test-stale-gate.js +57 -27
  147. package/dist/src/commands/test-verdict.d.ts +10 -1
  148. package/dist/src/commands/test-verdict.js +28 -1
  149. package/dist/src/commands/test.d.ts +6 -1
  150. package/dist/src/commands/test.js +66 -60
  151. package/dist/src/commands/token-lint.d.ts +14 -0
  152. package/dist/src/commands/token-lint.js +111 -0
  153. package/dist/src/commands/token.js +10 -0
  154. package/dist/src/commands/verify-scope.d.ts +28 -0
  155. package/dist/src/commands/verify-scope.js +67 -0
  156. package/dist/src/commands/verify.d.ts +10 -2
  157. package/dist/src/commands/verify.js +59 -35
  158. package/dist/src/core/build-baseline-types.d.ts +46 -0
  159. package/dist/src/core/build-baseline-validate.js +19 -0
  160. package/dist/src/core/build-baseline.d.ts +5 -1
  161. package/dist/src/core/build-baseline.js +3 -1
  162. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  163. package/dist/src/core/build-overwrite-guard.js +84 -4
  164. package/dist/src/core/build-prepare.d.ts +7 -0
  165. package/dist/src/core/build-prepare.js +14 -8
  166. package/dist/src/core/build-run-record.d.ts +74 -0
  167. package/dist/src/core/build-run-record.js +136 -0
  168. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  169. package/dist/src/core/checkjs-support-heads.js +104 -0
  170. package/dist/src/core/config-paths.d.ts +2 -2
  171. package/dist/src/core/config-paths.js +8 -0
  172. package/dist/src/core/config-validate-check.d.ts +8 -0
  173. package/dist/src/core/config-validate-check.js +143 -0
  174. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  175. package/dist/src/core/config-validate-file-size.js +91 -2
  176. package/dist/src/core/config-validate-history.d.ts +11 -0
  177. package/dist/src/core/config-validate-history.js +38 -0
  178. package/dist/src/core/config-validate-patch-policy.js +100 -0
  179. package/dist/src/core/config-validate-test-toolchains.js +7 -0
  180. package/dist/src/core/config-validate-token-scales.d.ts +6 -0
  181. package/dist/src/core/config-validate-token-scales.js +50 -0
  182. package/dist/src/core/config-validate.js +14 -5
  183. package/dist/src/core/config.d.ts +5 -0
  184. package/dist/src/core/config.js +16 -0
  185. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  186. package/dist/src/core/coverage-dispatch.js +44 -0
  187. package/dist/src/core/coverage-extend.d.ts +70 -45
  188. package/dist/src/core/coverage-extend.js +199 -144
  189. package/dist/src/core/coverage-records.d.ts +97 -0
  190. package/dist/src/core/coverage-records.js +350 -0
  191. package/dist/src/core/destructive.d.ts +7 -4
  192. package/dist/src/core/destructive.js +10 -10
  193. package/dist/src/core/engine-session-lock.d.ts +10 -1
  194. package/dist/src/core/engine-session-lock.js +28 -8
  195. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  196. package/dist/src/core/export-moved-code-shape.js +135 -44
  197. package/dist/src/core/file-lock-queue.d.ts +67 -0
  198. package/dist/src/core/file-lock-queue.js +221 -0
  199. package/dist/src/core/file-lock.d.ts +4 -25
  200. package/dist/src/core/file-lock.js +105 -138
  201. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  202. package/dist/src/core/furnace-apply-output.js +31 -1
  203. package/dist/src/core/furnace-apply.js +12 -2
  204. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  205. package/dist/src/core/furnace-composed-source.js +167 -0
  206. package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
  207. package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
  208. package/dist/src/core/furnace-config-validate.js +8 -0
  209. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  210. package/dist/src/core/furnace-css-fragments.js +26 -0
  211. package/dist/src/core/furnace-operation.d.ts +3 -3
  212. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  213. package/dist/src/core/furnace-registration-remove.js +53 -17
  214. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  215. package/dist/src/core/furnace-registration-rename.js +77 -0
  216. package/dist/src/core/furnace-registration.d.ts +14 -4
  217. package/dist/src/core/furnace-registration.js +44 -10
  218. package/dist/src/core/furnace-staleness.d.ts +7 -0
  219. package/dist/src/core/furnace-staleness.js +34 -24
  220. package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
  221. package/dist/src/core/furnace-validate-accessibility.js +10 -1
  222. package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
  223. package/dist/src/core/furnace-validate-exceptions.js +43 -0
  224. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  225. package/dist/src/core/furnace-validate-icon-button.js +49 -0
  226. package/dist/src/core/furnace-validate.js +1 -1
  227. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  228. package/dist/src/core/harness-port-fallback.js +96 -0
  229. package/dist/src/core/history-log-file.d.ts +61 -0
  230. package/dist/src/core/history-log-file.js +157 -0
  231. package/dist/src/core/history-log.d.ts +2 -1
  232. package/dist/src/core/history-log.js +2 -1
  233. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  234. package/dist/src/core/json-preserving-edit.js +199 -0
  235. package/dist/src/core/mach.js +8 -1
  236. package/dist/src/core/negative-control-diff.d.ts +39 -0
  237. package/dist/src/core/negative-control-diff.js +136 -0
  238. package/dist/src/core/negative-control.d.ts +138 -0
  239. package/dist/src/core/negative-control.js +356 -0
  240. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  241. package/dist/src/core/objdir-install-manifests.js +226 -0
  242. package/dist/src/core/patch-export-update.d.ts +2 -0
  243. package/dist/src/core/patch-export-update.js +1 -0
  244. package/dist/src/core/patch-export.js +3 -4
  245. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
  246. package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
  247. package/dist/src/core/patch-lint-checkjs.js +8 -11
  248. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  249. package/dist/src/core/patch-lint-code-lines.js +105 -0
  250. package/dist/src/core/patch-lint-cross.js +12 -4
  251. package/dist/src/core/patch-lint-css.d.ts +8 -2
  252. package/dist/src/core/patch-lint-css.js +10 -4
  253. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  254. package/dist/src/core/patch-lint-file-size.js +22 -11
  255. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
  256. package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
  257. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  258. package/dist/src/core/patch-lint-ignore.d.ts +152 -0
  259. package/dist/src/core/patch-lint-ignore.js +299 -0
  260. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  261. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  262. package/dist/src/core/patch-lint-size.d.ts +117 -0
  263. package/dist/src/core/patch-lint-size.js +323 -0
  264. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  265. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  266. package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
  267. package/dist/src/core/patch-lint-token-scale.js +109 -0
  268. package/dist/src/core/patch-lint.d.ts +11 -83
  269. package/dist/src/core/patch-lint.js +64 -286
  270. package/dist/src/core/patch-lock.js +7 -6
  271. package/dist/src/core/patch-manifest-consistency.js +7 -6
  272. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  273. package/dist/src/core/patch-manifest-io.js +19 -8
  274. package/dist/src/core/patch-manifest-validate.js +34 -2
  275. package/dist/src/core/patch-order-legal.d.ts +25 -0
  276. package/dist/src/core/patch-order-legal.js +83 -0
  277. package/dist/src/core/patch-policy-content.d.ts +40 -0
  278. package/dist/src/core/patch-policy-content.js +216 -0
  279. package/dist/src/core/patch-policy.d.ts +16 -3
  280. package/dist/src/core/patch-policy.js +16 -9
  281. package/dist/src/core/patch-retired.d.ts +35 -0
  282. package/dist/src/core/patch-retired.js +54 -0
  283. package/dist/src/core/patch-subset.d.ts +16 -0
  284. package/dist/src/core/patch-subset.js +66 -0
  285. package/dist/src/core/preflight-wait.d.ts +20 -0
  286. package/dist/src/core/preflight-wait.js +38 -0
  287. package/dist/src/core/rebase-from-version.d.ts +46 -0
  288. package/dist/src/core/rebase-from-version.js +71 -0
  289. package/dist/src/core/run-log.d.ts +20 -0
  290. package/dist/src/core/run-log.js +89 -3
  291. package/dist/src/core/run-profile.d.ts +7 -0
  292. package/dist/src/core/run-profile.js +7 -1
  293. package/dist/src/core/smoke-patterns.d.ts +5 -0
  294. package/dist/src/core/smoke-patterns.js +13 -0
  295. package/dist/src/core/test-harness-crash.d.ts +18 -1
  296. package/dist/src/core/test-harness-crash.js +6 -4
  297. package/dist/src/core/test-harness-signature.d.ts +17 -0
  298. package/dist/src/core/test-harness-signature.js +4 -0
  299. package/dist/src/core/test-run-completeness.d.ts +57 -0
  300. package/dist/src/core/test-run-completeness.js +144 -0
  301. package/dist/src/core/test-stale-check.d.ts +27 -1
  302. package/dist/src/core/test-stale-check.js +39 -49
  303. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  304. package/dist/src/core/test-stale-exempt.js +70 -0
  305. package/dist/src/core/token-lint.d.ts +50 -0
  306. package/dist/src/core/token-lint.js +126 -0
  307. package/dist/src/core/tree-guard.js +3 -0
  308. package/dist/src/core/typecheck-shim.d.ts +1 -1
  309. package/dist/src/core/typecheck-shim.js +90 -2
  310. package/dist/src/index.d.ts +8 -3
  311. package/dist/src/index.js +3 -1
  312. package/dist/src/types/commands/index.d.ts +2 -2
  313. package/dist/src/types/commands/options.d.ts +148 -21
  314. package/dist/src/types/commands/patches.d.ts +75 -1
  315. package/dist/src/types/config.d.ts +192 -0
  316. package/dist/src/types/config.js +9 -0
  317. package/dist/src/types/furnace.d.ts +43 -1
  318. package/dist/src/types/furnace.js +7 -0
  319. package/dist/src/types/index.d.ts +1 -1
  320. package/dist/src/utils/options.d.ts +34 -6
  321. package/dist/src/utils/options.js +86 -5
  322. package/dist/src/utils/path-glob.d.ts +15 -0
  323. package/dist/src/utils/path-glob.js +47 -0
  324. package/dist/src/utils/regex.d.ts +15 -3
  325. package/dist/src/utils/regex.js +37 -16
  326. package/docs/configuration.md +282 -9
  327. package/docs/exit-codes.md +18 -1
  328. package/docs/furnace.md +97 -3
  329. package/docs/lifecycle-invariants.md +52 -19
  330. package/docs/machine-output.md +74 -12
  331. package/docs/patch-workflow.md +297 -22
  332. package/docs/run-logs.md +20 -1
  333. package/docs/testing.md +276 -20
  334. package/package.json +1 -1
@@ -0,0 +1,74 @@
1
+ /**
2
+ * A record of the mach build in flight, kept in `.fireforge/build-run.json`
3
+ * so any session on the checkout can see why the bundle is missing.
4
+ *
5
+ * An interrupted `fireforge build` (a killed peer session, a closed
6
+ * terminal) can leave `dist/<App>.app/Contents/MacOS/` without its
7
+ * executable. Every later `fireforge test`, in every session, then failed
8
+ * at launch with nothing saying a build had died there, and the sessions
9
+ * that had not run the build had no way to find out.
10
+ *
11
+ * The record is written when a protected mach build starts and removed when
12
+ * it succeeds. A failed build leaves it with `status: "failed"` and the exit
13
+ * code. A build that never reached either end, because its process died,
14
+ * leaves `status: "running"` with a PID that no longer exists, which is how
15
+ * an interrupted run is recognized. The next build that runs to the end
16
+ * replaces whatever was there.
17
+ *
18
+ * Best-effort throughout: a build never fails because its record could not
19
+ * be written, and an unreadable record reads as "no record".
20
+ */
21
+ /** The persisted record. */
22
+ interface BuildRunRecord {
23
+ /** Whether the build is (or was last seen) running, or ended in failure. */
24
+ status: 'running' | 'failed';
25
+ /** PID of the FireForge process that ran the build. */
26
+ pid: number;
27
+ /** That process's boot-relative start tick, where procfs exposes one. */
28
+ startTick?: number;
29
+ /** The FireForge invocation, e.g. `fireforge build --ui`. */
30
+ command: string;
31
+ /** The mach command, e.g. `mach build faster`. */
32
+ machCommand: string;
33
+ /** ISO time the mach build started. */
34
+ startedAt: string;
35
+ /** ISO time a failed build ended. */
36
+ endedAt?: string;
37
+ /** Exit code of a failed build, when mach returned one. */
38
+ exitCode?: number;
39
+ }
40
+ /** What the record says about the last build. */
41
+ export type BuildRunState = {
42
+ kind: 'none';
43
+ } | {
44
+ kind: 'running' | 'interrupted' | 'failed';
45
+ record: BuildRunRecord;
46
+ };
47
+ /**
48
+ * Runs a mach build with its run record maintained around it. The record
49
+ * is removed on a zero exit and kept as `failed` on a non-zero exit or a
50
+ * throw, so only a build whose process died mid-run is left `running`.
51
+ *
52
+ * @param projectRoot - Project root holding `.fireforge/`
53
+ * @param machCommand - The mach command being run
54
+ * @param build - The build itself
55
+ * @returns Whatever the build returned
56
+ */
57
+ export declare function withBuildRunRecord<T extends {
58
+ exitCode: number;
59
+ }>(projectRoot: string, machCommand: string, build: () => Promise<T>): Promise<T>;
60
+ /**
61
+ * Reads the last build's record and decides whether its process is still
62
+ * running.
63
+ *
64
+ * @param projectRoot - Project root holding `.fireforge/`
65
+ */
66
+ export declare function readBuildRunState(projectRoot: string): Promise<BuildRunState>;
67
+ /**
68
+ * One sentence naming why the bundle may be missing, or undefined when
69
+ * there is no record to go on.
70
+ *
71
+ * @param state - From {@link readBuildRunState}
72
+ */
73
+ export declare function describeBuildRunState(state: BuildRunState): string | undefined;
74
+ export {};
@@ -0,0 +1,136 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * A record of the mach build in flight, kept in `.fireforge/build-run.json`
4
+ * so any session on the checkout can see why the bundle is missing.
5
+ *
6
+ * An interrupted `fireforge build` (a killed peer session, a closed
7
+ * terminal) can leave `dist/<App>.app/Contents/MacOS/` without its
8
+ * executable. Every later `fireforge test`, in every session, then failed
9
+ * at launch with nothing saying a build had died there, and the sessions
10
+ * that had not run the build had no way to find out.
11
+ *
12
+ * The record is written when a protected mach build starts and removed when
13
+ * it succeeds. A failed build leaves it with `status: "failed"` and the exit
14
+ * code. A build that never reached either end, because its process died,
15
+ * leaves `status: "running"` with a PID that no longer exists, which is how
16
+ * an interrupted run is recognized. The next build that runs to the end
17
+ * replaces whatever was there.
18
+ *
19
+ * Best-effort throughout: a build never fails because its record could not
20
+ * be written, and an unreadable record reads as "no record".
21
+ */
22
+ import { readFile, rm } from 'node:fs/promises';
23
+ import { join } from 'node:path';
24
+ import { toError } from '../utils/errors.js';
25
+ import { writeFileAtomic } from '../utils/fs.js';
26
+ import { verbose } from '../utils/logger.js';
27
+ import { FIREFORGE_DIR } from './config-paths.js';
28
+ import { isLockOwnerAlive, readProcessStartTick } from './file-lock-owner.js';
29
+ /** File name of the record under `.fireforge/`. */
30
+ const BUILD_RUN_RECORD = 'build-run.json';
31
+ function recordPath(projectRoot) {
32
+ return join(projectRoot, FIREFORGE_DIR, BUILD_RUN_RECORD);
33
+ }
34
+ async function writeRecord(projectRoot, record) {
35
+ try {
36
+ await writeFileAtomic(recordPath(projectRoot), `${JSON.stringify(record, null, 2)}\n`);
37
+ }
38
+ catch (error) {
39
+ verbose(`Build run record not written: ${toError(error).message}`);
40
+ }
41
+ }
42
+ /**
43
+ * Runs a mach build with its run record maintained around it. The record
44
+ * is removed on a zero exit and kept as `failed` on a non-zero exit or a
45
+ * throw, so only a build whose process died mid-run is left `running`.
46
+ *
47
+ * @param projectRoot - Project root holding `.fireforge/`
48
+ * @param machCommand - The mach command being run
49
+ * @param build - The build itself
50
+ * @returns Whatever the build returned
51
+ */
52
+ export async function withBuildRunRecord(projectRoot, machCommand, build) {
53
+ const startTick = readProcessStartTick(process.pid);
54
+ const started = {
55
+ status: 'running',
56
+ pid: process.pid,
57
+ ...(startTick !== undefined ? { startTick } : {}),
58
+ command: ['fireforge', ...process.argv.slice(2)].join(' '),
59
+ machCommand,
60
+ startedAt: new Date().toISOString(),
61
+ };
62
+ await writeRecord(projectRoot, started);
63
+ let result;
64
+ try {
65
+ result = await build();
66
+ }
67
+ catch (error) {
68
+ await writeRecord(projectRoot, {
69
+ ...started,
70
+ status: 'failed',
71
+ endedAt: new Date().toISOString(),
72
+ });
73
+ throw error;
74
+ }
75
+ if (result.exitCode === 0) {
76
+ await rm(recordPath(projectRoot), { force: true }).catch(() => undefined);
77
+ }
78
+ else {
79
+ await writeRecord(projectRoot, {
80
+ ...started,
81
+ status: 'failed',
82
+ endedAt: new Date().toISOString(),
83
+ exitCode: result.exitCode,
84
+ });
85
+ }
86
+ return result;
87
+ }
88
+ /**
89
+ * Reads the last build's record and decides whether its process is still
90
+ * running.
91
+ *
92
+ * @param projectRoot - Project root holding `.fireforge/`
93
+ */
94
+ export async function readBuildRunState(projectRoot) {
95
+ let record;
96
+ try {
97
+ record = JSON.parse(await readFile(recordPath(projectRoot), 'utf8'));
98
+ }
99
+ catch {
100
+ return { kind: 'none' };
101
+ }
102
+ if (typeof record.pid !== 'number' || typeof record.startedAt !== 'string') {
103
+ return { kind: 'none' };
104
+ }
105
+ if (record.status === 'failed')
106
+ return { kind: 'failed', record };
107
+ return {
108
+ kind: isLockOwnerAlive({ pid: record.pid, startTick: record.startTick })
109
+ ? 'running'
110
+ : 'interrupted',
111
+ record,
112
+ };
113
+ }
114
+ /**
115
+ * One sentence naming why the bundle may be missing, or undefined when
116
+ * there is no record to go on.
117
+ *
118
+ * @param state - From {@link readBuildRunState}
119
+ */
120
+ export function describeBuildRunState(state) {
121
+ if (state.kind === 'none')
122
+ return undefined;
123
+ const { record } = state;
124
+ const who = `"${record.command}" (${record.machCommand}, PID ${record.pid}, started ${record.startedAt})`;
125
+ switch (state.kind) {
126
+ case 'running':
127
+ return `A build is running right now: ${who}. The bundle is incomplete until it finishes.`;
128
+ case 'interrupted':
129
+ return (`The last build never finished: ${who} is gone without recording an end, so it was ` +
130
+ 'interrupted (killed, or its terminal closed) part-way, which leaves the bundle without ' +
131
+ 'its executable.');
132
+ case 'failed':
133
+ return (`The last build failed: ${who} ended ${record.endedAt ?? 'at an unrecorded time'}` +
134
+ `${record.exitCode !== undefined ? ` with exit code ${record.exitCode}` : ''}.`);
135
+ }
136
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Unowned support heads for the named-file checkJs pass of `test --build`.
3
+ *
4
+ * Each named test is checked as its own program: the test plus the
5
+ * patch-owned `head*.js` beside it. A suite moved onto a NEW support head
6
+ * (`head_world_page.js`, loaded with `loadSubScript`) cannot be proven
7
+ * before that head is exported, because the head is in no patch: every kit
8
+ * name reads as "Cannot find name" and the run is refused at preflight,
9
+ * with the generic hint, which offers the shim as a remedy.
10
+ *
11
+ * So an unowned same-directory `head*.js` joins the pass when a named test
12
+ * loads it with `loadSubScript` or a manifest beside it lists it in
13
+ * `support-files`, which are the two ways the harness itself would bring
14
+ * it into the test's scope. The operator is told it is unowned and how to
15
+ * adopt it, as for the `head.js` companions in
16
+ * `patch-lint-unmanaged-companion.ts`.
17
+ */
18
+ /** A same-directory head no patch owns, and why the pass includes it. */
19
+ export interface UnownedSupportHead {
20
+ /** Engine-relative path of the head. */
21
+ file: string;
22
+ /** What brings it into the test's scope. */
23
+ reason: string;
24
+ }
25
+ /**
26
+ * Finds the unowned same-directory `head*.js` files the named tests bring
27
+ * into scope.
28
+ *
29
+ * @param engineDir - Absolute engine directory
30
+ * @param selected - Engine-relative named test files
31
+ * @param owned - Every file the pass already includes
32
+ */
33
+ export declare function findUnownedSupportHeads(engineDir: string, selected: Iterable<string>, owned: ReadonlySet<string>): Promise<UnownedSupportHead[]>;
34
+ /**
35
+ * The notice for an included unowned head: the ownership fact, the
36
+ * adoption command, and an explicit "not the shim", for the same reason
37
+ * `formatUnmanagedCompanionHint` gives.
38
+ *
39
+ * @param head - From {@link findUnownedSupportHeads}
40
+ */
41
+ export declare function formatUnownedSupportHeadNotice(head: UnownedSupportHead): string;
@@ -0,0 +1,104 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Unowned support heads for the named-file checkJs pass of `test --build`.
4
+ *
5
+ * Each named test is checked as its own program: the test plus the
6
+ * patch-owned `head*.js` beside it. A suite moved onto a NEW support head
7
+ * (`head_world_page.js`, loaded with `loadSubScript`) cannot be proven
8
+ * before that head is exported, because the head is in no patch: every kit
9
+ * name reads as "Cannot find name" and the run is refused at preflight,
10
+ * with the generic hint, which offers the shim as a remedy.
11
+ *
12
+ * So an unowned same-directory `head*.js` joins the pass when a named test
13
+ * loads it with `loadSubScript` or a manifest beside it lists it in
14
+ * `support-files`, which are the two ways the harness itself would bring
15
+ * it into the test's scope. The operator is told it is unowned and how to
16
+ * adopt it, as for the `head.js` companions in
17
+ * `patch-lint-unmanaged-companion.ts`.
18
+ */
19
+ import { readdir, readFile } from 'node:fs/promises';
20
+ import { join } from 'node:path';
21
+ import { buildSupportFileMatcher, extractSupportFileEntries, } from './patch-lint-forward-registration.js';
22
+ import { findLoadSubScriptTargets } from './patch-lint-unmanaged-companion.js';
23
+ const HEAD_BASENAME_PATTERN = /^head(?:_.*)?\.js$/;
24
+ const TEST_MANIFEST_PATTERN = /^(?:xpcshell|mochitest|browser|chrome|a11y)\.(?:toml|ini)$/;
25
+ async function readOrUndefined(path) {
26
+ try {
27
+ return await readFile(path, 'utf8');
28
+ }
29
+ catch {
30
+ return undefined;
31
+ }
32
+ }
33
+ /** Why `head` is in scope for the named tests of its directory, if it is. */
34
+ async function findInclusionReason(engineDir, dir, head, context) {
35
+ const headName = head.slice(dir.length);
36
+ for (const test of context.tests) {
37
+ const source = await readOrUndefined(join(engineDir, test));
38
+ if (source === undefined)
39
+ continue;
40
+ const loads = findLoadSubScriptTargets(source).some((target) => target.slice(target.lastIndexOf('/') + 1) === headName);
41
+ if (loads)
42
+ return `${test} loads it with loadSubScript`;
43
+ }
44
+ for (const manifest of context.manifests) {
45
+ const source = await readOrUndefined(join(engineDir, manifest));
46
+ if (source === undefined)
47
+ continue;
48
+ const listed = extractSupportFileEntries(source).some(({ entry }) => buildSupportFileMatcher(manifest, entry)?.(head) === true);
49
+ if (listed)
50
+ return `${manifest} lists it in support-files`;
51
+ }
52
+ return undefined;
53
+ }
54
+ /**
55
+ * Finds the unowned same-directory `head*.js` files the named tests bring
56
+ * into scope.
57
+ *
58
+ * @param engineDir - Absolute engine directory
59
+ * @param selected - Engine-relative named test files
60
+ * @param owned - Every file the pass already includes
61
+ */
62
+ export async function findUnownedSupportHeads(engineDir, selected, owned) {
63
+ const testsByDir = new Map();
64
+ for (const test of selected) {
65
+ const dir = test.slice(0, test.lastIndexOf('/') + 1);
66
+ testsByDir.set(dir, [...(testsByDir.get(dir) ?? []), test]);
67
+ }
68
+ const heads = [];
69
+ for (const [dir, tests] of testsByDir) {
70
+ let names;
71
+ try {
72
+ names = (await readdir(join(engineDir, dir))).sort();
73
+ }
74
+ catch {
75
+ continue;
76
+ }
77
+ const manifests = names
78
+ .filter((name) => TEST_MANIFEST_PATTERN.test(name))
79
+ .map((name) => `${dir}${name}`);
80
+ const candidates = names
81
+ .filter((name) => HEAD_BASENAME_PATTERN.test(name))
82
+ .map((name) => `${dir}${name}`)
83
+ .filter((file) => !owned.has(file));
84
+ for (const file of candidates) {
85
+ const reason = await findInclusionReason(engineDir, dir, file, { tests, manifests });
86
+ if (reason !== undefined)
87
+ heads.push({ file, reason });
88
+ }
89
+ }
90
+ return heads;
91
+ }
92
+ /**
93
+ * The notice for an included unowned head: the ownership fact, the
94
+ * adoption command, and an explicit "not the shim", for the same reason
95
+ * `formatUnmanagedCompanionHint` gives.
96
+ *
97
+ * @param head - From {@link findUnownedSupportHeads}
98
+ */
99
+ export function formatUnownedSupportHeadNotice(head) {
100
+ return (`${head.file} is in no patch yet; the pass includes it because ${head.reason}. ` +
101
+ `Adopt it with "fireforge re-export <patch> --scan --scan-file ${head.file}" before ` +
102
+ 'export. Do NOT add its globals to the test shim: that would hide a genuinely missing ' +
103
+ 'helper once the head is exported.');
104
+ }
@@ -5,9 +5,9 @@ import type { ProjectPaths } from '../types/config.js';
5
5
  /** Name of the fireforge data directory */
6
6
  export declare const FIREFORGE_DIR = ".fireforge";
7
7
  /** Supported top-level fireforge.json keys backed by the current schema. */
8
- export declare const SUPPORTED_CONFIG_ROOT_KEYS: readonly ["name", "vendor", "appId", "binaryName", "firefox", "build", "buildAudit", "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", "history", "externalToolchains", "license", "wire", "patchLint", "patchPolicy", "typecheck", "markerComment", "check"];
9
9
  /** Supported config paths that can be read or set without --force. */
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", "test.reapOrphans", "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", "patchLint.forwardRegistration", "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", "test.reapOrphans", "test.mochitestPorts", "history", "history.path", "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", "patchLint.forwardRegistration", "patchLint.fileSizeMetric", "patchLint.patchSizeMetric", "patchPolicy", "typecheck", "typecheck.projects", "typecheck.extraShim", "markerComment", "check"];
11
11
  /**
12
12
  * Gets all project paths based on a root directory.
13
13
  * @param root - Root directory of the project
@@ -27,6 +27,7 @@ export const SUPPORTED_CONFIG_ROOT_KEYS = [
27
27
  'build',
28
28
  'buildAudit',
29
29
  'test',
30
+ 'history',
30
31
  'externalToolchains',
31
32
  'license',
32
33
  'wire',
@@ -34,6 +35,7 @@ export const SUPPORTED_CONFIG_ROOT_KEYS = [
34
35
  'patchPolicy',
35
36
  'typecheck',
36
37
  'markerComment',
38
+ 'check',
37
39
  ];
38
40
  /** Supported config paths that can be read or set without --force. */
39
41
  export const SUPPORTED_CONFIG_PATHS = [
@@ -54,6 +56,9 @@ export const SUPPORTED_CONFIG_PATHS = [
54
56
  'test.canaryPath',
55
57
  'test.canaryTimeoutSeconds',
56
58
  'test.reapOrphans',
59
+ 'test.mochitestPorts',
60
+ 'history',
61
+ 'history.path',
57
62
  'externalToolchains',
58
63
  'wire',
59
64
  'wire.subscriptDir',
@@ -69,11 +74,14 @@ export const SUPPORTED_CONFIG_PATHS = [
69
74
  'patchLint.testAssertionFloor',
70
75
  'patchLint.chromeScriptJsDoc',
71
76
  'patchLint.forwardRegistration',
77
+ 'patchLint.fileSizeMetric',
78
+ 'patchLint.patchSizeMetric',
72
79
  'patchPolicy',
73
80
  'typecheck',
74
81
  'typecheck.projects',
75
82
  'typecheck.extraShim',
76
83
  'markerComment',
84
+ 'check',
77
85
  ];
78
86
  /**
79
87
  * Gets all project paths based on a root directory.
@@ -0,0 +1,8 @@
1
+ import { type CheckConfig } from '../types/config.js';
2
+ /**
3
+ * Parses the `check` block.
4
+ *
5
+ * @param raw - The raw `check` value from fireforge.json
6
+ * @returns The validated block
7
+ */
8
+ export declare function parseCheckConfig(raw: unknown): CheckConfig;
@@ -0,0 +1,143 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Validation for the `check` block (`fireforge check`).
4
+ *
5
+ * Split out of `config-validate.ts`, which is at its per-file line budget,
6
+ * following the `config-validate-build-audit.ts` precedent. Unknown keys are
7
+ * refused: a misspelt `extraSteps` would otherwise drop a project audit from
8
+ * the gate without a word.
9
+ */
10
+ import { ConfigError } from '../errors/config.js';
11
+ import { CHECK_BUILTIN_STEPS, } from '../types/config.js';
12
+ import { isContainedRelativePath } from '../utils/paths.js';
13
+ import { isObject } from '../utils/validation.js';
14
+ const STEP_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
15
+ function assertKnownKeys(raw, field, known) {
16
+ for (const key of Object.keys(raw)) {
17
+ if (!known.includes(key)) {
18
+ throw new ConfigError(`Config field "${field}" has unknown key "${key}"`);
19
+ }
20
+ }
21
+ }
22
+ function parseStringList(raw, field) {
23
+ if (!Array.isArray(raw) || raw.some((entry) => typeof entry !== 'string' || entry === '')) {
24
+ throw new ConfigError(`Config field "${field}" must be an array of non-empty strings`);
25
+ }
26
+ return raw;
27
+ }
28
+ function parseNonNegativeInteger(raw, field) {
29
+ if (typeof raw !== 'number' || !Number.isInteger(raw) || raw < 0) {
30
+ throw new ConfigError(`Config field "${field}" must be a non-negative integer`);
31
+ }
32
+ return raw;
33
+ }
34
+ function isBuiltinStep(value) {
35
+ return CHECK_BUILTIN_STEPS.includes(value);
36
+ }
37
+ function parseSkip(raw) {
38
+ const skip = parseStringList(raw, 'check.skip');
39
+ for (const entry of skip) {
40
+ if (!isBuiltinStep(entry)) {
41
+ throw new ConfigError(`Config field "check.skip" names unknown step "${entry}". Valid: ${CHECK_BUILTIN_STEPS.join(', ')}`);
42
+ }
43
+ }
44
+ return skip;
45
+ }
46
+ function parseStatusPolicy(raw) {
47
+ if (!isObject(raw))
48
+ throw new ConfigError('Config field "check.status" must be a plain object');
49
+ assertKnownKeys(raw, 'check.status', ['failOn', 'allow', 'failOnUnknown']);
50
+ const out = {};
51
+ if (raw['failOn'] !== undefined)
52
+ out.failOn = parseStringList(raw['failOn'], 'check.status.failOn');
53
+ if (raw['allow'] !== undefined)
54
+ out.allow = parseStringList(raw['allow'], 'check.status.allow');
55
+ if (raw['failOnUnknown'] !== undefined) {
56
+ if (typeof raw['failOnUnknown'] !== 'boolean') {
57
+ throw new ConfigError('Config field "check.status.failOnUnknown" must be a boolean');
58
+ }
59
+ out.failOnUnknown = raw['failOnUnknown'];
60
+ }
61
+ return out;
62
+ }
63
+ function parseMaxWarnings(raw) {
64
+ if (!isObject(raw)) {
65
+ throw new ConfigError('Config field "check.maxWarnings" must be a plain object');
66
+ }
67
+ assertKnownKeys(raw, 'check.maxWarnings', ['lint', 'furnace']);
68
+ const out = {};
69
+ if (raw['lint'] !== undefined) {
70
+ out.lint = parseNonNegativeInteger(raw['lint'], 'check.maxWarnings.lint');
71
+ }
72
+ if (raw['furnace'] !== undefined) {
73
+ out.furnace = parseNonNegativeInteger(raw['furnace'], 'check.maxWarnings.furnace');
74
+ }
75
+ return out;
76
+ }
77
+ function parseExtraStep(raw, field) {
78
+ if (!isObject(raw)) {
79
+ throw new ConfigError(`Config field "${field}" must be a plain object with "name" and "command"`);
80
+ }
81
+ assertKnownKeys(raw, field, ['name', 'command', 'cwd', 'timeout']);
82
+ const name = raw['name'];
83
+ if (typeof name !== 'string' || !STEP_NAME_PATTERN.test(name)) {
84
+ throw new ConfigError(`Config field "${field}.name" must be lowercase letters, digits and dashes (e.g. "policy-audit")`);
85
+ }
86
+ if (isBuiltinStep(name)) {
87
+ throw new ConfigError(`Config field "${field}.name" "${name}" is a built-in step name`);
88
+ }
89
+ const command = raw['command'];
90
+ if (typeof command !== 'string' || command.trim() === '') {
91
+ throw new ConfigError(`Config field "${field}.command" must be a non-empty string`);
92
+ }
93
+ const step = { name, command };
94
+ if (raw['cwd'] !== undefined) {
95
+ const cwd = raw['cwd'];
96
+ if (typeof cwd !== 'string' || cwd.trim() === '' || !isContainedRelativePath(cwd)) {
97
+ throw new ConfigError(`Config field "${field}.cwd" must be a project-relative path`);
98
+ }
99
+ step.cwd = cwd;
100
+ }
101
+ if (raw['timeout'] !== undefined) {
102
+ const timeout = parseNonNegativeInteger(raw['timeout'], `${field}.timeout`);
103
+ if (timeout === 0) {
104
+ throw new ConfigError(`Config field "${field}.timeout" must be a positive number of seconds`);
105
+ }
106
+ step.timeout = timeout;
107
+ }
108
+ return step;
109
+ }
110
+ function parseExtraSteps(raw) {
111
+ if (!Array.isArray(raw))
112
+ throw new ConfigError('Config field "check.extraSteps" must be an array');
113
+ const steps = raw.map((entry, index) => parseExtraStep(entry, `check.extraSteps[${String(index)}]`));
114
+ const seen = new Set();
115
+ for (const step of steps) {
116
+ if (seen.has(step.name)) {
117
+ throw new ConfigError(`Config field "check.extraSteps" repeats the step name "${step.name}"`);
118
+ }
119
+ seen.add(step.name);
120
+ }
121
+ return steps;
122
+ }
123
+ /**
124
+ * Parses the `check` block.
125
+ *
126
+ * @param raw - The raw `check` value from fireforge.json
127
+ * @returns The validated block
128
+ */
129
+ export function parseCheckConfig(raw) {
130
+ if (!isObject(raw))
131
+ throw new ConfigError('Config field "check" must be a plain object');
132
+ assertKnownKeys(raw, 'check', ['skip', 'status', 'maxWarnings', 'extraSteps']);
133
+ const out = {};
134
+ if (raw['skip'] !== undefined)
135
+ out.skip = parseSkip(raw['skip']);
136
+ if (raw['status'] !== undefined)
137
+ out.status = parseStatusPolicy(raw['status']);
138
+ if (raw['maxWarnings'] !== undefined)
139
+ out.maxWarnings = parseMaxWarnings(raw['maxWarnings']);
140
+ if (raw['extraSteps'] !== undefined)
141
+ out.extraSteps = parseExtraSteps(raw['extraSteps']);
142
+ return out;
143
+ }
@@ -1,12 +1,15 @@
1
1
  /**
2
- * Validation for `patchLint.fileSizeThresholds`.
2
+ * Validation for the size-rule keys of `patchLint`: `fileSizeThresholds`,
3
+ * `fileSizeMetric`, `patchSizeThresholds`, `patchSizeMetric` and
4
+ * `patchSizeRegistrationFiles`.
3
5
  *
4
6
  * Split out of `config-validate.ts`, which is at its per-file line budget.
5
7
  */
6
- import type { PatchLintFileSizeThresholds } from '../types/config.js';
8
+ import type { PatchLintConfig } from '../types/config.js';
7
9
  /**
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`.
10
+ * Parses every size-rule key of the `patchLint` block.
11
+ *
12
+ * @param read - Reads one raw `patchLint` field by key
13
+ * @returns The parsed fields, only those present
11
14
  */
12
- export declare function parsePatchLintFileSizeThresholds(raw: unknown): PatchLintFileSizeThresholds;
15
+ export declare function parsePatchLintSizeFields(read: (key: string) => unknown): Pick<PatchLintConfig, 'fileSizeThresholds' | 'fileSizeMetric' | 'patchSizeThresholds' | 'patchSizeMetric' | 'patchSizeRegistrationFiles'>;