@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
@@ -4,7 +4,7 @@
4
4
  * budgets. `gatePlacementPlan` owns every policy/confirmation decision a
5
5
  * placement export makes before the locked commit in `export.ts` runs.
6
6
  */
7
- import type { ExportOptions, PatchMetadata } from '../types/commands/index.js';
7
+ import type { ExportOptions } from '../types/commands/index.js';
8
8
  import type { FireForgeConfig } from '../types/config.js';
9
9
  import type { SpinnerHandle } from '../utils/logger.js';
10
10
  import { type PlacementPlan } from './export-flow.js';
@@ -14,7 +14,11 @@ import { type PlacementPlan } from './export-flow.js';
14
14
  * shares this shape. With exactOptionalPropertyTypes the keys must be
15
15
  * omitted entirely (not set to undefined) when the flags are absent.
16
16
  */
17
- export declare function patchMetadataExtras(options: ExportOptions): Partial<Pick<PatchMetadata, 'tier' | 'lintIgnore'>>;
17
+ export declare function patchMetadataExtras(options: ExportOptions): {
18
+ tier?: 'branding';
19
+ /** CLI flags only ever carry bare check IDs. */
20
+ lintIgnore?: string[];
21
+ };
18
22
  /**
19
23
  * Resolves and gates the placement plan when any placement flag
20
24
  * (`--order`/`--before`/`--after`) was given: rejects the `--supersede`
@@ -1,3 +1,4 @@
1
+ import type { LintIgnoreChecks } from '../core/patch-lint-ignore.js';
1
2
  import type { PatchesManifest, PatchLintIssue } from '../types/commands/index.js';
2
3
  import type { ExportOptions, PatchCategory } from '../types/commands/index.js';
3
4
  import type { FireForgeConfig } from '../types/config.js';
@@ -22,7 +23,7 @@ export interface RunPatchLintOptions {
22
23
  * `large-patch-lines` on a cohesive branding bundle that genuinely cannot
23
24
  * be split.
24
25
  */
