create-template-html-css 2.0.4 → 2.1.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 (66) hide show
  1. package/CHANGELOG.md +305 -0
  2. package/HTML-VS-REACT.md +289 -0
  3. package/QUICKSTART-REACT.md +293 -0
  4. package/REACT-SUPPORT-SUMMARY.md +235 -0
  5. package/README.md +193 -12
  6. package/bin/cli.js +98 -759
  7. package/bin/commands/create.js +272 -0
  8. package/bin/commands/gallery.js +42 -0
  9. package/bin/commands/insert.js +123 -0
  10. package/bin/commands/list.js +73 -0
  11. package/package.json +10 -3
  12. package/src/component-choices.js +7 -0
  13. package/src/components-registry.js +112 -0
  14. package/src/format-utils.js +49 -0
  15. package/src/generator.js +83 -594
  16. package/src/generators/color-schemes.js +78 -0
  17. package/src/generators/color-utils.js +108 -0
  18. package/src/generators/component-filters.js +151 -0
  19. package/src/generators/html-generators.js +180 -0
  20. package/src/generators/validation.js +43 -0
  21. package/src/index.js +2 -1
  22. package/src/inserter.js +55 -233
  23. package/src/inserters/backup-utils.js +20 -0
  24. package/src/inserters/component-loader.js +68 -0
  25. package/src/inserters/html-utils.js +31 -0
  26. package/src/inserters/indentation-utils.js +90 -0
  27. package/src/inserters/validation-utils.js +49 -0
  28. package/src/react-component-choices.js +45 -0
  29. package/src/react-file-operations.js +172 -0
  30. package/src/react-generator.js +208 -0
  31. package/src/react-templates.js +350 -0
  32. package/src/utils/file-utils.js +97 -0
  33. package/src/utils/path-utils.js +32 -0
  34. package/src/utils/string-utils.js +51 -0
  35. package/src/utils/template-loader.js +91 -0
  36. package/templates/_shared/PATTERNS.md +246 -0
  37. package/templates/_shared/README.md +74 -0
  38. package/templates/_shared/base.css +18 -0
  39. package/templates/blackjack/index.html +1 -1
  40. package/templates/breakout/index.html +1 -1
  41. package/templates/connect-four/index.html +1 -1
  42. package/templates/dice-game/index.html +1 -1
  43. package/templates/flappy-bird/index.html +1 -1
  44. package/templates/pong/index.html +1 -1
  45. package/templates/skeleton/index.html +4 -4
  46. package/templates/slot-machine/index.html +1 -1
  47. package/templates/tetris/index.html +1 -1
  48. package/templates-react/README.md +126 -0
  49. package/templates-react/button/Button.css +88 -0
  50. package/templates-react/button/Button.example.jsx +40 -0
  51. package/templates-react/button/Button.jsx +29 -0
  52. package/templates-react/card/Card.css +86 -0
  53. package/templates-react/card/Card.example.jsx +49 -0
  54. package/templates-react/card/Card.jsx +35 -0
  55. package/templates-react/counter/Counter.css +99 -0
  56. package/templates-react/counter/Counter.example.jsx +45 -0
  57. package/templates-react/counter/Counter.jsx +70 -0
  58. package/templates-react/form/Form.css +128 -0
  59. package/templates-react/form/Form.example.jsx +65 -0
  60. package/templates-react/form/Form.jsx +125 -0
  61. package/templates-react/modal/Modal.css +152 -0
  62. package/templates-react/modal/Modal.example.jsx +90 -0
  63. package/templates-react/modal/Modal.jsx +46 -0
  64. package/templates-react/todo-list/TodoList.css +236 -0
  65. package/templates-react/todo-list/TodoList.example.jsx +15 -0
  66. package/templates-react/todo-list/TodoList.jsx +84 -0
