create-template-html-css 2.0.4 → 2.2.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 (99) hide show
  1. package/CHANGELOG.md +436 -0
  2. package/CODE-SPLITTING-GUIDE.md +274 -0
  3. package/COMPONENTS-GALLERY.html +143 -8
  4. package/HTML-VS-REACT.md +289 -0
  5. package/QUICKSTART-REACT.md +293 -0
  6. package/REACT-SUPPORT-SUMMARY.md +235 -0
  7. package/README.md +261 -12
  8. package/bin/cli.js +100 -759
  9. package/bin/commands/create.js +288 -0
  10. package/bin/commands/gallery.js +42 -0
  11. package/bin/commands/insert.js +123 -0
  12. package/bin/commands/list.js +73 -0
  13. package/package.json +10 -3
  14. package/src/component-choices.js +7 -0
  15. package/src/components-registry.js +112 -0
  16. package/src/format-utils.js +49 -0
  17. package/src/generator.js +83 -594
  18. package/src/generators/color-schemes.js +78 -0
  19. package/src/generators/color-utils.js +108 -0
  20. package/src/generators/component-filters.js +151 -0
  21. package/src/generators/html-generators.js +180 -0
  22. package/src/generators/validation.js +43 -0
  23. package/src/index.js +2 -1
  24. package/src/inserter.js +55 -233
  25. package/src/inserters/backup-utils.js +20 -0
  26. package/src/inserters/component-loader.js +68 -0
  27. package/src/inserters/html-utils.js +31 -0
  28. package/src/inserters/indentation-utils.js +90 -0
  29. package/src/inserters/validation-utils.js +49 -0
  30. package/src/react-component-choices.js +97 -0
  31. package/src/react-component-templates.js +182 -0
  32. package/src/react-file-operations.js +172 -0
  33. package/src/react-generator.js +219 -0
  34. package/src/react-templates.js +418 -0
  35. package/src/templates/basic-components-templates.js +157 -0
  36. package/src/templates/form-components-templates.js +194 -0
  37. package/src/templates/interactive-components-templates.js +139 -0
  38. package/src/utils/file-utils.js +97 -0
  39. package/src/utils/path-utils.js +32 -0
  40. package/src/utils/string-utils.js +51 -0
  41. package/src/utils/template-loader.js +91 -0
  42. package/templates/_shared/PATTERNS.md +246 -0
  43. package/templates/_shared/README.md +74 -0
  44. package/templates/_shared/base.css +18 -0
  45. package/templates/blackjack/index.html +1 -1
  46. package/templates/breakout/index.html +1 -1
  47. package/templates/connect-four/index.html +1 -1
  48. package/templates/dice-game/index.html +1 -1
  49. package/templates/flappy-bird/index.html +1 -1
  50. package/templates/pong/index.html +1 -1
  51. package/templates/skeleton/index.html +4 -4
  52. package/templates/slot-machine/index.html +1 -1
  53. package/templates/tetris/index.html +1 -1
  54. package/templates-react/README.md +126 -0
  55. package/templates-react/alert/Alert.css +158 -0
  56. package/templates-react/alert/Alert.example.jsx +106 -0
  57. package/templates-react/alert/Alert.jsx +61 -0
  58. package/templates-react/badge/Badge.css +196 -0
  59. package/templates-react/badge/Badge.example.jsx +182 -0
  60. package/templates-react/badge/Badge.jsx +44 -0
  61. package/templates-react/button/Button.css +88 -0
  62. package/templates-react/button/Button.example.jsx +40 -0
  63. package/templates-react/button/Button.jsx +29 -0
  64. package/templates-react/card/Card.css +86 -0
  65. package/templates-react/card/Card.example.jsx +49 -0
  66. package/templates-react/card/Card.jsx +35 -0
  67. package/templates-react/checkbox/Checkbox.css +217 -0
  68. package/templates-react/checkbox/Checkbox.example.jsx +141 -0
  69. package/templates-react/checkbox/Checkbox.jsx +82 -0
  70. package/templates-react/counter/Counter.css +99 -0
  71. package/templates-react/counter/Counter.example.jsx +45 -0
  72. package/templates-react/counter/Counter.jsx +70 -0
  73. package/templates-react/dropdown/Dropdown.css +237 -0
  74. package/templates-react/dropdown/Dropdown.example.jsx +98 -0
  75. package/templates-react/dropdown/Dropdown.jsx +154 -0
  76. package/templates-react/form/Form.css +128 -0
  77. package/templates-react/form/Form.example.jsx +64 -0
  78. package/templates-react/form/Form.jsx +125 -0
  79. package/templates-react/input/Input.css +113 -0
  80. package/templates-react/input/Input.example.jsx +82 -0
  81. package/templates-react/input/Input.jsx +87 -0
  82. package/templates-react/modal/Modal.css +152 -0
  83. package/templates-react/modal/Modal.example.jsx +90 -0
  84. package/templates-react/modal/Modal.jsx +46 -0
  85. package/templates-react/navbar/Navbar.css +139 -0
  86. package/templates-react/navbar/Navbar.example.jsx +37 -0
  87. package/templates-react/navbar/Navbar.jsx +62 -0
  88. package/templates-react/progress/Progress.css +247 -0
  89. package/templates-react/progress/Progress.example.jsx +244 -0
  90. package/templates-react/progress/Progress.jsx +79 -0
  91. package/templates-react/switch/Switch.css +244 -0
  92. package/templates-react/switch/Switch.example.jsx +221 -0
  93. package/templates-react/switch/Switch.jsx +98 -0
  94. package/templates-react/todo-list/TodoList.css +236 -0
  95. package/templates-react/todo-list/TodoList.example.jsx +15 -0
  96. package/templates-react/todo-list/TodoList.jsx +84 -0
  97. package/templates-react/tooltip/Tooltip.css +165 -0
  98. package/templates-react/tooltip/Tooltip.example.jsx +166 -0
  99. package/templates-react/tooltip/Tooltip.jsx +176 -0
