@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
@@ -22,19 +22,47 @@ import { assertConfirmationAvailable, confirmDestructive } from '../../core/dest
22
22
  import { appendHistoryBestEffort } from '../../core/history-log.js';
23
23
  import { formatPatchNotFoundError } from '../../core/patch-identifier-suggest.js';
24
24
  import { buildPatchQueueContext } from '../../core/patch-lint.js';
25
+ import { buildLintIgnoreChecks } from '../../core/patch-lint-ignore.js';
25
26
  import { withPatchDirectoryLock } from '../../core/patch-lock.js';
26
27
  import { loadPatchesManifest, renumberPatchesInManifest, resolvePatchIdentifier, savePatchesManifest, validatePatchesManifest, } from '../../core/patch-manifest.js';
27
28
  import { enforcePatchPolicy } from '../../core/patch-policy.js';
29
+ import { manifestWithRetiredSlots } from '../../core/patch-retired.js';
28
30
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
29
31
  import { toError } from '../../utils/errors.js';
30
32
  import { readText, removeFile, writeText } from '../../utils/fs.js';
31
33
  import { info, intro, outro, success, warn } from '../../utils/logger.js';
32
34
  import { addWaitLockOption, commanderArgParser, pickDefined, resolveWaitLockSeconds, } from '../../utils/options.js';
33
35
  import { normalizePatchDisplayName, parsePositiveIntegerFlag } from '../../utils/validation.js';
36
+ import { withDeclarationsReport } from '../declarations-report.js';
34
37
  import { proceedAfterDecision } from '../destructive-decision.js';
35
38
  import { placementPlansEqual, resolvePlacementPlan } from '../export-flow.js';
36
39
  import { runPatchLint } from '../export-shared.js';
