@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,138 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Pure content transformation functions for patch operations.
4
+ * These operate on file content strings without filesystem side effects.
5
+ */
6
+ import { PatchError } from '../errors/patch.js';
7
+ import { readText } from '../utils/fs.js';
8
+ import { isNewFileInPatch, parseHunksForFile } from './patch-parse.js';
9
+ /**
10
+ * Extracts the complete file content from a "new file" patch.
11
+ * When targetFile is provided, only extracts content for that file
12
+ * (required for multi-file patches).
13
+ * @param patchPath - Path to the patch file
14
+ * @param targetFile - Optional target file to scope extraction to
15
+ * @returns The file content that the patch would create
16
+ */
17
+ export async function extractNewFileContent(patchPath, targetFile) {
18
+ const content = await readText(patchPath);
19
+ const lines = content.split('\n');
20
+ const contentLines = [];
21
+ let inHunk = false;
22
+ let inTargetFile = !targetFile; // If no targetFile, accept all sections
23
+ let hasNoNewlineMarker = false;
24
+ for (const line of lines) {
25
+ // Track which file section we're in
26
+ if (line.startsWith('diff --git')) {
27
+ if (targetFile) {
28
+ const match = /^diff --git a\/.+ b\/(.+)$/.exec(line);
29
+ const wasInTarget = inTargetFile;
30
+ inTargetFile = match?.[1] === targetFile;
31
+ // If we were in the target file and hit a new diff header, we're done
32
+ if (wasInTarget && !inTargetFile)
33
+ break;
34
+ }
35
+ inHunk = false;
36
+ continue;
37
+ }
38
+ if (!inTargetFile)
39
+ continue;
40
+ // Start of hunk
41
+ if (line.startsWith('@@')) {
42
+ inHunk = true;
43
+ continue;
44
+ }
45
+ if (inHunk) {
46
+ // Check for "No newline at end of file" marker
47
+ if (line === '\') {
48
+ hasNoNewlineMarker = true;
49
+ continue;
50
+ }
51
+ // Lines starting with + are added content (skip the + prefix)
52
+ if (line.startsWith('+')) {
53
+ contentLines.push(line.slice(1));
54
+ }
55
+ // Lines starting with - are removed (shouldn't exist in new file patches)
56
+ // Context lines (no prefix) shouldn't exist in new file patches
57
+ }
58
+ }
59
+ // Join lines and handle trailing newline
60
+ const result = contentLines.join('\n');
61
+ return hasNoNewlineMarker ? result : result + '\n';
62
+ }
63
+ /**
64
+ * Applies a patch's changes to content.
65
+ * @param content - Original content (null for new files)
66
+ * @param patchPath - Path to the patch file
67
+ * @param targetFile - The file path within the patch
68
+ * @returns Modified content
69
+ */
70
+ export async function applyPatchToContent(content, patchPath, targetFile) {
71
+ const patchContent = await readText(patchPath);
72
+ // Check if this is a new file patch for the target file specifically
73
+ if (content === null) {
74
+ if (isNewFileInPatch(patchContent, targetFile)) {
75
+ return await extractNewFileContent(patchPath, targetFile);
76
+ }
77
+ // If not a new file patch but content is null, return empty
78
+ return '';
79
+ }
80
+ const hunks = parseHunksForFile(patchContent, targetFile);
81
+ if (hunks.length === 0) {
82
+ return content;
83
+ }
84
+ // Apply hunks
85
+ const contentLines = content.split('\n');
86
+ // Remove trailing empty line if content ends with newline (but not for empty files)
87
+ if (contentLines.length > 1 && contentLines[contentLines.length - 1] === '') {
88
+ contentLines.pop();
89
+ }
90
+ // Process hunks in reverse order to preserve line numbers
91
+ const sortedHunks = [...hunks].sort((a, b) => b.oldStart - a.oldStart);
92
+ // The "no newline at end" marker applies to the last hunk in file order
93
+ // (highest oldStart), which is the *first* hunk in our reverse-sorted array.
94
+ const lastHunkNoNewline = sortedHunks[0]?.noNewlineAtEnd ?? false;
95
+ for (const hunk of sortedHunks) {
96
+ const newLines = [];
97
+ // Compute actual old-line count from hunk body for cross-check
98
+ let actualOldCount = 0;
99
+ for (const line of hunk.lines) {
100
+ if (line.startsWith('+')) {
101
+ newLines.push(line.slice(1));
102
+ }
103
+ else if (line.startsWith(' ')) {
104
+ newLines.push(line.slice(1));
105
+ actualOldCount++;
106
+ }
107
+ else if (line.startsWith('-')) {
108
+ actualOldCount++;
109
+ }
110
+ // Lines starting with '-' are removed (not added to newLines)
111
+ }
112
+ if (actualOldCount !== hunk.oldCount) {
113
+ throw new PatchError(`Patch hunk header mismatch for ${targetFile}: header says ${hunk.oldCount} old lines but body has ${actualOldCount}`, targetFile);
114
+ }
115
+ // Replace the old lines with new lines
116
+ const startIndex = hunk.oldStart - 1;
117
+ // Verify context lines match before applying
118
+ let verifyIndex = startIndex;
119
+ for (const hunkLine of hunk.lines) {
120
+ if (hunkLine.startsWith(' ') || hunkLine.startsWith('-')) {
121
+ const expectedContent = hunkLine.slice(1);
122
+ const actualContent = contentLines[verifyIndex];
123
+ if (actualContent !== expectedContent) {
124
+ throw new PatchError(`Patch context mismatch at line ${verifyIndex + 1} for ${targetFile}: ` +
125
+ `expected "${expectedContent}", got "${actualContent}"`, targetFile);
126
+ }
127
+ verifyIndex++;
128
+ }
129
+ }
130
+ contentLines.splice(startIndex, hunk.oldCount, ...newLines);
131
+ }
132
+ // Respect the no-newline-at-end-of-file marker from the last hunk
133
+ if (lastHunkNoNewline) {
134
+ return contentLines.join('\n');
135
+ }
136
+ return contentLines.join('\n') + '\n';
137
+ }
138
+ //# sourceMappingURL=patch-transform.js.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Rebase session persistence for multi-patch ESR version upgrades.
3
+ * Session state is stored at `.fireforge/rebase-session.json` and
4
+ * survives across CLI invocations so the user can fix conflicts and
5
+ * resume with `fireforge rebase --continue`.
6
+ */
7
+ export type RebasePatchStatus = 'pending' | 'applied-clean' | 'applied-fuzz' | 'failed' | 'resolved' | 'skipped';
8
+ export interface RebasePatchEntry {
9
+ filename: string;
10
+ status: RebasePatchStatus;
11
+ /** Fuzz factor used when status is `applied-fuzz`. */
12
+ fuzzFactor?: number;
13
+ /** Error message when status is `failed`. */
14
+ error?: string;
15
+ /** Files that caused conflicts. */
16
+ conflictingFiles?: string[];
17
+ }
18
+ export interface RebaseSession {
19
+ /** ISO timestamp when the rebase started. */
20
+ startedAt: string;
21
+ /** ESR version being rebased FROM. */
22
+ fromVersion: string;
23
+ /** ESR version being rebased TO. */
24
+ toVersion: string;
25
+ /** Commit hash recorded before the rebase started (for --abort). */
26
+ preRebaseCommit: string;
27
+ /** Ordered list of all patches and their status. */
28
+ patches: RebasePatchEntry[];
29
+ /** Index of the next patch to process (resume point). */
30
+ currentIndex: number;
31
+ }
32
+ /**
33
+ * Loads an existing rebase session, or returns `null` if none exists.
34
+ */
35
+ export declare function loadRebaseSession(projectRoot: string): Promise<RebaseSession | null>;
36
+ /**
37
+ * Persists a rebase session atomically.
38
+ */
39
+ export declare function saveRebaseSession(projectRoot: string, session: RebaseSession): Promise<void>;
40
+ /**
41
+ * Removes the rebase session file.
42
+ */
43
+ export declare function clearRebaseSession(projectRoot: string): Promise<void>;
44
+ /**
45
+ * Returns `true` when an active rebase session exists on disk.
46
+ */
47
+ export declare function hasActiveRebaseSession(projectRoot: string): Promise<boolean>;
@@ -0,0 +1,65 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Rebase session persistence for multi-patch ESR version upgrades.
4
+ * Session state is stored at `.fireforge/rebase-session.json` and
5
+ * survives across CLI invocations so the user can fix conflicts and
6
+ * resume with `fireforge rebase --continue`.
7
+ */
8
+ import { join } from 'node:path';
9
+ import { pathExists, readJson, removeFile, writeJson } from '../utils/fs.js';
10
+ import { isArray, isObject, isString } from '../utils/validation.js';
11
+ import { getProjectPaths } from './config-paths.js';
12
+ import { createSiblingLockPath, withFileLock } from './file-lock.js';
13
+ // ── Helpers ──
14
+ const SESSION_FILENAME = 'rebase-session.json';
15
+ function sessionPath(projectRoot) {
16
+ return join(getProjectPaths(projectRoot).fireforgeDir, SESSION_FILENAME);
17
+ }
18
+ function isValidSession(data) {
19
+ if (!isObject(data))
20
+ return false;
21
+ return (isString(data['startedAt']) &&
22
+ isString(data['fromVersion']) &&
23
+ isString(data['toVersion']) &&
24
+ isString(data['preRebaseCommit']) &&
25
+ isArray(data['patches']) &&
26
+ typeof data['currentIndex'] === 'number');
27
+ }
28
+ // ── Public API ──
29
+ /**
30
+ * Loads an existing rebase session, or returns `null` if none exists.
31
+ */
32
+ export async function loadRebaseSession(projectRoot) {
33
+ const path = sessionPath(projectRoot);
34
+ if (!(await pathExists(path)))
35
+ return null;
36
+ const data = await readJson(path);
37
+ if (!isValidSession(data))
38
+ return null;
39
+ return data;
40
+ }
41
+ /**
42
+ * Persists a rebase session atomically.
43
+ */
44
+ export async function saveRebaseSession(projectRoot, session) {
45
+ const path = sessionPath(projectRoot);
46
+ await withFileLock(createSiblingLockPath(path, '.rebase-session.lock'), async () => {
47
+ await writeJson(path, session);
48
+ });
49
+ }
50
+ /**
51
+ * Removes the rebase session file.
52
+ */
53
+ export async function clearRebaseSession(projectRoot) {
54
+ const path = sessionPath(projectRoot);
55
+ if (await pathExists(path)) {
56
+ await removeFile(path);
57
+ }
58
+ }
59
+ /**
60
+ * Returns `true` when an active rebase session exists on disk.
61
+ */
62
+ export async function hasActiveRebaseSession(projectRoot) {
63
+ return pathExists(sessionPath(projectRoot));
64
+ }
65
+ //# sourceMappingURL=rebase-session.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * JS/content registration in browser/base/jar.mn.
3
+ */
4
+ import type { RegisterResult } from './manifest-register.js';
5
+ /**
6
+ * Registers a JS/content file in browser/base/jar.mn.
7
+ *
8
+ * Entry format (8-space indent):
9
+ * content/browser/{name}.js (content/{name}.js)
10
+ */
11
+ export declare function registerBrowserContent(engineDir: string, fileName: string, after?: string, sourcePath?: string, dryRun?: boolean): Promise<RegisterResult>;
@@ -0,0 +1,116 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * JS/content registration in browser/base/jar.mn.
4
+ */
5
+ import { join } from 'node:path';
6
+ import { GeneralError } from '../errors/base.js';
7
+ import { pathExists, readText, writeText } from '../utils/fs.js';
8
+ import { findAlphabeticalPosition, findAlphabeticalTokenPosition } from './manifest-helpers.js';
9
+ import { tokenizeJarMn } from './manifest-tokenizers.js';
10
+ import { withParserFallback } from './parser-fallback.js';
11
+ /**
12
+ * Tokenizer-based implementation for browser content registration.
13
+ */
14
+ function registerBrowserContentTokenized(content, fileName, entry, after) {
15
+ const lines = content.split('\n');
16
+ const tokens = tokenizeJarMn(lines);
17
+ let afterFallback = false;
18
+ let insertIndex;
19
+ let previousEntry;
20
+ if (after) {
21
+ const afterPattern = new RegExp(`(?:^|/)${after.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\s|\\)|$)`);
22
+ const afterToken = tokens.find((t) => afterPattern.test(t.raw));
23
+ if (afterToken) {
24
+ insertIndex = afterToken.lineIndex + 1;
25
+ previousEntry = afterToken.raw.trim();
26
+ }
27
+ else {
28
+ afterFallback = true;
29
+ ({ insertIndex, previousEntry } = findAlphabeticalTokenPosition(tokens, /content\/browser\/([^\s]+)/, fileName));
30
+ }
31
+ }
32
+ else {
33
+ ({ insertIndex, previousEntry } = findAlphabeticalTokenPosition(tokens, /content\/browser\/([^\s]+)/, fileName));
34
+ }
35
+ if (insertIndex === -1) {
36
+ throw new GeneralError('Could not find content/browser/ section in browser/base/jar.mn');
37
+ }
38
+ lines.splice(insertIndex, 0, entry);
39
+ return { result: lines.join('\n'), previousEntry, afterFallback };
40
+ }
41
+ /**
42
+ * Legacy line-based implementation preserved as fallback.
43
+ */
44
+ function legacyRegisterBrowserContent(content, fileName, entry, after) {
45
+ const lines = content.split('\n');
46
+ let afterFallback = false;
47
+ const extractKey = (line) => {
48
+ const match = /content\/browser\/([^\s]+)/.exec(line);
49
+ return match?.[1];
50
+ };
51
+ let sectionStart = -1;
52
+ let sectionEnd = lines.length;
53
+ for (let i = 0; i < lines.length; i++) {
54
+ const line = lines[i];
55
+ if (line === undefined)
56
+ continue;
57
+ if (/^\s+content\/browser\//.test(line)) {
58
+ if (sectionStart === -1)
59
+ sectionStart = i;
60
+ sectionEnd = i + 1;
61
+ }
62
+ }
63
+ if (sectionStart === -1) {
64
+ throw new GeneralError('Could not find content/browser/ section in browser/base/jar.mn');
65
+ }
66
+ let insertIndex;
67
+ let previousEntry;
68
+ if (after) {
69
+ const afterLineIdx = lines.findIndex((l) => l.includes(after));
70
+ if (afterLineIdx !== -1) {
71
+ insertIndex = afterLineIdx + 1;
72
+ previousEntry = lines[afterLineIdx]?.trim();
73
+ }
74
+ else {
75
+ afterFallback = true;
76
+ ({ insertIndex, previousEntry } = findAlphabeticalPosition(lines, sectionStart, sectionEnd, fileName, extractKey));
77
+ }
78
+ }
79
+ else {
80
+ ({ insertIndex, previousEntry } = findAlphabeticalPosition(lines, sectionStart, sectionEnd, fileName, extractKey));
81
+ }
82
+ lines.splice(insertIndex, 0, entry);
83
+ return { result: lines.join('\n'), previousEntry, afterFallback };
84
+ }
85
+ /**
86
+ * Registers a JS/content file in browser/base/jar.mn.
87
+ *
88
+ * Entry format (8-space indent):
89
+ * content/browser/{name}.js (content/{name}.js)
90
+ */
91
+ export async function registerBrowserContent(engineDir, fileName, after, sourcePath, dryRun = false) {
92
+ const manifest = 'browser/base/jar.mn';
93
+ const manifestPath = join(engineDir, manifest);
94
+ if (!(await pathExists(manifestPath))) {
95
+ throw new GeneralError(`Manifest not found: ${manifest}`);
96
+ }
97
+ const source = (sourcePath ?? `content/${fileName}`).replace(/\\/g, '/');
98
+ const entry = ` content/browser/${fileName} (${source})`.replace(/\\/g, '/');
99
+ const content = await readText(manifestPath);
100
+ // Idempotency check
101
+ if (content.includes(`content/browser/${fileName}`)) {
102
+ return { manifest, entry, skipped: true };
103
+ }
104
+ const { value } = withParserFallback(() => registerBrowserContentTokenized(content, fileName, entry, after), () => legacyRegisterBrowserContent(content, fileName, entry, after), manifest);
105
+ if (!dryRun) {
106
+ await writeText(manifestPath, value.result);
107
+ }
108
+ return {
109
+ manifest,
110
+ entry,
111
+ previousEntry: value.previousEntry,
112
+ skipped: false,
113
+ afterFallback: value.afterFallback,
114
+ };
115
+ }
116
+ //# sourceMappingURL=register-browser-content.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Module registration in browser/modules/{binaryName}/moz.build.
3
+ */
4
+ import type { RegisterResult } from './manifest-register.js';
5
+ /**
6
+ * Registers a module in browser/modules/{binaryName}/moz.build.
7
+ *
8
+ * Entry format:
9
+ * "{name}.sys.mjs",
10
+ */
11
+ export declare function registerFireForgeModule(engineDir: string, fileName: string, moduleDir: string, dryRun?: boolean): Promise<RegisterResult>;
@@ -0,0 +1,76 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Module registration in browser/modules/{binaryName}/moz.build.
4
+ */
5
+ import { join } from 'node:path';
6
+ import { GeneralError } from '../errors/base.js';
7
+ import { pathExists, readText, writeText } from '../utils/fs.js';
8
+ import { findAlphabeticalMozBuildPosition, findAlphabeticalPosition } from './manifest-helpers.js';
9
+ import { tokenizeMozBuildList } from './manifest-tokenizers.js';
10
+ import { withParserFallback } from './parser-fallback.js';
11
+ /**
12
+ * Tokenizer-based implementation for module registration.
13
+ */
14
+ function registerFireForgeModuleTokenized(content, fileName, entry) {
15
+ const lines = content.split('\n');
16
+ const listResult = tokenizeMozBuildList(lines, /EXTRA_JS_MODULES/);
17
+ if (!listResult) {
18
+ throw new GeneralError('Could not find EXTRA_JS_MODULES in moz.build');
19
+ }
20
+ const { insertIndex, previousEntry } = findAlphabeticalMozBuildPosition(listResult.tokens, fileName);
21
+ lines.splice(insertIndex, 0, entry);
22
+ return { result: lines.join('\n'), previousEntry };
23
+ }
24
+ /**
25
+ * Legacy line-based implementation preserved as fallback.
26
+ */
27
+ function legacyRegisterFireForgeModule(content, fileName, entry, moduleDir) {
28
+ const lines = content.split('\n');
29
+ const extractKey = (line) => {
30
+ const match = /^\s+"([^"]+\.sys\.mjs)"/.exec(line);
31
+ return match?.[1];
32
+ };
33
+ let sectionStart = -1;
34
+ let sectionEnd = lines.length;
35
+ for (let i = 0; i < lines.length; i++) {
36
+ const line = lines[i];
37
+ if (line === undefined)
38
+ continue;
39
+ if (/^\s+"[^"]+\.sys\.mjs"/.test(line)) {
40
+ if (sectionStart === -1)
41
+ sectionStart = i;
42
+ sectionEnd = i + 1;
43
+ }
44
+ }
45
+ if (sectionStart === -1) {
46
+ throw new GeneralError(`Could not find module list section in ${moduleDir}/moz.build`);
47
+ }
48
+ const { insertIndex, previousEntry } = findAlphabeticalPosition(lines, sectionStart, sectionEnd, fileName, extractKey);
49
+ lines.splice(insertIndex, 0, entry);
50
+ return { result: lines.join('\n'), previousEntry };
51
+ }
52
+ /**
53
+ * Registers a module in browser/modules/{binaryName}/moz.build.
54
+ *
55
+ * Entry format:
56
+ * "{name}.sys.mjs",
57
+ */
58
+ export async function registerFireForgeModule(engineDir, fileName, moduleDir, dryRun = false) {
59
+ const manifest = `${moduleDir}/moz.build`;
60
+ const manifestPath = join(engineDir, manifest);
61
+ if (!(await pathExists(manifestPath))) {
62
+ throw new GeneralError(`Manifest not found: ${manifest}`);
63
+ }
64
+ const entry = ` "${fileName}",`.replace(/\\/g, '/');
65
+ const content = await readText(manifestPath);
66
+ // Idempotency check
67
+ if (content.includes(`"${fileName}"`)) {
68
+ return { manifest, entry, skipped: true };
69
+ }
70
+ const { value } = withParserFallback(() => registerFireForgeModuleTokenized(content, fileName, entry), () => legacyRegisterFireForgeModule(content, fileName, entry, moduleDir), manifest);
71
+ if (!dryRun) {
72
+ await writeText(manifestPath, value.result);
73
+ }
74
+ return { manifest, entry, previousEntry: value.previousEntry, skipped: false };
75
+ }
76
+ //# sourceMappingURL=register-module.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * CSS registration in browser/themes/shared/jar.inc.mn.
3
+ */
4
+ import type { RegisterResult } from './manifest-register.js';
5
+ /**
6
+ * Registers a CSS file in browser/themes/shared/jar.inc.mn.
7
+ *
8
+ * Entry format:
9
+ * skin/classic/browser/{name}.css (../shared/{name}.css)
10
+ */
11
+ export declare function registerSharedCSS(engineDir: string, fileName: string, after?: string, dryRun?: boolean): Promise<RegisterResult>;
@@ -0,0 +1,117 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * CSS registration in browser/themes/shared/jar.inc.mn.
4
+ */
5
+ import { basename, join } from 'node:path';
6
+ import { GeneralError } from '../errors/base.js';
7
+ import { pathExists, readText, writeText } from '../utils/fs.js';
8
+ import { findAlphabeticalPosition, findAlphabeticalTokenPosition } from './manifest-helpers.js';
9
+ import { tokenizeJarMn } from './manifest-tokenizers.js';
10
+ import { withParserFallback } from './parser-fallback.js';
11
+ /**
12
+ * Tokenizer-based implementation for shared CSS registration.
13
+ */
14
+ function registerSharedCSSTokenized(content, name, entry, after) {
15
+ const lines = content.split('\n');
16
+ const tokens = tokenizeJarMn(lines);
17
+ let afterFallback = false;
18
+ let insertIndex;
19
+ let previousEntry;
20
+ if (after) {
21
+ const afterPattern = new RegExp(`(?:^|/)${after.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\s|\\)|$)`);
22
+ const afterToken = tokens.find((t) => afterPattern.test(t.raw));
23
+ if (afterToken) {
24
+ insertIndex = afterToken.lineIndex + 1;
25
+ previousEntry = afterToken.raw.trim();
26
+ }
27
+ else {
28
+ afterFallback = true;
29
+ ({ insertIndex, previousEntry } = findAlphabeticalTokenPosition(tokens, /skin\/classic\/browser\/([^.]+)\.css/, name));
30
+ }
31
+ }
32
+ else {
33
+ ({ insertIndex, previousEntry } = findAlphabeticalTokenPosition(tokens, /skin\/classic\/browser\/([^.]+)\.css/, name));
34
+ }
35
+ if (insertIndex === -1) {
36
+ throw new GeneralError('Could not find skin/classic/browser/ section in jar.inc.mn');
37
+ }
38
+ lines.splice(insertIndex, 0, entry);
39
+ return { result: lines.join('\n'), insertIndex, previousEntry, afterFallback };
40
+ }
41
+ /**
42
+ * Legacy line-based implementation preserved as fallback.
43
+ */
44
+ function legacyRegisterSharedCSS(content, name, entry, after) {
45
+ const lines = content.split('\n');
46
+ let afterFallback = false;
47
+ const extractKey = (line) => {
48
+ const match = /skin\/classic\/browser\/([^.]+)\.css/.exec(line);
49
+ return match?.[1];
50
+ };
51
+ let insertIndex;
52
+ let previousEntry;
53
+ // Find skin/classic/browser/ section boundaries
54
+ let sectionStart = -1;
55
+ let sectionEnd = lines.length;
56
+ for (let i = 0; i < lines.length; i++) {
57
+ const line = lines[i];
58
+ if (line === undefined)
59
+ continue;
60
+ if (/skin\/classic\/browser\//.test(line)) {
61
+ if (sectionStart === -1)
62
+ sectionStart = i;
63
+ sectionEnd = i + 1;
64
+ }
65
+ }
66
+ if (sectionStart === -1) {
67
+ throw new GeneralError('Could not find skin/classic/browser/ section in jar.inc.mn');
68
+ }
69
+ if (after) {
70
+ const afterLineIdx = lines.findIndex((l) => l.includes(after));
71
+ if (afterLineIdx !== -1) {
72
+ insertIndex = afterLineIdx + 1;
73
+ previousEntry = lines[afterLineIdx]?.trim();
74
+ }
75
+ else {
76
+ afterFallback = true;
77
+ ({ insertIndex, previousEntry } = findAlphabeticalPosition(lines, sectionStart, sectionEnd, name, extractKey));
78
+ }
79
+ }
80
+ else {
81
+ ({ insertIndex, previousEntry } = findAlphabeticalPosition(lines, sectionStart, sectionEnd, name, extractKey));
82
+ }
83
+ lines.splice(insertIndex, 0, entry);
84
+ return { result: lines.join('\n'), previousEntry, afterFallback };
85
+ }
86
+ /**
87
+ * Registers a CSS file in browser/themes/shared/jar.inc.mn.
88
+ *
89
+ * Entry format:
90
+ * skin/classic/browser/{name}.css (../shared/{name}.css)
91
+ */
92
+ export async function registerSharedCSS(engineDir, fileName, after, dryRun = false) {
93
+ const manifest = 'browser/themes/shared/jar.inc.mn';
94
+ const manifestPath = join(engineDir, manifest);
95
+ if (!(await pathExists(manifestPath))) {
96
+ throw new GeneralError(`Manifest not found: ${manifest}`);
97
+ }
98
+ const name = basename(fileName, '.css');
99
+ const entry = ` skin/classic/browser/${name}.css (../shared/${name}.css)`.replace(/\\/g, '/');
100
+ const content = await readText(manifestPath);
101
+ // Idempotency check
102
+ if (content.includes(`skin/classic/browser/${name}.css`)) {
103
+ return { manifest, entry, skipped: true };
104
+ }
105
+ const { value } = withParserFallback(() => registerSharedCSSTokenized(content, name, entry, after), () => legacyRegisterSharedCSS(content, name, entry, after), manifest);
106
+ if (!dryRun) {
107
+ await writeText(manifestPath, value.result);
108
+ }
109
+ return {
110
+ manifest,
111
+ entry,
112
+ previousEntry: value.previousEntry,
113
+ skipped: false,
114
+ afterFallback: value.afterFallback,
115
+ };
116
+ }
117
+ //# sourceMappingURL=register-shared-css.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Test manifest registration in browser/base/moz.build.
3
+ */
4
+ import type { RegisterResult } from './manifest-register.js';
5
+ /**
6
+ * Registers a test manifest (browser.toml) in browser/base/moz.build.
7
+ *
8
+ * Entry format:
9
+ * "content/test/{dir}/browser.toml",
10
+ */
11
+ export declare function registerTestManifest(engineDir: string, testDir: string, dryRun?: boolean): Promise<RegisterResult>;
12
+ /**
13
+ * Deregisters a test manifest (browser.toml) from browser/base/moz.build.
14
+ * @param engineDir - Path to the engine directory
15
+ * @param testDir - Test directory name (e.g. 'mybrowser')
16
+ * @returns Whether the entry was removed
17
+ */
18
+ export declare function deregisterTestManifest(engineDir: string, testDir: string): Promise<boolean>;