@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
@@ -1,10 +1,23 @@
1
1
  /**
2
2
  * Normalizes generated patch files before they are written to disk.
3
3
  *
4
- * Kept as a narrow chokepoint for future artifact-level fixes. It must keep
5
- * unified-diff hunk markers intact, but marker-only blank payload lines should
6
- * not carry extra trailing whitespace (`"+ "` / `"- "` / `" "`), because raw
7
- * repository whitespace checks flag those generated patch artifact lines even
8
- * when the engine diff is clean.
4
+ * Currently the identity function, kept as a narrow chokepoint for future
5
+ * artifact-level fixes so every export path (`commitExportedPatch`,
6
+ * `commitPlacementExport`, `updatePatch`, `patch split`) funnels through
7
+ * one place.
8
+ *
9
+ * History: this used to strip marker lines whose payload was pure
10
+ * whitespace (`/^[ +-]\s+$/` → bare marker) to appease repository
11
+ * trailing-whitespace checks. That corrupted real content — Firefox
12
+ * sources contain whitespace-only lines, so a ` `/`-` line whose payload
13
+ * was e.g. two spaces no longer matched the pristine tree (the freshly
14
+ * exported patch failed `git apply --check`), and a `+` line silently
15
+ * changed what the patch produces, making re-import diverge from the
16
+ * engine state it was exported from (2026-07-05 review, finding M2).
17
+ * The whitespace check itself excludes `patches/*.patch`
18
+ * (scripts/check-worktree-whitespace.mjs), so the trimming had no
19
+ * remaining justification. Patch bodies are now written byte-for-byte as
20
+ * git produced them — including the single-space rendering of blank
21
+ * context lines, which `re-export.integration.test.ts` pins.
9
22
  */
10
23
  export declare function normalizePatchArtifact(content: string): string;
@@ -2,21 +2,25 @@
2
2
  /**
3
3
  * Normalizes generated patch files before they are written to disk.
4
4
  *
5
- * Kept as a narrow chokepoint for future artifact-level fixes. It must keep
6
- * unified-diff hunk markers intact, but marker-only blank payload lines should
7
- * not carry extra trailing whitespace (`"+ "` / `"- "` / `" "`), because raw
8
- * repository whitespace checks flag those generated patch artifact lines even
9
- * when the engine diff is clean.
5
+ * Currently the identity function, kept as a narrow chokepoint for future
6
+ * artifact-level fixes so every export path (`commitExportedPatch`,
7
+ * `commitPlacementExport`, `updatePatch`, `patch split`) funnels through
8
+ * one place.
9
+ *
10
+ * History: this used to strip marker lines whose payload was pure
11
+ * whitespace (`/^[ +-]\s+$/` → bare marker) to appease repository
12
+ * trailing-whitespace checks. That corrupted real content — Firefox
13
+ * sources contain whitespace-only lines, so a ` `/`-` line whose payload
14
+ * was e.g. two spaces no longer matched the pristine tree (the freshly
15
+ * exported patch failed `git apply --check`), and a `+` line silently
16
+ * changed what the patch produces, making re-import diverge from the
17
+ * engine state it was exported from (2026-07-05 review, finding M2).
18
+ * The whitespace check itself excludes `patches/*.patch`
19
+ * (scripts/check-worktree-whitespace.mjs), so the trimming had no
20
+ * remaining justification. Patch bodies are now written byte-for-byte as
21
+ * git produced them — including the single-space rendering of blank
22
+ * context lines, which `re-export.integration.test.ts` pins.
10
23
  */
11
24
  export function normalizePatchArtifact(content) {
12
- return content
13
- .split('\n')
14
- .map((line) => {
15
- if (/^[ +-]\s+$/.test(line)) {
16
- return line[0] ?? line;
17
- }
18
- return line;
19
- })
20
- .join('\n');
25
+ return content;
21
26
  }
22
- //# sourceMappingURL=patch-artifact-normalize.js.map
@@ -100,4 +100,3 @@ export async function findAllPatchesForFilesWithDetails(patchesDir, targetFiles,
100
100
  }
101
101
  return results;
102
102
  }