37
- import { assertSourceOwnsFiles, buildNewPatchMetadata, buildSplitDiff, buildSplitSummary, findOwnerRewriteHolders, mergeStagedForwardImports, projectSplitManifest, rewriteSplitOwners, runProjectedSplitLint, } from './split-plan.js';
40
+ import { assertSourceOwnsFiles, buildNewPatchMetadata, buildSplitDiff, buildSplitSummary, mergeStagedForwardImports, projectSplitManifest, runProjectedSplitLint, splitDeclarationRenamer, } from './split-plan.js';
41
+ import { describeStagedMaintenance, emptyStagedMaintenance, findOwnerRewriteHolders, rewriteSplitOwners, withoutDroppedDeclarations, withoutRelocatedDeclarations, } from './split-staged.js';
42
+ /**
43
+ * History payload for the declarations a split re-homed or dropped, omitted
44
+ * when it touched none so unaffected records keep their old shape.
45
+ */
46
+ function stagedMaintenanceHistory(plan) {
47
+ const { relocated, dropped } = plan.stagedMaintenance;
48
+ const relocatedCount = (relocated.forwardImports?.length ?? 0) + (relocated.registrations?.length ?? 0);
49
+ return {
50
+ ...(relocatedCount > 0 ? { relocatedDeclarations: relocated } : {}),
51
+ ...(dropped.length > 0 ? { droppedDeclarations: dropped } : {}),
52
+ };
53
+ }
54
+ /**
55
+ * Prints the owner rewrites and the declarations the move re-homed or
56
+ * dropped, after a committed split or `move-files --create`.
57
+ */
58
+ export function reportSplitStagedChanges(plan) {
59
+ if (plan.ownerRewrites.length > 0) {
60
+ info(`Re-pointed staged-dependency owners in: ${plan.ownerRewrites.join(', ')}`);
61
+ }
62
+ for (const line of describeStagedMaintenance(plan.stagedMaintenance, plan.source.filename, plan.placement.newFilename)) {
63
+ info(line);
64
+ }
65
+ }
38
66
  /**
39
67
  * Commits a confirmed split under the patch directory lock: renumber →
40
68
  * write new patch body → write shrunken source body → single manifest
@@ -84,13 +112,17 @@ export async function commitPatchSplit(patchesDir, plan, newMetadata, options, c
84
112
  if (decls?.length) {
85
113
  withOwners = mergeStagedForwardImports(withOwners, decls);
86
114
  }
115
+ withOwners = withoutDroppedDeclarations(withOwners, plan.stagedMaintenance.dropped);
87
116
  if (patch.filename !== effectiveSourceFilename)
88
117
  return withOwners;
89
- return { ...withOwners, filesAffected: plan.remainingFiles };
118
+ return {
119
+ ...withoutRelocatedDeclarations(withOwners, movedSet),
120
+ filesAffected: plan.remainingFiles,
121
+ };
90
122
  });
91
123
  updatedPatches.push(newMetadata);
92
124
  updatedPatches.sort((a, b) => a.order - b.order || a.filename.localeCompare(b.filename));
93
- const updated = validatePatchesManifest({ ...fresh, patches: updatedPatches });
125
+ const updated = validatePatchesManifest(manifestWithRetiredSlots(fresh, updatedPatches));
94
126
  await savePatchesManifest(patchesDir, updated);
95
127
  await appendHistoryBestEffort(patchesDir, {
96
128
  operation: 'patch-split',
@@ -100,6 +132,7 @@ export async function commitPatchSplit(patchesDir, plan, newMetadata, options, c
100
132
  order: plan.placement.insertionOrder,
101
133
  files: plan.movedFiles,
102
134
  ownerRewrites: plan.ownerRewrites,
135
+ ...stagedMaintenanceHistory(plan),
103
136
  renames: [...plan.placement.renameMap.entries()].map(([from, entry]) => ({
104
137
  from,
105
138
  to: entry.newFilename,
@@ -212,16 +245,27 @@ export async function patchSplitCommand(projectRoot, sourceId, options) {
212
245
  name: displayName,
213
246
  description: options.description ?? '',
214
247
  ownerRewrites: findOwnerRewriteHolders(manifest.patches, source.filename, movedSet),
215
- // Populated by runProjectedSplitLint below (forward edges into the new patch).
248
+ // Populated by runProjectedSplitLint below (forward edges into the new
249
+ // patch, and the declarations the move re-homes or makes unused).
216
250
  stagedDependencyAdditions: new Map(),
251
+ stagedMaintenance: emptyStagedMaintenance(),
217
252
  };
253
+ // Policy first: the projected manifest (orders, ranges, the gap rule)
254
+ // does not depend on the lint below, so a refused --order fails before
255
+ // the per-patch and projected lint passes instead of after them.
256
+ enforcePatchPolicy({
257
+ config,
258
+ manifest: projectSplitManifest(manifest, plan, buildNewPatchMetadata(plan, config)),
259
+ command: 'patch split',
260
+ forceUnsafe: options.forceUnsafe === true,
261
+ });
218
262
  // Per-patch lint both projected bodies, threading the source patch's
219
263
  // tier/lintIgnore so an intentional-advisory patch can still split.
220
264
  // The whole-queue context (built once, with the config for committed-gate
221
265
  // parity) resolves cross-patch imports and sibling head.js harness roots
222
266
  // instead of linting each projected body blind.
223
267
  const patchQueueCtx = await buildPatchQueueContext(paths.patches, config);
224
- const ignoreChecks = source.lintIgnore ? new Set(source.lintIgnore) : undefined;
268
+ const ignoreChecks = buildLintIgnoreChecks(source.lintIgnore);
225
269
  await runPatchLint({
226
270
  engineDir: paths.engine,
227
271
  filesAffected: remainingFiles,
@@ -242,15 +286,16 @@ export async function patchSplitCommand(projectRoot, sourceId, options) {
242
286
  ignoreChecks,
243
287
  patchTier: source.tier,
244
288
  });
245
- const { conflicts, stagedDependencyAdditions } = runProjectedSplitLint(plan, patchQueueCtx);
246
- plan.stagedDependencyAdditions = stagedDependencyAdditions;
247
- const newMetadata = buildNewPatchMetadata(plan, config);
248
- enforcePatchPolicy({
249
- config,
250
- manifest: projectSplitManifest(manifest, plan, newMetadata),
251
- command: 'patch split',
252
- forceUnsafe: options.forceUnsafe === true,
289
+ const projected = runProjectedSplitLint(plan, patchQueueCtx);
290
+ plan.stagedDependencyAdditions = projected.stagedDependencyAdditions;
291
+ plan.stagedMaintenance = projected.stagedMaintenance;
292
+ const conflicts = await withDeclarationsReport(projected.conflicts, {
293
+ projectRoot,
294
+ regressions: projected.regressions,
295
+ out: options.declarationsOut,
296
+ rename: splitDeclarationRenamer(plan),
253
297
  });
298
+ const newMetadata = buildNewPatchMetadata(plan, config);
254
299
  const decision = await confirmDestructive({
255
300
  operation: 'patch-split',
256
301
  title: `Split ${plan.movedFiles.length} file(s) out of ${source.filename} into ${placement.newFilename}`,
@@ -264,9 +309,7 @@ export async function patchSplitCommand(projectRoot, sourceId, options) {
264
309
  return;
265
310
  await commitPatchSplit(paths.patches, plan, newMetadata, options, config);
266
311
  success(`Split ${source.filename}: ${placement.newFilename} now owns ${plan.movedFiles.length} file(s)`);
267
- if (plan.ownerRewrites.length > 0) {
268
- info(`Re-pointed staged-dependency owners in: ${plan.ownerRewrites.join(', ')}`);
269
- }
312
+ reportSplitStagedChanges(plan);
270
313
  outro('Split complete');
271
314
  }
272
315
  /**
@@ -287,7 +330,8 @@ export function registerPatchSplit(parent, context) {
287
330
  .option('--dry-run', 'Show what would happen without writing')
288
331
  .option('-y, --yes', 'Skip confirmation prompt (required for non-TTY)')
289
332
  .option('--force-unsafe', 'Bypass projected-lint refusals')
290
- .option('--skip-lint', 'Skip per-patch lint of the projected bodies');
333
+ .option('--skip-lint', 'Skip per-patch lint of the projected bodies')
334
+ .option('--declarations-out <path>', 'Where a refusal writes its one --from-ready staged-declaration report (default: .fireforge/declarations/<timestamp>.json)');
291
335
  addWaitLockOption(command).action(withErrorHandling(async (sourceId, options) => {
292
336
  await patchSplitCommand(getProjectRoot(), sourceId, {
293
337
  files: options.files,
@@ -302,6 +346,7 @@ export function registerPatchSplit(parent, context) {
302
346
  yes: options.yes,
303
347
  forceUnsafe: options.forceUnsafe,
304
348
  skipLint: options.skipLint,
349
+ declarationsOut: options.declarationsOut,
305
350
  waitLock: options.waitLock,
306
351
  }),
307
352
  });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `fireforge patch stage <name>...`: stages the named patches' `.patch`
3
+ * files and only their rows of `patches/patches.json` in the project
4
+ * repository's git index.
5
+ *
6
+ * The case it exists for: several sessions edit one checkout's queue, and
7
+ * each must commit only its own patches. Staging the whole manifest takes
8
+ * every session's rows; staging one owner's hunks of it with
9
+ * `git apply --cached --unidiff-zero` placed a kept insertion at its
10
+ * new-side line number once a dropped hunk above had shifted the file, and
11
+ * an invalid manifest was committed. This command builds the staged
12
+ * manifest from HEAD's rows plus the named working rows (see
13
+ * `core/patch-manifest-stage.ts`), validates it, and writes it straight to
14
+ * the index with `git hash-object` + `git update-index`. The working copy
15
+ * is never touched, and nothing is committed.
16
+ */
17
+ import { Command } from 'commander';
18
+ import type { CommandContext } from '../../types/cli.js';
19
+ import type { PatchStageOptions } from '../../types/commands/index.js';
20
+ /**
21
+ * Runs the `patch stage` command.
22
+ *
23
+ * @param projectRoot - Project root directory (a git working tree)
24
+ * @param identifiers - Patch filenames, ordinals or names to stage
25
+ * @param options - Command options
26
+ */
27
+ export declare function patchStageCommand(projectRoot: string, identifiers: readonly string[], options?: PatchStageOptions): Promise<void>;
28
+ /**
29
+ * Registers the `patch stage` subcommand on the `patch` parent.
30
+ *
31
+ * @param parent - Parent Commander command
32
+ * @param context - Shared CLI registration context
33
+ */
34
+ export declare function registerPatchStage(parent: Command, context: CommandContext): void;
@@ -0,0 +1,153 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * `fireforge patch stage <name>...`: stages the named patches' `.patch`
4
+ * files and only their rows of `patches/patches.json` in the project
5
+ * repository's git index.
6
+ *
7
+ * The case it exists for: several sessions edit one checkout's queue, and
8
+ * each must commit only its own patches. Staging the whole manifest takes
9
+ * every session's rows; staging one owner's hunks of it with
10
+ * `git apply --cached --unidiff-zero` placed a kept insertion at its
11
+ * new-side line number once a dropped hunk above had shifted the file, and
12
+ * an invalid manifest was committed. This command builds the staged
13
+ * manifest from HEAD's rows plus the named working rows (see
14
+ * `core/patch-manifest-stage.ts`), validates it, and writes it straight to
15
+ * the index with `git hash-object` + `git update-index`. The working copy
16
+ * is never touched, and nothing is committed.
17
+ */
18
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
19
+ import { tmpdir } from 'node:os';
20
+ import { join, relative, sep } from 'node:path';
21
+ import { Command } from 'commander';
22
+ import { getProjectPaths } from '../../core/config.js';
23
+ import { git } from '../../core/git-base.js';
24
+ import { withPatchDirectoryLock } from '../../core/patch-lock.js';
25
+ import { loadPatchesManifestForWrite, resolvePatchIdentifier, validatePatchesManifest, } from '../../core/patch-manifest.js';
26
+ import { mergeStagedManifest, serializeStagedManifest, } from '../../core/patch-manifest-stage.js';
27
+ import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
28
+ import { toError } from '../../utils/errors.js';
29
+ import { pathExists } from '../../utils/fs.js';
30
+ import { info, intro, outro } from '../../utils/logger.js';
31
+ import { addWaitLockOption, pickDefined, resolveWaitLockSeconds } from '../../utils/options.js';
32
+ import { exec } from '../../utils/process.js';
33
+ /** A project-relative path in git's separator. */
34
+ function gitPath(projectRoot, path) {
35
+ return relative(projectRoot, path).split(sep).join('/');
36
+ }
37
+ /** HEAD's manifest, or null when HEAD has none (or there is no HEAD yet). */
38
+ async function readHeadManifest(projectRoot, manifestPath) {
39
+ const shown = await exec('git', ['show', `HEAD:./${manifestPath}`], { cwd: projectRoot });
40
+ if (shown.exitCode !== 0)
41
+ return null;
42
+ try {
43
+ return validatePatchesManifest(JSON.parse(shown.stdout));
44
+ }
45
+ catch (error) {
46
+ throw new GeneralError(`HEAD's ${manifestPath} is not a valid manifest (${toError(error).message}); stage it by hand.`);
47
+ }
48
+ }
49
+ /** Resolves each identifier to a filename, against the working rows then HEAD's. */
50
+ function resolveFilenames(identifiers, working, head) {
51
+ const filenames = new Set();
52
+ for (const identifier of identifiers) {
53
+ const live = resolvePatchIdentifier(identifier, working.patches);
54
+ // A patch deleted (or renamed away) in the working copy is named by its
55
+ // HEAD filename, so its row can be staged as removed.
56
+ const gone = (head?.patches ?? []).find((row) => row.filename === identifier || row.filename === `${identifier}.patch`);
57
+ const filename = live?.filename ?? gone?.filename;
58
+ if (filename === undefined) {
59
+ throw new InvalidArgumentError(`No patch "${identifier}" in the working manifest or at HEAD.`, identifier);
60
+ }
61
+ filenames.add(filename);
62
+ }
63
+ return filenames;
64
+ }
65
+ /** Stages one `.patch` file: its content, or its removal when it is gone. */
66
+ async function stagePatchFile(projectRoot, patchPath) {
67
+ if (await pathExists(join(projectRoot, patchPath))) {
68
+ await git(['add', '--', patchPath], projectRoot);
69
+ return;
70
+ }
71
+ const tracked = await exec('git', ['ls-files', '--error-unmatch', '--', patchPath], {
72
+ cwd: projectRoot,
73
+ });
74
+ if (tracked.exitCode === 0) {
75
+ await git(['rm', '--cached', '--quiet', '--', patchPath], projectRoot);
76
+ }
77
+ }
78
+ /** Writes `content` as a blob and points the index entry at it. */
79
+ async function stageBlob(projectRoot, path, content) {
80
+ const scratch = await mkdtemp(join(tmpdir(), 'fireforge-stage-'));
81
+ try {
82
+ const file = join(scratch, 'patches.json');
83
+ await writeFile(file, content);
84
+ const sha = (await git(['hash-object', '-w', '--no-filters', file], projectRoot)).trim();
85
+ await git(['update-index', '--add', '--cacheinfo', `100644,${sha},${path}`], projectRoot);
86
+ }
87
+ finally {
88
+ await rm(scratch, { recursive: true, force: true });
89
+ }
90
+ }
91
+ /**
92
+ * Runs the `patch stage` command.
93
+ *
94
+ * @param projectRoot - Project root directory (a git working tree)
95
+ * @param identifiers - Patch filenames, ordinals or names to stage
96
+ * @param options - Command options
97
+ */
98
+ export async function patchStageCommand(projectRoot, identifiers, options = {}) {
99
+ intro(options.dryRun ? 'FireForge patch stage (dry run)' : 'FireForge patch stage');
100
+ const probe = await exec('git', ['rev-parse', '--is-inside-work-tree'], { cwd: projectRoot });
101
+ if (probe.exitCode !== 0 || probe.stdout.trim() !== 'true') {
102
+ throw new GeneralError('patch stage needs the project to be a git working tree.');
103
+ }
104
+ const { patches: patchesDir } = getProjectPaths(projectRoot);
105
+ const manifestPath = gitPath(projectRoot, join(patchesDir, 'patches.json'));
106
+ // Under the patch lock, so the rows and `.patch` files staged are one
107
+ // consistent snapshot rather than a peer's half-finished write.
108
+ await withPatchDirectoryLock(patchesDir, async () => {
109
+ const working = await loadPatchesManifestForWrite(patchesDir);
110
+ if (working === null) {
111
+ throw new GeneralError(`No ${manifestPath} to stage.`);
112
+ }
113
+ const head = await readHeadManifest(projectRoot, manifestPath);
114
+ const filenames = resolveFilenames(identifiers, working, head);
115
+ let staged;
116
+ try {
117
+ staged = mergeStagedManifest(head, working, filenames);
118
+ }
119
+ catch (error) {
120
+ throw new GeneralError(`Refusing to stage: ${toError(error).message}.`);
121
+ }
122
+ const prefix = options.dryRun === true ? '[dry-run] ' : '';
123
+ for (const { filename, change } of staged.changes) {
124
+ info(`${prefix}${filename}: row ${change}; .patch file staged`);
125
+ }
126
+ if (options.dryRun === true)
127
+ return;
128
+ await stageBlob(projectRoot, manifestPath, serializeStagedManifest(staged.manifest));
129
+ for (const filename of filenames) {
130
+ await stagePatchFile(projectRoot, gitPath(projectRoot, join(patchesDir, filename)));
131
+ }
132
+ info(`Staged ${filenames.size} patch(es) and their ${manifestPath} rows. Other rows stay as at HEAD; ` +
133
+ 'nothing was committed.');
134
+ }, { waitLockSeconds: resolveWaitLockSeconds(options.waitLock), command: 'patch stage' });
135
+ outro(options.dryRun ? 'Dry run complete — nothing staged' : 'Patch stage complete');
136
+ }
137
+ /**
138
+ * Registers the `patch stage` subcommand on the `patch` parent.
139
+ *
140
+ * @param parent - Parent Commander command
141
+ * @param context - Shared CLI registration context
142
+ */
143
+ export function registerPatchStage(parent, context) {
144
+ const { getProjectRoot, withErrorHandling } = context;
145
+ const command = parent
146
+ .command('stage <names...>')
147
+ .description("Stage the named patches' .patch files and only their patches.json rows in the git index " +
148
+ "(HEAD's rows for every other patch); validates the staged manifest, commits nothing")
149
+ .option('--dry-run', 'Show which rows would be staged without touching the index');
150
+ addWaitLockOption(command).action(withErrorHandling(async (names, options) => {
151
+ await patchStageCommand(getProjectRoot(), names, pickDefined(options));
152
+ }));
153
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Batch input and history delta for `patch staged-dependency`.
3
+ *
4
+ * `--from <json-file>` lets one `--add` call declare many edges under one
5
+ * lock hold. Turning on `patchLint.forwardRegistration: "extended"` on a real
6
+ * queue surfaced hundreds of undeclared registrations, each printed with its
7
+ * own command, and one call per edge was the only way to declare them. The
8
+ * file is either a `lint --per-patch --report` JSON (its findings carry the
9
+ * `stagedDeclarations` that discharge them) or a plain array of
10
+ * declarations.
11
+ *
12
+ * The history record of every call holds only the delta (`added` /
13
+ * `removed`). Recording the patch's whole list before and after grew the
14
+ * log quadratically with the list: 849 single-edge calls took one consumer's
15
+ * log from 36 MB to 184 MB.
16
+ */
17
+ import type { PatchStagedDependencies, PatchStagedForwardImport, PatchStagedRegistration } from '../../types/commands/index.js';
18
+ /** Declarations read from a `--from` file for one target patch. */
19
+ export interface StagedDependencyBatch {
20
+ forwardImports: PatchStagedForwardImport[];
21
+ registrations: PatchStagedRegistration[];
22
+ /** Entries naming a different patch, left out. */
23
+ skippedForOtherPatches: number;
24
+ }
25
+ /**
26
+ * Reads the declarations a `--from` file supplies for `targetFilename`.
27
+ *
28
+ * Every entry takes `file`, `creates`, optional `owner` and `reason`, and
29
+ * either `specifier` (an import) or `line` (a registration); `kind` may
30
+ * name the shape explicitly. An entry with a `patch` field naming another
31
+ * patch is skipped and counted, so one report can be applied patch by
32
+ * patch. `defaultReason` (the `--reason` flag) fills entries without one.
33
+ *
34
+ * @param path - The JSON file
35
+ * @param targetFilename - Filename of the patch being edited
36
+ * @param defaultReason - Reason for entries that carry none
37
+ */
38
+ export declare function readStagedDependencyBatch(path: string, targetFilename: string, defaultReason?: string): Promise<StagedDependencyBatch>;
39
+ /**
40
+ * The history delta between two committed `stagedDependencies` values:
41
+ * the declarations the call added and the ones it removed. Each side is
42
+ * omitted when empty. Older log records carry `before`/`after` instead;
43
+ * a reader should accept either.
44
+ */
45
+ export declare function stagedDependencyDelta(before: PatchStagedDependencies | undefined, after: PatchStagedDependencies | undefined): {
46
+ added?: PatchStagedDependencies;
47
+ removed?: PatchStagedDependencies;
48
+ };
@@ -0,0 +1,176 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Batch input and history delta for `patch staged-dependency`.
4
+ *
5
+ * `--from <json-file>` lets one `--add` call declare many edges under one
6
+ * lock hold. Turning on `patchLint.forwardRegistration: "extended"` on a real
7
+ * queue surfaced hundreds of undeclared registrations, each printed with its
8
+ * own command, and one call per edge was the only way to declare them. The
9
+ * file is either a `lint --per-patch --report` JSON (its findings carry the
10
+ * `stagedDeclarations` that discharge them) or a plain array of
11
+ * declarations.
12
+ *
13
+ * The history record of every call holds only the delta (`added` /
14
+ * `removed`). Recording the patch's whole list before and after grew the
15
+ * log quadratically with the list: 849 single-edge calls took one consumer's
16
+ * log from 36 MB to 184 MB.
17
+ */
18
+ import { InvalidArgumentError } from '../../errors/base.js';
19
+ import { toError } from '../../utils/errors.js';
20
+ import { readJson } from '../../utils/fs.js';
21
+ import { isArray, isObject } from '../../utils/validation.js';
22
+ function optionalString(record, key, label) {
23
+ const value = record[key];
24
+ if (value === undefined)
25
+ return undefined;
26
+ if (typeof value !== 'string' || value.length === 0) {
27
+ throw new InvalidArgumentError(`${label}.${key} must be a non-empty string.`, '--from');
28
+ }
29
+ return value;
30
+ }
31
+ function requiredString(record, key, label) {
32
+ const value = optionalString(record, key, label);
33
+ if (value === undefined) {
34
+ throw new InvalidArgumentError(`${label} is missing "${key}".`, '--from');
35
+ }
36
+ return value;
37
+ }
38
+ /**
39
+ * Collects the declaration records a parsed file holds: a lint report's
40
+ * `stagedDeclarations` (from `queueIssues` and every patch's `issues`), or
41
+ * the elements of a plain array.
42
+ */
43
+ function collectRecords(data) {
44
+ if (isArray(data)) {
45
+ return data.map((entry, index) => {
46
+ if (!isObject(entry)) {
47
+ throw new InvalidArgumentError(`--from entry [${index}] must be an object.`, '--from');
48
+ }
49
+ return { record: entry, label: `--from entry [${index}]` };
50
+ });
51
+ }
52
+ if (!isObject(data)) {
53
+ throw new InvalidArgumentError('--from must hold a JSON array of declarations or a "lint --per-patch --report" document.', '--from');
54
+ }
55
+ const issues = [];
56
+ if (isArray(data['queueIssues']))
57
+ issues.push(...data['queueIssues']);
58
+ if (isArray(data['patches'])) {
59
+ for (const patch of data['patches']) {
60
+ if (isObject(patch) && isArray(patch['issues']))
61
+ issues.push(...patch['issues']);
62
+ }
63
+ }
64
+ const records = [];
65
+ for (const issue of issues) {
66
+ if (!isObject(issue) || !isArray(issue['stagedDeclarations']))
67
+ continue;
68
+ for (const declaration of issue['stagedDeclarations']) {
69
+ if (isObject(declaration)) {
70
+ records.push({
71
+ record: declaration,
72
+ label: `stagedDeclarations of [${String(issue['check'])}] ${String(issue['file'])}`,
73
+ });
74
+ }
75
+ }
76
+ }
77
+ return records;
78
+ }
79
+ /**
80
+ * Reads the declarations a `--from` file supplies for `targetFilename`.
81
+ *
82
+ * Every entry takes `file`, `creates`, optional `owner` and `reason`, and
83
+ * either `specifier` (an import) or `line` (a registration); `kind` may
84
+ * name the shape explicitly. An entry with a `patch` field naming another
85
+ * patch is skipped and counted, so one report can be applied patch by
86
+ * patch. `defaultReason` (the `--reason` flag) fills entries without one.
87
+ *
88
+ * @param path - The JSON file
89
+ * @param targetFilename - Filename of the patch being edited
90
+ * @param defaultReason - Reason for entries that carry none
91
+ */
92
+ export async function readStagedDependencyBatch(path, targetFilename, defaultReason) {
93
+ let data;
94
+ try {
95
+ data = await readJson(path);
96
+ }
97
+ catch (error) {
98
+ throw new InvalidArgumentError(`--from ${path} could not be read as JSON: ${toError(error).message}`, '--from');
99
+ }
100
+ const batch = {
101
+ forwardImports: [],
102
+ registrations: [],
103
+ skippedForOtherPatches: 0,
104
+ };
105
+ for (const { record, label } of collectRecords(data)) {
106
+ const patch = optionalString(record, 'patch', label);
107
+ if (patch !== undefined && patch !== targetFilename) {
108
+ batch.skippedForOtherPatches += 1;
109
+ continue;
110
+ }
111
+ const specifier = optionalString(record, 'specifier', label);
112
+ const line = optionalString(record, 'line', label);
113
+ const kind = record['kind'] ?? (line !== undefined ? 'registration' : 'import');
114
+ if (kind !== 'import' && kind !== 'registration') {
115
+ throw new InvalidArgumentError(`${label}.kind must be "import" or "registration".`, '--from');
116
+ }
117
+ const file = requiredString(record, 'file', label);
118
+ const creates = requiredString(record, 'creates', label);
119
+ const owner = optionalString(record, 'owner', label);
120
+ const reason = optionalString(record, 'reason', label) ?? defaultReason;
121
+ const extras = {
122
+ ...(owner !== undefined ? { owner } : {}),
123
+ ...(reason !== undefined ? { reason } : {}),
124
+ };
125
+ if (kind === 'import') {
126
+ if (specifier === undefined || line !== undefined) {
127
+ throw new InvalidArgumentError(`${label} is an import declaration and needs "specifier" (not "line").`, '--from');
128
+ }
129
+ batch.forwardImports.push({ file, specifier, creates, ...extras });
130
+ }
131
+ else {
132
+ if (line === undefined || specifier !== undefined) {
133
+ throw new InvalidArgumentError(`${label} is a registration declaration and needs "line" (not "specifier").`, '--from');
134
+ }
135
+ batch.registrations.push({ file, line, creates, ...extras });
136
+ }
137
+ }
138
+ return batch;
139
+ }
140
+ function entryKey(entry) {
141
+ return JSON.stringify(entry);
142
+ }
143
+ function difference(left, right) {
144
+ const keys = new Set((right ?? []).map(entryKey));
145
+ return (left ?? []).filter((entry) => !keys.has(entryKey(entry)));
146
+ }
147
+ function nonEmpty(staged) {
148
+ const forwardImports = staged.forwardImports ?? [];
149
+ const registrations = staged.registrations ?? [];
150
+ if (forwardImports.length === 0 && registrations.length === 0)
151
+ return undefined;
152
+ return {
153
+ ...(forwardImports.length > 0 ? { forwardImports } : {}),
154
+ ...(registrations.length > 0 ? { registrations } : {}),
155
+ };
156
+ }
157
+ /**
158
+ * The history delta between two committed `stagedDependencies` values:
159
+ * the declarations the call added and the ones it removed. Each side is
160
+ * omitted when empty. Older log records carry `before`/`after` instead;
161
+ * a reader should accept either.
162
+ */
163
+ export function stagedDependencyDelta(before, after) {
164
+ const added = nonEmpty({
165
+ forwardImports: difference(after?.forwardImports, before?.forwardImports),
166
+ registrations: difference(after?.registrations, before?.registrations),
167
+ });
168
+ const removed = nonEmpty({
169
+ forwardImports: difference(before?.forwardImports, after?.forwardImports),
170
+ registrations: difference(before?.registrations, after?.registrations),
171
+ });
172
+ return {
173
+ ...(added !== undefined ? { added } : {}),
174
+ ...(removed !== undefined ? { removed } : {}),
175
+ };
176
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Kind-neutral views and list operations over staged-dependency
3
+ * declarations, shared by `patch staged-dependency` and its `--from` batch
4
+ * path. Split out of `staged-dependency.ts` for the per-file line budget.
5
+ */
6
+ import type { PatchStagedDependencies, PatchStagedForwardImport, PatchStagedRegistration } from '../../types/commands/index.js';
7
+ /** The command's mutation mode. */
8
+ export type StagedDependencyMode = 'add' | 'remove' | 'clear';
9
+ /** The declaration shape (`--kind`). */
10
+ export type StagedDependencyKind = 'import' | 'registration';
11
+ /**
12
+ * One staged-dependency entry of either kind. The `matcher` field is the
13
+ * kind-specific middle term: the import specifier for import entries, the
14
+ * registration line for registration entries. Key/removal/label logic is
15
+ * shared across kinds through this shape.
16
+ */
17
+ export interface StagedEntryView {
18
+ file: string;
19
+ matcher: string;
20
+ creates: string;
21
+ owner?: string;
22
+ reason?: string;
23
+ }
24
+ /** View of an import declaration. */
25
+ export declare function importView(dependency: PatchStagedForwardImport): StagedEntryView;
26
+ /** View of a registration declaration. */
27
+ export declare function registrationView(dependency: PatchStagedRegistration): StagedEntryView;
28
+ /** One-line operator label for a declaration. */
29
+ export declare function dependencyLabel(kind: StagedDependencyKind, view: StagedEntryView): string;
30
+ /**
31
+ * Applies one add/remove/clear to a declaration list: `add` appends unless
32
+ * an entry with the same file, matcher, creates and owner exists.
33
+ */
34
+ export declare function applyMode<T>(existing: readonly T[], mode: StagedDependencyMode, target: StagedEntryView | undefined, toView: (entry: T) => StagedEntryView, addition: T | undefined): T[];
35
+ /** Number of declarations of both kinds. */
36
+ export declare function countStagedEntries(staged: PatchStagedDependencies | undefined): number;
@@ -0,0 +1,52 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Kind-neutral views and list operations over staged-dependency
4
+ * declarations, shared by `patch staged-dependency` and its `--from` batch
5
+ * path. Split out of `staged-dependency.ts` for the per-file line budget.
6
+ */
7
+ /** View of an import declaration. */
8
+ export function importView(dependency) {
9
+ return { ...dependency, matcher: dependency.specifier };
10
+ }
11
+ /** View of a registration declaration. */
12
+ export function registrationView(dependency) {
13
+ return { ...dependency, matcher: dependency.line };
14
+ }
15
+ function dependencyKey(view) {
16
+ return [view.file, view.matcher, view.creates, view.owner ?? ''].join('\0');
17
+ }
18
+ /** One-line operator label for a declaration. */
19
+ export function dependencyLabel(kind, view) {
20
+ const owner = view.owner ? ` owner=${view.owner}` : '';
21
+ const reason = view.reason ? ` reason="${view.reason}"` : '';
22
+ if (kind === 'import') {
23
+ return `${view.file} imports "${view.matcher}" from ${view.creates}${owner}${reason}`;
24
+ }
25
+ return `${view.file} registers "${view.matcher}" for ${view.creates}${owner}${reason}`;
26
+ }
27
+ function matchesTarget(view, target) {
28
+ return (view.file === target.file &&
29
+ view.matcher === target.matcher &&
30
+ view.creates === target.creates &&
31
+ (target.owner === undefined || view.owner === target.owner));
32
+ }
33
+ /**
34
+ * Applies one add/remove/clear to a declaration list: `add` appends unless
35
+ * an entry with the same file, matcher, creates and owner exists.
36
+ */
37
+ export function applyMode(existing, mode, target, toView, addition) {
38
+ if (mode === 'clear')
39
+ return [];
40
+ if (!target)
41
+ return [...existing];
42
+ if (mode === 'remove')
43
+ return existing.filter((entry) => !matchesTarget(toView(entry), target));
44
+ const seen = new Set(existing.map((entry) => dependencyKey(toView(entry))));
45
+ if (seen.has(dependencyKey(target)) || addition === undefined)
46
+ return [...existing];
47
+ return [...existing, addition];
48
+ }
49
+ /** Number of declarations of both kinds. */
50
+ export function countStagedEntries(staged) {
51
+ return (staged?.forwardImports?.length ?? 0) + (staged?.registrations?.length ?? 0);
52
+ }