real-prototypes-skill 0.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/.claude/skills/agent-browser-skill/SKILL.md +252 -0
- package/.claude/skills/real-prototypes-skill/.gitignore +188 -0
- package/.claude/skills/real-prototypes-skill/ACCESSIBILITY.md +668 -0
- package/.claude/skills/real-prototypes-skill/INSTALL.md +259 -0
- package/.claude/skills/real-prototypes-skill/LICENSE +21 -0
- package/.claude/skills/real-prototypes-skill/PUBLISH.md +310 -0
- package/.claude/skills/real-prototypes-skill/QUICKSTART.md +240 -0
- package/.claude/skills/real-prototypes-skill/README.md +442 -0
- package/.claude/skills/real-prototypes-skill/SKILL.md +375 -0
- package/.claude/skills/real-prototypes-skill/capture/capture-engine.js +1153 -0
- package/.claude/skills/real-prototypes-skill/capture/config.schema.json +170 -0
- package/.claude/skills/real-prototypes-skill/cli.js +596 -0
- package/.claude/skills/real-prototypes-skill/docs/TROUBLESHOOTING.md +278 -0
- package/.claude/skills/real-prototypes-skill/docs/schemas/capture-config.md +167 -0
- package/.claude/skills/real-prototypes-skill/docs/schemas/design-tokens.md +183 -0
- package/.claude/skills/real-prototypes-skill/docs/schemas/manifest.md +169 -0
- package/.claude/skills/real-prototypes-skill/examples/CLAUDE.md.example +73 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/CLAUDE.md +136 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/FEATURES.md +222 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/README.md +82 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/design-tokens.json +87 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/homepage-viewport.png +0 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/prototype-chatbot-final.png +0 -0
- package/.claude/skills/real-prototypes-skill/examples/amazon-chatbot/references/screenshots/prototype-fullpage-v2.png +0 -0
- package/.claude/skills/real-prototypes-skill/references/accessibility-fixes.md +298 -0
- package/.claude/skills/real-prototypes-skill/references/accessibility-report.json +253 -0
- package/.claude/skills/real-prototypes-skill/scripts/CAPTURE-ENHANCEMENTS.md +344 -0
- package/.claude/skills/real-prototypes-skill/scripts/IMPLEMENTATION-SUMMARY.md +517 -0
- package/.claude/skills/real-prototypes-skill/scripts/QUICK-START.md +229 -0
- package/.claude/skills/real-prototypes-skill/scripts/QUICKSTART-layout-analysis.md +148 -0
- package/.claude/skills/real-prototypes-skill/scripts/README-analyze-layout.md +407 -0
- package/.claude/skills/real-prototypes-skill/scripts/analyze-layout.js +880 -0
- package/.claude/skills/real-prototypes-skill/scripts/capture-platform.js +203 -0
- package/.claude/skills/real-prototypes-skill/scripts/comprehensive-capture.js +597 -0
- package/.claude/skills/real-prototypes-skill/scripts/create-manifest.js +338 -0
- package/.claude/skills/real-prototypes-skill/scripts/enterprise-pipeline.js +428 -0
- package/.claude/skills/real-prototypes-skill/scripts/extract-tokens.js +468 -0
- package/.claude/skills/real-prototypes-skill/scripts/full-site-capture.js +738 -0
- package/.claude/skills/real-prototypes-skill/scripts/generate-tailwind-config.js +296 -0
- package/.claude/skills/real-prototypes-skill/scripts/integrate-accessibility.sh +161 -0
- package/.claude/skills/real-prototypes-skill/scripts/manifest-schema.json +302 -0
- package/.claude/skills/real-prototypes-skill/scripts/setup-prototype.sh +167 -0
- package/.claude/skills/real-prototypes-skill/scripts/test-analyze-layout.js +338 -0
- package/.claude/skills/real-prototypes-skill/scripts/test-validation.js +307 -0
- package/.claude/skills/real-prototypes-skill/scripts/validate-accessibility.js +598 -0
- package/.claude/skills/real-prototypes-skill/scripts/validate-manifest.js +499 -0
- package/.claude/skills/real-prototypes-skill/scripts/validate-output.js +361 -0
- package/.claude/skills/real-prototypes-skill/scripts/validate-prerequisites.js +319 -0
- package/.claude/skills/real-prototypes-skill/scripts/verify-layout-analysis.sh +77 -0
- package/.claude/skills/real-prototypes-skill/templates/dashboard-widget.tsx.template +91 -0
- package/.claude/skills/real-prototypes-skill/templates/data-table.tsx.template +193 -0
- package/.claude/skills/real-prototypes-skill/templates/form-section.tsx.template +250 -0
- package/.claude/skills/real-prototypes-skill/templates/modal-dialog.tsx.template +239 -0
- package/.claude/skills/real-prototypes-skill/templates/nav-item.tsx.template +265 -0
- package/.claude/skills/real-prototypes-skill/validation/validation-engine.js +559 -0
- package/.env.example +74 -0
- package/LICENSE +21 -0
- package/README.md +444 -0
- package/bin/cli.js +319 -0
- package/package.json +59 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validation Engine
|
|
5
|
+
*
|
|
6
|
+
* Enterprise-grade validation for platform capture and prototype generation.
|
|
7
|
+
* Runs as gates before and after key operations.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
class ValidationEngine {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.referenceDir = options.referenceDir || './references';
|
|
16
|
+
this.prototypeDir = options.prototypeDir || './prototype';
|
|
17
|
+
this.errors = [];
|
|
18
|
+
this.warnings = [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
log(message, type = 'info') {
|
|
22
|
+
const colors = {
|
|
23
|
+
info: '\x1b[36m',
|
|
24
|
+
success: '\x1b[32m',
|
|
25
|
+
warning: '\x1b[33m',
|
|
26
|
+
error: '\x1b[31m',
|
|
27
|
+
reset: '\x1b[0m'
|
|
28
|
+
};
|
|
29
|
+
const prefix = { info: 'ℹ', success: '✓', warning: '⚠', error: '✗' }[type] || '→';
|
|
30
|
+
console.log(`${colors[type] || ''}${prefix} ${message}${colors.reset}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Pre-Capture Validation
|
|
35
|
+
* Validates configuration before starting capture
|
|
36
|
+
*/
|
|
37
|
+
validatePreCapture(config) {
|
|
38
|
+
this.log('Running pre-capture validation...', 'info');
|
|
39
|
+
const checks = [];
|
|
40
|
+
|
|
41
|
+
// Check platform URL
|
|
42
|
+
checks.push({
|
|
43
|
+
name: 'Platform URL valid',
|
|
44
|
+
passed: this.isValidUrl(config.platform?.baseUrl),
|
|
45
|
+
message: config.platform?.baseUrl ? `URL: ${config.platform.baseUrl}` : 'Missing platform.baseUrl'
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Check credentials available
|
|
49
|
+
if (config.auth?.type === 'form') {
|
|
50
|
+
const hasEmail = process.env.PLATFORM_EMAIL || config.auth?.credentials?.email;
|
|
51
|
+
const hasPassword = process.env.PLATFORM_PASSWORD || config.auth?.credentials?.password;
|
|
52
|
+
checks.push({
|
|
53
|
+
name: 'Credentials available',
|
|
54
|
+
passed: !!(hasEmail && hasPassword),
|
|
55
|
+
message: hasEmail && hasPassword ? 'Credentials found' : 'Missing PLATFORM_EMAIL or PLATFORM_PASSWORD'
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check output directory writable
|
|
60
|
+
const outputDir = config.output?.directory || './references';
|
|
61
|
+
checks.push({
|
|
62
|
+
name: 'Output directory accessible',
|
|
63
|
+
passed: this.isDirectoryWritable(outputDir),
|
|
64
|
+
message: `Directory: ${outputDir}`
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return this.summarizeChecks('Pre-Capture', checks);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Post-Capture Validation
|
|
72
|
+
* Validates capture completeness
|
|
73
|
+
*/
|
|
74
|
+
validatePostCapture(config = {}) {
|
|
75
|
+
this.log('Running post-capture validation...', 'info');
|
|
76
|
+
const checks = [];
|
|
77
|
+
|
|
78
|
+
// Check manifest exists
|
|
79
|
+
const manifestPath = path.join(this.referenceDir, 'manifest.json');
|
|
80
|
+
const manifestExists = fs.existsSync(manifestPath);
|
|
81
|
+
checks.push({
|
|
82
|
+
name: 'Manifest exists',
|
|
83
|
+
passed: manifestExists,
|
|
84
|
+
message: manifestExists ? manifestPath : 'manifest.json not found'
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (!manifestExists) {
|
|
88
|
+
return this.summarizeChecks('Post-Capture', checks);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
92
|
+
|
|
93
|
+
// Check minimum pages
|
|
94
|
+
const minPages = config.validation?.minPages || 5;
|
|
95
|
+
checks.push({
|
|
96
|
+
name: 'Minimum pages captured',
|
|
97
|
+
passed: manifest.pages?.length >= minPages,
|
|
98
|
+
message: `${manifest.pages?.length || 0} pages (minimum: ${minPages})`
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Check screenshots exist
|
|
102
|
+
const missingScreenshots = [];
|
|
103
|
+
manifest.pages?.forEach(page => {
|
|
104
|
+
if (page.screenshot) {
|
|
105
|
+
const screenshotPath = path.join(this.referenceDir, page.screenshot);
|
|
106
|
+
if (!fs.existsSync(screenshotPath)) {
|
|
107
|
+
missingScreenshots.push(page.screenshot);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
checks.push({
|
|
112
|
+
name: 'All screenshots exist',
|
|
113
|
+
passed: missingScreenshots.length === 0,
|
|
114
|
+
message: missingScreenshots.length === 0
|
|
115
|
+
? `${manifest.pages?.length || 0} screenshots verified`
|
|
116
|
+
: `Missing: ${missingScreenshots.slice(0, 3).join(', ')}${missingScreenshots.length > 3 ? '...' : ''}`
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Check design tokens
|
|
120
|
+
const tokensPath = path.join(this.referenceDir, 'design-tokens.json');
|
|
121
|
+
const tokensExist = fs.existsSync(tokensPath);
|
|
122
|
+
checks.push({
|
|
123
|
+
name: 'Design tokens extracted',
|
|
124
|
+
passed: tokensExist,
|
|
125
|
+
message: tokensExist ? 'design-tokens.json found' : 'design-tokens.json missing'
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (tokensExist) {
|
|
129
|
+
const tokens = JSON.parse(fs.readFileSync(tokensPath, 'utf-8'));
|
|
130
|
+
const minColors = config.validation?.minColors || 10;
|
|
131
|
+
checks.push({
|
|
132
|
+
name: 'Sufficient colors extracted',
|
|
133
|
+
passed: tokens.totalColorsFound >= minColors,
|
|
134
|
+
message: `${tokens.totalColorsFound} colors (minimum: ${minColors})`
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Check primary color exists
|
|
138
|
+
checks.push({
|
|
139
|
+
name: 'Primary color identified',
|
|
140
|
+
passed: !!tokens.colors?.primary,
|
|
141
|
+
message: tokens.colors?.primary || 'Primary color not identified'
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Validate page entries have required fields
|
|
146
|
+
const pagesWithMissingNames = (manifest.pages || []).filter(p => !p.name);
|
|
147
|
+
if (pagesWithMissingNames.length > 0) {
|
|
148
|
+
checks.push({
|
|
149
|
+
name: 'Page entries have names',
|
|
150
|
+
passed: false,
|
|
151
|
+
message: `${pagesWithMissingNames.length} pages missing 'name' field. Check manifest.json structure.`
|
|
152
|
+
});
|
|
153
|
+
this.warnings.push(
|
|
154
|
+
'Pages missing name field - manifest.json may be malformed. ' +
|
|
155
|
+
'Each page entry requires: { name: "page-name", url: "/path", screenshot: "screenshots/file.png" }'
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check for common page patterns (safely handle missing names)
|
|
160
|
+
const pageNames = (manifest.pages || [])
|
|
161
|
+
.filter(p => p && p.name)
|
|
162
|
+
.map(p => p.name.toLowerCase());
|
|
163
|
+
|
|
164
|
+
// List pages should have detail pages
|
|
165
|
+
const listPages = pageNames.filter(n => n.includes('list') || n.includes('accounts') || n.includes('contacts'));
|
|
166
|
+
const detailPages = pageNames.filter(n => n.includes('detail'));
|
|
167
|
+
|
|
168
|
+
if (listPages.length > 0) {
|
|
169
|
+
checks.push({
|
|
170
|
+
name: 'List-Detail pattern complete',
|
|
171
|
+
passed: detailPages.length > 0,
|
|
172
|
+
message: detailPages.length > 0
|
|
173
|
+
? `${listPages.length} list pages, ${detailPages.length} detail pages`
|
|
174
|
+
: `${listPages.length} list pages found but no detail pages captured`
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Check for common pages
|
|
179
|
+
const commonPages = ['settings', 'search'];
|
|
180
|
+
commonPages.forEach(pageName => {
|
|
181
|
+
const found = pageNames.some(n => n.includes(pageName));
|
|
182
|
+
if (!found) {
|
|
183
|
+
this.warnings.push(`Common page '${pageName}' not found - may be missing`);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Check HTML files if configured
|
|
188
|
+
if (config.output?.html !== false) {
|
|
189
|
+
const htmlDir = path.join(this.referenceDir, 'html');
|
|
190
|
+
const htmlCount = fs.existsSync(htmlDir)
|
|
191
|
+
? fs.readdirSync(htmlDir).filter(f => f.endsWith('.html')).length
|
|
192
|
+
: 0;
|
|
193
|
+
checks.push({
|
|
194
|
+
name: 'HTML files captured',
|
|
195
|
+
passed: htmlCount > 0,
|
|
196
|
+
message: `${htmlCount} HTML files`
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return this.summarizeChecks('Post-Capture', checks);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Pre-Generation Validation
|
|
205
|
+
* Validates prerequisites before prototype generation
|
|
206
|
+
*/
|
|
207
|
+
validatePreGeneration() {
|
|
208
|
+
this.log('Running pre-generation validation...', 'info');
|
|
209
|
+
const checks = [];
|
|
210
|
+
|
|
211
|
+
// Check manifest
|
|
212
|
+
const manifestPath = path.join(this.referenceDir, 'manifest.json');
|
|
213
|
+
checks.push({
|
|
214
|
+
name: 'Manifest exists',
|
|
215
|
+
passed: fs.existsSync(manifestPath),
|
|
216
|
+
message: fs.existsSync(manifestPath) ? 'Found' : 'Missing - run capture first'
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Check design tokens
|
|
220
|
+
const tokensPath = path.join(this.referenceDir, 'design-tokens.json');
|
|
221
|
+
const tokensExist = fs.existsSync(tokensPath);
|
|
222
|
+
checks.push({
|
|
223
|
+
name: 'Design tokens exist',
|
|
224
|
+
passed: tokensExist,
|
|
225
|
+
message: tokensExist ? 'Found' : 'Missing - colors will be incorrect'
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
if (tokensExist) {
|
|
229
|
+
const tokens = JSON.parse(fs.readFileSync(tokensPath, 'utf-8'));
|
|
230
|
+
|
|
231
|
+
// Validate required color categories
|
|
232
|
+
const requiredColors = ['primary', 'text', 'background', 'border'];
|
|
233
|
+
const missingColors = requiredColors.filter(c => !tokens.colors?.[c]);
|
|
234
|
+
|
|
235
|
+
checks.push({
|
|
236
|
+
name: 'Required color categories',
|
|
237
|
+
passed: missingColors.length === 0,
|
|
238
|
+
message: missingColors.length === 0
|
|
239
|
+
? 'All required colors present'
|
|
240
|
+
: `Missing: ${missingColors.join(', ')}`
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Check screenshots directory
|
|
245
|
+
const screenshotsDir = path.join(this.referenceDir, 'screenshots');
|
|
246
|
+
const screenshotCount = fs.existsSync(screenshotsDir)
|
|
247
|
+
? fs.readdirSync(screenshotsDir).filter(f => f.endsWith('.png')).length
|
|
248
|
+
: 0;
|
|
249
|
+
|
|
250
|
+
checks.push({
|
|
251
|
+
name: 'Screenshots available',
|
|
252
|
+
passed: screenshotCount > 0,
|
|
253
|
+
message: `${screenshotCount} screenshots found`
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return this.summarizeChecks('Pre-Generation', checks);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Post-Generation Validation
|
|
261
|
+
* Validates generated prototype for accuracy
|
|
262
|
+
*/
|
|
263
|
+
validatePostGeneration() {
|
|
264
|
+
this.log('Running post-generation validation...', 'info');
|
|
265
|
+
const checks = [];
|
|
266
|
+
|
|
267
|
+
// Check prototype directory exists
|
|
268
|
+
checks.push({
|
|
269
|
+
name: 'Prototype directory exists',
|
|
270
|
+
passed: fs.existsSync(this.prototypeDir),
|
|
271
|
+
message: this.prototypeDir
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (!fs.existsSync(this.prototypeDir)) {
|
|
275
|
+
return this.summarizeChecks('Post-Generation', checks);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Check package.json
|
|
279
|
+
const packagePath = path.join(this.prototypeDir, 'package.json');
|
|
280
|
+
checks.push({
|
|
281
|
+
name: 'package.json exists',
|
|
282
|
+
passed: fs.existsSync(packagePath),
|
|
283
|
+
message: fs.existsSync(packagePath) ? 'Found' : 'Missing'
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Check Tailwind config
|
|
287
|
+
const tailwindPath = path.join(this.prototypeDir, 'tailwind.config.js');
|
|
288
|
+
const tailwindTsPath = path.join(this.prototypeDir, 'tailwind.config.ts');
|
|
289
|
+
checks.push({
|
|
290
|
+
name: 'Tailwind config exists',
|
|
291
|
+
passed: fs.existsSync(tailwindPath) || fs.existsSync(tailwindTsPath),
|
|
292
|
+
message: fs.existsSync(tailwindPath) || fs.existsSync(tailwindTsPath) ? 'Found' : 'Missing'
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Validate colors in generated code
|
|
296
|
+
const colorValidation = this.validateGeneratedColors();
|
|
297
|
+
checks.push({
|
|
298
|
+
name: 'Colors match design tokens',
|
|
299
|
+
passed: colorValidation.passed,
|
|
300
|
+
message: colorValidation.message
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Check for Tailwind default colors (should not be used)
|
|
304
|
+
const defaultColorCheck = this.checkForDefaultColors();
|
|
305
|
+
checks.push({
|
|
306
|
+
name: 'No Tailwind default colors',
|
|
307
|
+
passed: defaultColorCheck.passed,
|
|
308
|
+
message: defaultColorCheck.message
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Check component files exist
|
|
312
|
+
const componentsDir = path.join(this.prototypeDir, 'src', 'components');
|
|
313
|
+
const componentCount = fs.existsSync(componentsDir)
|
|
314
|
+
? fs.readdirSync(componentsDir).filter(f => f.endsWith('.tsx')).length
|
|
315
|
+
: 0;
|
|
316
|
+
|
|
317
|
+
checks.push({
|
|
318
|
+
name: 'Components generated',
|
|
319
|
+
passed: componentCount > 0,
|
|
320
|
+
message: `${componentCount} components`
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return this.summarizeChecks('Post-Generation', checks);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Validate that generated colors match design tokens
|
|
328
|
+
*/
|
|
329
|
+
validateGeneratedColors() {
|
|
330
|
+
const tokensPath = path.join(this.referenceDir, 'design-tokens.json');
|
|
331
|
+
if (!fs.existsSync(tokensPath)) {
|
|
332
|
+
return { passed: false, message: 'Design tokens not found' };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const tokens = JSON.parse(fs.readFileSync(tokensPath, 'utf-8'));
|
|
336
|
+
const allowedColors = new Set();
|
|
337
|
+
|
|
338
|
+
// Add all colors from tokens
|
|
339
|
+
const addColors = (obj) => {
|
|
340
|
+
Object.values(obj).forEach(value => {
|
|
341
|
+
if (typeof value === 'string' && value.startsWith('#')) {
|
|
342
|
+
allowedColors.add(value.toLowerCase());
|
|
343
|
+
} else if (typeof value === 'object') {
|
|
344
|
+
addColors(value);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
};
|
|
348
|
+
addColors(tokens.colors || {});
|
|
349
|
+
tokens.rawColors?.forEach(([color]) => allowedColors.add(color.toLowerCase()));
|
|
350
|
+
|
|
351
|
+
// Add common acceptable colors
|
|
352
|
+
['#fff', '#ffffff', '#000', '#000000', 'transparent', 'inherit', 'currentColor'].forEach(c =>
|
|
353
|
+
allowedColors.add(c.toLowerCase())
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
// Scan generated files for colors
|
|
357
|
+
const srcDir = path.join(this.prototypeDir, 'src');
|
|
358
|
+
if (!fs.existsSync(srcDir)) {
|
|
359
|
+
return { passed: false, message: 'Source directory not found' };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const invalidColors = new Set();
|
|
363
|
+
this.scanFilesForColors(srcDir, allowedColors, invalidColors);
|
|
364
|
+
|
|
365
|
+
if (invalidColors.size === 0) {
|
|
366
|
+
return { passed: true, message: 'All colors from design tokens' };
|
|
367
|
+
} else {
|
|
368
|
+
return {
|
|
369
|
+
passed: false,
|
|
370
|
+
message: `Invalid colors found: ${[...invalidColors].slice(0, 5).join(', ')}${invalidColors.size > 5 ? '...' : ''}`
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
scanFilesForColors(dir, allowedColors, invalidColors) {
|
|
376
|
+
const files = fs.readdirSync(dir);
|
|
377
|
+
|
|
378
|
+
for (const file of files) {
|
|
379
|
+
const filePath = path.join(dir, file);
|
|
380
|
+
const stat = fs.statSync(filePath);
|
|
381
|
+
|
|
382
|
+
if (stat.isDirectory()) {
|
|
383
|
+
this.scanFilesForColors(filePath, allowedColors, invalidColors);
|
|
384
|
+
} else if (file.endsWith('.tsx') || file.endsWith('.ts') || file.endsWith('.css')) {
|
|
385
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
386
|
+
|
|
387
|
+
// Find hex colors
|
|
388
|
+
const hexColors = content.match(/#[0-9a-fA-F]{3,8}\b/g) || [];
|
|
389
|
+
hexColors.forEach(color => {
|
|
390
|
+
const normalized = color.toLowerCase();
|
|
391
|
+
if (!allowedColors.has(normalized)) {
|
|
392
|
+
invalidColors.add(normalized);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Check for Tailwind default color classes
|
|
401
|
+
*/
|
|
402
|
+
checkForDefaultColors() {
|
|
403
|
+
const srcDir = path.join(this.prototypeDir, 'src');
|
|
404
|
+
if (!fs.existsSync(srcDir)) {
|
|
405
|
+
return { passed: true, message: 'Source directory not found' };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Tailwind default color patterns that shouldn't be used
|
|
409
|
+
const defaultColorPatterns = [
|
|
410
|
+
/\b(bg|text|border|ring)-(red|blue|green|yellow|purple|pink|indigo|gray|slate|zinc|neutral|stone|orange|amber|lime|emerald|teal|cyan|sky|violet|fuchsia|rose)-\d{2,3}\b/g
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
const foundDefaults = new Set();
|
|
414
|
+
this.scanFilesForPatterns(srcDir, defaultColorPatterns, foundDefaults);
|
|
415
|
+
|
|
416
|
+
if (foundDefaults.size === 0) {
|
|
417
|
+
return { passed: true, message: 'No default Tailwind colors used' };
|
|
418
|
+
} else {
|
|
419
|
+
return {
|
|
420
|
+
passed: false,
|
|
421
|
+
message: `Default colors found: ${[...foundDefaults].slice(0, 5).join(', ')}`
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
scanFilesForPatterns(dir, patterns, matches) {
|
|
427
|
+
const files = fs.readdirSync(dir);
|
|
428
|
+
|
|
429
|
+
for (const file of files) {
|
|
430
|
+
const filePath = path.join(dir, file);
|
|
431
|
+
const stat = fs.statSync(filePath);
|
|
432
|
+
|
|
433
|
+
if (stat.isDirectory()) {
|
|
434
|
+
this.scanFilesForPatterns(filePath, patterns, matches);
|
|
435
|
+
} else if (file.endsWith('.tsx') || file.endsWith('.ts')) {
|
|
436
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
437
|
+
|
|
438
|
+
patterns.forEach(pattern => {
|
|
439
|
+
const found = content.match(pattern) || [];
|
|
440
|
+
found.forEach(match => matches.add(match));
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Helper methods
|
|
448
|
+
*/
|
|
449
|
+
isValidUrl(url) {
|
|
450
|
+
try {
|
|
451
|
+
new URL(url);
|
|
452
|
+
return true;
|
|
453
|
+
} catch {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
isDirectoryWritable(dir) {
|
|
459
|
+
try {
|
|
460
|
+
if (!fs.existsSync(dir)) {
|
|
461
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
462
|
+
}
|
|
463
|
+
const testFile = path.join(dir, '.write-test');
|
|
464
|
+
fs.writeFileSync(testFile, 'test');
|
|
465
|
+
fs.unlinkSync(testFile);
|
|
466
|
+
return true;
|
|
467
|
+
} catch {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
summarizeChecks(phase, checks) {
|
|
473
|
+
console.log(`\n${phase} Validation Results:`);
|
|
474
|
+
console.log('─'.repeat(50));
|
|
475
|
+
|
|
476
|
+
let allPassed = true;
|
|
477
|
+
checks.forEach(check => {
|
|
478
|
+
const icon = check.passed ? '✓' : '✗';
|
|
479
|
+
const color = check.passed ? '\x1b[32m' : '\x1b[31m';
|
|
480
|
+
console.log(`${color}${icon}\x1b[0m ${check.name}`);
|
|
481
|
+
console.log(` ${check.message}`);
|
|
482
|
+
if (!check.passed) allPassed = false;
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
if (this.warnings.length > 0) {
|
|
486
|
+
console.log('\nWarnings:');
|
|
487
|
+
this.warnings.forEach(w => console.log(`\x1b[33m⚠\x1b[0m ${w}`));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
console.log('─'.repeat(50));
|
|
491
|
+
console.log(`Status: ${allPassed ? '\x1b[32mPASSED\x1b[0m' : '\x1b[31mFAILED\x1b[0m'}`);
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
passed: allPassed,
|
|
495
|
+
checks,
|
|
496
|
+
warnings: this.warnings,
|
|
497
|
+
errors: this.errors
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// CLI
|
|
503
|
+
if (require.main === module) {
|
|
504
|
+
const args = process.argv.slice(2);
|
|
505
|
+
const phase = args[0] || 'all';
|
|
506
|
+
|
|
507
|
+
const validator = new ValidationEngine({
|
|
508
|
+
referenceDir: args[1] || './references',
|
|
509
|
+
prototypeDir: args[2] || './prototype'
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
let result;
|
|
513
|
+
switch (phase) {
|
|
514
|
+
case 'pre-capture':
|
|
515
|
+
const config = args[1] ? JSON.parse(fs.readFileSync(args[1], 'utf-8')) : {};
|
|
516
|
+
result = validator.validatePreCapture(config);
|
|
517
|
+
break;
|
|
518
|
+
case 'post-capture':
|
|
519
|
+
result = validator.validatePostCapture();
|
|
520
|
+
break;
|
|
521
|
+
case 'pre-generation':
|
|
522
|
+
result = validator.validatePreGeneration();
|
|
523
|
+
break;
|
|
524
|
+
case 'post-generation':
|
|
525
|
+
result = validator.validatePostGeneration();
|
|
526
|
+
break;
|
|
527
|
+
case 'all':
|
|
528
|
+
console.log('\n' + '='.repeat(60));
|
|
529
|
+
console.log('RUNNING ALL VALIDATIONS');
|
|
530
|
+
console.log('='.repeat(60));
|
|
531
|
+
|
|
532
|
+
const results = [];
|
|
533
|
+
results.push(validator.validatePostCapture());
|
|
534
|
+
results.push(validator.validatePreGeneration());
|
|
535
|
+
results.push(validator.validatePostGeneration());
|
|
536
|
+
|
|
537
|
+
result = { passed: results.every(r => r.passed) };
|
|
538
|
+
break;
|
|
539
|
+
default:
|
|
540
|
+
console.log(`
|
|
541
|
+
Validation Engine
|
|
542
|
+
|
|
543
|
+
Usage:
|
|
544
|
+
node validation-engine.js <phase> [referenceDir] [prototypeDir]
|
|
545
|
+
|
|
546
|
+
Phases:
|
|
547
|
+
pre-capture Validate before capture (requires config file path)
|
|
548
|
+
post-capture Validate after capture
|
|
549
|
+
pre-generation Validate before prototype generation
|
|
550
|
+
post-generation Validate after prototype generation
|
|
551
|
+
all Run all applicable validations
|
|
552
|
+
`);
|
|
553
|
+
process.exit(0);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
process.exit(result.passed ? 0 : 1);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
module.exports = { ValidationEngine };
|
package/.env.example
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# ============================================
|
|
2
|
+
# Real Prototypes - Configuration Template
|
|
3
|
+
# ============================================
|
|
4
|
+
# Copy this file to CLAUDE.md in your project root,
|
|
5
|
+
# or set these as environment variables.
|
|
6
|
+
#
|
|
7
|
+
# IMPORTANT: Never commit credentials to version control!
|
|
8
|
+
# ============================================
|
|
9
|
+
|
|
10
|
+
# ----------------------
|
|
11
|
+
# Platform Credentials
|
|
12
|
+
# ----------------------
|
|
13
|
+
# The target platform URL to capture
|
|
14
|
+
PLATFORM_URL=https://www.example.com
|
|
15
|
+
|
|
16
|
+
# Login credentials (if the platform requires authentication)
|
|
17
|
+
PLATFORM_EMAIL=your@email.com
|
|
18
|
+
PLATFORM_PASSWORD=your-password
|
|
19
|
+
|
|
20
|
+
# ----------------------
|
|
21
|
+
# Capture Settings
|
|
22
|
+
# ----------------------
|
|
23
|
+
|
|
24
|
+
# Pages to capture
|
|
25
|
+
# Option 1: Specify pages manually (comma-separated paths)
|
|
26
|
+
PAGES_TO_CAPTURE=/homepage,/products,/checkout,/account
|
|
27
|
+
|
|
28
|
+
# Option 2: Auto-discover all pages
|
|
29
|
+
# PAGES_TO_CAPTURE=auto
|
|
30
|
+
|
|
31
|
+
# Option 3: Disable auto-capture (only capture specified pages)
|
|
32
|
+
# PAGES_TO_CAPTURE=off
|
|
33
|
+
|
|
34
|
+
# Capture mode
|
|
35
|
+
# "full" = capture all discovered pages up to MAX_PAGES
|
|
36
|
+
# "manual" = only capture pages listed in PAGES_TO_CAPTURE
|
|
37
|
+
CAPTURE_MODE=manual
|
|
38
|
+
|
|
39
|
+
# Maximum pages to capture in auto/full mode
|
|
40
|
+
MAX_PAGES=50
|
|
41
|
+
|
|
42
|
+
# Viewport dimensions (pixels)
|
|
43
|
+
VIEWPORT_WIDTH=1920
|
|
44
|
+
VIEWPORT_HEIGHT=1080
|
|
45
|
+
|
|
46
|
+
# Wait time after page load before capturing (milliseconds)
|
|
47
|
+
# Increase this if pages have slow-loading content or animations
|
|
48
|
+
WAIT_AFTER_LOAD=2000
|
|
49
|
+
|
|
50
|
+
# ----------------------
|
|
51
|
+
# Advanced Settings
|
|
52
|
+
# ----------------------
|
|
53
|
+
|
|
54
|
+
# Capture multiple viewports (comma-separated: width:height)
|
|
55
|
+
# VIEWPORTS=1920:1080,768:1024,375:812
|
|
56
|
+
|
|
57
|
+
# Exclude URL patterns from capture (comma-separated)
|
|
58
|
+
# EXCLUDE_PATTERNS=/logout,/signout,/delete,/admin
|
|
59
|
+
|
|
60
|
+
# Enable interaction capture (click buttons, tabs, dropdowns)
|
|
61
|
+
# CAPTURE_INTERACTIONS=true
|
|
62
|
+
|
|
63
|
+
# ----------------------
|
|
64
|
+
# Output Settings
|
|
65
|
+
# ----------------------
|
|
66
|
+
|
|
67
|
+
# Output directory for captured references
|
|
68
|
+
# OUTPUT_DIR=./references
|
|
69
|
+
|
|
70
|
+
# Save HTML source alongside screenshots
|
|
71
|
+
# SAVE_HTML=true
|
|
72
|
+
|
|
73
|
+
# Extract and save design tokens (colors, fonts, spacing)
|
|
74
|
+
# EXTRACT_DESIGN_TOKENS=true
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kaidhar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|