@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,246 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { basename, join } from 'node:path';
5
+ import { toError } from '../utils/errors.js';
6
+ import { pathExists, readText } from '../utils/fs.js';
7
+ import { verbose } from '../utils/logger.js';
8
+ import { exec } from '../utils/process.js';
9
+ import { ensureGit } from './git-base.js';
10
+ import { fileExistsInHead } from './git-file-ops.js';
11
+ import { getUntrackedFiles } from './git-status.js';
12
+ /**
13
+ * Gets the diff for a specific file.
14
+ * @param repoDir - Repository directory
15
+ * @param filePath - Path to the file (relative to repo)
16
+ * @returns Diff content
17
+ */
18
+ export async function getFileDiff(repoDir, filePath) {
19
+ await ensureGit();
20
+ const result = await exec('git', ['diff', 'HEAD', '--', filePath], { cwd: repoDir });
21
+ return result.stdout;
22
+ }
23
+ /**
24
+ * Generates a unified diff for a new (untracked) file.
25
+ * @param repoDir - Repository directory
26
+ * @param filePath - Path to the file (relative to repo)
27
+ * @returns Diff content in unified diff format
28
+ */
29
+ export async function generateNewFileDiff(repoDir, filePath) {
30
+ const fullPath = join(repoDir, filePath);
31
+ const content = await readText(fullPath);
32
+ // Compute the abbreviated git blob hash for the index line
33
+ let blobHash = '0000000000';
34
+ try {
35
+ const hashResult = await exec('git', ['hash-object', fullPath], { cwd: repoDir });
36
+ const fullHash = hashResult.stdout.trim();
37
+ if (fullHash.length >= 10) {
38
+ blobHash = fullHash.slice(0, 10);
39
+ }
40
+ }
41
+ catch (error) {
42
+ verbose(`git hash-object failed for ${filePath}; falling back to zero blob hash: ${toError(error).message}`);
43
+ }
44
+ // Handle empty files
45
+ if (content.length === 0) {
46
+ return [
47
+ `diff --git a/${filePath} b/${filePath}`,
48
+ 'new file mode 100644',
49
+ `index 0000000000..${blobHash}`,
50
+ '--- /dev/null',
51
+ `+++ b/${filePath}`,
52
+ '',
53
+ ].join('\n');
54
+ }
55
+ const lines = content.split('\n');
56
+ // Handle files that don't end with newline
57
+ const hasTrailingNewline = content.endsWith('\n');
58
+ const lineCount = hasTrailingNewline ? lines.length - 1 : lines.length;
59
+ // Build the unified diff format for a new file
60
+ const diffLines = [
61
+ `diff --git a/${filePath} b/${filePath}`,
62
+ 'new file mode 100644',
63
+ `index 0000000000..${blobHash}`,
64
+ '--- /dev/null',
65
+ `+++ b/${filePath}`,
66
+ `@@ -0,0 +1,${lineCount} @@`,
67
+ ];
68
+ // Add each line with a + prefix
69
+ for (let i = 0; i < lineCount; i++) {
70
+ diffLines.push(`+${lines[i]}`);
71
+ }
72
+ // Add "No newline at end of file" marker if needed
73
+ if (!hasTrailingNewline && lineCount > 0) {
74
+ diffLines.push('\');
75
+ }
76
+ return diffLines.join('\n') + '\n';
77
+ }
78
+ /**
79
+ * Generates a patch for a file.
80
+ * If the file is tracked in HEAD, it generates a standard contextual diff.
81
+ * If the file is untracked (new), it generates a "new file" format patch (snapshot).
82
+ * This ensures standard 3-way mergeable context diffs for existing Mozilla files.
83
+ * @param repoDir - Repository directory
84
+ * @param filePath - Path to the file (relative to repo)
85
+ * @returns Diff content in unified diff format
86
+ */
87
+ export async function generateFullFilePatch(repoDir, filePath) {
88
+ await ensureGit();
89
+ // If file exists in HEAD, use standard git diff HEAD -- <file>
90
+ // This generates a contextual diff that is safer for rebasing
91
+ if (await fileExistsInHead(repoDir, filePath)) {
92
+ return getFileDiff(repoDir, filePath);
93
+ }
94
+ // If file is new/untracked, use the full-file "new file" format
95
+ return generateNewFileDiff(repoDir, filePath);
96
+ }
97
+ /**
98
+ * Generates a unified diff between base content and current file content.
99
+ * @param repoDir - Repository directory
100
+ * @param filePath - Path to the file (relative to repo)
101
+ * @param baseContent - The base content to diff against
102
+ * @returns Unified diff in git format
103
+ */
104
+ export async function generateModificationDiff(repoDir, filePath, baseContent) {
105
+ const fullPath = join(repoDir, filePath);
106
+ const currentContent = await readText(fullPath);
107
+ // If contents are identical, return empty diff
108
+ if (baseContent === currentContent) {
109
+ return '';
110
+ }
111
+ const tempDir = await mkdtemp(join(tmpdir(), 'fireforge-diff-'));
112
+ const tempFile = join(tempDir, basename(filePath));
113
+ try {
114
+ await writeFile(tempFile, baseContent);
115
+ // git diff --no-index exits code 1 when files differ — that's normal
116
+ const result = await exec('git', ['diff', '--no-index', '--', tempFile, fullPath], {
117
+ cwd: repoDir,
118
+ });
119
+ const output = result.stdout;
120
+ if (!output) {
121
+ return '';
122
+ }
123
+ // Post-process: fix paths in the diff header only (before the first @@ hunk)
124
+ const lines = output.split('\n');
125
+ let pastHeader = false;
126
+ const fixedLines = lines.map((line) => {
127
+ if (!pastHeader && line.startsWith('@@')) {
128
+ pastHeader = true;
129
+ }
130
+ if (!pastHeader) {
131
+ if (line.startsWith('diff --git')) {
132
+ return `diff --git a/${filePath} b/${filePath}`;
133
+ }
134
+ if (line.startsWith('--- ')) {
135
+ return `--- a/${filePath}`;
136
+ }
137
+ if (line.startsWith('+++ ')) {
138
+ return `+++ b/${filePath}`;
139
+ }
140
+ }
141
+ return line;
142
+ });
143
+ return fixedLines.join('\n');
144
+ }
145
+ finally {
146
+ await rm(tempDir, { recursive: true, force: true });
147
+ }
148
+ }
149
+ /**
150
+ * Gets the diff for all modified files, including untracked (new) files.
151
+ * @param repoDir - Repository directory
152
+ * @returns Diff content
153
+ */
154
+ export async function getAllDiff(repoDir) {
155
+ await ensureGit();
156
+ // Get diff for tracked files
157
+ const result = await exec('git', ['diff', 'HEAD'], { cwd: repoDir });
158
+ const trackedDiff = result.stdout;
159
+ // Get untracked files (properly expanded, not directories)
160
+ const untrackedFiles = await getUntrackedFiles(repoDir);
161
+ // Generate diffs for untracked files
162
+ const untrackedDiffs = [];
163
+ for (const file of untrackedFiles) {
164
+ const diff = await generateNewFileDiff(repoDir, file);
165
+ untrackedDiffs.push(diff);
166
+ }
167
+ // Combine all diffs — each already ends with \n, so concatenate directly
168
+ // to avoid inserting blank lines between diff sections.
169
+ const allDiffs = [trackedDiff, ...untrackedDiffs].filter((d) => d.trim().length > 0);
170
+ const combined = allDiffs.join('');
171
+ return combined.endsWith('\n') ? combined : combined + '\n';
172
+ }
173
+ /**
174
+ * Builds a combined diff against HEAD for the provided files without touching
175
+ * the real git index. Tracked files use `git diff HEAD`; untracked files use
176
+ * synthesized new-file diffs.
177
+ * @param repoDir - Repository directory
178
+ * @param files - File paths to diff (relative to repo root)
179
+ * @returns Combined diff content
180
+ */
181
+ export async function getDiffForFilesAgainstHead(repoDir, files) {
182
+ await ensureGit();
183
+ const uniqueFiles = [...new Set(files)].sort();
184
+ const diffs = [];
185
+ for (const file of uniqueFiles) {
186
+ if (await fileExistsInHead(repoDir, file)) {
187
+ const diff = await getFileDiff(repoDir, file);
188
+ if (diff.trim()) {
189
+ diffs.push(diff);
190
+ }
191
+ continue;
192
+ }
193
+ if (!(await pathExists(join(repoDir, file)))) {
194
+ continue;
195
+ }
196
+ const diff = await generateNewFileDiff(repoDir, file);
197
+ if (diff.trim()) {
198
+ diffs.push(diff);
199
+ }
200
+ }
201
+ if (diffs.length === 0) {
202
+ return '';
203
+ }
204
+ // Each diff from git already ends with \n. Concatenate directly to
205
+ // preserve context lines (including trailing whitespace-only context)
206
+ // and avoid inserting blank lines between diff sections.
207
+ const combined = diffs.join('');
208
+ return combined.endsWith('\n') ? combined : combined + '\n';
209
+ }
210
+ /**
211
+ * Generates a combined diff for staged files against HEAD.
212
+ * @param repoDir - Repository directory
213
+ * @param files - File paths to diff (relative to repo)
214
+ * @returns Diff content for the staged files
215
+ */
216
+ export async function getStagedDiffForFiles(repoDir, files) {
217
+ await ensureGit();
218
+ const result = await exec('git', ['diff', '--cached', 'HEAD', '--', ...files], { cwd: repoDir });
219
+ return result.stdout;
220
+ }
221
+ /**
222
+ * Generates a GIT binary patch for a binary file.
223
+ * For tracked files, uses `git diff --binary HEAD`.
224
+ * For untracked files, temporarily stages with `--intent-to-add` to produce a diff.
225
+ * @param repoDir - Repository directory
226
+ * @param filePath - File path (relative to repo root)
227
+ * @returns The binary diff string, or empty string if no diff
228
+ */
229
+ export async function generateBinaryFilePatch(repoDir, filePath) {
230
+ await ensureGit();
231
+ // Try tracked file diff first
232
+ const result = await exec('git', ['diff', '--binary', 'HEAD', '--', filePath], { cwd: repoDir });
233
+ if (result.stdout.trim())
234
+ return result.stdout;
235
+ // For untracked files, stage temporarily to produce a binary diff
236
+ try {
237
+ await exec('git', ['add', '--intent-to-add', '--', filePath], { cwd: repoDir });
238
+ const diffResult = await exec('git', ['diff', '--binary', '--', filePath], { cwd: repoDir });
239
+ return diffResult.stdout;
240
+ }
241
+ finally {
242
+ // Always unstage, even if diff fails
243
+ await exec('git', ['reset', 'HEAD', '--', filePath], { cwd: repoDir });
244
+ }
245
+ }
246
+ //# sourceMappingURL=git-diff.js.map
@@ -0,0 +1,65 @@
1
+ import type { GitStatusEntry } from './git-base.js';
2
+ /**
3
+ * Discards changes to a specific file.
4
+ * @param repoDir - Repository directory
5
+ * @param filePath - Path to the file (relative to repo)
6
+ */
7
+ export declare function discardFile(repoDir: string, filePath: string): Promise<void>;
8
+ /**
9
+ * Restores a tracked path from HEAD, including staged changes.
10
+ * @param repoDir - Repository directory
11
+ * @param filePath - Path to the file (relative to repo)
12
+ */
13
+ export declare function restoreTrackedPath(repoDir: string, filePath: string): Promise<void>;
14
+ /**
15
+ * Removes an untracked path from disk.
16
+ * @param repoDir - Repository directory
17
+ * @param filePath - Path to the file (relative to repo)
18
+ */
19
+ export declare function removeUntrackedPath(repoDir: string, filePath: string): Promise<void>;
20
+ /**
21
+ * Removes a path that is present only in the index/worktree and not in HEAD.
22
+ * @param repoDir - Repository directory
23
+ * @param filePath - Path to remove
24
+ */
25
+ export declare function removeAddedPath(repoDir: string, filePath: string): Promise<void>;
26
+ /**
27
+ * Discards a status entry according to its git state.
28
+ * @param repoDir - Repository directory
29
+ * @param entry - Parsed git status entry
30
+ */
31
+ export declare function discardStatusEntry(repoDir: string, entry: GitStatusEntry): Promise<void>;
32
+ /**
33
+ * Stages specific files in the repository.
34
+ * @param repoDir - Repository directory
35
+ * @param files - File paths to stage (relative to repo)
36
+ */
37
+ export declare function stageFiles(repoDir: string, files: string[]): Promise<void>;
38
+ /**
39
+ * Unstages specific files from the index.
40
+ * @param repoDir - Repository directory
41
+ * @param files - File paths to unstage (relative to repo)
42
+ */
43
+ export declare function unstageFiles(repoDir: string, files: string[]): Promise<void>;
44
+ /**
45
+ * Checks if a file exists in the HEAD commit.
46
+ * @param repoDir - Repository directory
47
+ * @param filePath - Path to the file (relative to repo)
48
+ * @returns true if file exists in HEAD
49
+ */
50
+ export declare function fileExistsInHead(repoDir: string, filePath: string): Promise<boolean>;
51
+ /**
52
+ * Gets the content of a file from HEAD commit.
53
+ * @param repoDir - Repository directory
54
+ * @param filePath - Path to the file (relative to repo)
55
+ * @returns File content or null if file doesn't exist in HEAD
56
+ */
57
+ export declare function getFileContentFromHead(repoDir: string, filePath: string): Promise<string | null>;
58
+ /**
59
+ * Checks if a file is binary by looking for NUL bytes in the first 8KB.
60
+ * Uses the same heuristic as git.
61
+ * @param repoDir - Repository directory
62
+ * @param filePath - File path (relative to repo root)
63
+ * @returns true if the file appears to be binary
64
+ */
65
+ export declare function isBinaryFile(repoDir: string, filePath: string): Promise<boolean>;
@@ -0,0 +1,141 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { open } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { removeFile } from '../utils/fs.js';
5
+ import { exec } from '../utils/process.js';
6
+ import { ensureGit, git } from './git-base.js';
7
+ /**
8
+ * Discards changes to a specific file.
9
+ * @param repoDir - Repository directory
10
+ * @param filePath - Path to the file (relative to repo)
11
+ */
12
+ export async function discardFile(repoDir, filePath) {
13
+ await restoreTrackedPath(repoDir, filePath);
14
+ }
15
+ /**
16
+ * Restores a tracked path from HEAD, including staged changes.
17
+ * @param repoDir - Repository directory
18
+ * @param filePath - Path to the file (relative to repo)
19
+ */
20
+ export async function restoreTrackedPath(repoDir, filePath) {
21
+ await ensureGit();
22
+ await git(['restore', '--source', 'HEAD', '--staged', '--worktree', '--', filePath], repoDir);
23
+ }
24
+ /**
25
+ * Removes an untracked path from disk.
26
+ * @param repoDir - Repository directory
27
+ * @param filePath - Path to the file (relative to repo)
28
+ */
29
+ export async function removeUntrackedPath(repoDir, filePath) {
30
+ const fullPath = join(repoDir, filePath);
31
+ await removeFile(fullPath);
32
+ }
33
+ /**
34
+ * Removes a path that is present only in the index/worktree and not in HEAD.
35
+ * @param repoDir - Repository directory
36
+ * @param filePath - Path to remove
37
+ */
38
+ export async function removeAddedPath(repoDir, filePath) {
39
+ await ensureGit();
40
+ await exec('git', ['reset', 'HEAD', '--', filePath], { cwd: repoDir });
41
+ await removeUntrackedPath(repoDir, filePath);
42
+ }
43
+ /**
44
+ * Discards a status entry according to its git state.
45
+ * @param repoDir - Repository directory
46
+ * @param entry - Parsed git status entry
47
+ */
48
+ export async function discardStatusEntry(repoDir, entry) {
49
+ if (entry.isUntracked) {
50
+ await removeUntrackedPath(repoDir, entry.file);
51
+ return;
52
+ }
53
+ if (entry.isRenameOrCopy && entry.originalPath) {
54
+ await restoreTrackedPath(repoDir, entry.originalPath);
55
+ if (await fileExistsInHead(repoDir, entry.file)) {
56
+ await restoreTrackedPath(repoDir, entry.file);
57
+ }
58
+ else {
59
+ await removeAddedPath(repoDir, entry.file);
60
+ }
61
+ return;
62
+ }
63
+ if (!(await fileExistsInHead(repoDir, entry.file))) {
64
+ await removeAddedPath(repoDir, entry.file);
65
+ return;
66
+ }
67
+ await restoreTrackedPath(repoDir, entry.file);
68
+ }
69
+ /**
70
+ * Stages specific files in the repository.
71
+ * @param repoDir - Repository directory
72
+ * @param files - File paths to stage (relative to repo)
73
+ */
74
+ export async function stageFiles(repoDir, files) {
75
+ await ensureGit();
76
+ await git(['add', '--', ...files], repoDir);
77
+ }
78
+ /**
79
+ * Unstages specific files from the index.
80
+ * @param repoDir - Repository directory
81
+ * @param files - File paths to unstage (relative to repo)
82
+ */
83
+ export async function unstageFiles(repoDir, files) {
84
+ await ensureGit();
85
+ await git(['reset', 'HEAD', '--', ...files], repoDir);
86
+ }
87
+ /**
88
+ * Checks if a file exists in the HEAD commit.
89
+ * @param repoDir - Repository directory
90
+ * @param filePath - Path to the file (relative to repo)
91
+ * @returns true if file exists in HEAD
92
+ */
93
+ export async function fileExistsInHead(repoDir, filePath) {
94
+ await ensureGit();
95
+ const result = await exec('git', ['ls-tree', 'HEAD', '--', filePath], { cwd: repoDir });
96
+ return result.stdout.trim().length > 0;
97
+ }
98
+ /**
99
+ * Gets the content of a file from HEAD commit.
100
+ * @param repoDir - Repository directory
101
+ * @param filePath - Path to the file (relative to repo)
102
+ * @returns File content or null if file doesn't exist in HEAD
103
+ */
104
+ export async function getFileContentFromHead(repoDir, filePath) {
105
+ await ensureGit();
106
+ const result = await exec('git', ['show', `HEAD:${filePath}`], { cwd: repoDir });
107
+ if (result.exitCode !== 0) {
108
+ return null;
109
+ }
110
+ return result.stdout;
111
+ }
112
+ /**
113
+ * Checks if a file is binary by looking for NUL bytes in the first 8KB.
114
+ * Uses the same heuristic as git.
115
+ * @param repoDir - Repository directory
116
+ * @param filePath - File path (relative to repo root)
117
+ * @returns true if the file appears to be binary
118
+ */
119
+ export async function isBinaryFile(repoDir, filePath) {
120
+ const fullPath = join(repoDir, filePath);
121
+ try {
122
+ const fh = await open(fullPath, 'r');
123
+ try {
124
+ const buf = Buffer.alloc(8192);
125
+ const { bytesRead } = await fh.read(buf, 0, 8192, 0);
126
+ for (let i = 0; i < bytesRead; i++) {
127
+ if (buf[i] === 0)
128
+ return true;
129
+ }
130
+ return false;
131
+ }
132
+ finally {
133
+ await fh.close();
134
+ }
135
+ }
136
+ catch (error) {
137
+ void error;
138
+ return false;
139
+ }
140
+ }
141
+ //# sourceMappingURL=git-file-ops.js.map
@@ -0,0 +1,65 @@
1
+ import type { GitStatusEntry } from './git-base.js';
2
+ /**
3
+ * Parses NUL-delimited porcelain status output.
4
+ * @param output - Raw git status output
5
+ * @returns Parsed entries
6
+ */
7
+ /** @internal Exported for testing */
8
+ export declare function parsePorcelainStatus(output: string): GitStatusEntry[];
9
+ /**
10
+ * Gets structured working tree status entries.
11
+ * @param repoDir - Repository directory
12
+ * @returns Parsed git status entries
13
+ */
14
+ export declare function getWorkingTreeStatus(repoDir: string): Promise<GitStatusEntry[]>;
15
+ /**
16
+ * Expands collapsed untracked directory entries into individual file entries.
17
+ * Git status may report "?? dir/" instead of listing each file underneath.
18
+ * @param repoDir - Repository directory
19
+ * @param entries - Parsed status entries
20
+ * @returns Status entries with untracked directories expanded to individual files
21
+ */
22
+ export declare function expandUntrackedDirectoryEntries(repoDir: string, entries: GitStatusEntry[]): Promise<GitStatusEntry[]>;
23
+ /**
24
+ * Gets the list of modified files.
25
+ * @param repoDir - Repository directory
26
+ * @returns List of modified file paths
27
+ */
28
+ export declare function getModifiedFiles(repoDir: string): Promise<string[]>;
29
+ /**
30
+ * Gets all untracked files (including files inside untracked directories).
31
+ * @param repoDir - Repository directory
32
+ * @returns List of untracked file paths
33
+ */
34
+ export declare function getUntrackedFiles(repoDir: string): Promise<string[]>;
35
+ /**
36
+ * Gets untracked files within a specific directory.
37
+ * Uses path-scoped git ls-files for efficiency in large repos.
38
+ * @param repoDir - Repository directory
39
+ * @param dir - Directory path (relative to repo root)
40
+ * @returns List of untracked file paths relative to repo root
41
+ */
42
+ export declare function getUntrackedFilesInDir(repoDir: string, dir: string): Promise<string[]>;
43
+ /**
44
+ * Gets modified (tracked) files within a specific directory.
45
+ * Uses path-scoped git diff for efficiency in large repos.
46
+ * @param repoDir - Repository directory
47
+ * @param dir - Directory path (relative to repo root)
48
+ * @returns List of modified file paths relative to repo root
49
+ */
50
+ export declare function getModifiedFilesInDir(repoDir: string, dir: string): Promise<string[]>;
51
+ /**
52
+ * Checks if any of the specified files have uncommitted changes.
53
+ * @param repoDir - Repository directory
54
+ * @param files - File paths to check (relative to repo root)
55
+ * @returns List of dirty file paths
56
+ */
57
+ export declare function getDirtyFiles(repoDir: string, files: string[]): Promise<string[]>;
58
+ /**
59
+ * Lists all files in a directory (tracked and untracked, respecting .gitignore).
60
+ * Combines git ls-files for tracked files and --others for untracked files.
61
+ * @param repoDir - Repository directory
62
+ * @param dir - Directory path (relative to repo root)
63
+ * @returns List of file paths relative to repo root
64
+ */
65
+ export declare function listAllFilesInDir(repoDir: string, dir: string): Promise<string[]>;
@@ -0,0 +1,163 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { exec } from '../utils/process.js';
3
+ import { ensureGit } from './git-base.js';
4
+ /**
5
+ * Parses NUL-delimited porcelain status output.
6
+ * @param output - Raw git status output
7
+ * @returns Parsed entries
8
+ */
9
+ /** @internal Exported for testing */
10
+ export function parsePorcelainStatus(output) {
11
+ const records = output.split('\0').filter((record) => record.length > 0);
12
+ const entries = [];
13
+ for (let i = 0; i < records.length; i++) {
14
+ const record = records[i];
15
+ if (!record || record.length < 4)
16
+ continue;
17
+ const indexStatus = record[0] ?? ' ';
18
+ const worktreeStatus = record[1] ?? ' ';
19
+ const status = `${indexStatus}${worktreeStatus}`;
20
+ const pathField = record.slice(3);
21
+ const isRenameOrCopy = indexStatus === 'R' || indexStatus === 'C';
22
+ const originalPath = isRenameOrCopy ? records[i + 1] : undefined;
23
+ if (isRenameOrCopy) {
24
+ i++;
25
+ }
26
+ entries.push({
27
+ status,
28
+ indexStatus,
29
+ worktreeStatus,
30
+ file: pathField,
31
+ ...(originalPath !== undefined ? { originalPath } : {}),
32
+ isUntracked: indexStatus === '?' && worktreeStatus === '?',
33
+ isRenameOrCopy,
34
+ isDeleted: indexStatus === 'D' || worktreeStatus === 'D',
35
+ });
36
+ }
37
+ return entries;
38
+ }
39
+ /**
40
+ * Gets structured working tree status entries.
41
+ * @param repoDir - Repository directory
42
+ * @returns Parsed git status entries
43
+ */
44
+ export async function getWorkingTreeStatus(repoDir) {
45
+ await ensureGit();
46
+ const result = await exec('git', ['status', '--porcelain=v1', '-z'], { cwd: repoDir });
47
+ return parsePorcelainStatus(result.stdout);
48
+ }
49
+ /**
50
+ * Expands collapsed untracked directory entries into individual file entries.
51
+ * Git status may report "?? dir/" instead of listing each file underneath.
52
+ * @param repoDir - Repository directory
53
+ * @param entries - Parsed status entries
54
+ * @returns Status entries with untracked directories expanded to individual files
55
+ */
56
+ export async function expandUntrackedDirectoryEntries(repoDir, entries) {
57
+ const expanded = [];
58
+ for (const entry of entries) {
59
+ if (!entry.isUntracked || !entry.file.endsWith('/')) {
60
+ expanded.push(entry);
61
+ continue;
62
+ }
63
+ const individualFiles = await getUntrackedFilesInDir(repoDir, entry.file);
64
+ for (const file of individualFiles) {
65
+ expanded.push({
66
+ status: '??',
67
+ indexStatus: '?',
68
+ worktreeStatus: '?',
69
+ file,
70
+ isUntracked: true,
71
+ isRenameOrCopy: false,
72
+ isDeleted: false,
73
+ });
74
+ }
75
+ }
76
+ return expanded;
77
+ }
78
+ /**
79
+ * Gets the list of modified files.
80
+ * @param repoDir - Repository directory
81
+ * @returns List of modified file paths
82
+ */
83
+ export async function getModifiedFiles(repoDir) {
84
+ const entries = await getWorkingTreeStatus(repoDir);
85
+ return entries.map((entry) => entry.file);
86
+ }
87
+ /**
88
+ * Gets all untracked files (including files inside untracked directories).
89
+ * @param repoDir - Repository directory
90
+ * @returns List of untracked file paths
91
+ */
92
+ export async function getUntrackedFiles(repoDir) {
93
+ await ensureGit();
94
+ // Use git ls-files to get all untracked files, which properly expands directories
95
+ const result = await exec('git', ['ls-files', '--others', '--exclude-standard'], {
96
+ cwd: repoDir,
97
+ });
98
+ return result.stdout.split('\n').filter((line) => line.trim().length > 0);
99
+ }
100
+ /**
101
+ * Gets untracked files within a specific directory.
102
+ * Uses path-scoped git ls-files for efficiency in large repos.
103
+ * @param repoDir - Repository directory
104
+ * @param dir - Directory path (relative to repo root)
105
+ * @returns List of untracked file paths relative to repo root
106
+ */
107
+ export async function getUntrackedFilesInDir(repoDir, dir) {
108
+ await ensureGit();
109
+ const result = await exec('git', ['ls-files', '--others', '--exclude-standard', '--', dir], {
110
+ cwd: repoDir,
111
+ });
112
+ return result.stdout.split('\n').filter((line) => line.trim().length > 0);
113
+ }
114
+ /**
115
+ * Gets modified (tracked) files within a specific directory.
116
+ * Uses path-scoped git diff for efficiency in large repos.
117
+ * @param repoDir - Repository directory
118
+ * @param dir - Directory path (relative to repo root)
119
+ * @returns List of modified file paths relative to repo root
120
+ */
121
+ export async function getModifiedFilesInDir(repoDir, dir) {
122
+ await ensureGit();
123
+ const result = await exec('git', ['diff', '--name-only', 'HEAD', '--', dir], { cwd: repoDir });
124
+ return result.stdout.split('\n').filter((line) => line.trim().length > 0);
125
+ }
126
+ /**
127
+ * Checks if any of the specified files have uncommitted changes.
128
+ * @param repoDir - Repository directory
129
+ * @param files - File paths to check (relative to repo root)
130
+ * @returns List of dirty file paths
131
+ */
132
+ export async function getDirtyFiles(repoDir, files) {
133
+ if (files.length === 0)
134
+ return [];
135
+ await ensureGit();
136
+ // Check both staged and unstaged changes for the given files
137
+ const result = await exec('git', ['diff', '--name-only', 'HEAD', '--', ...files], {
138
+ cwd: repoDir,
139
+ });
140
+ const tracked = result.stdout.split('\n').filter((line) => line.trim().length > 0);
141
+ // Also check for untracked files
142
+ const untrackedResult = await exec('git', ['ls-files', '--others', '--exclude-standard', '--', ...files], { cwd: repoDir });
143
+ const untracked = untrackedResult.stdout.split('\n').filter((line) => line.trim().length > 0);
144
+ return [...new Set([...tracked, ...untracked])].sort();
145
+ }
146
+ /**
147
+ * Lists all files in a directory (tracked and untracked, respecting .gitignore).
148
+ * Combines git ls-files for tracked files and --others for untracked files.
149
+ * @param repoDir - Repository directory
150
+ * @param dir - Directory path (relative to repo root)
151
+ * @returns List of file paths relative to repo root
152
+ */
153
+ export async function listAllFilesInDir(repoDir, dir) {
154
+ await ensureGit();
155
+ const tracked = await exec('git', ['ls-files', '--', dir], { cwd: repoDir });
156
+ const trackedFiles = tracked.stdout.split('\n').filter((line) => line.trim().length > 0);
157
+ const untracked = await exec('git', ['ls-files', '--others', '--exclude-standard', '--', dir], {
158
+ cwd: repoDir,
159
+ });
160
+ const untrackedFiles = untracked.stdout.split('\n').filter((line) => line.trim().length > 0);
161
+ return [...new Set([...trackedFiles, ...untrackedFiles])].sort();
162
+ }
163
+ //# sourceMappingURL=git-status.js.map