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
package/src/generator.js CHANGED
@@ -1,555 +1,48 @@
1
- const fs = require("fs").promises;
2
- const path = require("path");
1
+ import { promises as fs } from "fs";
2
+ import path from "path";
3
+ import { getDirname } from "./utils/path-utils.js";
4
+ import {
5
+ createComponentDirs,
6
+ writeHtmlFile,
7
+ writeCssFile,
8
+ writeJsFile,
9
+ } from "./utils/file-utils.js";
10
+ import {
11
+ getTemplatePath,
12
+ readTemplateHtml,
13
+ readTemplateCss,
14
+ readTemplateJs,
15
+ } from "./utils/template-loader.js";
16
+ import { COLOR_SCHEMES, getColorScheme } from "./generators/color-schemes.js";
17
+ import {
18
+ applyCustomColors,
19
+ addDarkModeStyles,
20
+ } from "./generators/color-utils.js";
21
+ import {
22
+ filterButtonVariations,
23
+ filterCardVariations,
24
+ filterSpinnerVariations,
25
+ } from "./generators/component-filters.js";
26
+ import {
27
+ generateNavigationItems,
28
+ generateFormFields,
29
+ } from "./generators/html-generators.js";
30
+ import { VALID_COMPONENTS, sanitizeFilename } from "./generators/validation.js";
31
+
32
+ const __dirname = getDirname(import.meta.url);
3
33
 
4
34
  /**
5
- * Formats HTML content with prettier (optional - falls back to original if not available)
35
+ * Generate a template component with all its files
36
+ * @param {Object} options - Generation options
37
+ * @returns {Promise<string>} Path to generated directory
6
38
  */