103
- //# sourceMappingURL=patch-export-coverage.js.map
@@ -36,4 +36,3 @@ export async function mutatePatchMetadata(patchesDir, filename, mutator) {
36
36
  return { before: changed.before, after: changed.after };
37
37
  });
38
38
  }
39
- //# sourceMappingURL=patch-export-metadata.js.map
@@ -5,7 +5,7 @@ import { pathExists, readText, writeText } from '../utils/fs.js';
5
5
  import { warn } from '../utils/logger.js';
6
6
  import { withPatchDirectoryLock } from './patch-apply.js';
7
7
  import { normalizePatchArtifact } from './patch-artifact-normalize.js';
8
- import { loadPatchesManifest, mutatePatchRowsInManifest } from './patch-manifest.js';
8
+ import { loadPatchesManifestForWrite, mutatePatchRowsInManifest } from './patch-manifest.js';
9
9
  import { buildProjectedManifest, enforcePatchPolicy } from './patch-policy.js';
10
10
  /**
11
11
  * Updates a patch file body and its manifest row under the same patch
@@ -14,7 +14,7 @@ import { buildProjectedManifest, enforcePatchPolicy } from './patch-policy.js';
14
14
  */
15
15
  export async function updatePatchAndMetadata(patchesDir, filename, newContent, updates, onCommitted, policyGate) {
16
16
  await withPatchDirectoryLock(patchesDir, async () => {
17
- const manifest = await loadPatchesManifest(patchesDir);
17
+ const manifest = await loadPatchesManifestForWrite(patchesDir);
18
18
  if (!manifest) {
19
19
  throw new Error('Cannot update patch metadata: patches.json is missing.');
20
20
  }
@@ -65,4 +65,3 @@ export async function updatePatchAndMetadata(patchesDir, filename, newContent, u
65
65
  }
66
66
  });
67
67
  }
68
- //# sourceMappingURL=patch-export-update.js.map
@@ -30,6 +30,8 @@ export declare function sanitizeName(name: string): string;
30
30
  * double-prefix incident) also collapses. Exported for direct testing.
31
31
  */
32
32
  export declare function stripRedundantCategoryPrefix(sanitizedName: string, category: string): string;
33
+ /** Sanitizes a patch name and drops redundant category/full-stem prefixes. */
34
+ export declare function patchNameSlug(name: string, category: string): string;
33
35
  /**
34
36
  * Generates the next patch filename with category.
35
37
  * @param patchesDir - Path to the patches directory
@@ -99,12 +101,6 @@ export declare function findExistingPatchForFile(patchesDir: string, filePath: s
99
101
  * @param newContent - New patch content
100
102
  */
101
103
  export declare function updatePatch(patchPath: string, newContent: string): Promise<void>;
102
- /**
103
- * Deletes a patch file and removes it from the manifest.
104
- * @param patchesDir - Path to the patches directory
105
- * @param filename - Patch filename to delete
106
- */
107
- export declare function deletePatch(patchesDir: string, filename: string): Promise<void>;
108
104
  /**
109
105
  * Fully computed plan for a pending export. Returned from
110
106
  * {@link planExport} so that `--dry-run` previews can render the full
@@ -1,5 +1,4 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
- import { unlink } from 'node:fs/promises';
3
2
  import { join } from 'node:path';
4
3
  import { toError } from '../utils/errors.js';
5
4
  import { pathExists, readText, removeFile, writeText } from '../utils/fs.js';
@@ -8,7 +7,7 @@ import { PATCH_CATEGORIES } from '../utils/validation.js';
8
7
  import { discoverPatches, withPatchDirectoryLock } from './patch-apply.js';
9
8
  import { normalizePatchArtifact } from './patch-artifact-normalize.js';
10
9
  import { findAllPatchesForFilesWithDetails, } from './patch-export-coverage.js';
11
- import { addPatchToManifest, loadPatchesManifest, PATCHES_MANIFEST, savePatchesManifest, } from './patch-manifest.js';
10
+ import { addPatchToManifest, loadPatchesManifestForWrite, PATCHES_MANIFEST, savePatchesManifest, } from './patch-manifest.js';
12
11
  import { allocatePolicyOrder, enforcePatchPolicy } from './patch-policy.js';
13
12
  export { findAllPatchesForFiles, findAllPatchesForFilesWithDetails, findSupersededPatches, isPatchFullyCovered, } from './patch-export-coverage.js';
14
13
  export { mutatePatchMetadata, updatePatchMetadata, } from './patch-export-metadata.js';
@@ -46,6 +45,9 @@ export function sanitizeName(name) {
46
45
  .replace(/^-+|-+$/g, '')
47
46
  .slice(0, 50);
48
47
  }
48
+ function escapeRegExp(value) {
49
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
50
+ }
49
51
  /**
50
52
  * Strips a leading `NNN-<category>-` prefix from a sanitized name slug.
51
53
  * Operators frequently pass the DESIRED FILENAME stem to `--name`
@@ -56,15 +58,25 @@ export function sanitizeName(name) {
56
58
  * double-prefix incident) also collapses. Exported for direct testing.
57
59
  */
