@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
@@ -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>;
@@ -1,17 +1,18 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { loadConfig } from '../../core/config.js';
3
3
  import { applyAllComponents } from '../../core/furnace-apply.js';
4
- import { logApplyResult } from '../../core/furnace-apply-output.js';
4
+ import { formatSkipBreakdown, logApplyResult } from '../../core/furnace-apply-output.js';
5
5
  import { getFurnacePaths } from '../../core/furnace-config.js';
6
6
  import { reportJsconfigPathsSync } from '../../core/furnace-jsconfig.js';
7
- import { runFurnaceMutation, waitLockMutationOptions, } from '../../core/furnace-operation.js';
7
+ import { runFurnaceMutation, waitLockMutationOptions } from '../../core/furnace-operation.js';
8
8
  import { assertFurnaceReady } from '../../core/furnace-precondition.js';
9
- import { getPersistableAppliedEntry, getStepFailureCount, persistSingleComponentState, shouldPersistSingleComponentState, } from '../../core/furnace-state-persist.js';
9
+ import { getStepFailureCount } from '../../core/furnace-state-persist.js';
10
10
  import { hasBlockingStepErrors } from '../../core/furnace-step-errors.js';
11
11
  import { findOverrideBaseVersionDrift, formatOverrideBaseVersionDriftError, formatOverrideBaseVersionDriftWarning, } from '../../core/furnace-version-drift.js';
12
12
  import { FurnaceError } from '../../errors/furnace.js';
13
13
  import { info, intro, note, notice, outro, spinner, warn } from '../../utils/logger.js';
14
- import { formatFragmentIncluderNotice, mergeApplyResults, refreshStaleFragmentIncluders, } from './deploy-fragment-includers.js';
14
+ import { formatFragmentIncluderNotice } from './deploy-fragment-includers.js';
15
+ import { deployNamedComponents } from './deploy-named.js';
15
16
  import { runDeployValidation } from './validation-output.js';
16
17
  /**
17
18
  * Builds the final deploy failure summary from apply and validation error counts.
@@ -39,51 +40,6 @@ function getFailedComponentNames(result) {
39
40
  }
40
41
  return failed;
41
42
  }
42
- /**
43
- * Applies a single named override or custom component in targeted deploy
44
- * mode.
45
- *
46
- * Delegates to {@link applyAllComponents} with a `componentName` filter so
47
- * targeted deploys run the exact same pipeline as deploy-all, including
48
- * workspace-deletion detection, engine orphan undeploy, and jar.mn /
49
- * customElements.js re-sync. Calling the per-component apply helpers
50
- * directly never prunes: renaming a helper file in the workspace then leaves
51
- * the old deployed file and its stale jar.mn line in the engine.
52
- *
53
- * `persistState: false` matters here: the batch persist path replaces
54
- * `appliedChecksums` wholesale with only this run's entries, which for a
55
- * named deploy would wipe every other component's state. Named deploy keeps
56
- * its per-component state merge ({@link persistSingleComponentState}) and
57
- * its atomicity gate ({@link shouldPersistSingleComponentState}) at the call
58
- * site. Rollback on failure happens inside `applyAllComponents`. The journal
59
- * returned on success is ignored (the deploy keeps its files).
60
- *
61
- * The caller then runs the fragment-includer follow-up
62
- * ({@link refreshStaleFragmentIncluders}) ONLY after this component's state
63
- * is persisted: each includer is applied and persisted as its own unit, so
64
- * a signal mid-loop restores at most the includer in flight and leaves every
65
- * earlier unit committed. Moving the persist after the includer loop would
66
- * silently break that atomicity.
67
- *
68
- * @param name - Component name to apply
69
- * @param config - Loaded Furnace configuration
70
- * @param isDryRun - Whether file writes should be skipped
71
- * @returns Apply result for the named component, or `stock` for stock-only
72
- * entries
73
- */
74
- async function applyNamedComponent(name, config, isDryRun, projectRoot, operationContext) {
75
- if (!(name in config.overrides) && !(name in config.custom)) {
76
- if (config.stock.includes(name)) {
77
- return 'stock';
78
- }
79
- throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
80
- }
81
- return applyAllComponents(projectRoot, isDryRun, {
82
- componentName: name,
83
- persistState: false,
84
- ...(operationContext ? { operationContext } : {}),
85
- });
86
- }
87
43
  /**
88
44
  * Prints the deploy summary after apply and validation complete.
89
45
  * @param result - Aggregate apply result
@@ -109,7 +65,7 @@ function printDeploymentSummary(result, totalErrors, totalWarnings, componentCou
109
65
  : ''), 'Dry Run Summary');
110
66
  }
111
67
  else {
112
- note(`Applied ${appliedCount}, skipped ${skippedCount}\n` +
68
+ note(`Applied ${appliedCount}, skipped ${skippedCount}${formatSkipBreakdown(result.skipped)}\n` +
113
69
  `${applyErrors} apply error(s)` +
114
70
  (stepErrorCount > 0 ? `, ${stepErrorCount} registration step error(s)` : '') +
115
71
  `\n` +
@@ -132,14 +88,25 @@ function enforceScopedOverrideVersionDriftPreflight(scopedDrift, force) {
132
88
  throw new FurnaceError(formatOverrideBaseVersionDriftError(scopedDrift));
133
89
  }
134
90
  }
91
+ /**
92
+ * Normalises the positional argument: a single name (programmatic callers)
93
+ * or the variadic list commander passes. An empty list means "deploy all".
94
+ */
95
+ function normalizeDeployNames(names) {
96
+ if (names === undefined)
97
+ return [];
98
+ return typeof names === 'string' ? [names] : [...names];
99
+ }
135
100
  /**
136
101
  * Runs the furnace deploy command: apply components then validate in one step.
137
102
  * @param projectRoot - Root directory of the project
138
- * @param name - Optional component name to deploy (deploys all if omitted)
103
+ * @param names - Component name(s) to deploy, all under one lock hold
104
+ * (deploys all when omitted or empty)
139
105
  * @param options - Command options
140
106
  */
