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,499 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* validate-manifest.js
|
|
5
|
+
*
|
|
6
|
+
* Validates a manifest.json file against the manifest schema.
|
|
7
|
+
* Uses only Node.js built-in modules (no external dependencies like Ajv).
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node validate-manifest.js <manifest-path>
|
|
11
|
+
* node validate-manifest.js <manifest-path> [schema-path]
|
|
12
|
+
*
|
|
13
|
+
* Arguments:
|
|
14
|
+
* manifest-path Path to the manifest.json file to validate
|
|
15
|
+
* schema-path Optional path to schema file (defaults to manifest-schema.json in same directory)
|
|
16
|
+
*
|
|
17
|
+
* Example:
|
|
18
|
+
* node validate-manifest.js ./references/manifest.json
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
// Validation result tracking
|
|
25
|
+
let errors = [];
|
|
26
|
+
let warnings = [];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse command line arguments
|
|
30
|
+
*/
|
|
31
|
+
function parseArgs() {
|
|
32
|
+
const args = process.argv.slice(2);
|
|
33
|
+
|
|
34
|
+
if (args.length < 1) {
|
|
35
|
+
console.error('Usage: node validate-manifest.js <manifest-path> [schema-path]');
|
|
36
|
+
console.error('');
|
|
37
|
+
console.error('Arguments:');
|
|
38
|
+
console.error(' manifest-path Path to the manifest.json file to validate');
|
|
39
|
+
console.error(' schema-path Optional path to schema file (defaults to manifest-schema.json)');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const scriptDir = path.dirname(__filename);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
manifestPath: path.resolve(args[0]),
|
|
47
|
+
schemaPath: args[1] ? path.resolve(args[1]) : path.join(scriptDir, 'manifest-schema.json')
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Load and parse a JSON file
|
|
53
|
+
*/
|
|
54
|
+
function loadJsonFile(filePath, description) {
|
|
55
|
+
if (!fs.existsSync(filePath)) {
|
|
56
|
+
console.error(`Error: ${description} not found: ${filePath}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
62
|
+
return JSON.parse(content);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (err instanceof SyntaxError) {
|
|
65
|
+
console.error(`Error: Invalid JSON in ${description}: ${err.message}`);
|
|
66
|
+
} else {
|
|
67
|
+
console.error(`Error reading ${description}: ${err.message}`);
|
|
68
|
+
}
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Add an error to the validation results
|
|
75
|
+
*/
|
|
76
|
+
function addError(path, message) {
|
|
77
|
+
errors.push({ path, message });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Add a warning to the validation results
|
|
82
|
+
*/
|
|
83
|
+
function addWarning(path, message) {
|
|
84
|
+
warnings.push({ path, message });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get the type of a value (more specific than typeof)
|
|
89
|
+
*/
|
|
90
|
+
function getType(value) {
|
|
91
|
+
if (value === null) return 'null';
|
|
92
|
+
if (Array.isArray(value)) return 'array';
|
|
93
|
+
return typeof value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Validate a value matches the expected type
|
|
98
|
+
*/
|
|
99
|
+
function validateType(value, expectedType, path) {
|
|
100
|
+
const actualType = getType(value);
|
|
101
|
+
|
|
102
|
+
if (expectedType === 'array') {
|
|
103
|
+
if (!Array.isArray(value)) {
|
|
104
|
+
addError(path, `Expected array, got ${actualType}`);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
} else if (expectedType === 'object') {
|
|
108
|
+
if (actualType !== 'object' || Array.isArray(value) || value === null) {
|
|
109
|
+
addError(path, `Expected object, got ${actualType}`);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
} else if (expectedType === 'string') {
|
|
113
|
+
if (typeof value !== 'string') {
|
|
114
|
+
addError(path, `Expected string, got ${actualType}`);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
} else if (expectedType === 'number') {
|
|
118
|
+
if (typeof value !== 'number') {
|
|
119
|
+
addError(path, `Expected number, got ${actualType}`);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
} else if (expectedType === 'boolean') {
|
|
123
|
+
if (typeof value !== 'boolean') {
|
|
124
|
+
addError(path, `Expected boolean, got ${actualType}`);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
} else if (expectedType === 'integer') {
|
|
128
|
+
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
|
129
|
+
addError(path, `Expected integer, got ${actualType}`);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Validate string format
|
|
139
|
+
*/
|
|
140
|
+
function validateFormat(value, format, path) {
|
|
141
|
+
if (typeof value !== 'string') return true;
|
|
142
|
+
|
|
143
|
+
switch (format) {
|
|
144
|
+
case 'uri':
|
|
145
|
+
try {
|
|
146
|
+
new URL(value);
|
|
147
|
+
} catch {
|
|
148
|
+
addError(path, `Invalid URI format: ${value}`);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
case 'date-time':
|
|
153
|
+
const date = new Date(value);
|
|
154
|
+
if (isNaN(date.getTime())) {
|
|
155
|
+
addError(path, `Invalid date-time format: ${value}`);
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
case 'email':
|
|
160
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
161
|
+
addWarning(path, `Possibly invalid email format: ${value}`);
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Validate a string against a pattern
|
|
171
|
+
*/
|
|
172
|
+
function validatePattern(value, pattern, path) {
|
|
173
|
+
if (typeof value !== 'string') return true;
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const regex = new RegExp(pattern);
|
|
177
|
+
if (!regex.test(value)) {
|
|
178
|
+
addError(path, `Value "${value}" does not match pattern: ${pattern}`);
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
addWarning(path, `Invalid pattern in schema: ${pattern}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Validate enum values
|
|
190
|
+
*/
|
|
191
|
+
function validateEnum(value, enumValues, path) {
|
|
192
|
+
if (!enumValues.includes(value)) {
|
|
193
|
+
addError(path, `Value "${value}" not in allowed values: ${enumValues.join(', ')}`);
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Validate string length constraints
|
|
201
|
+
*/
|
|
202
|
+
function validateStringLength(value, schema, path) {
|
|
203
|
+
if (typeof value !== 'string') return true;
|
|
204
|
+
|
|
205
|
+
if (schema.minLength !== undefined && value.length < schema.minLength) {
|
|
206
|
+
addError(path, `String length ${value.length} is less than minimum ${schema.minLength}`);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (schema.maxLength !== undefined && value.length > schema.maxLength) {
|
|
211
|
+
addError(path, `String length ${value.length} exceeds maximum ${schema.maxLength}`);
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Validate array constraints
|
|
220
|
+
*/
|
|
221
|
+
function validateArray(value, schema, path) {
|
|
222
|
+
if (!Array.isArray(value)) return true;
|
|
223
|
+
|
|
224
|
+
let valid = true;
|
|
225
|
+
|
|
226
|
+
if (schema.minItems !== undefined && value.length < schema.minItems) {
|
|
227
|
+
addError(path, `Array length ${value.length} is less than minimum ${schema.minItems}`);
|
|
228
|
+
valid = false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (schema.maxItems !== undefined && value.length > schema.maxItems) {
|
|
232
|
+
addError(path, `Array length ${value.length} exceeds maximum ${schema.maxItems}`);
|
|
233
|
+
valid = false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Validate items
|
|
237
|
+
if (schema.items && value.length > 0) {
|
|
238
|
+
for (let i = 0; i < value.length; i++) {
|
|
239
|
+
if (!validateValue(value[i], schema.items, `${path}[${i}]`)) {
|
|
240
|
+
valid = false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return valid;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Validate object against schema
|
|
250
|
+
*/
|
|
251
|
+
function validateObject(value, schema, path) {
|
|
252
|
+
if (getType(value) !== 'object') return true;
|
|
253
|
+
|
|
254
|
+
let valid = true;
|
|
255
|
+
|
|
256
|
+
// Check required properties
|
|
257
|
+
if (schema.required) {
|
|
258
|
+
for (const prop of schema.required) {
|
|
259
|
+
if (!(prop in value)) {
|
|
260
|
+
addError(path, `Missing required property: ${prop}`);
|
|
261
|
+
valid = false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Validate defined properties
|
|
267
|
+
if (schema.properties) {
|
|
268
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
269
|
+
if (propName in value) {
|
|
270
|
+
if (!validateValue(value[propName], propSchema, `${path}.${propName}`)) {
|
|
271
|
+
valid = false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Check for additional properties
|
|
278
|
+
if (schema.additionalProperties === false) {
|
|
279
|
+
const allowedProps = new Set(Object.keys(schema.properties || {}));
|
|
280
|
+
for (const prop of Object.keys(value)) {
|
|
281
|
+
if (!allowedProps.has(prop)) {
|
|
282
|
+
addWarning(path, `Unexpected property: ${prop}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
} else if (typeof schema.additionalProperties === 'object') {
|
|
286
|
+
// Validate additional properties against schema
|
|
287
|
+
const definedProps = new Set(Object.keys(schema.properties || {}));
|
|
288
|
+
for (const [propName, propValue] of Object.entries(value)) {
|
|
289
|
+
if (!definedProps.has(propName)) {
|
|
290
|
+
if (!validateValue(propValue, schema.additionalProperties, `${path}.${propName}`)) {
|
|
291
|
+
valid = false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return valid;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Main validation function for any value
|
|
302
|
+
*/
|
|
303
|
+
function validateValue(value, schema, path = '$') {
|
|
304
|
+
if (!schema) return true;
|
|
305
|
+
|
|
306
|
+
let valid = true;
|
|
307
|
+
|
|
308
|
+
// Handle type validation
|
|
309
|
+
if (schema.type) {
|
|
310
|
+
if (!validateType(value, schema.type, path)) {
|
|
311
|
+
return false; // Stop validation if type doesn't match
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Handle format validation
|
|
316
|
+
if (schema.format) {
|
|
317
|
+
if (!validateFormat(value, schema.format, path)) {
|
|
318
|
+
valid = false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Handle pattern validation
|
|
323
|
+
if (schema.pattern) {
|
|
324
|
+
if (!validatePattern(value, schema.pattern, path)) {
|
|
325
|
+
valid = false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Handle enum validation
|
|
330
|
+
if (schema.enum) {
|
|
331
|
+
if (!validateEnum(value, schema.enum, path)) {
|
|
332
|
+
valid = false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Handle string constraints
|
|
337
|
+
if (schema.type === 'string') {
|
|
338
|
+
if (!validateStringLength(value, schema, path)) {
|
|
339
|
+
valid = false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Handle array validation
|
|
344
|
+
if (schema.type === 'array' || Array.isArray(value)) {
|
|
345
|
+
if (!validateArray(value, schema, path)) {
|
|
346
|
+
valid = false;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Handle object validation
|
|
351
|
+
if (schema.type === 'object' || (getType(value) === 'object' && schema.properties)) {
|
|
352
|
+
if (!validateObject(value, schema, path)) {
|
|
353
|
+
valid = false;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return valid;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Additional semantic validations specific to manifest files
|
|
362
|
+
*/
|
|
363
|
+
function validateManifestSemantics(manifest) {
|
|
364
|
+
// Check for duplicate page IDs
|
|
365
|
+
if (manifest.pages && Array.isArray(manifest.pages)) {
|
|
366
|
+
const pageIds = new Set();
|
|
367
|
+
for (const page of manifest.pages) {
|
|
368
|
+
if (page.id) {
|
|
369
|
+
if (pageIds.has(page.id)) {
|
|
370
|
+
addError('$.pages', `Duplicate page ID: ${page.id}`);
|
|
371
|
+
}
|
|
372
|
+
pageIds.add(page.id);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Validate screenshot files exist (warning only)
|
|
378
|
+
if (manifest.pages) {
|
|
379
|
+
for (const page of manifest.pages) {
|
|
380
|
+
if (page.screenshots) {
|
|
381
|
+
for (const screenshot of page.screenshots) {
|
|
382
|
+
if (screenshot.file && !screenshot.file.match(/\.(png|jpg|jpeg|webp|gif)$/i)) {
|
|
383
|
+
addWarning(`$.pages[${page.id}].screenshots`,
|
|
384
|
+
`Unusual screenshot extension: ${screenshot.file}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Check platform URL is valid
|
|
392
|
+
if (manifest.platform && manifest.platform.url) {
|
|
393
|
+
try {
|
|
394
|
+
new URL(manifest.platform.url);
|
|
395
|
+
} catch {
|
|
396
|
+
addError('$.platform.url', `Invalid URL: ${manifest.platform.url}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Warn if no pages defined
|
|
401
|
+
if (!manifest.pages || manifest.pages.length === 0) {
|
|
402
|
+
addWarning('$.pages', 'Manifest has no pages defined');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Warn if design tokens are empty
|
|
406
|
+
if (manifest.designTokens) {
|
|
407
|
+
const hasColors = manifest.designTokens.colors &&
|
|
408
|
+
Object.keys(manifest.designTokens.colors).length > 0;
|
|
409
|
+
const hasTypography = manifest.designTokens.typography &&
|
|
410
|
+
Object.keys(manifest.designTokens.typography).length > 0;
|
|
411
|
+
|
|
412
|
+
if (!hasColors && !hasTypography) {
|
|
413
|
+
addWarning('$.designTokens', 'Design tokens are empty - consider adding colors and typography');
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Print validation results
|
|
420
|
+
*/
|
|
421
|
+
function printResults() {
|
|
422
|
+
const hasErrors = errors.length > 0;
|
|
423
|
+
const hasWarnings = warnings.length > 0;
|
|
424
|
+
|
|
425
|
+
if (!hasErrors && !hasWarnings) {
|
|
426
|
+
console.log('Validation passed! No errors or warnings.');
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (hasErrors) {
|
|
431
|
+
console.log(`\nErrors (${errors.length}):`);
|
|
432
|
+
for (const error of errors) {
|
|
433
|
+
console.log(` [ERROR] ${error.path}: ${error.message}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (hasWarnings) {
|
|
438
|
+
console.log(`\nWarnings (${warnings.length}):`);
|
|
439
|
+
for (const warning of warnings) {
|
|
440
|
+
console.log(` [WARN] ${warning.path}: ${warning.message}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
console.log('');
|
|
445
|
+
|
|
446
|
+
if (hasErrors) {
|
|
447
|
+
console.log('Validation FAILED with errors.');
|
|
448
|
+
return false;
|
|
449
|
+
} else {
|
|
450
|
+
console.log('Validation passed with warnings.');
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Main execution
|
|
457
|
+
*/
|
|
458
|
+
function main() {
|
|
459
|
+
const { manifestPath, schemaPath } = parseArgs();
|
|
460
|
+
|
|
461
|
+
console.log('Manifest Validation');
|
|
462
|
+
console.log('===================');
|
|
463
|
+
console.log(`Manifest: ${manifestPath}`);
|
|
464
|
+
console.log(`Schema: ${schemaPath}`);
|
|
465
|
+
console.log('');
|
|
466
|
+
|
|
467
|
+
// Reset validation state
|
|
468
|
+
errors = [];
|
|
469
|
+
warnings = [];
|
|
470
|
+
|
|
471
|
+
// Load files
|
|
472
|
+
const manifest = loadJsonFile(manifestPath, 'Manifest file');
|
|
473
|
+
const schema = loadJsonFile(schemaPath, 'Schema file');
|
|
474
|
+
|
|
475
|
+
// Validate against schema
|
|
476
|
+
console.log('Validating against schema...');
|
|
477
|
+
validateValue(manifest, schema);
|
|
478
|
+
|
|
479
|
+
// Run semantic validations
|
|
480
|
+
console.log('Running semantic validations...');
|
|
481
|
+
validateManifestSemantics(manifest);
|
|
482
|
+
|
|
483
|
+
// Print results
|
|
484
|
+
const success = printResults();
|
|
485
|
+
|
|
486
|
+
process.exit(success ? 0 : 1);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Run if executed directly
|
|
490
|
+
if (require.main === module) {
|
|
491
|
+
main();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Export for testing
|
|
495
|
+
module.exports = {
|
|
496
|
+
validateValue,
|
|
497
|
+
validateManifestSemantics,
|
|
498
|
+
loadJsonFile
|
|
499
|
+
};
|