58
60
  export function stripRedundantCategoryPrefix(sanitizedName, category) {
59
- const prefix = new RegExp(`^\\d+-${category}-`);
61
+ const prefixes = [
62
+ new RegExp(`^\\d+-${escapeRegExp(category)}-`),
63
+ new RegExp(`^${escapeRegExp(category)}-`),
64
+ ];
60
65
  let stripped = sanitizedName;
61
- while (prefix.test(stripped)) {
62
- stripped = stripped.replace(prefix, '');
66
+ let changed = true;
67
+ while (changed) {
68
+ changed = false;
69
+ for (const prefix of prefixes) {
70
+ if (prefix.test(stripped)) {
71
+ stripped = stripped.replace(prefix, '');
72
+ changed = true;
73
+ }
74
+ }
63
75
  }
64
76
  return stripped.length > 0 ? stripped : sanitizedName;
65
77
  }
66
- /** Sanitizes a patch name and drops a redundant `NNN-<category>-` prefix. */
67
- function patchNameSlug(name, category) {
78
+ /** Sanitizes a patch name and drops redundant category/full-stem prefixes. */
79
+ export function patchNameSlug(name, category) {
68
80
  return stripRedundantCategoryPrefix(sanitizeName(name), category);
69
81
  }
70
82
  /**
@@ -221,45 +233,6 @@ export async function findExistingPatchForFile(patchesDir, filePath) {
221
233
  export async function updatePatch(patchPath, newContent) {
222
234
  await writeText(patchPath, normalizePatchArtifact(newContent));
223
235
  }
224
- /**
225
- * Deletes a patch file and removes it from the manifest.
226
- * @param patchesDir - Path to the patches directory
227
- * @param filename - Patch filename to delete
228
- */
229
- export async function deletePatch(patchesDir, filename) {
230
- await withPatchDirectoryLock(patchesDir, async () => {
231
- const patchPath = join(patchesDir, filename);
232
- const manifest = await loadPatchesManifest(patchesDir);
233
- const updatedManifest = manifest
234
- ? {
235
- ...manifest,
236
- patches: manifest.patches.filter((patch) => patch.filename !== filename),
237
- }
238
- : null;
239
- // Update manifest first so interrupted deletions leave an explicit repairable
240
- // extra patch file rather than silently dropping metadata for an absent file.
241
- if (updatedManifest) {
242
- await savePatchesManifest(patchesDir, updatedManifest);
243
- }
244
- if (!(await pathExists(patchPath))) {
245
- return;
246
- }
247
- try {
248
- await unlink(patchPath);
249
- }
250
- catch (error) {
251
- if (manifest) {
252
- try {
253
- await savePatchesManifest(patchesDir, manifest);
254
- }
255
- catch (error) {
256
- warn(`Failed to restore manifest after patch deletion error for "${filename}": ${toError(error).message}`);
257
- }
258
- }
259
- throw error;
260
- }
261
- });
262
- }
263
236
  /**
264
237
  * Internal planning helper. Does NOT take the patch directory lock — the
265
238
  * caller must already hold it — because the two public entry points
@@ -269,7 +242,11 @@ export async function deletePatch(patchesDir, filename) {
269
242
  * instead of by parallel implementations that can drift.
270
243
  */
271
244
  async function computeExportPlanUnderLock(input) {
272
- const manifestBefore = await loadPatchesManifest(input.patchesDir);
245
+ // ForWrite: a corrupt manifest read as null here would produce a
246
+ // manifestAfter containing ONLY the new patch — committing that plan
247
+ // wipes the queue metadata, and the rollback path would then delete
248
+ // patches.json entirely because manifestBefore looked absent.
249
+ const manifestBefore = await loadPatchesManifestForWrite(input.patchesDir);
273
250
  const policyOrder = input.config !== undefined
274
251
  ? allocatePolicyOrder(input.config, manifestBefore?.patches ?? [], input.category)
275
252
  : null;
@@ -341,4 +318,3 @@ export async function planExport(input) {
341
318
  };
342
319
  });
343
320
  }
