@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
@@ -96,12 +96,6 @@ export interface DoctorCheckDefinition {
96
96
  */
97
97
  fix?: string;
98
98
  }
99
- /**
100
- * Resolves a {@link DoctorCheck} to its effective severity. `severity` is the
101
- * single source of truth; this exists so every consumer of a
102
- * `DoctorCheck[]` reads it the same way.
103
- */
104
- export declare function resolveDoctorSeverity(check: DoctorCheck): 'ok' | 'warning' | 'error';
105
99
  /**
106
100
  * Builds a DoctorCheck object representing a successful "OK" check.
107
101
  */
@@ -4,14 +4,6 @@
4
4
  * `doctor.ts` and sibling check modules. Kept separate so registries can import
5
5
  * these symbols without creating cycles through `doctor.ts`.
6
6
  */
7
- /**
8
- * Resolves a {@link DoctorCheck} to its effective severity. `severity` is the
9
- * single source of truth; this exists so every consumer of a
10
- * `DoctorCheck[]` reads it the same way.
11
- */
12
- export function resolveDoctorSeverity(check) {
13
- return check.severity;
14
- }
15
7
  /**
16
8
  * Builds a DoctorCheck object representing a successful "OK" check.
17
9
  */
@@ -27,12 +27,6 @@ export interface OrphanedHarnessWorker {
27
27
  cpuSeconds: number;
28
28
  command: string;
29
29
  }
30
- /**
31
- * Parses a `ps` TIME (accumulated CPU) value. Implemented in terms of
32
- * {@link parsePsDuration} — `time=` and `etime=` share one grammar, and a
33
- * second copy is how the two would drift.
34
- */
35
- export declare function parseCpuTime(time: string): number;
36
30
  /**
37
31
  * Scans `ps -axo pid=,ppid=,time=,command=` output for orphaned harness
38
32
  * workers: PPID 1 (reparented to init/launchd), a multiprocessing
@@ -29,14 +29,6 @@ import { ok, warning } from './doctor-check-core.js';
29
29
  const HARNESS_WORKER_COMMAND_PATTERN = /multiprocessing\.(?:spawn|forkserver)\b|multiprocessing\.resource_tracker|from multiprocessing\.\w+ import/;
30
30
  /** Minimum accumulated CPU time before a match is reported (10 minutes). */
31
31
  const DEFAULT_MIN_CPU_SECONDS = 600;
