@stripchatdev/create-stripchat-extension 0.1.45 → 0.1.47

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 (214) hide show
  1. package/dist/bin/cli/actions/updateAction.d.ts +6 -0
  2. package/dist/bin/cli/actions/updateAction.js +33 -0
  3. package/dist/bin/cli/helpers/printCliError.d.ts +1 -0
  4. package/dist/bin/cli/helpers/printCliError.js +21 -0
  5. package/dist/bin/cli/index.js +70 -0
  6. package/dist/bin/create.d.ts +2 -0
  7. package/dist/bin/create.js +238 -0
  8. package/dist/package.json +5 -2
  9. package/dist/src/constants.d.ts +14 -9
  10. package/dist/src/constants.js +53 -8
  11. package/dist/src/dependencies/constants.d.ts +2 -0
  12. package/dist/src/dependencies/constants.js +11 -0
  13. package/dist/src/dependencies/resolveDependencies.d.ts +6 -0
  14. package/dist/src/dependencies/resolveDependencies.js +43 -0
  15. package/dist/src/dependencies/resolveDependencies.test.js +74 -0
  16. package/dist/src/helpers/error.d.ts +3 -0
  17. package/dist/src/helpers/error.js +9 -0
  18. package/dist/src/helpers/error.test.js +15 -0
  19. package/dist/src/helpers/getIsDirectoryExists.d.ts +7 -0
  20. package/dist/src/helpers/getIsDirectoryExists.js +23 -0
  21. package/dist/src/helpers/getIsDirectoryExists.test.js +34 -0
  22. package/dist/src/helpers/getIsJsxFramework.d.ts +2 -0
  23. package/dist/src/helpers/getIsJsxFramework.js +2 -0
  24. package/dist/src/helpers/getIsJsxFramework.test.js +10 -0
  25. package/dist/src/helpers/getIsTypescript.d.ts +2 -0
  26. package/dist/src/helpers/getIsTypescript.js +1 -0
  27. package/dist/src/helpers/getIsTypescript.test.js +10 -0
  28. package/dist/src/helpers/isSubPath.d.ts +1 -0
  29. package/dist/src/helpers/isSubPath.js +5 -0
  30. package/dist/src/helpers/normalizePath.d.ts +2 -0
  31. package/dist/src/helpers/normalizePath.js +4 -0
  32. package/dist/src/helpers/normalizePath.test.js +19 -0
  33. package/dist/src/migrator/executor.d.ts +9 -0
  34. package/dist/src/migrator/executor.js +112 -0
  35. package/dist/src/migrator/migration.d.ts +3 -0
  36. package/dist/src/migrator/migration.js +18 -0
  37. package/dist/src/migrator/migration.test.js +74 -0
  38. package/dist/src/migrator/migrations/migrations.test.js +97 -0
  39. package/dist/src/migrator/migrations/v2/AGENTS_md/001_recreate-with-generated-part.d.ts +2 -0
  40. package/dist/src/migrator/migrations/v2/AGENTS_md/001_recreate-with-generated-part.js +14 -0
  41. package/dist/src/migrator/migrations/v2/_project-config_json/001_create.d.ts +2 -0
  42. package/dist/src/migrator/migrations/v2/_project-config_json/001_create.js +14 -0
  43. package/dist/src/migrator/migrations/v2/vite_config_ts/001_turn_off_minify_and_assets_inline.d.ts +2 -0
  44. package/dist/src/migrator/migrations/v2/vite_config_ts/001_turn_off_minify_and_assets_inline.js +118 -0
  45. package/dist/src/migrator/planner.d.ts +5 -0
  46. package/dist/src/migrator/planner.js +65 -0
  47. package/dist/src/migrator/planner.test.js +40 -0
  48. package/dist/src/migrator/types.d.ts +29 -0
  49. package/dist/src/migrator/updates/updates.test.js +102 -0
  50. package/dist/src/migrator/updates/v2/AGENTS_md/001_update-generated-part.d.ts +2 -0
  51. package/dist/src/migrator/updates/v2/AGENTS_md/001_update-generated-part.js +38 -0
  52. package/dist/src/migrator/updates/v2/package_json/001_update-versions.d.ts +2 -0
  53. package/dist/src/migrator/updates/v2/package_json/001_update-versions.js +77 -0
  54. package/dist/src/migrator/updates/v2/package_json/002_add-cli-commands.d.ts +2 -0
  55. package/dist/src/migrator/updates/v2/package_json/002_add-cli-commands.js +61 -0
  56. package/dist/src/migrator/validators/getIsValidMigration.d.ts +2 -0
  57. package/dist/src/migrator/validators/getIsValidMigration.js +3 -0
  58. package/dist/src/migrator/validators/getIsValidMigration.test.js +13 -0
  59. package/dist/src/projectConfig/buildProjectConfig.d.ts +15 -0
  60. package/dist/src/projectConfig/buildProjectConfig.js +27 -0
  61. package/dist/src/projectConfig/detectPackageManagerRuntime.d.ts +2 -0
  62. package/dist/src/{helpers/detectPackageManager.js → projectConfig/detectPackageManagerRuntime.js} +1 -1
  63. package/dist/src/{helpers/detectPackageManager.test.js → projectConfig/detectPackageManagerRuntime.test.js} +10 -10
  64. package/dist/src/projectConfig/recoverProjectConfig.d.ts +1 -0
  65. package/dist/src/projectConfig/recoverProjectConfig.js +34 -0
  66. package/dist/src/projectConfig/recovery/detectPackageManagerFromFiles.d.ts +2 -0
  67. package/dist/src/projectConfig/recovery/detectPackageManagerFromFiles.js +16 -0
  68. package/dist/src/projectConfig/recovery/detectPackageManagerFromFiles.test.d.ts +1 -0
  69. package/dist/src/projectConfig/recovery/detectPackageManagerFromFiles.test.js +37 -0
  70. package/dist/src/projectConfig/recovery/parseAgentsMd.d.ts +10 -0
  71. package/dist/src/projectConfig/recovery/parseAgentsMd.js +52 -0
  72. package/dist/src/projectConfig/recovery/parseAgentsMd.test.d.ts +1 -0
  73. package/dist/src/projectConfig/recovery/parseAgentsMd.test.js +67 -0
  74. package/dist/src/projectConfig/recovery/resolveProjectNameFromDir.d.ts +1 -0
  75. package/dist/src/projectConfig/recovery/resolveProjectNameFromDir.js +11 -0
  76. package/dist/src/projectConfig/recovery/resolveProjectNameFromDir.test.d.ts +1 -0
  77. package/dist/src/projectConfig/recovery/resolveProjectNameFromDir.test.js +21 -0
  78. package/dist/src/projectConfig/resolveExistsProjectConfig.d.ts +2 -0
  79. package/dist/src/projectConfig/resolveExistsProjectConfig.js +34 -0
  80. package/dist/src/projectConfig/updateProjectConfig.d.ts +2 -0
  81. package/dist/src/projectConfig/updateProjectConfig.js +20 -0
  82. package/dist/src/projectConfig/validators/getIsValidProjectConfig.d.ts +2 -0
  83. package/dist/src/projectConfig/validators/getIsValidProjectConfig.js +6 -0
  84. package/dist/src/projectConfig/validators/getIsValidProjectConfig.test.d.ts +1 -0
  85. package/dist/src/projectConfig/validators/getIsValidProjectConfig.test.js +33 -0
  86. package/dist/src/projectConfig/validators/validateProjectConfig.d.ts +2 -0
  87. package/dist/src/projectConfig/validators/validateProjectConfig.js +30 -0
  88. package/dist/src/projectConfig/validators/validateProjectConfig.test.d.ts +1 -0
  89. package/dist/src/projectConfig/validators/validateProjectConfig.test.js +215 -0
  90. package/dist/src/templater/executor.d.ts +2 -0
  91. package/dist/src/templater/executor.js +22 -0
  92. package/dist/src/templater/executor.test.d.ts +1 -0
  93. package/dist/src/{executor.test.js → templater/executor.test.js} +25 -11
  94. package/dist/src/templater/helpers/getIsTypescriptOnlyFile.d.ts +1 -0
  95. package/dist/src/templater/helpers/getIsTypescriptOnlyFile.js +10 -0
  96. package/dist/src/templater/helpers/getIsTypescriptOnlyFile.test.d.ts +1 -0
  97. package/dist/src/templater/helpers/getIsTypescriptOnlyFile.test.js +25 -0
  98. package/dist/src/templater/helpers/resolveTemplateExtension.d.ts +1 -0
  99. package/dist/src/templater/helpers/resolveTemplateExtension.js +16 -0
  100. package/dist/src/templater/helpers/resolveTemplateExtension.test.d.ts +1 -0
  101. package/dist/src/{mappers → templater/mappers}/mapProjectConfigToTemplateModel.d.ts +1 -1
  102. package/dist/src/{mappers → templater/mappers}/mapProjectConfigToTemplateModel.js +6 -8
  103. package/dist/src/templater/mappers/mapProjectConfigToTemplateModel.test.d.ts +1 -0
  104. package/dist/src/{mappers → templater/mappers}/mapProjectConfigToTemplateModel.test.js +29 -23
  105. package/dist/src/templater/planner.d.ts +2 -0
  106. package/dist/src/templater/planner.js +104 -0
  107. package/dist/src/templater/planner.test.d.ts +1 -0
  108. package/dist/src/templater/planner.test.js +64 -0
  109. package/dist/src/templates/constants.d.ts +6 -0
  110. package/dist/src/templates/constants.js +11 -0
  111. package/dist/src/templates/helpers/resolveTemplatesDir.d.ts +1 -0
  112. package/dist/src/templates/helpers/resolveTemplatesDir.js +16 -0
  113. package/dist/src/templates/templates.d.ts +10 -0
  114. package/dist/src/templates/templates.js +133 -0
  115. package/dist/src/templates/templates.test.d.ts +1 -0
  116. package/dist/src/templates/templates.test.js +263 -0
  117. package/dist/src/templates/types.d.ts +21 -0
  118. package/dist/src/templates/types.js +1 -0
  119. package/dist/src/types.d.ts +28 -9
  120. package/dist/src/validators/getIsValidCategory.d.ts +2 -0
  121. package/dist/src/validators/getIsValidCategory.js +2 -0
  122. package/dist/src/validators/getIsValidCategory.test.d.ts +1 -0
  123. package/dist/src/validators/{isValidCategory.test.js → getIsValidCategory.test.js} +3 -3
  124. package/dist/src/validators/getIsValidFramework.d.ts +2 -0
  125. package/dist/src/validators/getIsValidFramework.js +2 -0
  126. package/dist/src/validators/getIsValidFramework.test.d.ts +1 -0
  127. package/dist/src/validators/{isValidFramework.test.js → getIsValidFramework.test.js} +3 -3
  128. package/dist/src/validators/getIsValidLanguage.d.ts +2 -0
  129. package/dist/src/validators/getIsValidLanguage.js +2 -0
  130. package/dist/src/validators/getIsValidLanguage.test.d.ts +1 -0
  131. package/dist/src/validators/{isValidLanguage.test.js → getIsValidLanguage.test.js} +3 -3
  132. package/dist/src/validators/getIsValidPackageManager.d.ts +2 -0
  133. package/dist/src/validators/getIsValidPackageManager.js +4 -0
  134. package/dist/src/validators/getIsValidPackageManager.test.d.ts +1 -0
  135. package/dist/src/validators/getIsValidPackageManager.test.js +11 -0
  136. package/dist/src/validators/getIsValidSlot.d.ts +2 -0
  137. package/dist/src/validators/getIsValidSlot.js +4 -0
  138. package/dist/src/validators/getIsValidSlot.test.d.ts +1 -0
  139. package/dist/src/validators/getIsValidSlot.test.js +26 -0
  140. package/dist/src/validators/validateSlots.js +40 -8
  141. package/dist/src/validators/validateSlots.test.js +125 -34
  142. package/dist/tsconfig.tsbuildinfo +1 -1
  143. package/package.json +5 -2
  144. package/templates/v1/vite.config.ts.ejs +27 -0
  145. package/templates/v2/.project-config.json.ejs +1 -0
  146. package/templates/v2/AGENTS.md.ejs +712 -0
  147. package/templates/v2/README.md.ejs +31 -0
  148. package/templates/v2/package.json.ejs +26 -0
  149. package/dist/bin/cli.js +0 -221
  150. package/dist/src/executor.d.ts +0 -2
  151. package/dist/src/executor.js +0 -22
  152. package/dist/src/helpers/detectPackageManager.d.ts +0 -2
  153. package/dist/src/helpers/isDirectory.d.ts +0 -1
  154. package/dist/src/helpers/isDirectory.js +0 -9
  155. package/dist/src/helpers/isDirectory.test.js +0 -22
  156. package/dist/src/helpers/isTypescriptOnlyFile.d.ts +0 -1
  157. package/dist/src/helpers/isTypescriptOnlyFile.js +0 -5
  158. package/dist/src/helpers/isTypescriptOnlyFile.test.js +0 -22
  159. package/dist/src/helpers/normalizeRelPath.d.ts +0 -1
  160. package/dist/src/helpers/normalizeRelPath.js +0 -1
  161. package/dist/src/helpers/normalizeRelPath.test.js +0 -19
  162. package/dist/src/helpers/resolveTemplateExtension.d.ts +0 -1
  163. package/dist/src/helpers/resolveTemplateExtension.js +0 -17
  164. package/dist/src/helpers/resolveTemplatesDir.d.ts +0 -1
  165. package/dist/src/helpers/resolveTemplatesDir.js +0 -11
  166. package/dist/src/helpers/resolveTemplatesDir.test.js +0 -21
  167. package/dist/src/planner.d.ts +0 -2
  168. package/dist/src/planner.js +0 -126
  169. package/dist/src/planner.test.js +0 -58
  170. package/dist/src/validators/index.d.ts +0 -6
  171. package/dist/src/validators/index.js +0 -6
  172. package/dist/src/validators/isValidCategory.d.ts +0 -2
  173. package/dist/src/validators/isValidCategory.js +0 -2
  174. package/dist/src/validators/isValidFramework.d.ts +0 -2
  175. package/dist/src/validators/isValidFramework.js +0 -2
  176. package/dist/src/validators/isValidLanguage.d.ts +0 -2
  177. package/dist/src/validators/isValidLanguage.js +0 -2
  178. package/dist/src/validators/validateProjectDir.d.ts +0 -1
  179. package/dist/src/validators/validateProjectDir.js +0 -6
  180. package/dist/src/validators/validateProjectDir.test.js +0 -19
  181. package/dist/bin/{cli.d.ts → cli/index.d.ts} +0 -0
  182. package/dist/src/{executor.test.d.ts → dependencies/resolveDependencies.test.d.ts} +0 -0
  183. package/dist/src/helpers/{detectPackageManager.test.d.ts → error.test.d.ts} +0 -0
  184. package/dist/src/helpers/{isDirectory.test.d.ts → getIsDirectoryExists.test.d.ts} +0 -0
  185. package/dist/src/helpers/{isTypescriptOnlyFile.test.d.ts → getIsJsxFramework.test.d.ts} +0 -0
  186. package/dist/src/helpers/{normalizeRelPath.test.d.ts → getIsTypescript.test.d.ts} +0 -0
  187. package/dist/src/helpers/{resolveTemplateExtension.test.d.ts → normalizePath.test.d.ts} +0 -0
  188. package/dist/src/{helpers/resolveTemplatesDir.test.d.ts → migrator/migration.test.d.ts} +0 -0
  189. package/dist/src/{mappers/mapProjectConfigToTemplateModel.test.d.ts → migrator/migrations/migrations.test.d.ts} +0 -0
  190. package/dist/src/{planner.test.d.ts → migrator/planner.test.d.ts} +0 -0
  191. package/dist/src/{validators/isValidCategory.test.d.ts → migrator/types.js} +0 -0
  192. package/dist/src/{validators/isValidFramework.test.d.ts → migrator/updates/updates.test.d.ts} +0 -0
  193. package/dist/src/{validators/isValidLanguage.test.d.ts → migrator/validators/getIsValidMigration.test.d.ts} +0 -0
  194. package/dist/src/{validators/validateProjectDir.test.d.ts → projectConfig/detectPackageManagerRuntime.test.d.ts} +0 -0
  195. package/dist/src/{helpers → templater/helpers}/resolveTemplateExtension.test.js +0 -0
  196. package/templates/{.gitignore.ejs → v1/.gitignore.ejs} +0 -0
  197. package/templates/{AGENTS.md.ejs → v1/AGENTS.md.ejs} +0 -0
  198. package/templates/{CLAUDE.md → v1/CLAUDE.md} +0 -0
  199. package/templates/{README.md.ejs → v1/README.md.ejs} +0 -0
  200. package/templates/{package.json.ejs → v1/package.json.ejs} +0 -0
  201. package/templates/{public → v1/public}/icon.svg +0 -0
  202. package/templates/{public → v1/public}/manifest.json.ejs +0 -0
  203. package/templates/{public → v1/public}/resolveSlotPage.js.ejs +0 -0
  204. package/templates/{src → v1/src}/slots/background/app.ts.ejs +0 -0
  205. package/templates/{src → v1/src}/slots/background/background.html.ejs +0 -0
  206. package/templates/{src → v1/src}/slots/background/main.ts.ejs +0 -0
  207. package/templates/{src/slots/slot/slot.html.ejs → v1/src/slots/slot/__SLOT_KEY__.html.ejs} +0 -0
  208. package/templates/{src → v1/src}/slots/slot/app.ts.ejs +0 -0
  209. package/templates/{src → v1/src}/slots/slot/app.tsx.ejs +0 -0
  210. package/templates/{src → v1/src}/slots/slot/main.css.ejs +0 -0
  211. package/templates/{src → v1/src}/slots/slot/main.ts.ejs +0 -0
  212. package/templates/{src → v1/src}/slots/slot/main.tsx.ejs +0 -0
  213. package/templates/{tsconfig.json.ejs → v1/tsconfig.json.ejs} +0 -0
  214. package/templates/{vite.config.ts.ejs → v2/vite.config.ts.ejs} +1 -1