@@ -0,0 +1,350 @@
1
+ // ============================================================================
2
+ // CONSTANTS
3
+ // ============================================================================
4
+
5
+ // Dependencies versions
6
+ const REACT_VERSION = "^18.2.0";
7
+ const VITE_VERSION = "^5.0.0";
8
+ const VITE_REACT_PLUGIN_VERSION = "^4.2.0";
9
+
10
+ // ============================================================================
11
+ // TEMPLATE HELPERS
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Create App.jsx template wrapper
16
+ * @param {string} componentName - Component name (PascalCase)
17
+ * @param {string} additionalImports - Additional imports from React (e.g., "useState, useEffect")
18
+ * @param {string} content - JSX content inside App div
19
+ * @returns {string} Complete App.jsx code
20
+ */
21
+ function createAppTemplate(componentName, additionalImports = '', content) {
22
+ const reactImports = additionalImports
23
+ ? `import React, { ${additionalImports} } from 'react';`
24
+ : `import React from 'react';`;
25
+
26
+ return `${reactImports}
27
+ import ${componentName} from './components/${componentName}/${componentName}';
28
+ import './components/${componentName}/${componentName}.css';
29
+
30
+ function App() {
31
+ ${content}
32
+ }
33
+
34
+ export default App;`;
35
+ }
36
+
37
+ // ============================================================================
38
+ // APP.JSX GENERATOR
39
+ // ============================================================================
40
+
41
+ /**
42
+ * Generate App.jsx content
43
+ */
44
+ function generateAppJsx(componentName, componentKebab) {
45
+ // Component-specific content (inside App function)
46
+ const componentContent = {
47
+ button: ` const handleClick = () => {
48
+ alert('Button clicked!');
49
+ };
50
+
51
+ return (
52
+ <div className="App" style={{
53
+ padding: '40px',
54
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
55
+ backgroundColor: '#f5f5f5',
56
+ minHeight: '100vh'
57
+ }}>
58
+ <h1 style={{ marginBottom: '30px', color: '#333' }}>${componentName} Component Examples</h1>
59
+
60
+ <div style={{ marginBottom: '30px' }}>
61
+ <h3 style={{ marginBottom: '15px', color: '#666' }}>Variants:</h3>
62
+ <div style={{ display: 'flex', gap: '15px', flexWrap: 'wrap' }}>
63
+ <${componentName} variant="primary" onClick={handleClick}>Primary</${componentName}>
64
+ <${componentName} variant="secondary" onClick={handleClick}>Secondary</${componentName}>
65
+ <${componentName} variant="success" onClick={handleClick}>Success</${componentName}>
66
+ <${componentName} variant="danger" onClick={handleClick}>Danger</${componentName}>
67
+ <${componentName} variant="outline" onClick={handleClick}>Outline</${componentName}>
68
+ </div>
69
+ </div>
70
+
71
+ <div style={{ marginBottom: '30px' }}>
72
+ <h3 style={{ marginBottom: '15px', color: '#666' }}>Sizes:</h3>
73
+ <div style={{ display: 'flex', gap: '15px', alignItems: 'center', flexWrap: 'wrap' }}>
74
+ <${componentName} size="small" onClick={handleClick}>Small</${componentName}>
75
+ <${componentName} size="medium" onClick={handleClick}>Medium</${componentName}>
76
+ <${componentName} size="large" onClick={handleClick}>Large</${componentName}>
77
+ </div>
78
+ </div>
79
+
80
+ <div style={{ marginBottom: '30px' }}>
81
+ <h3 style={{ marginBottom: '15px', color: '#666' }}>States:</h3>
82
+ <div style={{ display: 'flex', gap: '15px', flexWrap: 'wrap' }}>
83
+ <${componentName} onClick={handleClick}>Normal</${componentName}>
84
+ <${componentName} disabled>Disabled</${componentName}>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ );`,
89
+
90
+ card: ` return (
91
+ <div className="App" style={{ padding: '40px', backgroundColor: '#f5f5f5', minHeight: '100vh' }}>
92
+ <h1 style={{ marginBottom: '30px' }}>${componentName} Component</h1>
93
+ <${componentName}
94
+ title="Example Card"
95
+ description="This is an example card component with a beautiful design"
96
+ image="https://via.placeholder.com/400x200"
97
+ />
98
+ </div>
99
+ );`,
100
+
101
+ counter: ` return (
102
+ <div className="App" style={{ padding: '40px' }}>
103
+ <h1>Counter Component</h1>
104
+ <${componentName}
105
+ initialValue={0}
106
+ min={0}
107
+ max={100}
108
+ onChange={(value) => console.log('Count:', value)}
109
+ />
110
+ </div>
111
+ );`,
112
+
113
+ form: ` return (
114
+ <div className="App" style={{ padding: '40px' }}>
115
+ <${componentName}
116
+ title="Contact Form"
117
+ fields={[
118
+ { name: 'name', label: 'Name', type: 'text', required: true },
119
+ { name: 'email', label: 'Email', type: 'email', required: true },
120
+ ]}
121
+ onSubmit={(data) => console.log('Form data:', data)}
122
+ />
123
+ </div>
124
+ );`,
125
+
126
+ "todo-list": ` return (
127
+ <div className="App" style={{ padding: '40px' }}>
128
+ <h1>Todo List</h1>
129
+ <${componentName} />
130
+ </div>
131
+ );`,
132
+ };
133
+
134
+ // Modal needs useState import
135
+ if (componentKebab === 'modal') {
136
+ const content = ` const [isOpen, setIsOpen] = useState(false);
137
+
138
+ return (
139
+ <div className="App" style={{ padding: '40px' }}>
140
+ <button onClick={() => setIsOpen(true)}>Open Modal</button>
141
+ <${componentName}
142
+ isOpen={isOpen}
143
+ onClose={() => setIsOpen(false)}
144
+ title="Example Modal"
145
+ >
146
+ <p>This is the modal content</p>
147
+ </${componentName}>
148
+ </div>
149
+ );`;
150
+ return createAppTemplate(componentName, 'useState', content);
151
+ }
152
+
153
+ // Use component-specific content if available
154
+ const content = componentContent[componentKebab] || ` return (
155
+ <div className="App">
156
+ <${componentName} />
157
+ </div>
158
+ );`;
159
+
160
+ return createAppTemplate(componentName, '', content);
161
+ }
162
+
163
+ // ============================================================================
164
+ // ENTRY POINT GENERATORS
165
+ // ============================================================================
166
+
167
+ /**
168
+ * Generate index.jsx content
169
+ */
170
+ function generateIndexJs() {
171
+ return `import React from 'react';
172
+ import ReactDOM from 'react-dom/client';
173
+ import App from './App';
174
+
175
+ const root = ReactDOM.createRoot(document.getElementById('root'));
176
+ root.render(
177
+ <React.StrictMode>
178
+ <App />
179
+ </React.StrictMode>
180
+ );
181
+ `;
182
+ }
183
+
184
+ /**
185
+ * Generate index.html content
186
+ */
187
+ function generateIndexHtml(title) {
188
+ return `<!DOCTYPE html>
189
+ <html lang="en">
190
+ <head>
191
+ <meta charset="UTF-8">
192
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
193
+ <title>${title}</title>
194
+ </head>
195
+ <body>
196
+ <div id="root"></div>
197
+ <script type="module" src="/src/index.jsx"></script>
198
+ </body>
199
+ </html>
200
+ `;
201
+ }
202
+
203
+ // ============================================================================
204
+ // CONFIG FILE GENERATORS
205
+ // ============================================================================
206
+
207
+ /**
208
+ * Generate package.json content
209
+ */
210
+ function generatePackageJson(name) {
211
+ return {
212
+ name: name,
213
+ version: "0.1.0",
214
+ type: "module",
215
+ private: true,
216
+ dependencies: {
217
+ react: REACT_VERSION,
218
+ "react-dom": REACT_VERSION,
219
+ },
220
+ devDependencies: {
221
+ "@vitejs/plugin-react": VITE_REACT_PLUGIN_VERSION,
222
+ vite: VITE_VERSION,
223
+ },
224
+ scripts: {
225
+ dev: "vite",
226
+ build: "vite build",
227
+ preview: "vite preview",
228
+ },
229
+ };
230
+ }
231
+
232
+ /**
233
+ * Generate .gitignore content
234
+ */
235
+ function generateGitignore() {
236
+ return `# Dependencies
237
+ node_modules/
238
+
239
+ # Production
240
+ dist/
241
+ build/
242
+
243
+ # Development
244
+ .DS_Store
245
+ *.log
246
+ .env
247
+ .env.local
248
+ .env.development.local
249
+ .env.test.local
250
+ .env.production.local
251
+
252
+ # Editor
253
+ .vscode/
254
+ .idea/
255
+ *.swp
256
+ *.swo
257
+ `;
258
+ }
259
+
260
+ /**
261
+ * Generate vite.config.js content
262
+ */
263
+ function generateViteConfig() {
264
+ return `import { defineConfig } from 'vite';
265
+ import react from '@vitejs/plugin-react';
266
+
267
+ // https://vitejs.dev/config/
268
+ export default defineConfig({
269
+ plugins: [react()],
270
+ });
271
+ `;
272
+ }
273
+
274
+ /**
275
+ * Generate README.md content
276
+ */
277
+ function generateReadme(name, componentName) {
278
+ return `# ${name}
279
+
280
+ React component project generated with create-template-html-css.
281
+
282
+ ## Component: ${componentName}
283
+
284
+ ### Installation
285
+
286
+ \`\`\`bash
287
+ npm install
288
+ \`\`\`
289
+
290
+ ### Development
291
+
292
+ \`\`\`bash
293
+ npm run dev
294
+ \`\`\`
295
+
296
+ Open [http://localhost:5173](http://localhost:5173) to view it in your browser.
297
+
298
+ ### Build
299
+
300
+ \`\`\`bash
301
+ npm run build
302
+ \`\`\`
303
+
304
+ Builds the app for production to the \`dist\` folder.
305
+
306
+ ### Preview Production Build
307
+
308
+ \`\`\`bash
309
+ npm run preview
310
+ \`\`\`
311
+
312
+ ## Project Structure
313
+
314
+ \`\`\`
315
+ ${name}/
316
+ ├── src/
317
+ │ ├── components/
318
+ │ │ └── ${componentName}/
319
+ │ │ ├── ${componentName}.jsx
320
+ │ │ └── ${componentName}.css
321
+ │ ├── App.jsx
322
+ │ └── index.jsx
323
+ ├── index.html
324
+ ├── package.json
325
+ └── README.md
326
+ \`\`\`
327
+
328
+ ## Customization
329
+
330
+ Feel free to modify the component files in \`src/components/${componentName}/\` to suit your needs.
331
+
332
+ ## License
333
+
334
+ MIT
335
+ `;
336
+ }
337
+
338
+ // ============================================================================
339
+ // EXPORTS
340
+ // ============================================================================
341
+
342
+ export {
343
+ generateAppJsx,
344
+ generateIndexJs,
345
+ generateIndexHtml,
346
+ generatePackageJson,
347
+ generateGitignore,
348
+ generateViteConfig,
349
+ generateReadme,
350
+ };
@@ -0,0 +1,97 @@
1
+ /**
2
+ * File system utilities
3
+ * Handles common file and directory operations with formatting
4
+ */
5
+
6
+ import fs from "fs/promises";
7
+ import path from "path";
8
+ import { formatHtml, formatCss, formatJs } from "../format-utils.js";
9
+
10
+ /**
11
+ * Creates a directory with all parent directories
12
+ * @param {string} dirPath - Directory path to create
13
+ * @returns {Promise<void>}
14
+ */
15
+ export async function ensureDir(dirPath) {
16
+ await fs.mkdir(dirPath, { recursive: true });
17
+ }
18
+
19
+ /**
20
+ * Creates multiple directories
21
+ * @param {string[]} dirPaths - Array of directory paths to create
22
+ * @returns {Promise<void>}
23
+ */
24
+ export async function ensureDirs(...dirPaths) {
25
+ await Promise.all(dirPaths.map((dir) => ensureDir(dir)));
26
+ }
27
+
28
+ /**
29
+ * Writes formatted HTML content to a file
30
+ * @param {string} filePath - Path to the HTML file
31
+ * @param {string} content - HTML content to write
32
+ * @returns {Promise<void>}
33
+ */
34
+ export async function writeHtmlFile(filePath, content) {
35
+ const formatted = await formatHtml(content);
36
+ await fs.writeFile(filePath, formatted);
37
+ }
38
+
39
+ /**
40
+ * Writes formatted CSS content to a file
41
+ * @param {string} filePath - Path to the CSS file
42
+ * @param {string} content - CSS content to write
43
+ * @returns {Promise<void>}
44
+ */
45
+ export async function writeCssFile(filePath, content) {
46
+ const formatted = await formatCss(content);
47
+ await fs.writeFile(filePath, formatted);
48
+ }
49
+
50
+ /**
51
+ * Writes formatted JavaScript content to a file
52
+ * @param {string} filePath - Path to the JS file
53
+ * @param {string} content - JavaScript content to write
54
+ * @returns {Promise<void>}
55
+ */
56
+ export async function writeJsFile(filePath, content) {
57
+ const formatted = await formatJs(content);
58
+ await fs.writeFile(filePath, formatted);
59
+ }
60
+
61
+ /**
62
+ * Creates directory structure for component files
63
+ * @param {string} basePath - Base output directory path
64
+ * @returns {Promise<Object>} Object with outputDir, cssDir, jsDir paths
65
+ */
66
+ export async function createComponentDirs(basePath) {
67
+ const outputDir = basePath;
68
+ const cssDir = path.join(basePath, "css");
69
+ const jsDir = path.join(basePath, "js");
70
+
71
+ await ensureDirs(outputDir, cssDir, jsDir);
72
+
73
+ return { outputDir, cssDir, jsDir };
74
+ }
75
+
76
+ /**
77
+ * Writes component files (HTML, CSS, JS) to their respective directories
78
+ * @param {Object} options - File writing options
79
+ * @param {string} options.outputDir - Output directory path
80
+ * @param {string} options.cssDir - CSS directory path
81
+ * @param {string} options.jsDir - JS directory path
82
+ * @param {string} options.htmlContent - HTML content
83
+ * @param {string} options.cssContent - CSS content
84
+ * @param {string} [options.jsContent] - Optional JS content
85
+ * @returns {Promise<void>}
86
+ */
87
+ export async function writeComponentFiles(options) {
88
+ const { outputDir, cssDir, jsDir, htmlContent, cssContent, jsContent } =
89
+ options;
90
+
91
+ await writeHtmlFile(path.join(outputDir, "index.html"), htmlContent);
92
+ await writeCssFile(path.join(cssDir, "style.css"), cssContent);
93
+
94
+ if (jsContent) {
95
+ await writeJsFile(path.join(jsDir, "script.js"), jsContent);
96
+ }
97
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Path utilities for ES Modules
3
+ * Provides __dirname functionality in ES Modules environment
4
+ */
5
+
6
+ import { fileURLToPath } from "url";
7
+ import { dirname } from "path";
8
+
9
+ /**
10
+ * Gets the directory name of a module (equivalent to __dirname in CommonJS)
11
+ * @param {string} importMetaUrl - import.meta.url from the calling module
12
+ * @returns {string} Directory path of the module
13
+ * @example
14
+ * import { getDirname } from './utils/path-utils.js';
15
+ * const __dirname = getDirname(import.meta.url);
16
+ */
17
+ export function getDirname(importMetaUrl) {
18
+ const __filename = fileURLToPath(importMetaUrl);
19
+ return dirname(__filename);
20
+ }
21
+
22
+ /**
23
+ * Gets the file path of a module (equivalent to __filename in CommonJS)
24
+ * @param {string} importMetaUrl - import.meta.url from the calling module
25
+ * @returns {string} File path of the module
26
+ * @example
27
+ * import { getFilename } from './utils/path-utils.js';
28
+ * const __filename = getFilename(import.meta.url);
29
+ */
30
+ export function getFilename(importMetaUrl) {
31
+ return fileURLToPath(importMetaUrl);
32
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * String manipulation utilities
3
+ * Helper functions for common string operations
4
+ */
5
+
6
+ /**
7
+ * Convert a string to a valid HTML/CSS ID
8
+ * Converts to lowercase and replaces spaces with hyphens
9
+ * @param {string} text - Text to convert
10
+ * @returns {string} Valid ID string (e.g., "My Item" -> "my-item")
11
+ */
12
+ export function textToId(text) {
13
+ return text.toLowerCase().replace(/\s+/g, "-");
14
+ }
15
+
16
+ /**
17
+ * Sanitize a string for use as a filename
18
+ * Removes invalid characters and trims whitespace
19
+ * @param {string} filename - Filename to sanitize
20
+ * @returns {string} Sanitized filename
21
+ */
22
+ export function sanitizeForFilename(filename) {
23
+ return filename.replace(/[<>:"|?*]/g, "").trim();
24
+ }
25
+
26
+ /**
27
+ * Parse comma-separated list into array
28
+ * Trims whitespace and filters empty items
29
+ * @param {string} input - Comma-separated string
30
+ * @returns {string[]} Array of trimmed non-empty strings
31
+ */
32
+ export function parseCommaSeparated(input) {
33
+ return input
34
+ .split(",")
35
+ .map((item) => item.trim())
36
+ .filter((item) => item.length > 0);
37
+ }
38
+
39
+ /**
40
+ * Parse key:value pairs from comma-separated string
41
+ * @param {string} input - Comma-separated "key:value" pairs
42
+ * @returns {Array<{key: string, value: string}>} Array of parsed pairs
43
+ */
44
+ export function parseKeyValuePairs(input) {
45
+ return parseCommaSeparated(input)
46
+ .map((pair) => {
47
+ const [key, value] = pair.split(":").map((s) => s.trim());
48
+ return key && value ? { key, value } : null;
49
+ })
50
+ .filter(Boolean);
51
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Template loader utilities
3
+ * Centralized functions for loading template files from the templates directory
4
+ */
5
+
6
+ import fs from "fs/promises";
7
+ import path from "path";
8
+ import { getDirname } from "./path-utils.js";
9
+
10
+ const __dirname = getDirname(import.meta.url);
11
+
12
+ /**
13
+ * Get the path to a template directory
14
+ * @param {string} component - Component name
15
+ * @returns {string} Absolute path to template directory
16
+ */
17
+ export function getTemplatePath(component) {
18
+ return path.join(__dirname, "..", "..", "templates", component);
19
+ }
20
+
21
+ /**
22
+ * Read a template file from the templates directory
23
+ * @param {string} component - Component name
24
+ * @param {string} filename - File to read (e.g., "index.html", "style.css", "script.js")
25
+ * @returns {Promise<string>} File content
26
+ */
27
+ export async function readTemplateFile(component, filename) {
28
+ const templatePath = getTemplatePath(component);
29
+ return await fs.readFile(path.join(templatePath, filename), "utf-8");
30
+ }
31
+
32
+ /**
33
+ * Read template HTML file
34
+ * @param {string} component - Component name
35
+ * @returns {Promise<string>} HTML content
36
+ */
37
+ export async function readTemplateHtml(component) {
38
+ return await readTemplateFile(component, "index.html");
39
+ }
40
+
41
+ /**
42
+ * Read template CSS file
43
+ * Tries css/style.css first, falls back to style.css
44
+ * @param {string} component - Component name
45
+ * @returns {Promise<string>} CSS content
46
+ */
47
+ export async function readTemplateCss(component) {
48
+ const templatePath = getTemplatePath(component);
49
+
50
+ try {
51
+ return await fs.readFile(
52
+ path.join(templatePath, "css", "style.css"),
53
+ "utf-8"
54
+ );
55
+ } catch {
56
+ return await fs.readFile(path.join(templatePath, "style.css"), "utf-8");
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Read template JavaScript file
62
+ * Tries js/script.js first, falls back to script.js, returns null if not found
63
+ * @param {string} component - Component name
64
+ * @returns {Promise<string|null>} JavaScript content or null if not found
65
+ */
66
+ export async function readTemplateJs(component) {
67
+ const templatePath = getTemplatePath(component);
68
+
69
+ try {
70
+ return await fs.readFile(
71
+ path.join(templatePath, "js", "script.js"),
72
+ "utf-8"
73
+ );
74
+ } catch {
75
+ try {
76
+ return await fs.readFile(path.join(templatePath, "script.js"), "utf-8");
77
+ } catch {
78
+ return null; // No JavaScript file for this component
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Check if a template JavaScript file exists
85
+ * @param {string} component - Component name
86
+ * @returns {Promise<boolean>} True if JS file exists
87
+ */
88
+ export async function hasTemplateJs(component) {
89
+ const js = await readTemplateJs(component);
90
+ return js !== null;
91
+ }