344
- //# sourceMappingURL=patch-export.js.map
@@ -51,4 +51,3 @@ export async function getAllTargetFilesFromPatch(patchPath) {
51
51
  const content = await readText(patchPath);
52
52
  return extractAffectedFiles(content);
53
53
  }
54
- //# sourceMappingURL=patch-files.js.map
@@ -105,4 +105,3 @@ export function formatPatchNotFoundError(identifier, patches) {
105
105
  }
106
106
  return `${lead} No close match found among ${patches.length} patches in the queue. Run "fireforge patch list" to see them.`;
107
107
  }
108
- //# sourceMappingURL=patch-identifier-suggest.js.map
@@ -414,4 +414,3 @@ export async function invokePatchLintCheckJsGrouped(repoDir, patchOwnedFiles, pa
414
414
  : { strict };
415
415
  return runCheckJsGrouped(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, mode);
416
416
  }
417
- //# sourceMappingURL=patch-lint-checkjs.js.map
@@ -84,4 +84,3 @@ export function lintChromeScriptJsDocForFile(file, content, isChromeOwned, mode)
84
84
  severity: issue.severity ?? mode,
85
85
  }));
86
86
  }
87
- //# sourceMappingURL=patch-lint-chrome-jsdoc.js.map
@@ -495,4 +495,3 @@ export function lintPatchQueueForwardImports(ctx) {
495
495
  export function lintPatchQueue(ctx) {
496
496
  return [...lintPatchQueueDuplicateCreations(ctx), ...lintPatchQueueForwardImports(ctx)];
497
497
  }
498
- //# sourceMappingURL=patch-lint-cross.js.map
@@ -169,4 +169,3 @@ export async function lintPatchedCss(repoDir, affectedFiles, diffContent, config
169
169
  }
170
170
  return issues;
171
171
  }
172
- //# sourceMappingURL=patch-lint-css.js.map
@@ -105,4 +105,3 @@ export function tagLintIssues(issues, diffFiles) {
105
105
  }
106
106
  return issues;
107
107
  }
108
- //# sourceMappingURL=patch-lint-diff-tag.js.map
@@ -6,7 +6,8 @@
6
6
  * cross-patch lint body (in `patch-lint-cross.ts`) can both depend on
7
7
  * the same diff walkers without inducing a circular import. Callers
8
8
  * should keep importing these through `patch-lint.ts` — this file is
9
- * an implementation detail.
9
+ * an implementation detail. The actual line-walking lives in
10
+ * `parseDiffSections` (patch-parse.ts), the codebase's single diff walker.
10
11
  */
11
12
  /**
12
13
  * Extracts new-file paths from a unified diff by scanning for `new file mode` markers.
@@ -7,23 +7,18 @@
7
7
  * cross-patch lint body (in `patch-lint-cross.ts`) can both depend on
8
8
  * the same diff walkers without inducing a circular import. Callers
9
9
  * should keep importing these through `patch-lint.ts` — this file is
10
- * an implementation detail.
10
+ * an implementation detail. The actual line-walking lives in
11
+ * `parseDiffSections` (patch-parse.ts), the codebase's single diff walker.
11
12
  */
