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,172 @@
1
+ import { promises as fs } from "fs";
2
+ import path from "path";
3
+ import { getDirname } from "./utils/path-utils.js";
4
+ import { COLOR_SCHEMES } from "./generators/color-schemes.js";
5
+ import { applyCustomColors } from "./generators/color-utils.js";
6
+
7
+ const __dirname = getDirname(import.meta.url);
8
+
9
+ // ============================================================================
10
+ // CONSTANTS
11
+ // ============================================================================
12
+
13
+ // Default colors
14
+ const DEFAULT_PRIMARY_COLOR = "#667eea";
15
+ const DEFAULT_SECONDARY_COLOR = "#764ba2";
16
+
17
+ // ============================================================================
18
+ // PATH UTILITIES
19
+ // ============================================================================
20
+
21
+ /**
22
+ * Get template path for React components
23
+ * @param {string} component - Component name
24
+ * @returns {string} Absolute path to template
25
+ */
26
+ function getReactTemplatePath(component) {
27
+ return path.join(__dirname, "..", "templates-react", component);
28
+ }
29
+
30
+ // ============================================================================
31
+ // FILE READING
32
+ // ============================================================================
33
+
34
+ /**
35
+ * Read React component file (.jsx)
36
+ * @param {string} component - Component name
37
+ * @param {string} filename - File name
38
+ * @returns {Promise<string>} File content
39
+ */
40
+ async function readReactFile(component, filename) {
41
+ const templatePath = getReactTemplatePath(component);
42
+ const filePath = path.join(templatePath, filename);
43
+
44
+ try {
45
+ return await fs.readFile(filePath, "utf-8");
46
+ } catch (error) {
47
+ if (error.code === "ENOENT") {
48
+ return null;
49
+ }
50
+ throw error;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Read and process component template files
56
+ * @param {string} component - Component name (kebab-case)
57
+ * @param {string} componentName - Component name (PascalCase)
58
+ * @param {string} primaryColor - Primary color
59
+ * @param {string} secondaryColor - Secondary color
60
+ * @returns {Promise<Object>} Processed JSX and CSS content
61
+ */
62
+ async function readComponentFiles(component, componentName, primaryColor, secondaryColor) {
63
+ const jsxContent = await readReactFile(component, `${componentName}.jsx`);
64
+ let cssContent = await readReactFile(component, `${componentName}.css`);
65
+
66
+ if (!jsxContent) {
67
+ throw new Error(`React template not found for component: ${component}`);
68
+ }
69
+
70
+ // Apply custom colors to CSS
71
+ if (cssContent && (primaryColor || secondaryColor)) {
72
+ cssContent = applyCustomColors(cssContent, primaryColor, secondaryColor);
73
+ }
74
+
75
+ return { jsxContent, cssContent };
76
+ }
77
+
78
+ // ============================================================================
79
+ // FILE WRITING
80
+ // ============================================================================
81
+
82
+ /**
83
+ * Write component files to directory
84
+ * @param {string} componentDir - Component directory path
85
+ * @param {string} componentName - Component name (PascalCase)
86
+ * @param {string} jsxContent - JSX file content
87
+ * @param {string} cssContent - CSS file content
88
+ */
89
+ async function writeComponentFiles(componentDir, componentName, jsxContent, cssContent) {
90
+ await fs.writeFile(
91
+ path.join(componentDir, `${componentName}.jsx`),
92
+ jsxContent,
93
+ "utf-8"
94
+ );
95
+
96
+ if (cssContent) {
97
+ await fs.writeFile(
98
+ path.join(componentDir, `${componentName}.css`),
99
+ cssContent,
100
+ "utf-8"
101
+ );
102
+ }
103
+ }
104
+
105
+ // ============================================================================
106
+ // PROJECT STRUCTURE
107
+ // ============================================================================
108
+
109
+ /**
110
+ * Create React project structure
111
+ * @param {string} outputDir - Output directory path
112
+ * @returns {Promise<Object>} Directory paths
113
+ */
114
+ async function createReactProjectStructure(outputDir) {
115
+ await fs.mkdir(outputDir, { recursive: true });
116
+
117
+ const srcDir = path.join(outputDir, "src");
118
+ await fs.mkdir(srcDir, { recursive: true });
119
+
120
+ const componentsDir = path.join(srcDir, "components");
121
+ await fs.mkdir(componentsDir, { recursive: true });
122
+
123
+ return { outputDir, srcDir, componentsDir };
124
+ }
125
+
126
+ // ============================================================================
127
+ // UTILITY FUNCTIONS
128
+ // ============================================================================
129
+
130
+ /**
131
+ * Convert component name to PascalCase
132
+ * @param {string} name - Component name (kebab-case)
133
+ * @returns {string} PascalCase name
134
+ */
135
+ function toPascalCase(name) {
136
+ return name
137
+ .split("-")
138
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
139
+ .join("");
140
+ }
141
+
142
+ /**
143
+ * Resolve color scheme to actual colors
144
+ * @param {Object} options - Color options
145
+ * @returns {Object} Resolved colors
146
+ */
147
+ function resolveColors(options) {
148
+ const { colorScheme, primaryColor, secondaryColor } = options;
149
+
150
+ let finalPrimaryColor = primaryColor || DEFAULT_PRIMARY_COLOR;
151
+ let finalSecondaryColor = secondaryColor || DEFAULT_SECONDARY_COLOR;
152
+
153
+ if (colorScheme && COLOR_SCHEMES[colorScheme]) {
154
+ const scheme = COLOR_SCHEMES[colorScheme];
155
+ finalPrimaryColor = scheme.primary;
156
+ finalSecondaryColor = scheme.secondary;
157
+ }
158
+
159
+ return { finalPrimaryColor, finalSecondaryColor };
160
+ }
161
+
162
+ // ============================================================================
163
+ // EXPORTS
164
+ // ============================================================================
165
+
166
+ export {
167
+ readComponentFiles,
168
+ writeComponentFiles,
169
+ createReactProjectStructure,
170
+ toPascalCase,
171
+ resolveColors,
172
+ };
@@ -0,0 +1,208 @@
1
+ import { promises as fs } from "fs";
2
+ import path from "path";
3
+ import { sanitizeFilename } from "./generators/validation.js";
4
+ import {
5
+ readComponentFiles,
6
+ writeComponentFiles,
7
+ createReactProjectStructure,
8
+ toPascalCase,
9
+ resolveColors,
10
+ } from "./react-file-operations.js";
11
+ import {
12
+ generateAppJsx,
13
+ generateIndexJs,
14
+ generateIndexHtml,
15
+ generatePackageJson,
16
+ generateGitignore,
17
+ generateViteConfig,
18
+ generateReadme,
19
+ } from "./react-templates.js";
20
+
21
+ // ============================================================================
22
+ // CONSTANTS
23
+ // ============================================================================
24
+
25
+ // Valid React components
26
+ export const VALID_REACT_COMPONENTS = [
27
+ "button",
28
+ "card",
29
+ "counter",
30
+ "form",
31
+ "modal",
32
+ "todo-list",
33
+ ];
34
+
35
+ // ============================================================================
36
+ // VALIDATORS
37
+ // ============================================================================
38
+
39
+ /**
40
+ * Validate React component name
41
+ * @param {string} component - Component name to validate
42
+ * @throws {Error} If component is invalid
43
+ */
44
+ function validateComponent(component) {
45
+ if (!VALID_REACT_COMPONENTS.includes(component)) {
46
+ throw new Error(
47
+ `Invalid React component: ${component}. Must be one of: ${VALID_REACT_COMPONENTS.join(", ")}`
48
+ );
49
+ }
50
+ }
51
+
52
+ // ============================================================================
53
+ // MAIN EXPORT FUNCTIONS
54
+ // ============================================================================
55
+
56
+ /**
57
+ * Generate React component files
58
+ * @param {Object} options - Generation options
59
+ * @returns {Promise<string>} Path to generated directory
60
+ */
61
+ async function generateReactTemplate(options) {
62
+ const {
63
+ component,
64
+ name,
65
+ colorScheme,
66
+ primaryColor,
67
+ secondaryColor,
68
+ darkMode,
69
+ } = options;
70
+
71
+ // Resolve colors
72
+ const { finalPrimaryColor, finalSecondaryColor } = resolveColors({
73
+ colorScheme,
74
+ primaryColor,
75
+ secondaryColor,
76
+ });
77
+
78
+ // Security: Validate component name
79
+ validateComponent(component);
80
+
81
+ // Security: Sanitize name to prevent path traversal
82
+ const safeName = sanitizeFilename(name);
83
+ if (!safeName || safeName.length === 0) {
84
+ throw new Error("Invalid name provided");
85
+ }
86
+
87
+ // Create project structure
88
+ const outputDir = path.join(process.cwd(), safeName);
89
+ const { srcDir, componentsDir } = await createReactProjectStructure(outputDir);
90
+
91
+ // Get component name in PascalCase
92
+ const componentName = toPascalCase(component);
93
+ const componentDir = path.join(componentsDir, componentName);
94
+ await fs.mkdir(componentDir, { recursive: true });
95
+
96
+ // Read and process component files
97
+ const { jsxContent, cssContent } = await readComponentFiles(
98
+ component,
99
+ componentName,
100
+ finalPrimaryColor,
101
+ finalSecondaryColor
102
+ );
103
+
104
+ // Write component files
105
+ await writeComponentFiles(componentDir, componentName, jsxContent, cssContent);
106
+
107
+ // Create App.jsx
108
+ const appContent = generateAppJsx(componentName, component);
109
+ await fs.writeFile(path.join(srcDir, "App.jsx"), appContent, "utf-8");
110
+
111
+ // Create index.jsx
112
+ const indexContent = generateIndexJs();
113
+ await fs.writeFile(path.join(srcDir, "index.jsx"), indexContent, "utf-8");
114
+
115
+ // Create index.html
116
+ const htmlContent = generateIndexHtml(safeName);
117
+ await fs.writeFile(path.join(outputDir, "index.html"), htmlContent, "utf-8");
118
+
119
+ // Create package.json
120
+ const packageJson = generatePackageJson(safeName);
121
+ await fs.writeFile(
122
+ path.join(outputDir, "package.json"),
123
+ JSON.stringify(packageJson, null, 2),
124
+ "utf-8"
125
+ );
126
+
127
+ // Create .gitignore
128
+ const gitignoreContent = generateGitignore();
129
+ await fs.writeFile(path.join(outputDir, ".gitignore"), gitignoreContent, "utf-8");
130
+
131
+ // Create vite.config.js
132
+ const viteConfig = generateViteConfig();
133
+ await fs.writeFile(path.join(outputDir, "vite.config.js"), viteConfig, "utf-8");
134
+
135
+ // Create README.md
136
+ const readmeContent = generateReadme(safeName, componentName);
137
+ await fs.writeFile(path.join(outputDir, "README.md"), readmeContent, "utf-8");
138
+
139
+ return outputDir;
140
+ }
141
+
142
+ /**
143
+ * Add React component only (without full project structure)
144
+ * @param {Object} options - Generation options
145
+ * @returns {Promise<string>} Path to generated component directory
146
+ */
147
+ async function addReactComponentOnly(options) {
148
+ const {
149
+ component,
150
+ outputDir = process.cwd(),
151
+ colorScheme,
152
+ primaryColor,
153
+ secondaryColor,
154
+ } = options;
155
+
156
+ // Resolve colors
157
+ const { finalPrimaryColor, finalSecondaryColor } = resolveColors({
158
+ colorScheme,
159
+ primaryColor,
160
+ secondaryColor,
161
+ });
162
+
163
+ // Security: Validate component name
164
+ validateComponent(component);
165
+
166
+ // Get component name in PascalCase
167
+ const componentName = toPascalCase(component);
168
+ const componentDir = path.join(outputDir, componentName);
169
+
170
+ // Check if component already exists
171
+ try {
172
+ await fs.access(componentDir);
173
+ throw new Error(`Component directory already exists: ${componentDir}`);
174
+ } catch (error) {
175
+ if (error.code !== "ENOENT") {
176
+ throw error;
177
+ }
178
+ }
179
+
180
+ await fs.mkdir(componentDir, { recursive: true });
181
+
182
+ // Read and process component files
183
+ const { jsxContent, cssContent } = await readComponentFiles(
184
+ component,
185
+ componentName,
186
+ finalPrimaryColor,
187
+ finalSecondaryColor
188
+ );
189
+
190
+ // Write component files
191
+ await writeComponentFiles(componentDir, componentName, jsxContent, cssContent);
192
+
193
+ console.log(`✓ Created ${componentName} component in ${componentDir}`);
194
+ console.log(`\nFiles created:`);
195
+ console.log(` - ${componentName}.jsx`);
196
+ console.log(` - ${componentName}.css`);
197
+ console.log(`\nUsage example:`);
198
+ console.log(` import ${componentName} from './${componentName}/${componentName}';`);
199
+ console.log(` import './${componentName}/${componentName}.css';`);
200
+
201
+ return componentDir;
202
+ }
203
+
204
+ // ============================================================================
205
+ // EXPORTS
206
+ // ============================================================================
207
+
208
+ export { generateReactTemplate, addReactComponentOnly };