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.
- 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/breakout/index.html +1 -1
- package/templates/connect-four/index.html +1 -1
- package/templates/dice-game/index.html +1 -1
- package/templates/flappy-bird/index.html +1 -1
- package/templates/pong/index.html +1 -1
- package/templates/skeleton/index.html +4 -4
- package/templates/slot-machine/index.html +1 -1
- package/templates/tetris/index.html +1 -1
- 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,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 };
|