@hominis/fireforge 0.49.2 → 0.51.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 (400) hide show
  1. package/CHANGELOG.md +87 -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 +41 -0
  10. package/dist/src/commands/check-report.js +91 -0
  11. package/dist/src/commands/check-run.d.ts +33 -0
  12. package/dist/src/commands/check-run.js +112 -0
  13. package/dist/src/commands/check-steps.d.ts +75 -0
  14. package/dist/src/commands/check-steps.js +200 -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/declarations-report.d.ts +43 -0
  19. package/dist/src/commands/declarations-report.js +76 -0
  20. package/dist/src/commands/doctor-engine-eslint.js +6 -1
  21. package/dist/src/commands/doctor-source-pin.js +4 -1
  22. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  23. package/dist/src/commands/export-adopt-check.js +98 -0
  24. package/dist/src/commands/export-flow.js +29 -14
  25. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  26. package/dist/src/commands/export-placement-gate.js +8 -0
  27. package/dist/src/commands/export-shared.d.ts +2 -1
  28. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  29. package/dist/src/commands/export-staged-registration.js +91 -0
  30. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  31. package/dist/src/commands/forward-import-remedy.js +63 -0
  32. package/dist/src/commands/furnace/apply.js +3 -3
  33. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  34. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  35. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  36. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  37. package/dist/src/commands/furnace/create.js +2 -2
  38. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  39. package/dist/src/commands/furnace/deploy-named.js +107 -0
  40. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  41. package/dist/src/commands/furnace/deploy.js +50 -86
  42. package/dist/src/commands/furnace/diff.js +26 -3
  43. package/dist/src/commands/furnace/index.js +66 -41
  44. package/dist/src/commands/furnace/init.js +13 -6
  45. package/dist/src/commands/furnace/override.js +4 -2
  46. package/dist/src/commands/furnace/preview.js +4 -3
  47. package/dist/src/commands/furnace/refresh.js +2 -2
  48. package/dist/src/commands/furnace/remove.js +4 -3
  49. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  50. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  51. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  52. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  53. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  54. package/dist/src/commands/furnace/rename-registrations.js +98 -0
  55. package/dist/src/commands/furnace/rename.d.ts +2 -1
  56. package/dist/src/commands/furnace/rename.js +28 -66
  57. package/dist/src/commands/furnace/scan.d.ts +9 -4
  58. package/dist/src/commands/furnace/scan.js +11 -9
  59. package/dist/src/commands/furnace/sync.js +3 -2
  60. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  61. package/dist/src/commands/furnace/validate-report.js +91 -0
  62. package/dist/src/commands/furnace/validate.d.ts +1 -1
  63. package/dist/src/commands/furnace/validate.js +151 -113
  64. package/dist/src/commands/furnace/validation-output.js +14 -3
  65. package/dist/src/commands/lint-per-patch.js +36 -61
  66. package/dist/src/commands/lint-per-run-checkjs.js +2 -2
  67. package/dist/src/commands/lint-report.d.ts +8 -4
  68. package/dist/src/commands/lint-report.js +20 -5
  69. package/dist/src/commands/lint.js +15 -34
  70. package/dist/src/commands/manifest.js +4 -0
  71. package/dist/src/commands/park.d.ts +22 -0
  72. package/dist/src/commands/park.js +232 -0
  73. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  74. package/dist/src/commands/patch/delete.d.ts +1 -1
  75. package/dist/src/commands/patch/delete.js +24 -104
  76. package/dist/src/commands/patch/index.d.ts +3 -3
  77. package/dist/src/commands/patch/index.js +10 -4
  78. package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
  79. package/dist/src/commands/patch/lint-ignore-from.js +142 -0
  80. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  81. package/dist/src/commands/patch/lint-ignore.js +78 -31
  82. package/dist/src/commands/patch/move-files-create.js +34 -18
  83. package/dist/src/commands/patch/move-files-into.js +15 -7
  84. package/dist/src/commands/patch/move-files.js +6 -2
  85. package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
  86. package/dist/src/commands/patch/removal-created-files.js +83 -0
  87. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  88. package/dist/src/commands/patch/removal-dependents.js +133 -0
  89. package/dist/src/commands/patch/rename.js +20 -1
  90. package/dist/src/commands/patch/reserve.d.ts +33 -0
  91. package/dist/src/commands/patch/reserve.js +152 -0
  92. package/dist/src/commands/patch/retire.d.ts +34 -0
  93. package/dist/src/commands/patch/retire.js +133 -0
  94. package/dist/src/commands/patch/split-plan.d.ts +17 -8
  95. package/dist/src/commands/patch/split-plan.js +60 -28
  96. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  97. package/dist/src/commands/patch/split-staged.js +202 -0
  98. package/dist/src/commands/patch/split.d.ts +5 -0
  99. package/dist/src/commands/patch/split.js +62 -17
  100. package/dist/src/commands/patch/stage.d.ts +34 -0
  101. package/dist/src/commands/patch/stage.js +153 -0
  102. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  103. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  104. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  105. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  106. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  107. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  108. package/dist/src/commands/patch/staged-dependency-remove.d.ts +31 -0
  109. package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
  110. package/dist/src/commands/patch/staged-dependency.js +54 -139
  111. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  112. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  113. package/dist/src/commands/re-export-adjacent.d.ts +50 -2
  114. package/dist/src/commands/re-export-adjacent.js +160 -40
  115. package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
  116. package/dist/src/commands/re-export-bulk-scan.js +22 -7
  117. package/dist/src/commands/re-export-drift.d.ts +36 -8
  118. package/dist/src/commands/re-export-drift.js +73 -49
  119. package/dist/src/commands/re-export-files.js +11 -8
  120. package/dist/src/commands/re-export-hunks.d.ts +35 -0
  121. package/dist/src/commands/re-export-hunks.js +220 -0
  122. package/dist/src/commands/re-export-lint.d.ts +2 -1
  123. package/dist/src/commands/re-export-lint.js +23 -10
  124. package/dist/src/commands/re-export-options.js +74 -6
  125. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  126. package/dist/src/commands/re-export-preflight.js +160 -0
  127. package/dist/src/commands/re-export-purity-scope.d.ts +18 -0
  128. package/dist/src/commands/re-export-purity-scope.js +131 -0
  129. package/dist/src/commands/re-export-register.js +56 -17
  130. package/dist/src/commands/re-export-removed.d.ts +13 -2
  131. package/dist/src/commands/re-export-removed.js +57 -12
  132. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  133. package/dist/src/commands/re-export-scan-owner.js +116 -0
  134. package/dist/src/commands/re-export-scan.d.ts +19 -9
  135. package/dist/src/commands/re-export-scan.js +60 -24
  136. package/dist/src/commands/re-export-single.js +122 -46
  137. package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
  138. package/dist/src/commands/re-export-stale-registrations.js +62 -0
  139. package/dist/src/commands/re-export-summary.d.ts +28 -0
  140. package/dist/src/commands/re-export-summary.js +36 -0
  141. package/dist/src/commands/re-export.js +71 -57
  142. package/dist/src/commands/rebase/index.js +17 -17
  143. package/dist/src/commands/run.js +16 -2
  144. package/dist/src/commands/status-check.d.ts +55 -16
  145. package/dist/src/commands/status-check.js +92 -37
  146. package/dist/src/commands/status-json.d.ts +22 -9
  147. package/dist/src/commands/status-json.js +47 -32
  148. package/dist/src/commands/status-output.js +3 -1
  149. package/dist/src/commands/status-scope.d.ts +37 -0
  150. package/dist/src/commands/status-scope.js +54 -0
  151. package/dist/src/commands/status.js +66 -23
  152. package/dist/src/commands/test-browser-preflight.js +19 -3
  153. package/dist/src/commands/test-build-furnace.d.ts +22 -0
  154. package/dist/src/commands/test-build-furnace.js +54 -0
  155. package/dist/src/commands/test-build-phase.js +64 -41
  156. package/dist/src/commands/test-checkjs.js +10 -2
  157. package/dist/src/commands/test-diagnose.js +7 -0
  158. package/dist/src/commands/test-dispatch.d.ts +39 -0
  159. package/dist/src/commands/test-dispatch.js +211 -0
  160. package/dist/src/commands/test-furnace-reach.d.ts +37 -0
  161. package/dist/src/commands/test-furnace-reach.js +132 -0
  162. package/dist/src/commands/test-modes.d.ts +16 -1
  163. package/dist/src/commands/test-modes.js +24 -1
  164. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  165. package/dist/src/commands/test-multi-verdict.js +111 -0
  166. package/dist/src/commands/test-orchestrate.d.ts +52 -0
  167. package/dist/src/commands/test-orchestrate.js +240 -0
  168. package/dist/src/commands/test-perf-scope.d.ts +50 -0
  169. package/dist/src/commands/test-perf-scope.js +138 -0
  170. package/dist/src/commands/test-prelock.d.ts +51 -0
  171. package/dist/src/commands/test-prelock.js +273 -0
  172. package/dist/src/commands/test-register.js +69 -10
  173. package/dist/src/commands/test-results.d.ts +62 -0
  174. package/dist/src/commands/test-results.js +62 -0
  175. package/dist/src/commands/test-run.d.ts +13 -55
  176. package/dist/src/commands/test-run.js +74 -99
  177. package/dist/src/commands/test-shards.d.ts +110 -0
  178. package/dist/src/commands/test-shards.js +255 -0
  179. package/dist/src/commands/test-stale-gate.d.ts +5 -1
  180. package/dist/src/commands/test-stale-gate.js +60 -28
  181. package/dist/src/commands/test-verdict.d.ts +11 -1
  182. package/dist/src/commands/test-verdict.js +28 -1
  183. package/dist/src/commands/test.d.ts +6 -1
  184. package/dist/src/commands/test.js +120 -124
  185. package/dist/src/commands/token-lint.d.ts +14 -0
  186. package/dist/src/commands/token-lint.js +151 -0
  187. package/dist/src/commands/token.js +10 -0
  188. package/dist/src/commands/verify-scope.d.ts +28 -0
  189. package/dist/src/commands/verify-scope.js +67 -0
  190. package/dist/src/commands/verify.d.ts +10 -2
  191. package/dist/src/commands/verify.js +67 -36
  192. package/dist/src/core/build-baseline-types.d.ts +46 -0
  193. package/dist/src/core/build-baseline-validate.js +19 -0
  194. package/dist/src/core/build-baseline.d.ts +5 -1
  195. package/dist/src/core/build-baseline.js +3 -1
  196. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  197. package/dist/src/core/build-overwrite-guard.js +84 -4
  198. package/dist/src/core/build-prepare.d.ts +13 -0
  199. package/dist/src/core/build-prepare.js +42 -10
  200. package/dist/src/core/build-run-record.d.ts +74 -0
  201. package/dist/src/core/build-run-record.js +136 -0
  202. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  203. package/dist/src/core/checkjs-support-heads.js +104 -0
  204. package/dist/src/core/config-paths.d.ts +2 -2
  205. package/dist/src/core/config-paths.js +10 -0
  206. package/dist/src/core/config-validate-check.d.ts +8 -0
  207. package/dist/src/core/config-validate-check.js +219 -0
  208. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  209. package/dist/src/core/config-validate-file-size.js +91 -2
  210. package/dist/src/core/config-validate-history.d.ts +11 -0
  211. package/dist/src/core/config-validate-history.js +38 -0
  212. package/dist/src/core/config-validate-patch-policy.js +100 -0
  213. package/dist/src/core/config-validate-test-toolchains.js +21 -0
  214. package/dist/src/core/config-validate-token-scales.d.ts +9 -0
  215. package/dist/src/core/config-validate-token-scales.js +92 -0
  216. package/dist/src/core/config-validate.js +20 -5
  217. package/dist/src/core/config.d.ts +5 -0
  218. package/dist/src/core/config.js +16 -0
  219. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  220. package/dist/src/core/coverage-dispatch.js +44 -0
  221. package/dist/src/core/coverage-extend.d.ts +70 -45
  222. package/dist/src/core/coverage-extend.js +199 -144
  223. package/dist/src/core/coverage-records.d.ts +108 -0
  224. package/dist/src/core/coverage-records.js +384 -0
  225. package/dist/src/core/destructive.d.ts +7 -4
  226. package/dist/src/core/destructive.js +10 -10
  227. package/dist/src/core/engine-session-lock.d.ts +16 -1
  228. package/dist/src/core/engine-session-lock.js +38 -10
  229. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  230. package/dist/src/core/export-moved-code-shape.js +135 -44
  231. package/dist/src/core/file-lock-queue.d.ts +67 -0
  232. package/dist/src/core/file-lock-queue.js +221 -0
  233. package/dist/src/core/file-lock-shared.d.ts +57 -0
  234. package/dist/src/core/file-lock-shared.js +117 -0
  235. package/dist/src/core/file-lock.d.ts +13 -25
  236. package/dist/src/core/file-lock.js +163 -140
  237. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  238. package/dist/src/core/furnace-apply-output.js +31 -1
  239. package/dist/src/core/furnace-apply.js +22 -12
  240. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  241. package/dist/src/core/furnace-composed-source.js +167 -0
  242. package/dist/src/core/furnace-config-validate-exceptions.d.ts +16 -0
  243. package/dist/src/core/furnace-config-validate-exceptions.js +102 -0
  244. package/dist/src/core/furnace-config-validate.js +14 -0
  245. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  246. package/dist/src/core/furnace-css-fragments.js +26 -0
  247. package/dist/src/core/furnace-operation.d.ts +3 -3
  248. package/dist/src/core/furnace-registration-jar-inplace.d.ts +12 -0
  249. package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
  250. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  251. package/dist/src/core/furnace-registration-remove.js +53 -17
  252. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  253. package/dist/src/core/furnace-registration-rename.js +77 -0
  254. package/dist/src/core/furnace-registration.d.ts +14 -4
  255. package/dist/src/core/furnace-registration.js +44 -10
  256. package/dist/src/core/furnace-staleness.d.ts +7 -0
  257. package/dist/src/core/furnace-staleness.js +34 -24
  258. package/dist/src/core/furnace-validate-accessibility.d.ts +8 -4
  259. package/dist/src/core/furnace-validate-accessibility.js +71 -28
  260. package/dist/src/core/furnace-validate-exceptions.d.ts +48 -0
  261. package/dist/src/core/furnace-validate-exceptions.js +80 -0
  262. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  263. package/dist/src/core/furnace-validate-icon-button.js +78 -0
  264. package/dist/src/core/furnace-validate.js +1 -1
  265. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  266. package/dist/src/core/harness-port-fallback.js +96 -0
  267. package/dist/src/core/history-log-file.d.ts +61 -0
  268. package/dist/src/core/history-log-file.js +164 -0
  269. package/dist/src/core/history-log.d.ts +2 -1
  270. package/dist/src/core/history-log.js +2 -1
  271. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  272. package/dist/src/core/json-preserving-edit.js +199 -0
  273. package/dist/src/core/line-prefixer.d.ts +18 -0
  274. package/dist/src/core/line-prefixer.js +34 -0
  275. package/dist/src/core/mach.d.ts +7 -0
  276. package/dist/src/core/mach.js +32 -10
  277. package/dist/src/core/module-resolution-preflight.d.ts +19 -3
  278. package/dist/src/core/module-resolution-preflight.js +24 -6
  279. package/dist/src/core/negative-control-compiled.d.ts +27 -0
  280. package/dist/src/core/negative-control-compiled.js +73 -0
  281. package/dist/src/core/negative-control-diff.d.ts +51 -0
  282. package/dist/src/core/negative-control-diff.js +179 -0
  283. package/dist/src/core/negative-control.d.ts +150 -0
  284. package/dist/src/core/negative-control.js +399 -0
  285. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  286. package/dist/src/core/objdir-install-manifests.js +226 -0
  287. package/dist/src/core/park-store.d.ts +63 -0
  288. package/dist/src/core/park-store.js +216 -0
  289. package/dist/src/core/patch-export-update.d.ts +6 -0
  290. package/dist/src/core/patch-export-update.js +13 -3
  291. package/dist/src/core/patch-export.d.ts +3 -2
  292. package/dist/src/core/patch-export.js +22 -16
  293. package/dist/src/core/patch-files.js +26 -8
  294. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +39 -0
  295. package/dist/src/core/patch-lint-checkjs-any-import.js +63 -0
  296. package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
  297. package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
  298. package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
  299. package/dist/src/core/patch-lint-checkjs.js +20 -13
  300. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  301. package/dist/src/core/patch-lint-code-lines.js +105 -0
  302. package/dist/src/core/patch-lint-cross.d.ts +3 -1
  303. package/dist/src/core/patch-lint-cross.js +17 -4
  304. package/dist/src/core/patch-lint-css.d.ts +8 -2
  305. package/dist/src/core/patch-lint-css.js +24 -14
  306. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  307. package/dist/src/core/patch-lint-file-size.js +22 -11
  308. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +23 -0
  309. package/dist/src/core/patch-lint-forward-registration-extended.js +36 -11
  310. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  311. package/dist/src/core/patch-lint-ignore.d.ts +196 -0
  312. package/dist/src/core/patch-lint-ignore.js +429 -0
  313. package/dist/src/core/patch-lint-ownership.d.ts +10 -0
  314. package/dist/src/core/patch-lint-ownership.js +26 -0
  315. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  316. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  317. package/dist/src/core/patch-lint-size.d.ts +117 -0
  318. package/dist/src/core/patch-lint-size.js +331 -0
  319. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  320. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  321. package/dist/src/core/patch-lint-token-scale.d.ts +57 -0
  322. package/dist/src/core/patch-lint-token-scale.js +286 -0
  323. package/dist/src/core/patch-lint.d.ts +11 -83
  324. package/dist/src/core/patch-lint.js +66 -288
  325. package/dist/src/core/patch-lock.js +7 -6
  326. package/dist/src/core/patch-manifest-consistency.js +7 -6
  327. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  328. package/dist/src/core/patch-manifest-io.js +27 -9
  329. package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
  330. package/dist/src/core/patch-manifest-key-order.js +61 -0
  331. package/dist/src/core/patch-manifest-stage.d.ts +46 -0
  332. package/dist/src/core/patch-manifest-stage.js +96 -0
  333. package/dist/src/core/patch-manifest-validate.js +61 -3
  334. package/dist/src/core/patch-order-legal.d.ts +26 -0
  335. package/dist/src/core/patch-order-legal.js +88 -0
  336. package/dist/src/core/patch-policy-content.d.ts +40 -0
  337. package/dist/src/core/patch-policy-content.js +216 -0
  338. package/dist/src/core/patch-policy.d.ts +22 -5
  339. package/dist/src/core/patch-policy.js +26 -12
  340. package/dist/src/core/patch-retired.d.ts +57 -0
  341. package/dist/src/core/patch-retired.js +92 -0
  342. package/dist/src/core/patch-subset.d.ts +16 -0
  343. package/dist/src/core/patch-subset.js +66 -0
  344. package/dist/src/core/preflight-wait.d.ts +20 -0
  345. package/dist/src/core/preflight-wait.js +38 -0
  346. package/dist/src/core/rebase-from-version.d.ts +46 -0
  347. package/dist/src/core/rebase-from-version.js +71 -0
  348. package/dist/src/core/run-log.d.ts +26 -0
  349. package/dist/src/core/run-log.js +106 -4
  350. package/dist/src/core/run-profile.d.ts +7 -0
  351. package/dist/src/core/run-profile.js +7 -1
  352. package/dist/src/core/smoke-patterns.d.ts +5 -0
  353. package/dist/src/core/smoke-patterns.js +13 -0
  354. package/dist/src/core/test-harness-crash.d.ts +19 -1
  355. package/dist/src/core/test-harness-crash.js +8 -9
  356. package/dist/src/core/test-harness-signature.d.ts +33 -0
  357. package/dist/src/core/test-harness-signature.js +23 -1
  358. package/dist/src/core/test-raw-log.d.ts +66 -0
  359. package/dist/src/core/test-raw-log.js +122 -0
  360. package/dist/src/core/test-run-completeness.d.ts +90 -0
  361. package/dist/src/core/test-run-completeness.js +184 -0
  362. package/dist/src/core/test-stale-check.d.ts +27 -1
  363. package/dist/src/core/test-stale-check.js +39 -49
  364. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  365. package/dist/src/core/test-stale-exempt.js +70 -0
  366. package/dist/src/core/test-xpcshell-retry.d.ts +2 -0
  367. package/dist/src/core/test-xpcshell-retry.js +2 -1
  368. package/dist/src/core/token-lint.d.ts +53 -0
  369. package/dist/src/core/token-lint.js +131 -0
  370. package/dist/src/core/token-sibling-sheets.d.ts +46 -0
  371. package/dist/src/core/token-sibling-sheets.js +119 -0
  372. package/dist/src/core/tree-guard.js +5 -0
  373. package/dist/src/core/typecheck-shim.d.ts +6 -2
  374. package/dist/src/core/typecheck-shim.js +205 -4
  375. package/dist/src/core/typecheck.js +3 -1
  376. package/dist/src/index.d.ts +14 -3
  377. package/dist/src/index.js +6 -1
  378. package/dist/src/types/commands/index.d.ts +2 -2
  379. package/dist/src/types/commands/options.d.ts +212 -24
  380. package/dist/src/types/commands/patches.d.ts +118 -1
  381. package/dist/src/types/config.d.ts +264 -0
  382. package/dist/src/types/config.js +9 -0
  383. package/dist/src/types/furnace.d.ts +63 -1
  384. package/dist/src/types/furnace.js +7 -0
  385. package/dist/src/types/index.d.ts +1 -1
  386. package/dist/src/utils/options.d.ts +36 -7
  387. package/dist/src/utils/options.js +94 -8
  388. package/dist/src/utils/path-glob.d.ts +15 -0
  389. package/dist/src/utils/path-glob.js +47 -0
  390. package/dist/src/utils/regex.d.ts +15 -3
  391. package/dist/src/utils/regex.js +37 -16
  392. package/docs/configuration.md +369 -11
  393. package/docs/exit-codes.md +18 -1
  394. package/docs/furnace.md +139 -3
  395. package/docs/lifecycle-invariants.md +65 -30
  396. package/docs/machine-output.md +120 -12
  397. package/docs/patch-workflow.md +458 -22
  398. package/docs/run-logs.md +20 -1
  399. package/docs/testing.md +346 -21
  400. package/package.json +3 -3
