real-prototypes-skill 0.1.1 → 0.1.3
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/.claude/skills/real-prototypes-skill/SKILL.md +212 -16
- package/.claude/skills/real-prototypes-skill/cli.js +523 -17
- package/.claude/skills/real-prototypes-skill/scripts/detect-prototype.js +652 -0
- package/.claude/skills/real-prototypes-skill/scripts/extract-components.js +731 -0
- package/.claude/skills/real-prototypes-skill/scripts/extract-css.js +557 -0
- package/.claude/skills/real-prototypes-skill/scripts/generate-plan.js +744 -0
- package/.claude/skills/real-prototypes-skill/scripts/html-to-react.js +645 -0
- package/.claude/skills/real-prototypes-skill/scripts/inject-component.js +604 -0
- package/.claude/skills/real-prototypes-skill/scripts/project-structure.js +457 -0
- package/.claude/skills/real-prototypes-skill/scripts/visual-diff.js +474 -0
- package/.claude/skills/real-prototypes-skill/validation/color-validator.js +496 -0
- package/bin/cli.js +66 -15
- package/package.json +4 -1
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CSS Extraction Module
|
|
5
|
+
*
|
|
6
|
+
* Extracts styles from captured HTML files to understand the
|
|
7
|
+
* styling approach used by the original platform.
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Parses <style> tags from HTML
|
|
11
|
+
* - Extracts inline styles from elements
|
|
12
|
+
* - Identifies styling paradigm (CSS modules, Tailwind, SLDS, etc.)
|
|
13
|
+
* - Generates CSS module files
|
|
14
|
+
* - Maps CSS classes to their definitions
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* node extract-css.js --project <name> --page <page>
|
|
18
|
+
* node extract-css.js --input ./html/page.html --output ./styles
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const { JSDOM } = require('jsdom');
|
|
24
|
+
|
|
25
|
+
// Common CSS frameworks/libraries patterns
|
|
26
|
+
const FRAMEWORK_PATTERNS = {
|
|
27
|
+
tailwind: {
|
|
28
|
+
patterns: [
|
|
29
|
+
/\b(flex|grid|block|inline|hidden)\b/,
|
|
30
|
+
/\b(p|m|w|h|gap|space)-(\d+|auto|full|screen|px)\b/,
|
|
31
|
+
/\b(bg|text|border)-(gray|blue|red|green|yellow|purple|pink|indigo|white|black|transparent)-?\d*/,
|
|
32
|
+
/\btext-(xs|sm|base|lg|xl|2xl|3xl)\b/,
|
|
33
|
+
/\b(rounded|shadow|opacity|transition|duration)-?\w*/
|
|
34
|
+
],
|
|
35
|
+
confidence: 0
|
|
36
|
+
},
|
|
37
|
+
slds: {
|
|
38
|
+
patterns: [
|
|
39
|
+
/\bslds-[\w-]+/,
|
|
40
|
+
/\bsf-[\w-]+/
|
|
41
|
+
],
|
|
42
|
+
confidence: 0
|
|
43
|
+
},
|
|
44
|
+
bootstrap: {
|
|
45
|
+
patterns: [
|
|
46
|
+
/\bbtn-[\w-]+/,
|
|
47
|
+
/\bcol-[\w-]+/,
|
|
48
|
+
/\brow\b/,
|
|
49
|
+
/\bcontainer(-fluid)?\b/,
|
|
50
|
+
/\bcard-[\w-]+/,
|
|
51
|
+
/\bform-[\w-]+/
|
|
52
|
+
],
|
|
53
|
+
confidence: 0
|
|
54
|
+
},
|
|
55
|
+
materialUI: {
|
|
56
|
+
patterns: [
|
|
57
|
+
/\bMui[\w-]+/,
|
|
58
|
+
/\bmdc-[\w-]+/
|
|
59
|
+
],
|
|
60
|
+
confidence: 0
|
|
61
|
+
},
|
|
62
|
+
antd: {
|
|
63
|
+
patterns: [
|
|
64
|
+
/\bant-[\w-]+/
|
|
65
|
+
],
|
|
66
|
+
confidence: 0
|
|
67
|
+
},
|
|
68
|
+
custom: {
|
|
69
|
+
patterns: [],
|
|
70
|
+
confidence: 0
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
class CSSExtractor {
|
|
75
|
+
constructor(options = {}) {
|
|
76
|
+
this.options = {
|
|
77
|
+
generateModules: true,
|
|
78
|
+
analyzeFramework: true,
|
|
79
|
+
extractInline: true,
|
|
80
|
+
...options
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
this.dom = null;
|
|
84
|
+
this.document = null;
|
|
85
|
+
this.styleSheets = [];
|
|
86
|
+
this.inlineStyles = [];
|
|
87
|
+
this.classUsage = new Map();
|
|
88
|
+
this.detectedFramework = null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Load HTML from file
|
|
93
|
+
*/
|
|
94
|
+
loadFromFile(htmlPath) {
|
|
95
|
+
if (!fs.existsSync(htmlPath)) {
|
|
96
|
+
throw new Error(`HTML file not found: ${htmlPath}`);
|
|
97
|
+
}
|
|
98
|
+
const html = fs.readFileSync(htmlPath, 'utf-8');
|
|
99
|
+
return this.loadFromString(html);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Load HTML from string
|
|
104
|
+
*/
|
|
105
|
+
loadFromString(html) {
|
|
106
|
+
this.dom = new JSDOM(html);
|
|
107
|
+
this.document = this.dom.window.document;
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Extract all <style> tags
|
|
113
|
+
*/
|
|
114
|
+
extractStyleTags() {
|
|
115
|
+
const styleTags = this.document.querySelectorAll('style');
|
|
116
|
+
this.styleSheets = [];
|
|
117
|
+
|
|
118
|
+
for (const style of styleTags) {
|
|
119
|
+
const css = style.textContent.trim();
|
|
120
|
+
if (css) {
|
|
121
|
+
this.styleSheets.push({
|
|
122
|
+
type: 'embedded',
|
|
123
|
+
content: css,
|
|
124
|
+
rules: this.parseCSS(css)
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return this.styleSheets;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Parse CSS string into rules
|
|
134
|
+
*/
|
|
135
|
+
parseCSS(css) {
|
|
136
|
+
const rules = [];
|
|
137
|
+
|
|
138
|
+
// Remove comments
|
|
139
|
+
css = css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
140
|
+
|
|
141
|
+
// Simple regex-based parser (not comprehensive but works for most cases)
|
|
142
|
+
const rulePattern = /([^{}]+)\{([^{}]+)\}/g;
|
|
143
|
+
let match;
|
|
144
|
+
|
|
145
|
+
while ((match = rulePattern.exec(css)) !== null) {
|
|
146
|
+
const selector = match[1].trim();
|
|
147
|
+
const declarations = match[2].trim();
|
|
148
|
+
|
|
149
|
+
const properties = {};
|
|
150
|
+
const propPattern = /([\w-]+)\s*:\s*([^;]+)/g;
|
|
151
|
+
let propMatch;
|
|
152
|
+
|
|
153
|
+
while ((propMatch = propPattern.exec(declarations)) !== null) {
|
|
154
|
+
properties[propMatch[1].trim()] = propMatch[2].trim();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
rules.push({
|
|
158
|
+
selector,
|
|
159
|
+
properties,
|
|
160
|
+
raw: `${selector} { ${declarations} }`
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return rules;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Extract inline styles from all elements
|
|
169
|
+
*/
|
|
170
|
+
extractInlineStyles() {
|
|
171
|
+
const elements = this.document.querySelectorAll('[style]');
|
|
172
|
+
this.inlineStyles = [];
|
|
173
|
+
|
|
174
|
+
for (const el of elements) {
|
|
175
|
+
const styleAttr = el.getAttribute('style');
|
|
176
|
+
if (styleAttr) {
|
|
177
|
+
const properties = this.parseInlineStyle(styleAttr);
|
|
178
|
+
const tagName = el.tagName.toLowerCase();
|
|
179
|
+
const className = el.className || '';
|
|
180
|
+
const id = el.id || '';
|
|
181
|
+
|
|
182
|
+
this.inlineStyles.push({
|
|
183
|
+
element: tagName,
|
|
184
|
+
className,
|
|
185
|
+
id,
|
|
186
|
+
style: styleAttr,
|
|
187
|
+
properties,
|
|
188
|
+
selector: this.generateSelector(el)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return this.inlineStyles;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Parse inline style string to object
|
|
198
|
+
*/
|
|
199
|
+
parseInlineStyle(styleStr) {
|
|
200
|
+
const properties = {};
|
|
201
|
+
const rules = styleStr.split(';').filter(Boolean);
|
|
202
|
+
|
|
203
|
+
for (const rule of rules) {
|
|
204
|
+
const [property, value] = rule.split(':').map(s => s.trim());
|
|
205
|
+
if (property && value) {
|
|
206
|
+
properties[property] = value;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return properties;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Generate CSS selector for element
|
|
215
|
+
*/
|
|
216
|
+
generateSelector(el) {
|
|
217
|
+
const tagName = el.tagName.toLowerCase();
|
|
218
|
+
|
|
219
|
+
if (el.id) {
|
|
220
|
+
return `#${el.id}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (el.className && typeof el.className === 'string') {
|
|
224
|
+
const firstClass = el.className.split(' ')[0];
|
|
225
|
+
return `.${firstClass}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return tagName;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Analyze class usage across all elements
|
|
233
|
+
*/
|
|
234
|
+
analyzeClassUsage() {
|
|
235
|
+
const elements = this.document.querySelectorAll('[class]');
|
|
236
|
+
|
|
237
|
+
for (const el of elements) {
|
|
238
|
+
const className = el.className;
|
|
239
|
+
if (typeof className !== 'string') continue;
|
|
240
|
+
|
|
241
|
+
const classes = className.split(/\s+/).filter(Boolean);
|
|
242
|
+
|
|
243
|
+
for (const cls of classes) {
|
|
244
|
+
if (!this.classUsage.has(cls)) {
|
|
245
|
+
this.classUsage.set(cls, { count: 0, elements: [] });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const usage = this.classUsage.get(cls);
|
|
249
|
+
usage.count++;
|
|
250
|
+
if (usage.elements.length < 3) {
|
|
251
|
+
usage.elements.push(el.tagName.toLowerCase());
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return this.classUsage;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Detect CSS framework/library used
|
|
261
|
+
*/
|
|
262
|
+
detectFramework() {
|
|
263
|
+
this.analyzeClassUsage();
|
|
264
|
+
|
|
265
|
+
const allClasses = Array.from(this.classUsage.keys()).join(' ');
|
|
266
|
+
|
|
267
|
+
for (const [name, config] of Object.entries(FRAMEWORK_PATTERNS)) {
|
|
268
|
+
let matches = 0;
|
|
269
|
+
for (const pattern of config.patterns) {
|
|
270
|
+
const matchResult = allClasses.match(pattern);
|
|
271
|
+
if (matchResult) {
|
|
272
|
+
matches += matchResult.length;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
config.confidence = matches;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Find framework with highest confidence
|
|
279
|
+
let bestFramework = 'custom';
|
|
280
|
+
let bestConfidence = 0;
|
|
281
|
+
|
|
282
|
+
for (const [name, config] of Object.entries(FRAMEWORK_PATTERNS)) {
|
|
283
|
+
if (config.confidence > bestConfidence) {
|
|
284
|
+
bestFramework = name;
|
|
285
|
+
bestConfidence = config.confidence;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this.detectedFramework = {
|
|
290
|
+
name: bestFramework,
|
|
291
|
+
confidence: bestConfidence,
|
|
292
|
+
allScores: Object.fromEntries(
|
|
293
|
+
Object.entries(FRAMEWORK_PATTERNS).map(([k, v]) => [k, v.confidence])
|
|
294
|
+
)
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
return this.detectedFramework;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Generate CSS module from extracted styles
|
|
302
|
+
*/
|
|
303
|
+
generateCSSModule() {
|
|
304
|
+
const moduleContent = [];
|
|
305
|
+
|
|
306
|
+
// Add extracted stylesheet rules
|
|
307
|
+
for (const sheet of this.styleSheets) {
|
|
308
|
+
for (const rule of sheet.rules) {
|
|
309
|
+
moduleContent.push(rule.raw);
|
|
310
|
+
moduleContent.push('');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Add inline styles as classes
|
|
315
|
+
if (this.inlineStyles.length > 0) {
|
|
316
|
+
moduleContent.push('/* Inline styles extracted as classes */');
|
|
317
|
+
|
|
318
|
+
for (let i = 0; i < this.inlineStyles.length; i++) {
|
|
319
|
+
const style = this.inlineStyles[i];
|
|
320
|
+
const className = style.className ?
|
|
321
|
+
style.className.split(' ')[0] :
|
|
322
|
+
`inline-${i}`;
|
|
323
|
+
|
|
324
|
+
const declarations = Object.entries(style.properties)
|
|
325
|
+
.map(([prop, val]) => ` ${prop}: ${val};`)
|
|
326
|
+
.join('\n');
|
|
327
|
+
|
|
328
|
+
moduleContent.push(`.${className} {`);
|
|
329
|
+
moduleContent.push(declarations);
|
|
330
|
+
moduleContent.push('}');
|
|
331
|
+
moduleContent.push('');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return moduleContent.join('\n');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get most used classes
|
|
340
|
+
*/
|
|
341
|
+
getMostUsedClasses(limit = 20) {
|
|
342
|
+
const sorted = Array.from(this.classUsage.entries())
|
|
343
|
+
.sort((a, b) => b[1].count - a[1].count)
|
|
344
|
+
.slice(0, limit);
|
|
345
|
+
|
|
346
|
+
return sorted.map(([cls, usage]) => ({
|
|
347
|
+
class: cls,
|
|
348
|
+
count: usage.count,
|
|
349
|
+
elements: usage.elements
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Get styling recommendation
|
|
355
|
+
*/
|
|
356
|
+
getStylingRecommendation() {
|
|
357
|
+
const framework = this.detectFramework();
|
|
358
|
+
|
|
359
|
+
const recommendations = {
|
|
360
|
+
tailwind: 'Use Tailwind utility classes. Match exact class combinations.',
|
|
361
|
+
slds: 'Use Salesforce Lightning Design System (SLDS) classes. Import @salesforce-ux/design-system.',
|
|
362
|
+
bootstrap: 'Use Bootstrap classes. Import bootstrap CSS.',
|
|
363
|
+
materialUI: 'Use Material-UI components and styling. Import @mui/material.',
|
|
364
|
+
antd: 'Use Ant Design components. Import antd.',
|
|
365
|
+
custom: 'Use CSS modules or inline styles to match the custom styling.'
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
framework: framework.name,
|
|
370
|
+
confidence: framework.confidence,
|
|
371
|
+
recommendation: recommendations[framework.name] || recommendations.custom,
|
|
372
|
+
approach: framework.confidence > 10 ? 'framework' : 'inline-styles'
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Extract all data
|
|
378
|
+
*/
|
|
379
|
+
extract() {
|
|
380
|
+
this.extractStyleTags();
|
|
381
|
+
this.extractInlineStyles();
|
|
382
|
+
this.analyzeClassUsage();
|
|
383
|
+
this.detectFramework();
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
styleSheets: this.styleSheets,
|
|
387
|
+
inlineStyles: this.inlineStyles,
|
|
388
|
+
classUsage: Object.fromEntries(this.classUsage),
|
|
389
|
+
framework: this.detectedFramework,
|
|
390
|
+
recommendation: this.getStylingRecommendation(),
|
|
391
|
+
mostUsedClasses: this.getMostUsedClasses()
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Format results for CLI output
|
|
397
|
+
*/
|
|
398
|
+
formatResults() {
|
|
399
|
+
const lines = [];
|
|
400
|
+
const data = this.extract();
|
|
401
|
+
|
|
402
|
+
lines.push('\x1b[1mCSS Extraction Results\x1b[0m\n');
|
|
403
|
+
|
|
404
|
+
// Framework detection
|
|
405
|
+
lines.push('\x1b[1mDetected Framework:\x1b[0m');
|
|
406
|
+
lines.push(` ${data.framework.name} (confidence: ${data.framework.confidence})`);
|
|
407
|
+
lines.push('');
|
|
408
|
+
|
|
409
|
+
// Recommendation
|
|
410
|
+
lines.push('\x1b[1mRecommendation:\x1b[0m');
|
|
411
|
+
lines.push(` ${data.recommendation.recommendation}`);
|
|
412
|
+
lines.push(` Approach: ${data.recommendation.approach}`);
|
|
413
|
+
lines.push('');
|
|
414
|
+
|
|
415
|
+
// Style sheets
|
|
416
|
+
lines.push(`\x1b[1mEmbedded Style Tags:\x1b[0m ${data.styleSheets.length}`);
|
|
417
|
+
for (const sheet of data.styleSheets.slice(0, 3)) {
|
|
418
|
+
lines.push(` ${sheet.rules.length} rules`);
|
|
419
|
+
}
|
|
420
|
+
lines.push('');
|
|
421
|
+
|
|
422
|
+
// Inline styles
|
|
423
|
+
lines.push(`\x1b[1mInline Styles:\x1b[0m ${data.inlineStyles.length}`);
|
|
424
|
+
lines.push('');
|
|
425
|
+
|
|
426
|
+
// Most used classes
|
|
427
|
+
lines.push('\x1b[1mMost Used Classes:\x1b[0m');
|
|
428
|
+
for (const cls of data.mostUsedClasses.slice(0, 10)) {
|
|
429
|
+
lines.push(` ${cls.class} (${cls.count}x)`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return lines.join('\n');
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Extract CSS from HTML file
|
|
438
|
+
*/
|
|
439
|
+
function extractCSS(htmlPath) {
|
|
440
|
+
const extractor = new CSSExtractor();
|
|
441
|
+
extractor.loadFromFile(htmlPath);
|
|
442
|
+
return extractor.extract();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Write CSS module to file
|
|
447
|
+
*/
|
|
448
|
+
function writeCSSModule(extractor, outputPath) {
|
|
449
|
+
const content = extractor.generateCSSModule();
|
|
450
|
+
const dir = path.dirname(outputPath);
|
|
451
|
+
|
|
452
|
+
if (!fs.existsSync(dir)) {
|
|
453
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
fs.writeFileSync(outputPath, content);
|
|
457
|
+
return outputPath;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// CLI execution
|
|
461
|
+
if (require.main === module) {
|
|
462
|
+
const args = process.argv.slice(2);
|
|
463
|
+
let inputPath = null;
|
|
464
|
+
let outputPath = null;
|
|
465
|
+
let projectName = null;
|
|
466
|
+
let pageName = null;
|
|
467
|
+
let jsonOutput = false;
|
|
468
|
+
|
|
469
|
+
for (let i = 0; i < args.length; i++) {
|
|
470
|
+
switch (args[i]) {
|
|
471
|
+
case '--input':
|
|
472
|
+
case '-i':
|
|
473
|
+
inputPath = args[++i];
|
|
474
|
+
break;
|
|
475
|
+
case '--output':
|
|
476
|
+
case '-o':
|
|
477
|
+
outputPath = args[++i];
|
|
478
|
+
break;
|
|
479
|
+
case '--project':
|
|
480
|
+
projectName = args[++i];
|
|
481
|
+
break;
|
|
482
|
+
case '--page':
|
|
483
|
+
pageName = args[++i];
|
|
484
|
+
break;
|
|
485
|
+
case '--json':
|
|
486
|
+
jsonOutput = true;
|
|
487
|
+
break;
|
|
488
|
+
case '--help':
|
|
489
|
+
case '-h':
|
|
490
|
+
console.log(`
|
|
491
|
+
Usage: node extract-css.js [options]
|
|
492
|
+
|
|
493
|
+
Options:
|
|
494
|
+
--input, -i <path> Path to HTML file
|
|
495
|
+
--output, -o <path> Output path for CSS module
|
|
496
|
+
--project <name> Project name
|
|
497
|
+
--page <name> Page name (used with --project)
|
|
498
|
+
--json Output as JSON
|
|
499
|
+
--help, -h Show this help
|
|
500
|
+
|
|
501
|
+
Examples:
|
|
502
|
+
node extract-css.js --input ./html/page.html
|
|
503
|
+
node extract-css.js --project my-app --page homepage
|
|
504
|
+
node extract-css.js -i ./page.html -o ./styles/page.module.css
|
|
505
|
+
`);
|
|
506
|
+
process.exit(0);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Handle project-based paths
|
|
511
|
+
if (projectName) {
|
|
512
|
+
const SKILL_DIR = path.dirname(__dirname);
|
|
513
|
+
const PROJECTS_DIR = path.resolve(SKILL_DIR, '../../../projects');
|
|
514
|
+
const projectDir = path.join(PROJECTS_DIR, projectName);
|
|
515
|
+
|
|
516
|
+
if (pageName) {
|
|
517
|
+
inputPath = path.join(projectDir, 'references', 'html', `${pageName}.html`);
|
|
518
|
+
outputPath = outputPath || path.join(projectDir, 'prototype', 'src', 'styles', `${pageName}.module.css`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (!inputPath) {
|
|
523
|
+
console.error('\x1b[31mError:\x1b[0m --input is required');
|
|
524
|
+
process.exit(1);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
const extractor = new CSSExtractor();
|
|
529
|
+
extractor.loadFromFile(inputPath);
|
|
530
|
+
|
|
531
|
+
if (jsonOutput) {
|
|
532
|
+
console.log(JSON.stringify(extractor.extract(), null, 2));
|
|
533
|
+
} else {
|
|
534
|
+
console.log(`\nInput: ${inputPath}`);
|
|
535
|
+
if (outputPath) {
|
|
536
|
+
console.log(`Output: ${outputPath}`);
|
|
537
|
+
}
|
|
538
|
+
console.log('');
|
|
539
|
+
console.log(extractor.formatResults());
|
|
540
|
+
|
|
541
|
+
if (outputPath) {
|
|
542
|
+
writeCSSModule(extractor, outputPath);
|
|
543
|
+
console.log(`\n\x1b[32m✓ CSS module written to: ${outputPath}\x1b[0m`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
} catch (error) {
|
|
548
|
+
console.error(`\x1b[31mError:\x1b[0m ${error.message}`);
|
|
549
|
+
process.exit(1);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
module.exports = {
|
|
554
|
+
CSSExtractor,
|
|
555
|
+
extractCSS,
|
|
556
|
+
writeCSSModule
|
|
557
|
+
};
|