openuispec 0.2.15 → 0.2.17
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/README.md +4 -2
- package/check/audit.ts +291 -0
- package/check/index.ts +19 -3
- package/docs/cli.md +29 -3
- package/docs/file-formats.md +83 -0
- package/docs/implementation-notes.md +8 -0
- package/examples/social-app/openuispec/contracts/action_trigger.yaml +8 -0
- package/examples/social-app/openuispec/contracts/collection.yaml +8 -0
- package/examples/social-app/openuispec/contracts/data_display.yaml +8 -0
- package/examples/social-app/openuispec/contracts/feedback.yaml +8 -0
- package/examples/social-app/openuispec/contracts/input_field.yaml +8 -0
- package/examples/social-app/openuispec/contracts/nav_container.yaml +9 -0
- package/examples/social-app/openuispec/contracts/surface.yaml +8 -0
- package/examples/social-app/openuispec/openuispec.yaml +40 -0
- package/examples/social-app/openuispec/tokens/color.yaml +4 -0
- package/examples/social-app/openuispec/tokens/motion.yaml +4 -0
- package/examples/social-app/openuispec/tokens/typography.yaml +11 -0
- package/examples/taskflow/openuispec/contracts/action_trigger.yaml +9 -1
- package/examples/taskflow/openuispec/contracts/collection.yaml +9 -1
- package/examples/taskflow/openuispec/contracts/data_display.yaml +9 -1
- package/examples/taskflow/openuispec/contracts/feedback.yaml +9 -1
- package/examples/taskflow/openuispec/contracts/input_field.yaml +8 -0
- package/examples/taskflow/openuispec/contracts/nav_container.yaml +10 -1
- package/examples/taskflow/openuispec/contracts/surface.yaml +9 -1
- package/examples/taskflow/openuispec/openuispec.yaml +40 -0
- package/examples/taskflow/openuispec/tokens/color.yaml +4 -0
- package/examples/taskflow/openuispec/tokens/motion.yaml +4 -0
- package/examples/taskflow/openuispec/tokens/typography.yaml +11 -0
- package/examples/todo-orbit/openuispec/contracts/action_trigger.yaml +7 -0
- package/examples/todo-orbit/openuispec/contracts/collection.yaml +7 -0
- package/examples/todo-orbit/openuispec/contracts/data_display.yaml +7 -0
- package/examples/todo-orbit/openuispec/contracts/feedback.yaml +7 -0
- package/examples/todo-orbit/openuispec/contracts/input_field.yaml +7 -0
- package/examples/todo-orbit/openuispec/contracts/nav_container.yaml +8 -0
- package/examples/todo-orbit/openuispec/contracts/surface.yaml +7 -0
- package/examples/todo-orbit/openuispec/openuispec.yaml +40 -0
- package/examples/todo-orbit/openuispec/tokens/color.yaml +4 -0
- package/examples/todo-orbit/openuispec/tokens/motion.yaml +4 -0
- package/examples/todo-orbit/openuispec/tokens/typography.yaml +11 -0
- package/mcp-server/index.ts +15 -3
- package/package.json +1 -1
- package/prepare/index.ts +102 -0
- package/schema/component.schema.json +5 -0
- package/schema/contract.schema.json +11 -1
- package/schema/custom-contract.schema.json +5 -0
- package/schema/openuispec.schema.json +47 -0
- package/schema/tokens/color.schema.json +5 -0
- package/schema/tokens/motion.schema.json +5 -0
- package/schema/tokens/typography.schema.json +10 -0
- package/schema/validate.ts +21 -22
package/prepare/index.ts
CHANGED
|
@@ -172,6 +172,17 @@ export interface PrepareResult {
|
|
|
172
172
|
shared_layers?: SharedLayerInfo[];
|
|
173
173
|
bootstrap?: PrepareBootstrapBundle;
|
|
174
174
|
spec_contents?: SpecFileContent[];
|
|
175
|
+
anti_patterns?: {
|
|
176
|
+
universal: Record<string, string[]>;
|
|
177
|
+
contract_specific: Record<string, string[]>;
|
|
178
|
+
project_specific: string[];
|
|
179
|
+
};
|
|
180
|
+
design_context?: {
|
|
181
|
+
personality?: string;
|
|
182
|
+
complexity: 'restrained' | 'balanced' | 'elaborate';
|
|
183
|
+
audience?: string;
|
|
184
|
+
complexity_rule: string;
|
|
185
|
+
};
|
|
175
186
|
next_steps: string[];
|
|
176
187
|
}
|
|
177
188
|
|
|
@@ -643,9 +654,92 @@ function generationRules(target: string, outputDir: string, manifest: Record<str
|
|
|
643
654
|
rules.push(`Target "${target}" scope: ${structure.scope}`);
|
|
644
655
|
}
|
|
645
656
|
|
|
657
|
+
// Include extra_rules from manifest, filtered by target platform tag
|
|
658
|
+
const extraRules: string[] = Array.isArray(manifest.generation?.extra_rules)
|
|
659
|
+
? manifest.generation.extra_rules.filter((rule: any): rule is string => typeof rule === "string")
|
|
660
|
+
: [];
|
|
661
|
+
for (const rule of extraRules) {
|
|
662
|
+
if (matchesTargetPlatform(rule, target)) rules.push(rule);
|
|
663
|
+
}
|
|
664
|
+
|
|
646
665
|
return rules;
|
|
647
666
|
}
|
|
648
667
|
|
|
668
|
+
function matchesTargetPlatform(item: string, target: string): boolean {
|
|
669
|
+
const tagMatch = item.match(/^\[([a-z]+)\]/i);
|
|
670
|
+
return !tagMatch || tagMatch[1].toLowerCase() === target;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function complexityRule(complexity: string): string {
|
|
674
|
+
switch (complexity) {
|
|
675
|
+
case 'restrained':
|
|
676
|
+
return 'Minimal motion (required state transitions only). No decorative shadows. Clean whitespace. Precise token application. No background effects.';
|
|
677
|
+
case 'elaborate':
|
|
678
|
+
return 'Rich animations with staggered reveals. Creative elevation. Platform-specific flourishes.';
|
|
679
|
+
default:
|
|
680
|
+
return 'Apply all motion.patterns. Use elevation tokens fully. Standard state animations.';
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function buildDesignContext(manifest: Record<string, any>): PrepareResult['design_context'] {
|
|
685
|
+
const design = manifest.design;
|
|
686
|
+
if (!design) return undefined;
|
|
687
|
+
const complexity = (design.complexity as 'restrained' | 'balanced' | 'elaborate') ?? 'balanced';
|
|
688
|
+
return {
|
|
689
|
+
...(design.personality ? { personality: design.personality } : {}),
|
|
690
|
+
complexity,
|
|
691
|
+
...(design.audience ? { audience: design.audience } : {}),
|
|
692
|
+
complexity_rule: complexityRule(complexity),
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function buildAntiPatterns(
|
|
697
|
+
manifest: Record<string, any>,
|
|
698
|
+
projectDir: string,
|
|
699
|
+
target: string
|
|
700
|
+
): PrepareResult['anti_patterns'] {
|
|
701
|
+
// Universal anti-patterns from generation_guidance
|
|
702
|
+
const universal: Record<string, string[]> = {};
|
|
703
|
+
const universalRaw = manifest.generation_guidance?.universal_anti_patterns ?? {};
|
|
704
|
+
for (const [domain, items] of Object.entries(universalRaw)) {
|
|
705
|
+
if (Array.isArray(items)) {
|
|
706
|
+
const filtered = (items as string[]).filter((item) => matchesTargetPlatform(item, target));
|
|
707
|
+
if (filtered.length > 0) universal[domain] = filtered;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Contract-specific must_avoid
|
|
712
|
+
const contract_specific: Record<string, string[]> = {};
|
|
713
|
+
const contractsDir = resolve(projectDir, manifest.includes?.contracts ?? './contracts/');
|
|
714
|
+
if (existsSync(contractsDir)) {
|
|
715
|
+
for (const file of readdirSync(contractsDir).filter((f) => f.endsWith('.yaml') && !f.startsWith('x_'))) {
|
|
716
|
+
try {
|
|
717
|
+
const content = YAML.parse(readFileSync(join(contractsDir, file), 'utf-8'));
|
|
718
|
+
const contractName = Object.keys(content)[0];
|
|
719
|
+
const mustAvoid: string[] = content[contractName]?.generation?.must_avoid ?? [];
|
|
720
|
+
if (mustAvoid.length > 0) {
|
|
721
|
+
const filtered = mustAvoid.filter((item: string) => matchesTargetPlatform(item, target));
|
|
722
|
+
if (filtered.length > 0) contract_specific[contractName] = filtered;
|
|
723
|
+
}
|
|
724
|
+
} catch {
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Project-specific avoid from design section
|
|
731
|
+
const project_specific: string[] = [];
|
|
732
|
+
const designAvoid: string[] = manifest.design?.avoid ?? [];
|
|
733
|
+
for (const item of designAvoid) {
|
|
734
|
+
if (matchesTargetPlatform(item, target)) project_specific.push(item);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const hasContent = Object.keys(universal).length > 0 || Object.keys(contract_specific).length > 0 || project_specific.length > 0;
|
|
738
|
+
if (!hasContent) return undefined;
|
|
739
|
+
|
|
740
|
+
return { universal, contract_specific, project_specific };
|
|
741
|
+
}
|
|
742
|
+
|
|
649
743
|
function localizationConstraints(
|
|
650
744
|
target: string,
|
|
651
745
|
platformConfig?: Pick<PreparePlatformConfig, "framework">
|
|
@@ -1228,6 +1322,8 @@ function buildBootstrapPrepareResult(cwd: string, target: string, includeContent
|
|
|
1228
1322
|
const outputDirExists = existsSync(outputDir);
|
|
1229
1323
|
const snapshotPath = join(outputDir, ".openuispec-state.json");
|
|
1230
1324
|
const snapshotFileExists = existsSync(snapshotPath);
|
|
1325
|
+
const antiPatterns = buildAntiPatterns(manifest, projectDir, target);
|
|
1326
|
+
const designContext = buildDesignContext(manifest);
|
|
1231
1327
|
|
|
1232
1328
|
return {
|
|
1233
1329
|
mode: "bootstrap",
|
|
@@ -1259,6 +1355,8 @@ function buildBootstrapPrepareResult(cwd: string, target: string, includeContent
|
|
|
1259
1355
|
items: [],
|
|
1260
1356
|
...(sharedLayerInfos.length > 0 ? { shared_layers: sharedLayerInfos } : {}),
|
|
1261
1357
|
...(includeContents ? { spec_contents: readAllSpecContents(projectDir) } : {}),
|
|
1358
|
+
...(antiPatterns ? { anti_patterns: antiPatterns } : {}),
|
|
1359
|
+
...(designContext ? { design_context: designContext } : {}),
|
|
1262
1360
|
bootstrap: {
|
|
1263
1361
|
output_exists: existsSync(outputDir),
|
|
1264
1362
|
generation_ready: missingDecisions.length === 0 && backendContextReady && !pendingUserConfirmation,
|
|
@@ -1290,6 +1388,8 @@ function buildUpdatePrepareResult(cwd: string, target: string, includeContents:
|
|
|
1290
1388
|
const sharedLayerConfigs = sharedLayersForTarget(projectDir, target);
|
|
1291
1389
|
const codeRoots = suggestCodeRoots(target, outputDir, projectDir, sharedLayerConfigs);
|
|
1292
1390
|
const manifest = readManifest(projectDir);
|
|
1391
|
+
const antiPatterns = buildAntiPatterns(manifest, projectDir, target);
|
|
1392
|
+
const designContext = buildDesignContext(manifest);
|
|
1293
1393
|
const sharedLayerInfos = buildSharedLayerInfos(projectDir, target, sharedLayerConfigs);
|
|
1294
1394
|
const platformDef = readPlatformDefinition(projectDir, manifest, target);
|
|
1295
1395
|
const platformConfig = buildPlatformConfig(target, platformDef);
|
|
@@ -1333,6 +1433,8 @@ function buildUpdatePrepareResult(cwd: string, target: string, includeContents:
|
|
|
1333
1433
|
items,
|
|
1334
1434
|
...(sharedLayerInfos.length > 0 ? { shared_layers: sharedLayerInfos } : {}),
|
|
1335
1435
|
...(includeContents ? { spec_contents: readAllSpecContents(projectDir) } : {}),
|
|
1436
|
+
...(antiPatterns ? { anti_patterns: antiPatterns } : {}),
|
|
1437
|
+
...(designContext ? { design_context: designContext } : {}),
|
|
1336
1438
|
next_steps: nextSteps,
|
|
1337
1439
|
};
|
|
1338
1440
|
}
|
|
@@ -107,6 +107,11 @@
|
|
|
107
107
|
"may_handle": {
|
|
108
108
|
"type": "array",
|
|
109
109
|
"items": { "type": "string" }
|
|
110
|
+
},
|
|
111
|
+
"must_avoid": {
|
|
112
|
+
"type": "array",
|
|
113
|
+
"items": { "type": "string" },
|
|
114
|
+
"description": "Anti-patterns for AI generators. Strings may start with [web], [ios], or [android] to scope to a platform. Unmarked items apply to all platforms."
|
|
110
115
|
}
|
|
111
116
|
},
|
|
112
117
|
"additionalProperties": false
|
|
@@ -71,6 +71,11 @@
|
|
|
71
71
|
"may_handle": {
|
|
72
72
|
"type": "array",
|
|
73
73
|
"items": { "type": "string" }
|
|
74
|
+
},
|
|
75
|
+
"must_avoid": {
|
|
76
|
+
"type": "array",
|
|
77
|
+
"items": { "type": "string" },
|
|
78
|
+
"description": "Anti-patterns for AI generators. Strings may start with [web], [ios], or [android] to scope to a platform. Unmarked items apply to all platforms."
|
|
74
79
|
}
|
|
75
80
|
},
|
|
76
81
|
"additionalProperties": false
|
|
@@ -116,7 +121,12 @@
|
|
|
116
121
|
"properties": {
|
|
117
122
|
"must_handle": { "type": "array", "items": { "type": "string" } },
|
|
118
123
|
"should_handle": { "type": "array", "items": { "type": "string" } },
|
|
119
|
-
"may_handle": { "type": "array", "items": { "type": "string" } }
|
|
124
|
+
"may_handle": { "type": "array", "items": { "type": "string" } },
|
|
125
|
+
"must_avoid": {
|
|
126
|
+
"type": "array",
|
|
127
|
+
"items": { "type": "string" },
|
|
128
|
+
"description": "Anti-patterns for AI generators. Strings may start with [web], [ios], or [android] to scope to a platform. Unmarked items apply to all platforms."
|
|
129
|
+
}
|
|
120
130
|
},
|
|
121
131
|
"additionalProperties": false
|
|
122
132
|
}
|
|
@@ -112,6 +112,11 @@
|
|
|
112
112
|
"type": "array",
|
|
113
113
|
"items": { "type": "string" },
|
|
114
114
|
"description": "Optional enhancements the generator MAY implement"
|
|
115
|
+
},
|
|
116
|
+
"must_avoid": {
|
|
117
|
+
"type": "array",
|
|
118
|
+
"items": { "type": "string" },
|
|
119
|
+
"description": "Anti-patterns for AI generators. Strings may start with [web], [ios], or [android] to scope to a platform. Unmarked items apply to all platforms."
|
|
115
120
|
}
|
|
116
121
|
},
|
|
117
122
|
"additionalProperties": false
|
|
@@ -259,6 +259,53 @@
|
|
|
259
259
|
"type": "string"
|
|
260
260
|
}
|
|
261
261
|
},
|
|
262
|
+
"design": {
|
|
263
|
+
"type": "object",
|
|
264
|
+
"description": "Design intent and anti-patterns for this project",
|
|
265
|
+
"properties": {
|
|
266
|
+
"personality": { "type": "string", "description": "Brief description of the brand's visual personality" },
|
|
267
|
+
"complexity": {
|
|
268
|
+
"type": "string",
|
|
269
|
+
"enum": ["restrained", "balanced", "elaborate"],
|
|
270
|
+
"default": "balanced",
|
|
271
|
+
"description": "How elaborate animations, effects, and visual details should be"
|
|
272
|
+
},
|
|
273
|
+
"audience": { "type": "string", "description": "Who uses this app — informs tone and complexity" },
|
|
274
|
+
"avoid": {
|
|
275
|
+
"type": "array",
|
|
276
|
+
"items": { "type": "string" },
|
|
277
|
+
"description": "Project-specific anti-patterns. May use [web]/[ios]/[android] scope tags."
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
"additionalProperties": false
|
|
281
|
+
},
|
|
282
|
+
"generation_guidance": {
|
|
283
|
+
"type": "object",
|
|
284
|
+
"description": "Universal anti-patterns and audit config for AI generators",
|
|
285
|
+
"properties": {
|
|
286
|
+
"universal_anti_patterns": {
|
|
287
|
+
"type": "object",
|
|
288
|
+
"description": "Cross-contract anti-patterns by domain",
|
|
289
|
+
"properties": {
|
|
290
|
+
"typography": { "type": "array", "items": { "type": "string" } },
|
|
291
|
+
"color": { "type": "array", "items": { "type": "string" } },
|
|
292
|
+
"spacing": { "type": "array", "items": { "type": "string" } },
|
|
293
|
+
"motion": { "type": "array", "items": { "type": "string" } },
|
|
294
|
+
"elevation": { "type": "array", "items": { "type": "string" } },
|
|
295
|
+
"layout": { "type": "array", "items": { "type": "string" } },
|
|
296
|
+
"accessibility": { "type": "array", "items": { "type": "string" } }
|
|
297
|
+
},
|
|
298
|
+
"additionalProperties": false
|
|
299
|
+
},
|
|
300
|
+
"audit_threshold": {
|
|
301
|
+
"type": "integer",
|
|
302
|
+
"minimum": 0,
|
|
303
|
+
"maximum": 100,
|
|
304
|
+
"description": "Minimum design quality score. Default 0 (informational). --min-score CLI flag takes precedence."
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
"additionalProperties": false
|
|
308
|
+
},
|
|
262
309
|
"api": {
|
|
263
310
|
"type": "object",
|
|
264
311
|
"description": "API endpoint definitions",
|
|
@@ -75,6 +75,11 @@
|
|
|
75
75
|
},
|
|
76
76
|
"contrast_min": {
|
|
77
77
|
"type": "number"
|
|
78
|
+
},
|
|
79
|
+
"generation_notes": {
|
|
80
|
+
"type": "array",
|
|
81
|
+
"items": { "type": "string" },
|
|
82
|
+
"description": "Sparse guidance for AI generators. Use [web]/[ios]/[android] tags for platform-specific notes."
|
|
78
83
|
}
|
|
79
84
|
},
|
|
80
85
|
"required": [
|
|
@@ -75,6 +75,11 @@
|
|
|
75
75
|
"pattern": {
|
|
76
76
|
"type": "string",
|
|
77
77
|
"description": "Named animation pattern"
|
|
78
|
+
},
|
|
79
|
+
"generation_notes": {
|
|
80
|
+
"type": "array",
|
|
81
|
+
"items": { "type": "string" },
|
|
82
|
+
"description": "Sparse guidance for AI generators. Use [web]/[ios]/[android] tags for platform-specific notes."
|
|
78
83
|
}
|
|
79
84
|
},
|
|
80
85
|
"additionalProperties": false
|
|
@@ -60,6 +60,11 @@
|
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"additionalProperties": false
|
|
63
|
+
},
|
|
64
|
+
"generation_notes": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"items": { "type": "string" },
|
|
67
|
+
"description": "Sparse guidance for AI generators. Use [web]/[ios]/[android] tags for platform-specific notes."
|
|
63
68
|
}
|
|
64
69
|
},
|
|
65
70
|
"required": [
|
|
@@ -93,6 +98,11 @@
|
|
|
93
98
|
"capitalize",
|
|
94
99
|
"none"
|
|
95
100
|
]
|
|
101
|
+
},
|
|
102
|
+
"generation_notes": {
|
|
103
|
+
"type": "array",
|
|
104
|
+
"items": { "type": "string" },
|
|
105
|
+
"description": "Sparse guidance for AI generators. Use [web]/[ios]/[android] tags for platform-specific notes."
|
|
96
106
|
}
|
|
97
107
|
},
|
|
98
108
|
"required": [
|
package/schema/validate.ts
CHANGED
|
@@ -399,6 +399,16 @@ function buildAjv(): AjvInstance {
|
|
|
399
399
|
}
|
|
400
400
|
|
|
401
401
|
const BASE = "https://openuispec.rsteam.uz/schema/";
|
|
402
|
+
const TOKEN_FILE_SCHEMAS: Record<string, string> = {
|
|
403
|
+
"color.yaml": "color.schema.json",
|
|
404
|
+
"typography.yaml": "typography.schema.json",
|
|
405
|
+
"spacing.yaml": "spacing.schema.json",
|
|
406
|
+
"elevation.yaml": "elevation.schema.json",
|
|
407
|
+
"motion.yaml": "motion.schema.json",
|
|
408
|
+
"layout.yaml": "layout.schema.json",
|
|
409
|
+
"themes.yaml": "themes.schema.json",
|
|
410
|
+
"icons.yaml": "icons.schema.json",
|
|
411
|
+
};
|
|
402
412
|
|
|
403
413
|
// ── validate one file ────────────────────────────────────────────────
|
|
404
414
|
|
|
@@ -536,20 +546,13 @@ const GROUPS: Record<string, ValidationGroup> = {
|
|
|
536
546
|
run(ajv, projectDir, includes) {
|
|
537
547
|
let errors = 0;
|
|
538
548
|
const tokensDir = resolveInclude(projectDir, includes.tokens);
|
|
539
|
-
const
|
|
540
|
-
"color.yaml": "color.schema.json",
|
|
541
|
-
"typography.yaml": "typography.schema.json",
|
|
542
|
-
"spacing.yaml": "spacing.schema.json",
|
|
543
|
-
"elevation.yaml": "elevation.schema.json",
|
|
544
|
-
"motion.yaml": "motion.schema.json",
|
|
545
|
-
"layout.yaml": "layout.schema.json",
|
|
546
|
-
"themes.yaml": "themes.schema.json",
|
|
547
|
-
"icons.yaml": "icons.schema.json",
|
|
548
|
-
};
|
|
549
|
-
for (const [data, schema] of Object.entries(tokenMap)) {
|
|
549
|
+
for (const [data, schema] of Object.entries(TOKEN_FILE_SCHEMAS)) {
|
|
550
550
|
const filePath = join(tokensDir, data);
|
|
551
551
|
if (existsSync(filePath)) {
|
|
552
552
|
errors += validateFile(ajv, filePath, `${BASE}tokens/${schema}`);
|
|
553
|
+
} else {
|
|
554
|
+
console.log(` FAIL ${data} (required token file is missing)`);
|
|
555
|
+
errors += 1;
|
|
553
556
|
}
|
|
554
557
|
}
|
|
555
558
|
return errors;
|
|
@@ -557,20 +560,16 @@ const GROUPS: Record<string, ValidationGroup> = {
|
|
|
557
560
|
collectJson(ajv, projectDir, includes, groupKey) {
|
|
558
561
|
const errors: JsonError[] = [];
|
|
559
562
|
const tokensDir = resolveInclude(projectDir, includes.tokens);
|
|
560
|
-
const
|
|
561
|
-
"color.yaml": "color.schema.json",
|
|
562
|
-
"typography.yaml": "typography.schema.json",
|
|
563
|
-
"spacing.yaml": "spacing.schema.json",
|
|
564
|
-
"elevation.yaml": "elevation.schema.json",
|
|
565
|
-
"motion.yaml": "motion.schema.json",
|
|
566
|
-
"layout.yaml": "layout.schema.json",
|
|
567
|
-
"themes.yaml": "themes.schema.json",
|
|
568
|
-
"icons.yaml": "icons.schema.json",
|
|
569
|
-
};
|
|
570
|
-
for (const [data, schema] of Object.entries(tokenMap)) {
|
|
563
|
+
for (const [data, schema] of Object.entries(TOKEN_FILE_SCHEMAS)) {
|
|
571
564
|
const filePath = join(tokensDir, data);
|
|
572
565
|
if (existsSync(filePath)) {
|
|
573
566
|
errors.push(...collectValidateFile(ajv, filePath, `${BASE}tokens/${schema}`));
|
|
567
|
+
} else {
|
|
568
|
+
errors.push({
|
|
569
|
+
file: data,
|
|
570
|
+
path: "(root)",
|
|
571
|
+
message: "required token file is missing",
|
|
572
|
+
});
|
|
574
573
|
}
|
|
575
574
|
}
|
|
576
575
|
return { group: groupKey, errors };
|