@hominis/fireforge 0.9.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 (316) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE.md +294 -0
  3. package/README.md +435 -0
  4. package/dist/bin/fireforge.d.ts +10 -0
  5. package/dist/bin/fireforge.js +29 -0
  6. package/dist/src/cli.d.ts +33 -0
  7. package/dist/src/cli.js +180 -0
  8. package/dist/src/commands/bootstrap.d.ts +9 -0
  9. package/dist/src/commands/bootstrap.js +73 -0
  10. package/dist/src/commands/build.d.ts +11 -0
  11. package/dist/src/commands/build.js +102 -0
  12. package/dist/src/commands/config.d.ts +13 -0
  13. package/dist/src/commands/config.js +135 -0
  14. package/dist/src/commands/discard.d.ts +12 -0
  15. package/dist/src/commands/discard.js +84 -0
  16. package/dist/src/commands/doctor.d.ts +18 -0
  17. package/dist/src/commands/doctor.js +356 -0
  18. package/dist/src/commands/download.d.ts +11 -0
  19. package/dist/src/commands/download.js +127 -0
  20. package/dist/src/commands/export-all.d.ts +11 -0
  21. package/dist/src/commands/export-all.js +122 -0
  22. package/dist/src/commands/export-shared.d.ts +48 -0
  23. package/dist/src/commands/export-shared.js +208 -0
  24. package/dist/src/commands/export.d.ts +13 -0
  25. package/dist/src/commands/export.js +178 -0
  26. package/dist/src/commands/furnace/apply.d.ts +7 -0
  27. package/dist/src/commands/furnace/apply.js +80 -0
  28. package/dist/src/commands/furnace/create.d.ts +8 -0
  29. package/dist/src/commands/furnace/create.js +377 -0
  30. package/dist/src/commands/furnace/deploy.d.ts +8 -0
  31. package/dist/src/commands/furnace/deploy.js +338 -0
  32. package/dist/src/commands/furnace/diff.d.ts +7 -0
  33. package/dist/src/commands/furnace/diff.js +119 -0
  34. package/dist/src/commands/furnace/index.d.ts +16 -0
  35. package/dist/src/commands/furnace/index.js +121 -0
  36. package/dist/src/commands/furnace/list.d.ts +5 -0
  37. package/dist/src/commands/furnace/list.js +65 -0
  38. package/dist/src/commands/furnace/override.d.ts +8 -0
  39. package/dist/src/commands/furnace/override.js +188 -0
  40. package/dist/src/commands/furnace/preview.d.ts +7 -0
  41. package/dist/src/commands/furnace/preview.js +96 -0
  42. package/dist/src/commands/furnace/remove.d.ts +8 -0
  43. package/dist/src/commands/furnace/remove.js +159 -0
  44. package/dist/src/commands/furnace/scan.d.ts +5 -0
  45. package/dist/src/commands/furnace/scan.js +112 -0
  46. package/dist/src/commands/furnace/status.d.ts +7 -0
  47. package/dist/src/commands/furnace/status.js +137 -0
  48. package/dist/src/commands/furnace/validate.d.ts +6 -0
  49. package/dist/src/commands/furnace/validate.js +91 -0
  50. package/dist/src/commands/furnace/validation-output.d.ts +7 -0
  51. package/dist/src/commands/furnace/validation-output.js +22 -0
  52. package/dist/src/commands/import.d.ts +11 -0
  53. package/dist/src/commands/import.js +241 -0
  54. package/dist/src/commands/lint.d.ts +10 -0
  55. package/dist/src/commands/lint.js +118 -0
  56. package/dist/src/commands/package.d.ts +11 -0
  57. package/dist/src/commands/package.js +80 -0
  58. package/dist/src/commands/re-export.d.ts +12 -0
  59. package/dist/src/commands/re-export.js +242 -0
  60. package/dist/src/commands/rebase/abort.d.ts +7 -0
  61. package/dist/src/commands/rebase/abort.js +49 -0
  62. package/dist/src/commands/rebase/confirm.d.ts +18 -0
  63. package/dist/src/commands/rebase/confirm.js +33 -0
  64. package/dist/src/commands/rebase/continue.d.ts +7 -0
  65. package/dist/src/commands/rebase/continue.js +81 -0
  66. package/dist/src/commands/rebase/index.d.ts +22 -0
  67. package/dist/src/commands/rebase/index.js +127 -0
  68. package/dist/src/commands/rebase/patch-loop.d.ts +9 -0
  69. package/dist/src/commands/rebase/patch-loop.js +135 -0
  70. package/dist/src/commands/rebase/summary.d.ts +12 -0
  71. package/dist/src/commands/rebase/summary.js +43 -0
  72. package/dist/src/commands/rebase.d.ts +4 -0
  73. package/dist/src/commands/rebase.js +6 -0
  74. package/dist/src/commands/register.d.ts +13 -0
  75. package/dist/src/commands/register.js +67 -0
  76. package/dist/src/commands/reset.d.ts +11 -0
  77. package/dist/src/commands/reset.js +83 -0
  78. package/dist/src/commands/resolve.d.ts +9 -0
  79. package/dist/src/commands/resolve.js +124 -0
  80. package/dist/src/commands/run.d.ts +9 -0
  81. package/dist/src/commands/run.js +91 -0
  82. package/dist/src/commands/setup-support.d.ts +23 -0
  83. package/dist/src/commands/setup-support.js +310 -0
  84. package/dist/src/commands/setup.d.ts +11 -0
  85. package/dist/src/commands/setup.js +94 -0
  86. package/dist/src/commands/status.d.ts +11 -0
  87. package/dist/src/commands/status.js +268 -0
  88. package/dist/src/commands/test.d.ts +12 -0
  89. package/dist/src/commands/test.js +182 -0
  90. package/dist/src/commands/token-coverage.d.ts +5 -0
  91. package/dist/src/commands/token-coverage.js +57 -0
  92. package/dist/src/commands/token.d.ts +14 -0
  93. package/dist/src/commands/token.js +121 -0
  94. package/dist/src/commands/watch.d.ts +9 -0
  95. package/dist/src/commands/watch.js +112 -0
  96. package/dist/src/commands/wire.d.ts +13 -0
  97. package/dist/src/commands/wire.js +149 -0
  98. package/dist/src/core/ast-utils.d.ts +47 -0
  99. package/dist/src/core/ast-utils.js +57 -0
  100. package/dist/src/core/brand-validation.d.ts +7 -0
  101. package/dist/src/core/brand-validation.js +15 -0
  102. package/dist/src/core/branding.d.ts +49 -0
  103. package/dist/src/core/branding.js +229 -0
  104. package/dist/src/core/browser-wire.d.ts +40 -0
  105. package/dist/src/core/browser-wire.js +66 -0
  106. package/dist/src/core/build-prepare.d.ts +25 -0
  107. package/dist/src/core/build-prepare.js +93 -0
  108. package/dist/src/core/config-mutate.d.ts +15 -0
  109. package/dist/src/core/config-mutate.js +51 -0
  110. package/dist/src/core/config-paths.d.ts +28 -0
  111. package/dist/src/core/config-paths.js +65 -0
  112. package/dist/src/core/config-state.d.ts +28 -0
  113. package/dist/src/core/config-state.js +152 -0
  114. package/dist/src/core/config-validate.d.ts +11 -0
  115. package/dist/src/core/config-validate.js +141 -0
  116. package/dist/src/core/config.d.ts +39 -0
  117. package/dist/src/core/config.js +70 -0
  118. package/dist/src/core/file-lock.d.ts +11 -0
  119. package/dist/src/core/file-lock.js +80 -0
  120. package/dist/src/core/firefox-archive.d.ts +40 -0
  121. package/dist/src/core/firefox-archive.js +63 -0
  122. package/dist/src/core/firefox-cache.d.ts +23 -0
  123. package/dist/src/core/firefox-cache.js +134 -0
  124. package/dist/src/core/firefox-download.d.ts +21 -0
  125. package/dist/src/core/firefox-download.js +129 -0
  126. package/dist/src/core/firefox-extract.d.ts +21 -0
  127. package/dist/src/core/firefox-extract.js +53 -0
  128. package/dist/src/core/firefox.d.ts +34 -0
  129. package/dist/src/core/firefox.js +78 -0
  130. package/dist/src/core/furnace-apply-helpers.d.ts +21 -0
  131. package/dist/src/core/furnace-apply-helpers.js +244 -0
  132. package/dist/src/core/furnace-apply.d.ts +16 -0
  133. package/dist/src/core/furnace-apply.js +147 -0
  134. package/dist/src/core/furnace-config.d.ts +94 -0
  135. package/dist/src/core/furnace-config.js +372 -0
  136. package/dist/src/core/furnace-constants.d.ts +4 -0
  137. package/dist/src/core/furnace-constants.js +6 -0
  138. package/dist/src/core/furnace-registration-ast.d.ts +24 -0
  139. package/dist/src/core/furnace-registration-ast.js +218 -0
  140. package/dist/src/core/furnace-registration-remove.d.ts +14 -0
  141. package/dist/src/core/furnace-registration-remove.js +89 -0
  142. package/dist/src/core/furnace-registration-validate.d.ts +20 -0
  143. package/dist/src/core/furnace-registration-validate.js +40 -0
  144. package/dist/src/core/furnace-registration.d.ts +29 -0
  145. package/dist/src/core/furnace-registration.js +96 -0
  146. package/dist/src/core/furnace-rollback.d.ts +20 -0
  147. package/dist/src/core/furnace-rollback.js +66 -0
  148. package/dist/src/core/furnace-scanner.d.ts +40 -0
  149. package/dist/src/core/furnace-scanner.js +143 -0
  150. package/dist/src/core/furnace-stories.d.ts +37 -0
  151. package/dist/src/core/furnace-stories.js +185 -0
  152. package/dist/src/core/furnace-validate-accessibility.d.ts +6 -0
  153. package/dist/src/core/furnace-validate-accessibility.js +32 -0
  154. package/dist/src/core/furnace-validate-checks.d.ts +4 -0
  155. package/dist/src/core/furnace-validate-checks.js +7 -0
  156. package/dist/src/core/furnace-validate-compatibility.d.ts +6 -0
  157. package/dist/src/core/furnace-validate-compatibility.js +57 -0
  158. package/dist/src/core/furnace-validate-helpers.d.ts +28 -0
  159. package/dist/src/core/furnace-validate-helpers.js +129 -0
  160. package/dist/src/core/furnace-validate-registration.d.ts +37 -0
  161. package/dist/src/core/furnace-validate-registration.js +220 -0
  162. package/dist/src/core/furnace-validate-structure.d.ts +6 -0
  163. package/dist/src/core/furnace-validate-structure.js +66 -0
  164. package/dist/src/core/furnace-validate.d.ts +16 -0
  165. package/dist/src/core/furnace-validate.js +103 -0
  166. package/dist/src/core/git-base.d.ts +47 -0
  167. package/dist/src/core/git-base.js +50 -0
  168. package/dist/src/core/git-diff.d.ts +63 -0
  169. package/dist/src/core/git-diff.js +246 -0
  170. package/dist/src/core/git-file-ops.d.ts +65 -0
  171. package/dist/src/core/git-file-ops.js +141 -0
  172. package/dist/src/core/git-status.d.ts +65 -0
  173. package/dist/src/core/git-status.js +163 -0
  174. package/dist/src/core/git.d.ts +113 -0
  175. package/dist/src/core/git.js +363 -0
  176. package/dist/src/core/license-headers.d.ts +36 -0
  177. package/dist/src/core/license-headers.js +83 -0
  178. package/dist/src/core/mach-build-artifacts.d.ts +29 -0
  179. package/dist/src/core/mach-build-artifacts.js +117 -0
  180. package/dist/src/core/mach-mozconfig.d.ts +17 -0
  181. package/dist/src/core/mach-mozconfig.js +50 -0
  182. package/dist/src/core/mach-python.d.ts +16 -0
  183. package/dist/src/core/mach-python.js +126 -0
  184. package/dist/src/core/mach.d.ts +106 -0
  185. package/dist/src/core/mach.js +166 -0
  186. package/dist/src/core/manifest-helpers.d.ts +25 -0
  187. package/dist/src/core/manifest-helpers.js +96 -0
  188. package/dist/src/core/manifest-register.d.ts +30 -0
  189. package/dist/src/core/manifest-register.js +65 -0
  190. package/dist/src/core/manifest-rules.d.ts +39 -0
  191. package/dist/src/core/manifest-rules.js +151 -0
  192. package/dist/src/core/manifest-tokenizers.d.ts +34 -0
  193. package/dist/src/core/manifest-tokenizers.js +84 -0
  194. package/dist/src/core/parser-fallback.d.ts +36 -0
  195. package/dist/src/core/parser-fallback.js +43 -0
  196. package/dist/src/core/patch-apply-fuzz.d.ts +29 -0
  197. package/dist/src/core/patch-apply-fuzz.js +70 -0
  198. package/dist/src/core/patch-apply.d.ts +46 -0
  199. package/dist/src/core/patch-apply.js +235 -0
  200. package/dist/src/core/patch-export.d.ts +99 -0
  201. package/dist/src/core/patch-export.js +314 -0
  202. package/dist/src/core/patch-files.d.ts +11 -0
  203. package/dist/src/core/patch-files.js +51 -0
  204. package/dist/src/core/patch-lint.d.ts +72 -0
  205. package/dist/src/core/patch-lint.js +403 -0
  206. package/dist/src/core/patch-lock.d.ts +8 -0
  207. package/dist/src/core/patch-lock.js +29 -0
  208. package/dist/src/core/patch-manifest-consistency.d.ts +24 -0
  209. package/dist/src/core/patch-manifest-consistency.js +135 -0
  210. package/dist/src/core/patch-manifest-io.d.ts +36 -0
  211. package/dist/src/core/patch-manifest-io.js +77 -0
  212. package/dist/src/core/patch-manifest-query.d.ts +48 -0
  213. package/dist/src/core/patch-manifest-query.js +124 -0
  214. package/dist/src/core/patch-manifest-validate.d.ts +22 -0
  215. package/dist/src/core/patch-manifest-validate.js +72 -0
  216. package/dist/src/core/patch-manifest.d.ts +11 -0
  217. package/dist/src/core/patch-manifest.js +12 -0
  218. package/dist/src/core/patch-parse.d.ts +43 -0
  219. package/dist/src/core/patch-parse.js +143 -0
  220. package/dist/src/core/patch-transform.d.ts +21 -0
  221. package/dist/src/core/patch-transform.js +138 -0
  222. package/dist/src/core/rebase-session.d.ts +47 -0
  223. package/dist/src/core/rebase-session.js +65 -0
  224. package/dist/src/core/register-browser-content.d.ts +11 -0
  225. package/dist/src/core/register-browser-content.js +116 -0
  226. package/dist/src/core/register-module.d.ts +11 -0
  227. package/dist/src/core/register-module.js +76 -0
  228. package/dist/src/core/register-shared-css.d.ts +11 -0
  229. package/dist/src/core/register-shared-css.js +117 -0
  230. package/dist/src/core/register-test-manifest.d.ts +18 -0
  231. package/dist/src/core/register-test-manifest.js +99 -0
  232. package/dist/src/core/state-file.d.ts +4 -0
  233. package/dist/src/core/state-file.js +25 -0
  234. package/dist/src/core/token-coverage.d.ts +12 -0
  235. package/dist/src/core/token-coverage.js +74 -0
  236. package/dist/src/core/token-manager.d.ts +55 -0
  237. package/dist/src/core/token-manager.js +387 -0
  238. package/dist/src/core/wire-destroy.d.ts +21 -0
  239. package/dist/src/core/wire-destroy.js +103 -0
  240. package/dist/src/core/wire-dom-fragment.d.ts +23 -0
  241. package/dist/src/core/wire-dom-fragment.js +129 -0
  242. package/dist/src/core/wire-init.d.ts +23 -0
  243. package/dist/src/core/wire-init.js +201 -0
  244. package/dist/src/core/wire-subscript.d.ts +20 -0
  245. package/dist/src/core/wire-subscript.js +134 -0
  246. package/dist/src/core/wire-targets.d.ts +7 -0
  247. package/dist/src/core/wire-targets.js +9 -0
  248. package/dist/src/core/wire-utils.d.ts +88 -0
  249. package/dist/src/core/wire-utils.js +279 -0
  250. package/dist/src/errors/base.d.ts +60 -0
  251. package/dist/src/errors/base.js +87 -0
  252. package/dist/src/errors/build.d.ts +52 -0
  253. package/dist/src/errors/build.js +114 -0
  254. package/dist/src/errors/codes.d.ts +29 -0
  255. package/dist/src/errors/codes.js +30 -0
  256. package/dist/src/errors/config.d.ts +31 -0
  257. package/dist/src/errors/config.js +61 -0
  258. package/dist/src/errors/download.d.ts +42 -0
  259. package/dist/src/errors/download.js +95 -0
  260. package/dist/src/errors/furnace.d.ts +10 -0
  261. package/dist/src/errors/furnace.js +22 -0
  262. package/dist/src/errors/git.d.ts +41 -0
  263. package/dist/src/errors/git.js +99 -0
  264. package/dist/src/errors/patch.d.ts +10 -0
  265. package/dist/src/errors/patch.js +26 -0
  266. package/dist/src/errors/rebase.d.ts +20 -0
  267. package/dist/src/errors/rebase.js +30 -0
  268. package/dist/src/index.d.ts +21 -0
  269. package/dist/src/index.js +21 -0
  270. package/dist/src/types/cli.d.ts +14 -0
  271. package/dist/src/types/cli.js +2 -0
  272. package/dist/src/types/commands/index.d.ts +6 -0
  273. package/dist/src/types/commands/index.js +6 -0
  274. package/dist/src/types/commands/options.d.ts +239 -0
  275. package/dist/src/types/commands/options.js +6 -0
  276. package/dist/src/types/commands/patches.d.ts +89 -0
  277. package/dist/src/types/commands/patches.js +6 -0
  278. package/dist/src/types/commands/project.d.ts +71 -0
  279. package/dist/src/types/commands/project.js +6 -0
  280. package/dist/src/types/config.d.ts +101 -0
  281. package/dist/src/types/config.js +2 -0
  282. package/dist/src/types/furnace.d.ts +158 -0
  283. package/dist/src/types/furnace.js +2 -0
  284. package/dist/src/types/index.d.ts +6 -0
  285. package/dist/src/types/index.js +6 -0
  286. package/dist/src/utils/errors.d.ts +2 -0
  287. package/dist/src/utils/errors.js +15 -0
  288. package/dist/src/utils/fs.d.ts +72 -0
  289. package/dist/src/utils/fs.js +179 -0
  290. package/dist/src/utils/logger.d.ts +58 -0
  291. package/dist/src/utils/logger.js +120 -0
  292. package/dist/src/utils/options.d.ts +8 -0
  293. package/dist/src/utils/options.js +16 -0
  294. package/dist/src/utils/package-root.d.ts +10 -0
  295. package/dist/src/utils/package-root.js +53 -0
  296. package/dist/src/utils/parse.d.ts +110 -0
  297. package/dist/src/utils/parse.js +200 -0
  298. package/dist/src/utils/paths.d.ts +10 -0
  299. package/dist/src/utils/paths.js +43 -0
  300. package/dist/src/utils/platform.d.ts +38 -0
  301. package/dist/src/utils/platform.js +56 -0
  302. package/dist/src/utils/process.d.ts +80 -0
  303. package/dist/src/utils/process.js +188 -0
  304. package/dist/src/utils/regex.d.ts +24 -0
  305. package/dist/src/utils/regex.js +40 -0
  306. package/dist/src/utils/validation.d.ts +133 -0
  307. package/dist/src/utils/validation.js +250 -0
  308. package/package.json +106 -0
  309. package/templates/configs/common.mozconfig +24 -0
  310. package/templates/configs/darwin.mozconfig +10 -0
  311. package/templates/configs/linux.mozconfig +12 -0
  312. package/templates/configs/win32.mozconfig +14 -0
  313. package/templates/licenses/0BSD.md +14 -0
  314. package/templates/licenses/EUPL-1.2.md +294 -0
  315. package/templates/licenses/GPL-2.0-or-later.md +339 -0
  316. package/templates/licenses/MPL-2.0.md +383 -0