13
+ import { parseDiffSections } from './patch-parse.js';
12
14
  /**
13
15
  * Extracts new-file paths from a unified diff by scanning for `new file mode` markers.
14
16
  */
15
17
  export function detectNewFilesInDiff(diffContent) {
16
18
  const newFiles = new Set();
17
- const lines = diffContent.split('\n');
18
- let currentFile = null;
19
- for (const line of lines) {
20
- if (line.startsWith('diff --git')) {
21
- const match = /^diff --git a\/.+ b\/(.+)$/.exec(line);
22
- currentFile = match?.[1] ?? null;
23
- continue;
24
- }
25
- if (line.startsWith('new file mode') && currentFile) {
26
- newFiles.add(currentFile);
19
+ for (const section of parseDiffSections(diffContent)) {
20
+ if (section.isNewFile) {
21
+ newFiles.add(section.targetPath);
27
22
  }
28
23
  }
29
24
  return newFiles;
@@ -34,27 +29,18 @@ export function detectNewFilesInDiff(diffContent) {
34
29
  */
35
30
  export function extractAddedLinesPerFile(diffContent) {
36
31
  const result = new Map();
37
- const lines = diffContent.split('\n');
38
- let currentFile = null;
39
- let inHunk = false;
40
- for (const line of lines) {
41
- if (line.startsWith('diff --git')) {
42
- const match = /^diff --git a\/.+ b\/(.+)$/.exec(line);
43
- currentFile = match?.[1] ?? null;
44
- inHunk = false;
45
- continue;
46
- }
47
- if (line.startsWith('@@')) {
48
- inHunk = true;
49
- continue;
50
- }
51
- if (inHunk && currentFile && line.startsWith('+') && !line.startsWith('+++')) {
52
- let arr = result.get(currentFile);
53
- if (!arr) {
54
- arr = [];
55
- result.set(currentFile, arr);
32
+ for (const section of parseDiffSections(diffContent)) {
33
+ for (const hunk of section.hunks) {
34
+ for (const line of hunk.lines) {
35
+ if (!line.startsWith('+'))
36
+ continue;
37
+ let arr = result.get(section.targetPath);
38
+ if (!arr) {
39
+ arr = [];
40
+ result.set(section.targetPath, arr);
41
+ }
42
+ arr.push(line.slice(1));
56
43
  }
57
- arr.push(line.slice(1));
58
44
  }
59
45
  }
60
46
  return result;
@@ -81,4 +67,3 @@ export function buildModifiedFileAdditionsFromDiff(diff) {
81
67
  }
82
68
  return result;
83
69
  }
84
- //# sourceMappingURL=patch-lint-diff.js.map
@@ -65,4 +65,3 @@ export function hasRelativeImport(content) {
65
65
  }
66
66
  return RELATIVE_IMPORT_FALLBACK_PATTERN.test(stripJsComments(content));
67
67
  }
68
- //# sourceMappingURL=patch-lint-imports.js.map
@@ -450,4 +450,3 @@ export function validateExportJsDoc(source, options) {
450
450
  }
451
451
  return issues;
452
452
  }
453
- //# sourceMappingURL=patch-lint-jsdoc.js.map
@@ -165,4 +165,3 @@ export function lintObserverTopics(strippedContent, file, binaryName) {
165
165
  }
166
166
  return issues;
167
167
  }
168
- //# sourceMappingURL=patch-lint-observer.js.map
@@ -67,4 +67,3 @@ export function resolvePatchOwnedSysMjs(currentNewFiles, patchQueueCtx) {
67
67
  export function resolvePatchOwnedChromeScripts(currentNewFiles, patchQueueCtx) {
68
68
  return resolveOwned(currentNewFiles, patchQueueCtx, (file) => file.endsWith('.js') && !file.endsWith('.sys.mjs'));
69
69
  }
70
- //# sourceMappingURL=patch-lint-ownership.js.map
@@ -5,4 +5,3 @@
5
5
  */
6
6
  export { buildPatchQueueContext, collectForwardImportEdges, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, } from './patch-lint-cross.js';
7
7
  export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
8
- //# sourceMappingURL=patch-lint-reexports.js.map
@@ -1,5 +1,5 @@
1
1
  import type { PatchLintIssue } from '../types/commands/index.js';
2
- import type { FireForgeConfig } from '../types/config.js';
2
+ import type { FireForgeConfig, ProjectLicense } from '../types/config.js';
3
3
  import { type CommentStyle } from './license-headers.js';
4
4
  import { lintPatchedCss } from './patch-lint-css.js';
5
5
  export * from './patch-lint-reexports.js';
@@ -26,7 +26,42 @@ export declare function isTestFile(file: string): boolean;
26
26
  */
27
27
  export declare function commentStyleForFile(file: string): CommentStyle | null;
28
28
  /**
29
- * Checks new files for required license headers.
29
+ * Decides whether a NEW file's leading header satisfies the
30
+ * `missing-license-header` rule. Single owner of the acceptance policy —
31
+ * `lintNewFileHeaders` (the rule) and `autoFixLicenseHeaders` (the
32
+ * interactive export fixer) must agree, or the fixer offers to stack a
33
+ * second header onto a file the lint already accepts.
34
+ *
35
+ * Accepted shapes, in order:
36
+ * - the project's own header for the file's comment style;
37
+ * - under `browser/branding/`, ANY recognised license header in the
38
+ * matching style: the setup-generated branding directory is copied
39
+ * from Firefox's `unofficial` template and arrives with Mozilla
40
+ * MPL-2.0 headers — legitimate for copyright purposes (the assets are
41
+ * Mozilla's) even when the fork's own code is 0BSD / EUPL-1.2 /
42
+ * GPL-2.0-or-later;
43
+ * - on an MPL-2.0 project, any recognised header in the matching style;
44
+ * - the verbatim upstream MPL-2.0 block header (`/* … *\/`, optionally
45
+ * behind a leading editor-directive comment) REGARDLESS of the
46
+ * project license, on any file whose comment syntax is the block form
47
+ * (JS and CSS): a file copied from the Firefox tree legitimately
48
+ * keeps Mozilla's header for provenance in an EUPL/GPL/0BSD project
49
+ * too. 0.35.0 accepted this on JS only; field verification showed a
50
+ * derived CSS file carrying the identical three-line block header
51
+ * still needed a patch-level lintIgnore. Only the block form is
52
+ * accepted — the `// `-style MPL header is FireForge-generated, not
53
+ * upstream provenance — and `hash`-style files (FTL) stay excluded
54
+ * because `/* … *\/` is not a comment there.
55
+ *
56
+ * @param file - Engine-relative path of the new file
57
+ * @param content - File content
58
+ * @param style - Comment style resolved for the file
59
+ * @param license - The project license
60
+ */
61
+ export declare function isAcceptableNewFileHeader(file: string, content: string, style: CommentStyle, license: ProjectLicense): boolean;
62
+ /**
63
+ * Checks new files for required license headers (acceptance policy in
64
+ * {@link isAcceptableNewFileHeader}).
30
65
  *
31
66
  * @param repoDir - Absolute path to the engine directory
32
67
  * @param newFiles - New file paths (relative to repoDir)
@@ -2,7 +2,7 @@
2
2
  import { dirname, join } from 'node:path';
3
3
  import { pathExists, readText } from '../utils/fs.js';
4
4
  import { stripJsComments } from '../utils/regex.js';
5
- import { containsUpstreamLicenseText, getLicenseHeader, hasAnyLicenseHeader, hasAnyLicenseHeaderAnyStyle, } from './license-headers.js';
5
+ import { containsUpstreamLicenseText, getLicenseHeader, hasAnyLicenseHeader, hasAnyLicenseHeaderAnyStyle, hasUpstreamMplBlockHeader, } from './license-headers.js';
6
6
  import { invokePatchLintCheckJs } from './patch-lint-checkjs.js';
7
7
  import { lintChromeScriptJsDocForFile } from './patch-lint-chrome-jsdoc.js';
8
8
  import { lintPatchedCss } from './patch-lint-css.js';
@@ -211,7 +211,52 @@ export function commentStyleForFile(file) {
211
211
  // License header lint
212
212
  // ---------------------------------------------------------------------------
213
213
  /**
214
- * Checks new files for required license headers.
214
+ * Decides whether a NEW file's leading header satisfies the
215
+ * `missing-license-header` rule. Single owner of the acceptance policy —
216
+ * `lintNewFileHeaders` (the rule) and `autoFixLicenseHeaders` (the
217
+ * interactive export fixer) must agree, or the fixer offers to stack a
218
+ * second header onto a file the lint already accepts.
219
+ *
220
+ * Accepted shapes, in order:
221
+ * - the project's own header for the file's comment style;
222
+ * - under `browser/branding/`, ANY recognised license header in the
223
+ * matching style: the setup-generated branding directory is copied
224
+ * from Firefox's `unofficial` template and arrives with Mozilla
225
+ * MPL-2.0 headers — legitimate for copyright purposes (the assets are
226
+ * Mozilla's) even when the fork's own code is 0BSD / EUPL-1.2 /
227
+ * GPL-2.0-or-later;
228
+ * - on an MPL-2.0 project, any recognised header in the matching style;
229
+ * - the verbatim upstream MPL-2.0 block header (`/* … *\/`, optionally
230
+ * behind a leading editor-directive comment) REGARDLESS of the
231
+ * project license, on any file whose comment syntax is the block form
232
+ * (JS and CSS): a file copied from the Firefox tree legitimately
233
+ * keeps Mozilla's header for provenance in an EUPL/GPL/0BSD project
234
+ * too. 0.35.0 accepted this on JS only; field verification showed a
235
+ * derived CSS file carrying the identical three-line block header
236
+ * still needed a patch-level lintIgnore. Only the block form is
237
+ * accepted — the `// `-style MPL header is FireForge-generated, not
238
+ * upstream provenance — and `hash`-style files (FTL) stay excluded
239
+ * because `/* … *\/` is not a comment there.
240
+ *
241
+ * @param file - Engine-relative path of the new file
242
+ * @param content - File content
243
+ * @param style - Comment style resolved for the file
244
+ * @param license - The project license
245
+ */
246
+ export function isAcceptableNewFileHeader(file, content, style, license) {
247
+ if (content.startsWith(getLicenseHeader(license, style)))
248
+ return true;
249
+ if (file.startsWith('browser/branding/') && hasAnyLicenseHeader(content, style))
250
+ return true;
251
+ if (license === 'MPL-2.0' && hasAnyLicenseHeader(content, style))
252
+ return true;
253
+ if ((style === 'js' || style === 'css') && hasUpstreamMplBlockHeader(content))
254
+ return true;
255
+ return false;
256
+ }
257
+ /**
258
+ * Checks new files for required license headers (acceptance policy in
259
+ * {@link isAcceptableNewFileHeader}).
215
260
  *
216
261
  * @param repoDir - Absolute path to the engine directory
217
262
  * @param newFiles - New file paths (relative to repoDir)
@@ -229,27 +274,7 @@ export async function lintNewFileHeaders(repoDir, newFiles, config) {
229
274
  if (!(await pathExists(filePath)))
230
275
  continue;
231
276
  const content = await readText(filePath);
232
- const expectedHeader = getLicenseHeader(license, style);
233
- // Auto-exempt `browser/branding/` when the file carries ANY recognised
234
- // license header in the matching comment style. The setup-generated
235
- // branding directory is copied from Firefox's `unofficial` template,
236
- // which arrives with Mozilla MPL-2.0 headers — those are legitimate
237
- // for copyright purposes (the assets are Mozilla's) even when the
238
- // fork's own code is 0BSD / EUPL-1.2 / GPL-2.0-or-later. The narrower
239
- // license-match rule would force operators to either rewrite the
240
- // copied headers (misrepresenting authorship) or suppress the lint
241
- // with `--skip-lint` (hiding real issues elsewhere).
242
- if (content.startsWith(expectedHeader))
243
- continue;
244
- if (file.startsWith('browser/branding/') && hasAnyLicenseHeader(content, style))
245
- continue;
246
- // Accept the MPL-2.0 block-comment form (`/* ... */` with leading `*`)
247
- // for any JS file — that is the canonical upstream Firefox header
248
- // shape, and `hasAnyLicenseHeader` above also recognises it. The
249
- // explicit guard mirrors the branding carve-out so operators using the
250
- // standard Mozilla header on a JS file (e.g. one copied from upstream
251
- // browser/base/content) do not need `--skip-lint` to land it.
252
- if (license === 'MPL-2.0' && hasAnyLicenseHeader(content, style))
277
+ if (isAcceptableNewFileHeader(file, content, style, license))
253
278
  continue;
254
279
  issues.push({
255
280
  file,
@@ -609,4 +634,3 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
609
634
  }
610
635
  return issues;
611
636
  }
612
- //# sourceMappingURL=patch-lint.js.map
@@ -27,4 +27,3 @@ export async function withPatchDirectoryLock(patchesDir, operation) {
27
27
  throw new PatchError(toError(error).message);
28
28
  });
29
29
  }
30
- //# sourceMappingURL=patch-lock.js.map
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { stat } from 'node:fs/promises';
6
6
  import { discoverPatches, getAllTargetFilesFromPatch } from './patch-files.js';
7
+ import { withPatchDirectoryLock } from './patch-lock.js';
7
8
  import { loadPatchesManifestState, PATCHES_MANIFEST, savePatchesManifest, } from './patch-manifest-io.js';
8
9
  import { inferPatchMetadataFromFilename } from './patch-manifest-validate.js';
9
10
  /**
@@ -89,6 +90,14 @@ export async function validatePatchesManifestConsistency(patchesDir) {
89
90
  * plus the filenames that were reconstructed from generic defaults.
90
91
  */
91
92
  export async function rebuildPatchesManifest(patchesDir, fallbackSourceEsrVersion) {
93
+ // The whole read → discover → rebuild → save cycle runs under the shared
94
+ // patch-directory lock (invariant 2, docs/lifecycle-invariants.md):
95
+ // manifest writes serialize on this lock, and a repair racing a
96
+ // concurrent export/reorder used to be able to clobber the other
97
+ // writer's manifest. Not reentrant — callers must not already hold it.
98
+ return withPatchDirectoryLock(patchesDir, () => rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVersion));
99
+ }
100
+ async function rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVersion) {
92
101
  const manifestState = await loadPatchesManifestState(patchesDir);
93
102
  const existingEntries = new Map();
94
103
  if (manifestState.manifest) {
@@ -162,4 +171,3 @@ function normalizeFiles(files) {
162
171
  function sameStringArray(left, right) {
163
172
  return left.length === right.length && left.every((value, index) => value === right[index]);
164
173
  }
165
- //# sourceMappingURL=patch-manifest-consistency.js.map
@@ -37,10 +37,26 @@ export interface PatchManifestRowMutationResult {
37
37
  export declare function loadPatchesManifestState(patchesDir: string): Promise<LoadedManifestState>;
38
38
  /**
39
39
  * Loads the patches manifest if it exists.
40
+ *
41
+ * READ-ONLY callers only: this collapses "absent" and "corrupt" into null.
42
+ * Any code path that will WRITE the manifest (or delete patch files based
43
+ * on its content) must use {@link loadPatchesManifestForWrite} instead —
44
+ * treating a corrupt manifest as empty and then saving destroys every
45
+ * existing patch's metadata.
46
+ *
40
47
  * @param patchesDir - Path to the patches directory
41
48
  * @returns PatchesManifest or null if not found
42
49
  */
43
50
  export declare function loadPatchesManifest(patchesDir: string): Promise<PatchesManifest | null>;
51
+ /**
52
+ * Loads the patches manifest for a mutating operation.
53
+ *
54
+ * Returns null only when the manifest genuinely does not exist; a manifest
55
+ * that exists but fails to parse/validate throws
56
+ * {@link PatchManifestCorruptError} so the caller aborts instead of
57
+ * rebuilding an empty queue over the top of the corrupt file.
58
+ */
59
+ export declare function loadPatchesManifestForWrite(patchesDir: string): Promise<PatchesManifest | null>;
44
60
  /**
45
61
  * Saves the patches manifest.
46
62
  * @param patchesDir - Path to the patches directory