ui-ux-pro-max-cli 2.8.8
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 +99 -0
- package/assets/data/_sync_all.py +414 -0
- package/assets/data/app-interface.csv +31 -0
- package/assets/data/charts.csv +26 -0
- package/assets/data/colors.csv +162 -0
- package/assets/data/design.csv +1776 -0
- package/assets/data/draft.csv +1779 -0
- package/assets/data/google-fonts.csv +1924 -0
- package/assets/data/icons.csv +106 -0
- package/assets/data/landing.csv +35 -0
- package/assets/data/products.csv +162 -0
- package/assets/data/react-performance.csv +45 -0
- package/assets/data/stacks/angular.csv +51 -0
- package/assets/data/stacks/astro.csv +54 -0
- package/assets/data/stacks/flutter.csv +53 -0
- package/assets/data/stacks/html-tailwind.csv +56 -0
- package/assets/data/stacks/javafx.csv +76 -0
- package/assets/data/stacks/jetpack-compose.csv +53 -0
- package/assets/data/stacks/laravel.csv +51 -0
- package/assets/data/stacks/nextjs.csv +53 -0
- package/assets/data/stacks/nuxt-ui.csv +71 -0
- package/assets/data/stacks/nuxtjs.csv +59 -0
- package/assets/data/stacks/react-native.csv +52 -0
- package/assets/data/stacks/react.csv +54 -0
- package/assets/data/stacks/shadcn.csv +61 -0
- package/assets/data/stacks/svelte.csv +54 -0
- package/assets/data/stacks/swiftui.csv +51 -0
- package/assets/data/stacks/threejs.csv +54 -0
- package/assets/data/stacks/vue.csv +50 -0
- package/assets/data/styles.csv +85 -0
- package/assets/data/typography.csv +74 -0
- package/assets/data/ui-reasoning.csv +162 -0
- package/assets/data/ux-guidelines.csv +100 -0
- package/assets/scripts/core.py +263 -0
- package/assets/scripts/design_system.py +1157 -0
- package/assets/scripts/search.py +114 -0
- package/assets/skills/banner-design/SKILL.md +196 -0
- package/assets/skills/banner-design/references/banner-sizes-and-styles.md +118 -0
- package/assets/skills/brand/SKILL.md +97 -0
- package/assets/skills/brand/references/approval-checklist.md +169 -0
- package/assets/skills/brand/references/asset-organization.md +157 -0
- package/assets/skills/brand/references/brand-guideline-template.md +140 -0
- package/assets/skills/brand/references/color-palette-management.md +186 -0
- package/assets/skills/brand/references/consistency-checklist.md +94 -0
- package/assets/skills/brand/references/logo-usage-rules.md +185 -0
- package/assets/skills/brand/references/messaging-framework.md +85 -0
- package/assets/skills/brand/references/typography-specifications.md +214 -0
- package/assets/skills/brand/references/update.md +118 -0
- package/assets/skills/brand/references/visual-identity.md +96 -0
- package/assets/skills/brand/references/voice-framework.md +88 -0
- package/assets/skills/brand/scripts/extract-colors.cjs +341 -0
- package/assets/skills/brand/scripts/inject-brand-context.cjs +349 -0
- package/assets/skills/brand/scripts/sync-brand-to-tokens.cjs +266 -0
- package/assets/skills/brand/scripts/validate-asset.cjs +387 -0
- package/assets/skills/brand/templates/brand-guidelines-starter.md +275 -0
- package/assets/skills/design/SKILL.md +313 -0
- package/assets/skills/design/data/cip/deliverables.csv +51 -0
- package/assets/skills/design/data/cip/industries.csv +21 -0
- package/assets/skills/design/data/cip/mockup-contexts.csv +21 -0
- package/assets/skills/design/data/cip/styles.csv +21 -0
- package/assets/skills/design/data/icon/styles.csv +16 -0
- package/assets/skills/design/data/logo/colors.csv +56 -0
- package/assets/skills/design/data/logo/industries.csv +56 -0
- package/assets/skills/design/data/logo/styles.csv +56 -0
- package/assets/skills/design/references/banner-sizes-and-styles.md +118 -0
- package/assets/skills/design/references/cip-deliverable-guide.md +95 -0
- package/assets/skills/design/references/cip-design.md +121 -0
- package/assets/skills/design/references/cip-prompt-engineering.md +84 -0
- package/assets/skills/design/references/cip-style-guide.md +68 -0
- package/assets/skills/design/references/design-routing.md +207 -0
- package/assets/skills/design/references/icon-design.md +122 -0
- package/assets/skills/design/references/logo-color-psychology.md +101 -0
- package/assets/skills/design/references/logo-design.md +92 -0
- package/assets/skills/design/references/logo-prompt-engineering.md +158 -0
- package/assets/skills/design/references/logo-style-guide.md +109 -0
- package/assets/skills/design/references/slides-copywriting-formulas.md +84 -0
- package/assets/skills/design/references/slides-create.md +4 -0
- package/assets/skills/design/references/slides-html-template.md +295 -0
- package/assets/skills/design/references/slides-layout-patterns.md +137 -0
- package/assets/skills/design/references/slides-strategies.md +94 -0
- package/assets/skills/design/references/slides.md +42 -0
- package/assets/skills/design/references/social-photos-design.md +329 -0
- package/assets/skills/design/scripts/cip/core.py +215 -0
- package/assets/skills/design/scripts/cip/generate.py +484 -0
- package/assets/skills/design/scripts/cip/render-html.py +424 -0
- package/assets/skills/design/scripts/cip/search.py +127 -0
- package/assets/skills/design/scripts/icon/generate.py +487 -0
- package/assets/skills/design/scripts/logo/core.py +175 -0
- package/assets/skills/design/scripts/logo/generate.py +362 -0
- package/assets/skills/design/scripts/logo/search.py +114 -0
- package/assets/skills/design-system/SKILL.md +244 -0
- package/assets/skills/design-system/data/slide-backgrounds.csv +11 -0
- package/assets/skills/design-system/data/slide-charts.csv +26 -0
- package/assets/skills/design-system/data/slide-color-logic.csv +14 -0
- package/assets/skills/design-system/data/slide-copy.csv +26 -0
- package/assets/skills/design-system/data/slide-layout-logic.csv +16 -0
- package/assets/skills/design-system/data/slide-layouts.csv +26 -0
- package/assets/skills/design-system/data/slide-strategies.csv +16 -0
- package/assets/skills/design-system/data/slide-typography.csv +15 -0
- package/assets/skills/design-system/references/component-specs.md +236 -0
- package/assets/skills/design-system/references/component-tokens.md +214 -0
- package/assets/skills/design-system/references/primitive-tokens.md +203 -0
- package/assets/skills/design-system/references/semantic-tokens.md +215 -0
- package/assets/skills/design-system/references/states-and-variants.md +241 -0
- package/assets/skills/design-system/references/tailwind-integration.md +251 -0
- package/assets/skills/design-system/references/token-architecture.md +224 -0
- package/assets/skills/design-system/scripts/embed-tokens.cjs +99 -0
- package/assets/skills/design-system/scripts/fetch-background.py +317 -0
- package/assets/skills/design-system/scripts/generate-slide.py +770 -0
- package/assets/skills/design-system/scripts/generate-tokens.cjs +205 -0
- package/assets/skills/design-system/scripts/html-token-validator.py +327 -0
- package/assets/skills/design-system/scripts/search-slides.py +218 -0
- package/assets/skills/design-system/scripts/slide-token-validator.py +35 -0
- package/assets/skills/design-system/scripts/slide_search_core.py +453 -0
- package/assets/skills/design-system/scripts/validate-tokens.cjs +251 -0
- package/assets/skills/design-system/templates/design-tokens-starter.json +143 -0
- package/assets/skills/slides/SKILL.md +40 -0
- package/assets/skills/slides/references/copywriting-formulas.md +84 -0
- package/assets/skills/slides/references/create.md +4 -0
- package/assets/skills/slides/references/html-template.md +295 -0
- package/assets/skills/slides/references/layout-patterns.md +137 -0
- package/assets/skills/slides/references/slide-strategies.md +94 -0
- package/assets/skills/ui-styling/LICENSE.txt +202 -0
- package/assets/skills/ui-styling/SKILL.md +324 -0
- package/assets/skills/ui-styling/references/canvas-design-system.md +320 -0
- package/assets/skills/ui-styling/references/shadcn-accessibility.md +471 -0
- package/assets/skills/ui-styling/references/shadcn-components.md +424 -0
- package/assets/skills/ui-styling/references/shadcn-theming.md +373 -0
- package/assets/skills/ui-styling/references/tailwind-customization.md +483 -0
- package/assets/skills/ui-styling/references/tailwind-responsive.md +382 -0
- package/assets/skills/ui-styling/references/tailwind-utilities.md +455 -0
- package/assets/skills/ui-styling/scripts/requirements.txt +17 -0
- package/assets/skills/ui-styling/scripts/shadcn_add.py +308 -0
- package/assets/skills/ui-styling/scripts/tailwind_config_gen.py +473 -0
- package/assets/skills/ui-styling/scripts/tests/coverage-ui.json +1 -0
- package/assets/skills/ui-styling/scripts/tests/requirements.txt +3 -0
- package/assets/skills/ui-styling/scripts/tests/test_shadcn_add.py +266 -0
- package/assets/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py +336 -0
- package/assets/templates/base/quick-reference.md +297 -0
- package/assets/templates/base/skill-content.md +368 -0
- package/assets/templates/platforms/agent.json +21 -0
- package/assets/templates/platforms/augment.json +18 -0
- package/assets/templates/platforms/claude.json +21 -0
- package/assets/templates/platforms/codebuddy.json +21 -0
- package/assets/templates/platforms/codex.json +21 -0
- package/assets/templates/platforms/continue.json +21 -0
- package/assets/templates/platforms/copilot.json +21 -0
- package/assets/templates/platforms/cursor.json +21 -0
- package/assets/templates/platforms/droid.json +21 -0
- package/assets/templates/platforms/gemini.json +21 -0
- package/assets/templates/platforms/kilocode.json +21 -0
- package/assets/templates/platforms/kiro.json +21 -0
- package/assets/templates/platforms/opencode.json +21 -0
- package/assets/templates/platforms/qoder.json +21 -0
- package/assets/templates/platforms/roocode.json +21 -0
- package/assets/templates/platforms/trae.json +21 -0
- package/assets/templates/platforms/warp.json +18 -0
- package/assets/templates/platforms/windsurf.json +21 -0
- package/dist/index.js +10630 -0
- package/package.json +51 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate CSS variables from design tokens JSON
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node generate-tokens.cjs --config tokens.json -o tokens.css
|
|
7
|
+
* node generate-tokens.cjs --config tokens.json --format tailwind
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parse command line arguments
|
|
15
|
+
*/
|
|
16
|
+
function parseArgs() {
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const options = {
|
|
19
|
+
config: null,
|
|
20
|
+
output: null,
|
|
21
|
+
format: 'css' // css | tailwind
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < args.length; i++) {
|
|
25
|
+
if (args[i] === '--config' || args[i] === '-c') {
|
|
26
|
+
options.config = args[++i];
|
|
27
|
+
} else if (args[i] === '--output' || args[i] === '-o') {
|
|
28
|
+
options.output = args[++i];
|
|
29
|
+
} else if (args[i] === '--format' || args[i] === '-f') {
|
|
30
|
+
options.format = args[++i];
|
|
31
|
+
} else if (args[i] === '--help' || args[i] === '-h') {
|
|
32
|
+
console.log(`
|
|
33
|
+
Usage: node generate-tokens.cjs [options]
|
|
34
|
+
|
|
35
|
+
Options:
|
|
36
|
+
-c, --config <file> Input JSON token file (required)
|
|
37
|
+
-o, --output <file> Output file (default: stdout)
|
|
38
|
+
-f, --format <type> Output format: css | tailwind (default: css)
|
|
39
|
+
-h, --help Show this help
|
|
40
|
+
`);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return options;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve token references like {primitive.color.blue.600}
|
|
50
|
+
*/
|
|
51
|
+
function resolveReference(value, tokens) {
|
|
52
|
+
if (typeof value !== 'string' || !value.startsWith('{')) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const path = value.slice(1, -1).split('.');
|
|
57
|
+
let result = tokens;
|
|
58
|
+
|
|
59
|
+
for (const key of path) {
|
|
60
|
+
result = result?.[key];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (result?.$value) {
|
|
64
|
+
return resolveReference(result.$value, tokens);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return result || value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert token name to CSS variable name
|
|
72
|
+
*/
|
|
73
|
+
function toCssVarName(path) {
|
|
74
|
+
return '--' + path.join('-').replace(/\./g, '-');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Flatten tokens into CSS variables
|
|
79
|
+
*/
|
|
80
|
+
function flattenTokens(obj, tokens, prefix = [], result = {}) {
|
|
81
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
82
|
+
const currentPath = [...prefix, key];
|
|
83
|
+
|
|
84
|
+
if (value && typeof value === 'object') {
|
|
85
|
+
if (value.$value !== undefined) {
|
|
86
|
+
// This is a token
|
|
87
|
+
const cssVar = toCssVarName(currentPath);
|
|
88
|
+
const resolvedValue = resolveReference(value.$value, tokens);
|
|
89
|
+
result[cssVar] = resolvedValue;
|
|
90
|
+
} else {
|
|
91
|
+
// Recurse into nested object
|
|
92
|
+
flattenTokens(value, tokens, currentPath, result);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Generate CSS output
|
|
102
|
+
*/
|
|
103
|
+
function generateCSS(tokens) {
|
|
104
|
+
const primitive = flattenTokens(tokens.primitive || {}, tokens, ['primitive']);
|
|
105
|
+
const semantic = flattenTokens(tokens.semantic || {}, tokens, []);
|
|
106
|
+
const component = flattenTokens(tokens.component || {}, tokens, []);
|
|
107
|
+
const darkSemantic = flattenTokens(tokens.dark?.semantic || {}, tokens, []);
|
|
108
|
+
|
|
109
|
+
let css = `/* Design Tokens - Auto-generated */
|
|
110
|
+
/* Do not edit directly - modify tokens.json instead */
|
|
111
|
+
|
|
112
|
+
/* === PRIMITIVES === */
|
|
113
|
+
:root {
|
|
114
|
+
${Object.entries(primitive).map(([k, v]) => ` ${k}: ${v};`).join('\n')}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* === SEMANTIC === */
|
|
118
|
+
:root {
|
|
119
|
+
${Object.entries(semantic).map(([k, v]) => ` ${k}: ${v};`).join('\n')}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* === COMPONENTS === */
|
|
123
|
+
:root {
|
|
124
|
+
${Object.entries(component).map(([k, v]) => ` ${k}: ${v};`).join('\n')}
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
if (Object.keys(darkSemantic).length > 0) {
|
|
129
|
+
css += `
|
|
130
|
+
/* === DARK MODE === */
|
|
131
|
+
.dark {
|
|
132
|
+
${Object.entries(darkSemantic).map(([k, v]) => ` ${k}: ${v};`).join('\n')}
|
|
133
|
+
}
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return css;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Generate Tailwind config output
|
|
142
|
+
*/
|
|
143
|
+
function generateTailwind(tokens) {
|
|
144
|
+
const semantic = flattenTokens(tokens.semantic || {}, tokens, []);
|
|
145
|
+
|
|
146
|
+
// Extract colors for Tailwind
|
|
147
|
+
const colors = {};
|
|
148
|
+
for (const [key, value] of Object.entries(semantic)) {
|
|
149
|
+
if (key.includes('color')) {
|
|
150
|
+
const name = key.replace('--color-', '').replace(/-/g, '.');
|
|
151
|
+
colors[name] = `var(${key})`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return `// Tailwind color config - Auto-generated
|
|
156
|
+
// Add to tailwind.config.ts theme.extend.colors
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
colors: ${JSON.stringify(colors, null, 2).replace(/"/g, "'")}
|
|
160
|
+
};
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Main
|
|
166
|
+
*/
|
|
167
|
+
function main() {
|
|
168
|
+
const options = parseArgs();
|
|
169
|
+
|
|
170
|
+
if (!options.config) {
|
|
171
|
+
console.error('Error: --config is required');
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Resolve config path
|
|
176
|
+
const configPath = path.resolve(process.cwd(), options.config);
|
|
177
|
+
|
|
178
|
+
if (!fs.existsSync(configPath)) {
|
|
179
|
+
console.error(`Error: Config file not found: ${configPath}`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Read and parse tokens
|
|
184
|
+
const tokens = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
185
|
+
|
|
186
|
+
// Generate output
|
|
187
|
+
let output;
|
|
188
|
+
if (options.format === 'tailwind') {
|
|
189
|
+
output = generateTailwind(tokens);
|
|
190
|
+
} else {
|
|
191
|
+
output = generateCSS(tokens);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Write output
|
|
195
|
+
if (options.output) {
|
|
196
|
+
const outputPath = path.resolve(process.cwd(), options.output);
|
|
197
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
198
|
+
fs.writeFileSync(outputPath, output);
|
|
199
|
+
console.log(`Generated: ${outputPath}`);
|
|
200
|
+
} else {
|
|
201
|
+
console.log(output);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
main();
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
HTML Design Token Validator
|
|
4
|
+
Ensures all HTML assets (slides, infographics, etc.) use design tokens.
|
|
5
|
+
Source of truth: assets/design-tokens.css
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python html-token-validator.py # Validate all HTML assets
|
|
9
|
+
python html-token-validator.py --type slides # Validate only slides
|
|
10
|
+
python html-token-validator.py --type infographics # Validate only infographics
|
|
11
|
+
python html-token-validator.py path/to/file.html # Validate specific file
|
|
12
|
+
python html-token-validator.py --fix # Auto-fix issues (WIP)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Dict, List, Tuple, Optional
|
|
20
|
+
|
|
21
|
+
# Project root relative to this script
|
|
22
|
+
PROJECT_ROOT = Path(__file__).parent.parent.parent.parent.parent
|
|
23
|
+
TOKENS_JSON_PATH = PROJECT_ROOT / 'assets' / 'design-tokens.json'
|
|
24
|
+
TOKENS_CSS_PATH = PROJECT_ROOT / 'assets' / 'design-tokens.css'
|
|
25
|
+
|
|
26
|
+
# Asset directories to validate
|
|
27
|
+
ASSET_DIRS = {
|
|
28
|
+
'slides': PROJECT_ROOT / 'assets' / 'designs' / 'slides',
|
|
29
|
+
'infographics': PROJECT_ROOT / 'assets' / 'infographics',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Patterns that indicate hardcoded values (should use tokens)
|
|
33
|
+
FORBIDDEN_PATTERNS = [
|
|
34
|
+
(r'#[0-9A-Fa-f]{3,8}\b', 'hex color'),
|
|
35
|
+
(r'rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)', 'rgb color'),
|
|
36
|
+
(r'rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\)', 'rgba color'),
|
|
37
|
+
(r'hsl\([^)]+\)', 'hsl color'),
|
|
38
|
+
(r"font-family:\s*'[^v][^a][^r][^']*',", 'hardcoded font'), # Exclude var()
|
|
39
|
+
(r'font-family:\s*"[^v][^a][^r][^"]*",', 'hardcoded font'),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
# Allowed rgba patterns (brand colors with transparency - CSS limitation)
|
|
43
|
+
# These are derived from brand tokens but need rgba for transparency
|
|
44
|
+
ALLOWED_RGBA_PATTERNS = [
|
|
45
|
+
r'rgba\(\s*59\s*,\s*130\s*,\s*246', # --color-primary (#3B82F6)
|
|
46
|
+
r'rgba\(\s*245\s*,\s*158\s*,\s*11', # --color-secondary (#F59E0B)
|
|
47
|
+
r'rgba\(\s*16\s*,\s*185\s*,\s*129', # --color-accent (#10B981)
|
|
48
|
+
r'rgba\(\s*20\s*,\s*184\s*,\s*166', # --color-accent alt (#14B8A6)
|
|
49
|
+
r'rgba\(\s*0\s*,\s*0\s*,\s*0', # black transparency (common)
|
|
50
|
+
r'rgba\(\s*255\s*,\s*255\s*,\s*255', # white transparency (common)
|
|
51
|
+
r'rgba\(\s*15\s*,\s*23\s*,\s*42', # --color-surface (#0F172A)
|
|
52
|
+
r'rgba\(\s*7\s*,\s*11\s*,\s*20', # --color-background (#070B14)
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
# Allowed exceptions (external images, etc.)
|
|
56
|
+
ALLOWED_EXCEPTIONS = [
|
|
57
|
+
'pexels.com', 'unsplash.com', 'youtube.com', 'ytimg.com',
|
|
58
|
+
'googlefonts', 'fonts.googleapis.com', 'fonts.gstatic.com',
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ValidationResult:
|
|
63
|
+
"""Validation result for a single file."""
|
|
64
|
+
def __init__(self, file_path: Path):
|
|
65
|
+
self.file_path = file_path
|
|
66
|
+
self.errors: List[str] = []
|
|
67
|
+
self.warnings: List[str] = []
|
|
68
|
+
self.passed = True
|
|
69
|
+
|
|
70
|
+
def add_error(self, msg: str):
|
|
71
|
+
self.errors.append(msg)
|
|
72
|
+
self.passed = False
|
|
73
|
+
|
|
74
|
+
def add_warning(self, msg: str):
|
|
75
|
+
self.warnings.append(msg)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def load_css_variables() -> Dict[str, str]:
|
|
79
|
+
"""Load CSS variables from design-tokens.css."""
|
|
80
|
+
variables = {}
|
|
81
|
+
if TOKENS_CSS_PATH.exists():
|
|
82
|
+
content = TOKENS_CSS_PATH.read_text()
|
|
83
|
+
# Extract --var-name: value patterns
|
|
84
|
+
for match in re.finditer(r'(--[\w-]+):\s*([^;]+);', content):
|
|
85
|
+
variables[match.group(1)] = match.group(2).strip()
|
|
86
|
+
return variables
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def is_inside_block(content: str, match_pos: int, open_tag: str, close_tag: str) -> bool:
|
|
90
|
+
"""Check if position is inside a specific HTML block."""
|
|
91
|
+
pre = content[:match_pos]
|
|
92
|
+
tag_open = pre.rfind(open_tag)
|
|
93
|
+
tag_close = pre.rfind(close_tag)
|
|
94
|
+
return tag_open > tag_close
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def is_allowed_exception(context: str) -> bool:
|
|
98
|
+
"""Check if the hardcoded value is in an allowed exception context."""
|
|
99
|
+
context_lower = context.lower()
|
|
100
|
+
return any(exc in context_lower for exc in ALLOWED_EXCEPTIONS)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def is_allowed_rgba(match_text: str) -> bool:
|
|
104
|
+
"""Check if rgba pattern uses brand colors (allowed for transparency)."""
|
|
105
|
+
return any(re.match(pattern, match_text) for pattern in ALLOWED_RGBA_PATTERNS)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_context(content: str, pos: int, chars: int = 100) -> str:
|
|
109
|
+
"""Get surrounding context for a match position."""
|
|
110
|
+
start = max(0, pos - chars)
|
|
111
|
+
end = min(len(content), pos + chars)
|
|
112
|
+
return content[start:end]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def validate_html(content: str, file_path: Path, verbose: bool = False) -> ValidationResult:
|
|
116
|
+
"""
|
|
117
|
+
Validate HTML content for design token compliance.
|
|
118
|
+
|
|
119
|
+
Checks:
|
|
120
|
+
1. design-tokens.css import present
|
|
121
|
+
2. No hardcoded colors in CSS (except in <script> for Chart.js)
|
|
122
|
+
3. No hardcoded fonts
|
|
123
|
+
4. Uses var(--token-name) pattern
|
|
124
|
+
"""
|
|
125
|
+
result = ValidationResult(file_path)
|
|
126
|
+
|
|
127
|
+
# 1. Check for design-tokens.css import
|
|
128
|
+
if 'design-tokens.css' not in content:
|
|
129
|
+
result.add_error("Missing design-tokens.css import")
|
|
130
|
+
|
|
131
|
+
# 2. Check for forbidden patterns in CSS
|
|
132
|
+
for pattern, description in FORBIDDEN_PATTERNS:
|
|
133
|
+
for match in re.finditer(pattern, content):
|
|
134
|
+
match_text = match.group()
|
|
135
|
+
match_pos = match.start()
|
|
136
|
+
context = get_context(content, match_pos)
|
|
137
|
+
|
|
138
|
+
# Skip if in <script> block (Chart.js allowed)
|
|
139
|
+
if is_inside_block(content, match_pos, '<script', '</script>'):
|
|
140
|
+
if verbose:
|
|
141
|
+
result.add_warning(f"Allowed in <script>: {match_text}")
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
# Skip if in allowed exception context (external URLs)
|
|
145
|
+
if is_allowed_exception(context):
|
|
146
|
+
if verbose:
|
|
147
|
+
result.add_warning(f"Allowed external: {match_text}")
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
# Skip rgba using brand colors (needed for transparency effects)
|
|
151
|
+
if description == 'rgba color' and is_allowed_rgba(match_text):
|
|
152
|
+
if verbose:
|
|
153
|
+
result.add_warning(f"Allowed brand rgba: {match_text}")
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
# Skip if part of var() reference (false positive)
|
|
157
|
+
if 'var(' in context and match_text in context:
|
|
158
|
+
# Check if it's a fallback value in var()
|
|
159
|
+
var_pattern = rf'var\([^)]*{re.escape(match_text)}[^)]*\)'
|
|
160
|
+
if re.search(var_pattern, context):
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
# Error if in <style> or inline style
|
|
164
|
+
if is_inside_block(content, match_pos, '<style', '</style>'):
|
|
165
|
+
result.add_error(f"Hardcoded {description} in <style>: {match_text}")
|
|
166
|
+
elif 'style="' in context:
|
|
167
|
+
result.add_error(f"Hardcoded {description} in inline style: {match_text}")
|
|
168
|
+
|
|
169
|
+
# 3. Check for required var() usage indicators
|
|
170
|
+
token_patterns = [
|
|
171
|
+
r'var\(--color-',
|
|
172
|
+
r'var\(--primitive-',
|
|
173
|
+
r'var\(--typography-',
|
|
174
|
+
r'var\(--card-',
|
|
175
|
+
r'var\(--button-',
|
|
176
|
+
]
|
|
177
|
+
token_count = sum(len(re.findall(p, content)) for p in token_patterns)
|
|
178
|
+
|
|
179
|
+
if token_count < 5:
|
|
180
|
+
result.add_warning(f"Low token usage ({token_count} var() references). Consider using more design tokens.")
|
|
181
|
+
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def validate_file(file_path: Path, verbose: bool = False) -> ValidationResult:
|
|
186
|
+
"""Validate a single HTML file."""
|
|
187
|
+
if not file_path.exists():
|
|
188
|
+
result = ValidationResult(file_path)
|
|
189
|
+
result.add_error("File not found")
|
|
190
|
+
return result
|
|
191
|
+
|
|
192
|
+
content = file_path.read_text()
|
|
193
|
+
return validate_html(content, file_path, verbose)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def validate_directory(dir_path: Path, verbose: bool = False) -> List[ValidationResult]:
|
|
197
|
+
"""Validate all HTML files in a directory."""
|
|
198
|
+
results = []
|
|
199
|
+
if dir_path.exists():
|
|
200
|
+
for html_file in sorted(dir_path.glob('*.html')):
|
|
201
|
+
results.append(validate_file(html_file, verbose))
|
|
202
|
+
return results
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def print_result(result: ValidationResult, verbose: bool = False):
|
|
206
|
+
"""Print validation result for a file."""
|
|
207
|
+
status = "✓" if result.passed else "✗"
|
|
208
|
+
print(f" {status} {result.file_path.name}")
|
|
209
|
+
|
|
210
|
+
if result.errors:
|
|
211
|
+
for error in result.errors[:5]: # Limit output
|
|
212
|
+
print(f" ├─ {error}")
|
|
213
|
+
if len(result.errors) > 5:
|
|
214
|
+
print(f" └─ ... and {len(result.errors) - 5} more errors")
|
|
215
|
+
|
|
216
|
+
if verbose and result.warnings:
|
|
217
|
+
for warning in result.warnings[:3]:
|
|
218
|
+
print(f" [warn] {warning}")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def print_summary(all_results: Dict[str, List[ValidationResult]]):
|
|
222
|
+
"""Print summary of all validation results."""
|
|
223
|
+
total_files = 0
|
|
224
|
+
total_passed = 0
|
|
225
|
+
total_errors = 0
|
|
226
|
+
|
|
227
|
+
print("\n" + "=" * 60)
|
|
228
|
+
print("HTML DESIGN TOKEN VALIDATION SUMMARY")
|
|
229
|
+
print("=" * 60)
|
|
230
|
+
|
|
231
|
+
for asset_type, results in all_results.items():
|
|
232
|
+
if not results:
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
passed = sum(1 for r in results if r.passed)
|
|
236
|
+
failed = len(results) - passed
|
|
237
|
+
errors = sum(len(r.errors) for r in results)
|
|
238
|
+
|
|
239
|
+
total_files += len(results)
|
|
240
|
+
total_passed += passed
|
|
241
|
+
total_errors += errors
|
|
242
|
+
|
|
243
|
+
status = "✓" if failed == 0 else "✗"
|
|
244
|
+
print(f"\n{status} {asset_type.upper()}: {passed}/{len(results)} passed")
|
|
245
|
+
|
|
246
|
+
for result in results:
|
|
247
|
+
if not result.passed:
|
|
248
|
+
print_result(result)
|
|
249
|
+
|
|
250
|
+
print("\n" + "-" * 60)
|
|
251
|
+
if total_errors == 0:
|
|
252
|
+
print(f"✓ ALL PASSED: {total_passed}/{total_files} files valid")
|
|
253
|
+
else:
|
|
254
|
+
print(f"✗ FAILED: {total_files - total_passed}/{total_files} files have issues ({total_errors} total errors)")
|
|
255
|
+
print("-" * 60)
|
|
256
|
+
|
|
257
|
+
return total_errors == 0
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def main():
|
|
261
|
+
"""CLI entry point."""
|
|
262
|
+
import argparse
|
|
263
|
+
|
|
264
|
+
parser = argparse.ArgumentParser(
|
|
265
|
+
description='Validate HTML assets for design token compliance',
|
|
266
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
267
|
+
epilog="""
|
|
268
|
+
Examples:
|
|
269
|
+
%(prog)s # Validate all HTML assets
|
|
270
|
+
%(prog)s --type slides # Validate only slides
|
|
271
|
+
%(prog)s --type infographics # Validate only infographics
|
|
272
|
+
%(prog)s path/to/file.html # Validate specific file
|
|
273
|
+
%(prog)s --colors # Show brand colors from tokens
|
|
274
|
+
"""
|
|
275
|
+
)
|
|
276
|
+
parser.add_argument('files', nargs='*', help='Specific HTML files to validate')
|
|
277
|
+
parser.add_argument('-t', '--type', choices=['slides', 'infographics', 'all'],
|
|
278
|
+
default='all', help='Asset type to validate')
|
|
279
|
+
parser.add_argument('-v', '--verbose', action='store_true', help='Show warnings')
|
|
280
|
+
parser.add_argument('--colors', action='store_true', help='Print CSS variables from tokens')
|
|
281
|
+
parser.add_argument('--fix', action='store_true', help='Auto-fix issues (experimental)')
|
|
282
|
+
|
|
283
|
+
args = parser.parse_args()
|
|
284
|
+
|
|
285
|
+
# Show colors mode
|
|
286
|
+
if args.colors:
|
|
287
|
+
variables = load_css_variables()
|
|
288
|
+
print("\nDesign Tokens (from design-tokens.css):")
|
|
289
|
+
print("-" * 40)
|
|
290
|
+
for name, value in sorted(variables.items())[:30]:
|
|
291
|
+
print(f" {name}: {value}")
|
|
292
|
+
if len(variables) > 30:
|
|
293
|
+
print(f" ... and {len(variables) - 30} more")
|
|
294
|
+
return
|
|
295
|
+
|
|
296
|
+
all_results: Dict[str, List[ValidationResult]] = {}
|
|
297
|
+
|
|
298
|
+
# Validate specific files
|
|
299
|
+
if args.files:
|
|
300
|
+
results = []
|
|
301
|
+
for file_path in args.files:
|
|
302
|
+
path = Path(file_path)
|
|
303
|
+
if path.exists():
|
|
304
|
+
results.append(validate_file(path, args.verbose))
|
|
305
|
+
else:
|
|
306
|
+
result = ValidationResult(path)
|
|
307
|
+
result.add_error("File not found")
|
|
308
|
+
results.append(result)
|
|
309
|
+
all_results['specified'] = results
|
|
310
|
+
else:
|
|
311
|
+
# Validate by type
|
|
312
|
+
types_to_check = ASSET_DIRS.keys() if args.type == 'all' else [args.type]
|
|
313
|
+
|
|
314
|
+
for asset_type in types_to_check:
|
|
315
|
+
if asset_type in ASSET_DIRS:
|
|
316
|
+
results = validate_directory(ASSET_DIRS[asset_type], args.verbose)
|
|
317
|
+
all_results[asset_type] = results
|
|
318
|
+
|
|
319
|
+
# Print results
|
|
320
|
+
success = print_summary(all_results)
|
|
321
|
+
|
|
322
|
+
if not success:
|
|
323
|
+
sys.exit(1)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
if __name__ == '__main__':
|
|
327
|
+
main()
|