@@ -0,0 +1,314 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { unlink } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { toError } from '../utils/errors.js';
5
+ import { pathExists, readText, removeFile, writeText } from '../utils/fs.js';
6
+ import { warn } from '../utils/logger.js';
7
+ import { PATCH_CATEGORIES } from '../utils/validation.js';
8
+ import { discoverPatches, isNewFilePatch, withPatchDirectoryLock } from './patch-apply.js';
9
+ import { addPatchToManifest, loadPatchesManifest, PATCHES_MANIFEST, savePatchesManifest, } from './patch-manifest.js';
10
+ /**
11
+ * Gets the next patch number for a new patch.
12
+ * @param patchesDir - Path to the patches directory
13
+ * @returns Next patch number (e.g., "005" for 4 existing patches)
14
+ */
15
+ export async function getNextPatchNumber(patchesDir) {
16
+ const patches = await discoverPatches(patchesDir);
17
+ if (patches.length === 0) {
18
+ return '001';
19
+ }
20
+ const finitePatches = patches.filter((p) => Number.isFinite(p.order));
21
+ if (finitePatches.length === 0)
22
+ return '001';
23
+ const maxOrder = finitePatches.reduce((max, p) => Math.max(max, p.order), 0);
24
+ const nextNumber = maxOrder + 1;
25
+ return String(nextNumber).padStart(Math.max(3, String(nextNumber).length), '0');
26
+ }
27
+ /**
28
+ * Sanitizes a string for use in a filename.
29
+ */
30
+ function sanitizeName(name) {
31
+ return name
32
+ .toLowerCase()
33
+ .replace(/[^a-z0-9]+/g, '-')
34
+ .replace(/^-+|-+$/g, '')
35
+ .slice(0, 50);
36
+ }
37
+ /**
38
+ * Generates the next patch filename with category.
39
+ * @param patchesDir - Path to the patches directory
40
+ * @param category - Patch category
41
+ * @param name - Human-readable name
42
+ * @returns Filename like "001-ui-sidebar.patch"
43
+ */
44
+ export async function getNextPatchFilename(patchesDir, category, name) {
45
+ const patchNumber = await getNextPatchNumber(patchesDir);
46
+ const sanitizedName = sanitizeName(name);
47
+ return `${patchNumber}-${category}-${sanitizedName}.patch`;
48
+ }
49
+ /**
50
+ * Commits a freshly generated patch file and manifest update under an exclusive
51
+ * patch directory lock so concurrent exports cannot allocate the same number.
52
+ */
53
+ export async function commitExportedPatch(input) {
54
+ return withPatchDirectoryLock(input.patchesDir, async () => {
55
+ const patchFilename = await getNextPatchFilename(input.patchesDir, input.category, input.name);
56
+ const patchPath = join(input.patchesDir, patchFilename);
57
+ const metadata = {
58
+ filename: patchFilename,
59
+ order: parseInt(patchFilename.split('-')[0] ?? '0', 10),
60
+ category: input.category,
61
+ name: input.name,
62
+ description: input.description,
63
+ createdAt: new Date().toISOString(),
64
+ sourceEsrVersion: input.sourceEsrVersion,
65
+ filesAffected: input.filesAffected,
66
+ };
67
+ const superseded = await findAllPatchesForFiles(input.patchesDir, input.filesAffected, patchFilename);
68
+ const supersededFilenames = superseded.map((patch) => patch.filename);
69
+ const originalManifest = await loadPatchesManifest(input.patchesDir);
70
+ const originalPatchContent = (await pathExists(patchPath)) ? await readText(patchPath) : null;
71
+ const removedPatchContents = new Map();
72
+ for (const oldPatch of superseded) {
73
+ if (await pathExists(oldPatch.path)) {
74
+ removedPatchContents.set(oldPatch.path, await readText(oldPatch.path));
75
+ }
76
+ }
77
+ try {
78
+ await writeText(patchPath, input.diff);
79
+ await addPatchToManifest(input.patchesDir, metadata, supersededFilenames);
80
+ for (const oldPatch of superseded) {
81
+ await removeFile(oldPatch.path);
82
+ }
83
+ }
84
+ catch (error) {
85
+ // Best-effort rollback: wrap each operation so a secondary failure
86
+ // never masks the original failure.
87
+ try {
88
+ if (originalPatchContent === null) {
89
+ await removeFile(patchPath);
90
+ }
91
+ else {
92
+ await writeText(patchPath, originalPatchContent);
93
+ }
94
+ }
95
+ catch (error) {
96
+ warn(`Rollback warning: could not restore patch file: ${toError(error).message}`);
97
+ }
98
+ for (const [oldPatchPath, oldPatchContent] of removedPatchContents) {
99
+ try {
100
+ await writeText(oldPatchPath, oldPatchContent);
101
+ }
102
+ catch (error) {
103
+ warn(`Rollback warning: could not restore ${oldPatchPath}: ${toError(error).message}`);
104
+ }
105
+ }
106
+ try {
107
+ if (originalManifest) {
108
+ await savePatchesManifest(input.patchesDir, originalManifest);
109
+ }
110
+ else {
111
+ await removeFile(join(input.patchesDir, PATCHES_MANIFEST));
112
+ }
113
+ }
114
+ catch (error) {
115
+ warn(`Rollback warning: could not restore manifest: ${toError(error).message}`);
116
+ }
117
+ throw error;
118
+ }
119
+ return {
120
+ patchFilename,
121
+ metadata,
122
+ superseded,
123
+ };
124
+ });
125
+ }
126
+ /**
127
+ * Parses a patch filename to extract order, category, and name.
128
+ * Supports both new format (001-category-name.patch) and legacy (001-name.patch).
129
+ */
130
+ export function parseFilename(filename) {
131
+ // New format: 001-ui-sidebar.patch
132
+ const newMatch = /^(\d+)-([a-z]+)-(.+)\.patch$/.exec(filename);
133
+ if (newMatch?.[1] && newMatch[2] && newMatch[3]) {
134
+ const orderStr = newMatch[1];
135
+ const category = newMatch[2];
136
+ const name = newMatch[3];
137
+ if (PATCH_CATEGORIES.includes(category)) {
138
+ return {
139
+ order: parseInt(orderStr, 10),
140
+ category: category,
141
+ name,
142
+ };
143
+ }
144
+ }
145
+ // Legacy format: 001-name.patch
146
+ const legacyMatch = /^(\d+)-(.+)\.patch$/.exec(filename);
147
+ if (legacyMatch?.[1] && legacyMatch[2]) {
148
+ return {
149
+ order: parseInt(legacyMatch[1], 10),
150
+ category: null,
151
+ name: legacyMatch[2],
152
+ };
153
+ }
154
+ return { order: Infinity, category: null, name: filename };
155
+ }
156
+ /**
157
+ * Finds an existing patch that contains the specified file.
158
+ * Returns the most recent (highest order) patch if multiple exist.
159
+ * @param patchesDir - Path to the patches directory
160
+ * @param filePath - File path to search for
161
+ * @returns The patch info and metadata, or null if not found
162
+ */
163
+ export async function findExistingPatchForFile(patchesDir, filePath) {
164
+ const { findPatchesAffectingFile } = await import('./patch-manifest.js');
165
+ const affectingPatches = await findPatchesAffectingFile(patchesDir, filePath);
166
+ if (affectingPatches.length === 0) {
167
+ return null;
168
+ }
169
+ // Return the most recent (highest order) patch
170
+ return affectingPatches[affectingPatches.length - 1] ?? null;
171
+ }
172
+ /**
173
+ * Updates the content of a patch file.
174
+ * @param patchPath - Path to the patch file
175
+ * @param newContent - New patch content
176
+ */
177
+ export async function updatePatch(patchPath, newContent) {
178
+ await writeText(patchPath, newContent);
179
+ }
180
+ /**
181
+ * Updates metadata for a patch in the manifest.
182
+ * @param patchesDir - Path to the patches directory
183
+ * @param filename - Patch filename
184
+ * @param updates - Partial metadata updates
185
+ */
186
+ export async function updatePatchMetadata(patchesDir, filename, updates) {
187
+ await withPatchDirectoryLock(patchesDir, async () => {
188
+ const manifest = await loadPatchesManifest(patchesDir);
189
+ if (!manifest)
190
+ return;
191
+ const patchIndex = manifest.patches.findIndex((p) => p.filename === filename);
192
+ if (patchIndex === -1)
193
+ return;
194
+ const existingPatch = manifest.patches[patchIndex];
195
+ if (existingPatch) {
196
+ manifest.patches[patchIndex] = { ...existingPatch, ...updates };
197
+ await savePatchesManifest(patchesDir, manifest);
198
+ }
199
+ });
200
+ }
201
+ /**
202
+ * Finds patches that are completely superseded by newer patches.
203
+ * A patch is superseded if all its affected files are covered by newer patches.
204
+ * @param patchesDir - Path to the patches directory
205
+ * @param newPatchFiles - Files affected by the new patch
206
+ * @param excludeFilename - Filename to exclude from results (the new patch itself)
207
+ * @returns Superseded patches
208
+ */
209
+ export async function findSupersededPatches(patchesDir, newPatchFiles, excludeFilename) {
210
+ const manifest = await loadPatchesManifest(patchesDir);
211
+ if (!manifest)
212
+ return [];
213
+ const patches = await discoverPatches(patchesDir);
214
+ const superseded = [];
215
+ for (const metadata of manifest.patches) {
216
+ // Skip the new patch itself
217
+ if (excludeFilename && metadata.filename === excludeFilename)
218
+ continue;
219
+ // Check if this is a "new file" patch (single file, created from scratch)
220
+ // A patch is superseded if it's a single-file new-file patch and
221
+ // the new patch covers the same file
222
+ if (metadata.filesAffected.length === 1) {
223
+ const affectedFile = metadata.filesAffected[0];
224
+ if (affectedFile && newPatchFiles.includes(affectedFile)) {
225
+ const patch = patches.find((p) => p.filename === metadata.filename);
226
+ if (patch && (await isNewFilePatch(patch.path))) {
227
+ superseded.push(patch);
228
+ }
229
+ }
230
+ }
231
+ }
232
+ return superseded;
233
+ }
234
+ /**
235
+ * Deletes a patch file and removes it from the manifest.
236
+ * @param patchesDir - Path to the patches directory
237
+ * @param filename - Patch filename to delete
238
+ */
239
+ export async function deletePatch(patchesDir, filename) {
240
+ await withPatchDirectoryLock(patchesDir, async () => {
241
+ const patchPath = join(patchesDir, filename);
242
+ const manifest = await loadPatchesManifest(patchesDir);
243
+ const updatedManifest = manifest
244
+ ? {
245
+ ...manifest,
246
+ patches: manifest.patches.filter((patch) => patch.filename !== filename),
247
+ }
248
+ : null;
249
+ // Update manifest first so interrupted deletions leave an explicit repairable
250
+ // extra patch file rather than silently dropping metadata for an absent file.
251
+ if (updatedManifest) {
252
+ await savePatchesManifest(patchesDir, updatedManifest);
253
+ }
254
+ if (!(await pathExists(patchPath))) {
255
+ return;
256
+ }
257
+ try {
258
+ await unlink(patchPath);
259
+ }
260
+ catch (error) {
261
+ if (manifest) {
262
+ try {
263
+ await savePatchesManifest(patchesDir, manifest);
264
+ }
265
+ catch (error) {
266
+ warn(`Failed to restore manifest after patch deletion error for "${filename}": ${toError(error).message}`);
267
+ }
268
+ }
269
+ throw error;
270
+ }
271
+ });
272
+ }
273
+ /**
274
+ * Checks whether a patch is fully covered by a new export.
275
+ * A patch is fully covered when every file it affects is present in the new export.
276
+ * @param patchFiles - Files affected by the existing patch
277
+ * @param targetFiles - Files affected by the new export
278
+ * @returns True when the existing patch is fully covered
279
+ */
280
+ export function isPatchFullyCovered(patchFiles, targetFiles) {
281
+ if (patchFiles.length === 0) {
282
+ return false;
283
+ }
284
+ const targetFileSet = new Set(targetFiles);
285
+ return patchFiles.every((file) => targetFileSet.has(file));
286
+ }
287
+ /**
288
+ * Finds patches whose filesAffected entries are fully covered by the specified files.
289
+ * Used for complete supersession when exporting full-file patches.
290
+ * @param patchesDir - Path to the patches directory
291
+ * @param targetFiles - Files affected by the new export
292
+ * @param excludeFilename - Filename to exclude from results (the new patch itself)
293
+ * @returns Patches that are fully covered by the new export
294
+ */
295
+ export async function findAllPatchesForFiles(patchesDir, targetFiles, excludeFilename) {
296
+ const manifest = await loadPatchesManifest(patchesDir);
297
+ if (!manifest)
298
+ return [];
299
+ const patches = await discoverPatches(patchesDir);
300
+ const superseded = [];
301
+ for (const metadata of manifest.patches) {
302
+ // Skip the new patch itself
303
+ if (excludeFilename && metadata.filename === excludeFilename)
304
+ continue;
305
+ if (isPatchFullyCovered(metadata.filesAffected, targetFiles)) {
306
+ const patch = patches.find((p) => p.filename === metadata.filename);
307
+ if (patch) {
308
+ superseded.push(patch);
309
+ }
310
+ }
311
+ }
312
+ return superseded;
313
+ }
314
+ //# sourceMappingURL=patch-export.js.map
@@ -0,0 +1,11 @@
1
+ import type { PatchInfo } from '../types/commands/index.js';
2
+ /** Discovers patch files in a directory and returns them in apply order. */
3
+ export declare function discoverPatches(patchesDir: string): Promise<PatchInfo[]>;
4
+ /** Counts the patch files currently present in a patch directory. */
5
+ export declare function countPatches(patchesDir: string): Promise<number>;
6
+ /** Checks whether a patch creates a new file rather than modifying an existing one. */
7
+ export declare function isNewFilePatch(patchPath: string): Promise<boolean>;
8
+ /** Returns the first target file path referenced by a patch, if any. */
9
+ export declare function getTargetFileFromPatch(patchPath: string): Promise<string | null>;
10
+ /** Returns all target file paths referenced by a multi-file patch. */
11
+ export declare function getAllTargetFilesFromPatch(patchPath: string): Promise<string[]>;
@@ -0,0 +1,51 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { readdir } from 'node:fs/promises';
3
+ import { extname, join } from 'node:path';
4
+ import { pathExists, readText } from '../utils/fs.js';
5
+ import { extractOrder } from './patch-parse.js';
6
+ /** Discovers patch files in a directory and returns them in apply order. */
7
+ export async function discoverPatches(patchesDir) {
8
+ if (!(await pathExists(patchesDir))) {
9
+ return [];
10
+ }
11
+ const entries = await readdir(patchesDir, { withFileTypes: true });
12
+ const patches = entries
13
+ .filter((entry) => entry.isFile() && extname(entry.name) === '.patch')
14
+ .map((entry) => ({
15
+ path: join(patchesDir, entry.name),
16
+ filename: entry.name,
17
+ order: extractOrder(entry.name),
18
+ }));
19
+ patches.sort((a, b) => a.order - b.order || a.filename.localeCompare(b.filename));
20
+ return patches;
21
+ }
22
+ /** Counts the patch files currently present in a patch directory. */
23
+ export async function countPatches(patchesDir) {
24
+ const patches = await discoverPatches(patchesDir);
25
+ return patches.length;
26
+ }
27
+ /** Checks whether a patch creates a new file rather than modifying an existing one. */
28
+ export async function isNewFilePatch(patchPath) {
29
+ const content = await readText(patchPath);
30
+ return content.includes('new file mode') && content.includes('--- /dev/null');
31
+ }
32
+ /** Returns the first target file path referenced by a patch, if any. */
33
+ export async function getTargetFileFromPatch(patchPath) {
34
+ const content = await readText(patchPath);
35
+ const match = /^\+\+\+ b\/(.+)$/m.exec(content);
36
+ return match?.[1] ?? null;
37
+ }
38
+ /** Returns all target file paths referenced by a multi-file patch. */
39
+ export async function getAllTargetFilesFromPatch(patchPath) {
40
+ const content = await readText(patchPath);
41
+ const files = [];
42
+ const regex = /^\+\+\+ b\/(.+)$/gm;
43
+ let match;
44
+ while ((match = regex.exec(content)) !== null) {
45
+ if (match[1]) {
46
+ files.push(match[1]);
47
+ }
48
+ }
49
+ return files;
50
+ }
51
+ //# sourceMappingURL=patch-files.js.map
@@ -0,0 +1,72 @@
1
+ import type { PatchLintIssue } from '../types/commands/index.js';
2
+ import type { FireForgeConfig } from '../types/config.js';
3
+ import { type CommentStyle } from './license-headers.js';
4
+ /**
5
+ * Detects comment style from file extension for license header checks.
6
+ */
7
+ export declare function commentStyleForFile(file: string): CommentStyle | null;
8
+ /**
9
+ * Extracts new-file paths from a unified diff by scanning for `new file mode` markers.
10
+ */
11
+ export declare function detectNewFilesInDiff(diffContent: string): Set<string>;
12
+ /**
13
+ * Lints patched CSS files for raw color values and non-tokenized custom properties.
14
+ *
15
+ * @param repoDir - Absolute path to the engine (repository) directory
16
+ * @param affectedFiles - File paths (relative to repoDir) affected by the patch
17
+ * @returns Array of lint issues found
18
+ */
19
+ export declare function lintPatchedCss(repoDir: string, affectedFiles: string[]): Promise<PatchLintIssue[]>;
20
+ /**
21
+ * Checks new files for required license headers.
22
+ *
23
+ * @param repoDir - Absolute path to the engine directory
24
+ * @param newFiles - New file paths (relative to repoDir)
25
+ * @param config - Project configuration
26
+ * @returns Array of lint issues
27
+ */
28
+ export declare function lintNewFileHeaders(repoDir: string, newFiles: string[], config: FireForgeConfig): Promise<PatchLintIssue[]>;
29
+ /**
30
+ * Lints patched JS/MJS files for import conventions, file size, JSDoc, and
31
+ * observer topic naming.
32
+ *
33
+ * @param repoDir - Absolute path to the engine directory
34
+ * @param affectedFiles - File paths (relative to repoDir)
35
+ * @param newFiles - Set of files that are newly created in this patch
36
+ * @param config - Project configuration
37
+ * @returns Array of lint issues
38
+ */
39
+ export declare function lintPatchedJs(repoDir: string, affectedFiles: string[], newFiles: Set<string>, config: FireForgeConfig): Promise<PatchLintIssue[]>;
40
+ /**
41
+ * Checks that modifications to existing (non-new) JS/MJS files include at
42
+ * least one `// BINARYNAME:` comment in the added lines.
43
+ *
44
+ * @param diffContent - Raw unified diff string
45
+ * @param config - Project configuration
46
+ * @returns Array of lint issues
47
+ */
48
+ export declare function lintModificationComments(diffContent: string, config: FireForgeConfig): PatchLintIssue[];
49
+ /**
50
+ * Checks patch size and emits advisory warnings.
51
+ */
52
+ export declare function lintPatchSize(filesAffected: string[], lineCount: number): PatchLintIssue[];
53
+ /**
54
+ * Checks that modified (non-new) files with a supported extension still
55
+ * start with a recognized license header.
56
+ *
57
+ * @param repoDir - Engine root directory
58
+ * @param affectedFiles - All files affected by the patch
59
+ * @param newFiles - Set of newly created files (excluded from this check)
60
+ * @returns Warning-level lint issues for files missing any recognized header
61
+ */
62
+ export declare function lintModifiedFileHeaders(repoDir: string, affectedFiles: string[], newFiles: Set<string>): Promise<PatchLintIssue[]>;
63
+ /**
64
+ * Runs all patch lint checks and returns combined issues.
65
+ *
66
+ * @param repoDir - Absolute path to the engine directory
67
+ * @param affectedFiles - File paths (relative to repoDir) affected by the patch
68
+ * @param diffContent - Raw unified diff string
69
+ * @param config - Project configuration
70
+ * @returns Array of all lint issues found
71
+ */
72
+ export declare function lintExportedPatch(repoDir: string, affectedFiles: string[], diffContent: string, config: FireForgeConfig): Promise<PatchLintIssue[]>;