141
- export async function furnaceDeployCommand(projectRoot, name, options = {}) {
107
+ export async function furnaceDeployCommand(projectRoot, names, options = {}) {
142
108
  const isDryRun = options.dryRun ?? false;
109
+ const requested = normalizeDeployNames(names);
143
110
  intro(isDryRun ? 'Furnace Deploy (dry run)' : 'Furnace Deploy');
144
111
  const { config } = await assertFurnaceReady(projectRoot);
145
112
  const furnacePaths = getFurnacePaths(projectRoot);
@@ -157,7 +124,9 @@ export async function furnaceDeployCommand(projectRoot, name, options = {}) {
157
124
  const forgeConfig = await loadConfig(projectRoot);
158
125
  const driftEntries = findOverrideBaseVersionDrift(config, forgeConfig.firefox.version);
159
126
  const force = options.force ?? false;
160
- const scopedDrift = name ? driftEntries.filter((entry) => entry.name === name) : driftEntries;
127
+ const scopedDrift = requested.length > 0
128
+ ? driftEntries.filter((entry) => requested.includes(entry.name))
129
+ : driftEntries;
161
130
  enforceScopedOverrideVersionDriftPreflight(scopedDrift, force);
162
131
  // --- Step 1: Apply ---
163
132
  const applySpinner = spinner(isDryRun ? 'Calculating planned actions...' : 'Applying components to engine...');
@@ -166,55 +135,37 @@ export async function furnaceDeployCommand(projectRoot, name, options = {}) {
166
135
  // phase below is read-only and runs outside the lock so two concurrent
167
136
  // `furnace deploy` runs only contend on the actual mutation.
168
137
  const applyOutcome = await runFurnaceMutation(projectRoot, 'deploy-rollback', async (ctx) => {
169
- if (name) {
170
- const namedApplyResult = await applyNamedComponent(name, config, isDryRun, projectRoot, ctx);
171
- if (namedApplyResult === 'stock') {
172
- return { kind: 'stock' };
173
- }
174
- // Named deploy is atomic: state is persisted only when every apply
175
- // step succeeded. Any rollback triggered by applyNamedComponent has
176
- // already restored the engine to its pre-deploy state, so persisting
177
- // partial checksums here would mis-report the next status/apply run
178
- // against a workspace that was never actually deployed.
179
- const primaryClean = shouldPersistSingleComponentState(namedApplyResult, isDryRun);
180
- if (primaryClean) {
181
- await persistSingleComponentState(projectRoot, getPersistableAppliedEntry('Deploy', name, namedApplyResult.applied[0]), furnacePaths);
182
- }
183
- // Other deployed includers of a fragment this component includes
184
- // would otherwise keep a stale expansion. Only after a clean,
185
- // persisted primary apply (dry-run always previews).
186
- if (!primaryClean && !isDryRun) {
187
- return { kind: 'result', result: namedApplyResult };
188
- }
189
- const includers = await refreshStaleFragmentIncluders({
138
+ if (requested.length > 0) {
139
+ return deployNamedComponents({
190
140
  projectRoot,
191
- name,
141
+ names: requested,
192
142
  config,
193
143
  furnacePaths,
194
144
  isDryRun,
195
145
  operationContext: ctx,
196
146
  });
197
- return {
198
- kind: 'result',
199
- result: mergeApplyResults(namedApplyResult, includers.results),
200
- includers,
201
- };
202
147
  }
203
148
  const allResult = await applyAllComponents(projectRoot, isDryRun, {
204
149
  operationContext: ctx,
205
150
  });
206
- return { kind: 'result', result: allResult };
151
+ return { kind: 'all', result: allResult };
207
152
  }, { dryRun: isDryRun, ...waitLockMutationOptions(options.waitLockSeconds) });
153
+ const warnStock = () => {
154
+ for (const stockName of applyOutcome.kind === 'all' ? [] : applyOutcome.stock) {
155
+ warn(`"${stockName}" is a stock component. Stock components are not applied locally.`);
156
+ }
157
+ };
208
158
  if (applyOutcome.kind === 'stock') {
209
159
  applySpinner.stop('Apply skipped');
210
- warn(`"${name}" is a stock component. Stock components are not applied locally.`);
160
+ warnStock();
211
161
  outro(isDryRun ? 'Dry run complete (no files modified)' : 'Deploy complete');
212
162
  return;
213
163
  }
214
164
  const result = applyOutcome.result;
215
165
  applySpinner.stop(isDryRun ? 'Planned actions calculated' : 'Components applied');
166
+ warnStock();
216
167
  logApplyResult(result, isDryRun);
217
- if (applyOutcome.includers) {
168
+ if (applyOutcome.kind === 'result') {
218
169
  for (const line of formatFragmentIncluderNotice(applyOutcome.includers, isDryRun)) {
219
170
  notice(line);
220
171
  }
@@ -235,20 +186,33 @@ export async function furnaceDeployCommand(projectRoot, name, options = {}) {
235
186
  }
236
187
  const validateSpinner = spinner(isDryRun ? 'Validating (read-only)...' : 'Validating...');
237
188
  const failedComponents = getFailedComponentNames(result);
189
+ // A named deploy validates every named component that applied, then the
190
+ // includers it refreshed. The first clean name leads (the validation
191
+ // runner skips the whole named branch when its lead failed), and every
192
+ // failed name is counted as skipped.
193
+ const deployed = applyOutcome.kind === 'result' ? applyOutcome.deployed : [];
194
+ const failedNamed = deployed.filter((tag) => failedComponents.has(tag));
195
+ const lead = deployed.find((tag) => !failedComponents.has(tag)) ?? deployed[0];
238
196
  const validation = await runDeployValidation({
239
197
  validateSpinner,
240
- name,
198
+ name: lead,
241
199
  config,
242
200
  furnacePaths,
243
201
  failedComponents,
244
202
  isDryRun,
245
203
  projectRoot,
246
204
  dryRunActions: result.actions,
247
- alsoValidate: applyOutcome.includers?.refreshed ?? [],
205
+ alsoValidate: applyOutcome.kind === 'result'
206
+ ? [...deployed.filter((tag) => tag !== lead), ...applyOutcome.includers.refreshed]
207
+ : [],
248
208
  });
249
209
  if (validation.done)
250
210
  return;
251
- const { totalErrors, totalWarnings, componentCount, skippedValidationCount } = validation;
211
+ const { totalErrors, totalWarnings, componentCount } = validation;
212
+ // The runner counts a failed lead itself; failed extras it drops silently.
213
+ const skippedValidationCount = lead !== undefined && failedComponents.has(lead)
214
+ ? failedNamed.length
215
+ : validation.skippedValidationCount + failedNamed.length;
252
216
  // --- Step 3: Summary ---
253
217
  printDeploymentSummary(result, totalErrors, totalWarnings, componentCount, skippedValidationCount, isDryRun);
254
218
  }
@@ -4,6 +4,8 @@ import { join } from 'node:path';
4
4
  import { getProjectPaths, loadState } from '../../core/config.js';
5
5
  import { diffLines, renderHunks } from '../../core/diff-hunks.js';
6
6
  import { getOverrideEngineTargetPath } from '../../core/furnace-apply-helpers.js';
7
+ import { normalizeForChecksum } from '../../core/furnace-checksum-utils.js';
8
+ import { composeComponentSource } from '../../core/furnace-composed-source.js';
7
9
  import { getFurnacePaths, loadFurnaceConfig } from '../../core/furnace-config.js';
8
10
  import { isComponentSourceFile, resolveFtlDir } from '../../core/furnace-constants.js';
9
11
  import { getFileContentAtRef } from '../../core/git-file-ops.js';
@@ -108,6 +110,14 @@ async function diffOverride(name, projectRoot, config) {
108
110
  * copy. Shows what would change (or has changed) on the next
109
111
  * `furnace apply`.
110
112
  *
113
+ * The workspace side is the Furnace-composed source: a stylesheet with
114
+ * `@fireforge-include` directives is compared in its fragment-expanded
115
+ * form, exactly as apply deploys it. Comparing the raw source instead
116
+ * reported every freshly deployed fragment includer as a large difference
117
+ * (the inlined fragment bodies and end markers), so the diff could not show
118
+ * whether a deploy would change anything. Line endings and a BOM are
119
+ * ignored, as apply's own drift check ignores them.
120
+ *
111
121
  * `.ftl` files deploy to `engine/<ftlDir>/<name>.ftl` via
112
122
  * `applyCustomFtlFile`, not to `customConfig.targetPath`, so the
113
123
  * deployment-target lookup has to branch on extension. Without the branch, a
@@ -136,7 +146,19 @@ async function diffCustom(name, projectRoot, config) {
136
146
  if (!isComponentSourceFile(entry.name))
137
147
  continue;
138
148
  const workspacePath = join(customDir, entry.name);
139
- const workspaceContent = await readText(workspacePath);
149
+ const rawWorkspace = await readText(workspacePath);
150
+ let workspaceContent = rawWorkspace;
151
+ let composedNote = '';
152
+ try {
153
+ workspaceContent = await composeComponentSource(rawWorkspace, entry.name, furnacePaths.sharedDir);
154
+ if (workspaceContent !== rawWorkspace)
155
+ composedNote = ' (composed: fragments expanded)';
156
+ }
157
+ catch (error) {
158
+ // A missing fragment: no composed form exists, so show the raw
159
+ // source and say why; `furnace validate` reports the fragment itself.
160
+ composedNote = ` (raw: ${toError(error).message})`;
161
+ }
140
162
  // `.ftl` files deploy to the locale tree, not the component's
141
163
  // targetPath. Mirror `applyCustomFtlFile`'s target computation so the
142
164
  // diff header and the existence probe name the same path apply
@@ -154,13 +176,14 @@ async function diffCustom(name, projectRoot, config) {
154
176
  hasDifferences = true;
155
177
  continue;
156
178
  }
157
- const deployedContent = await readText(deployedPath);
179
+ const deployedContent = normalizeForChecksum(await readText(deployedPath));
180
+ workspaceContent = normalizeForChecksum(workspaceContent);
158
181
  if (workspaceContent === deployedContent) {
159
182
  continue;
160
183
  }
161
184
  hasDifferences = true;
162
185
  info(`--- ${deployedDisplayPath}`);
163
- info(`+++ components/custom/${name}/${entry.name}`);
186
+ info(`+++ components/custom/${name}/${entry.name}${composedNote}`);
164
187
  for (const line of formatUnifiedDiff(deployedContent, workspaceContent)) {
165
188
  info(line);
166
189
  }