package/src/inserter.js CHANGED
@@ -1,150 +1,43 @@
1
- const fs = require("fs").promises;
2
- const path = require("path");
3
-
4
- // Security: Validate component name against whitelist
5
- const VALID_COMPONENTS = [
6
- "button",
7
- "card",
8
- "form",
9
- "navigation",
10
- "modal",
11
- "footer",
12
- "hero",
13
- "slider",
14
- "table",
15
- "spinner",
16
- "animated-card",
17
- "typing-effect",
18
- "fade-gallery",
19
- "grid-layout",
20
- "masonry-grid",
21
- "dashboard-grid",
22
- "flex-layout",
23
- "flex-cards",
24
- "flex-dashboard",
25
- "todo-list",
26
- "counter",
27
- "accordion",
28
- "tabs",
29
- ];
30
-
31
1
  /**
32
- * Extracts indentation from a line
2
+ * Component insertion utilities
3
+ * Inserts components into existing HTML files
33
4
  */
34
- function getIndentation(line) {
35
- const match = line.match(/^(\s*)/);
36
- return match ? match[1] : "";
37
- }
38
5
 
39
- /**
40
- * Checks if a component is already inserted in the HTML
41
- */
42
- function isComponentAlreadyInserted(htmlContent, component) {
43
- const commentPattern = new RegExp(
44
- `<!-- ${component.toUpperCase()} Component -->`,
45
- "i",
46
- );
47
- return commentPattern.test(htmlContent);
48
- }
49
-
50
- /**
51
- * Formats HTML content with prettier (optional - falls back to original if not available)
52
- */
53
- async function formatHtml(htmlContent) {
54
- try {
55
- const prettier = require("prettier");
56
- return await prettier.format(htmlContent, { parser: "html" });
57
- } catch (error) {
58
- // Prettier not installed or error formatting - return original content
59
- return htmlContent;
60
- }
61
- }
6
+ import fs from "fs/promises";
7
+ import path from "path";
8
+ import { ensureDir, writeCssFile, writeJsFile, writeHtmlFile } from "./utils/file-utils.js";
9
+ import {
10
+ VALID_COMPONENTS,
11
+ isComponentAlreadyInserted,
12
+ validateHtmlStructure,
13
+ } from "./inserters/validation-utils.js";
14
+ import { getHtmlIndentation } from "./inserters/html-utils.js";
15
+ import { createBackup } from "./inserters/backup-utils.js";
16
+ import {
17
+ loadComponentHtml,
18
+ loadComponentCss,
19
+ loadComponentJs,
20
+ extractBodyContent,
21
+ } from "./inserters/component-loader.js";
22
+ import {
23
+ normalizeIndentation,
24
+ createInlineStyleTag,
25
+ createInlineScriptTag,
26
+ createStyleLink,
27
+ createScriptTag,
28
+ createComponentInsertion,
29
+ } from "./inserters/indentation-utils.js";
62
30
 
