@hominis/fireforge 0.34.2 → 0.36.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 (360) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +43 -5
  3. package/dist/bin/fireforge.d.ts +4 -0
  4. package/dist/bin/fireforge.js +25 -6
  5. package/dist/src/cli.js +22 -5
  6. package/dist/src/commands/bootstrap-checks.js +0 -1
  7. package/dist/src/commands/bootstrap.d.ts +2 -1
  8. package/dist/src/commands/bootstrap.js +16 -7
  9. package/dist/src/commands/build.js +17 -3
  10. package/dist/src/commands/config.js +30 -10
  11. package/dist/src/commands/discard.js +24 -5
  12. package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
  13. package/dist/src/commands/doctor-check-core.js +0 -1
  14. package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
  15. package/dist/src/commands/doctor-external-toolchains.js +54 -0
  16. package/dist/src/commands/doctor-furnace-jar.js +0 -1
  17. package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
  18. package/dist/src/commands/doctor-furnace.js +2 -2
  19. package/dist/src/commands/doctor-working-tree.js +0 -1
  20. package/dist/src/commands/doctor.js +4 -1
  21. package/dist/src/commands/download.js +51 -15
  22. package/dist/src/commands/export-all.js +5 -3
  23. package/dist/src/commands/export-flow.js +12 -7
  24. package/dist/src/commands/export-placement-gate.js +0 -1
  25. package/dist/src/commands/export-placement-policy.js +0 -1
  26. package/dist/src/commands/export-shared.d.ts +5 -1
  27. package/dist/src/commands/export-shared.js +18 -6
  28. package/dist/src/commands/export.js +15 -10
  29. package/dist/src/commands/furnace/apply.js +22 -6
  30. package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
  31. package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
  32. package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
  33. package/dist/src/commands/furnace/chrome-doc.js +0 -1
  34. package/dist/src/commands/furnace/create-browser-test.js +0 -1
  35. package/dist/src/commands/furnace/create-dry-run.js +0 -1
  36. package/dist/src/commands/furnace/create-features.js +0 -1
  37. package/dist/src/commands/furnace/create-mochikit.js +0 -1
  38. package/dist/src/commands/furnace/create-readback.js +0 -1
  39. package/dist/src/commands/furnace/create-templates.js +0 -1
  40. package/dist/src/commands/furnace/create-validation.js +0 -1
  41. package/dist/src/commands/furnace/create-xpcshell.js +0 -1
  42. package/dist/src/commands/furnace/create.js +0 -1
  43. package/dist/src/commands/furnace/deploy.js +7 -83
  44. package/dist/src/commands/furnace/diff.js +0 -1
  45. package/dist/src/commands/furnace/index.js +59 -25
  46. package/dist/src/commands/furnace/init.js +0 -1
  47. package/dist/src/commands/furnace/list.js +0 -1
  48. package/dist/src/commands/furnace/override.js +0 -1
  49. package/dist/src/commands/furnace/preview.js +3 -3
  50. package/dist/src/commands/furnace/refresh.js +0 -1
  51. package/dist/src/commands/furnace/remove-state.d.ts +15 -0
  52. package/dist/src/commands/furnace/remove-state.js +47 -1
  53. package/dist/src/commands/furnace/remove.js +3 -9
  54. package/dist/src/commands/furnace/rename-browser-test.js +1 -5
  55. package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
  56. package/dist/src/commands/furnace/rename-helpers.js +30 -1
  57. package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
  58. package/dist/src/commands/furnace/rename.js +15 -36
  59. package/dist/src/commands/furnace/scan.js +0 -1
  60. package/dist/src/commands/furnace/status.js +0 -1
  61. package/dist/src/commands/furnace/sync.js +76 -4
  62. package/dist/src/commands/furnace/validate.js +0 -1
  63. package/dist/src/commands/furnace/validation-output.js +0 -1
  64. package/dist/src/commands/import.js +35 -9
  65. package/dist/src/commands/lint-per-patch.js +19 -7
  66. package/dist/src/commands/lint.js +13 -3
  67. package/dist/src/commands/manifest.js +1 -2
  68. package/dist/src/commands/package.js +2 -1
  69. package/dist/src/commands/patch/compact.js +1 -1
  70. package/dist/src/commands/patch/delete.js +1 -1
  71. package/dist/src/commands/patch/index.js +1 -1
  72. package/dist/src/commands/patch/lint-ignore.js +21 -6
  73. package/dist/src/commands/patch/move-files.js +1 -1
  74. package/dist/src/commands/patch/patch-context.js +0 -1
  75. package/dist/src/commands/patch/rename.js +4 -4
  76. package/dist/src/commands/patch/reorder.js +3 -4
  77. package/dist/src/commands/patch/split-plan.js +2 -1
  78. package/dist/src/commands/patch/split.js +4 -4
  79. package/dist/src/commands/patch/staged-dependency.js +1 -1
  80. package/dist/src/commands/patch/tier.js +18 -5
  81. package/dist/src/commands/re-export-bulk-scan.js +0 -1
  82. package/dist/src/commands/re-export-files.js +1 -1
  83. package/dist/src/commands/re-export-options.js +0 -1
  84. package/dist/src/commands/re-export-scan.js +0 -1
  85. package/dist/src/commands/re-export.js +6 -4
  86. package/dist/src/commands/rebase/abort.js +2 -9
  87. package/dist/src/commands/rebase/confirm.js +0 -1
  88. package/dist/src/commands/rebase/conflict-summary.js +0 -1
  89. package/dist/src/commands/rebase/continue.js +0 -1
  90. package/dist/src/commands/rebase/index.d.ts +1 -1
  91. package/dist/src/commands/rebase/index.js +10 -15
  92. package/dist/src/commands/rebase/patch-loop.js +38 -7
  93. package/dist/src/commands/rebase/summary.js +2 -3
  94. package/dist/src/commands/rebase.js +0 -1
  95. package/dist/src/commands/register.js +1 -1
  96. package/dist/src/commands/reset.js +16 -9
  97. package/dist/src/commands/resolve.js +2 -2
  98. package/dist/src/commands/run.js +18 -6
  99. package/dist/src/commands/setup-support.js +0 -1
  100. package/dist/src/commands/setup.js +1 -2
  101. package/dist/src/commands/source.js +1 -2
  102. package/dist/src/commands/status-output.js +1 -12
  103. package/dist/src/commands/status.js +147 -133
  104. package/dist/src/commands/test-appdir.js +0 -1
  105. package/dist/src/commands/test-diagnose.js +18 -2
  106. package/dist/src/commands/test-modes.d.ts +15 -0
  107. package/dist/src/commands/test-modes.js +61 -0
  108. package/dist/src/commands/test-register.d.ts +4 -0
  109. package/dist/src/commands/test-register.js +64 -0
  110. package/dist/src/commands/test-run.d.ts +23 -7
  111. package/dist/src/commands/test-run.js +15 -14
  112. package/dist/src/commands/test.d.ts +0 -4
  113. package/dist/src/commands/test.js +95 -56
  114. package/dist/src/commands/token-coverage.js +0 -1
  115. package/dist/src/commands/token.js +1 -2
  116. package/dist/src/commands/typecheck.js +1 -1
  117. package/dist/src/commands/verify.js +3 -9
  118. package/dist/src/commands/watch.js +5 -1
  119. package/dist/src/commands/wire.js +1 -1
  120. package/dist/src/core/ast-utils.js +0 -1
  121. package/dist/src/core/brand-validation.js +0 -1
  122. package/dist/src/core/branding.js +0 -1
  123. package/dist/src/core/browser-wire.js +0 -1
  124. package/dist/src/core/build-audit-platform.js +0 -1
  125. package/dist/src/core/build-audit-registration.js +0 -1
  126. package/dist/src/core/build-audit-resolve.js +0 -1
  127. package/dist/src/core/build-audit-transforms.js +0 -1
  128. package/dist/src/core/build-audit.js +0 -1
  129. package/dist/src/core/build-baseline-types.js +0 -1
  130. package/dist/src/core/build-baseline.js +0 -1
  131. package/dist/src/core/build-prepare.js +21 -7
  132. package/dist/src/core/config-mutate.js +0 -1
  133. package/dist/src/core/config-paths.d.ts +2 -2
  134. package/dist/src/core/config-paths.js +6 -1
  135. package/dist/src/core/config-state.js +0 -1
  136. package/dist/src/core/config-validate-patch-policy.js +0 -1
  137. package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
  138. package/dist/src/core/config-validate-test-toolchains.js +105 -0
  139. package/dist/src/core/config-validate.js +3 -1
  140. package/dist/src/core/config.js +0 -1
  141. package/dist/src/core/destructive.js +0 -1
  142. package/dist/src/core/diff-hunks.js +13 -7
  143. package/dist/src/core/engine-changes.js +0 -1
  144. package/dist/src/core/engine-session-lock.d.ts +12 -0
  145. package/dist/src/core/engine-session-lock.js +45 -0
  146. package/dist/src/core/file-lock.d.ts +8 -0
  147. package/dist/src/core/file-lock.js +157 -48
  148. package/dist/src/core/firefox-archive.d.ts +4 -0
  149. package/dist/src/core/firefox-archive.js +6 -1
  150. package/dist/src/core/firefox-cache.js +67 -8
  151. package/dist/src/core/firefox-download.js +0 -1
  152. package/dist/src/core/firefox-extract.d.ts +21 -1
  153. package/dist/src/core/firefox-extract.js +178 -9
  154. package/dist/src/core/firefox-ignorefile.js +0 -1
  155. package/dist/src/core/firefox.d.ts +12 -0
  156. package/dist/src/core/firefox.js +64 -2
  157. package/dist/src/core/furnace-apply-dry-run.js +0 -1
  158. package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
  159. package/dist/src/core/furnace-apply-ftl.js +10 -5
  160. package/dist/src/core/furnace-apply-helpers.js +0 -1
  161. package/dist/src/core/furnace-apply-output.js +2 -2
  162. package/dist/src/core/furnace-apply.d.ts +7 -1
  163. package/dist/src/core/furnace-apply.js +47 -13
  164. package/dist/src/core/furnace-checksum-utils.js +0 -1
  165. package/dist/src/core/furnace-config-array-utils.js +0 -1
  166. package/dist/src/core/furnace-config-custom.js +0 -1
  167. package/dist/src/core/furnace-config-order.js +0 -1
  168. package/dist/src/core/furnace-config-tokens.js +0 -1
  169. package/dist/src/core/furnace-config-validate.js +0 -1
  170. package/dist/src/core/furnace-config.d.ts +11 -0
  171. package/dist/src/core/furnace-config.js +37 -1
  172. package/dist/src/core/furnace-constants.js +0 -1
  173. package/dist/src/core/furnace-css-fragments.js +0 -1
  174. package/dist/src/core/furnace-graph-utils.js +0 -1
  175. package/dist/src/core/furnace-jsconfig.d.ts +2 -3
  176. package/dist/src/core/furnace-jsconfig.js +25 -18
  177. package/dist/src/core/furnace-marker.js +0 -1
  178. package/dist/src/core/furnace-operation.js +0 -1
  179. package/dist/src/core/furnace-refresh.js +0 -1
  180. package/dist/src/core/furnace-registration-ast.d.ts +14 -0
  181. package/dist/src/core/furnace-registration-ast.js +1 -2
  182. package/dist/src/core/furnace-registration-remove.js +0 -1
  183. package/dist/src/core/furnace-registration-validate.js +1 -5
  184. package/dist/src/core/furnace-registration.js +0 -1
  185. package/dist/src/core/furnace-rollback.js +0 -1
  186. package/dist/src/core/furnace-scanner.js +0 -1
  187. package/dist/src/core/furnace-staleness.js +0 -1
  188. package/dist/src/core/furnace-state-persist.d.ts +72 -0
  189. package/dist/src/core/furnace-state-persist.js +118 -0
  190. package/dist/src/core/furnace-step-errors.d.ts +23 -0
  191. package/dist/src/core/furnace-step-errors.js +25 -0
  192. package/dist/src/core/furnace-stories.js +0 -1
  193. package/dist/src/core/furnace-validate-accessibility.js +0 -1
  194. package/dist/src/core/furnace-validate-checks.js +0 -1
  195. package/dist/src/core/furnace-validate-compatibility.js +0 -1
  196. package/dist/src/core/furnace-validate-helpers.js +0 -1
  197. package/dist/src/core/furnace-validate-registration.js +7 -7
  198. package/dist/src/core/furnace-validate-structure.d.ts +8 -0
  199. package/dist/src/core/furnace-validate-structure.js +11 -2
  200. package/dist/src/core/furnace-validate.js +0 -1
  201. package/dist/src/core/furnace-version-drift.d.ts +6 -3
  202. package/dist/src/core/furnace-version-drift.js +6 -4
  203. package/dist/src/core/git-base.js +0 -1
  204. package/dist/src/core/git-diff.js +0 -1
  205. package/dist/src/core/git-file-ops.js +0 -1
  206. package/dist/src/core/git-status.d.ts +13 -0
  207. package/dist/src/core/git-status.js +32 -2
  208. package/dist/src/core/git.d.ts +10 -1
  209. package/dist/src/core/git.js +65 -8
  210. package/dist/src/core/license-headers.d.ts +16 -0
  211. package/dist/src/core/license-headers.js +19 -1
  212. package/dist/src/core/lint-cache.js +0 -1
  213. package/dist/src/core/lint-projection.js +0 -1
  214. package/dist/src/core/mach-build-artifacts.js +0 -1
  215. package/dist/src/core/mach-error-hints.js +27 -1
  216. package/dist/src/core/mach-mozconfig.js +0 -1
  217. package/dist/src/core/mach-python.js +0 -1
  218. package/dist/src/core/mach-resource-shim.js +0 -1
  219. package/dist/src/core/mach.js +0 -1
  220. package/dist/src/core/manifest-helpers.js +0 -1
  221. package/dist/src/core/manifest-register.js +0 -1
  222. package/dist/src/core/manifest-rules.js +0 -1
  223. package/dist/src/core/manifest-tokenizers.js +0 -1
  224. package/dist/src/core/marionette-port.d.ts +8 -0
  225. package/dist/src/core/marionette-port.js +34 -3
  226. package/dist/src/core/marionette-preflight.js +0 -1
  227. package/dist/src/core/markdown-table.js +0 -1
  228. package/dist/src/core/ownership-table.js +2 -9
  229. package/dist/src/core/parser-fallback.js +0 -1
  230. package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
  231. package/dist/src/core/patch-apply-fuzz.js +71 -34
  232. package/dist/src/core/patch-apply.d.ts +30 -0
  233. package/dist/src/core/patch-apply.js +152 -39
  234. package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
  235. package/dist/src/core/patch-artifact-normalize.js +19 -15
  236. package/dist/src/core/patch-export-coverage.js +0 -1
  237. package/dist/src/core/patch-export-metadata.js +0 -1
  238. package/dist/src/core/patch-export-update.js +2 -3
  239. package/dist/src/core/patch-export.d.ts +2 -6
  240. package/dist/src/core/patch-export.js +24 -48
  241. package/dist/src/core/patch-files.js +0 -1
  242. package/dist/src/core/patch-identifier-suggest.js +0 -1
  243. package/dist/src/core/patch-lint-checkjs.js +0 -1
  244. package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
  245. package/dist/src/core/patch-lint-cross.js +0 -1
  246. package/dist/src/core/patch-lint-css.js +0 -1
  247. package/dist/src/core/patch-lint-diff-tag.js +0 -1
  248. package/dist/src/core/patch-lint-diff.d.ts +2 -1
  249. package/dist/src/core/patch-lint-diff.js +17 -32
  250. package/dist/src/core/patch-lint-imports.js +0 -1
  251. package/dist/src/core/patch-lint-jsdoc.js +0 -1
  252. package/dist/src/core/patch-lint-observer.js +0 -1
  253. package/dist/src/core/patch-lint-ownership.js +0 -1
  254. package/dist/src/core/patch-lint-reexports.js +0 -1
  255. package/dist/src/core/patch-lint.d.ts +37 -2
  256. package/dist/src/core/patch-lint.js +48 -24
  257. package/dist/src/core/patch-lock.js +0 -1
  258. package/dist/src/core/patch-manifest-consistency.js +9 -1
  259. package/dist/src/core/patch-manifest-io.d.ts +16 -0
  260. package/dist/src/core/patch-manifest-io.js +37 -12
  261. package/dist/src/core/patch-manifest-query.d.ts +6 -0
  262. package/dist/src/core/patch-manifest-query.js +27 -12
  263. package/dist/src/core/patch-manifest-resolve.js +0 -1
  264. package/dist/src/core/patch-manifest-validate.js +0 -1
  265. package/dist/src/core/patch-manifest.d.ts +1 -1
  266. package/dist/src/core/patch-manifest.js +1 -2
  267. package/dist/src/core/patch-parse.d.ts +59 -0
  268. package/dist/src/core/patch-parse.js +209 -92
  269. package/dist/src/core/patch-policy.js +0 -1
  270. package/dist/src/core/patch-registration-refs.js +16 -26
  271. package/dist/src/core/patch-source-metadata.js +0 -1
  272. package/dist/src/core/patch-transform.d.ts +4 -0
  273. package/dist/src/core/patch-transform.js +30 -38
  274. package/dist/src/core/rebase-session.js +0 -1
  275. package/dist/src/core/register-browser-content.js +0 -1
  276. package/dist/src/core/register-module.js +0 -1
  277. package/dist/src/core/register-result.js +0 -1
  278. package/dist/src/core/register-scaffold.js +0 -1
  279. package/dist/src/core/register-shared-css.js +0 -1
  280. package/dist/src/core/register-test-manifest.js +0 -1
  281. package/dist/src/core/register-xpcshell-test.js +0 -1
  282. package/dist/src/core/shared-ftl.js +0 -1
  283. package/dist/src/core/signal-critical.js +0 -1
  284. package/dist/src/core/smoke-patterns.js +0 -1
  285. package/dist/src/core/state-file.js +0 -1
  286. package/dist/src/core/status-classify.d.ts +16 -0
  287. package/dist/src/core/status-classify.js +24 -16
  288. package/dist/src/core/test-harness-crash.d.ts +64 -7
  289. package/dist/src/core/test-harness-crash.js +196 -15
  290. package/dist/src/core/test-harness-output.js +0 -1
  291. package/dist/src/core/test-path-scope.d.ts +62 -0
  292. package/dist/src/core/test-path-scope.js +148 -0
  293. package/dist/src/core/test-stale-check.js +0 -1
  294. package/dist/src/core/test-stale-symlink.js +0 -1
  295. package/dist/src/core/test-xpcshell-retry.js +0 -1
  296. package/dist/src/core/token-coverage.js +0 -1
  297. package/dist/src/core/token-dark-mode.js +0 -1
  298. package/dist/src/core/token-docs.js +0 -1
  299. package/dist/src/core/token-manager.js +0 -1
  300. package/dist/src/core/token-scaffold.js +0 -1
  301. package/dist/src/core/token-variant.js +0 -1
  302. package/dist/src/core/toolchain-preflight.d.ts +85 -0
  303. package/dist/src/core/toolchain-preflight.js +256 -0
  304. package/dist/src/core/typecheck-shim.d.ts +4 -1
  305. package/dist/src/core/typecheck-shim.js +61 -18
  306. package/dist/src/core/typecheck.js +0 -1
  307. package/dist/src/core/wire-destroy.js +0 -1
  308. package/dist/src/core/wire-dom-fragment.js +0 -1
  309. package/dist/src/core/wire-init.js +0 -1
  310. package/dist/src/core/wire-subscript.js +0 -1
  311. package/dist/src/core/wire-targets.js +0 -1
  312. package/dist/src/core/wire-utils.js +0 -1
  313. package/dist/src/core/xpcshell-appdir.js +1 -4
  314. package/dist/src/errors/base.d.ts +29 -0
  315. package/dist/src/errors/base.js +35 -1
  316. package/dist/src/errors/build.js +0 -1
  317. package/dist/src/errors/codes.d.ts +6 -0
  318. package/dist/src/errors/codes.js +6 -1
  319. package/dist/src/errors/config.js +0 -1
  320. package/dist/src/errors/download.js +2 -1
  321. package/dist/src/errors/furnace.d.ts +11 -1
  322. package/dist/src/errors/furnace.js +6 -5
  323. package/dist/src/errors/git.js +0 -1
  324. package/dist/src/errors/patch.d.ts +17 -0
  325. package/dist/src/errors/patch.js +29 -1
  326. package/dist/src/errors/rebase.js +0 -1
  327. package/dist/src/errors/run.js +1 -1
  328. package/dist/src/index.js +0 -1
  329. package/dist/src/types/cli.js +0 -1
  330. package/dist/src/types/commands/index.js +0 -1
  331. package/dist/src/types/commands/options.d.ts +20 -5
  332. package/dist/src/types/commands/options.js +0 -1
  333. package/dist/src/types/commands/patches.d.ts +8 -0
  334. package/dist/src/types/commands/patches.js +0 -1
  335. package/dist/src/types/commands/project.d.ts +11 -2
  336. package/dist/src/types/commands/project.js +0 -1
  337. package/dist/src/types/config.d.ts +32 -0
  338. package/dist/src/types/config.js +0 -1
  339. package/dist/src/types/furnace.d.ts +10 -0
  340. package/dist/src/types/furnace.js +0 -1
  341. package/dist/src/types/index.js +0 -1
  342. package/dist/src/types/typecheck.js +0 -1
  343. package/dist/src/utils/elapsed.js +0 -1
  344. package/dist/src/utils/errors.js +0 -1
  345. package/dist/src/utils/fs.js +37 -5
  346. package/dist/src/utils/logger.d.ts +7 -0
  347. package/dist/src/utils/logger.js +85 -3
  348. package/dist/src/utils/options.d.ts +15 -0
  349. package/dist/src/utils/options.js +25 -1
  350. package/dist/src/utils/package-root.js +11 -4
  351. package/dist/src/utils/parse.js +0 -1
  352. package/dist/src/utils/paths.js +0 -1
  353. package/dist/src/utils/platform.js +0 -1
  354. package/dist/src/utils/process.d.ts +33 -4
  355. package/dist/src/utils/process.js +183 -28
  356. package/dist/src/utils/regex.d.ts +13 -3
  357. package/dist/src/utils/regex.js +53 -4
  358. package/dist/src/utils/validation.d.ts +0 -12
  359. package/dist/src/utils/validation.js +13 -26
  360. package/package.json +8 -7