25
- ignoreChecks?: ReadonlySet<string> | undefined;
26
+ ignoreChecks?: LintIgnoreChecks | undefined;
26
27
  /**
27
28
  * Optional explicit tier override (threaded from `PatchMetadata.tier`).
28
29
  * Forces the branding-tier thresholds when set, independent of the
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Lets `export --order` land a new system module whose moz.build line is
3
+ * still unexported working-tree drift in the patch that registers it.
4
+ *
5
+ * The two refusals used to deadlock: the module's export failed
6
+ * `unregistered-system-module` because no patch body carried its moz.build
7
+ * line yet, and the registering patch could not be re-exported first
8
+ * because its line named a file no patch created. The way through is
9
+ * declared intent plus evidence: when the registering patch carries a
10
+ * staged registration for the module and the working tree's moz.build has
11
+ * exactly that line, the export is accepted and the operator is told to
12
+ * re-export the registering patch next. Without the declaration the
13
+ * refusal names the steps, with the exact `patch staged-dependency`
14
+ * command, instead of stopping at "register it".
15
+ *
16
+ * Consumed by export-flow.ts.
17
+ */
18
+ import type { PatchQueueContext } from '../core/patch-lint.js';
19
+ import type { PatchLintIssue } from '../types/commands/index.js';
20
+ /** Result of {@link acceptTreeRegisteredModules}. */
21
+ export interface TreeRegistrationOutcome {
22
+ /** The projected errors left after accepted modules are removed. */
23
+ remaining: PatchLintIssue[];
24
+ /** One note per accepted module, naming the re-export to run next. */
25
+ accepted: string[];
26
+ /** Detail lines for modules whose registration is in the tree but undeclared. */
27
+ advice: string[];
28
+ }
29
+ /**
30
+ * Removes `unregistered-system-module` findings for modules the pending
31
+ * export creates when a queue patch declares their registration as a
32
+ * staged dependency and the working tree's moz.build carries that line.
33
+ * For a module whose line is in the tree but undeclared, returns the steps
34
+ * that make it acceptable.
35
+ *
36
+ * @param args - The engine directory (the working tree), the current queue,
37
+ * the filename the pending export would take, the files it creates, and
38
+ * the placement projection's error findings
39
+ */
40
+ export declare function acceptTreeRegisteredModules(args: {
41
+ engineDir: string;
42
+ baseCtx: PatchQueueContext;
43
+ newFilename: string;
44
+ pendingNewFiles: ReadonlySet<string>;
45
+ projectedIssues: readonly PatchLintIssue[];
46
+ }): Promise<TreeRegistrationOutcome>;
@@ -0,0 +1,91 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Lets `export --order` land a new system module whose moz.build line is
4
+ * still unexported working-tree drift in the patch that registers it.
5
+ *
6
+ * The two refusals used to deadlock: the module's export failed
7
+ * `unregistered-system-module` because no patch body carried its moz.build
8
+ * line yet, and the registering patch could not be re-exported first
9
+ * because its line named a file no patch created. The way through is
10
+ * declared intent plus evidence: when the registering patch carries a
11
+ * staged registration for the module and the working tree's moz.build has
12
+ * exactly that line, the export is accepted and the operator is told to
13
+ * re-export the registering patch next. Without the declaration the
14
+ * refusal names the steps, with the exact `patch staged-dependency`
15
+ * command, instead of stopping at "register it".
16
+ *
17
+ * Consumed by export-flow.ts.
18
+ */
19
+ import { basename, dirname, join } from 'node:path';
20
+ import { STAGED_DEPENDENCY_REASON_PLACEHOLDER } from '../core/patch-lint-staged-registration.js';
21
+ import { pathExists, readText } from '../utils/fs.js';
22
+ async function readLines(path) {
23
+ if (!(await pathExists(path)))
24
+ return undefined;
25
+ return (await readText(path)).split('\n').map((line) => line.trim());
26
+ }
27
+ /** The nearest moz.build at or above `modulePath` whose lines name `leaf` quoted. */
28
+ async function findTreeRegistration(engineDir, modulePath) {
29
+ const leaf = basename(modulePath);
30
+ let dir = dirname(modulePath);
31
+ for (;;) {
32
+ const file = dir === '.' ? 'moz.build' : `${dir}/moz.build`;
33
+ const line = (await readLines(join(engineDir, file)))?.find((candidate) => candidate.includes(`"${leaf}"`) || candidate.includes(`'${leaf}'`));
34
+ if (line !== undefined)
35
+ return { file, line };
36
+ if (dir === '.' || dir === '')
37
+ return undefined;
38
+ const parent = dirname(dir);
39
+ dir = parent === dir ? '.' : parent;
40
+ }
41
+ }
42
+ /**
43
+ * Removes `unregistered-system-module` findings for modules the pending
44
+ * export creates when a queue patch declares their registration as a
45
+ * staged dependency and the working tree's moz.build carries that line.
46
+ * For a module whose line is in the tree but undeclared, returns the steps
47
+ * that make it acceptable.
48
+ *
49
+ * @param args - The engine directory (the working tree), the current queue,
50
+ * the filename the pending export would take, the files it creates, and
51
+ * the placement projection's error findings
52
+ */
53
+ export async function acceptTreeRegisteredModules(args) {
54
+ const outcome = { remaining: [], accepted: [], advice: [] };
55
+ for (const issue of args.projectedIssues) {
56
+ if (issue.check !== 'unregistered-system-module' || !args.pendingNewFiles.has(issue.file)) {
57
+ outcome.remaining.push(issue);
58
+ continue;
59
+ }
60
+ const declared = args.baseCtx.entries.flatMap((entry) => (entry.metadata?.stagedDependencies?.registrations ?? [])
61
+ .filter((registration) => registration.creates === issue.file &&
62
+ (registration.owner === undefined || registration.owner === args.newFilename))
63
+ .map((registration) => ({ holder: entry.filename, registration })));
64
+ let accepted = false;
65
+ for (const { holder, registration } of declared) {
66
+ const lines = await readLines(join(args.engineDir, registration.file));
67
+ if (lines?.includes(registration.line.trim()) !== true)
68
+ continue;
69
+ outcome.accepted.push(`${issue.file} is registered by the working tree's ${registration.file} ` +
70
+ `("${registration.line.trim()}"), which ${holder} declares as a staged registration ` +
71
+ `but has not exported yet. Re-export ${holder} right after this export to land the ` +
72
+ `line: fireforge re-export ${holder}`);
73
+ accepted = true;
74
+ break;
75
+ }
76
+ if (accepted)
77
+ continue;
78
+ outcome.remaining.push(issue);
79
+ const tree = await findTreeRegistration(args.engineDir, issue.file);
80
+ if (tree === undefined)
81
+ continue;
82
+ const owner = args.baseCtx.entries.find((entry) => entry.metadata?.filesAffected.includes(tree.file))?.filename;
83
+ if (owner === undefined)
84
+ continue;
85
+ outcome.advice.push('', `${issue.file} is registered in the working tree's ${tree.file}, but ${owner} has not ` +
86
+ 'exported that line. Land both without untangling the tree:', ` 1. fireforge patch staged-dependency ${owner} --add --kind registration --file ${tree.file} ` +
87
+ `--line "${tree.line.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}" --creates ${issue.file} ` +
88
+ `--owner ${args.newFilename} ${STAGED_DEPENDENCY_REASON_PLACEHOLDER}`, ' 2. re-run this export (a declared registration present in the working tree is accepted)', ` 3. fireforge re-export ${owner}`);
89
+ }
90
+ return outcome;
91
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The remedy printed under a forward-import refusal from `re-export`
3
+ * (plain refresh and `--scan` adoption alike): the earliest order the files
4
+ * can take as their own later patch, and the exact `patch staged-dependency
5
+ * --add` commands that declare the edges instead.
6
+ *
7
+ * The refusal used to end with "export them with --order <n> or run
8
+ * patch staged-dependency --add", which left the operator to work out both
9
+ * the number and every flag of the declaration from the finding text.
10
+ */
11
+ import type { PatchesManifest, PatchLintIssue } from '../types/commands/index.js';
12
+ import type { FireForgeConfig } from '../types/config.js';
13
+ /** Inputs to {@link formatForwardImportRemedy}. */
14
+ export interface ForwardImportRemedyInput {
15
+ /** What to do after the fix, e.g. "re-running the scan". */
16
+ reRunHint: string;
17
+ /** The refusal's findings; only `forward-import` and `forward-registration` ones are read. */
18
+ issues: readonly PatchLintIssue[];
19
+ /** Current manifest (null when unavailable: the order is then not named). */
20
+ manifest: PatchesManifest | null;
21
+ /** Project config; only `patchPolicy` is read. */
22
+ config?: Pick<FireForgeConfig, 'patchPolicy'>;
23
+ /** Category the files would be exported under (the refused patch's). */
24
+ category?: string;
25
+ }
26
+ /**
27
+ * Renders the remedy lines. Falls back to the generic sentence when the
28
+ * findings carry no structured declarations.
29
+ *
30
+ * @param input - See {@link ForwardImportRemedyInput}
31
+ */
32
+ export declare function formatForwardImportRemedy(input: ForwardImportRemedyInput): string;
@@ -0,0 +1,63 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * The remedy printed under a forward-import refusal from `re-export`
4
+ * (plain refresh and `--scan` adoption alike): the earliest order the files
5
+ * can take as their own later patch, and the exact `patch staged-dependency
6
+ * --add` commands that declare the edges instead.
7
+ *
8
+ * The refusal used to end with "export them with --order <n> or run
9
+ * patch staged-dependency --add", which left the operator to work out both
10
+ * the number and every flag of the declaration from the finding text.
11
+ */
12
+ import { STAGED_DEPENDENCY_REASON_PLACEHOLDER } from '../core/patch-lint-staged-registration.js';
13
+ import { earliestLegalOrderAfter } from '../core/patch-order-legal.js';
14
+ import { formatPatchOrder } from '../core/patch-parse.js';
15
+ function quoteArg(value) {
16
+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
17
+ }
18
+ function declarationCommand(declaration) {
19
+ const anchor = declaration.kind === 'import'
20
+ ? `--specifier ${quoteArg(declaration.specifier ?? '')}`
21
+ : `--kind registration --line ${quoteArg(declaration.line ?? '')}`;
22
+ return (`fireforge patch staged-dependency ${declaration.patch} --add --file ${declaration.file} ` +
23
+ `${anchor} --creates ${declaration.creates} --owner ${declaration.owner} ` +
24
+ STAGED_DEPENDENCY_REASON_PLACEHOLDER);
25
+ }
26
+ /**
27
+ * Renders the remedy lines. Falls back to the generic sentence when the
28
+ * findings carry no structured declarations.
29
+ *
30
+ * @param input - See {@link ForwardImportRemedyInput}
31
+ */
32
+ export function formatForwardImportRemedy(input) {
33
+ const declarations = input.issues
34
+ .filter((issue) => issue.check === 'forward-import' || issue.check === 'forward-registration')
35
+ .flatMap((issue) => issue.stagedDeclarations ?? []);
36
+ const ownerOrders = declarations
37
+ .map((declaration) => input.manifest?.patches.find((patch) => patch.filename === declaration.owner)?.order)
38
+ .filter((order) => order !== undefined);
39
+ const latestOwner = ownerOrders.length > 0 ? Math.max(...ownerOrders) : undefined;
40
+ const legalOrder = latestOwner !== undefined && input.manifest !== null && input.category !== undefined
41
+ ? earliestLegalOrderAfter(input.config ?? {}, input.manifest, input.category, latestOwner)
42
+ : null;
43
+ const orderText = legalOrder !== null
44
+ ? `"fireforge export <files> --order ${formatPatchOrder(legalOrder)}"; ` +
45
+ `${formatPatchOrder(legalOrder)} is the earliest legal order after the latest creator ` +
46
+ `(${formatPatchOrder(latestOwner ?? legalOrder)})`
47
+ : '"fireforge export --order <n>"';
48
+ const lines = [
49
+ `Export those files as their own later patch (${orderText}; or ` +
50
+ '"fireforge patch split --order <n>"), or declare the intentional dependency with ' +
51
+ `"fireforge patch staged-dependency --add" before ${input.reRunHint}` +
52
+ (declarations.length > 0 ? ':' : '.'),
53
+ ];
54
+ const seen = new Set();
55
+ for (const declaration of declarations) {
56
+ const command = declarationCommand(declaration);
57
+ if (seen.has(command))
58
+ continue;
59
+ seen.add(command);
60
+ lines.push(` ${command}`);
61
+ }
62
+ return lines.join('\n');
63
+ }
@@ -2,7 +2,7 @@
2
2
  import { watch as fsWatch } from 'node:fs';
3
3
  import { loadConfig } from '../../core/config.js';
4
4
  import { applyAllComponents, computeComponentChecksums } from '../../core/furnace-apply.js';
5
- import { logApplyResult } from '../../core/furnace-apply-output.js';
5
+ import { formatSkipBreakdown, logApplyResult } from '../../core/furnace-apply-output.js';
6
6
  import { getFurnacePaths } from '../../core/furnace-config.js';
7
7
  import { isComponentSourceFile } from '../../core/furnace-constants.js';
8
8
  import { runFurnaceMutation, waitLockMutationOptions } from '../../core/furnace-operation.js';
@@ -108,7 +108,7 @@ async function runWatchLoop(projectRoot) {
108
108
  }
109
109
  const applied = result.applied.length;
110
110
  const skipped = result.skipped.length;
111
- info(`Re-applied: ${applied} applied, ${skipped} skipped`);
111
+ info(`Re-applied: ${applied} applied, ${skipped} skipped${formatSkipBreakdown(result.skipped)}`);
112
112
  retryNeeded = false;
113
113
  lastChecksums = await snapshotWatchedChecksums(watchDirs);
114
114
  }