63
31
  /**
64
- * Formats CSS content with prettier (optional - falls back to original if not available)
32
+ * Inserts a component into an existing HTML file
33
+ * @param {Object} options - Insertion options
34
+ * @param {string} options.component - Component name to insert
35
+ * @param {string} options.targetFile - Target HTML file path
36
+ * @param {string} options.styleMode - Style insertion mode ('inline' or 'separate')
37
+ * @param {string} options.scriptMode - Script insertion mode ('inline' or 'separate')
38
+ * @param {boolean} options.createBackup - Whether to create a backup before insertion
39
+ * @returns {Promise<Object>} Insertion result with success status and paths
65
40
  */
66
- async function formatCss(cssContent) {
67
- try {
68
- const prettier = require("prettier");
69
- return await prettier.format(cssContent, { parser: "css" });
70
- } catch (error) {
71
- // Prettier not installed or error formatting - return original content
72
- return cssContent;
73
- }
74
- }
75
-
76
- /**
77
- * Formats JavaScript content with prettier (optional - falls back to original if not available)
78
- */
79
- async function formatJs(jsContent) {
80
- try {
81
- const prettier = require("prettier");
82
- return await prettier.format(jsContent, { parser: "babel" });
83
- } catch (error) {
84
- // Prettier not installed or error formatting - return original content
85
- return jsContent;
86
- }
87
- }
88
-
89
- /**
90
- * Gets the indentation level used in an HTML file
91
- */
92
- function getHtmlIndentation(htmlContent) {
93
- // Look for any indented line to determine the standard indentation
94
- const match = htmlContent.match(/\n(\s+)\S/);
95
- return match ? match[1] : " "; // default to 4 spaces
96
- }
97
-
98
- /**
99
- * Validates that HTML file has proper structure
100
- * @param {string} htmlContent - The HTML content to validate
101
- * @returns {Object} Object with valid property and any errors
102
- */
103
- function validateHtmlStructure(htmlContent) {
104
- const errors = [];
105
-
106
- if (!htmlContent.includes("<!DOCTYPE")) {
107
- errors.push("Missing DOCTYPE declaration");
108
- }
109
-
110
- if (!htmlContent.includes("<html")) {
111
- errors.push("Missing <html> tag");
112
- }
113
-
114
- if (!htmlContent.includes("<head>") && !htmlContent.includes("<head ")) {
115
- errors.push("Missing <head> tag");
116
- }
117
-
118
- if (!htmlContent.includes("</head>")) {
119
- errors.push("Missing closing </head> tag");
120
- }
121
-
122
- if (!htmlContent.includes("<body>") && !htmlContent.includes("<body ")) {
123
- errors.push("Missing <body> tag");
124
- }
125
-
126
- if (!htmlContent.includes("</body>")) {
127
- errors.push("Missing closing </body> tag");
128
- }
129
-
130
- return {
131
- valid: errors.length === 0,
132
- errors,
133
- };
134
- }
135
-
136
- /**
137
- * Creates a backup of the original file before insertion
138
- * @param {string} targetPath - Path to the original file
139
- * @returns {Promise<string>} Path to the backup file
140
- */
141
- async function createBackup(targetPath) {
142
- const backupPath = `${targetPath}.backup`;
143
- const content = await fs.readFile(targetPath, "utf-8");
144
- await fs.writeFile(backupPath, content, "utf-8");
145
- return backupPath;
146
- }
147
-
148
41
  async function insertComponent(options) {
149
42
  const {
150
43
  component,
@@ -203,150 +96,79 @@ async function insertComponent(options) {
203
96
  throw new Error("Target HTML file does not have a </head> tag");
204
97
  }
205
98
 
206
- // Get component templates
207
- const templateDir = path.join(__dirname, "..", "templates", component);
208
- const componentHtml = await fs.readFile(
209
- path.join(templateDir, "index.html"),
210
- "utf-8",
211
- );
212
-
213
- // Try to read CSS from css/ subfolder, fall back to root
214
- let componentCss;
215
- try {
216
- componentCss = await fs.readFile(
217
- path.join(templateDir, "css", "style.css"),
218
- "utf-8",
219
- );
220
- } catch {
221
- componentCss = await fs.readFile(
222
- path.join(templateDir, "style.css"),
223
- "utf-8",
224
- );
225
- }
226
-
227
- // Extract component body content (only the inner content, not the body tags)
228
- const bodyMatch = componentHtml.match(/<body[^>]*>\s*([\s\S]*?)\s*<\/body>/i);
229
- if (!bodyMatch) {
230
- throw new Error("Invalid component template structure");
231
- }
232
-
233
- let componentBody = bodyMatch[1].trim();
99
+ // Load component templates
100
+ const componentHtml = await loadComponentHtml(component);
101
+ const componentCss = await loadComponentCss(component);
234
102
 
235
- // Remove any script and style tags that might be in the body
236
- componentBody = componentBody
237
- .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
238
- .trim();
103
+ // Extract component body content
104
+ let componentBody = extractBodyContent(componentHtml);
239
105
 
240
106
  // Get indentation used in the HTML file
241
107
  const baseIndent = getHtmlIndentation(htmlContent);
242
108
 
243
109
  // Normalize component body indentation
244
- const lines = componentBody
245
- .split("\n")
246
- .map((line) => {
247
- if (line.trim() === "") return "";
248
- return baseIndent + line.trim();
249
- })
250
- .join("\n");
251
- componentBody = lines;
110
+ componentBody = normalizeIndentation(componentBody, baseIndent);
252
111
 
253
112
  // Insert component HTML before closing </body> tag
254
113
  htmlContent = htmlContent.replace(
255
114
  "</body>",
256
- `${baseIndent}<!-- ${component.toUpperCase()} Component -->\n${componentBody}\n\n</body>`,
115
+ createComponentInsertion(component, componentBody, baseIndent),
257
116
  );
258
117
 
259
118
  // Handle CSS
260
119
  if (styleMode === "inline") {
261
- // Normalize CSS indentation
262
- const normalizedCss = componentCss
263
- .split("\n")
264
- .map((line) => {
265
- if (line.trim() === "") return "";
266
- return baseIndent + " " + line.trim();
267
- })
268
- .join("\n");
269
-
270
120
  htmlContent = htmlContent.replace(
271
121
  "</head>",
272
- `${baseIndent}<style id="${component}-styles">\n${baseIndent} /* ${component.toUpperCase()} Component Styles */\n${normalizedCss}\n${baseIndent}</style>\n</head>`,
122
+ createInlineStyleTag(component, componentCss, baseIndent),
273
123
  );
274
124
  } else if (styleMode === "separate") {
275
125
  // Create css directory if needed
276
126
  const cssDir = path.join(path.dirname(targetPath), "css");
277
- await fs.mkdir(cssDir, { recursive: true });
127
+ await ensureDir(cssDir);
278
128
 
279
129
  // Create separate CSS file in css/ folder
280
130
  const cssFileName = `${component}-component.css`;
281
131
  const cssPath = path.join(cssDir, cssFileName);
282
- const formattedCss = await formatCss(
283
- `/* ${component.toUpperCase()} Component Styles */\n\n${componentCss}`,
284
- );
285
- await fs.writeFile(cssPath, formattedCss);
132
+ const cssWithComment = `/* ${component.toUpperCase()} Component Styles */\n\n${componentCss}`;
133
+ await writeCssFile(cssPath, cssWithComment);
286
134
 
287
135
  // Add link to CSS file
288
136
  htmlContent = htmlContent.replace(
289
137
  "</head>",
290
- `${baseIndent}<link rel="stylesheet" href="css/${cssFileName}">\n</head>`,
138
+ createStyleLink(cssFileName, baseIndent),
291
139
  );
292
140
  }
293
141
 
294
142
  // Handle JavaScript
295
- try {
296
- // Try to read JS from js/ subfolder, fall back to root
297
- let componentJs;
298
- try {
299
- componentJs = await fs.readFile(
300
- path.join(templateDir, "js", "script.js"),
301
- "utf-8",
302
- );
303
- } catch {
304
- componentJs = await fs.readFile(
305
- path.join(templateDir, "script.js"),
306
- "utf-8",
307
- );
308
- }
143
+ const componentJs = await loadComponentJs(component);
309
144
 
145
+ if (componentJs) {
310
146
  if (scriptMode === "inline") {
311
- // Normalize JS indentation
312
- const normalizedJs = componentJs
313
- .split("\n")
314
- .map((line) => {
315
- if (line.trim() === "") return "";
316
- return baseIndent + " " + line.trim();
317
- })
318
- .join("\n");
319
-
320
147
  htmlContent = htmlContent.replace(
321
148
  "</body>",
322
- `${baseIndent}<script id="${component}-script">\n${baseIndent} // ${component.toUpperCase()} Component Script\n${normalizedJs}\n${baseIndent}</script>\n</body>`,
149
+ createInlineScriptTag(component, componentJs, baseIndent),
323
150
  );
324
151
  } else if (scriptMode === "separate") {
325
152
  // Create js directory if needed
326
153
  const jsDir = path.join(path.dirname(targetPath), "js");
327
- await fs.mkdir(jsDir, { recursive: true });
154
+ await ensureDir(jsDir);
328
155
 
329
156
  // Create separate JS file in js/ folder
330
157
  const jsFileName = `${component}-component.js`;
331
158
  const jsPath = path.join(jsDir, jsFileName);
332
- const formattedJs = await formatJs(
333
- `// ${component.toUpperCase()} Component Script\n\n${componentJs}`,
334
- );
335
- await fs.writeFile(jsPath, formattedJs);
159
+ const jsWithComment = `// ${component.toUpperCase()} Component Script\n\n${componentJs}`;
160
+ await writeJsFile(jsPath, jsWithComment);
336
161
 
337
162
  // Add script tag
338
163
  htmlContent = htmlContent.replace(
339
164
  "</body>",
340
- `${baseIndent}<script src="js/${jsFileName}" id="${component}-script"></script>\n</body>`,
165
+ createScriptTag(component, jsFileName, baseIndent),
341
166
  );
342
167
  }
343
- } catch (error) {
344
- // No JavaScript file for this component, skip
345
168
  }
346
169
 
347
170
  // Write updated HTML with prettier formatting
348
- const formattedHtml = await formatHtml(htmlContent);
349
- await fs.writeFile(targetPath, formattedHtml);
171
+ await writeHtmlFile(targetPath, htmlContent);
350
172
 
351
173
  return {
352
174
  targetFile: targetPath,
@@ -358,4 +180,4 @@ async function insertComponent(options) {
358
180
  };
359
181
  }
360
182
 
361
- module.exports = { insertComponent, validateHtmlStructure, createBackup };
183
+ export { insertComponent, validateHtmlStructure, createBackup };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Backup utilities for file operations
3
+ * Handles creating backups before modifying files
4
+ */
5
+
6
+ import fs from "fs/promises";
7
+
8
+ /**
9
+ * Creates a backup of the original file before insertion
10
+ * @param {string} targetPath - Path to the original file
11
+ * @returns {Promise<string>} Path to the backup file
12
+ */
13
+ async function createBackup(targetPath) {
14
+ const backupPath = `${targetPath}.backup`;
15
+ const content = await fs.readFile(targetPath, "utf-8");
16
+ await fs.writeFile(backupPath, content, "utf-8");
17
+ return backupPath;
18
+ }
19
+
20
+ export { createBackup };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Component template loader
3
+ * Loads HTML, CSS, and JavaScript files from template directories
4
+ */
5
+
6
+ import {
7
+ readTemplateHtml,
8
+ readTemplateCss,
9
+ readTemplateJs,
10
+ } from "../utils/template-loader.js";
11
+
12
+ /**
13
+ * Loads component HTML template
14
+ * @param {string} component - Component name
15
+ * @returns {Promise<string>} HTML content
16
+ */
17
+ async function loadComponentHtml(component) {
18
+ return await readTemplateHtml(component);
19
+ }
20
+
21
+ /**
22
+ * Loads component CSS template
23
+ * Tries css/ subfolder first, falls back to root
24
+ * @param {string} component - Component name
25
+ * @returns {Promise<string>} CSS content
26
+ */
27
+ async function loadComponentCss(component) {
28
+ return await readTemplateCss(component);
29
+ }
30
+
31
+ /**
32
+ * Loads component JavaScript template
33
+ * Tries js/ subfolder first, falls back to root
34
+ * @param {string} component - Component name
35
+ * @returns {Promise<string|null>} JavaScript content or null if not found
36
+ */
37
+ async function loadComponentJs(component) {
38
+ return await readTemplateJs(component);
39
+ }
40
+
41
+ /**
42
+ * Extracts body content from component HTML
43
+ * Removes script and style tags
44
+ * @param {string} componentHtml - Full component HTML
45
+ * @returns {string} Extracted body content
46
+ */
47
+ function extractBodyContent(componentHtml) {
48
+ const bodyMatch = componentHtml.match(/<body[^>]*>\s*([\s\S]*?)\s*<\/body>/i);
49
+ if (!bodyMatch) {
50
+ throw new Error("Invalid component template structure");
51
+ }
52
+
53
+ let componentBody = bodyMatch[1].trim();
54
+
55
+ // Remove any script and style tags that might be in the body
56
+ componentBody = componentBody
57
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
58
+ .trim();
59
+
60
+ return componentBody;
61
+ }
62
+
63
+ export {
64
+ loadComponentHtml,
65
+ loadComponentCss,
66
+ loadComponentJs,
67
+ extractBodyContent,
68
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * HTML manipulation utilities
3
+ * Handles indentation and HTML parsing operations
4
+ */
5
+
6
+ /**
7
+ * Extracts the indentation string from a line
8
+ * @param {string} line - Line to extract indentation from
9
+ * @returns {string} The indentation string (spaces or tabs)
10
+ */
11
+ function getIndentation(line) {
12
+ const match = line.match(/^(\s+)/);
13
+ return match ? match[1] : "";
14
+ }
15
+
16
+ /**
17
+ * Determines the standard indentation used in HTML content
18
+ * @param {string} htmlContent - HTML content to analyze
19
+ * @returns {string} The detected indentation string (defaults to 2 spaces)
20
+ */
21
+ function getHtmlIndentation(htmlContent) {
22
+ const lines = htmlContent.split("\n");
23
+ for (const line of lines) {
24
+ if (line.trim() && line.startsWith(" ")) {
25
+ return getIndentation(line);
26
+ }
27
+ }
28
+ return " "; // Default to 2 spaces
29
+ }
30
+
31
+ export { getIndentation, getHtmlIndentation };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Content indentation utilities
3
+ * Handles indentation normalization for HTML, CSS, and JS content
4
+ */
5
+
6
+ /**
7
+ * Normalizes content indentation with base indent
8
+ * @param {string} content - Content to normalize
9
+ * @param {string} baseIndent - Base indentation to apply
10
+ * @param {string} extraIndent - Additional indentation (default: empty)
11
+ * @returns {string} Normalized content with proper indentation
12
+ */
13
+ export function normalizeIndentation(content, baseIndent, extraIndent = "") {
14
+ return content
15
+ .split("\n")
16
+ .map((line) => {
17
+ if (line.trim() === "") return "";
18
+ return baseIndent + extraIndent + line.trim();
19
+ })
20
+ .join("\n");
21
+ }
22
+
23
+ /**
24
+ * Creates inline style tag with normalized indentation
25
+ * @param {string} component - Component name
26
+ * @param {string} cssContent - CSS content
27
+ * @param {string} baseIndent - Base indentation
28
+ * @returns {string} Formatted style tag
29
+ */
30
+ export function createInlineStyleTag(component, cssContent, baseIndent) {
31
+ const normalizedCss = normalizeIndentation(cssContent, baseIndent, " ");
32
+ return `${baseIndent}<style id="${component}-styles">
33
+ ${baseIndent} /* ${component.toUpperCase()} Component Styles */
34
+ ${normalizedCss}
35
+ ${baseIndent}</style>
36
+ </head>`;
37
+ }
38
+
39
+ /**
40
+ * Creates inline script tag with normalized indentation
41
+ * @param {string} component - Component name
42
+ * @param {string} jsContent - JavaScript content
43
+ * @param {string} baseIndent - Base indentation
44
+ * @returns {string} Formatted script tag
45
+ */
46
+ export function createInlineScriptTag(component, jsContent, baseIndent) {
47
+ const normalizedJs = normalizeIndentation(jsContent, baseIndent, " ");
48
+ return `${baseIndent}<script id="${component}-script">
49
+ ${baseIndent} // ${component.toUpperCase()} Component Script
50
+ ${normalizedJs}
51
+ ${baseIndent}</script>
52
+ </body>`;
53
+ }
54
+
55
+ /**
56
+ * Creates external stylesheet link
57
+ * @param {string} cssFileName - CSS file name
58
+ * @param {string} baseIndent - Base indentation
59
+ * @returns {string} Link tag
60
+ */
61
+ export function createStyleLink(cssFileName, baseIndent) {
62
+ return `${baseIndent}<link rel="stylesheet" href="css/${cssFileName}">
63
+ </head>`;
64
+ }
65
+
66
+ /**
67
+ * Creates external script tag
68
+ * @param {string} component - Component name
69
+ * @param {string} jsFileName - JavaScript file name
70
+ * @param {string} baseIndent - Base indentation
71
+ * @returns {string} Script tag
72
+ */
73
+ export function createScriptTag(component, jsFileName, baseIndent) {
74
+ return `${baseIndent}<script src="js/${jsFileName}" id="${component}-script"></script>
75
+ </body>`;
76
+ }
77
+
78
+ /**
79
+ * Creates component HTML insertion with comment
80
+ * @param {string} component - Component name
81
+ * @param {string} componentBody - Component HTML body
82
+ * @param {string} baseIndent - Base indentation
83
+ * @returns {string} Formatted HTML with component comment
84
+ */
85
+ export function createComponentInsertion(component, componentBody, baseIndent) {
86
+ return `${baseIndent}<!-- ${component.toUpperCase()} Component -->
87
+ ${componentBody}
88
+
89
+ </body>`;
90
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Component validation utilities for the insert command
3
+ * Validates component names and HTML structure
4
+ */
5
+
6
+ import { VALID_COMPONENTS } from "../components-registry.js";
7
+
8
+ /**
9
+ * Checks if a component has already been inserted in the HTML content
10
+ * @param {string} htmlContent - HTML content to check
11
+ * @param {string} component - Component name to look for
12
+ * @returns {boolean} True if component is already inserted
13
+ */
14
+ function isComponentAlreadyInserted(htmlContent, component) {
15
+ const componentComment = `<!-- ${component.toUpperCase()} Component -->`;
16
+ return htmlContent.includes(componentComment);
17
+ }
18
+
19
+ /**
20
+ * Validates HTML file structure for required tags
21
+ * @param {string} htmlContent - HTML content to validate
22
+ * @returns {Object} Validation result with valid flag and errors array
23
+ */
24
+ function validateHtmlStructure(htmlContent) {
25
+ const errors = [];
26
+
27
+ if (!htmlContent.includes("<head>") && !htmlContent.includes("<head ")) {
28
+ errors.push("Missing <head> tag");
29
+ }
30
+
31
+ if (!htmlContent.includes("</head>")) {
32
+ errors.push("Missing closing </head> tag");
33
+ }
34
+
35
+ if (!htmlContent.includes("<body>") && !htmlContent.includes("<body ")) {
36
+ errors.push("Missing <body> tag");
37
+ }
38
+
39
+ if (!htmlContent.includes("</body>")) {
40
+ errors.push("Missing closing </body> tag");
41
+ }
42
+
43
+ return {
44
+ valid: errors.length === 0,
45
+ errors,
46
+ };
47
+ }
48
+
49
+ export { VALID_COMPONENTS, isComponentAlreadyInserted, validateHtmlStructure };