@@ -12,8 +12,9 @@ import { rename } from 'node:fs/promises';
12
12
  import { join } from 'node:path';
13
13
  import { FireForgeError } from '../errors/base.js';
14
14
  import { ExitCode } from '../errors/codes.js';
15
+ import { PatchManifestCorruptError } from '../errors/patch.js';
15
16
  import { toError } from '../utils/errors.js';
16
- import { pathExists, readJson, removeFile, writeJson } from '../utils/fs.js';
17
+ import { pathExistsStrict, readJson, removeFile, writeJson } from '../utils/fs.js';
17
18
  import { warn } from '../utils/logger.js';
18
19
  import { isArray, isObject } from '../utils/validation.js';
19
20
  import { validatePatchesManifest } from './patch-manifest-validate.js';
@@ -25,7 +26,7 @@ export const PATCHES_MANIFEST = 'patches.json';
25
26
  */
26
27
  export async function loadPatchesManifestState(patchesDir) {
27
28
  const manifestPath = join(patchesDir, PATCHES_MANIFEST);
28
- if (!(await pathExists(manifestPath))) {
29
+ if (!(await pathExistsStrict(manifestPath))) {
29
30
  return { exists: false, manifest: null, parseError: undefined };
30
31
  }
31
32
  try {
@@ -46,6 +47,13 @@ export async function loadPatchesManifestState(patchesDir) {
46
47
  }
47
48
  /**
48
49
  * Loads the patches manifest if it exists.
50
+ *
51
+ * READ-ONLY callers only: this collapses "absent" and "corrupt" into null.
52
+ * Any code path that will WRITE the manifest (or delete patch files based
53
+ * on its content) must use {@link loadPatchesManifestForWrite} instead —
54
+ * treating a corrupt manifest as empty and then saving destroys every
55
+ * existing patch's metadata.
56
+ *
49
57
  * @param patchesDir - Path to the patches directory
50
58
  * @returns PatchesManifest or null if not found
51
59
  */
@@ -53,6 +61,21 @@ export async function loadPatchesManifest(patchesDir) {
53
61
  const state = await loadPatchesManifestState(patchesDir);
54
62
  return state.manifest;
55
63
  }
64
+ /**
65
+ * Loads the patches manifest for a mutating operation.
66
+ *
67
+ * Returns null only when the manifest genuinely does not exist; a manifest
68
+ * that exists but fails to parse/validate throws
69
+ * {@link PatchManifestCorruptError} so the caller aborts instead of
70
+ * rebuilding an empty queue over the top of the corrupt file.
71
+ */
72
+ export async function loadPatchesManifestForWrite(patchesDir) {
73
+ const state = await loadPatchesManifestState(patchesDir);
74
+ if (state.exists && state.manifest === null) {
75
+ throw new PatchManifestCorruptError(join(patchesDir, PATCHES_MANIFEST), state.parseError);
76
+ }
77
+ return state.manifest;
78
+ }
56
79
  /**
57
80
  * Saves the patches manifest.
58
81
  * @param patchesDir - Path to the patches directory
@@ -70,7 +93,7 @@ export async function savePatchesManifest(patchesDir, manifest) {
70
93
  */
71
94
  export async function mutatePatchRowsInManifest(patchesDir, filenames, mutator) {
72
95
  const manifestPath = join(patchesDir, PATCHES_MANIFEST);
73
- if (!(await pathExists(manifestPath)))
96
+ if (!(await pathExistsStrict(manifestPath)))
74
97
  return null;
75
98
  const rawManifest = await readJson(manifestPath);
76
99
  const beforeManifest = validatePatchesManifest(rawManifest);
@@ -125,7 +148,8 @@ export async function mutatePatchRowsInManifest(patchesDir, filenames, mutator)
125
148
  * @param removeFilenames - Optional filenames to remove in the same read-modify-write cycle
126
149
  */
127
150
  export async function addPatchToManifest(patchesDir, metadata, removeFilenames) {
128
- const manifest = (await loadPatchesManifest(patchesDir)) ?? {
151
+ // ForWrite: a corrupt manifest must abort here, not be rebuilt as empty.
152
+ const manifest = (await loadPatchesManifestForWrite(patchesDir)) ?? {
129
153
  version: 1,
130
154
  patches: [],
131
155
  };
@@ -156,7 +180,7 @@ export async function addPatchToManifest(patchesDir, metadata, removeFilenames)
156
180
  * false when no matching entry existed
157
181
  */
158
182
  export async function removePatchFromManifest(patchesDir, filename) {
159
- const manifest = await loadPatchesManifest(patchesDir);
183
+ const manifest = await loadPatchesManifestForWrite(patchesDir);
160
184
  if (!manifest)
161
185
  return false;
162
186
  const originalLength = manifest.patches.length;
@@ -240,7 +264,7 @@ export function rewriteStagedDependencyOwners(patch, renameLookup) {
240
264
  export async function renumberPatchesInManifest(patchesDir, renameMap) {
241
265
  if (renameMap.size === 0)
242
266
  return;
243
- const manifest = await loadPatchesManifest(patchesDir);
267
+ const manifest = await loadPatchesManifestForWrite(patchesDir);
244
268
  if (!manifest) {
245
269
  throw new Error('Cannot renumber patches: patches.json is missing.');
246
270
  }
@@ -252,7 +276,7 @@ export async function renumberPatchesInManifest(patchesDir, renameMap) {
252
276
  try {
253
277
  for (const [oldFilename, entry] of renameMap) {
254
278
  const oldPath = join(patchesDir, oldFilename);
255
- if (!(await pathExists(oldPath))) {
279
+ if (!(await pathExistsStrict(oldPath))) {
256
280
  throw new Error(`Cannot renumber: patch file is missing on disk: ${oldFilename}`);
257
281
  }
258
282
  const stagedName = `.fireforge-renumber-${stagingId}-${oldFilename}`;
@@ -280,7 +304,7 @@ export async function renumberPatchesInManifest(patchesDir, renameMap) {
280
304
  for (const stagedEntry of stagedRenames) {
281
305
  const { staged, toEntry } = stagedEntry;
282
306
  const targetPath = join(patchesDir, toEntry.newFilename);
283
- if (await pathExists(targetPath)) {
307
+ if (await pathExistsStrict(targetPath)) {
284
308
  throw new Error(`Cannot renumber: target patch filename already exists on disk: ${toEntry.newFilename}`);
285
309
  }
286
310
  await rename(join(patchesDir, staged), targetPath);
@@ -291,7 +315,7 @@ export async function renumberPatchesInManifest(patchesDir, renameMap) {
291
315
  // described: manifest rewrote to new filenames while the old
292
316
  // files stayed on disk. If the assert ever fires, the Phase 2
293
317
  // rollback will undo prior moves before re-throwing.
294
- if (!(await pathExists(targetPath))) {
318
+ if (!(await pathExistsStrict(targetPath))) {
295
319
  throw new Error(`Rename postcondition failed: expected ${toEntry.newFilename} to exist after rename, but it was not found on disk.`);
296
320
  }
297
321
  completedFinalRenames.push(stagedEntry);
@@ -425,10 +449,12 @@ export class PatchDeleteRollbackError extends FireForgeError {
425
449
  */
426
450
  export async function removePatchFileAndManifest(patchesDir, filename) {
427
451
  const patchPath = join(patchesDir, filename);
428
- const originalManifest = await loadPatchesManifest(patchesDir);
452
+ // ForWrite: deleting the patch FILE while a corrupt manifest still
453
+ // references it would strand the queue; abort on corruption instead.
454
+ const originalManifest = await loadPatchesManifestForWrite(patchesDir);
429
455
  const removedFromManifest = await removePatchFromManifest(patchesDir, filename);
430
456
  try {
431
- if (await pathExists(patchPath)) {
457
+ if (await pathExistsStrict(patchPath)) {
432
458
  await removeFile(patchPath);
433
459
  }
434
460
  }
@@ -451,4 +477,3 @@ export async function removePatchFileAndManifest(patchesDir, filename) {
451
477
  throw deleteError;
452
478
  }
453
479
  }
454
- //# sourceMappingURL=patch-manifest-io.js.map
@@ -42,6 +42,12 @@ export declare function validatePatchIntegrity(patchesDir: string, engineDir: st
42
42
  /**
43
43
  * Stamps multiple patches with a new source version in a single
44
44
  * manifest read-modify-write cycle.
45
+ *
46
+ * Acquires the shared patch-directory lock for the read-modify-write, so a
47
+ * concurrent export/reorder/metadata update cannot interleave with the stamp.
48
+ * The lock is not reentrant — callers must NOT invoke this while already
49
+ * holding {@link withPatchDirectoryLock} on the same patches directory.
50
+ *
45
51
  * @param patchesDir - Path to the patches directory
46
52
  * @param filenames - Patch filenames to update
47
53
  * @param newVersion - Version string to set (e.g. "140.9.0esr")
@@ -3,8 +3,9 @@
3
3
  * Query helpers: finding patches by file, integrity checks, version compat, stamping.
4
4
  */
5
5
  import { readText } from '../utils/fs.js';
6
- import { fileExistsInHead } from './git-file-ops.js';
6
+ import { listTrackedInHead } from './git-file-ops.js';
7
7
  import { discoverPatches, getAllTargetFilesFromPatch } from './patch-files.js';
8
+ import { withPatchDirectoryLock } from './patch-lock.js';
8
9
  import { loadPatchesManifest, mutatePatchRowsInManifest } from './patch-manifest-io.js';
9
10
  import { isNewFileInPatch } from './patch-parse.js';
10
11
  /**
@@ -78,6 +79,12 @@ export async function findPatchesAffectingFile(patchesDir, filePath) {
78
79
  export async function validatePatchIntegrity(patchesDir, engineDir) {
79
80
  const issues = [];
80
81
  const patches = await discoverPatches(patchesDir);
82
+ // Collect every modification target first, then resolve HEAD existence in
83
+ // ONE batched ls-tree pass (chunked for ARG_MAX). The per-file
84
+ // fileExistsInHead fan-out spawned one git process per (patch × target) on
85
+ // every import — a 56-file branding patch alone cost ~56 spawns before a
86
+ // single patch was applied.
87
+ const modificationTargets = [];
81
88
  for (const patch of patches) {
82
89
  // Check all files in the patch (supports multi-file patches)
83
90
  const patchContent = await readText(patch.path);
@@ -86,14 +93,17 @@ export async function validatePatchIntegrity(patchesDir, engineDir) {
86
93
  // Skip new-file sections — they don't need to exist in HEAD
87
94
  if (isNewFileInPatch(patchContent, targetFile))
88
95
  continue;
89
- const existsInHead = await fileExistsInHead(engineDir, targetFile);
90
- if (!existsInHead) {
91
- issues.push({
92
- filename: patch.filename,
93
- message: `Modification patch for file that doesn't exist in source. Re-export with: fireforge export ${targetFile}`,
94
- targetFile,
95
- });
96
- }
96
+ modificationTargets.push({ filename: patch.filename, targetFile });
97
+ }
98
+ }
99
+ const tracked = await listTrackedInHead(engineDir, modificationTargets.map((t) => t.targetFile));
100
+ for (const { filename, targetFile } of modificationTargets) {
101
+ if (!tracked.has(targetFile)) {
102
+ issues.push({
103
+ filename,
104
+ message: `Modification patch for file that doesn't exist in source. Re-export with: fireforge export ${targetFile}`,
105
+ targetFile,
106
+ });
97
107
  }
98
108
  }
99
109
  return issues;
@@ -101,12 +111,18 @@ export async function validatePatchIntegrity(patchesDir, engineDir) {
101
111
  /**
102
112
  * Stamps multiple patches with a new source version in a single
103
113
  * manifest read-modify-write cycle.
114
+ *
115
+ * Acquires the shared patch-directory lock for the read-modify-write, so a
116
+ * concurrent export/reorder/metadata update cannot interleave with the stamp.
117
+ * The lock is not reentrant — callers must NOT invoke this while already
118
+ * holding {@link withPatchDirectoryLock} on the same patches directory.
119
+ *
104
120
  * @param patchesDir - Path to the patches directory
105
121
  * @param filenames - Patch filenames to update
106
122
  * @param newVersion - Version string to set (e.g. "140.9.0esr")
107
123
  */
108
124
  export async function stampPatchVersions(patchesDir, filenames, newVersion, newProduct) {
109
- await mutatePatchRowsInManifest(patchesDir, filenames, (patch, rawPatch) => {
125
+ await withPatchDirectoryLock(patchesDir, () => mutatePatchRowsInManifest(patchesDir, filenames, (patch, rawPatch) => {
110
126
  if (patch.sourceEsrVersion !== newVersion ||
111
127
  rawPatch['sourceVersion'] !== newVersion ||
112
128
  (newProduct !== undefined && rawPatch['sourceProduct'] !== newProduct)) {
@@ -119,6 +135,5 @@ export async function stampPatchVersions(patchesDir, filenames, newVersion, newP
119
135
  };
120
136
  }
121
137
  return null;
122
- });
138
+ }));
123
139
  }
124
- //# sourceMappingURL=patch-manifest-query.js.map
@@ -36,4 +36,3 @@ export function resolvePatchIdentifier(identifier, patches) {
36
36
  // manifest at export time. See function docstring.
37
37
  return patches.find((p) => p.name === identifier) ?? null;
38
38
  }
39
- //# sourceMappingURL=patch-manifest-resolve.js.map
@@ -144,4 +144,3 @@ export function inferPatchMetadataFromFilename(filename) {
144
144
  }
145
145
  return { category: 'infra', name: filename.replace(/\.patch$/, '') };
146
146
  }
147
- //# sourceMappingURL=patch-manifest-validate.js.map
@@ -5,7 +5,7 @@
5
5
  * is an implementation detail.
6
6
  */
7
7
  export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
8
- export { addPatchToManifest, loadPatchesManifest, mutatePatchRowsInManifest, PATCHES_MANIFEST, type PatchManifestRowMutation, type PatchManifestRowMutationResult, type PatchRenameEntry, removePatchFileAndManifest, renumberPatchesInManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from './patch-manifest-io.js';
8
+ export { addPatchToManifest, loadPatchesManifest, loadPatchesManifestForWrite, mutatePatchRowsInManifest, PATCHES_MANIFEST, type PatchManifestRowMutation, type PatchManifestRowMutationResult, type PatchRenameEntry, removePatchFileAndManifest, renumberPatchesInManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from './patch-manifest-io.js';
9
9
  export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
10
10
  export { resolvePatchIdentifier } from './patch-manifest-resolve.js';
11
11
  export { validatePatchesManifest } from './patch-manifest-validate.js';
@@ -6,8 +6,7 @@
6
6
  * is an implementation detail.
7
7
  */
8
8
  export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
9
- export { addPatchToManifest, loadPatchesManifest, mutatePatchRowsInManifest, PATCHES_MANIFEST, removePatchFileAndManifest, renumberPatchesInManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from './patch-manifest-io.js';
9
+ export { addPatchToManifest, loadPatchesManifest, loadPatchesManifestForWrite, mutatePatchRowsInManifest, PATCHES_MANIFEST, removePatchFileAndManifest, renumberPatchesInManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from './patch-manifest-io.js';
10
10
  export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
11
11
  export { resolvePatchIdentifier } from './patch-manifest-resolve.js';
12
12
  export { validatePatchesManifest } from './patch-manifest-validate.js';
13
- //# sourceMappingURL=patch-manifest.js.map
@@ -1,6 +1,15 @@
1
1
  /**
2
2
  * Pure parsing functions for extracting information from patch files.
3
3
  * All functions are synchronous and operate on string content.
4
+ *
5
+ * This module is the ONE unified-diff walker in the codebase. Before
6
+ * {@link parseDiffSections} existed, six hand-rolled `diff --git` walkers
7
+ * lived across patch-parse, patch-lint-diff, patch-registration-refs, and
8
+ * patch-transform — with divergent CRLF handling (a CRLF-saved patch file
9
+ * silently failed target-file matching and `\ No newline` detection on
10
+ * Windows), no quoted-path support, and greedy path captures that
11
+ * mis-split any path containing ` b/`. New diff-shaped parsing must build
12
+ * on {@link parseDiffSections} rather than re-walking lines.
4
13
  */
5
14
  /**
6
15
  * Extracts the order number from a patch filename.
@@ -9,6 +18,56 @@
9
18
  * @returns Order number, or Infinity if no prefix
10
19
  */
11
20
  export declare function extractOrder(filename: string): number;
21
+ /**
22
+ * A parsed `diff --git` header: both sides' paths, unquoted and with any
23
+ * CRLF residue stripped.
24
+ */
25
+ export interface DiffGitHeader {
26
+ /** Old-side (`a/`) path. */
27
+ sourcePath: string;
28
+ /** New-side (`b/`) path — the file the patch produces. */
29
+ targetPath: string;
30
+ }
31
+ /**
32
+ * Parses a `diff --git` line into its two paths.
33
+ *
34
+ * Handles the quoted form git emits for paths with special characters
35
+ * (`diff --git "a/x y" "b/x y"`), and disambiguates the unquoted form for
36
+ * paths that themselves contain ` b/`: for the overwhelmingly common
37
+ * non-rename case the two paths are identical, so a symmetric split
38
+ * (`a/<p> b/<p>`) is tried first — the previous greedy regex
39
+ * (`a\/.+ b\/(.+)$`) split such lines at the LAST ` b/` and returned a
40
+ * truncated path. Falls back to a non-greedy split for renames.
41
+ *
42
+ * @returns null when the line is not a diff --git header.
43
+ */
44
+ export declare function parseDiffGitHeader(line: string): DiffGitHeader | null;
45
+ /**
46
+ * One `diff --git` section of a unified diff, fully structured.
47
+ */
48
+ export interface DiffSection {
49
+ /** New-side (`b/`) path — the file this section produces. */
50
+ targetPath: string;
51
+ /** Old-side (`a/`) path. */
52
+ sourcePath: string;
53
+ /** True when the section carries a `new file mode` marker. */
54
+ isNewFile: boolean;
55
+ /** True when the section carries a `deleted file mode` marker. */
56
+ isDeletedFile: boolean;
57
+ /**
58
+ * True for binary sections (`GIT binary patch` or `Binary files … differ`).
59
+ * Binary sections have no parseable hunks; text-oriented consumers must
60
+ * refuse them rather than treat base85 lines starting with `+` as content.
61
+ */
62
+ isBinary: boolean;
63
+ /** Parsed hunks, in file order. Empty for binary or metadata-only sections. */
64
+ hunks: ParsedHunk[];
65
+ }
66
+ /**
67
+ * Parses a unified diff into structured per-file sections — the single
68
+ * shared walker every diff consumer builds on.
69
+ */
70
+ export declare function parseDiffSections(diffContent: string): DiffSection[];
12
71
  /**
13
72
  * Checks whether a specific file is a new-file addition within a patch.
14
73
  * For multi-file patches, only inspects the section belonging to targetFile.