@@ -318,7 +318,7 @@ export async function furnaceApplyCommand(projectRoot, name, options = {}) {
318
318
  outro(`Dry run complete — would apply ${appliedCount}, skip ${skippedCount}`);
319
319
  }
320
320
  else {
321
- outro(`Applied ${appliedCount}, skipped ${skippedCount}`);
321
+ outro(`Applied ${appliedCount}, skipped ${skippedCount}${formatSkipBreakdown(result.skipped)}`);
322
322
  }
323
323
  // Watch mode: re-apply on file changes in component directories
324
324
  if (options.watch && !dryRun) {
@@ -2,8 +2,9 @@
2
2
  * `fireforge furnace chrome-doc remove <name>` removes the files and
3
3
  * registrations created by `furnace chrome-doc create`.
4
4
  */
5
+ import type { FurnaceLockWaitable } from '../../types/commands/index.js';
5
6
  /** Options for `furnace chrome-doc remove`. */
6
- export interface FurnaceChromeDocRemoveOptions {
7
+ export interface FurnaceChromeDocRemoveOptions extends FurnaceLockWaitable {
7
8
  /** Skip confirmation. Required for real non-interactive removal. */
8
9
  yes?: boolean;
9
10
  /** Print the removal plan without writing files. */
@@ -8,7 +8,7 @@ import { join } from 'node:path';
8
8
  import { confirm } from '@clack/prompts';
9
9
  import { loadConfig } from '../../core/config.js';
10
10
  import { stdioIsInteractive } from '../../core/destructive.js';
11
- import { completeJournalRollback, runFurnaceMutation, } from '../../core/furnace-operation.js';
11
+ import { completeJournalRollback, runFurnaceMutation, waitLockMutationOptions, } from '../../core/furnace-operation.js';
12
12
  import { assertFurnaceEngineReady } from '../../core/furnace-precondition.js';
13
13
  import { createRollbackJournal, snapshotDir, snapshotFile, } from '../../core/furnace-rollback.js';
14
14
  import { FurnaceError } from '../../errors/furnace.js';
@@ -138,7 +138,7 @@ export async function furnaceChromeDocRemoveCommand(projectRoot, name, options =
138
138
  plan,
139
139
  binaryName: forgeConfig.binaryName,
140
140
  operationContext: ctx,
141
- }));
141
+ }), waitLockMutationOptions(options.waitLockSeconds));
142
142
  info(`Removed ${result.removedFiles} source file${result.removedFiles === 1 ? '' : 's'} and ` +
143
143
  `${result.removedJarEntries} jar registration${result.removedJarEntries === 1 ? '' : 's'} for "${name}".`);
