create-template-html-css 2.0.3 → 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.
- package/CHANGELOG.md +305 -0
- package/HTML-VS-REACT.md +289 -0
- package/QUICKSTART-REACT.md +293 -0
- package/REACT-SUPPORT-SUMMARY.md +235 -0
- package/README.md +193 -12
- package/bin/cli.js +98 -759
- package/bin/commands/create.js +272 -0
- package/bin/commands/gallery.js +42 -0
- package/bin/commands/insert.js +123 -0
- package/bin/commands/list.js +73 -0
- package/package.json +10 -3
- package/src/component-choices.js +7 -0
- package/src/components-registry.js +112 -0
- package/src/format-utils.js +49 -0
- package/src/generator.js +83 -594
- package/src/generators/color-schemes.js +78 -0
- package/src/generators/color-utils.js +108 -0
- package/src/generators/component-filters.js +151 -0
- package/src/generators/html-generators.js +180 -0
- package/src/generators/validation.js +43 -0
- package/src/index.js +2 -1
- package/src/inserter.js +55 -233
- package/src/inserters/backup-utils.js +20 -0
- package/src/inserters/component-loader.js +68 -0
- package/src/inserters/html-utils.js +31 -0
- package/src/inserters/indentation-utils.js +90 -0
- package/src/inserters/validation-utils.js +49 -0
- package/src/react-component-choices.js +45 -0
- package/src/react-file-operations.js +172 -0
- package/src/react-generator.js +208 -0
- package/src/react-templates.js +350 -0
- package/src/utils/file-utils.js +97 -0
- package/src/utils/path-utils.js +32 -0
- package/src/utils/string-utils.js +51 -0
- package/src/utils/template-loader.js +91 -0
- package/templates/_shared/PATTERNS.md +246 -0
- package/templates/_shared/README.md +74 -0
- package/templates/_shared/base.css +18 -0
- package/templates/blackjack/index.html +1 -1
- package/templates/blackjack/script.js +9 -9
- package/templates/breakout/index.html +1 -1
- package/templates/breakout/script.js +6 -6
- package/templates/connect-four/index.html +1 -1
- package/templates/connect-four/script.js +5 -5
- package/templates/dice-game/index.html +1 -1
- package/templates/dice-game/script.js +20 -20
- package/templates/flappy-bird/index.html +1 -1
- package/templates/flappy-bird/script.js +10 -10
- package/templates/pong/index.html +1 -1
- package/templates/pong/script.js +8 -8
- package/templates/skeleton/index.html +4 -4
- package/templates/slot-machine/index.html +1 -1
- package/templates/slot-machine/script.js +6 -6
- package/templates/tetris/index.html +1 -1
- package/templates/tetris/script.js +5 -5
- package/templates-react/README.md +126 -0
- package/templates-react/button/Button.css +88 -0
- package/templates-react/button/Button.example.jsx +40 -0
- package/templates-react/button/Button.jsx +29 -0
- package/templates-react/card/Card.css +86 -0
- package/templates-react/card/Card.example.jsx +49 -0
- package/templates-react/card/Card.jsx +35 -0
- package/templates-react/counter/Counter.css +99 -0
- package/templates-react/counter/Counter.example.jsx +45 -0
- package/templates-react/counter/Counter.jsx +70 -0
- package/templates-react/form/Form.css +128 -0
- package/templates-react/form/Form.example.jsx +65 -0
- package/templates-react/form/Form.jsx +125 -0
- package/templates-react/modal/Modal.css +152 -0
- package/templates-react/modal/Modal.example.jsx +90 -0
- package/templates-react/modal/Modal.jsx +46 -0
- package/templates-react/todo-list/TodoList.css +236 -0
- package/templates-react/todo-list/TodoList.example.jsx +15 -0
- 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
|
+
}
|