@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,377 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { join } from 'node:path';
3
+ import { multiselect, text } from '@clack/prompts';
4
+ import { getProjectPaths, loadConfig } from '../../core/config.js';
5
+ import { ensureFurnaceConfig, getFurnacePaths, writeFurnaceConfig, } from '../../core/furnace-config.js';
6
+ import { isComponentInEngine } from '../../core/furnace-scanner.js';
7
+ import { DEFAULT_LICENSE, getLicenseHeader } from '../../core/license-headers.js';
8
+ import { registerTestManifest } from '../../core/manifest-register.js';
9
+ import { InvalidArgumentError } from '../../errors/base.js';
10
+ import { FurnaceError } from '../../errors/furnace.js';
11
+ import { toError } from '../../utils/errors.js';
12
+ import { ensureDir, pathExists, readText, writeText } from '../../utils/fs.js';
13
+ import { cancel, intro, isCancel, note, outro, success, warn } from '../../utils/logger.js';
14
+ /**
15
+ * Converts a kebab-case tag name to PascalCase class name.
16
+ * e.g. "moz-sidebar-panel" → "MozSidebarPanel"
17
+ */
18
+ function tagNameToClassName(tagName) {
19
+ return tagName
20
+ .split('-')
21
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
22
+ .join('');
23
+ }
24
+ /**
25
+ * Validates a custom element tag name.
26
+ * @returns Error message if invalid, undefined if valid
27
+ */
28
+ function validateTagName(name) {
29
+ if (!name.trim())
30
+ return 'Name is required';
31
+ if (!name.includes('-'))
32
+ return 'Custom element names must contain a hyphen (e.g., "my-widget")';
33
+ if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)+$/.test(name))
34
+ return 'Name must be lowercase, start with a letter, and use hyphens to separate words (e.g., "my-widget")';
35
+ return undefined;
36
+ }
37
+ /**
38
+ * Checks if a component name conflicts with existing entries in furnace.json.
39
+ */
40
+ function checkNameConflict(config, name) {
41
+ if (name in config.custom) {
42
+ return `A custom component named "${name}" already exists in furnace.json`;
43
+ }
44
+ if (name in config.overrides) {
45
+ return `An override component named "${name}" already exists in furnace.json`;
46
+ }
47
+ return undefined;
48
+ }
49
+ /**
50
+ * Generates the .mjs file content for a custom component.
51
+ */
52
+ function generateMjsContent(name, className, description, localized, header) {
53
+ const connectedCallback = localized
54
+ ? `
55
+ connectedCallback() {
56
+ super.connectedCallback();
57
+ this.insertFTLIfNeeded("${name}.ftl");
58
+ }
59
+ `
60
+ : '';
61
+ return `${header}
62
+
63
+ import { html } from "chrome://global/content/vendor/lit.all.mjs";
64
+ import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
65
+
66
+ /**
67
+ * ${description || name}
68
+ *
69
+ * @tagname ${name}
70
+ */
71
+ class ${className} extends MozLitElement {
72
+ static properties = {};
73
+
74
+ constructor() {
75
+ super();
76
+ }
77
+ ${connectedCallback}
78
+ render() {
79
+ return html\`
80
+ <link rel="stylesheet" href="chrome://global/content/elements/${name}.css" />
81
+ <slot></slot>
82
+ \`;
83
+ }
84
+ }
85
+ customElements.define("${name}", ${className});
86
+ `;
87
+ }
88
+ /**
89
+ * Generates the .css file content for a custom component.
90
+ */
91
+ function generateCssContent(header) {
92
+ return `${header}
93
+
94
+ :host {
95
+ display: block;
96
+ }
97
+ `;
98
+ }
99
+ /**
100
+ * Generates the .ftl file content for a custom component.
101
+ */
102
+ function generateFtlContent(name, header) {
103
+ return `${header}
104
+
105
+ ## Strings for the ${name} component
106
+ `;
107
+ }
108
+ /**
109
+ * Scaffolds browser mochitest files for a newly created custom component.
110
+ * @param componentName - Custom element tag name
111
+ * @param license - Project license used for generated headers
112
+ * @param forgeConfig - Project config fields needed for test naming
113
+ * @param paths - Resolved project paths used to place test files
114
+ * @returns Relative test filenames created or updated for the component
115
+ */
116
+ async function scaffoldTestFiles(componentName, license, forgeConfig, paths) {
117
+ const strippedName = componentName.startsWith('moz-') ? componentName.slice(4) : componentName;
118
+ // Avoid double-prefixing: strip binaryName prefix since testDirName already uses it
119
+ const testDirName = forgeConfig.binaryName;
120
+ const withoutBinaryPrefix = strippedName.startsWith(testDirName + '-')
121
+ ? strippedName.slice(testDirName.length + 1)
122
+ : strippedName;
123
+ const underscored = withoutBinaryPrefix.replace(/-/g, '_');
124
+ const testFileName = `browser_${testDirName}_${underscored}.js`;
125
+ const testDir = join(paths.engine, 'browser/base/content/test', testDirName);
126
+ await ensureDir(testDir);
127
+ const jsHeader = getLicenseHeader(license, 'js');
128
+ const hashHeader = getLicenseHeader(license, 'hash');
129
+ const testFiles = [];
130
+ // browser.toml — create if missing, append entry if existing
131
+ const tomlPath = join(testDir, 'browser.toml');
132
+ if (await pathExists(tomlPath)) {
133
+ // Append the new test entry if not already present
134
+ const existingToml = await readText(tomlPath);
135
+ if (!existingToml.includes(`["${testFileName}"]`)) {
136
+ await writeText(tomlPath, existingToml.trimEnd() + `\n\n["${testFileName}"]\n`);
137
+ }
138
+ }
139
+ else {
140
+ const browserToml = `${hashHeader}
141
+
142
+ [DEFAULT]
143
+ support-files = ["head.js"]
144
+
145
+ ["${testFileName}"]
146
+ `;
147
+ await writeText(tomlPath, browserToml);
148
+ }
149
+ testFiles.push('browser.toml');
150
+ // head.js — only create if it doesn't exist (shared across components)
151
+ const headPath = join(testDir, 'head.js');
152
+ if (!(await pathExists(headPath))) {
153
+ const headJs = `${jsHeader}
154
+
155
+ "use strict";
156
+
157
+ /**
158
+ * Wait for a custom element to be defined.
159
+ * @param {string} tag - Custom element tag name
160
+ * @returns {Promise<CustomElementConstructor>}
161
+ */
162
+ async function waitForElement(tag) {
163
+ return customElements.whenDefined(tag);
164
+ }
165
+ `;
166
+ await writeText(headPath, headJs);
167
+ testFiles.push('head.js');
168
+ }
169
+ // browser_{binaryName}_{stripped}.js
170
+ const testJs = `${jsHeader}
171
+
172
+ "use strict";
173
+
174
+ add_task(async function test_${underscored}_defined() {
175
+ const ctor = await waitForElement("${componentName}");
176
+ Assert.ok(ctor, "${componentName} custom element should be defined");
177
+ Assert.equal(typeof ctor, "function", "Constructor should be a function");
178
+ });
179
+ `;
180
+ await writeText(join(testDir, testFileName), testJs);
181
+ testFiles.push(testFileName);
182
+ // Register in moz.build
183
+ try {
184
+ const registerResult = await registerTestManifest(paths.engine, testDirName);
185
+ if (!registerResult.skipped) {
186
+ success(`Registered test manifest in ${registerResult.manifest}`);
187
+ }
188
+ }
189
+ catch (error) {
190
+ warn(`Could not register test manifest in moz.build — ${toError(error).message}. Register manually with "fireforge register".`);
191
+ }
192
+ return testFiles;
193
+ }
194
+ /**
195
+ * Resolves the localized and registration feature flags for a new component.
196
+ * @param isInteractive - Whether interactive prompts are available
197
+ * @param options - CLI-provided feature flags
198
+ * @returns Final feature selections, or null when creation is cancelled
199
+ */
200
+ async function resolveCreateFeatures(isInteractive, options) {
201
+ let localized = options.localized ?? false;
202
+ let register = options.register ?? true;
203
+ if (isInteractive && options.localized === undefined && options.register === undefined) {
204
+ const features = await multiselect({
205
+ message: 'Component features:',
206
+ options: [
207
+ {
208
+ value: 'localized',
209
+ label: 'Fluent localization (data-l10n-id)',
210
+ },
211
+ {
212
+ value: 'register',
213
+ label: 'Register in customElements.js',
214
+ },
215
+ ],
216
+ initialValues: ['register'],
217
+ });
218
+ if (isCancel(features)) {
219
+ cancel('Create cancelled');
220
+ return null;
221
+ }
222
+ const selected = features;
223
+ localized = selected.includes('localized');
224
+ register = selected.includes('register');
225
+ }
226
+ return { localized, register };
227
+ }
228
+ /**
229
+ * Writes the scaffolded component source files to disk.
230
+ * @param componentDir - Destination component directory
231
+ * @param componentName - Custom element tag name
232
+ * @param className - Generated component class name
233
+ * @param description - Human-readable component description
234
+ * @param localized - Whether to include a Fluent file
235
+ * @param license - Project license used for generated headers
236
+ * @returns Relative filenames written for the component
237
+ */
238
+ async function writeComponentFiles(componentDir, componentName, className, description, localized, license) {
239
+ await ensureDir(componentDir);
240
+ const files = [`${componentName}.mjs`, `${componentName}.css`];
241
+ const mjsContent = generateMjsContent(componentName, className, description, localized, getLicenseHeader(license, 'js'));
242
+ await writeText(join(componentDir, `${componentName}.mjs`), mjsContent);
243
+ const cssContent = generateCssContent(getLicenseHeader(license, 'css'));
244
+ await writeText(join(componentDir, `${componentName}.css`), cssContent);
245
+ if (localized) {
246
+ const ftlContent = generateFtlContent(componentName, getLicenseHeader(license, 'hash'));
247
+ await writeText(join(componentDir, `${componentName}.ftl`), ftlContent);
248
+ files.push(`${componentName}.ftl`);
249
+ }
250
+ return files;
251
+ }
252
+ /**
253
+ * Runs the furnace create command to scaffold a new custom component.
254
+ * @param projectRoot - Root directory of the project
255
+ * @param name - Optional component tag name (prompted if not provided)
256
+ * @param options - CLI options for non-interactive mode
257
+ */
258
+ export async function furnaceCreateCommand(projectRoot, name, options = {}) {
259
+ const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
260
+ intro('Furnace Create');
261
+ // Load or create furnace.json
262
+ const config = await ensureFurnaceConfig(projectRoot);
263
+ const paths = getProjectPaths(projectRoot);
264
+ const forgeConfig = await loadConfig(projectRoot);
265
+ const license = forgeConfig.license ?? DEFAULT_LICENSE;
266
+ const furnacePaths = getFurnacePaths(projectRoot);
267
+ // --- Resolve component name ---
268
+ let componentName = name;
269
+ if (componentName) {
270
+ // Validate CLI-provided name
271
+ const validationError = validateTagName(componentName);
272
+ if (validationError) {
273
+ throw new InvalidArgumentError(validationError, 'name');
274
+ }
275
+ }
276
+ else if (isInteractive) {
277
+ const nameResult = await text({
278
+ message: 'Component tag name:',
279
+ placeholder: 'moz-my-widget',
280
+ validate: (value) => validateTagName(value ?? ''),
281
+ });
282
+ if (isCancel(nameResult)) {
283
+ cancel('Create cancelled');
284
+ return;
285
+ }
286
+ componentName = String(nameResult);
287
+ }
288
+ else {
289
+ throw new InvalidArgumentError('Component name is required in non-interactive mode.\n' +
290
+ 'Usage: fireforge furnace create <name> -d "description"', 'name');
291
+ }
292
+ // Check for conflicts
293
+ const conflict = checkNameConflict(config, componentName);
294
+ if (conflict) {
295
+ throw new FurnaceError(conflict, componentName);
296
+ }
297
+ // Check if it already exists in the engine source tree
298
+ if (await pathExists(paths.engine)) {
299
+ if (await isComponentInEngine(paths.engine, componentName)) {
300
+ throw new FurnaceError(`"${componentName}" already exists in the engine source tree. Use "fireforge furnace override" instead.`, componentName);
301
+ }
302
+ }
303
+ // Warn if name doesn't match componentPrefix
304
+ if (config.componentPrefix && !componentName.startsWith(config.componentPrefix)) {
305
+ warn(`Name "${componentName}" does not start with the configured prefix "${config.componentPrefix}".`);
306
+ }
307
+ // --- Resolve description ---
308
+ let description = options.description ?? '';
309
+ if (!description && isInteractive) {
310
+ const descResult = await text({
311
+ message: 'Description (optional):',
312
+ placeholder: 'A brief description of the component',
313
+ });
314
+ if (!isCancel(descResult)) {
315
+ description = String(descResult);
316
+ }
317
+ }
318
+ // --- Resolve features ---
319
+ const featureSelection = await resolveCreateFeatures(isInteractive, options);
320
+ if (!featureSelection) {
321
+ return;
322
+ }
323
+ const { localized, register } = featureSelection;
324
+ // --- Generate component files ---
325
+ const className = tagNameToClassName(componentName);
326
+ const componentDir = join(furnacePaths.customDir, componentName);
327
+ // Check if directory already exists on disk
328
+ if (await pathExists(componentDir)) {
329
+ throw new FurnaceError(`Directory already exists: components/custom/${componentName}`, componentName);
330
+ }
331
+ const files = await writeComponentFiles(componentDir, componentName, className, description, localized, license);
332
+ // --- Validate and process --compose ---
333
+ const composes = options.compose;
334
+ if (composes && composes.length > 0) {
335
+ for (const tag of composes) {
336
+ if (!config.stock.includes(tag)) {
337
+ warn(`Composed tag "${tag}" is not in the stock array of furnace.json.`);
338
+ }
339
+ }
340
+ }
341
+ // --- Update furnace.json ---
342
+ const customEntry = {
343
+ description,
344
+ targetPath: `toolkit/content/widgets/${componentName}`,
345
+ register,
346
+ localized,
347
+ };
348
+ if (composes && composes.length > 0) {
349
+ customEntry.composes = composes;
350
+ }
351
+ config.custom[componentName] = customEntry;
352
+ await writeFurnaceConfig(projectRoot, config);
353
+ // --- Scaffold tests if requested ---
354
+ const withTests = options.withTests ?? false;
355
+ const testFiles = [];
356
+ if (withTests) {
357
+ const scafFiles = await scaffoldTestFiles(componentName, license, forgeConfig, paths);
358
+ testFiles.push(...scafFiles);
359
+ }
360
+ // --- Success ---
361
+ let noteParts = `Files created in components/custom/${componentName}/:\n` +
362
+ files.map((f) => ` ${f}`).join('\n');
363
+ if (testFiles.length > 0) {
364
+ noteParts +=
365
+ `\n\nTest files in engine/browser/base/content/test/${forgeConfig.binaryName}/:\n` +
366
+ testFiles.map((f) => ` ${f}`).join('\n');
367
+ }
368
+ noteParts +=
369
+ '\n\n' +
370
+ 'Next steps:\n' +
371
+ ` 1. Edit component files in components/custom/${componentName}/\n` +
372
+ ' 2. Run "fireforge furnace preview" to see it\n' +
373
+ ' 3. Run "fireforge build" to apply and build';
374
+ note(noteParts, componentName);
375
+ outro('Component created');
376
+ }
377
+ //# sourceMappingURL=create.js.map
@@ -0,0 +1,8 @@
1
+ import type { FurnaceDeployOptions } from '../../types/commands/index.js';
2
+ /**
3
+ * Runs the furnace deploy command: apply components then validate in one step.
4
+ * @param projectRoot - Root directory of the project
5
+ * @param name - Optional component name to deploy (deploys all if omitted)
6
+ * @param options - Command options
7
+ */
8
+ export declare function furnaceDeployCommand(projectRoot: string, name?: string, options?: FurnaceDeployOptions): Promise<void>;