144
144
  if (result.removedTestDir) {
@@ -15,8 +15,9 @@
15
15
  * mid-scaffold restores every touched file. A successful run leaves the tree
16
16
  * ready for `fireforge build`.
17
17
  */
18
+ import type { FurnaceLockWaitable } from '../../types/commands/index.js';
18
19
  /** Options for `furnace chrome-doc create`. */
19
- export interface FurnaceChromeDocCreateOptions {
20
+ export interface FurnaceChromeDocCreateOptions extends FurnaceLockWaitable {
20
21
  /**
21
22
  * Emit the titlebar-buttonbox markup and leave platform window controls
22
23
  * visible. Defaults to `true` because the common case is a full chrome
@@ -19,7 +19,7 @@
19
19
  import { join } from 'node:path';
20
20
  import { loadConfig } from '../../core/config.js';
21
21
  import { furnaceConfigExists, loadFurnaceConfig } from '../../core/furnace-config.js';
22
- import { completeJournalRollback, runFurnaceMutation, } from '../../core/furnace-operation.js';
22
+ import { completeJournalRollback, runFurnaceMutation, waitLockMutationOptions, } from '../../core/furnace-operation.js';
23
23
  import { assertFurnaceEngineReady } from '../../core/furnace-precondition.js';
24
24
  import { createRollbackJournal, recordCreatedDir, snapshotFile, } from '../../core/furnace-rollback.js';
25
25
  import { DEFAULT_LICENSE, getLicenseHeader } from '../../core/license-headers.js';
@@ -343,7 +343,7 @@ export async function furnaceChromeDocCreateCommand(projectRoot, name, options =
343
343
  browserWindow,
344
344
  binaryName: forgeConfig.binaryName,
345
345
  operationContext: ctx,
346
- }));
346
+ }), waitLockMutationOptions(options.waitLockSeconds));
347
347
  const nextSteps = [
348
348
  ` 1. Edit engine/browser/base/content/${name}.xhtml and fill in the body.`,
349
349
  ` 2. Localize strings in engine/browser/locales/en-US/browser/${name}.ftl.`,
@@ -5,7 +5,7 @@ import { getProjectPaths, loadConfig } from '../../core/config.js';
5
5
  import { stdioIsInteractive } from '../../core/destructive.js';
6
6
  import { getFurnacePaths, writeFurnaceConfig } from '../../core/furnace-config.js';
7
7
  import { resolveFtlChromeSubPath, tagNameToClassName, WIDGETS_DIR, } from '../../core/furnace-constants.js';
8
- import { completeJournalRollback, runFurnaceMutation, } from '../../core/furnace-operation.js';
8
+ import { completeJournalRollback, runFurnaceMutation, waitLockMutationOptions, } from '../../core/furnace-operation.js';
9
9
  import { CUSTOM_ELEMENT_TAG_PATTERN, CUSTOM_ELEMENT_TAG_RULES, describeTagNameProblem, } from '../../core/furnace-registration-validate.js';
10
10
  import { createRollbackJournal, recordCreatedDir, snapshotFile, } from '../../core/furnace-rollback.js';
11
11
  import { isComponentInEngine, scanWidgetsDirectory } from '../../core/furnace-scanner.js';
@@ -394,7 +394,7 @@ export async function furnaceCreateCommand(projectRoot, name, options = {}) {
394
394
  testStyle,
395
395
  testDir,
396
396
  operationContext: ctx,
397
- }));
397
+ }), waitLockMutationOptions(options.waitLockSeconds));
398
398
  note(formatSuccessNote({
399
399
  componentName,
400
400
  files,
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Targeted `furnace deploy <name>...`: one or more named components applied
3
+ * under ONE furnace lock hold.
4
+ *
5
+ * Deploying a set of widgets one invocation at a time put every call at the
6
+ * back of the engine-lock queue, so a forty-widget deploy waited forty
7
+ * times. Naming them together waits once. Each named component is still its
8
+ * own atomic unit (applied, then its state persisted, before the next one
9
+ * starts), exactly as a single-name deploy, so a failure or signal midway
10
+ * leaves every earlier component committed and consistent.
11
+ */
12
+ import { type ApplyAllComponentsResult } from '../../core/furnace-apply.js';
13
+ import type { FurnacePaths } from '../../core/furnace-config.js';
14
+ import type { FurnaceOperationContext } from '../../core/furnace-operation.js';
15
+ import type { FurnaceConfig } from '../../types/furnace.js';
16
+ import { type FragmentIncluderRefresh } from './deploy-fragment-includers.js';
17
+ /** Outcome of {@link deployNamedComponents}. */
18
+ export type NamedDeployOutcome = {
19
+ kind: 'stock';
20
+ stock: string[];
21
+ } | {
22
+ kind: 'result';
23
+ result: ApplyAllComponentsResult;
24
+ includers: FragmentIncluderRefresh;
25
+ /** Named entries that are stock (warned about, never applied). */
26
+ stock: string[];
27
+ /** Named entries that are overrides or custom components, in order. */
28
+ deployed: string[];
29
+ };
30
+ /**
31
+ * Applies each named override or custom component through the same
32
+ * pipeline as deploy-all (`applyAllComponents` with a `componentName`
33
+ * filter), persisting each one's state right after it applies cleanly.
34
+ * Only then are the other stale includers of the fragments the cleanly
35
+ * applied components include refreshed, so an includer that is itself
36
+ * named is applied once, as a named component.
37
+ *
38
+ * Must run inside one `runFurnaceMutation` so the whole set shares a single
39
+ * lock hold.
40
+ */
41
+ export declare function deployNamedComponents(args: {
42
+ projectRoot: string;
43
+ names: readonly string[];
44
+ config: FurnaceConfig;
45
+ furnacePaths: FurnacePaths;
46
+ isDryRun: boolean;
47
+ operationContext: FurnaceOperationContext;
48
+ }): Promise<NamedDeployOutcome>;
@@ -0,0 +1,107 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Targeted `furnace deploy <name>...`: one or more named components applied
4
+ * under ONE furnace lock hold.
5
+ *
6
+ * Deploying a set of widgets one invocation at a time put every call at the
7
+ * back of the engine-lock queue, so a forty-widget deploy waited forty
8
+ * times. Naming them together waits once. Each named component is still its
9
+ * own atomic unit (applied, then its state persisted, before the next one
10
+ * starts), exactly as a single-name deploy, so a failure or signal midway
11
+ * leaves every earlier component committed and consistent.
12
+ */
13
+ import { applyAllComponents } from '../../core/furnace-apply.js';
14
+ import { getPersistableAppliedEntry, persistSingleComponentState, shouldPersistSingleComponentState, } from '../../core/furnace-state-persist.js';
15
+ import { FurnaceError } from '../../errors/furnace.js';
16
+ import { mergeApplyResults, refreshStaleFragmentIncluders, } from './deploy-fragment-includers.js';
17
+ /**
18
+ * Splits the requested names into deployable and stock entries, refusing
19
+ * the whole invocation before any write when one is unknown. Duplicates
20
+ * are dropped, keeping the first occurrence.
21
+ */
22
+ function partitionDeployNames(names, config) {
23
+ const deployable = [];
24
+ const stock = [];
25
+ for (const name of new Set(names)) {
26
+ if (name in config.overrides || name in config.custom)
27
+ deployable.push(name);
28
+ else if (config.stock.includes(name))
29
+ stock.push(name);
30
+ else
31
+ throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
32
+ }
33
+ return { deployable, stock };
34
+ }
35
+ /** Folds several includer refreshes into one, without repeating a tag. */
36
+ function mergeIncluderRefreshes(refreshes, named) {
37
+ // A named component is reported as deployed in its own right, never as
38
+ // an includer some other named component refreshed on its behalf.
39
+ const refreshed = [...new Set(refreshes.flatMap((r) => r.refreshed))].filter((tag) => !named.has(tag));
40
+ return {
41
+ fragments: [...new Set(refreshes.flatMap((r) => r.fragments))].sort(),
42
+ refreshed,
43
+ failed: refreshes.flatMap((r) => r.failed).filter((f) => !named.has(f.tag)),
44
+ results: refreshes.flatMap((r) => r.results),
45
+ };
46
+ }
47
+ /**
48
+ * Applies each named override or custom component through the same
49
+ * pipeline as deploy-all (`applyAllComponents` with a `componentName`
50
+ * filter), persisting each one's state right after it applies cleanly.
51
+ * Only then are the other stale includers of the fragments the cleanly
52
+ * applied components include refreshed, so an includer that is itself
53
+ * named is applied once, as a named component.
54
+ *
55
+ * Must run inside one `runFurnaceMutation` so the whole set shares a single
56
+ * lock hold.
57
+ */
58
+ export async function deployNamedComponents(args) {
59
+ const { projectRoot, names, config, furnacePaths, isDryRun, operationContext } = args;
60
+ const { deployable, stock } = partitionDeployNames(names, config);
61
+ if (deployable.length === 0)
62
+ return { kind: 'stock', stock };
63
+ const results = [];
64
+ const clean = [];
65
+ for (const name of deployable) {
66
+ const result = await applyAllComponents(projectRoot, isDryRun, {
67
+ componentName: name,
68
+ persistState: false,
69
+ operationContext,
70
+ });
71
+ results.push(result);
72
+ // Each named deploy is atomic: state is persisted only when every apply
73
+ // step succeeded, and any rollback has already restored the engine.
74
+ if (shouldPersistSingleComponentState(result, isDryRun)) {
75
+ await persistSingleComponentState(projectRoot, getPersistableAppliedEntry('Deploy', name, result.applied[0]), furnacePaths);
76
+ clean.push(name);
77
+ }
78
+ else if (isDryRun) {
79
+ clean.push(name);
80
+ }
81
+ }
82
+ // Other deployed includers of a fragment these components include would
83
+ // otherwise keep a stale expansion. Only after a clean, persisted apply
84
+ // (dry-run always previews).
85
+ const refreshes = [];
86
+ for (const name of clean) {
87
+ refreshes.push(await refreshStaleFragmentIncluders({
88
+ projectRoot,
89
+ name,
90
+ config,
91
+ furnacePaths,
92
+ isDryRun,
93
+ operationContext,
94
+ }));
95
+ }
96
+ const includers = mergeIncluderRefreshes(refreshes, new Set(deployable));
97
+ const [first, ...rest] = results;
98
+ // `deployable` is non-empty, so `first` is always set.
99
+ const primary = first;
100
+ return {
101
+ kind: 'result',
102
+ result: mergeApplyResults(primary, [...rest, ...includers.results]),
103
+ includers,
104
+ stock,
105
+ deployed: deployable,
106
+ };
107
+ }
@@ -2,7 +2,8 @@ import type { FurnaceDeployOptions } from '../../types/commands/index.js';
2
2
  /**
3
3
  * Runs the furnace deploy command: apply components then validate in one step.
4
4
  * @param projectRoot - Root directory of the project
5
- * @param name - Optional component name to deploy (deploys all if omitted)
5
+ * @param names - Component name(s) to deploy, all under one lock hold
6
+ * (deploys all when omitted or empty)
6
7
  * @param options - Command options
7
8
  */
8
- export declare function furnaceDeployCommand(projectRoot: string, name?: string, options?: FurnaceDeployOptions): Promise<void>;
9
+ export declare function furnaceDeployCommand(projectRoot: string, names?: string | readonly string[], options?: FurnaceDeployOptions): Promise<void>;