@@ -81,7 +81,7 @@ export async function readBuildBaseline(projectRoot, invalid = 'ignore') {
81
81
  * @param options - See {@link WriteBuildBaselineOptions}
82
82
  */
83
83
  export async function writeBuildBaseline(options) {
84
- const { projectRoot, engineDir, binaryName, testPackagingCoverage, previousBaseline, recordedBy, staticComponentsHandling = 'auto', buildKind = 'full', } = options;
84
+ const { projectRoot, engineDir, binaryName, testPackagingCoverage, testCoverageRecords, fullCoverageAnchor, previousBaseline, recordedBy, staticComponentsHandling = 'auto', buildKind = 'full', } = options;
85
85
  let engineHeadSha = '';
86
86
  try {
87
87
  engineHeadSha = await getHead(engineDir);
@@ -116,6 +116,8 @@ export async function writeBuildBaseline(options) {
116
116
  ...(testInputFingerprints !== undefined ? { testInputFingerprints } : {}),
117
117
  ...(buildInputFingerprints !== undefined ? { buildInputFingerprints } : {}),
118
118
  ...(testPackagingCoverage !== undefined ? { testPackagingCoverage } : {}),
119
+ ...(testCoverageRecords !== undefined ? { testCoverageRecords } : {}),
120
+ ...(fullCoverageAnchor !== undefined ? { fullCoverageAnchor } : {}),
119
121
  ...(mozconfigHash !== undefined ? { mozconfigHash } : {}),
120
122
  ...(staticComponentsBaseline !== undefined ? { staticComponentsBaseline } : {}),
121
123
  ...(recordedBy !== undefined ? { recordedBy } : {}),
@@ -14,8 +14,17 @@
14
14
  * whose content is explained by neither a patch body nor the pristine
15
15
  * baseline. Advisory by default (a warning naming every file), refusing
16
16
  * under an explicit flag.
17
+ *
18
+ * A deployed Furnace file is checked against its component source as well
19
+ * (`deployed-unexported`): when the engine copy equals the Furnace-composed
20
+ * current source, differs from it only inside generated fragment
21
+ * expansions, or is exactly Furnace's last recorded deployment, the
22
+ * overwrite destroys nothing that is not a component source, so it is safe
23
+ * even though no patch records it yet. Without this, peers' deployed but
24
+ * unexported widgets refused every belted build on a shared checkout.
17
25
  */
18
26
  import type { FireForgeConfig } from '../types/config.js';
27
+ import { type DeployedSourceMatch } from './furnace-composed-source.js';
19
28
  import { type ClassifiedFile } from './status-classify.js';
20
29
  /** One file whose unexported content a build-prepare overwrite would destroy. */
21
30
  export interface UnexportedDriftAtRisk {
@@ -23,6 +32,24 @@ export interface UnexportedDriftAtRisk {
23
32
  classification: ClassifiedFile['classification'];
24
33
  owner?: string | undefined;
25
34
  }
35
+ /**
36
+ * A deployed Furnace file no patch records yet whose content is its
37
+ * component source's (see {@link DeployedSourceMatch}), so the overwrite is
38
+ * safe. Its patch still owes a re-export.
39
+ */
40
+ export interface DeployedUnexportedFile {
41
+ file: string;
42
+ classification: 'deployed-unexported';
43
+ match: DeployedSourceMatch;
44
+ owner?: string | undefined;
45
+ }
46
+ /** Outcome of {@link assessUnexportedDrift}. */
47
+ export interface UnexportedDriftAssessment {
48
+ /** Files whose content the overwrite would destroy unrecorded. */
49
+ atRisk: UnexportedDriftAtRisk[];
50
+ /** Files the overwrite rewrites from their own component source. */
51
+ deployedUnexported: DeployedUnexportedFile[];
52
+ }
26
53
  /**
27
54
  * Finds engine files that build-prepare is about to overwrite and whose
28
55
  * current content is recorded nowhere.
@@ -30,11 +57,34 @@ export interface UnexportedDriftAtRisk {
30
57
  * Fail-open: any probe failure is reported verbosely and yields an empty
31
58
  * list. A build must not fail because an advisory guard could not run.
32
59
  *
60
+ * @internal The at-risk half of {@link assessUnexportedDrift}, kept for the
61
+ * guard's tests; build-prepare consumes the full assessment.
62
+ *
33
63
  * @param projectRoot - Project root
34
64
  * @param config - Loaded FireForge config (reads `binaryName`)
35
65
  * @returns At-risk files, sorted by path
36
66
  */
37
67
  export declare function findUnexportedDriftAtRisk(projectRoot: string, config: FireForgeConfig): Promise<UnexportedDriftAtRisk[]>;
68
+ /**
69
+ * Classifies the engine files build-prepare is about to overwrite into the
70
+ * ones whose content is recorded nowhere (`atRisk`) and the deployed
71
+ * Furnace files no patch records yet whose content is their component
72
+ * source's (`deployedUnexported`, safe to overwrite).
73
+ *
74
+ * Fail-open like {@link findUnexportedDriftAtRisk}: a probe failure yields
75
+ * an empty assessment.
76
+ *
77
+ * @param projectRoot - Project root
78
+ * @param config - Loaded FireForge config (reads `binaryName`)
79
+ * @returns Both lists, sorted by path
80
+ */
81
+ export declare function assessUnexportedDrift(projectRoot: string, config: FireForgeConfig): Promise<UnexportedDriftAssessment>;
82
+ /**
83
+ * Renders the informational note for deployed Furnace files the overwrite
84
+ * rewrites from their own component source: safe for this build, but their
85
+ * patches still owe a re-export.
86
+ */
87
+ export declare function formatDeployedUnexportedNote(files: readonly DeployedUnexportedFile[]): string;
38
88
  /**
39
89
  * Renders the operator-facing warning (or refusal body) naming every file
40
90
  * whose unexported content the build is about to overwrite.
@@ -15,10 +15,19 @@
15
15
  * whose content is explained by neither a patch body nor the pristine
16
16
  * baseline. Advisory by default (a warning naming every file), refusing
17
17
  * under an explicit flag.
18
+ *
19
+ * A deployed Furnace file is checked against its component source as well
20
+ * (`deployed-unexported`): when the engine copy equals the Furnace-composed
21
+ * current source, differs from it only inside generated fragment
22
+ * expansions, or is exactly Furnace's last recorded deployment, the
23
+ * overwrite destroys nothing that is not a component source, so it is safe
24
+ * even though no patch records it yet. Without this, peers' deployed but
25
+ * unexported widgets refused every belted build on a shared checkout.
18
26
  */
19
27
  import { toError } from '../utils/errors.js';
20
28
  import { verbose } from '../utils/logger.js';
21
29
  import { getProjectPaths } from './config.js';
30
+ import { matchDeployedComponentFiles, } from './furnace-composed-source.js';
22
31
  import { collectFurnaceManagedPrefixes } from './furnace-config.js';
23
32
  import { expandUntrackedDirectoryEntries, getWorkingTreeStatus } from './git-status.js';
24
33
  import { classifyFiles } from './status-classify.js';
@@ -98,6 +107,40 @@ async function collectOverwrittenEntries(engineDir, status, binaryName, furnaceP
98
107
  return expanded.filter((entry) => !entry.file.endsWith('/') &&
99
108
  isOverwrittenByBuildPrepare(entry.file, binaryName, furnacePrefixes));
100
109
  }
110
+ /**
111
+ * Splits classified at-risk files into the ones whose engine content is a
112
+ * Furnace deployment of their component source and the rest.
113
+ *
114
+ * Fail-closed: when the source comparison cannot run, every file stays at
115
+ * risk, exactly as before the comparison existed.
116
+ */
117
+ async function separateDeployedUnexported(projectRoot, engineDir, atRisk) {
118
+ let matches;
119
+ try {
120
+ matches = await matchDeployedComponentFiles(projectRoot, engineDir, atRisk.map((entry) => entry.file));
121
+ }
122
+ catch (error) {
123
+ verbose(`Unexported-drift guard could not compare component sources: ${toError(error).message}`);
124
+ return { atRisk, deployedUnexported: [] };
125
+ }
126
+ const deployedUnexported = [];
127
+ const remaining = [];
128
+ for (const entry of atRisk) {
129
+ const match = matches.get(entry.file);
130
+ if (match === undefined) {
131
+ remaining.push(entry);
132
+ }
133
+ else {
134
+ deployedUnexported.push({
135
+ file: entry.file,
136
+ classification: 'deployed-unexported',
137
+ match,
138
+ owner: entry.owner,
139
+ });
140
+ }
141
+ }
142
+ return { atRisk: remaining, deployedUnexported };
143
+ }
101
144
  /**
102
145
  * Finds engine files that build-prepare is about to overwrite and whose
103
146
  * current content is recorded nowhere.
@@ -105,22 +148,42 @@ async function collectOverwrittenEntries(engineDir, status, binaryName, furnaceP
105
148
  * Fail-open: any probe failure is reported verbosely and yields an empty
106
149
  * list. A build must not fail because an advisory guard could not run.
107
150
  *
151
+ * @internal The at-risk half of {@link assessUnexportedDrift}, kept for the
152
+ * guard's tests; build-prepare consumes the full assessment.
153
+ *
108
154
  * @param projectRoot - Project root
109
155
  * @param config - Loaded FireForge config (reads `binaryName`)
110
156
  * @returns At-risk files, sorted by path
111
157
  */
112
158
  export async function findUnexportedDriftAtRisk(projectRoot, config) {
159
+ return (await assessUnexportedDrift(projectRoot, config)).atRisk;
160
+ }
161
+ /**
162
+ * Classifies the engine files build-prepare is about to overwrite into the
163
+ * ones whose content is recorded nowhere (`atRisk`) and the deployed
164
+ * Furnace files no patch records yet whose content is their component
165
+ * source's (`deployedUnexported`, safe to overwrite).
166
+ *
167
+ * Fail-open like {@link findUnexportedDriftAtRisk}: a probe failure yields
168
+ * an empty assessment.
169
+ *
170
+ * @param projectRoot - Project root
171
+ * @param config - Loaded FireForge config (reads `binaryName`)
172
+ * @returns Both lists, sorted by path
173
+ */
174
+ export async function assessUnexportedDrift(projectRoot, config) {
175
+ const empty = { atRisk: [], deployedUnexported: [] };
113
176
  const paths = getProjectPaths(projectRoot);
114
177
  try {
115
178
  const status = await getWorkingTreeStatus(paths.engine);
116
179
  if (status.length === 0)
117
- return [];
180
+ return empty;
118
181
  const furnacePrefixes = await collectFurnaceManagedPrefixes(projectRoot);
119
182
  const candidates = await collectOverwrittenEntries(paths.engine, status, config.binaryName, furnacePrefixes);
120
183
  if (candidates.length === 0)
121
- return [];
184
+ return empty;
122
185
  const classified = await classifyFiles(candidates, paths.engine, paths.patches, config.binaryName, furnacePrefixes);
123
- return classified
186
+ const atRisk = classified
124
187
  .filter((entry) => AT_RISK_CLASSIFICATIONS.has(entry.classification))
125
188
  .map((entry) => ({
126
189
  file: entry.file,
@@ -128,12 +191,29 @@ export async function findUnexportedDriftAtRisk(projectRoot, config) {
128
191
  owner: entry.owner,
129
192
  }))
130
193
  .sort((a, b) => a.file.localeCompare(b.file));
194
+ if (atRisk.length === 0 || furnacePrefixes.size === 0)
195
+ return { ...empty, atRisk };
196
+ return await separateDeployedUnexported(projectRoot, paths.engine, atRisk);
131
197
  }
132
198
  catch (error) {
133
199
  verbose(`Unexported-drift guard could not run: ${toError(error).message}`);
134
- return [];
200
+ return empty;
135
201
  }
136
202
  }
203
+ /**
204
+ * Renders the informational note for deployed Furnace files the overwrite
205
+ * rewrites from their own component source: safe for this build, but their
206
+ * patches still owe a re-export.
207
+ */
208
+ export function formatDeployedUnexportedNote(files) {
209
+ const rows = files.map((entry) => {
210
+ const owner = entry.owner !== undefined ? ` (owned by ${entry.owner})` : '';
211
+ return ` ${entry.file} [deployed-unexported: ${entry.match}]${owner}`;
212
+ });
213
+ return (`${files.length} deployed Furnace file(s) differ from their patch body but are a Furnace ` +
214
+ 'deployment of their component source, so this overwrite loses nothing; re-export their ' +
215
+ `patches when the component work is done:\n${rows.join('\n')}`);
216
+ }
137
217
  /**
138
218
  * Renders the operator-facing warning (or refusal body) naming every file
139
219
  * whose unexported content the build is about to overwrite.
@@ -10,6 +10,8 @@ import type { BuildBaseline } from './build-baseline-types.js';
10
10
  export interface BuildPreparation {
11
11
  /** Number of Furnace components applied (0 if none or no furnace.json) */
12
12
  furnaceApplied: number;
13
+ /** Names of the Furnace components applied, in apply order. */
14
+ furnaceAppliedNames?: string[];
13
15
  /** True when `mach configure` was auto-run to refresh a stale backend. */
14
16
  reconfigured: boolean;
15
17
  /** A changed packaging manifest cannot be safely handled by `mach build faster`. */
@@ -35,6 +37,17 @@ export interface PrepareBuildOptions {
35
37
  * Scripted gates want the stop. An interactive build wants the warning.
36
38
  */
37
39
  refuseUnexportedDrift?: boolean | undefined;
40
+ /**
41
+ * Skip the Furnace apply, so the build packages engine/ as it stands
42
+ * (`fireforge test --build --no-furnace-apply`).
43
+ */
44
+ skipFurnaceApply?: boolean | undefined;
45
+ /**
46
+ * Apply only these components (`fireforge test --build --furnace-apply
47
+ * reached`). Undefined applies every component with undeployed edits; an
48
+ * empty list applies none.
49
+ */
50
+ furnaceComponents?: readonly string[] | undefined;
38
51
  }
39
52
  /**
40
53
  * Describes an exit code in the shell's 128+N signal convention. Truncated
@@ -13,7 +13,7 @@ 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
15
  import { evaluateJarManifestEscalation, formatJarEscalationNotice, } from './build-jar-escalation.js';
16
- import { findUnexportedDriftAtRisk, formatUnexportedDriftWarning, } from './build-overwrite-guard.js';
16
+ import { assessUnexportedDrift, formatDeployedUnexportedNote, formatUnexportedDriftWarning, } from './build-overwrite-guard.js';
17
17
  import { hashEngineFile } from './coverage-extend.js';
18
18
  import { collectChangedEnginePaths, dropPathsMatchingFingerprints } from './engine-changes.js';
19
19
  import { applyAllComponents } from './furnace-apply.js';
@@ -156,7 +156,12 @@ export function isBackendInvalidatingFile(path) {
156
156
  * @throws GeneralError under `--refuse-unexported-drift`
157
157
  */
158
158
  async function reportUnexportedDriftBeforeOverwrite(projectRoot, config, options) {
159
- const atRisk = await findUnexportedDriftAtRisk(projectRoot, config);
159
+ const { atRisk, deployedUnexported } = await assessUnexportedDrift(projectRoot, config);
160
+ // Deployed Furnace files that are their component source's own
161
+ // deployment never block: the overwrite loses nothing. Say so, since
162
+ // their patches still owe a re-export.
163
+ if (deployedUnexported.length > 0)
164
+ info(formatDeployedUnexportedNote(deployedUnexported));
160
165
  if (atRisk.length === 0)
161
166
  return;
162
167
  const message = formatUnexportedDriftWarning(atRisk);
@@ -165,24 +170,48 @@ async function reportUnexportedDriftBeforeOverwrite(projectRoot, config, options
165
170
  }
166
171
  notice(message);
167
172
  }
173
+ /**
174
+ * Applies the named components one at a time, each under its own Furnace
175
+ * mutation (one rollback journal per apply), and merges the results.
176
+ */
177
+ async function applySelectedComponents(projectRoot, names) {
178
+ const merged = {
179
+ applied: [],
180
+ skipped: [],
181
+ errors: [],
182
+ warnings: [],
183
+ };
184
+ for (const componentName of names) {
185
+ const result = await runFurnaceMutation(projectRoot, 'apply-rollback', (ctx) => applyAllComponents(projectRoot, false, { operationContext: ctx, componentName }));
186
+ merged.applied.push(...result.applied);
187
+ merged.skipped.push(...result.skipped);
188
+ merged.errors.push(...result.errors);
189
+ merged.warnings.push(...(result.warnings ?? []));
190
+ }
191
+ return merged;
192
+ }
168
193
  /**
169
194
  * Applies Furnace components before the build when `furnace.json` exists.
170
195
  * Prints every warning the apply computed, then the loud sync banner.
171
196
  *
172
197
  * @param projectRoot - Project root
173
- * @returns Number of components written to engine/
198
+ * @param only - Apply just these components; every component when undefined
199
+ * @returns Names of the components written to engine/
174
200
  */
175
- async function applyFurnaceBeforeBuild(projectRoot) {
201
+ async function applyFurnaceBeforeBuild(projectRoot, only) {
176
202
  if (!(await furnaceConfigExists(projectRoot)))
177
- return 0;
203
+ return [];
178
204
  const furnaceConfig = await loadFurnaceConfig(projectRoot);
179
205
  const hasComponents = Object.keys(furnaceConfig.overrides).length > 0 || Object.keys(furnaceConfig.custom).length > 0;
180
206
  if (!hasComponents)
181
- return 0;
207
+ return [];
182
208
  const furnaceSpinner = spinner('Applying Furnace components...');
183
209
  let result;
184
210
  try {
185
- result = await runFurnaceMutation(projectRoot, 'apply-rollback', (ctx) => applyAllComponents(projectRoot, false, { operationContext: ctx }));
211
+ result =
212
+ only === undefined
213
+ ? await runFurnaceMutation(projectRoot, 'apply-rollback', (ctx) => applyAllComponents(projectRoot, false, { operationContext: ctx }))
214
+ : await applySelectedComponents(projectRoot, only);
186
215
  }
187
216
  catch (error) {
188
217
  furnaceSpinner.error('Failed to apply Furnace components');
@@ -230,7 +259,7 @@ async function applyFurnaceBeforeBuild(projectRoot) {
230
259
  else {
231
260
  furnaceSpinner.stop('Components up to date');
232
261
  }
233
- return furnaceApplied;
262
+ return result.applied.map((entry) => entry.name);
234
263
  }
235
264
  /**
236
265
  * Runs the auto-configure step (`mach configure`) against the freshly
@@ -344,7 +373,9 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
344
373
  throw error;
345
374
  }
346
375
  }
347
- const furnaceApplied = await applyFurnaceBeforeBuild(projectRoot);
376
+ const furnaceAppliedNames = options.skipFurnaceApply === true || options.furnaceComponents?.length === 0
377
+ ? []
378
+ : await applyFurnaceBeforeBuild(projectRoot, options.furnaceComponents);
348
379
  // Generate mozconfig
349
380
  const mozconfigSpinner = spinner('Generating mozconfig...');
350
381
  let mozconfigWritten;
@@ -374,7 +405,8 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
374
405
  reconfigured = true;
375
406
  }
376
407
  return {
377
- furnaceApplied,
408
+ furnaceApplied: furnaceAppliedNames.length,
409
+ furnaceAppliedNames,
378
410
  reconfigured,
379
411
  fullBuildRequired,
380
412
  ...(fullBuildReason !== undefined ? { fullBuildReason } : {}),
@@ -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;