7
- async function formatHtml(htmlContent) {
8
- try {
9
- const prettier = require("prettier");
10
- return await prettier.format(htmlContent, { parser: "html" });
11
- } catch (error) {
12
- // Prettier not installed or error formatting - return original content
13
- return htmlContent;
14
- }
15
- }
16
-
17
- /**
18
- * Formats CSS content with prettier (optional - falls back to original if not available)
19
- */
20
- async function formatCss(cssContent) {
21
- try {
22
- const prettier = require("prettier");
23
- return await prettier.format(cssContent, { parser: "css" });
24
- } catch (error) {
25
- // Prettier not installed or error formatting - return original content
26
- return cssContent;
27
- }
28
- }
29
-
30
- /**
31
- * Formats JavaScript content with prettier (optional - falls back to original if not available)
32
- */
33
- async function formatJs(jsContent) {
34
- try {
35
- const prettier = require("prettier");
36
- return await prettier.format(jsContent, { parser: "babel" });
37
- } catch (error) {
38
- // Prettier not installed or error formatting - return original content
39
- return jsContent;
40
- }
41
- }
42
-
43
- /**
44
- * Color scheme presets
45
- */
46
- const COLOR_SCHEMES = {
47
- vibrant: {
48
- name: "Vibrant",
49
- primary: "#FF6B6B",
50
- secondary: "#4ECDC4",
51
- description: "Bold reds and teals - energetic and modern"
52
- },
53
- pastel: {
54
- name: "Pastel",
55
- primary: "#FFB3BA",
56
- secondary: "#FFDFBA",
57
- description: "Soft pinks and peaches - calm and friendly"
58
- },
59
- ocean: {
60
- name: "Ocean",
61
- primary: "#0066CC",
62
- secondary: "#00CCFF",
63
- description: "Deep blues and cyans - professional and cool"
64
- },
65
- sunset: {
66
- name: "Sunset",
67
- primary: "#FF6B35",
68
- secondary: "#FFA500",
69
- description: "Warm oranges and reds - energetic glow"
70
- },
71
- forest: {
72
- name: "Forest",
73
- primary: "#2D6A4F",
74
- secondary: "#40916C",
75
- description: "Green tones - natural and organic"
76
- },
77
- purple: {
78
- name: "Purple",
79
- primary: "#7209B7",
80
- secondary: "#B5179E",
81
- description: "Deep purples - elegant and premium"
82
- },
83
- minimal: {
84
- name: "Minimal",
85
- primary: "#1A1A1A",
86
- secondary: "#666666",
87
- description: "Grays and blacks - clean and simple"
88
- },
89
- coral: {
90
- name: "Coral",
91
- primary: "#FF6B9D",
92
- secondary: "#FFA07A",
93
- description: "Coral pinks and salmon - warm and playful"
94
- },
95
- teal: {
96
- name: "Teal",
97
- primary: "#008B8B",
98
- secondary: "#20B2AA",
99
- description: "Teal hues - balanced and professional"
100
- },
101
- neon: {
102
- name: "Neon",
103
- primary: "#FF006E",
104
- secondary: "#00D9FF",
105
- description: "Bright neon colors - bold and futuristic"
106
- }
107
- };
108
-
109
- /**
110
- * Get color scheme by preset name
111
- */
112
- function getColorScheme(schemeName) {
113
- if (COLOR_SCHEMES[schemeName]) {
114
- return COLOR_SCHEMES[schemeName];
115
- }
116
- return null;
117
- }
118
-
119
- /**
120
- * Apply custom colors to CSS content using CSS variables
121
- */
122
- function applyCustomColors(cssContent, primaryColor, secondaryColor) {
123
- if (!primaryColor && !secondaryColor) {
124
- return cssContent;
125
- }
126
-
127
- // Create CSS variable overrides
128
- let colorVariables = ":root {\n";
129
-
130
- if (primaryColor) {
131
- colorVariables += ` --primary-color: ${primaryColor};\n`;
132
- // Also add as root variable that can override gradients
133
- colorVariables += ` --primary-rgb: ${hexToRgb(primaryColor)};\n`;
134
- }
135
-
136
- if (secondaryColor) {
137
- colorVariables += ` --secondary-color: ${secondaryColor};\n`;
138
- colorVariables += ` --secondary-rgb: ${hexToRgb(secondaryColor)};\n`;
139
- }
140
-
141
- colorVariables += "}\n\n";
142
-
143
- // If CSS doesn't have :root, add it
144
- if (!cssContent.includes(":root")) {
145
- return colorVariables + cssContent;
146
- }
147
-
148
- // Replace existing :root with extended one
149
- return cssContent.replace(/:root\s*{/, colorVariables.trim() + "\n\n:root {");
150
- }
151
-
152
- /**
153
- * Convert hex color to RGB values
154
- */
155
- function hexToRgb(hex) {
156
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
157
- if (result) {
158
- const r = parseInt(result[1], 16);
159
- const g = parseInt(result[2], 16);
160
- const b = parseInt(result[3], 16);
161
- return `${r}, ${g}, ${b}`;
162
- }
163
- return "102, 126, 234"; // fallback to default primary
164
- }
165
-
166
- /**
167
- * Add dark mode styles to CSS
168
- */
169
- function addDarkModeStyles(cssContent) {
170
- if (cssContent.includes("prefers-color-scheme")) {
171
- return cssContent; // Already has dark mode
172
- }
173
-
174
- // Create dark mode media query
175
- const darkModeStyles = `
176
- /* Dark Mode Support */
177
- @media (prefers-color-scheme: dark) {
178
- :root {
179
- --bg-primary: #1a1a1a;
180
- --bg-secondary: #2d2d2d;
181
- --text-primary: #ffffff;
182
- --text-secondary: #b0b0b0;
183
- --border-color: #404040;
184
- }
185
-
186
- body {
187
- background-color: var(--bg-primary, #1a1a1a);
188
- color: var(--text-primary, #ffffff);
189
- }
190
-
191
- .container,
192
- .card,
193
- .modal-content,
194
- .form-container {
195
- background-color: var(--bg-secondary, #2d2d2d);
196
- color: var(--text-primary, #ffffff);
197
- border-color: var(--border-color, #404040);
198
- }
199
-
200
- input,
201
- textarea,
202
- select {
203
- background-color: var(--bg-primary, #1a1a1a);
204
- color: var(--text-primary, #ffffff);
205
- border-color: var(--border-color, #404040);
206
- }
207
-
208
- input::placeholder {
209
- color: var(--text-secondary, #b0b0b0);
210
- }
211
- }`;
212
-
213
- return cssContent + darkModeStyles;
214
- }
215
-
216
- // Security: Validate component name against whitelist
217
- const VALID_COMPONENTS = [
218
- "button",
219
- "card",
220
- "form",
221
- "navigation",
222
- "modal",
223
- "footer",
224
- "hero",
225
- "slider",
226
- "table",
227
- "spinner",
228
- "animated-card",
229
- "typing-effect",
230
- "fade-gallery",
231
- "grid-layout",
232
- "masonry-grid",
233
- "dashboard-grid",
234
- "flex-layout",
235
- "flex-cards",
236
- "flex-dashboard",
237
- "todo-list",
238
- "counter",
239
- "accordion",
240
- "tabs",
241
- "login",
242
- "register",
243
- "skeleton",
244
- "tic-tac-toe",
245
- "memory-game",
246
- "snake-game",
247
- "guess-number",
248
- "game-2048",
249
- "whack-a-mole",
250
- "simon-says",
251
- "rock-paper-scissors",
252
- "breakout",
253
- "tetris",
254
- "flappy-bird",
255
- "connect-four",
256
- "blackjack",
257
- "slot-machine",
258
- "dice-game",
259
- "pong",
260
- ];
261
-
262
- // Security: Sanitize filename to prevent path traversal
263
- function sanitizeFilename(filename) {
264
- // Remove any path separators and parent directory references
265
- const sanitized = filename.replace(/[\/\\]/g, "").replace(/\.\.+/g, ".");
266
-
267
- // Additional validation: ensure name contains at least one alphanumeric character
268
- if (!sanitized || !/[a-zA-Z0-9]/.test(sanitized)) {
269
- return null;
270
- }
271
-
272
- // Remove any remaining dangerous characters
273
- return sanitized.replace(/[<>:"|?*]/g, "").trim();
274
- }
275
-
276
- /**
277
- * Filter button variations based on user selection
278
- */
279
- function filterButtonVariations(htmlContent, buttonVariations, selectedButtons) {
280
- if (buttonVariations === "all" || !selectedButtons || selectedButtons.length === 0) {
281
- return htmlContent; // Return all buttons
282
- }
283
-
284
- // Map selection to button classes
285
- const buttonMap = {
286
- primary: "btn-primary",
287
- secondary: "btn-secondary",
288
- success: "btn-success",
289
- danger: "btn-danger",
290
- outlined: "btn-outlined",
291
- disabled: "disabled"
292
- };
293
-
294
- let filteredHtml = htmlContent;
295
-
296
- // Remove buttons that weren't selected
297
- for (const [key, className] of Object.entries(buttonMap)) {
298
- if (!selectedButtons.includes(key)) {
299
- // Remove button and its comment
300
- const patterns = [
301
- new RegExp(`\\s*<!-- ${key.charAt(0).toUpperCase() + key.slice(1)} Button -->\\s*\\n\\s*<button[^>]*${className}[^>]*>.*?<\\/button>\\s*\\n`, 'gis'),
302
- new RegExp(`\\s*<button[^>]*${className}[^>]*>.*?<\\/button>\\s*\\n`, 'gis')
303
- ];
304
-
305
- patterns.forEach(pattern => {
306
- filteredHtml = filteredHtml.replace(pattern, '\n ');
307
- });
308
- }
309
- }
310
-
311
- return filteredHtml;
312
- }
313
-
314
- /**
315
- * Filter card variations based on user selection
316
- */
317
- function filterCardVariations(htmlContent, cardVariations, selectedCards) {
318
- if (cardVariations === "all" || !selectedCards || selectedCards.length === 0) {
319
- return htmlContent; // Return all cards
320
- }
321
-
322
- const cardComments = {
323
- modern: "Card 1 - Basic",
324
- premium: "Card 2 - With Price",
325
- blog: "Card 3 - With Tags",
326
- minimal: "Card 4 - Minimal",
327
- user: "Card 5 - With Avatar",
328
- interactive: "Card 6 - Interactive"
329
- };
330
-
331
- let filteredHtml = htmlContent;
332
-
333
- // Remove cards that weren't selected
334
- for (const [key, comment] of Object.entries(cardComments)) {
335
- if (!selectedCards.includes(key)) {
336
- // Remove entire card div including comment
337
- const pattern = new RegExp(`\\s*<!-- ${comment.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')} -->\\s*\\n\\s*<div class="card[^>]*>.*?<\\/div>\\s*\\n\\s*<\\/div>\\s*\\n`, 'gis');
338
- filteredHtml = filteredHtml.replace(pattern, '\n ');
339
- }
340
- }
341
-
342
- return filteredHtml;
343
- }
344
-
345
- /**
346
- * Filter spinner variations based on user selection
347
- */
348
- function filterSpinnerVariations(htmlContent, spinnerVariations, selectedSpinners) {
349
- if (spinnerVariations === "all" || !selectedSpinners || selectedSpinners.length === 0) {
350
- return htmlContent; // Return all spinners
351
- }
352
-
353
- const spinnerTypes = {
354
- circle: "Circle Spinner",
355
- dots: "Bouncing Dots",
356
- pulse: "Pulse Loader",
357
- bars: "Bar Loader",
358
- gradient: "Gradient Ring"
359
- };
360
-
361
- let filteredHtml = htmlContent;
362
-
363
- // Remove spinners that weren't selected
364
- for (const [key, title] of Object.entries(spinnerTypes)) {
365
- if (!selectedSpinners.includes(key)) {
366
- // Remove entire spinner-group div
367
- const pattern = new RegExp(`\\s*<!-- ${title} Spinner -->\\s*\\n\\s*<div class="spinner-group">.*?<\\/div>\\s*\\n\\s*<\\/div>\\s*\\n`, 'gis');
368
- filteredHtml = filteredHtml.replace(pattern, '\n ');
369
- }
370
- }
371
-
372
- return filteredHtml;
373
- }
374
-
375
- /**
376
- * Generate custom navigation items based on user input
377
- */
378
- function generateNavigationItems(htmlContent, navItems) {
379
- // Parse the comma-separated navigation items
380
- const items = navItems.split(',').map(item => item.trim()).filter(item => item.length > 0);
381
-
382
- // Generate navigation HTML
383
- let navHtml = '';
384
- items.forEach((item, index) => {
385
- const itemId = item.toLowerCase().replace(/\s+/g, '-');
386
- const activeClass = index === 0 ? ' active' : '';
387
- navHtml += ` <li class="nav-item">
388
- <a href="#${itemId}" class="nav-link${activeClass}">${item}</a>
389
- </li>\n`;
390
- });
391
-
392
- // Replace the navigation items in the HTML
393
- const navMenuRegex = /<ul class="nav-menu"[^>]*>[\s\S]*?<\/ul>/;
394
- const replacement = `<ul class="nav-menu" id="navMenu">
395
- ${navHtml} </ul>`;
396
-
397
- htmlContent = htmlContent.replace(navMenuRegex, replacement);
398
-
399
- // Generate sections for each navigation item
400
- let sectionsHtml = '';
401
- items.forEach(item => {
402
- const itemId = item.toLowerCase().replace(/\s+/g, '-');
403
- sectionsHtml += ` <section id="${itemId}" class="section">
404
- <h1>${item}</h1>
405
- <p>Content for ${item} section</p>
406
- </section>
407
-
408
- `;
409
- });
410
-
411
- // Replace the sections in the HTML
412
- const mainContentRegex = /<main class="main-content">[\s\S]*?<\/main>/;
413
- const sectionsReplacement = `<main class="main-content">
414
- ${sectionsHtml} </main>`;
415
-
416
- htmlContent = htmlContent.replace(mainContentRegex, sectionsReplacement);
417
-
418
- return htmlContent;
419
- }
420
-
421
- /**
422
- * Generate custom form fields based on user selection
423
- */
424
- function generateFormFields(htmlContent, formFields, customFormFields) {
425
- let fieldsHtml = '';
426
-
427
- // Add standard fields
428
- formFields.forEach(field => {
429
- switch(field) {
430
- case 'name':
431
- fieldsHtml += ` <div class="form-group">
432
- <label for="name">Full Name</label>
433
- <input type="text" id="name" name="name" required>
434
- </div>
435
-
436
- `;
437
- break;
438
- case 'email':
439
- fieldsHtml += ` <div class="form-group">
440
- <label for="email">Email</label>
441
- <input type="email" id="email" name="email" required>
442
- </div>
443
-
444
- `;
445
- break;
446
- case 'phone':
447
- fieldsHtml += ` <div class="form-group">
448
- <label for="phone">Phone</label>
449
- <input type="tel" id="phone" name="phone">
450
- </div>
451
-
452
- `;
453
- break;
454
- case 'subject':
455
- fieldsHtml += ` <div class="form-group">
456
- <label for="subject">Subject</label>
457
- <select id="subject" name="subject" required>
458
- <option value="">Select a subject</option>
459
- <option value="support">Technical Support</option>
460
- <option value="sales">Sales</option>
461
- <option value="general">General</option>
462
- </select>
463
- </div>
464
-
465
- `;
466
- break;
467
- case 'message':
468
- fieldsHtml += ` <div class="form-group">
469
- <label for="message">Message</label>
470
- <textarea id="message" name="message" rows="5" required></textarea>
471
- </div>
472
-
473
- `;
474
- break;
475
- case 'terms':
476
- fieldsHtml += ` <div class="form-group checkbox-group">
477
- <input type="checkbox" id="terms" name="terms" required>
478
- <label for="terms">I agree to the terms of service</label>
479
- </div>
480
-
481
- `;
482
- break;
483
- }
484
- });
485
-
486
- // Add custom fields if provided
487
- if (customFormFields && customFormFields.trim().length > 0) {
488
- const customFields = customFormFields.split(',').map(f => f.trim()).filter(f => f.length > 0);
489
-
490
- customFields.forEach(field => {
491
- const [type, label] = field.split(':').map(s => s.trim());
492
- if (type && label) {
493
- const fieldId = label.toLowerCase().replace(/\s+/g, '-');
494
-
495
- if (type === 'textarea') {
496
- fieldsHtml += ` <div class="form-group">
497
- <label for="${fieldId}">${label}</label>
498
- <textarea id="${fieldId}" name="${fieldId}" rows="5"></textarea>
499
- </div>
500
-
501
- `;
502
- } else if (type === 'checkbox') {
503
- fieldsHtml += ` <div class="form-group checkbox-group">
504
- <input type="checkbox" id="${fieldId}" name="${fieldId}">
505
- <label for="${fieldId}">${label}</label>
506
- </div>
507
-
508
- `;
509
- } else if (type === 'select') {
510
- fieldsHtml += ` <div class="form-group">
511
- <label for="${fieldId}">${label}</label>
512
- <select id="${fieldId}" name="${fieldId}">
513
- <option value="">Select an option</option>
514
- <option value="option1">Option 1</option>
515
- <option value="option2">Option 2</option>
516
- </select>
517
- </div>
518
-
519
- `;
520
- } else {
521
- // Default to input with specified type
522
- fieldsHtml += ` <div class="form-group">
523
- <label for="${fieldId}">${label}</label>
524
- <input type="${type}" id="${fieldId}" name="${fieldId}">
525
- </div>
526
-
527
- `;
528
- }
529
- }
530
- });
531
- }
532
-
533
- // Replace the form fields in the HTML
534
- const formRegex = /<form class="form"[^>]*>[\s\S]*?<button type="submit"/;
535
- const replacement = `<form class="form" id="contactForm">
536
- <h1 class="form-title">Contact Us</h1>
537
- <p class="form-subtitle">Fill in the details and we'll get back to you soon</p>
538
-
539
- ${fieldsHtml} <button type="submit"`;
540
-
541
- htmlContent = htmlContent.replace(formRegex, replacement);
542
-
543
- return htmlContent;
544
- }
545
-
546
39
  async function generateTemplate(options) {
547
- const {
548
- component,
549
- name,
550
- includeJs,
551
- navItems,
552
- formFields,
40
+ const {
41
+ component,
42
+ name,
43
+ includeJs,
44
+ navItems,
45
+ formFields,
553
46
  customFormFields,
554
47
  buttonVariations,
555
48
  selectedButtons,
@@ -560,13 +53,13 @@ async function generateTemplate(options) {
560
53
  colorScheme,
561
54
  primaryColor,
562
55
  secondaryColor,
563
- darkMode
56
+ darkMode,
564
57
  } = options;
565
58
 
566
59
  // If colorScheme is provided, use those colors
567
60
  let finalPrimaryColor = primaryColor;
568
61
  let finalSecondaryColor = secondaryColor;
569
-
62
+
570
63
  if (colorScheme && COLOR_SCHEMES[colorScheme]) {
571
64
  const scheme = COLOR_SCHEMES[colorScheme];
572
65
  finalPrimaryColor = scheme.primary;
@@ -576,7 +69,7 @@ async function generateTemplate(options) {
576
69
  // Security: Validate component name
577
70
  if (!VALID_COMPONENTS.includes(component)) {
578
71
  throw new Error(
579
- `Invalid component: ${component}. Must be one of: ${VALID_COMPONENTS.join(", ")}`,
72
+ `Invalid component: ${component}. Must be one of: ${VALID_COMPONENTS.join(", ")}`
580
73
  );
581
74
  }
582
75
 
@@ -588,45 +81,49 @@ async function generateTemplate(options) {
588
81
 
589
82
  // Create output directory structure
590
83
  const outputDir = path.join(process.cwd(), safeName);
591
- const cssDir = path.join(outputDir, "css");
592
- const jsDir = path.join(outputDir, "js");
593
-
594
- await fs.mkdir(outputDir, { recursive: true });
595
- await fs.mkdir(cssDir, { recursive: true });
596
- await fs.mkdir(jsDir, { recursive: true });
84
+ const { cssDir, jsDir } = await createComponentDirs(outputDir);
597
85
 
598
86
  // Get template content
599
- const templateDir = path.join(__dirname, "..", "templates", component);
87
+ const templateDir = getTemplatePath(component);
600
88
 
601
89
  // Copy HTML file
602
- let htmlContent = await fs.readFile(
603
- path.join(templateDir, "index.html"),
604
- "utf-8",
605
- );
90
+ let htmlContent = await readTemplateHtml(component);
606
91
 
607
92
  // Replace placeholder name
608
93
  htmlContent = htmlContent.replace(/{{name}}/g, safeName);
609
-
94
+
610
95
  // Filter button variations
611
96
  if (component === "button") {
612
- htmlContent = filterButtonVariations(htmlContent, buttonVariations, selectedButtons);
97
+ htmlContent = filterButtonVariations(
98
+ htmlContent,
99
+ buttonVariations,
100
+ selectedButtons
101
+ );
613
102
  }
614
-
103
+
615
104
  // Filter card variations
616
105
  if (component === "card") {
617
- htmlContent = filterCardVariations(htmlContent, cardVariations, selectedCards);
106
+ htmlContent = filterCardVariations(
107
+ htmlContent,
108
+ cardVariations,
109
+ selectedCards
110
+ );
618
111
  }
619
-
112
+
620
113
  // Filter spinner variations
621
114
  if (component === "spinner") {
622
- htmlContent = filterSpinnerVariations(htmlContent, spinnerVariations, selectedSpinners);
115
+ htmlContent = filterSpinnerVariations(
116
+ htmlContent,
117
+ spinnerVariations,
118
+ selectedSpinners
119
+ );
623
120
  }
624
-
121
+
625
122
  // Custom navigation items
626
123
  if (component === "navigation" && navItems) {
627
124
  htmlContent = generateNavigationItems(htmlContent, navItems);
628
125
  }
629
-
126
+
630
127
  // Custom form fields
631
128
  if (component === "form" && formFields) {
632
129
  htmlContent = generateFormFields(htmlContent, formFields, customFormFields);
@@ -637,30 +134,28 @@ async function generateTemplate(options) {
637
134
  // Insert script tag before closing </body> tag
638
135
  htmlContent = htmlContent.replace(
639
136
  "</body>",
640
- ' <script src="js/script.js"></script>\n</body>',
137
+ ' <script src="js/script.js"></script>\n</body>'
641
138
  );
642
139
  }
643
140
 
644
141
  // Update CSS link to point to css/ folder
645
142
  htmlContent = htmlContent.replace(
646
143
  /<link[^>]*href="style\.css"[^>]*>/g,
647
- ' <link rel="stylesheet" href="css/style.css">',
144
+ ' <link rel="stylesheet" href="css/style.css">'
648
145
  );
649
146
 
650
- // Format HTML before writing
651
- htmlContent = await formatHtml(htmlContent);
652
-
653
- await fs.writeFile(path.join(outputDir, "index.html"), htmlContent);
147
+ await writeHtmlFile(path.join(outputDir, "index.html"), htmlContent);
654
148
 
655
149
  // Copy CSS file to css/ folder
656
- let cssContent = await fs.readFile(
657
- path.join(templateDir, "style.css"),
658
- "utf-8",
659
- );
150
+ let cssContent = await readTemplateCss(component);
660
151
 
661
152
  // Apply custom colors if provided (either from colorScheme or direct colors)
662
153
  if (finalPrimaryColor || finalSecondaryColor) {
663
- cssContent = applyCustomColors(cssContent, finalPrimaryColor, finalSecondaryColor);
154
+ cssContent = applyCustomColors(
155
+ cssContent,
156
+ finalPrimaryColor,
157
+ finalSecondaryColor
158
+ );
664
159
  }
665
160
 
666
161
  // Add dark mode support if requested
@@ -668,26 +163,20 @@ async function generateTemplate(options) {
668
163
  cssContent = addDarkModeStyles(cssContent);
669
164
  }
670
165
 
671
- const formattedCss = await formatCss(cssContent);
672
- await fs.writeFile(path.join(cssDir, "style.css"), formattedCss);
166
+ await writeCssFile(path.join(cssDir, "style.css"), cssContent);
673
167
 
674
168
  // Copy JS file to js/ folder if requested
675
169
  if (includeJs) {
676
- try {
677
- const jsContent = await fs.readFile(
678
- path.join(templateDir, "script.js"),
679
- "utf-8",
680
- );
681
- const formattedJs = await formatJs(jsContent);
682
- await fs.writeFile(path.join(jsDir, "script.js"), formattedJs);
683
- } catch (error) {
170
+ const jsContent = await readTemplateJs(component);
171
+ if (jsContent) {
172
+ await writeJsFile(path.join(jsDir, "script.js"), jsContent);
173
+ } else {
684
174
  // If no JS template exists, create a basic one
685
- const basicJs = await formatJs("// Add your JavaScript here\n");
686
- await fs.writeFile(path.join(jsDir, "script.js"), basicJs);
175
+ await writeJsFile(path.join(jsDir, "script.js"), "// Add your JavaScript here\n");
687
176
  }
688
177
  }
689
178
 
690
179
  return outputDir;
691
180
  }
692
181
 
693
- module.exports = { generateTemplate, COLOR_SCHEMES, getColorScheme };
182
+ export { generateTemplate, COLOR_SCHEMES, getColorScheme };