@@ -0,0 +1,31 @@
1
+ # <%= name %>
2
+
3
+ > **Note**
4
+ >
5
+ > This file can be safely modified to fit your project's needs.
6
+
7
+ Stripchat extension built with <%= framework === 'preact' ? 'Preact' : 'Vanilla' %> + <%= isTypescript ? 'TypeScript' : 'JavaScript' %>
8
+
9
+ ## Start Here
10
+
11
+ Read `AGENTS.md` first. It is the project contract for SDK usage, slot lifecycles, resolver rules, theming, and settings.
12
+
13
+ ## Documentation
14
+
15
+ Learn more about building Stripchat extensions in the [official documentation](https://extensions.stripchat.com/docs/).
16
+
17
+ ## Usage
18
+
19
+ ```bash
20
+ # Install dependencies
21
+ <%= packageManagerCommands.install %>
22
+
23
+ # Start development server
24
+ <%= packageManagerCommands.dev %>
25
+
26
+ # Build for production
27
+ <%= packageManagerCommands.build %>
28
+
29
+ # Update template
30
+ <%= packageManagerCommands.templateUpdate %>
31
+ ```
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "<%= name %>",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "<%- isTypescript ? 'tsc && vite build' : 'vite build' %>",
8
+ "preview": "vite preview",
9
+ <%_ const cliCommandsEntries = Object.entries(cliCommands); _%>
10
+ <%_ cliCommandsEntries.forEach(([name, value], index) => { _%>
11
+ "<%= name %>": "<%= value %>"<%= index < cliCommandsEntries.length - 1 ? ',' : '' %>
12
+ <%_ }) _%>
13
+ },
14
+ "dependencies": {
15
+ <%_ const dependencyEntries = Object.entries(dependencies); _%>
16
+ <%_ dependencyEntries.forEach(([name, version], index) => { _%>
17
+ "<%= name %>": "<%= version %>"<%= index < dependencyEntries.length - 1 ? ',' : '' %>
18
+ <%_ }) _%>
19
+ },
20
+ "devDependencies": {
21
+ <%_ const devDependencyEntries = Object.entries(devDependencies); _%>
22
+ <%_ devDependencyEntries.forEach(([name, version], index) => { _%>
23
+ "<%= name %>": "<%= version %>"<%= index < devDependencyEntries.length - 1 ? ',' : '' %>
24
+ <%_ }) _%>
25
+ }
26
+ }
package/dist/bin/cli.js DELETED
@@ -1,221 +0,0 @@
1
- #!/usr/bin/env node
2
- import { checkbox, input, select } from '@inquirer/prompts';
3
- import { join } from 'node:path';
4
- import { parseArgs } from 'node:util';
5
- import packageJson from '../package.json' with { type: 'json' };
6
- import { CATEGORIES, CATEGORY_LABELS, FRAMEWORK_LABELS, FRAMEWORKS, LANGUAGE_LABELS, LANGUAGES, REQUIRED_SLOTS_BY_CATEGORY, SLOT_RULES_BY_CATEGORY, SLOT_LABELS, } from '../src/constants.js';
7
- import { executeCommands } from '../src/executor.js';
8
- import { detectPackageManager } from '../src/helpers/detectPackageManager.js';
9
- import { mapProjectConfigToTemplateData } from '../src/mappers/mapProjectConfigToTemplateModel.js';
10
- import { planProject } from '../src/planner.js';
11
- import { isValidCategory, isValidFramework, isValidLanguage, validateName, validateProjectDir, validateSlots, } from '../src/validators/index.js';
12
- const HELP = `
13
- Usage: create-stripchat-extension [options]
14
-
15
- Create a new Stripchat extension project.
16
-
17
- Options:
18
- --name <name> Project name (used as a directory and package name)
19
- --category <category> Extension category (${CATEGORIES.join(', ')})
20
- --slots <items> Comma-separated slots (e.g. "EXTENSION_SLOT_MAIN_GAME_FUN,EXTENSION_SLOT_RIGHT_OVERLAY") or empty string
21
- --framework <framework> UI framework (${FRAMEWORKS.join(', ')})
22
- --language <lang> Language (${LANGUAGES.join(', ')})
23
- -h, --help Display this help message
24
- -v, --version Show version number
25
- `.trim();
26
- const PACKAGE_MANAGER = detectPackageManager();
27
- const parseCliArgs = () => {
28
- try {
29
- const { values } = parseArgs({
30
- args: process.argv.slice(2),
31
- options: {
32
- category: { type: 'string' },
33
- framework: { type: 'string' },
34
- help: { short: 'h', type: 'boolean' },
35
- language: { type: 'string' },
36
- name: { type: 'string' },
37
- slots: { type: 'string' },
38
- version: { short: 'v', type: 'boolean' },
39
- },
40
- strict: true,
41
- });
42
- return values;
43
- }
44
- catch (err) {
45
- console.error(`Error: ${err.message}\n`);
46
- console.log(HELP);
47
- process.exit(1);
48
- }
49
- };
50
- const resolveName = (flagValue) => {
51
- if (flagValue) {
52
- const result = validateName(flagValue);
53
- if (result !== true) {
54
- console.error(`Error: ${result}`);
55
- process.exit(1);
56
- }
57
- return Promise.resolve(flagValue.trim());
58
- }
59
- return input({
60
- message: 'Project name:',
61
- validate: validateName,
62
- });
63
- };
64
- const resolveCategory = (flagValue) => {
65
- if (flagValue) {
66
- if (!isValidCategory(flagValue)) {
67
- console.error(`Error: invalid category "${flagValue}". Choose one of: ${CATEGORIES.join(', ')}`);
68
- process.exit(1);
69
- }
70
- return Promise.resolve(flagValue);
71
- }
72
- return select({
73
- choices: CATEGORIES.map((cat) => ({
74
- name: CATEGORY_LABELS[cat],
75
- value: cat,
76
- })),
77
- message: 'Extension category:',
78
- });
79
- };
80
- const getSlotsErrorMessage = (category, requestedSlots) => {
81
- const rules = SLOT_RULES_BY_CATEGORY[category];
82
- const { invalid, violations } = validateSlots(requestedSlots, category);
83
- if (invalid.length > 0) {
84
- if (rules.optional.length === 0) {
85
- return `Error: invalid slot(s): "${invalid.join(', ')}". This category doesn't have optional slots.`;
86
- }
87
- return `Error: invalid slot(s) "${invalid.join(', ')}". Choose from: ${rules.optional.join(', ')}`;
88
- }
89
- if (violations.length > 0) {
90
- return `Error: ${violations.join(' ')}`;
91
- }
92
- return undefined;
93
- };
94
- const normalizePromptSlots = (value) => value.flatMap((item) => {
95
- if (typeof item === 'string') {
96
- return [item];
97
- }
98
- if (typeof item === 'object' && item !== null && 'value' in item && typeof item.value === 'string') {
99
- return [item.value];
100
- }
101
- return [];
102
- });
103
- const resolveSlots = async (flagValue, category) => {
104
- if (flagValue !== undefined) {
105
- const requested = flagValue.split(',').map((s) => s.trim());
106
- if (flagValue === '') {
107
- return {
108
- errorMessage: getSlotsErrorMessage(category, []),
109
- slots: [],
110
- };
111
- }
112
- return {
113
- errorMessage: getSlotsErrorMessage(category, requested),
114
- slots: validateSlots(requested, category).selected,
115
- };
116
- }
117
- const optionalSlots = SLOT_RULES_BY_CATEGORY[category].optional;
118
- const rawSelections = optionalSlots.length === 0
119
- ? []
120
- : await checkbox({
121
- choices: optionalSlots.map((slot) => ({
122
- checked: false,
123
- name: SLOT_LABELS[slot],
124
- value: slot,
125
- })),
126
- message: 'Select optional slots:',
127
- required: false,
128
- validate: (value) => getSlotsErrorMessage(category, normalizePromptSlots(value)) ?? true,
129
- });
130
- const selections = normalizePromptSlots(rawSelections);
131
- const { selected } = validateSlots(selections, category);
132
- return {
133
- errorMessage: getSlotsErrorMessage(category, selections),
134
- slots: selected,
135
- };
136
- };
137
- const resolveFramework = (flagValue) => {
138
- if (flagValue) {
139
- if (!isValidFramework(flagValue)) {
140
- console.error(`Error: invalid framework "${flagValue}". Choose one of: ${FRAMEWORKS.join(', ')}`);
141
- process.exit(1);
142
- }
143
- return Promise.resolve(flagValue);
144
- }
145
- return select({
146
- choices: FRAMEWORKS.map((framework) => ({
147
- name: FRAMEWORK_LABELS[framework],
148
- value: framework,
149
- })),
150
- message: 'UI framework:',
151
- });
152
- };
153
- const resolveLanguage = (flagValue) => {
154
- if (flagValue) {
155
- if (!isValidLanguage(flagValue)) {
156
- console.error(`Error: invalid language "${flagValue}". Choose one of: ${LANGUAGES.join(', ')}`);
157
- process.exit(1);
158
- }
159
- return Promise.resolve(flagValue);
160
- }
161
- return select({
162
- choices: LANGUAGES.map((lang) => ({
163
- name: LANGUAGE_LABELS[lang],
164
- value: lang,
165
- })),
166
- message: 'Language:',
167
- });
168
- };
169
- const main = async () => {
170
- const opts = parseCliArgs();
171
- if (opts.help) {
172
- console.log(HELP);
173
- process.exit(0);
174
- }
175
- if (opts.version) {
176
- console.log(packageJson.version);
177
- process.exit(0);
178
- }
179
- const name = await resolveName(opts.name);
180
- const category = await resolveCategory(opts.category);
181
- const hasSelectableSlots = SLOT_RULES_BY_CATEGORY[category].optional.length > 0;
182
- let selectedSlots = [];
183
- if (hasSelectableSlots) {
184
- const resolvedSlots = await resolveSlots(opts.slots, category);
185
- if (resolvedSlots.errorMessage) {
186
- console.error(resolvedSlots.errorMessage);
187
- process.exit(1);
188
- }
189
- selectedSlots = resolvedSlots.slots;
190
- }
191
- const isVanilla = category === 'chat-bots'
192
- || (category === 'broadcast-tools' && !selectedSlots.includes('EXTENSION_SLOT_MOVEABLE_OVERLAY'));
193
- const framework = isVanilla ? 'vanilla' : await resolveFramework(opts.framework);
194
- const language = await resolveLanguage(opts.language);
195
- const slots = [...selectedSlots, ...REQUIRED_SLOTS_BY_CATEGORY[category]];
196
- const projectDir = join(process.cwd(), name);
197
- validateProjectDir(projectDir, name);
198
- console.log(`\nCreating project in ./${name}...\n`);
199
- const data = mapProjectConfigToTemplateData({
200
- category,
201
- framework,
202
- language,
203
- name,
204
- packageManager: PACKAGE_MANAGER,
205
- slots,
206
- });
207
- const commands = await planProject(data, projectDir);
208
- await executeCommands(commands, (command) => console.log(` Created ${command.destPath}`));
209
- console.log('\nDone! Next steps:\n');
210
- console.log(` cd ${name}`);
211
- console.log(` ${data.packageManagerCommands.install}`);
212
- console.log(` ${data.packageManagerCommands.dev}\n`);
213
- };
214
- main().catch((err) => {
215
- if (err.name === 'ExitPromptError') {
216
- console.log('\nAborted.');
217
- process.exit(0);
218
- }
219
- console.error(err);
220
- process.exit(1);
221
- });
@@ -1,2 +0,0 @@
1
- import type { TTemplateCommand } from './types.js';
2
- export declare const executeCommands: (commands: TTemplateCommand[], cb?: (command: TTemplateCommand) => void) => Promise<void>;
@@ -1,22 +0,0 @@
1
- import ejs from 'ejs';
2
- import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises';
3
- import { dirname } from 'node:path';
4
- const renderTemplate = async (templatePath, data) => {
5
- const template = await readFile(templatePath, 'utf-8');
6
- return ejs.render(template, data, { filename: templatePath });
7
- };
8
- export const executeCommands = async (commands, cb) => {
9
- for (const command of commands) {
10
- await mkdir(dirname(command.destPath), { recursive: true });
11
- cb?.(command);
12
- if (command.type === 'render') {
13
- const content = await renderTemplate(command.srcPath, command.data);
14
- await writeFile(command.destPath, content, 'utf-8');
15
- continue;
16
- }
17
- if (command.type === 'copy') {
18
- await copyFile(command.srcPath, command.destPath);
19
- continue;
20
- }
21
- }
22
- };
@@ -1,2 +0,0 @@
1
- import type { TPackageManager } from '../types.js';
2
- export declare const detectPackageManager: () => TPackageManager;
@@ -1 +0,0 @@
1
- export declare const isDirectory: (pathValue: string) => boolean;
@@ -1,9 +0,0 @@
1
- import { statSync } from 'node:fs';
2
- export const isDirectory = (pathValue) => {
3
- try {
4
- return statSync(pathValue).isDirectory();
5
- }
6
- catch {
7
- return false;
8
- }
9
- };
@@ -1,22 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import { isDirectory } from './isDirectory.js';
3
- vi.mock('node:fs', () => ({
4
- statSync: (path) => {
5
- if (path === '/exists/dir')
6
- return { isDirectory: () => true };
7
- if (path === '/exists/file')
8
- return { isDirectory: () => false };
9
- throw new Error('ENOENT');
10
- },
11
- }));
12
- describe('isDirectory', () => {
13
- it('returns true for an existing directory', () => {
14
- expect(isDirectory('/exists/dir')).toBe(true);
15
- });
16
- it('returns false for an existing file', () => {
17
- expect(isDirectory('/exists/file')).toBe(false);
18
- });
19
- it('returns false when path does not exist', () => {
20
- expect(isDirectory('/does/not/exist')).toBe(false);
21
- });
22
- });
@@ -1 +0,0 @@
1
- export declare const isTypescriptOnlyFile: (normalizedRelPath: string) => boolean;
@@ -1,5 +0,0 @@
1
- import { TS_ONLY_FILES } from '../constants.js';
2
- export const isTypescriptOnlyFile = (normalizedRelPath) => {
3
- const withoutEjs = normalizedRelPath.replace(/\.ejs$/, '');
4
- return TS_ONLY_FILES.includes(withoutEjs);
5
- };
@@ -1,22 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { isTypescriptOnlyFile } from './isTypescriptOnlyFile.js';
3
- describe('isTypescriptOnlyFile', () => {
4
- it('returns true for tsconfig.json', () => {
5
- expect(isTypescriptOnlyFile('tsconfig.json')).toBe(true);
6
- });
7
- it('returns true for tsconfig.json.ejs (strips .ejs suffix)', () => {
8
- expect(isTypescriptOnlyFile('tsconfig.json.ejs')).toBe(true);
9
- });
10
- it('returns false for package.json', () => {
11
- expect(isTypescriptOnlyFile('package.json')).toBe(false);
12
- });
13
- it('returns false for package.json.ejs', () => {
14
- expect(isTypescriptOnlyFile('package.json.ejs')).toBe(false);
15
- });
16
- it('returns false for vite.config.ts.ejs', () => {
17
- expect(isTypescriptOnlyFile('vite.config.ts.ejs')).toBe(false);
18
- });
19
- it('returns false for an empty string', () => {
20
- expect(isTypescriptOnlyFile('')).toBe(false);
21
- });
22
- });
@@ -1 +0,0 @@
1
- export declare const normalizeRelPath: (pathValue: string) => string;
@@ -1 +0,0 @@
1
- export const normalizeRelPath = (pathValue) => pathValue.replaceAll('\\', '/');
@@ -1,19 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { normalizeRelPath } from './normalizeRelPath.js';
3
- describe('normalizeRelPath', () => {
4
- it('replaces backslashes with forward slashes', () => {
5
- expect(normalizeRelPath('src\\slots\\main')).toBe('src/slots/main');
6
- });
7
- it('replaces multiple backslashes', () => {
8
- expect(normalizeRelPath('a\\b\\c\\d')).toBe('a/b/c/d');
9
- });
10
- it('leaves forward slashes unchanged', () => {
11
- expect(normalizeRelPath('src/slots/main')).toBe('src/slots/main');
12
- });
13
- it('handles mixed separators', () => {
14
- expect(normalizeRelPath('src\\slots/main\\file.ts')).toBe('src/slots/main/file.ts');
15
- });
16
- it('returns empty string unchanged', () => {
17
- expect(normalizeRelPath('')).toBe('');
18
- });
19
- });
@@ -1 +0,0 @@
1
- export declare const resolveTemplateExtension: (normalizedRelPath: string, isTypescript: boolean) => string;
@@ -1,17 +0,0 @@
1
- const resolveOutputExtension = (templateExt, isTypescript) => {
2
- const map = {
3
- '.ts': isTypescript ? '.ts' : '.js',
4
- '.tsx': isTypescript ? '.tsx' : '.jsx',
5
- };
6
- return map[templateExt] ?? templateExt;
7
- };
8
- export const resolveTemplateExtension = (normalizedRelPath, isTypescript) => {
9
- const withoutEjs = normalizedRelPath.replace(/\.ejs$/, '');
10
- const extMatch = withoutEjs.match(/\.(tsx?|jsx?)$/);
11
- if (!extMatch) {
12
- return withoutEjs;
13
- }
14
- const templateExt = extMatch[0];
15
- const base = withoutEjs.slice(0, -templateExt.length);
16
- return `${base}${resolveOutputExtension(templateExt, isTypescript)}`;
17
- };
@@ -1 +0,0 @@
1
- export declare const resolveTemplatesDir: (moduleDir: string) => string;
@@ -1,11 +0,0 @@
1
- import { resolve } from 'node:path';
2
- import { isDirectory } from './isDirectory.js';
3
- export const resolveTemplatesDir = (moduleDir) => {
4
- const candidates = ['../templates', '../../templates'].map((candidate) => resolve(moduleDir, candidate));
5
- for (const candidate of candidates) {
6
- if (isDirectory(candidate)) {
7
- return candidate;
8
- }
9
- }
10
- throw new Error(`Could not find templates directory near ${moduleDir}. Tried: ${candidates.join(', ')}`);
11
- };
@@ -1,21 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import { resolveTemplatesDir } from './resolveTemplatesDir.js';
3
- vi.mock('./isDirectory', () => ({
4
- isDirectory: (path) => path.endsWith('/templates') && path.includes('good'),
5
- }));
6
- describe('resolveTemplatesDir', () => {
7
- it('returns ../templates when it exists', () => {
8
- const result = resolveTemplatesDir('/project/good/src');
9
- expect(result).toMatch(/\/templates$/);
10
- });
11
- it('returns ../../templates when ../templates does not exist', () => {
12
- const result = resolveTemplatesDir('/project/good/dist/src');
13
- expect(result).toMatch(/\/templates$/);
14
- });
15
- it('throws when no templates directory is found', () => {
16
- expect(() => resolveTemplatesDir('/project/bad/src')).toThrow(/Could not find templates directory/);
17
- });
18
- it('includes tried paths in error message', () => {
19
- expect(() => resolveTemplatesDir('/project/bad/src')).toThrow(/Tried:/);
20
- });
21
- });
@@ -1,2 +0,0 @@
1
- import type { TTemplateCommand, TTemplateData } from './types.js';
2
- export declare const planProject: (data: TTemplateData, projectDir: string) => Promise<TTemplateCommand[]>;
@@ -1,126 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { readdir } from 'node:fs/promises';
3
- import { dirname, join, relative } from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import { isTypescriptOnlyFile } from './helpers/isTypescriptOnlyFile.js';
6
- import { normalizeRelPath } from './helpers/normalizeRelPath.js';
7
- import { resolveTemplateExtension } from './helpers/resolveTemplateExtension.js';
8
- import { resolveTemplatesDir } from './helpers/resolveTemplatesDir.js';
9
- const __dirname = dirname(fileURLToPath(import.meta.url));
10
- const TEMPLATES_DIR = resolveTemplatesDir(__dirname);
11
- const SLOTS_TEMPLATE_DIR = join(TEMPLATES_DIR, 'src', 'slots');
12
- const GENERIC_SLOT_DIR = join(SLOTS_TEMPLATE_DIR, 'slot');
13
- const shouldSkipGlobalTemplate = (normalizedRelPath, data) => isTypescriptOnlyFile(normalizedRelPath) && !data.isTypescript;
14
- const shouldSkipSlotTemplate = (normalizedRelPath, allPaths, isJsxFramework) => {
15
- if (normalizedRelPath.endsWith('.tsx.ejs')) {
16
- const tsCounterpart = normalizedRelPath.replace(/\.tsx\.ejs$/, '.ts.ejs');
17
- if (allPaths.has(tsCounterpart) && !isJsxFramework) {
18
- return true;
19
- }
20
- }
21
- if (normalizedRelPath.endsWith('.ts.ejs')) {
22
- const tsxCounterpart = normalizedRelPath.replace(/\.ts\.ejs$/, '.tsx.ejs');
23
- if (allPaths.has(tsxCounterpart) && isJsxFramework) {
24
- return true;
25
- }
26
- }
27
- return false;
28
- };
29
- const listFiles = async (dir) => {
30
- const entries = await readdir(dir, { recursive: true, withFileTypes: true });
31
- return entries
32
- .filter((entry) => entry.isFile())
33
- .map((entry) => ({
34
- relPath: normalizeRelPath(relative(dir, join(entry.parentPath, entry.name))),
35
- srcPath: join(entry.parentPath, entry.name),
36
- }))
37
- .sort((a, b) => a.relPath.localeCompare(b.relPath));
38
- };
39
- const planGlobalTemplates = async (projectDir, data) => {
40
- const files = await listFiles(TEMPLATES_DIR);
41
- const commands = [];
42
- for (const file of files) {
43
- if (file.relPath.startsWith('src/slots/')) {
44
- continue;
45
- }
46
- if (shouldSkipGlobalTemplate(file.relPath, data)) {
47
- continue;
48
- }
49
- const isEjs = file.relPath.endsWith('.ejs');
50
- if (!isEjs) {
51
- commands.push({
52
- destPath: join(projectDir, ...file.relPath.split('/')),
53
- srcPath: file.srcPath,
54
- type: 'copy',
55
- });
56
- continue;
57
- }
58
- commands.push({
59
- data,
60
- destPath: join(projectDir, ...resolveTemplateExtension(file.relPath, data.isTypescript).split('/')),
61
- srcPath: file.srcPath,
62
- type: 'render',
63
- });
64
- }
65
- return commands;
66
- };
67
- const buildSlotTemplateData = (data, slotKey) => {
68
- const isBackground = slotKey === 'background';
69
- const entryExt = isBackground || !data.isJsxFramework ? data.ext : data.jsxExt;
70
- return {
71
- ...data,
72
- entryModulePath: `/src/slots/${slotKey}/main.${entryExt}`,
73
- slot: slotKey,
74
- stylePath: `/src/slots/${slotKey}/main.css`,
75
- };
76
- };
77
- const resolveSlotOutputPath = (templateRelPath, slotKey, templateDirName) => {
78
- const parts = templateRelPath.split('/');
79
- const fileName = parts.at(-1);
80
- const dirPrefix = parts.slice(0, -1).join('/');
81
- const renamedFile = fileName.startsWith(`${templateDirName}.`)
82
- ? `${slotKey}${fileName.slice(templateDirName.length)}`
83
- : fileName;
84
- return dirPrefix ? `${dirPrefix}/${renamedFile}` : renamedFile;
85
- };
86
- const planSlotTemplates = async (projectDir, data, slotKey) => {
87
- const specificDir = join(SLOTS_TEMPLATE_DIR, slotKey);
88
- const hasSpecificDir = existsSync(specificDir);
89
- const templateDir = hasSpecificDir ? specificDir : GENERIC_SLOT_DIR;
90
- const templateDirName = hasSpecificDir ? slotKey : 'slot';
91
- const slotData = buildSlotTemplateData(data, slotKey);
92
- const files = await listFiles(templateDir);
93
- const allPaths = new Set(files.map((f) => f.relPath));
94
- const commands = [];
95
- for (const file of files) {
96
- if (shouldSkipSlotTemplate(file.relPath, allPaths, data.isJsxFramework)) {
97
- continue;
98
- }
99
- const outputRelPath = resolveSlotOutputPath(file.relPath, slotKey, templateDirName);
100
- const resolvedPath = resolveTemplateExtension(outputRelPath, data.isTypescript);
101
- const isHtml = resolvedPath.endsWith('.html');
102
- const destPath = isHtml
103
- ? join(projectDir, resolvedPath)
104
- : join(projectDir, 'src', 'slots', slotKey, ...resolvedPath.split('/'));
105
- if (!file.relPath.endsWith('.ejs')) {
106
- commands.push({
107
- destPath,
108
- srcPath: file.srcPath,
109
- type: 'copy',
110
- });
111
- continue;
112
- }
113
- commands.push({
114
- data: slotData,
115
- destPath,
116
- srcPath: file.srcPath,
117
- type: 'render',
118
- });
119
- }
120
- return commands;
121
- };
122
- export const planProject = async (data, projectDir) => {
123
- const globalCommands = await planGlobalTemplates(projectDir, data);
124
- const slotCommandArrays = await Promise.all(data.slotsEntries.map(({ slotCamelCased }) => planSlotTemplates(projectDir, data, slotCamelCased)));
125
- return [...globalCommands, ...slotCommandArrays.flat()];
126
- };
@@ -1,58 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { CATEGORIES, FRAMEWORKS, LANGUAGES, SLOT_RULES_BY_CATEGORY, } from './constants.js';
3
- import { mapProjectConfigToTemplateData } from './mappers/mapProjectConfigToTemplateModel.js';
4
- import { planProject } from './planner.js';
5
- const PROJECT_DIR = '/output/my-ext';
6
- const buildLabel = (language, framework, category, slots) => `${language} + ${framework}, ${category} (${slots.join(', ')})`;
7
- const buildSlotCombos = (category) => {
8
- const rules = SLOT_RULES_BY_CATEGORY[category];
9
- const base = [...rules.requiredBase];
10
- const combos = [base];
11
- if (category === 'games-and-fun') {
12
- combos.length = 0;
13
- combos.push([...base, 'EXTENSION_SLOT_RIGHT_OVERLAY'], [...base, 'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY']);
14
- return combos;
15
- }
16
- if (category === 'sex-toys') {
17
- combos.push([...base, 'EXTENSION_SLOT_MOVEABLE_OVERLAY'], [...base, 'EXTENSION_SLOT_RIGHT_OVERLAY'], [...base, 'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY'], [...base, 'EXTENSION_SLOT_MOVEABLE_OVERLAY', 'EXTENSION_SLOT_RIGHT_OVERLAY'], [...base, 'EXTENSION_SLOT_MOVEABLE_OVERLAY', 'EXTENSION_SLOT_VIDEO_DECORATIVE_OVERLAY']);
18
- }
19
- if (category === 'broadcast-tools') {
20
- combos.push([...base, 'EXTENSION_SLOT_MOVEABLE_OVERLAY']);
21
- }
22
- return combos;
23
- };
24
- const SLOT_COMBOS_BY_CATEGORY = Object.fromEntries(CATEGORIES.map((category) => {
25
- return [category, buildSlotCombos(category)];
26
- }));
27
- const TEST_MATRIX = CATEGORIES.flatMap((category) => SLOT_COMBOS_BY_CATEGORY[category].flatMap((slots) => FRAMEWORKS.flatMap((framework) => LANGUAGES.map((language) => ({
28
- category,
29
- framework,
30
- label: buildLabel(language, framework, category, slots),
31
- language,
32
- slots,
33
- })))));
34
- const normalizeCommands = (commands) => commands.map((cmd) => {
35
- const srcRel = cmd.srcPath.replace(/^.*\/templates\//, 'templates/');
36
- const destRel = cmd.destPath.replace(/^\/output\//, '');
37
- if (cmd.type === 'copy') {
38
- return { type: 'copy', src: srcRel, dest: destRel };
39
- }
40
- return { type: 'render', src: srcRel, dest: destRel };
41
- });
42
- describe('planProject', () => {
43
- for (const { category, framework, label, language, slots } of TEST_MATRIX) {
44
- it(label, async () => {
45
- const config = {
46
- category,
47
- framework,
48
- language,
49
- name: 'my-ext',
50
- packageManager: 'npm',
51
- slots,
52
- };
53
- const data = mapProjectConfigToTemplateData(config);
54
- const commands = await planProject(data, PROJECT_DIR);
55
- expect(normalizeCommands(commands)).toMatchSnapshot();
56
- });
57
- }
58
- });
@@ -1,6 +0,0 @@
1
- export { isValidCategory } from './isValidCategory.js';
2
- export { isValidFramework } from './isValidFramework.js';
3
- export { isValidLanguage } from './isValidLanguage.js';
4
- export { validateName } from './validateName.js';
5
- export { validateProjectDir } from './validateProjectDir.js';
6
- export { validateSlots } from './validateSlots.js';