@hominis/fireforge 0.44.4 → 0.46.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 (378) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/dist/bin/fireforge.js +6 -0
  3. package/dist/build-info.json +4 -4
  4. package/dist/src/cli-usage-error.d.ts +46 -0
  5. package/dist/src/cli-usage-error.js +81 -0
  6. package/dist/src/cli.d.ts +5 -1
  7. package/dist/src/cli.js +20 -21
  8. package/dist/src/commands/bootstrap.js +1 -2
  9. package/dist/src/commands/build.js +15 -6
  10. package/dist/src/commands/destructive-decision.d.ts +19 -0
  11. package/dist/src/commands/destructive-decision.js +31 -0
  12. package/dist/src/commands/doctor-check-core.d.ts +0 -6
  13. package/dist/src/commands/doctor-check-core.js +0 -8
  14. package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
  15. package/dist/src/commands/doctor-orphaned-harness.js +4 -12
  16. package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
  17. package/dist/src/commands/doctor-source-pin.d.ts +29 -0
  18. package/dist/src/commands/doctor-source-pin.js +81 -0
  19. package/dist/src/commands/doctor.d.ts +2 -0
  20. package/dist/src/commands/doctor.js +9 -8
  21. package/dist/src/commands/download.js +35 -24
  22. package/dist/src/commands/export-all.js +21 -4
  23. package/dist/src/commands/export-flow.js +15 -60
  24. package/dist/src/commands/export-placement-conflicts.js +3 -3
  25. package/dist/src/commands/export-placement-gate.js +2 -8
  26. package/dist/src/commands/export-placement-policy.js +3 -5
  27. package/dist/src/commands/export-shared.d.ts +42 -23
  28. package/dist/src/commands/export-shared.js +4 -19
  29. package/dist/src/commands/export.js +10 -1
  30. package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
  31. package/dist/src/commands/furnace/authoring-config.js +19 -0
  32. package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
  33. package/dist/src/commands/furnace/create-browser-test.js +3 -8
  34. package/dist/src/commands/furnace/create-dry-run.js +2 -5
  35. package/dist/src/commands/furnace/create-templates.d.ts +17 -1
  36. package/dist/src/commands/furnace/create-templates.js +2 -1
  37. package/dist/src/commands/furnace/create.js +25 -19
  38. package/dist/src/commands/furnace/deploy.js +11 -2
  39. package/dist/src/commands/furnace/init.js +2 -1
  40. package/dist/src/commands/furnace/override.js +28 -19
  41. package/dist/src/commands/furnace/refresh.js +9 -1
  42. package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
  43. package/dist/src/commands/furnace/remove-mutations.js +0 -1
  44. package/dist/src/commands/furnace/remove.js +2 -6
  45. package/dist/src/commands/furnace/rename-browser-test.js +1 -7
  46. package/dist/src/commands/furnace/rename.js +29 -29
  47. package/dist/src/commands/furnace/status.js +12 -9
  48. package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
  49. package/dist/src/commands/furnace/test-file-name.js +39 -0
  50. package/dist/src/commands/furnace/validation-output.d.ts +21 -9
  51. package/dist/src/commands/furnace/validation-output.js +3 -8
  52. package/dist/src/commands/import.js +2 -9
  53. package/dist/src/commands/lint-per-patch.d.ts +2 -2
  54. package/dist/src/commands/lint-per-patch.js +19 -4
  55. package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
  56. package/dist/src/commands/lint.d.ts +2 -1
  57. package/dist/src/commands/lint.js +1 -1
  58. package/dist/src/commands/manifest.js +1 -1
  59. package/dist/src/commands/package.js +2 -4
  60. package/dist/src/commands/patch/compact.js +14 -15
  61. package/dist/src/commands/patch/delete.js +15 -25
  62. package/dist/src/commands/patch/entry-projection.d.ts +13 -0
  63. package/dist/src/commands/patch/entry-projection.js +24 -0
  64. package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
  65. package/dist/src/commands/patch/lint-ignore.js +19 -29
  66. package/dist/src/commands/patch/move-files-create.js +24 -10
  67. package/dist/src/commands/patch/move-files-into.js +38 -37
  68. package/dist/src/commands/patch/move-files.js +4 -13
  69. package/dist/src/commands/patch/rename.js +27 -41
  70. package/dist/src/commands/patch/reorder.js +25 -38
  71. package/dist/src/commands/patch/split-plan.d.ts +4 -5
  72. package/dist/src/commands/patch/split-plan.js +4 -11
  73. package/dist/src/commands/patch/split.js +45 -43
  74. package/dist/src/commands/patch/staged-dependency.js +16 -22
  75. package/dist/src/commands/patch/tier.js +15 -25
  76. package/dist/src/commands/patch-rename-map.d.ts +19 -0
  77. package/dist/src/commands/patch-rename-map.js +36 -0
  78. package/dist/src/commands/pending-resolution.d.ts +15 -0
  79. package/dist/src/commands/pending-resolution.js +25 -0
  80. package/dist/src/commands/re-export-adjacent.d.ts +32 -2
  81. package/dist/src/commands/re-export-adjacent.js +51 -10
  82. package/dist/src/commands/re-export-bulk-scan.js +6 -10
  83. package/dist/src/commands/re-export-drift.d.ts +8 -0
  84. package/dist/src/commands/re-export-drift.js +65 -4
  85. package/dist/src/commands/re-export-files.d.ts +2 -3
  86. package/dist/src/commands/re-export-files.js +38 -32
  87. package/dist/src/commands/re-export-lint.d.ts +3 -4
  88. package/dist/src/commands/re-export-lint.js +9 -2
  89. package/dist/src/commands/re-export-options.js +19 -5
  90. package/dist/src/commands/re-export-register.js +5 -1
  91. package/dist/src/commands/re-export-scan.js +8 -8
  92. package/dist/src/commands/re-export-single.d.ts +22 -3
  93. package/dist/src/commands/re-export-single.js +19 -11
  94. package/dist/src/commands/re-export.js +44 -27
  95. package/dist/src/commands/rebase/abort.js +3 -8
  96. package/dist/src/commands/rebase/continue.js +12 -12
  97. package/dist/src/commands/rebase/index.js +2 -2
  98. package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
  99. package/dist/src/commands/rebase/patch-loop.js +2 -7
  100. package/dist/src/commands/resolve.js +18 -10
  101. package/dist/src/commands/run.d.ts +0 -13
  102. package/dist/src/commands/run.js +12 -25
  103. package/dist/src/commands/setup-support.d.ts +16 -4
  104. package/dist/src/commands/setup-support.js +16 -16
  105. package/dist/src/commands/setup.js +3 -9
  106. package/dist/src/commands/source.js +8 -1
  107. package/dist/src/commands/status-check.js +2 -2
  108. package/dist/src/commands/status-output.js +2 -2
  109. package/dist/src/commands/status.js +3 -4
  110. package/dist/src/commands/test-build-phase.d.ts +2 -2
  111. package/dist/src/commands/test-build-phase.js +33 -8
  112. package/dist/src/commands/test-diagnose.js +14 -5
  113. package/dist/src/commands/test-modes.d.ts +2 -2
  114. package/dist/src/commands/test-modes.js +0 -1
  115. package/dist/src/commands/test-register.js +6 -3
  116. package/dist/src/commands/test-run.js +29 -21
  117. package/dist/src/commands/test-stale-gate.js +8 -5
  118. package/dist/src/commands/test-verdict.d.ts +16 -2
  119. package/dist/src/commands/test-verdict.js +18 -3
  120. package/dist/src/commands/test.js +51 -7
  121. package/dist/src/commands/token-list.d.ts +26 -0
  122. package/dist/src/commands/token-list.js +153 -0
  123. package/dist/src/commands/token.js +17 -1
  124. package/dist/src/commands/tree.js +7 -2
  125. package/dist/src/commands/typecheck.js +9 -9
  126. package/dist/src/commands/wire.js +4 -3
  127. package/dist/src/core/branding.d.ts +0 -7
  128. package/dist/src/core/branding.js +3 -2
  129. package/dist/src/core/browser-wire.js +6 -3
  130. package/dist/src/core/build-audit-platform.d.ts +29 -0
  131. package/dist/src/core/build-audit-platform.js +140 -0
  132. package/dist/src/core/build-audit-registration.js +3 -2
  133. package/dist/src/core/build-audit-skip.d.ts +57 -0
  134. package/dist/src/core/build-audit-skip.js +112 -0
  135. package/dist/src/core/build-audit.d.ts +15 -2
  136. package/dist/src/core/build-audit.js +154 -12
  137. package/dist/src/core/build-baseline.d.ts +45 -28
  138. package/dist/src/core/build-baseline.js +6 -29
  139. package/dist/src/core/build-jar-escalation.d.ts +64 -0
  140. package/dist/src/core/build-jar-escalation.js +128 -0
  141. package/dist/src/core/build-overwrite-guard.js +1 -1
  142. package/dist/src/core/build-prepare.d.ts +5 -2
  143. package/dist/src/core/build-prepare.js +34 -6
  144. package/dist/src/core/config-paths.d.ts +2 -6
  145. package/dist/src/core/config-paths.js +4 -2
  146. package/dist/src/core/config-state.d.ts +0 -6
  147. package/dist/src/core/config-state.js +1 -19
  148. package/dist/src/core/config-validate-build-audit.d.ts +8 -0
  149. package/dist/src/core/config-validate-build-audit.js +80 -0
  150. package/dist/src/core/config-validate-file-size.d.ts +12 -0
  151. package/dist/src/core/config-validate-file-size.js +65 -0
  152. package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
  153. package/dist/src/core/config-validate-patch-policy.js +3 -3
  154. package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
  155. package/dist/src/core/config-validate-test-toolchains.js +4 -5
  156. package/dist/src/core/config-validate.js +20 -3
  157. package/dist/src/core/config.d.ts +2 -2
  158. package/dist/src/core/config.js +11 -15
  159. package/dist/src/core/coverage-extend.js +4 -4
  160. package/dist/src/core/discard-baseline.js +7 -7
  161. package/dist/src/core/engine-precondition.d.ts +1 -9
  162. package/dist/src/core/engine-precondition.js +3 -8
  163. package/dist/src/core/engine-session-lock.js +13 -17
  164. package/dist/src/core/export-moved-code-shape.js +2 -2
  165. package/dist/src/core/file-lock-owner.d.ts +103 -0
  166. package/dist/src/core/file-lock-owner.js +259 -0
  167. package/dist/src/core/file-lock.d.ts +2 -7
  168. package/dist/src/core/file-lock.js +40 -91
  169. package/dist/src/core/firefox-cache.d.ts +13 -1
  170. package/dist/src/core/firefox-cache.js +59 -34
  171. package/dist/src/core/firefox-download.d.ts +15 -0
  172. package/dist/src/core/firefox-download.js +37 -21
  173. package/dist/src/core/firefox-extract.d.ts +6 -5
  174. package/dist/src/core/firefox-extract.js +36 -18
  175. package/dist/src/core/firefox.d.ts +30 -32
  176. package/dist/src/core/firefox.js +5 -36
  177. package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
  178. package/dist/src/core/furnace-apply-dry-run.js +1 -7
  179. package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
  180. package/dist/src/core/furnace-apply-helpers.js +8 -19
  181. package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
  182. package/dist/src/core/furnace-apply.d.ts +1 -1
  183. package/dist/src/core/furnace-apply.js +30 -10
  184. package/dist/src/core/furnace-config.d.ts +1 -1
  185. package/dist/src/core/furnace-config.js +23 -36
  186. package/dist/src/core/furnace-constants.js +3 -2
  187. package/dist/src/core/furnace-dir-entry.d.ts +20 -0
  188. package/dist/src/core/furnace-dir-entry.js +21 -0
  189. package/dist/src/core/furnace-operation.d.ts +2 -0
  190. package/dist/src/core/furnace-operation.js +3 -1
  191. package/dist/src/core/furnace-refresh.d.ts +18 -6
  192. package/dist/src/core/furnace-refresh.js +3 -6
  193. package/dist/src/core/furnace-registration-ast.d.ts +0 -1
  194. package/dist/src/core/furnace-registration-ast.js +0 -3
  195. package/dist/src/core/furnace-registration.d.ts +2 -1
  196. package/dist/src/core/furnace-registration.js +22 -29
  197. package/dist/src/core/furnace-scanner.js +3 -6
  198. package/dist/src/core/furnace-state-persist.d.ts +2 -2
  199. package/dist/src/core/furnace-validate-registration.js +5 -7
  200. package/dist/src/core/git-base.d.ts +3 -1
  201. package/dist/src/core/git-base.js +25 -3
  202. package/dist/src/core/git-diff-new-file.d.ts +40 -0
  203. package/dist/src/core/git-diff-new-file.js +119 -0
  204. package/dist/src/core/git-diff.d.ts +9 -9
  205. package/dist/src/core/git-diff.js +88 -157
  206. package/dist/src/core/git-file-ops.d.ts +0 -7
  207. package/dist/src/core/git-file-ops.js +4 -28
  208. package/dist/src/core/git-readonly-index.d.ts +0 -2
  209. package/dist/src/core/git-readonly-index.js +0 -4
  210. package/dist/src/core/git-status.d.ts +7 -0
  211. package/dist/src/core/git-status.js +21 -9
  212. package/dist/src/core/git.d.ts +0 -6
  213. package/dist/src/core/git.js +14 -43
  214. package/dist/src/core/harness-orphans.d.ts +73 -0
  215. package/dist/src/core/harness-orphans.js +187 -0
  216. package/dist/src/core/history-log.d.ts +19 -0
  217. package/dist/src/core/history-log.js +29 -0
  218. package/dist/src/core/mach-error-hints.d.ts +0 -5
  219. package/dist/src/core/mach-error-hints.js +1 -1
  220. package/dist/src/core/mach-known-noise-filter.d.ts +25 -3
  221. package/dist/src/core/mach-known-noise-filter.js +31 -10
  222. package/dist/src/core/mach-mozconfig.js +2 -1
  223. package/dist/src/core/mach-python.d.ts +2 -0
  224. package/dist/src/core/mach-python.js +12 -7
  225. package/dist/src/core/mach-resource-shim.d.ts +13 -1
  226. package/dist/src/core/mach-resource-shim.js +112 -9
  227. package/dist/src/core/mach.d.ts +46 -53
  228. package/dist/src/core/mach.js +31 -83
  229. package/dist/src/core/marionette-port.d.ts +0 -17
  230. package/dist/src/core/marionette-port.js +12 -58
  231. package/dist/src/core/marionette-preflight.js +9 -27
  232. package/dist/src/core/mochitest-server-port.js +3 -3
  233. package/dist/src/core/moz-manifest-rules.js +37 -33
  234. package/dist/src/core/patch-apply-fuzz.js +4 -4
  235. package/dist/src/core/patch-apply.d.ts +4 -5
  236. package/dist/src/core/patch-apply.js +55 -32
  237. package/dist/src/core/patch-export-coverage.d.ts +0 -9
  238. package/dist/src/core/patch-export-coverage.js +1 -30
  239. package/dist/src/core/patch-export-update.d.ts +18 -1
  240. package/dist/src/core/patch-export-update.js +4 -3
  241. package/dist/src/core/patch-export.d.ts +11 -21
  242. package/dist/src/core/patch-export.js +44 -66
  243. package/dist/src/core/patch-files.d.ts +0 -4
  244. package/dist/src/core/patch-files.js +0 -11
  245. package/dist/src/core/patch-lint-binary.d.ts +6 -12
  246. package/dist/src/core/patch-lint-cache.d.ts +17 -1
  247. package/dist/src/core/patch-lint-cache.js +3 -6
  248. package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
  249. package/dist/src/core/patch-lint-checkjs.js +43 -25
  250. package/dist/src/core/patch-lint-creators.d.ts +2 -6
  251. package/dist/src/core/patch-lint-cross.d.ts +16 -8
  252. package/dist/src/core/patch-lint-cross.js +49 -92
  253. package/dist/src/core/patch-lint-css.js +10 -6
  254. package/dist/src/core/patch-lint-file-size.d.ts +52 -0
  255. package/dist/src/core/patch-lint-file-size.js +53 -0
  256. package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
  257. package/dist/src/core/patch-lint-forward-registration.js +210 -0
  258. package/dist/src/core/patch-lint-jsdoc.js +36 -36
  259. package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
  260. package/dist/src/core/patch-lint-prettier.d.ts +36 -0
  261. package/dist/src/core/patch-lint-prettier.js +126 -0
  262. package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
  263. package/dist/src/core/patch-lint-queue-types.js +12 -0
  264. package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
  265. package/dist/src/core/patch-lint-staged-registration.js +90 -0
  266. package/dist/src/core/patch-lint-unmanaged-companion.d.ts +106 -0
  267. package/dist/src/core/patch-lint-unmanaged-companion.js +214 -0
  268. package/dist/src/core/patch-lint.d.ts +14 -9
  269. package/dist/src/core/patch-lint.js +27 -27
  270. package/dist/src/core/patch-lock.js +3 -3
  271. package/dist/src/core/patch-manifest-io.js +8 -7
  272. package/dist/src/core/patch-parse.d.ts +19 -8
  273. package/dist/src/core/patch-parse.js +25 -0
  274. package/dist/src/core/patch-policy.js +15 -27
  275. package/dist/src/core/patch-source-metadata.d.ts +1 -3
  276. package/dist/src/core/patch-source-metadata.js +0 -4
  277. package/dist/src/core/patch-transform.d.ts +21 -8
  278. package/dist/src/core/patch-transform.js +38 -10
  279. package/dist/src/core/process-cpu-time.js +1 -1
  280. package/dist/src/core/rebase-session.d.ts +2 -8
  281. package/dist/src/core/rebase-session.js +5 -17
  282. package/dist/src/core/register-browser-content.js +3 -2
  283. package/dist/src/core/register-browser-test.js +2 -1
  284. package/dist/src/core/register-module.js +2 -1
  285. package/dist/src/core/register-scaffold.js +5 -4
  286. package/dist/src/core/register-shared-css.js +2 -1
  287. package/dist/src/core/run-log-redact.d.ts +38 -0
  288. package/dist/src/core/run-log-redact.js +69 -0
  289. package/dist/src/core/run-log.d.ts +13 -2
  290. package/dist/src/core/run-log.js +67 -18
  291. package/dist/src/core/signal-critical.js +2 -4
  292. package/dist/src/core/smoke-patterns.d.ts +0 -1
  293. package/dist/src/core/smoke-patterns.js +18 -11
  294. package/dist/src/core/state-file.d.ts +12 -1
  295. package/dist/src/core/state-file.js +14 -1
  296. package/dist/src/core/status-classify.js +2 -1
  297. package/dist/src/core/test-changed-prefs.js +26 -11
  298. package/dist/src/core/test-harness-crash.js +61 -48
  299. package/dist/src/core/test-harness-verdict-notes.d.ts +55 -0
  300. package/dist/src/core/test-harness-verdict-notes.js +48 -0
  301. package/dist/src/core/test-stale-check.d.ts +23 -8
  302. package/dist/src/core/test-stale-check.js +77 -4
  303. package/dist/src/core/test-stall-triage.js +37 -19
  304. package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
  305. package/dist/src/core/test-xpcshell-retry.js +14 -11
  306. package/dist/src/core/token-category.d.ts +33 -0
  307. package/dist/src/core/token-category.js +56 -48
  308. package/dist/src/core/token-dark-mode.js +2 -14
  309. package/dist/src/core/token-inventory.d.ts +56 -0
  310. package/dist/src/core/token-inventory.js +109 -0
  311. package/dist/src/core/token-manager.d.ts +1 -1
  312. package/dist/src/core/token-manager.js +1 -1
  313. package/dist/src/core/token-variant.js +21 -22
  314. package/dist/src/core/toolchain-preflight.js +1 -1
  315. package/dist/src/core/tree-cow.d.ts +0 -2
  316. package/dist/src/core/tree-cow.js +2 -2
  317. package/dist/src/core/tree-store.js +4 -5
  318. package/dist/src/core/typecheck.js +2 -2
  319. package/dist/src/core/wire-dom-fragment.js +6 -17
  320. package/dist/src/core/wire-utils.js +5 -17
  321. package/dist/src/core/xpcshell-appdir.js +2 -4
  322. package/dist/src/errors/base.d.ts +53 -1
  323. package/dist/src/errors/base.js +79 -11
  324. package/dist/src/errors/build.d.ts +28 -0
  325. package/dist/src/errors/build.js +51 -10
  326. package/dist/src/errors/config.d.ts +0 -14
  327. package/dist/src/errors/config.js +13 -34
  328. package/dist/src/errors/download.d.ts +13 -0
  329. package/dist/src/errors/download.js +53 -24
  330. package/dist/src/errors/furnace.js +8 -9
  331. package/dist/src/errors/git.d.ts +8 -8
  332. package/dist/src/errors/git.js +20 -30
  333. package/dist/src/errors/patch.js +8 -10
  334. package/dist/src/errors/rebase.js +7 -7
  335. package/dist/src/types/commands/options.d.ts +96 -123
  336. package/dist/src/types/commands/patches.d.ts +17 -6
  337. package/dist/src/types/config.d.ts +75 -2
  338. package/dist/src/types/index.d.ts +0 -1
  339. package/dist/src/utils/assert.d.ts +3 -15
  340. package/dist/src/utils/assert.js +1 -18
  341. package/dist/src/utils/build-info.d.ts +1 -1
  342. package/dist/src/utils/child-env.d.ts +2 -1
  343. package/dist/src/utils/elapsed.js +10 -1
  344. package/dist/src/utils/fs.d.ts +4 -2
  345. package/dist/src/utils/fs.js +11 -14
  346. package/dist/src/utils/hash.d.ts +12 -0
  347. package/dist/src/utils/hash.js +16 -0
  348. package/dist/src/utils/line-dispatch.d.ts +15 -0
  349. package/dist/src/utils/line-dispatch.js +48 -0
  350. package/dist/src/utils/options.d.ts +1 -7
  351. package/dist/src/utils/options.js +6 -7
  352. package/dist/src/utils/package-root.js +3 -12
  353. package/dist/src/utils/platform.d.ts +0 -4
  354. package/dist/src/utils/process-group.d.ts +2 -1
  355. package/dist/src/utils/process-group.js +20 -19
  356. package/dist/src/utils/process.d.ts +40 -26
  357. package/dist/src/utils/process.js +115 -166
  358. package/dist/src/utils/ps-duration.js +3 -3
  359. package/dist/src/utils/sleep.d.ts +26 -0
  360. package/dist/src/utils/sleep.js +31 -0
  361. package/dist/src/utils/validation.d.ts +2 -38
  362. package/dist/src/utils/validation.js +7 -16
  363. package/docs/configuration.md +136 -1
  364. package/docs/exit-codes.md +34 -20
  365. package/docs/lifecycle-invariants.md +18 -12
  366. package/docs/machine-output.md +31 -8
  367. package/docs/patch-workflow.md +2 -1
  368. package/docs/run-logs.md +44 -4
  369. package/docs/testing.md +70 -11
  370. package/package.json +8 -7
  371. package/dist/src/commands/rebase.d.ts +0 -4
  372. package/dist/src/commands/rebase.js +0 -5
  373. package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
  374. package/dist/src/core/patch-artifact-normalize.js +0 -24
  375. package/dist/src/core/patch-lint-reexports.d.ts +0 -7
  376. package/dist/src/core/patch-lint-reexports.js +0 -8
  377. package/dist/src/core/wire-targets.d.ts +0 -7
  378. package/dist/src/core/wire-targets.js +0 -8
