@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,137 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { join } from 'node:path';
3
+ import { getProjectPaths } from '../../core/config.js';
4
+ import { extractComponentChecksums, hasComponentChanged } from '../../core/furnace-apply.js';
5
+ import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, loadFurnaceState, } from '../../core/furnace-config.js';
6
+ import { checkRegistrationConsistency } from '../../core/furnace-validate-checks.js';
7
+ import { FurnaceError } from '../../errors/furnace.js';
8
+ import { pathExists } from '../../utils/fs.js';
9
+ import { info, intro, note, outro, warn } from '../../utils/logger.js';
10
+ /**
11
+ * Displays detailed status for a single Furnace component, including registration drift.
12
+ * @param name - Component tag name to inspect
13
+ * @param config - Loaded Furnace configuration
14
+ * @param projectRoot - Root directory of the project
15
+ */
16
+ async function showDetailedComponentStatus(name, config, projectRoot) {
17
+ const customConfig = config.custom[name];
18
+ const overrideConfig = config.overrides[name];
19
+ if (!customConfig && !overrideConfig && !config.stock.includes(name)) {
20
+ throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
21
+ }
22
+ if (overrideConfig) {
23
+ info(`"${name}" is an override component (${overrideConfig.type}).`);
24
+ info(`Base path: ${overrideConfig.basePath}`);
25
+ info(`Base version: ${overrideConfig.baseVersion}`);
26
+ outro('');
27
+ return;
28
+ }
29
+ if (config.stock.includes(name)) {
30
+ info(`"${name}" is a stock component. No local registration to check.`);
31
+ outro('');
32
+ return;
33
+ }
34
+ if (!customConfig) {
35
+ outro('');
36
+ return;
37
+ }
38
+ // Custom component — run registration consistency check
39
+ const status = await checkRegistrationConsistency(projectRoot, name, customConfig);
40
+ const lines = [];
41
+ const check = (ok, label) => {
42
+ lines.push(`${ok ? '\u2713' : '\u2717'} ${label}`);
43
+ };
44
+ check(status.sourceExists, 'Source directory exists');
45
+ check(status.targetExists, 'Target directory exists in engine');
46
+ check(status.filesInSync, 'Source and target files in sync');
47
+ check(status.jarMnMjs, `jar.mn has ${name}.mjs entry`);
48
+ check(status.jarMnCss, `jar.mn has ${name}.css entry`);
49
+ check(status.customElementsPresent, 'Registered in customElements.js');
50
+ check(status.customElementsCorrectBlock, 'In correct DOMContentLoaded block');
51
+ if (status.driftedFiles.length > 0) {
52
+ lines.push(`Drifted files: ${status.driftedFiles.join(', ')}`);
53
+ }
54
+ if (status.missingTargetFiles.length > 0) {
55
+ lines.push(`Missing in engine: ${status.missingTargetFiles.join(', ')}`);
56
+ }
57
+ note(lines.join('\n'), `${name} Registration Status`);
58
+ outro('');
59
+ }
60
+ /**
61
+ * Runs the furnace status command to show an overview of Furnace state.
62
+ * When a component name is provided, shows detailed registration status.
63
+ * @param projectRoot - Root directory of the project
64
+ * @param name - Optional component name for detailed status
65
+ */
66
+ export async function furnaceStatusCommand(projectRoot, name) {
67
+ intro('Furnace');
68
+ if (!(await furnaceConfigExists(projectRoot))) {
69
+ info('Furnace is not configured. Run `fireforge furnace create` or `fireforge furnace override` to get started.');
70
+ outro('');
71
+ return;
72
+ }
73
+ const config = await loadFurnaceConfig(projectRoot);
74
+ const state = await loadFurnaceState(projectRoot);
75
+ const paths = getProjectPaths(projectRoot);
76
+ const furnacePaths = getFurnacePaths(projectRoot);
77
+ if (name) {
78
+ await showDetailedComponentStatus(name, config, projectRoot);
79
+ return;
80
+ }
81
+ // --- Overview mode ---
82
+ const overrideCount = Object.keys(config.overrides).length;
83
+ const customCount = Object.keys(config.custom).length;
84
+ const stockCount = config.stock.length;
85
+ // Build summary lines
86
+ const lines = [];
87
+ lines.push(`Component prefix: ${config.componentPrefix || '(none)'}`);
88
+ lines.push(`Stock components: ${stockCount}`);
89
+ // Overrides
90
+ lines.push(`Override components: ${overrideCount}`);
91
+ if (overrideCount > 0) {
92
+ for (const [oName, entry] of Object.entries(config.overrides)) {
93
+ lines.push(` ${oName} (${entry.type})`);
94
+ }
95
+ }
96
+ // Custom
97
+ lines.push(`Custom components: ${customCount}`);
98
+ if (customCount > 0) {
99
+ for (const cName of Object.keys(config.custom)) {
100
+ lines.push(` ${cName}`);
101
+ }
102
+ }
103
+ // Last apply
104
+ lines.push(`Last apply: ${state.lastApply ?? 'never'}`);
105
+ note(lines.join('\n'), 'Furnace Status');
106
+ // Check for changes since last apply
107
+ if (await pathExists(paths.engine)) {
108
+ let changesDetected = false;
109
+ for (const oName of Object.keys(config.overrides)) {
110
+ const componentDir = join(furnacePaths.overridesDir, oName);
111
+ if (!(await pathExists(componentDir)))
112
+ continue;
113
+ const previous = extractComponentChecksums(state.appliedChecksums, 'override', oName);
114
+ if (await hasComponentChanged(componentDir, previous)) {
115
+ changesDetected = true;
116
+ break;
117
+ }
118
+ }
119
+ if (!changesDetected) {
120
+ for (const cName of Object.keys(config.custom)) {
121
+ const componentDir = join(furnacePaths.customDir, cName);
122
+ if (!(await pathExists(componentDir)))
123
+ continue;
124
+ const previous = extractComponentChecksums(state.appliedChecksums, 'custom', cName);
125
+ if (await hasComponentChanged(componentDir, previous)) {
126
+ changesDetected = true;
127
+ break;
128
+ }
129
+ }
130
+ }
131
+ if (changesDetected) {
132
+ warn('Components have been modified since last apply. Run `fireforge build` or `fireforge furnace apply`.');
133
+ }
134
+ }
135
+ outro('');
136
+ }
137
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Runs the furnace validate command to perform static analysis on components.
3
+ * @param projectRoot - Root directory of the project
4
+ * @param name - Optional component name to validate (validates all if omitted)
5
+ */
6
+ export declare function furnaceValidateCommand(projectRoot: string, name?: string): Promise<void>;
@@ -0,0 +1,91 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { join } from 'node:path';
3
+ import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, } from '../../core/furnace-config.js';
4
+ import { validateAllComponents, validateComponent } from '../../core/furnace-validate.js';
5
+ import { FurnaceError } from '../../errors/furnace.js';
6
+ import { pathExists } from '../../utils/fs.js';
7
+ import { info, intro, note, outro, success } from '../../utils/logger.js';
8
+ import { displayValidationIssues } from './validation-output.js';
9
+ /**
10
+ * Runs the furnace validate command to perform static analysis on components.
11
+ * @param projectRoot - Root directory of the project
12
+ * @param name - Optional component name to validate (validates all if omitted)
13
+ */
14
+ export async function furnaceValidateCommand(projectRoot, name) {
15
+ intro('Furnace Validate');
16
+ if (!(await furnaceConfigExists(projectRoot))) {
17
+ throw new FurnaceError('No furnace.json found. Run "fireforge furnace create" or "fireforge furnace override" to get started.');
18
+ }
19
+ const config = await loadFurnaceConfig(projectRoot);
20
+ const furnacePaths = getFurnacePaths(projectRoot);
21
+ let totalErrors = 0;
22
+ let totalWarnings = 0;
23
+ let componentCount;
24
+ if (name) {
25
+ // --- Single component validation ---
26
+ let type;
27
+ let componentDir;
28
+ if (name in config.overrides) {
29
+ type = 'override';
30
+ componentDir = join(furnacePaths.overridesDir, name);
31
+ }
32
+ else if (name in config.custom) {
33
+ type = 'custom';
34
+ componentDir = join(furnacePaths.customDir, name);
35
+ }
36
+ else if (config.stock.includes(name)) {
37
+ info(`"${name}" is a stock component. Stock components are not validated locally.`);
38
+ outro('Validation complete');
39
+ return;
40
+ }
41
+ else {
42
+ throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
43
+ }
44
+ if (!(await pathExists(componentDir))) {
45
+ throw new FurnaceError(`Component directory not found for "${name}".`, name);
46
+ }
47
+ const issues = await validateComponent(componentDir, name, type, config, projectRoot);
48
+ componentCount = 1;
49
+ if (issues.length === 0) {
50
+ success(`${name} — all checks passed`);
51
+ }
52
+ else {
53
+ const [e, w] = displayValidationIssues(issues);
54
+ totalErrors += e;
55
+ totalWarnings += w;
56
+ }
57
+ }
58
+ else {
59
+ // --- Validate all components ---
60
+ const overrideCount = Object.keys(config.overrides).length;
61
+ const customCount = Object.keys(config.custom).length;
62
+ if (overrideCount === 0 && customCount === 0) {
63
+ info('No components to validate.');
64
+ outro('Done');
65
+ return;
66
+ }
67
+ if (config.stock.length > 0) {
68
+ info(`Skipping ${config.stock.length} stock component(s) (no local files to validate).`);
69
+ }
70
+ const results = await validateAllComponents(projectRoot);
71
+ componentCount = results.size;
72
+ for (const [componentName, issues] of results) {
73
+ if (issues.length === 0) {
74
+ success(`${componentName} — all checks passed`);
75
+ }
76
+ else {
77
+ const [e, w] = displayValidationIssues(issues);
78
+ totalErrors += e;
79
+ totalWarnings += w;
80
+ }
81
+ }
82
+ }
83
+ // Summary
84
+ note(`${totalErrors} error(s), ${totalWarnings} warning(s) across ${componentCount} component(s)`, 'Validation Summary');
85
+ if (totalErrors > 0) {
86
+ info('Fix the errors above and run "fireforge furnace validate" again.');
87
+ throw new FurnaceError(`Validation failed with ${totalErrors} error(s).`);
88
+ }
89
+ outro('Validation passed');
90
+ }
91
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1,7 @@
1
+ import type { ValidationIssue } from '../../types/furnace.js';
2
+ /**
3
+ * Displays validation issues and returns aggregated error and warning counts.
4
+ * @param issues - Validation issues to render
5
+ * @returns Tuple of [errorCount, warningCount]
6
+ */
7
+ export declare function displayValidationIssues(issues: ValidationIssue[]): [number, number];
@@ -0,0 +1,22 @@
1
+ import { error, warn } from '../../utils/logger.js';
2
+ /**
3
+ * Displays validation issues and returns aggregated error and warning counts.
4
+ * @param issues - Validation issues to render
5
+ * @returns Tuple of [errorCount, warningCount]
6
+ */
7
+ export function displayValidationIssues(issues) {
8
+ let errors = 0;
9
+ let warnings = 0;
10
+ for (const issue of issues) {
11
+ if (issue.severity === 'error') {
12
+ error(`${issue.component}: [${issue.check}] ${issue.message}`);
13
+ errors++;
14
+ }
15
+ else {
16
+ warn(`${issue.component}: [${issue.check}] ${issue.message}`);
17
+ warnings++;
18
+ }
19
+ }
20
+ return [errors, warnings];
21
+ }
22
+ //# sourceMappingURL=validation-output.js.map
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandContext } from '../types/cli.js';
3
+ import type { ImportOptions } from '../types/commands/index.js';
4
+ /**
5
+ * Runs the import command to apply patches.
6
+ * @param projectRoot - Root directory of the project
7
+ * @param options - Import options
8
+ */
9
+ export declare function importCommand(projectRoot: string, options?: ImportOptions): Promise<void>;
10
+ /** Registers the import command on the CLI program. */
11
+ export declare function registerImport(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
@@ -0,0 +1,241 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { join } from 'node:path';
3
+ import { confirm } from '@clack/prompts';
4
+ import { getProjectPaths, loadConfig, loadState, saveState } from '../core/config.js';
5
+ import { getHead } from '../core/git.js';
6
+ import { getDirtyFiles } from '../core/git-status.js';
7
+ import { applyPatchesWithContinue, computePatchedContent, countPatches, discoverPatches, extractAffectedFiles, PatchError, } from '../core/patch-apply.js';
8
+ import { checkVersionCompatibility, loadPatchesManifest, validatePatchesManifestConsistency, validatePatchIntegrity, } from '../core/patch-manifest.js';
9
+ import { GeneralError } from '../errors/base.js';
10
+ import { toError } from '../utils/errors.js';
11
+ import { pathExists, readText } from '../utils/fs.js';
12
+ import { error, info, intro, isCancel, outro, spinner, success, verbose, warn, } from '../utils/logger.js';
13
+ import { pickDefined } from '../utils/options.js';
14
+ async function getUnmanagedDirtyFiles(engineDir, patchesDir, dirtyFiles) {
15
+ const classifications = await Promise.all(dirtyFiles.map(async (file) => {
16
+ try {
17
+ const [expected, exists] = await Promise.all([
18
+ computePatchedContent(patchesDir, engineDir, file),
19
+ pathExists(join(engineDir, file)),
20
+ ]);
21
+ const actual = exists ? await readText(join(engineDir, file)) : null;
22
+ return actual === expected ? null : file;
23
+ }
24
+ catch (error) {
25
+ verbose(`Treating ${file} as unmanaged because patched-content classification failed: ${toError(error).message}`);
26
+ return file;
27
+ }
28
+ }));
29
+ return classifications.filter((file) => file !== null).sort();
30
+ }
31
+ function reportForcedOverwriteRisk(unmanagedDirtyFiles) {
32
+ warn(`--force will overwrite ${unmanagedDirtyFiles.length} unmanaged change${unmanagedDirtyFiles.length === 1 ? '' : 's'} in patch-touched file${unmanagedDirtyFiles.length === 1 ? '' : 's'}:`);
33
+ for (const file of unmanagedDirtyFiles) {
34
+ warn(` ${file}`);
35
+ }
36
+ warn('Patch reapplication may restore these paths to the engine baseline before reapplying patches.');
37
+ }
38
+ async function checkUncommittedPatchFiles(engineDir, patchesDir, forceImport) {
39
+ const patches = await discoverPatches(patchesDir);
40
+ const allTouchedFiles = new Set();
41
+ for (const patch of patches) {
42
+ const content = await readText(patch.path);
43
+ for (const file of extractAffectedFiles(content)) {
44
+ allTouchedFiles.add(file);
45
+ }
46
+ }
47
+ if (allTouchedFiles.size > 0) {
48
+ const dirtyFiles = await getDirtyFiles(engineDir, [...allTouchedFiles]);
49
+ if (dirtyFiles.length > 0) {
50
+ const unmanagedDirtyFiles = await getUnmanagedDirtyFiles(engineDir, patchesDir, dirtyFiles);
51
+ if (unmanagedDirtyFiles.length === 0) {
52
+ info('Patch-backed materialized files already match the stored patch stack.');
53
+ }
54
+ else if (!forceImport) {
55
+ warn('Uncommitted changes detected in files that patches will modify:');
56
+ for (const file of unmanagedDirtyFiles) {
57
+ warn(` ${file}`);
58
+ }
59
+ throw new GeneralError('Uncommitted changes in patch-touched files. Commit or stash them first, or use --force.');
60
+ }
61
+ else {
62
+ reportForcedOverwriteRisk(unmanagedDirtyFiles);
63
+ }
64
+ }
65
+ }
66
+ }
67
+ async function handlePatchFailures(summary, state, projectRoot) {
68
+ const firstFailed = summary.failed[0];
69
+ if (firstFailed) {
70
+ state.pendingResolution = {
71
+ patchFilename: firstFailed.patch.filename,
72
+ originalError: firstFailed.error ?? 'Unknown error',
73
+ };
74
+ await saveState(projectRoot, state);
75
+ }
76
+ for (const result of summary.failed) {
77
+ error(`\nFailed: ${result.patch.filename}`);
78
+ if (result.error) {
79
+ error(` Error: ${result.error}`);
80
+ }
81
+ if (result.conflictingFiles && result.conflictingFiles.length > 0) {
82
+ error(` Conflicting files:`);
83
+ for (const file of result.conflictingFiles) {
84
+ error(` - ${file}`);
85
+ }
86
+ }
87
+ }
88
+ if (summary.failed.length > 1) {
89
+ info(`\nNote: "fireforge resolve" will address the first failed patch (${firstFailed?.patch.filename}).`);
90
+ info('Re-run "fireforge import" after resolving to continue with remaining patches.');
91
+ }
92
+ if (summary.skipped.length > 0) {
93
+ warn(`\n${summary.skipped.length} patch(es) were skipped:`);
94
+ for (const patch of summary.skipped) {
95
+ warn(` - ${patch.filename}`);
96
+ }
97
+ info('\nUse --continue flag to attempt all patches');
98
+ }
99
+ info('\nResolution Instructions:');
100
+ if (firstFailed) {
101
+ info(` Patch ${firstFailed.patch.filename} failed to apply automatically.`);
102
+ }
103
+ info(' 1. Manually fix the conflicts in the engine/ directory (look for .rej files if any).');
104
+ info(' 2. Run "fireforge resolve" to update the patch file with your manual fixes and continue.');
105
+ throw new PatchError(`Failed to apply ${summary.failed.length} patch(es)`, firstFailed?.patch.filename);
106
+ }
107
+ async function checkEngineDrift(engineDir, baseCommit, forceImport) {
108
+ const currentHead = await getHead(engineDir);
109
+ if (currentHead === baseCommit)
110
+ return true;
111
+ if (!process.stdin.isTTY) {
112
+ if (!forceImport) {
113
+ throw new GeneralError('Engine HEAD has drifted from base commit. Re-run with --force to bypass drift check.');
114
+ }
115
+ warn('Engine HEAD has drifted from base commit. Continuing because --force was provided in non-interactive mode.');
116
+ }
117
+ else {
118
+ if (forceImport) {
119
+ warn('Engine HEAD has drifted from base commit. Continuing because --force was provided.');
120
+ }
121
+ else {
122
+ warn('Warning: Engine is not at the baseline commit.');
123
+ const shouldContinue = await confirm({
124
+ message: 'Engine HEAD has drifted. Applying patches now might lead to unexpected conflicts. Continue anyway?',
125
+ initialValue: false,
126
+ });
127
+ if (isCancel(shouldContinue) || !shouldContinue) {
128
+ outro('Import cancelled by user');
129
+ return false;
130
+ }
131
+ }
132
+ }
133
+ return true;
134
+ }
135
+ /**
136
+ * Runs the import command to apply patches.
137
+ * @param projectRoot - Root directory of the project
138
+ * @param options - Import options
139
+ */
140
+ export async function importCommand(projectRoot, options = {}) {
141
+ intro('FireForge Import');
142
+ const continueOnFailure = options.continue ?? false;
143
+ const forceImport = options.force ?? false;
144
+ const paths = getProjectPaths(projectRoot);
145
+ // Check if engine exists
146
+ if (!(await pathExists(paths.engine))) {
147
+ throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
148
+ }
149
+ // Engine consistency check before applying patches
150
+ const state = await loadState(projectRoot);
151
+ if (state.baseCommit) {
152
+ const shouldContinue = await checkEngineDrift(paths.engine, state.baseCommit, forceImport);
153
+ if (!shouldContinue)
154
+ return;
155
+ }
156
+ // Check if patches directory exists
157
+ if (!(await pathExists(paths.patches))) {
158
+ info('No patches directory found. Nothing to import.');
159
+ outro('Import complete (no patches)');
160
+ return;
161
+ }
162
+ // Count patches
163
+ const patchCount = await countPatches(paths.patches);
164
+ if (patchCount === 0) {
165
+ info('No patch files found in patches/ directory.');
166
+ outro('Import complete (no patches)');
167
+ return;
168
+ }
169
+ info(`Found ${patchCount} patch${patchCount === 1 ? '' : 'es'} to apply`);
170
+ const manifestConsistencyIssues = await validatePatchesManifestConsistency(paths.patches);
171
+ if (manifestConsistencyIssues.length > 0) {
172
+ const issueSummary = manifestConsistencyIssues.map((issue) => issue.message).join('\n ');
173
+ throw new GeneralError('Patch manifest consistency check failed. Repair patches/patches.json before importing.\n' +
174
+ ` ${issueSummary}\n\n` +
175
+ 'Run "fireforge doctor --repair-patches-manifest" to rebuild the manifest from on-disk patch files.');
176
+ }
177
+ // Load manifest and check version compatibility
178
+ const manifest = await loadPatchesManifest(paths.patches);
179
+ if (manifest) {
180
+ const config = await loadConfig(projectRoot);
181
+ const currentVersion = config.firefox.version;
182
+ for (const patch of manifest.patches) {
183
+ const warning = checkVersionCompatibility(patch.sourceEsrVersion, currentVersion);
184
+ if (warning) {
185
+ warn(`${patch.filename}: ${warning}`);
186
+ }
187
+ }
188
+ }
189
+ // Validate patch integrity (detect orphaned modification patches)
190
+ const integrityIssues = await validatePatchIntegrity(paths.patches, paths.engine);
191
+ if (integrityIssues.length > 0) {
192
+ warn('\nPatch integrity issues detected:');
193
+ for (const issue of integrityIssues) {
194
+ warn(` ${issue.filename}: ${issue.message}`);
195
+ }
196
+ info('Run "fireforge doctor" for more details.\n');
197
+ }
198
+ await checkUncommittedPatchFiles(paths.engine, paths.patches, forceImport);
199
+ const s = spinner('Applying patches...');
200
+ try {
201
+ const summary = await applyPatchesWithContinue(paths.patches, paths.engine, continueOnFailure);
202
+ // Handle failures
203
+ if (summary.failed.length > 0) {
204
+ s.error(`${summary.failed.length} patch(es) failed`);
205
+ await handlePatchFailures(summary, state, projectRoot);
206
+ }
207
+ // Count auto-resolved patches
208
+ const autoResolved = summary.succeeded.filter((r) => r.autoResolved);
209
+ const autoResolvedCount = autoResolved.length;
210
+ // Build success message
211
+ let stopMessage = `Applied ${summary.succeeded.length} patch${summary.succeeded.length === 1 ? '' : 'es'}`;
212
+ if (autoResolvedCount > 0) {
213
+ stopMessage += ` (${autoResolvedCount} auto-resolved)`;
214
+ }
215
+ s.stop(stopMessage);
216
+ // List applied patches
217
+ for (const result of summary.succeeded) {
218
+ const suffix = result.autoResolved ? ' (auto-resolved)' : '';
219
+ success(` ${result.patch.filename}${suffix}`);
220
+ }
221
+ outro('All patches applied successfully!');
222
+ }
223
+ catch (error) {
224
+ if (!(error instanceof PatchError)) {
225
+ s.error('Patch application failed');
226
+ }
227
+ throw error;
228
+ }
229
+ }
230
+ /** Registers the import command on the CLI program. */
231
+ export function registerImport(program, { getProjectRoot, withErrorHandling }) {
232
+ program
233
+ .command('import')
234
+ .description('Apply patches from the patches directory')
235
+ .option('--continue', 'Continue applying patches even if one fails')
236
+ .option('-f, --force', 'Proceed despite engine drift and overwrite unmanaged changes in patch-touched files')
237
+ .action(withErrorHandling(async (options) => {
238
+ await importCommand(getProjectRoot(), pickDefined(options));
239
+ }));
240
+ }
241
+ //# sourceMappingURL=import.js.map
@@ -0,0 +1,10 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandContext } from '../types/cli.js';
3
+ /**
4
+ * Runs the lint command to check engine changes against patch quality rules.
5
+ * @param projectRoot - Root directory of the project
6
+ * @param files - Optional file/directory paths to lint (relative to engine/)
7
+ */
8
+ export declare function lintCommand(projectRoot: string, files: string[]): Promise<void>;
9
+ /** Registers the lint command on the CLI program. */
10
+ export declare function registerLint(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
@@ -0,0 +1,118 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { stat } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { getProjectPaths, loadConfig } from '../core/config.js';
5
+ import { getStatusWithCodes, hasChanges, isGitRepository } from '../core/git.js';
6
+ import { getAllDiff, getDiffForFilesAgainstHead } from '../core/git-diff.js';
7
+ import { getModifiedFilesInDir, getUntrackedFiles, getUntrackedFilesInDir, } from '../core/git-status.js';
8
+ import { extractAffectedFiles } from '../core/patch-apply.js';
9
+ import { lintExportedPatch } from '../core/patch-lint.js';
10
+ import { GeneralError } from '../errors/base.js';
11
+ import { pathExists } from '../utils/fs.js';
12
+ import { info, intro, outro, success, warn } from '../utils/logger.js';
13
+ /**
14
+ * Runs the lint command to check engine changes against patch quality rules.
15
+ * @param projectRoot - Root directory of the project
16
+ * @param files - Optional file/directory paths to lint (relative to engine/)
17
+ */
18
+ export async function lintCommand(projectRoot, files) {
19
+ intro('FireForge Lint');
20
+ const paths = getProjectPaths(projectRoot);
21
+ if (!(await pathExists(paths.engine))) {
22
+ throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
23
+ }
24
+ if (!(await isGitRepository(paths.engine))) {
25
+ throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
26
+ }
27
+ let diff;
28
+ if (files.length > 0) {
29
+ // Collect specific files/directories
30
+ const collectedFiles = new Set();
31
+ let fileStatuses;
32
+ let untrackedFiles;
33
+ for (const inputPath of files) {
34
+ const fullInputPath = join(paths.engine, inputPath);
35
+ let isDirectory = false;
36
+ try {
37
+ const fileStat = await stat(fullInputPath);
38
+ isDirectory = fileStat.isDirectory();
39
+ }
40
+ catch {
41
+ // Treat as file
42
+ }
43
+ if (isDirectory) {
44
+ const dirPath = inputPath.endsWith('/') ? inputPath.slice(0, -1) : inputPath;
45
+ const modifiedFiles = await getModifiedFilesInDir(paths.engine, dirPath);
46
+ const dirUntrackedFiles = await getUntrackedFilesInDir(paths.engine, dirPath);
47
+ for (const f of modifiedFiles)
48
+ collectedFiles.add(f);
49
+ for (const f of dirUntrackedFiles)
50
+ collectedFiles.add(f);
51
+ }
52
+ else {
53
+ if (!fileStatuses) {
54
+ fileStatuses = await getStatusWithCodes(paths.engine);
55
+ }
56
+ if (!untrackedFiles) {
57
+ untrackedFiles = await getUntrackedFiles(paths.engine);
58
+ }
59
+ const hasStatus = fileStatuses.some((s) => s.file === inputPath) || untrackedFiles.includes(inputPath);
60
+ if (hasStatus) {
61
+ collectedFiles.add(inputPath);
62
+ }
63
+ }
64
+ }
65
+ if (collectedFiles.size === 0) {
66
+ info('No modified files found in the specified paths.');
67
+ outro('Nothing to lint');
68
+ return;
69
+ }
70
+ diff = await getDiffForFilesAgainstHead(paths.engine, [...collectedFiles].sort());
71
+ }
72
+ else {
73
+ // Lint all changes
74
+ if (!(await hasChanges(paths.engine))) {
75
+ info('No changes to lint.');
76
+ outro('Nothing to lint');
77
+ return;
78
+ }
79
+ diff = await getAllDiff(paths.engine);
80
+ }
81
+ if (!diff.trim()) {
82
+ info('No diff content to lint.');
83
+ outro('Nothing to lint');
84
+ return;
85
+ }
86
+ const config = await loadConfig(projectRoot);
87
+ const filesAffected = extractAffectedFiles(diff);
88
+ const issues = await lintExportedPatch(paths.engine, filesAffected, diff, config);
89
+ if (issues.length === 0) {
90
+ success('No lint issues found.');
91
+ outro('Lint passed');
92
+ return;
93
+ }
94
+ const errors = issues.filter((i) => i.severity === 'error');
95
+ const warnings = issues.filter((i) => i.severity === 'warning');
96
+ for (const issue of warnings) {
97
+ warn(`[${issue.check}] ${issue.file}: ${issue.message}`);
98
+ }
99
+ for (const issue of errors) {
100
+ warn(`ERROR [${issue.check}] ${issue.file}: ${issue.message}`);
101
+ }
102
+ info(`\nLint: ${errors.length} error(s), ${warnings.length} warning(s)`);
103
+ if (errors.length > 0) {
104
+ outro('Lint failed');
105
+ throw new GeneralError(`Patch lint found ${errors.length} error(s). Fix these before exporting.`);
106
+ }
107
+ outro('Lint passed with warnings');
108
+ }
109
+ /** Registers the lint command on the CLI program. */
110
+ export function registerLint(program, { getProjectRoot, withErrorHandling }) {
111
+ program
112
+ .command('lint [paths...]')
113
+ .description('Lint engine changes against patch quality rules')
114
+ .action(withErrorHandling(async (paths) => {
115
+ await lintCommand(getProjectRoot(), paths);
116
+ }));
117
+ }
118
+ //# sourceMappingURL=lint.js.map
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandContext } from '../types/cli.js';
3
+ import type { PackageOptions } from '../types/commands/index.js';
4
+ /**
5
+ * Runs the package command to create a distribution package.
6
+ * @param projectRoot - Root directory of the project
7
+ * @param options - Package options
8
+ */
9
+ export declare function packageCommand(projectRoot: string, options: PackageOptions): Promise<void>;
10
+ /** Registers the package command on the CLI program. */
11
+ export declare function registerPackage(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;