32
- /**
33
- * Parses a `ps` TIME (accumulated CPU) value. Implemented in terms of
34
- * {@link parsePsDuration} — `time=` and `etime=` share one grammar, and a
35
- * second copy is how the two would drift.
36
- */
37
- export function parseCpuTime(time) {
38
- return parsePsDuration(time);
39
- }
40
32
  /**
41
33
  * Scans `ps -axo pid=,ppid=,time=,command=` output for orphaned harness
42
34
  * workers: PPID 1 (reparented to init/launchd), a multiprocessing
@@ -57,7 +49,7 @@ export function findOrphanedHarnessWorkers(psOutput, minCpuSeconds = DEFAULT_MIN
57
49
  continue;
58
50
  if (!HARNESS_WORKER_COMMAND_PATTERN.test(command))
59
51
  continue;
60
- const cpuSeconds = parseCpuTime(cpuTime);
52
+ const cpuSeconds = parsePsDuration(cpuTime);
61
53
  if (Number.isNaN(cpuSeconds) || cpuSeconds < minCpuSeconds)
62
54
  continue;
63
55
  workers.push({ pid, ppid, cpuTime, cpuSeconds, command });
@@ -68,7 +60,7 @@ export function findOrphanedHarnessWorkers(psOutput, minCpuSeconds = DEFAULT_MIN
68
60
  async function listSystemProcesses() {
69
61
  const result = await exec('ps', ['-axo', 'pid=,ppid=,time=,command='], { timeout: 10000 });
70
62
  if (result.exitCode !== 0) {
71
- throw new Error(`ps exited ${String(result.exitCode)}`);
63
+ throw new Error(`ps exited ${result.exitCode}`);
72
64
  }
73
65
  return result.stdout;
74
66
  }
@@ -86,10 +78,10 @@ async function runOrphanedHarnessCheck() {
86
78
  return ok(CHECK_NAME);
87
79
  }
88
80
  const rows = orphans
89
- .map((w) => `PID ${String(w.pid)} (CPU time ${w.cpuTime}): ${w.command.slice(0, 160)}`)
81
+ .map((w) => `PID ${w.pid} (CPU time ${w.cpuTime}): ${w.command.slice(0, 160)}`)
90
82
  .join('; ');
91
83
  const pids = orphans.map((w) => String(w.pid)).join(' ');
92
- return warning(CHECK_NAME, `Found ${String(orphans.length)} orphaned Python multiprocessing worker(s) — PPID 1 with ` +
84
+ return warning(CHECK_NAME, `Found ${orphans.length} orphaned Python multiprocessing worker(s) — PPID 1 with ` +
93
85
  `high accumulated CPU time, the shape a test harness that died at startup leaves behind ` +
94
86
  `(field incident: one such worker busy-spun for ~26 days). ${rows}`, `These look like workers orphaned by a crashed test harness. Verify each command line, ` +
95
87
  `then terminate with: kill ${pids} (or kill -9 if a process survives). ` +
@@ -88,7 +88,7 @@ async function runPostRebaseAudit(ctx) {
88
88
  }
89
89
  const browserTomls = await collectBrowserTomlFiles(engineDir);
90
90
  if (browserTomls.unreadable.length > 0) {
91
- issues.push(`could not read ${String(browserTomls.unreadable.length)} ` +
91
+ issues.push(`could not read ${browserTomls.unreadable.length} ` +
92
92
  `${browserTomls.unreadable.length === 1 ? 'directory' : 'directories'} under ` +
93
93
  `browser/base/content/test: ${browserTomls.unreadable.join(', ')}`);
94
94
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Doctor check comparing the CONFIGURED Firefox source pin against what the
3
+ * engine checkout actually is.
4
+ *
5
+ * `fireforge source set` lands `firefox.version` inside `fireforge.json`,
6
+ * beside hand-maintained policy sections. That is convenient and it is also
7
+ * the whole problem: a routine `git checkout -- fireforge.json` — undoing an
8
+ * accidental reformat, say — silently reverts an uncommitted pin along with
9
+ * it, and nothing afterwards reports that the pin and the checkout have
10
+ * diverged. The tell in the field was a gate flipping green with no change
11
+ * that should have made it green.
12
+ *
13
+ * This is deliberately not a lock and not a refusal: a tree may legitimately
14
+ * be mid-migration between two versions. It only makes a mismatched tree
15
+ * VISIBLE, which is all that was missing.
16
+ *
17
+ * Scope is limited by what is actually recorded. `firefox.product`,
18
+ * `firefox.sha256` and `firefox.candidate` have NO counterpart anywhere on
19
+ * disk — `.fireforge/state.json` records only `downloadedVersion` and
20
+ * `baseCommit` — so there is nothing to compare them against and the check
21
+ * says nothing about them rather than implying it verified them.
22
+ */
23
+ import type { DoctorCheckDefinition } from './doctor-check-core.js';
24
+ /**
25
+ * Pin-vs-checkout reporting. Needs the loaded config, so it declares the
26
+ * dependency on the config check that populates `ctx.config`; skipped
27
+ * entirely without an engine, where there is nothing to compare.
28
+ */
29
+ export declare const SOURCE_PIN_DOCTOR_CHECK: DoctorCheckDefinition;
@@ -0,0 +1,81 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Doctor check comparing the CONFIGURED Firefox source pin against what the
4
+ * engine checkout actually is.
5
+ *
6
+ * `fireforge source set` lands `firefox.version` inside `fireforge.json`,
7
+ * beside hand-maintained policy sections. That is convenient and it is also
8
+ * the whole problem: a routine `git checkout -- fireforge.json` — undoing an
9
+ * accidental reformat, say — silently reverts an uncommitted pin along with
10
+ * it, and nothing afterwards reports that the pin and the checkout have
11
+ * diverged. The tell in the field was a gate flipping green with no change
12
+ * that should have made it green.
13
+ *
14
+ * This is deliberately not a lock and not a refusal: a tree may legitimately
15
+ * be mid-migration between two versions. It only makes a mismatched tree
16
+ * VISIBLE, which is all that was missing.
17
+ *
18
+ * Scope is limited by what is actually recorded. `firefox.product`,
19
+ * `firefox.sha256` and `firefox.candidate` have NO counterpart anywhere on
20
+ * disk — `.fireforge/state.json` records only `downloadedVersion` and
21
+ * `baseCommit` — so there is nothing to compare them against and the check
22
+ * says nothing about them rather than implying it verified them.
23
+ */
24
+ import { getFirefoxVersion } from '../core/firefox.js';
25
+ import { toError } from '../utils/errors.js';
26
+ import { ok, warning } from './doctor-check-core.js';
27
+ const CHECK_NAME = 'Source pin matches engine';
28
+ /**
29
+ * Reports the configured pin beside the two facts that can contradict it:
30
+ * the engine's own `browser/config/version.txt`, and the version the last
31
+ * download recorded into state.
32
+ */
33
+ async function runSourcePinCheck(ctx) {
34
+ const pinned = ctx.config?.firefox.version;
35
+ if (pinned === undefined) {
36
+ // The config failed to load (its own check already reported that), so
37
+ // there is no pin to compare. Silence beats a second complaint.
38
+ return ok(CHECK_NAME, 'Skipped: no configured source version');
39
+ }
40
+ let actual;
41
+ try {
42
+ // A blank or whitespace-only version.txt (a truncated write, a partial
43
+ // extraction) carries no version to compare — treat it as absent rather
44
+ // than reporting the engine as being at version "".
45
+ actual = (await getFirefoxVersion(ctx.paths.engine))?.trim() || undefined;
46
+ }
47
+ catch (error) {
48
+ // Best-effort: an unreadable version.txt is not a pin mismatch, and a
49
+ // diagnostic must never turn into a failure of its own.
50
+ return ok(CHECK_NAME, `Configured ${pinned}; engine version could not be read (${toError(error).message})`);
51
+ }
52
+ const downloaded = ctx.state.downloadedVersion;
53
+ const mismatches = [];
54
+ if (actual !== undefined && actual !== pinned) {
55
+ mismatches.push(`engine/browser/config/version.txt reads ${actual}`);
56
+ }
57
+ if (downloaded !== undefined && downloaded !== pinned) {
58
+ mismatches.push(`the last download recorded ${downloaded}`);
59
+ }
60
+ if (mismatches.length === 0) {
61
+ const observed = actual ?? downloaded;
62
+ return ok(CHECK_NAME, observed === undefined
63
+ ? `Configured ${pinned}; no engine version to compare against yet`
64
+ : `Configured ${pinned}, engine ${observed}`);
65
+ }
66
+ return warning(CHECK_NAME, `fireforge.json pins Firefox ${pinned}, but ${mismatches.join(' and ')}.`, 'If the pin is right, run "fireforge download" to bring the checkout to it. If the ' +
67
+ 'CHECKOUT is right, run "fireforge source set --version <version>" to record it — a ' +
68
+ 'pin set but not committed is lost by any revert of fireforge.json, which is the most ' +
69
+ 'common way these diverge.');
70
+ }
71
+ /**
72
+ * Pin-vs-checkout reporting. Needs the loaded config, so it declares the
73
+ * dependency on the config check that populates `ctx.config`; skipped
74
+ * entirely without an engine, where there is nothing to compare.
75
+ */
76
+ export const SOURCE_PIN_DOCTOR_CHECK = {
77
+ name: CHECK_NAME,
78
+ skipIf: (ctx) => !ctx.engineExists,
79
+ dependsOn: ['fireforge.json is valid'],
80
+ run: runSourcePinCheck,
81
+ };
@@ -21,6 +21,8 @@ export declare function validateCheckDependencies(checks: readonly DoctorCheckDe
21
21
  * context-population dependency chain (see {@link DOCTOR_CHECKS}) must
22
22
  * also update this list, which gives us a single place to notice and
23
23
  * think through the consequences.
24
+ *
25
+ * @internal Exported only so tests can reach it; not part of the public surface.
24
26
  */
25
27
  export declare const DOCTOR_CHECK_ORDER: readonly string[];
26
28
  /**
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { Command } from 'commander';
3
- import { configExists, getProjectPaths, loadConfig, loadState, updateState, } from '../core/config.js';
3
+ import { configExists, getProjectPaths, loadConfig, loadState } from '../core/config.js';
4
4
  import { furnaceConfigExists as checkFurnaceConfigExists } from '../core/furnace-config.js';
5
5
  import { getCurrentBranch, getHead, isGitRepository, isMissingHeadError } from '../core/git.js';
6
6
  import { ensureGit } from '../core/git-base.js';
@@ -14,13 +14,15 @@ import { pathExists } from '../utils/fs.js';
14
14
  import { error, info, intro, outro, success, warn } from '../utils/logger.js';
15
15
  import { addWaitLockOption } from '../utils/options.js';
16
16
  import { findExecutable } from '../utils/process.js';
17
- import { failure, ok, resolveDoctorSeverity, warning } from './doctor-check-core.js';
17
+ import { failure, ok, warning } from './doctor-check-core.js';
18
18
  import { EXTERNAL_TOOLCHAIN_DOCTOR_CHECK } from './doctor-external-toolchains.js';
19
19
  import { FURNACE_DOCTOR_CHECKS } from './doctor-furnace.js';
20
20
  import { ORPHANED_HARNESS_DOCTOR_CHECK } from './doctor-orphaned-harness.js';
21
21
  import { PATCH_MANIFEST_CONSISTENCY_CHECK } from './doctor-patch-manifest.js';
22
22
  import { POST_REBASE_AUDIT_CHECK } from './doctor-post-rebase-audit.js';
23
+ import { SOURCE_PIN_DOCTOR_CHECK } from './doctor-source-pin.js';
23
24
  import { inspectEngineWorkingTree } from './doctor-working-tree.js';
25
+ import { clearPendingResolution } from './pending-resolution.js';
24
26
  import { collectPatchQueueHealth } from './verify.js';
25
27
  /**
26
28
  * Runs a single check definition, converting thrown errors into
@@ -201,16 +203,13 @@ const DOCTOR_CHECKS = [
201
203
  if (health.errorCount > 0) {
202
204
  return failure('Pending Resolution', `Refusing to clear pending resolution for ${patchFilename}: patch queue health check found ${health.errorCount} error(s).`, 'Run "fireforge verify" for details, fix the queue, then retry "fireforge doctor --clear-resolution".');
203
205
  }
204
- await updateState(ctx.projectRoot, (current) => {
205
- const next = { ...current };
206
- delete next.pendingResolution;
207
- return next;
208
- });
206
+ await clearPendingResolution(ctx.projectRoot);
209
207
  return ok('Pending Resolution');
210
208
  }
211
209
  return failure('Pending Resolution', `You are currently resolving a conflict for patch ${patchFilename}.`, 'Build and Export commands may behave unexpectedly until "fireforge resolve" is completed. If the queue now verifies cleanly, run "fireforge doctor --clear-resolution" to discard the stale marker.');
212
210
  },
213
211
  },
212
+ SOURCE_PIN_DOCTOR_CHECK,
214
213
  {
215
214
  name: 'Engine is git repository',
216
215
  skipIf: (ctx) => !ctx.engineExists,
@@ -320,6 +319,8 @@ validateCheckDependencies(DOCTOR_CHECKS);
320
319
  * context-population dependency chain (see {@link DOCTOR_CHECKS}) must
321
320
  * also update this list, which gives us a single place to notice and
322
321
  * think through the consequences.
322
+ *
323
+ * @internal Exported only so tests can reach it; not part of the public surface.
323
324
  */
324
325
  export const DOCTOR_CHECK_ORDER = DOCTOR_CHECKS.map((check) => check.name);
325
326
  /**
@@ -334,7 +335,7 @@ export function reportDoctorResults(checks, mutations = []) {
334
335
  let warningCount = 0;
335
336
  let failedCount = 0;
336
337
  for (const check of checks) {
337
- const severity = resolveDoctorSeverity(check);
338
+ const severity = check.severity;
338
339
  if (severity === 'warning') {
339
340
  warn(`! ${check.name}: ${check.message}`);
340
341
  if (check.fix) {
@@ -110,11 +110,6 @@ function closeRestoreSpinner(restoreSpinner, result) {
110
110
  }
111
111
  restoreSpinner.stop('Patch-touched files restored');
112
112
  }
113
- async function clearStaleFurnaceApplyState(projectRoot) {
114
- // --force installs a new baseCommit, which invalidates every applied
115
- // checksum in furnace-state.json.
116
- await clearAppliedFurnaceState(projectRoot);
117
- }
118
113
  async function activateReplacementEngine(args) {
119
114
  const { engineDir, replacementDir, backupDir } = args;
120
115
  await rename(engineDir, backupDir);
@@ -155,28 +150,39 @@ async function restorePreviousEngine(args) {
155
150
  }
156
151
  }
157
152
  async function downloadAndExtractFirefox(args) {
158
- const { version, product, engineDir, cacheDir, sha256, candidate } = args;
153
+ const { version, product, engineDir, cacheDir, sha256, candidate, allowUnverifiedDownload } = args;
159
154
  let s = spinner(`Downloading Firefox ${version}...`);
160
155
  let lastPercent = 0;
161
156
  const phaseState = { value: 'download' };
162
157
  try {
163
- await downloadFirefoxSource(version, product, engineDir, cacheDir, (downloaded, total) => {
164
- if (total <= 0)
165
- return;
166
- const percent = Math.floor((downloaded / total) * 100);
167
- if (percent !== lastPercent && percent % 5 === 0) {
168
- s.message(`Downloading Firefox ${version}... ${percent}% (${formatBytes(downloaded)} / ${formatBytes(total)})`);
169
- lastPercent = percent;
170
- }
171
- }, (phase) => {
172
- if (phase === 'extract' && phaseState.value === 'download') {
173
- s.stop(`Firefox ${version} downloaded`);
174
- phaseState.value = 'extract';
175
- s = spinner(`Extracting Firefox ${version}... (decompressing ~600 MB of source; typically 30–90s)`);
176
- }
177
- }, sha256, (message) => {
178
- s.message(message);
179
- }, candidate);
158
+ await downloadFirefoxSource({
159
+ version: version,
160
+ product: product,
161
+ destDir: engineDir,
162
+ cacheDir: cacheDir,
163
+ onProgress: (downloaded, total) => {
164
+ if (total <= 0)
165
+ return;
166
+ const percent = Math.floor((downloaded / total) * 100);
167
+ if (percent !== lastPercent && percent % 5 === 0) {
168
+ s.message(`Downloading Firefox ${version}... ${percent}% (${formatBytes(downloaded)} / ${formatBytes(total)})`);
169
+ lastPercent = percent;
170
+ }
171
+ },
172
+ onPhase: (phase) => {
173
+ if (phase === 'extract' && phaseState.value === 'download') {
174
+ s.stop(`Firefox ${version} downloaded`);
175
+ phaseState.value = 'extract';
176
+ s = spinner(`Extracting Firefox ${version}... (decompressing ~600 MB of source; typically 30–90s)`);
177
+ }
178
+ },
179
+ expectedSha256: sha256,
180
+ onPhaseProgress: (message) => {
181
+ s.message(message);
182
+ },
183
+ candidate: candidate,
184
+ integrity: allowUnverifiedDownload === undefined ? undefined : { allowUnverifiedDownload },
185
+ });
180
186
  s.stop(phaseState.value === 'extract'
181
187
  ? `Firefox ${version} extracted`
182
188
  : `Firefox ${version} downloaded`);
@@ -235,7 +241,9 @@ async function initializeDownloadedEngine(args) {
235
241
  throw error;
236
242
  }
237
243
  if (replacementActivated) {
238
- await clearStaleFurnaceApplyState(projectRoot);
244
+ // --force installs a new baseCommit, which invalidates every applied
245
+ // checksum in furnace-state.json.
246
+ await clearAppliedFurnaceState(projectRoot);
239
247
  }
240
248
  await updateState(projectRoot, {
241
249
  downloadedVersion: version,
@@ -389,6 +397,9 @@ export async function downloadCommand(projectRoot, options) {
389
397
  ...(config.firefox.candidate !== undefined
390
398
  ? { candidate: config.firefox.candidate }
391
399
  : {}),
400
+ ...(config.firefox.allowUnverifiedDownload !== undefined
401
+ ? { allowUnverifiedDownload: config.firefox.allowUnverifiedDownload }
402
+ : {}),
392
403
  });
393
404
  if (replacementEngineDir && backupEngineDir) {
394
405
  warn('Activating replacement engine directory...');
@@ -19,10 +19,20 @@ import { info, intro, outro, spinner } from '../utils/logger.js';
19
19
  import { addWaitLockOption, pickDefined, resolveWaitLockSeconds } from '../utils/options.js';
20
20
  import { renderDryRunPreview } from './export-flow.js';
21
21
  import { autoFixLicenseHeaders, promptExportPatchMetadata, runPatchLint, runSupersedeAndOverlapGates, } from './export-shared.js';
22
+ /**
23
+ * The engine paths an export-all entry owns: its current file plus, for a
24
+ * rename, the path it came from. Spelled once because three call sites had
25
+ * copied the same `filter((value): value is string => !!value)` narrowing.
26
+ * @param entry - Export entry carrying `file` and an optional `originalPath`.
27
+ * @returns Both paths when the entry was renamed, otherwise just `file`.
28
+ */
29
+ function ownedPaths(entry) {
30
+ return [entry.file, entry.originalPath].filter((value) => !!value);
31
+ }
22
32
  async function checkBrandingManagedFiles(paths, config) {
23
33
  const changedFiles = await getWorkingTreeStatus(paths.engine);
24
34
  const brandingManagedFiles = changedFiles
25
- .flatMap((entry) => [entry.file, entry.originalPath].filter((value) => !!value))
35
+ .flatMap((entry) => ownedPaths(entry))
26
36
  .filter((file) => isBrandingManagedPath(file, config.binaryName));
27
37
  if (brandingManagedFiles.length > 0) {
28
38
  throw new GeneralError('Export-all refuses to capture tool-managed branding changes by default.\n\n' +
@@ -52,7 +62,7 @@ async function resolveFurnaceExclusionPolicy(paths, projectRoot, excludeFurnace)
52
62
  const rawStatus = await getWorkingTreeStatus(paths.engine);
53
63
  const changedFiles = await expandUntrackedDirectoryEntries(paths.engine, rawStatus);
54
64
  const furnaceManagedFiles = changedFiles
55
- .flatMap((entry) => [entry.file, entry.originalPath].filter((value) => !!value))
65
+ .flatMap((entry) => ownedPaths(entry))
56
66
  .filter((file) => [...prefixes].some((prefix) => file.startsWith(prefix)));
57
67
  if (furnaceManagedFiles.length === 0)
58
68
  return new Set();
@@ -196,7 +206,7 @@ export async function exportAllCommand(projectRoot, options = {}) {
196
206
  const allChanged = await expandUntrackedDirectoryEntries(paths.engine, rawChanged);
197
207
  const nonFurnacePaths = [
198
208
  ...new Set(allChanged
199
- .flatMap((entry) => [entry.file, entry.originalPath].filter((value) => !!value))
209
+ .flatMap((entry) => ownedPaths(entry))
200
210
  .filter((file) => !furnaceExcluded.has(file))),
201
211
  ].sort();
202
212
  scopedExportPaths = nonFurnacePaths;
@@ -254,7 +264,14 @@ export async function exportAllCommand(projectRoot, options = {}) {
254
264
  const patchQueueCtx = (await pathExists(paths.patches))
255
265
  ? await buildPatchQueueContext(paths.patches)
256
266
  : undefined;
257
- await runPatchLint(paths.engine, filesAffected, diff, config, options.skipLint, patchQueueCtx);
267
+ await runPatchLint({
268
+ engineDir: paths.engine,
269
+ filesAffected,
270
+ diffContent: diff,
271
+ config,
272
+ skipLint: options.skipLint,
273
+ patchQueueCtx,
274
+ });
258
275
  // Dry-run: enumerate filename, metadata, and supersede coverage without
259
276
  // writing. Mirrors `fireforge export --dry-run` so the same preview
260
277
  // surface is available for both targeted and aggregate exports. Runs
@@ -10,14 +10,12 @@
10
10
  import { join } from 'node:path';
11
11
  import {} from '../core/destructive.js';
12
12
  import { detectMovedCodeOverlaps, formatAdoptThenSplitRemedy, } from '../core/export-moved-code-shape.js';
13
- import { normalizePatchArtifact } from '../core/patch-artifact-normalize.js';
14
- import { findAllPatchesForFilesWithDetails, patchNameSlug, planExport, } from '../core/patch-export.js';
15
- import { buildModifiedFileAdditionsFromDiff, buildPatchQueueContext, detectNewFilesInDiff, lintPatchQueue, } from '../core/patch-lint.js';
13
+ import { buildPlanExportInput, findAllPatchesForFilesWithDetails, patchNameSlug, planExport, } from '../core/patch-export.js';
14
+ import { buildPatchQueueContext, lintPatchQueue } from '../core/patch-lint.js';
16
15
  import { withPatchDirectoryLock } from '../core/patch-lock.js';
17
16
  import { addPatchToManifest, loadPatchesManifest, loadPatchesManifestForWrite, renumberPatchesInManifest, resolvePatchIdentifier, savePatchesManifest, } from '../core/patch-manifest.js';
18
- import { requirePatchOrder } from '../core/patch-parse.js';
17
+ import { formatPatchOrder, requirePatchOrder } from '../core/patch-parse.js';
19
18
  import { applyRenameMapToManifest, buildProjectedManifest, enforcePatchPolicy, } from '../core/patch-policy.js';
20
- import { extractNewFileContentFromDiff } from '../core/patch-transform.js';
21
19
  import { GeneralError, InvalidArgumentError } from '../errors/base.js';
22
20
  import { toError } from '../utils/errors.js';
23
21
  import { pathExists, readText, removeFile, writeText } from '../utils/fs.js';
@@ -25,6 +23,8 @@ import { info, warn } from '../utils/logger.js';
25
23
  import { groupProjectedPlacementErrors } from './export-placement-conflicts.js';
26
24
  import { assertPlacementAvoidsReservedRanges } from './export-placement-policy.js';
27
25
  import { findPartialOwnershipOverlap } from './export-shared.js';
26
+ import { projectEntryBody } from './patch/entry-projection.js';
27
+ import { getSortedRenameEntries, renameMapsEqual } from './patch-rename-map.js';
28
28
  function buildFilenameForPlacement(category, name, order, width) {
29
29
  const padded = String(order).padStart(Math.max(3, width), '0');
30
30
  return `${padded}-${category}-${patchNameSlug(name, category)}.patch`;
@@ -35,9 +35,6 @@ function prefixWidthForPatches(manifestPatches, requestedOrder) {
35
35
  return Math.max(width, match?.[1]?.length ?? 3, String(requestedOrder).length);
36
36
  }, 3);
37
37
  }
38
- function getSortedRenameEntries(renameMap) {
39
- return Array.from(renameMap.entries()).sort((a, b) => a[1].newOrder - b[1].newOrder);
40
- }
41
38
  /**
42
39
  * Structural equality for placement plans — used by placement-mode export
43
40
  * and `patch split` to verify the queue did not change between the
@@ -47,20 +44,7 @@ export function placementPlansEqual(left, right) {
47
44
  if (left.insertionOrder !== right.insertionOrder || left.newFilename !== right.newFilename) {
48
45
  return false;
49
46
  }
50
- const leftEntries = getSortedRenameEntries(left.renameMap);
51
- const rightEntries = getSortedRenameEntries(right.renameMap);
52
- if (leftEntries.length !== rightEntries.length) {
53
- return false;
54
- }
55
- return leftEntries.every(([leftFilename, leftEntry], index) => {
56
- const rightTuple = rightEntries[index];
57
- if (!rightTuple)
58
- return false;
59
- const [rightFilename, rightEntry] = rightTuple;
60
- return (leftFilename === rightFilename &&
61
- leftEntry.newFilename === rightEntry.newFilename &&
62
- leftEntry.newOrder === rightEntry.newOrder);
63
- });
47
+ return renameMapsEqual(left.renameMap, right.renameMap);
64
48
  }
65
49
  /**
66
50
  * Computes the shift map that moves existing patches out of the requested
@@ -72,7 +56,7 @@ export function computePlacementPlan(manifestPatches, newPatchCategory, newPatch
72
56
  // Failing fast here prevents a NaN requestedOrder from producing a
73
57
  // filename like "NaN-ui-foo.patch".
74
58
  if (!Number.isInteger(requestedOrder) || requestedOrder <= 0) {
75
- throw new InvalidArgumentError(`computePlacementPlan requires a positive integer order, got ${String(requestedOrder)}.`, 'requestedOrder');
59
+ throw new InvalidArgumentError(`computePlacementPlan requires a positive integer order, got ${requestedOrder}.`, 'requestedOrder');
76
60
  }
77
61
  const sorted = [...manifestPatches].sort((a, b) => a.order - b.order);
78
62
  const renameMap = new Map();
@@ -90,7 +74,7 @@ export function computePlacementPlan(manifestPatches, newPatchCategory, newPatch
90
74
  }
91
75
  const newOrder = cursor + 1;
92
76
  const currentRest = patch.filename.replace(/^\d+-/, '');
93
- const newFilename = `${String(newOrder).padStart(prefixWidth, '0')}-${currentRest}`;
77
+ const newFilename = `${formatPatchOrder(newOrder, prefixWidth)}-${currentRest}`;
94
78
  renameMap.set(patch.filename, { newOrder, newFilename });
95
79
  cursor = newOrder;
96
80
  }
@@ -109,11 +93,11 @@ export function computePlacementPlan(manifestPatches, newPatchCategory, newPatch
109
93
  */
110
94
  export function computeExactPlacementPlan(manifestPatches, newPatchCategory, newPatchName, requestedOrder) {
111
95
  if (!Number.isInteger(requestedOrder) || requestedOrder <= 0) {
112
- throw new InvalidArgumentError(`--order must be a positive integer, got ${String(requestedOrder)}.`, '--order');
96
+ throw new InvalidArgumentError(`--order must be a positive integer, got ${requestedOrder}.`, '--order');
113
97
  }
114
98
  const occupied = manifestPatches.find((patch) => patch.order === requestedOrder);
115
99
  if (occupied) {
116
- throw new InvalidArgumentError(`--order ${String(requestedOrder)} is already occupied by ${occupied.filename}. ` +
100
+ throw new InvalidArgumentError(`--order ${requestedOrder} is already occupied by ${occupied.filename}. ` +
117
101
  'Choose an unused order or use --before/--after for positional insertion.', '--order');
118
102
  }
119
103
  return {
@@ -140,7 +124,7 @@ export async function resolvePlacementPlan(patchesDir, options, category, name,
140
124
  // function is called directly from the command body which could
141
125
  // reach here with a NaN/0/negative value passed in via test harness.
142
126
  if (!Number.isInteger(options.order) || options.order <= 0) {
143
- throw new InvalidArgumentError(`--order must be a positive integer, got ${String(options.order)}.`, '--order');
127
+ throw new InvalidArgumentError(`--order must be a positive integer, got ${options.order}.`, '--order');
144
128
  }
145
129
  return computeExactPlacementPlan(existingPatches, category, name, options.order);
146
130
  }
@@ -166,21 +150,6 @@ export async function resolvePlacementPlan(patchesDir, options, category, name,
166
150
  assertPlacementAvoidsReservedRanges(plan, existingPatches, config);
167
151
  return plan;
168
152
  }
169
- /**
170
- * Extracts the newly-created files a diff would produce and builds the
171
- * `newFiles` map in the shape expected by {@link PatchQueueEntry}. Used
172
- * to build a faithful synthetic entry for the pending patch when
173
- * projecting through cross-patch lint — without this the forward-import
174
- * rule cannot see imports authored by the new patch itself.
175
- */
176
- function buildNewFilesFromDiff(diff) {
177
- const newFiles = new Map();
178
- const newFilePaths = detectNewFilesInDiff(diff);
179
- for (const path of newFilePaths) {
180
- newFiles.set(path, extractNewFileContentFromDiff(diff, path));
181
- }
182
- return newFiles;
183
- }
184
153
  /**
185
154
  * Projects the placement through cross-patch lint to detect forward-imports
186
155
  * the renumber would introduce *or* that the new patch itself would
@@ -205,9 +174,7 @@ export async function projectPlacementForLint(patchesDir, plan, diff, config) {
205
174
  filename: plan.newFilename,
206
175
  order: plan.insertionOrder,
207
176
  metadata: null,
208
- diff,
209
- newFiles: buildNewFilesFromDiff(diff),
210
- modifiedFileAdditions: buildModifiedFileAdditionsFromDiff(diff),
177
+ ...projectEntryBody(diff),
211
178
  });
212
179
  projectedEntries.sort((a, b) => a.order - b.order || a.filename.localeCompare(b.filename));
213
180
  const policyRest = baseCtx.patchPolicy ? { patchPolicy: baseCtx.patchPolicy } : {};
@@ -309,7 +276,7 @@ export async function commitPlacementExport(input) {
309
276
  }
310
277
  // Normalize identically to commitExportedPatch — the two export
311
278
  // paths must produce one artifact contract for the same diff.
312
- await writeText(patchPath, normalizePatchArtifact(input.diff));
279
+ await writeText(patchPath, input.diff);
313
280
  await addPatchToManifest(input.patchesDir, {
314
281
  ...input.metadata,
315
282
  filename: currentPlan.newFilename,
@@ -395,19 +362,7 @@ export async function renderDryRunPreview(input) {
395
362
  const overlap = manifest !== null
396
363
  ? findPartialOwnershipOverlap(manifest, input.filesAffected, supersedingFilenames)
397
364
  : new Map();
398
- const plan = await planExport({
399
- patchesDir: input.patchesDir,
400
- category: input.category,
401
- name: input.name,
402
- description: input.description,
403
- filesAffected: input.filesAffected,
404
- sourceEsrVersion: input.sourceEsrVersion,
405
- ...(input.sourceProduct !== undefined ? { sourceProduct: input.sourceProduct } : {}),
406
- ...(input.sourceVersion !== undefined ? { sourceVersion: input.sourceVersion } : {}),
407
- ...(input.tier !== undefined ? { tier: input.tier } : {}),
408
- ...(input.lintIgnore !== undefined ? { lintIgnore: input.lintIgnore } : {}),
409
- ...(input.config !== undefined ? { config: input.config } : {}),
410
- });
365
+ const plan = await planExport(buildPlanExportInput(input));
411
366
  if (input.config !== undefined) {
412
367
  enforcePatchPolicy({
413
368
  config: input.config,
@@ -441,7 +396,7 @@ export async function renderDryRunPreview(input) {
441
396
  }
442
397
  if (overlap.size > 0) {
443
398
  const entries = [...overlap.entries()].sort(([a], [b]) => a.localeCompare(b));
444
- warn(`\n[dry-run] Would create cross-patch ownership overlap on ${String(entries.length)} file${entries.length === 1 ? '' : 's'}:`);
399
+ warn(`\n[dry-run] Would create cross-patch ownership overlap on ${entries.length} file${entries.length === 1 ? '' : 's'}:`);
445
400
  for (const [file, owners] of entries) {
446
401
  warn(` - ${file} already claimed by: ${owners.join(', ')}`);
447
402
  }
@@ -66,10 +66,10 @@ export function groupProjectedPlacementErrors(projectedErrors, baselineErrors, p
66
66
  details.push(` ${formatPatchLintIssue(issue)}`);
67
67
  }
68
68
  };
69
- pushGroup(`errors in the exported patch content (${String(exported.length)}):`, exported);
69
+ pushGroup(`errors in the exported patch content (${exported.length}):`, exported);
70
70
  pushGroup(`consequences of renumbering existing patches to make room at ordinal ` +
71
- `${String(plan.insertionOrder)} (${String(renumbering.length)}) — these come from ` +
71
+ `${plan.insertionOrder} (${renumbering.length}) — these come from ` +
72
72
  'renumbering, NOT from the exported content:', renumbering);
73
- pushGroup(`errors already present in the queue before this export (${String(preExisting.length)}):`, preExisting);
73
+ pushGroup(`errors already present in the queue before this export (${preExisting.length}):`, preExisting);
74
74
  return details;
75
75
  }
@@ -10,7 +10,7 @@ import { loadPatchesManifest } from '../core/patch-manifest.js';
10
10
  import { applyRenameMapToManifest, buildProjectedManifest, enforcePatchPolicy, } from '../core/patch-policy.js';
11
11
  import { buildPatchSourceMetadata } from '../core/patch-source-metadata.js';
12
12
  import { InvalidArgumentError } from '../errors/base.js';
13
- import { outro } from '../utils/logger.js';
13
+ import { proceedAfterDecision } from './destructive-decision.js';
14
14
  import { placementSummary, projectPlacementForLint, resolvePlacementPlan, } from './export-flow.js';
15
15
  /**
16
16
  * Spreadable optional metadata (`tier`, `lintIgnore`) derived from the
@@ -85,14 +85,8 @@ export async function gatePlacementPlan(args) {
85
85
  unsafeOverride: options.forceUnsafe === true,
86
86
  conflicts,
87
87
  });
88
- if (decision === 'dry-run') {
89
- outro('Dry run complete — no changes made');
88
+ if (!proceedAfterDecision(decision, 'Export cancelled'))
90
89
  return 'stop';
91
- }
92
- if (decision === 'declined') {
93
- outro('Export cancelled');
94
- return 'stop';
95
- }
96
90
  }
97
91
  else if (conflicts && options.forceUnsafe !== true) {
98
92
  s.stop();