@@ -18,18 +18,7 @@ import { escapeRegex } from '../../utils/regex.js';
18
18
  import { updateBrowserChromeTestContent } from './rename-browser-test.js';
19
19
  import { rekeyStateChecksums, renameComponentFileName, updateConfigForCustomRename, updateConfigForOverrideRename, } from './rename-helpers.js';
20
20
  import { renameXpcshellTestFiles } from './rename-xpcshell.js';
21
- /**
22
- * Derives the test file name for a component, matching the convention used by
23
- * `furnace create --with-tests`.
24
- */
25
- function deriveTestFileName(componentName, binaryName) {
26
- const strippedName = componentName.startsWith('moz-') ? componentName.slice(4) : componentName;
27
- const withoutBinaryPrefix = strippedName.startsWith(binaryName + '-')
28
- ? strippedName.slice(binaryName.length + 1)
29
- : strippedName;
30
- const underscored = withoutBinaryPrefix.replace(/-/g, '_');
31
- return `browser_${binaryName}_${underscored}.js`;
32
- }
21
+ import { browserTestFileName } from './test-file-name.js';
33
22
  /**
34
23
  * Renames test files created by `furnace create --with-tests` in the engine
35
24
  * test directory. Best-effort: failures are logged as warnings but do not
@@ -44,8 +33,8 @@ async function renameTestFiles(engineDir, projectRoot, oldName, newName, journal
44
33
  return; // Cannot determine test paths without config.
45
34
  }
46
35
  const binaryName = forgeConfig.binaryName;
47
- const oldTestFileName = deriveTestFileName(oldName, binaryName);
48
- const newTestFileName = deriveTestFileName(newName, binaryName);
36
+ const oldTestFileName = browserTestFileName(oldName, binaryName);
37
+ const newTestFileName = browserTestFileName(newName, binaryName);
49
38
  const testDir = join(engineDir, 'browser/base/content/test', binaryName);
50
39
  if (!(await pathExists(testDir)))
51
40
  return;
@@ -259,7 +248,15 @@ async function performRenameMutations(args) {
259
248
  if (isCustom && config.custom[newName]?.register && (await pathExists(args.engineDir))) {
260
249
  const ftlDir = resolveFtlDir(config.ftlBasePath);
261
250
  const isLocalized = config.custom[newName].localized;
262
- await updateEngineRegistrations(args.engineDir, oldName, newName, newDir, ftlDir, isLocalized, journal);
251
+ await updateEngineRegistrations({
252
+ engineDir: args.engineDir,
253
+ oldName,
254
+ newName,
255
+ newDir,
256
+ ftlDir,
257
+ isLocalized,
258
+ journal,
259
+ });
263
260
  }
264
261
  // 4. Re-key furnace-state.json checksums from old name to new name
265
262
  await rekeyStateChecksums(args.projectRoot, componentType, oldName, newName);
@@ -323,7 +320,14 @@ async function performRenameMutations(args) {
323
320
  }
324
321
  });
325
322
  }
326
- async function updateEngineRegistrations(engineDir, oldName, newName, newDir, ftlDir, isLocalized, journal) {
323
+ /**
324
+ * Re-points the engine-side registrations (customElements.js, jar.mn, and
325
+ * the localized `.ftl` entries) from the old tag name to the new one.
326
+ *
327
+ * @param options - See {@link UpdateEngineRegistrationsOptions}
328
+ */
329
+ async function updateEngineRegistrations(options) {
330
+ const { engineDir, oldName, newName, newDir, ftlDir, isLocalized, journal } = options;
327
331
  const customElementsPath = join(engineDir, 'toolkit/content/customElements.js');
328
332
  const jarMnPath = join(engineDir, 'toolkit/content/jar.mn');
329
333
  if (await pathExists(customElementsPath)) {
@@ -396,15 +400,15 @@ export async function furnaceRenameCommand(projectRoot, oldName, newName) {
396
400
  if (newName in config.custom || newName in config.overrides || config.stock.includes(newName)) {
397
401
  throw new FurnaceError(`A component named "${newName}" already exists in furnace.json.`, newName);
398
402
  }
399
- const componentType = isCustom ? 'custom' : 'override';
400
- // `componentType` is the furnace-state key (singular: `custom` /
401
- // `override`); the on-disk directory label differs — custom components
402
- // live under `components/custom/` (singular) while overrides live under
403
- // `components/overrides/` (plural). Appending an `s` to `componentType`
404
- // produces the wrong label `components/customs/` for custom components
405
- // and is correct for overrides only by coincidence. `componentDirLabel`
406
- // centralises the pick so every operator-facing string names the
407
- // directory that actually exists on disk.
403
+ // The furnace-state key is singular (`custom` / `override`, derived
404
+ // inside performRenameMutations); the on-disk directory label differs —
405
+ // custom components live under `components/custom/` (singular) while
406
+ // overrides live under `components/overrides/` (plural). Appending an
407
+ // `s` to the state key produces the wrong label `components/customs/`
408
+ // for custom components and is correct for overrides only by
409
+ // coincidence. `componentDirLabel` centralises the pick so every
410
+ // operator-facing string names the directory that actually exists on
411
+ // disk.
408
412
  const componentDirLabel = isCustom ? 'custom' : 'overrides';
409
413
  const baseDir = isCustom ? furnacePaths.customDir : furnacePaths.overridesDir;
410
414
  const oldDir = join(baseDir, oldName);
@@ -419,11 +423,7 @@ export async function furnaceRenameCommand(projectRoot, oldName, newName) {
419
423
  projectRoot,
420
424
  oldName,
421
425
  newName,
422
- oldDir,
423
426
  newDir,
424
- isCustom,
425
- componentType,
426
- config,
427
427
  furnaceConfigPath: furnacePaths.furnaceConfig,
428
428
  furnacePaths,
429
429
  engineDir: paths.engine,
@@ -11,15 +11,10 @@ import { pathExists } from '../../utils/fs.js';
11
11
  import { info, intro, note, outro, warn } from '../../utils/logger.js';
12
12
  /**
13
13
  * Displays detailed status for a single Furnace component, including registration drift.
14
- * @param name - Component tag name to inspect
15
- * @param config - Loaded Furnace configuration
16
- * @param state - Loaded furnace state, for applied/engine checksums
17
- * @param projectRoot - Root directory of the project
18
- * @param paths - Resolved project paths
19
- * @param furnacePaths - Resolved Furnace-specific paths
20
- * @param ftlDir - Engine-relative directory localized components deploy their `.ftl` into
14
+ * @param options - See {@link DetailedComponentStatusOptions}
21
15
  */
22
- async function showDetailedComponentStatus(name, config, state, projectRoot, paths, furnacePaths, ftlDir) {
16
+ async function showDetailedComponentStatus(options) {
17
+ const { name, config, state, projectRoot, paths, furnacePaths, ftlDir } = options;
23
18
  const customConfig = config.custom[name];
24
19
  const overrideConfig = config.overrides[name];
25
20
  if (!customConfig && !overrideConfig && !config.stock.includes(name)) {
@@ -113,7 +108,15 @@ export async function furnaceStatusCommand(projectRoot, name) {
113
108
  const furnacePaths = getFurnacePaths(projectRoot);
114
109
  const ftlDir = resolveFtlDir(config.ftlBasePath);
115
110
  if (name) {
116
- await showDetailedComponentStatus(name, config, state, projectRoot, paths, furnacePaths, ftlDir);
111
+ await showDetailedComponentStatus({
112
+ name,
113
+ config,
114
+ state,
115
+ projectRoot,
116
+ paths,
117
+ furnacePaths,
118
+ ftlDir,
119
+ });
117
120
  return;
118
121
  }
119
122
  // Surface a pendingRepair marker before the normal summary so it cannot
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Derives the underscored stem a component's generated test files are named
3
+ * after.
4
+ *
5
+ * Five modules (`create-browser-test`, `create-dry-run`, `remove`, `rename`,
6
+ * `rename-browser-test`) had each inlined this three-step transform. The steps
7
+ * are not arbitrary and must agree exactly, or `furnace rename` and
8
+ * `furnace remove` compute a different filename than `furnace create` wrote
9
+ * and silently leave the old test behind:
10
+ *
11
+ * 1. drop the upstream `moz-` custom-element prefix, which never appears in a
12
+ * test filename;
13
+ * 2. drop a leading `<binaryName>-`, because the test directory is already
14
+ * named after the binary and the prefix would be doubled;
15
+ * 3. underscore the remaining dashes, matching mozilla-central's
16
+ * `browser_*.js` convention.
17
+ * @param componentName - Custom-element name, e.g. `moz-mybrowser-sidebar`.
18
+ * @param binaryName - Configured binary name, e.g. `mybrowser`.
19
+ * @returns The stem, e.g. `sidebar`.
20
+ */
21
+ export declare function deriveTestStem(componentName: string, binaryName: string): string;
22
+ /**
23
+ * Builds the browser-chrome test filename for a component.
24
+ * @param componentName - Custom-element name.
25
+ * @param binaryName - Configured binary name.
26
+ * @returns The filename, e.g. `browser_mybrowser_sidebar.js`.
27
+ */
28
+ export declare function browserTestFileName(componentName: string, binaryName: string): string;
@@ -0,0 +1,39 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Derives the underscored stem a component's generated test files are named
4
+ * after.
5
+ *
6
+ * Five modules (`create-browser-test`, `create-dry-run`, `remove`, `rename`,
7
+ * `rename-browser-test`) had each inlined this three-step transform. The steps
8
+ * are not arbitrary and must agree exactly, or `furnace rename` and
9
+ * `furnace remove` compute a different filename than `furnace create` wrote
10
+ * and silently leave the old test behind:
11
+ *
12
+ * 1. drop the upstream `moz-` custom-element prefix, which never appears in a
13
+ * test filename;
14
+ * 2. drop a leading `<binaryName>-`, because the test directory is already
15
+ * named after the binary and the prefix would be doubled;
16
+ * 3. underscore the remaining dashes, matching mozilla-central's
17
+ * `browser_*.js` convention.
18
+ * @param componentName - Custom-element name, e.g. `moz-mybrowser-sidebar`.
19
+ * @param binaryName - Configured binary name, e.g. `mybrowser`.
20
+ * @returns The stem, e.g. `sidebar`.
21
+ */
22
+ export function deriveTestStem(componentName, binaryName) {
23
+ const withoutMozPrefix = componentName.startsWith('moz-')
24
+ ? componentName.slice(4)
25
+ : componentName;
26
+ const withoutBinaryPrefix = withoutMozPrefix.startsWith(`${binaryName}-`)
27
+ ? withoutMozPrefix.slice(binaryName.length + 1)
28
+ : withoutMozPrefix;
29
+ return withoutBinaryPrefix.replace(/-/g, '_');
30
+ }
31
+ /**
32
+ * Builds the browser-chrome test filename for a component.
33
+ * @param componentName - Custom-element name.
34
+ * @param binaryName - Configured binary name.
35
+ * @returns The filename, e.g. `browser_mybrowser_sidebar.js`.
36
+ */
37
+ export function browserTestFileName(componentName, binaryName) {
38
+ return `browser_${binaryName}_${deriveTestStem(componentName, binaryName)}.js`;
39
+ }
@@ -1,4 +1,4 @@
1
- import type { getFurnacePaths } from '../../core/furnace-config.js';
1
+ import type { FurnacePaths } from '../../core/furnace-config.js';
2
2
  import type { DryRunAction, FurnaceConfig, ValidationIssue } from '../../types/furnace.js';
3
3
  import { type SpinnerHandle } from '../../utils/logger.js';
4
4
  /**
@@ -16,15 +16,27 @@ export type ValidationResult = {
16
16
  componentCount: number;
17
17
  skippedValidationCount: number;
18
18
  };
19
+ export interface RunDeployValidationOptions {
20
+ /** Active spinner handle for progress display. */
21
+ validateSpinner: SpinnerHandle;
22
+ /** Optional component name (validates all if omitted). */
23
+ name: string | undefined;
24
+ /** Loaded Furnace configuration. */
25
+ config: FurnaceConfig;
26
+ /** Resolved Furnace workspace paths. */
27
+ furnacePaths: FurnacePaths;
28
+ /** Names of components whose apply step failed. */
29
+ failedComponents: Set<string>;
30
+ /** Whether deploy is running in dry-run mode. */
31
+ isDryRun: boolean;
32
+ /** Root directory of the project. */
33
+ projectRoot: string;
34
+ /** Projected actions of a dry run, used to filter projected issues. */
35
+ dryRunActions?: DryRunAction[] | undefined;
36
+ }
19
37
  /**
20
38
  * Runs the validation phase of a furnace deploy, checking all or a single component.
21
- * @param validateSpinner - Active spinner handle for progress display
22
- * @param name - Optional component name (validates all if omitted)
23
- * @param config - Loaded Furnace configuration
24
- * @param furnacePaths - Resolved Furnace workspace paths
25
- * @param failedComponents - Names of components whose apply step failed
26
- * @param isDryRun - Whether deploy is running in dry-run mode
27
- * @param projectRoot - Root directory of the project
39
+ * @param options - See {@link RunDeployValidationOptions}
28
40
  * @returns Validation counts, or `done: true` if the caller should early-return
29
41
  */
30
- export declare function runDeployValidation(validateSpinner: SpinnerHandle, name: string | undefined, config: FurnaceConfig, furnacePaths: ReturnType<typeof getFurnacePaths>, failedComponents: Set<string>, isDryRun: boolean, projectRoot: string, dryRunActions?: DryRunAction[]): Promise<ValidationResult>;
42
+ export declare function runDeployValidation(options: RunDeployValidationOptions): Promise<ValidationResult>;
@@ -56,16 +56,11 @@ function resolveNamedValidationTarget(name, config, furnacePaths) {
56
56
  }
57
57
  /**
58
58
  * Runs the validation phase of a furnace deploy, checking all or a single component.
59
- * @param validateSpinner - Active spinner handle for progress display
60
- * @param name - Optional component name (validates all if omitted)
61
- * @param config - Loaded Furnace configuration
62
- * @param furnacePaths - Resolved Furnace workspace paths
63
- * @param failedComponents - Names of components whose apply step failed
64
- * @param isDryRun - Whether deploy is running in dry-run mode
65
- * @param projectRoot - Root directory of the project
59
+ * @param options - See {@link RunDeployValidationOptions}
66
60
  * @returns Validation counts, or `done: true` if the caller should early-return
67
61
  */
68
- export async function runDeployValidation(validateSpinner, name, config, furnacePaths, failedComponents, isDryRun, projectRoot, dryRunActions) {
62
+ export async function runDeployValidation(options) {
63
+ const { validateSpinner, name, config, furnacePaths, failedComponents, isDryRun, projectRoot, dryRunActions, } = options;
69
64
  let totalErrors = 0;
70
65
  let totalWarnings = 0;
71
66
  let componentCount = 0;
@@ -437,16 +437,9 @@ export function registerImport(program, { getProjectRoot, withErrorHandling }) {
437
437
  // prompt, so a scripted import can run unattended while the integrity
438
438
  // refusal stays armed — which is the guard you actually want in CI.
439
439
  .option('-y, --yes', 'Answer the drift prompt non-interactively. Unlike --force, this does NOT waive the patch-integrity gate.')
440
- .option('--until <patch>', 'Apply patches only up to and including this patch (alias: --stop-at)')
441
- .option('--stop-at <patch>', 'Alias for --until')
440
+ .option('--until <patch>', 'Apply patches only up to and including this patch')
442
441
  .option('--dry-run', 'Preview which patches would be applied without modifying the engine')
443
442
  .action(withErrorHandling(async (options) => {
444
- // Accept both spellings; --until wins when both are passed.
445
- const merged = { ...options };
446
- if (merged.until === undefined && merged.stopAt !== undefined) {
447
- merged.until = merged.stopAt;
448
- }
449
- delete merged.stopAt;
450
- await importCommand(getProjectRoot(), pickDefined(merged));
443
+ await importCommand(getProjectRoot(), pickDefined(options));
451
444
  }));
452
445
  }
@@ -1,5 +1,5 @@
1
- import { getProjectPaths } from '../core/config.js';
2
1
  import type { LintCommandOptions } from '../types/commands/index.js';
2
+ import type { ProjectPaths } from '../types/config.js';
3
3
  /**
4
4
  * Lints each patch in the queue as its own isolated diff, honouring
5
5
  * per-patch `lintIgnore` entries. Cross-patch rules still run once over
@@ -7,4 +7,4 @@ import type { LintCommandOptions } from '../types/commands/index.js';
7
7
  * With `options.patches` set, only the named subset is linted (and the
8
8
  * queue-level findings are scoped to files those patches touch).
9
9
  */
10
- export declare function lintPerPatch(projectRoot: string, paths: ReturnType<typeof getProjectPaths>, options?: LintCommandOptions): Promise<void>;
10
+ export declare function lintPerPatch(projectRoot: string, paths: ProjectPaths, options?: LintCommandOptions): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { join } from 'node:path';
3
- import { getProjectPaths, loadConfig } from '../core/config.js';
3
+ import { loadConfig } from '../core/config.js';
4
4
  import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
5
5
  import { withPrivateGitIndex } from '../core/git-readonly-index.js';
6
6
  import { buildPatchQueueContext, countNonBinaryDiffLines, formatPatchLintIssue, lintExportedPatch, lintPatchQueue, resolvePatchSizeTier, } from '../core/patch-lint.js';
@@ -169,7 +169,15 @@ async function applyPerPatchResults(subset, results, issues, checkJs, cache) {
169
169
  issues.push(...(await checkJs.getGlobal()));
170
170
  }
171
171
  if (result.cacheWrite && cache) {
172
- setCachedPerPatchLintIssues(cache, patch.filename, result.cacheWrite.key, result.rawIssues, result.suppressedIssues, result.lineCount, result.cacheWrite.lintIgnore);
172
+ setCachedPerPatchLintIssues({
173
+ cache,
174
+ patchFilename: patch.filename,
175
+ key: result.cacheWrite.key,
176
+ issues: result.rawIssues,
177
+ suppressed: result.suppressedIssues,
178
+ lineCount: result.lineCount,
179
+ lintIgnore: result.cacheWrite.lintIgnore,
180
+ });
173
181
  totals.cacheDirty = true;
174
182
  }
175
183
  for (const issue of result.rawIssues) {
@@ -267,7 +275,7 @@ function reportPerPatchOutcome(issues, linted, skipped, options) {
267
275
  /**
268
276
  * Resolves the `--patches <name…>` subset filter against the manifest,
269
277
  * matching each requested name tolerantly (exact filename, filename ±
270
- * `.patch`, or the manifest `name` field). Throws listing the available
278
+ * `.patch`, the bare order number, or the manifest `name` field). Throws listing the available
271
279
  * patches when a requested name matches none, so a typo fails loud rather
272
280
  * than silently linting nothing.
273
281
  */
@@ -278,7 +286,14 @@ function selectPatchSubset(manifest, requested) {
278
286
  .filter((entry) => entry.length > 0);
279
287
  const patchAliases = (p) => {
280
288
  const aliases = new Set([p.filename, p.filename.replace(/\.patch$/, ''), p.name]);
289
+ // The bare order number, both as the manifest records it and as the
290
+ // filename spells it (`2` and `002`). The refusal message offers stems,
291
+ // and an operator reading `102-ui-canvas-tiles.patch` in a lint finding
292
+ // reaches for `102` first.
293
+ aliases.add(String(p.order));
281
294
  const match = /^(\d+)-([a-z]+)-(.+)\.patch$/.exec(p.filename);
295
+ if (match?.[1])
296
+ aliases.add(match[1]);
282
297
  if (match?.[2] && match[3]) {
283
298
  aliases.add(`${match[2]}-${match[3]}`);
284
299
  aliases.add(match[3]);
@@ -296,7 +311,7 @@ function selectPatchSubset(manifest, requested) {
296
311
  if (found.length === 0) {
297
312
  const available = manifest.patches.map((p) => p.filename).join(', ');
298
313
  throw new GeneralError(`No patch in the queue matches "${name}". In --per-patch mode, positional arguments ` +
299
- `and --patches select patches (filename, stem, manifest name, or slug) — not engine ` +
314
+ `and --patches select patches (filename, stem, order number, manifest name, or slug) — not engine ` +
300
315
  `files; drop --per-patch to lint engine paths. Available patches: ${available}`);
301
316
  }
302
317
  for (const p of found) {
@@ -20,10 +20,10 @@
20
20
  * less than cold" via `probeCheckJsGlobalIssues` instead of building
21
21
  * anything.
22
22
  */
23
- import type { getProjectPaths, loadConfig } from '../core/config.js';
24
23
  import { type GroupedCheckJsResult } from '../core/patch-lint-checkjs.js';
25
24
  import type { PatchQueueContext } from '../core/patch-lint-cross.js';
26
25
  import type { PatchLintIssue } from '../types/commands/index.js';
26
+ import type { FireForgeConfig, ProjectPaths } from '../types/config.js';
27
27
  /**
28
28
  * Queue-wide checkJs program built once per run and sliced per patch, so a
29
29
  * single type regression surfaces once against its owning patch instead of
@@ -61,4 +61,4 @@ export interface PerRunCheckJs {
61
61
  * subset's owned files and the test-file pass runs only the subset's own
62
62
  * scripts (head.js discovery still spans the queue).
63
63
  */
64
- export declare function buildPerRunCheckJs(projectRoot: string, paths: ReturnType<typeof getProjectPaths>, config: Awaited<ReturnType<typeof loadConfig>>, ctx: PatchQueueContext, rootScopePatches?: ReadonlySet<string>): PerRunCheckJs | undefined;
64
+ export declare function buildPerRunCheckJs(projectRoot: string, paths: ProjectPaths, config: FireForgeConfig, ctx: PatchQueueContext, rootScopePatches?: ReadonlySet<string>): PerRunCheckJs | undefined;
@@ -1,4 +1,5 @@
1
1
  import { Command } from 'commander';
2
+ import { type PatchQueueContext } from '../core/patch-lint.js';
2
3
  import type { CommandContext } from '../types/cli.js';
3
4
  import type { LintCommandOptions, PatchLintIssue } from '../types/commands/index.js';
4
5
  /**
@@ -34,7 +35,7 @@ export interface AggregateLintIgnoreResult {
34
35
  * @param ctx - Patch queue context used to attribute file → patch.
35
36
  * @returns Filtered issue list and the count of dropped findings.
36
37
  */
37
- export declare function applyAggregateLintIgnoreSuppression(issues: PatchLintIssue[], ctx: import('../core/patch-lint.js').PatchQueueContext): AggregateLintIgnoreResult;
38
+ export declare function applyAggregateLintIgnoreSuppression(issues: PatchLintIssue[], ctx: PatchQueueContext): AggregateLintIgnoreResult;
38
39
  /**
39
40
  * Runs the lint command to check engine changes against patch quality rules.
40
41
  * @param projectRoot - Root directory of the project
@@ -495,7 +495,7 @@ export function registerLint(program, { getProjectRoot, withErrorHandling }) {
495
495
  .option('--since <git-rev>', 'Tag issues as [introduced] or [cumulative] based on whether the file changed since <git-rev> (e.g. HEAD, a branch, a SHA)')
496
496
  .option('--only-introduced', 'Fail only on issues tagged [introduced] (requires --since). Cumulative errors still print but do not set a non-zero exit.')
497
497
  .option('--per-patch', "Lint each patch in the queue as its own isolated diff. Rescopes patch-size rules so they fire against individual patches rather than the aggregate. Honours each patch's `lintIgnore` entries.")
498
- .option('--patches <names...>', 'With --per-patch, lint only the named patches. Accepts repeated flags, comma lists, full filenames/stems, manifest names, category-prefixed slugs, or bare slugs. Positional arguments after --per-patch are treated the same way.')
498
+ .option('--patches <names...>', 'With --per-patch, lint only the named patches. Accepts repeated flags, comma lists, full filenames/stems, bare order numbers, manifest names, category-prefixed slugs, or bare slugs. Positional arguments after --per-patch are treated the same way.')
499
499
  .option('--max-warnings <n>', 'Fail when lint reports more than <n> warning(s); use 0 for warning-clean release gates.')
500
500
  .option('--no-cache', 'Bypass per-patch lint result cache reads and writes.')
501
501
  .option('--report <path>', 'With --per-patch, write a machine-readable JSON report (per-patch size metrics, tier, thresholds, issues, and lintIgnore-suppressed issues) to <path>.')
@@ -12,7 +12,7 @@ import { registerLint } from './lint.js';
12
12
  import { registerPackage } from './package.js';
13
13
  import { registerPatch } from './patch/index.js';
14
14
  import { registerReExport } from './re-export-register.js';
15
- import { registerRebase } from './rebase.js';
15
+ import { registerRebase } from './rebase/index.js';
16
16
  import { registerRegister } from './register.js';
17
17
  import { registerReset } from './reset.js';
18
18
  import { registerResolve } from './resolve.js';
@@ -8,6 +8,7 @@ import { hasBuildArtifacts, machPackageCapture } from '../core/mach.js';
8
8
  import { assertBuildArtifacts } from '../core/mach-build-artifacts.js';
9
9
  import { explainMachError } from '../core/mach-error-hints.js';
10
10
  import { BuildError } from '../errors/build.js';
11
+ import { elapsedSince } from '../utils/elapsed.js';
11
12
  import { error, info, intro, outro, verbose } from '../utils/logger.js';
12
13
  import { pickDefined } from '../utils/options.js';
13
14
  /**
@@ -54,10 +55,7 @@ export async function packageCommand(projectRoot, options) {
54
55
  catch (error) {
55
56
  throw new BuildError('Package process failed to start', 'mach package', error instanceof Error ? error : undefined);
56
57
  }
57
- const duration = Date.now() - startTime;
58
- const minutes = Math.floor(duration / 60000);
59
- const seconds = Math.floor((duration % 60000) / 1000);
60
- const timeStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
58
+ const timeStr = elapsedSince(startTime);
61
59
  if (result.exitCode !== 0) {
62
60
  error(`Packaging failed after ${timeStr}`);
63
61
  const combinedOutput = `${result.stdout}\n${result.stderr}`;
@@ -11,14 +11,16 @@
11
11
  */
12
12
  import { Command } from 'commander';
13
13
  import { loadConfig } from '../../core/config.js';
14
- import { appendHistory, confirmDestructive } from '../../core/destructive.js';
14
+ import { confirmDestructive } from '../../core/destructive.js';
15
+ import { appendHistoryBestEffort } from '../../core/history-log.js';
15
16
  import { withPatchDirectoryLock } from '../../core/patch-lock.js';
16
17
  import { loadPatchesManifest, renumberPatchesInManifest, } from '../../core/patch-manifest.js';
17
18
  import { applyRenameMapToManifest, enforcePatchPolicy } from '../../core/patch-policy.js';
18
19
  import { GeneralError } from '../../errors/base.js';
19
- import { toError } from '../../utils/errors.js';
20
20
  import { info, intro, outro, warn } from '../../utils/logger.js';
21
21
  import { addWaitLockOption, pickDefined, resolveWaitLockSeconds } from '../../utils/options.js';
22
+ import { proceedAfterDecision } from '../destructive-decision.js';
23
+ import { renameMapsEqual } from '../patch-rename-map.js';
22
24
  import { requirePatchQueue } from './patch-context.js';
23
25
  import { rebuildFilenameForOrder } from './reorder.js';
24
26
  /** True when `order` falls inside a configured reserved range. */
@@ -139,14 +141,8 @@ export async function patchCompactCommand(projectRoot, options = {}) {
139
141
  dryRun: options.dryRun === true,
140
142
  unsafeOverride: options.forceUnsafe === true,
141
143
  });
142
- if (decision === 'dry-run') {
143
- outro('Dry run complete — no changes made');
144
+ if (!proceedAfterDecision(decision, 'Compact cancelled'))
144
145
  return;
145
- }
146
- if (decision === 'declined') {
147
- outro('Compact cancelled');
148
- return;
149
- }
150
146
  await withPatchDirectoryLock(paths.patches, async () => {
151
147
  const currentManifest = await loadPatchesManifest(paths.patches);
152
148
  if (!currentManifest) {
@@ -157,6 +153,14 @@ export async function patchCompactCommand(projectRoot, options = {}) {
157
153
  info('Patch queue was compacted by another process. Nothing to do.');
158
154
  return;
159
155
  }
156
+ // The operator confirmed a specific plan. A queue that changed between
157
+ // the prompt and the lock (an export landing a new patch, a delete)
158
+ // yields a different plan whose renames were never shown — refuse
159
+ // rather than commit them under a history entry that says "ok", the
160
+ // same gate `patch reorder` applies.
161
+ if (!renameMapsEqual(renameMap, currentRenameMap)) {
162
+ throw new GeneralError('Patch queue changed while waiting for confirmation. Re-run compact to recompute the rename plan.');
163
+ }
160
164
  enforcePatchPolicy({
161
165
  config,
162
166
  manifest: applyRenameMapToManifest(currentManifest, currentRenameMap),
@@ -179,12 +183,7 @@ export async function patchCompactCommand(projectRoot, options = {}) {
179
183
  ...(options.forceUnsafe === true ? { unsafeOverride: true } : {}),
180
184
  result: 'ok',
181
185
  };
182
- try {
183
- await appendHistory(paths.patches, historyEntry);
184
- }
185
- catch (historyError) {
186
- warn(`History log append failed after patch compact committed: ${toError(historyError).message}`);
187
- }
186
+ await appendHistoryBestEffort(paths.patches, historyEntry, `patch compact committed`);
188
187
  }, { waitLockSeconds: resolveWaitLockSeconds(options.waitLock), command: 'patch compact' });
189
188
  info(`Compacted ${renameMap.size} patch(es).`);
190
189
  outro('Compact complete');
@@ -9,13 +9,14 @@
9
9
  */
10
10
  import { basename } from 'node:path';
11
11
  import { Command } from 'commander';
12
- import { appendHistory, confirmDestructive } from '../../core/destructive.js';
12
+ import { confirmDestructive } from '../../core/destructive.js';
13
+ import { appendHistoryBestEffort } from '../../core/history-log.js';
13
14
  import { buildPatchQueueContext, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, isForwardImportableFile, } from '../../core/patch-lint.js';
14
15
  import { withPatchDirectoryLock } from '../../core/patch-lock.js';
15
16
  import { removePatchFileAndManifest } from '../../core/patch-manifest.js';
16
- import { toError } from '../../utils/errors.js';
17
17
  import { info, intro, outro, warn } from '../../utils/logger.js';
18
18
  import { addWaitLockOption, pickDefined, resolveWaitLockSeconds } from '../../utils/options.js';
19
+ import { proceedAfterDecision } from '../destructive-decision.js';
19
20
  import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
20
21
  /**
21
22
  * Runs the `patch delete` command: removes a patch file and its manifest
@@ -138,14 +139,8 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
138
139
  unsafeOverride: options.forceUnsafe === true,
139
140
  conflicts,
140
141
  });
141
- if (decision === 'dry-run') {
142
- outro('Dry run complete — no changes made');
142
+ if (!proceedAfterDecision(decision, 'Delete cancelled'))
143
143
  return;
144
- }
145
- if (decision === 'declined') {
146
- outro('Delete cancelled');
147
- return;
148
- }
149
144
  // Proceed: remove under the patch directory lock so concurrent exports
150
145
  // cannot race us into the same manifest row. The history append lives
151
146
  // inside the same lock so two concurrent deletes cannot interleave history
@@ -156,22 +151,17 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
156
151
  // failure to the caller would mislead.
157
152
  await withPatchDirectoryLock(paths.patches, async () => {
158
153
  await removePatchFileAndManifest(paths.patches, target.filename);
159
- try {
160
- await appendHistory(paths.patches, {
161
- operation: 'patch-delete',
162
- args: {
163
- filename: target.filename,
164
- order: target.order,
165
- filesAffected: target.filesAffected,
166
- },
167
- ...(options.yes === true ? { yes: true } : {}),
168
- ...(options.forceUnsafe === true ? { unsafeOverride: true } : {}),
169
- result: 'ok',
170
- });
171
- }
172
- catch (historyError) {
173
- warn(`History log append failed after patch delete committed (${target.filename}): ${toError(historyError).message}`);
174
- }
154
+ await appendHistoryBestEffort(paths.patches, {
155
+ operation: 'patch-delete',
156
+ args: {
157
+ filename: target.filename,
158
+ order: target.order,
159
+ filesAffected: target.filesAffected,
160
+ },
161
+ ...(options.yes === true ? { yes: true } : {}),
162
+ ...(options.forceUnsafe === true ? { unsafeOverride: true } : {}),
163
+ result: 'ok',
164
+ }, `patch delete committed (${target.filename})`);
175
165
  }, { waitLockSeconds: resolveWaitLockSeconds(options.waitLock), command: 'patch delete' });
176
166
  info(`Deleted ${target.filename}.`);
177
167
  outro('Delete complete');
@@ -0,0 +1,13 @@
1
+ import type { PatchQueueEntry } from '../../core/patch-lint.js';
2
+ /**
3
+ * Projects a rewritten patch body into the three fields cross-patch lint reads
4
+ * off a queue entry.
5
+ *
6
+ * `patch split` and `patch move-files` each had a byte-identical copy of this
7
+ * (`buildEntryProjection` / `projectEntryBody`). Both feed the same linter, so
8
+ * a change to what the projection carries has to reach both or the two
9
+ * commands start refusing different things for the same queue.
10
+ * @param diff - The rewritten patch body.
11
+ * @returns The diff plus its derived new-file and modified-file projections.
12
+ */
13
+ export declare function projectEntryBody(diff: string): Pick<PatchQueueEntry, 'diff' | 'newFiles' | 'createdFiles' | 'modifiedFileAdditions'>;
@@ -0,0 +1,24 @@
1
+ import { buildModifiedFileAdditionsFromDiff } from '../../core/patch-lint.js';
2
+ import { detectNewFilesInDiff } from '../../core/patch-lint-diff.js';
3
+ import { buildNewFileTextProjection } from '../../core/patch-transform.js';
4
+ /**
5
+ * Projects a rewritten patch body into the three fields cross-patch lint reads
6
+ * off a queue entry.
7
+ *
8
+ * `patch split` and `patch move-files` each had a byte-identical copy of this
9
+ * (`buildEntryProjection` / `projectEntryBody`). Both feed the same linter, so
10
+ * a change to what the projection carries has to reach both or the two
11
+ * commands start refusing different things for the same queue.
12
+ * @param diff - The rewritten patch body.
13
+ * @returns The diff plus its derived new-file and modified-file projections.
14
+ */
15
+ export function projectEntryBody(diff) {
16
+ const newFiles = buildNewFileTextProjection(diff);
17
+ return {
18
+ diff,
19
+ newFiles,
20
+ // Body-kind agnostic: a binary creation is still a creation.
21
+ createdFiles: detectNewFilesInDiff(diff),
22
+ modifiedFileAdditions: buildModifiedFileAdditionsFromDiff(diff),
23
+ };
24
+ }
@@ -21,13 +21,6 @@
21
21
  import { Command } from 'commander';
22
22
  import type { CommandContext } from '../../types/cli.js';
23
23
  import type { PatchLintIgnoreOptions } from '../../types/commands/index.js';
24
- type LintIgnoreMode = 'add' | 'remove' | 'clear';
25
- /**
26
- * Renders a one-line summary of the planned change for use in
27
- * `info()` / dry-run / history args. Exported for unit-testing the
28
- * message format directly without mocking the logger transport.
29
- */
30
- export declare function describeChange(before: ReadonlyArray<string>, after: ReadonlyArray<string>, mode: LintIgnoreMode, values: ReadonlyArray<string>): string;
31
24
  /**
32
25
  * Runs the `patch lint-ignore` command: reads the patch's existing
33
26
  * `lintIgnore`, applies the requested mode, and writes the manifest.
@@ -44,4 +37,3 @@ export declare function patchLintIgnoreCommand(projectRoot: string, identifier:
44
37
  * @param context - Shared CLI registration context
45
38
  */
46
39
  export declare function registerPatchLintIgnore(parent: Command, context: CommandContext): void;
47
- export {};