@dryui/mcp 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/dist/ai-surface.d.ts +10 -0
- package/dist/architecture.d.ts +103 -0
- package/dist/architecture.js +6262 -0
- package/dist/architecture.json +24419 -0
- package/dist/check-contract.d.ts +1 -0
- package/dist/composition-data.d.ts +27 -0
- package/dist/composition-data.js +5502 -0
- package/dist/contract.d.ts +41 -0
- package/dist/contract.v1.json +22804 -0
- package/dist/contract.v1.schema.json +523 -0
- package/dist/generate-architecture.d.ts +1 -0
- package/dist/generate-contract.d.ts +1 -0
- package/dist/generate-llms-txt.d.ts +6 -0
- package/dist/generate-spec.d.ts +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +31474 -0
- package/dist/project-planner.d.ts +73 -0
- package/dist/project-planner.js +374 -0
- package/dist/reviewer.d.ts +28 -0
- package/dist/reviewer.js +744 -0
- package/dist/spec-formatters.d.ts +19 -0
- package/dist/spec-formatters.js +256 -0
- package/dist/spec-types.d.ts +83 -0
- package/dist/spec-types.js +0 -0
- package/dist/spec.json +22976 -0
- package/dist/theme-checker.d.ts +22 -0
- package/dist/theme-checker.js +823 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +72 -0
- package/dist/workspace-audit.d.ts +54 -0
- package/dist/workspace-audit.js +2099 -0
- package/package.json +94 -0
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
function __accessProp(key) {
|
|
7
|
+
return this[key];
|
|
8
|
+
}
|
|
9
|
+
var __toESMCache_node;
|
|
10
|
+
var __toESMCache_esm;
|
|
11
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
12
|
+
var canCache = mod != null && typeof mod === "object";
|
|
13
|
+
if (canCache) {
|
|
14
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
15
|
+
var cached = cache.get(mod);
|
|
16
|
+
if (cached)
|
|
17
|
+
return cached;
|
|
18
|
+
}
|
|
19
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
20
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
21
|
+
for (let key of __getOwnPropNames(mod))
|
|
22
|
+
if (!__hasOwnProp.call(to, key))
|
|
23
|
+
__defProp(to, key, {
|
|
24
|
+
get: __accessProp.bind(mod, key),
|
|
25
|
+
enumerable: true
|
|
26
|
+
});
|
|
27
|
+
if (canCache)
|
|
28
|
+
cache.set(mod, to);
|
|
29
|
+
return to;
|
|
30
|
+
};
|
|
31
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
32
|
+
var __returnValue = (v) => v;
|
|
33
|
+
function __exportSetter(name, newValue) {
|
|
34
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
35
|
+
}
|
|
36
|
+
var __export = (target, all) => {
|
|
37
|
+
for (var name in all)
|
|
38
|
+
__defProp(target, name, {
|
|
39
|
+
get: all[name],
|
|
40
|
+
enumerable: true,
|
|
41
|
+
configurable: true,
|
|
42
|
+
set: __exportSetter.bind(all, name)
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/utils.ts
|
|
47
|
+
function buildLineOffsets(text) {
|
|
48
|
+
const offsets = [0];
|
|
49
|
+
for (let i = 0;i < text.length; i++) {
|
|
50
|
+
if (text[i] === `
|
|
51
|
+
`)
|
|
52
|
+
offsets.push(i + 1);
|
|
53
|
+
}
|
|
54
|
+
return offsets;
|
|
55
|
+
}
|
|
56
|
+
function lineAtOffset(lineOffsets, offset) {
|
|
57
|
+
let lo = 0;
|
|
58
|
+
let hi = lineOffsets.length - 1;
|
|
59
|
+
while (lo < hi) {
|
|
60
|
+
const mid = lo + hi + 1 >> 1;
|
|
61
|
+
const midVal = lineOffsets[mid];
|
|
62
|
+
if (midVal !== undefined && midVal <= offset)
|
|
63
|
+
lo = mid;
|
|
64
|
+
else
|
|
65
|
+
hi = mid - 1;
|
|
66
|
+
}
|
|
67
|
+
return lo + 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/theme-checker.ts
|
|
71
|
+
function capture(match, index) {
|
|
72
|
+
const value = match[index];
|
|
73
|
+
if (value === undefined)
|
|
74
|
+
throw new Error(`Missing capture group ${index}`);
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
var REQUIRED_TOKENS = [
|
|
78
|
+
"--dry-color-text-strong",
|
|
79
|
+
"--dry-color-text-weak",
|
|
80
|
+
"--dry-color-icon",
|
|
81
|
+
"--dry-color-stroke-strong",
|
|
82
|
+
"--dry-color-stroke-weak",
|
|
83
|
+
"--dry-color-fill",
|
|
84
|
+
"--dry-color-fill-hover",
|
|
85
|
+
"--dry-color-fill-active",
|
|
86
|
+
"--dry-color-brand",
|
|
87
|
+
"--dry-color-text-brand",
|
|
88
|
+
"--dry-color-fill-brand",
|
|
89
|
+
"--dry-color-fill-brand-hover",
|
|
90
|
+
"--dry-color-fill-brand-active",
|
|
91
|
+
"--dry-color-fill-brand-weak",
|
|
92
|
+
"--dry-color-stroke-brand",
|
|
93
|
+
"--dry-color-on-brand",
|
|
94
|
+
"--dry-color-focus-ring",
|
|
95
|
+
"--dry-color-bg-base",
|
|
96
|
+
"--dry-color-bg-raised",
|
|
97
|
+
"--dry-color-bg-overlay",
|
|
98
|
+
"--dry-color-text-error",
|
|
99
|
+
"--dry-color-fill-error",
|
|
100
|
+
"--dry-color-fill-error-hover",
|
|
101
|
+
"--dry-color-fill-error-weak",
|
|
102
|
+
"--dry-color-stroke-error",
|
|
103
|
+
"--dry-color-on-error",
|
|
104
|
+
"--dry-color-text-warning",
|
|
105
|
+
"--dry-color-fill-warning",
|
|
106
|
+
"--dry-color-fill-warning-hover",
|
|
107
|
+
"--dry-color-fill-warning-weak",
|
|
108
|
+
"--dry-color-stroke-warning",
|
|
109
|
+
"--dry-color-on-warning",
|
|
110
|
+
"--dry-color-text-success",
|
|
111
|
+
"--dry-color-fill-success",
|
|
112
|
+
"--dry-color-fill-success-hover",
|
|
113
|
+
"--dry-color-fill-success-weak",
|
|
114
|
+
"--dry-color-stroke-success",
|
|
115
|
+
"--dry-color-on-success",
|
|
116
|
+
"--dry-color-text-info",
|
|
117
|
+
"--dry-color-fill-info",
|
|
118
|
+
"--dry-color-fill-info-hover",
|
|
119
|
+
"--dry-color-fill-info-weak",
|
|
120
|
+
"--dry-color-stroke-info",
|
|
121
|
+
"--dry-color-on-info",
|
|
122
|
+
"--dry-shadow-raised",
|
|
123
|
+
"--dry-shadow-overlay",
|
|
124
|
+
"--dry-color-overlay-backdrop",
|
|
125
|
+
"--dry-color-overlay-backdrop-strong"
|
|
126
|
+
];
|
|
127
|
+
var FULL_THEME_THRESHOLD = 4;
|
|
128
|
+
var NAMED_COLORS = new Set([
|
|
129
|
+
"aliceblue",
|
|
130
|
+
"antiquewhite",
|
|
131
|
+
"aqua",
|
|
132
|
+
"aquamarine",
|
|
133
|
+
"azure",
|
|
134
|
+
"beige",
|
|
135
|
+
"bisque",
|
|
136
|
+
"black",
|
|
137
|
+
"blanchedalmond",
|
|
138
|
+
"blue",
|
|
139
|
+
"blueviolet",
|
|
140
|
+
"brown",
|
|
141
|
+
"burlywood",
|
|
142
|
+
"cadetblue",
|
|
143
|
+
"chartreuse",
|
|
144
|
+
"chocolate",
|
|
145
|
+
"coral",
|
|
146
|
+
"cornflowerblue",
|
|
147
|
+
"cornsilk",
|
|
148
|
+
"crimson",
|
|
149
|
+
"cyan",
|
|
150
|
+
"darkblue",
|
|
151
|
+
"darkcyan",
|
|
152
|
+
"darkgoldenrod",
|
|
153
|
+
"darkgray",
|
|
154
|
+
"darkgreen",
|
|
155
|
+
"darkgrey",
|
|
156
|
+
"darkkhaki",
|
|
157
|
+
"darkmagenta",
|
|
158
|
+
"darkolivegreen",
|
|
159
|
+
"darkorange",
|
|
160
|
+
"darkorchid",
|
|
161
|
+
"darkred",
|
|
162
|
+
"darksalmon",
|
|
163
|
+
"darkseagreen",
|
|
164
|
+
"darkslateblue",
|
|
165
|
+
"darkslategray",
|
|
166
|
+
"darkslategrey",
|
|
167
|
+
"darkturquoise",
|
|
168
|
+
"darkviolet",
|
|
169
|
+
"deeppink",
|
|
170
|
+
"deepskyblue",
|
|
171
|
+
"dimgray",
|
|
172
|
+
"dimgrey",
|
|
173
|
+
"dodgerblue",
|
|
174
|
+
"firebrick",
|
|
175
|
+
"floralwhite",
|
|
176
|
+
"forestgreen",
|
|
177
|
+
"fuchsia",
|
|
178
|
+
"gainsboro",
|
|
179
|
+
"ghostwhite",
|
|
180
|
+
"gold",
|
|
181
|
+
"goldenrod",
|
|
182
|
+
"gray",
|
|
183
|
+
"green",
|
|
184
|
+
"greenyellow",
|
|
185
|
+
"grey",
|
|
186
|
+
"honeydew",
|
|
187
|
+
"hotpink",
|
|
188
|
+
"indianred",
|
|
189
|
+
"indigo",
|
|
190
|
+
"ivory",
|
|
191
|
+
"khaki",
|
|
192
|
+
"lavender",
|
|
193
|
+
"lavenderblush",
|
|
194
|
+
"lawngreen",
|
|
195
|
+
"lemonchiffon",
|
|
196
|
+
"lightblue",
|
|
197
|
+
"lightcoral",
|
|
198
|
+
"lightcyan",
|
|
199
|
+
"lightgoldenrodyellow",
|
|
200
|
+
"lightgray",
|
|
201
|
+
"lightgreen",
|
|
202
|
+
"lightgrey",
|
|
203
|
+
"lightpink",
|
|
204
|
+
"lightsalmon",
|
|
205
|
+
"lightseagreen",
|
|
206
|
+
"lightskyblue",
|
|
207
|
+
"lightslategray",
|
|
208
|
+
"lightslategrey",
|
|
209
|
+
"lightsteelblue",
|
|
210
|
+
"lightyellow",
|
|
211
|
+
"lime",
|
|
212
|
+
"limegreen",
|
|
213
|
+
"linen",
|
|
214
|
+
"magenta",
|
|
215
|
+
"maroon",
|
|
216
|
+
"mediumaquamarine",
|
|
217
|
+
"mediumblue",
|
|
218
|
+
"mediumorchid",
|
|
219
|
+
"mediumpurple",
|
|
220
|
+
"mediumseagreen",
|
|
221
|
+
"mediumslateblue",
|
|
222
|
+
"mediumspringgreen",
|
|
223
|
+
"mediumturquoise",
|
|
224
|
+
"mediumvioletred",
|
|
225
|
+
"midnightblue",
|
|
226
|
+
"mintcream",
|
|
227
|
+
"mistyrose",
|
|
228
|
+
"moccasin",
|
|
229
|
+
"navajowhite",
|
|
230
|
+
"navy",
|
|
231
|
+
"oldlace",
|
|
232
|
+
"olive",
|
|
233
|
+
"olivedrab",
|
|
234
|
+
"orange",
|
|
235
|
+
"orangered",
|
|
236
|
+
"orchid",
|
|
237
|
+
"palegoldenrod",
|
|
238
|
+
"palegreen",
|
|
239
|
+
"paleturquoise",
|
|
240
|
+
"palevioletred",
|
|
241
|
+
"papayawhip",
|
|
242
|
+
"peachpuff",
|
|
243
|
+
"peru",
|
|
244
|
+
"pink",
|
|
245
|
+
"plum",
|
|
246
|
+
"powderblue",
|
|
247
|
+
"purple",
|
|
248
|
+
"rebeccapurple",
|
|
249
|
+
"red",
|
|
250
|
+
"rosybrown",
|
|
251
|
+
"royalblue",
|
|
252
|
+
"saddlebrown",
|
|
253
|
+
"salmon",
|
|
254
|
+
"sandybrown",
|
|
255
|
+
"seagreen",
|
|
256
|
+
"seashell",
|
|
257
|
+
"sienna",
|
|
258
|
+
"silver",
|
|
259
|
+
"skyblue",
|
|
260
|
+
"slateblue",
|
|
261
|
+
"slategray",
|
|
262
|
+
"slategrey",
|
|
263
|
+
"snow",
|
|
264
|
+
"springgreen",
|
|
265
|
+
"steelblue",
|
|
266
|
+
"tan",
|
|
267
|
+
"teal",
|
|
268
|
+
"thistle",
|
|
269
|
+
"tomato",
|
|
270
|
+
"turquoise",
|
|
271
|
+
"violet",
|
|
272
|
+
"wheat",
|
|
273
|
+
"white",
|
|
274
|
+
"whitesmoke",
|
|
275
|
+
"yellow",
|
|
276
|
+
"yellowgreen",
|
|
277
|
+
"transparent",
|
|
278
|
+
"currentcolor",
|
|
279
|
+
"inherit"
|
|
280
|
+
]);
|
|
281
|
+
var SURFACE_TOKENS = new Set([
|
|
282
|
+
"--dry-color-bg-base",
|
|
283
|
+
"--dry-color-bg-raised",
|
|
284
|
+
"--dry-color-bg-overlay"
|
|
285
|
+
]);
|
|
286
|
+
function stripComments(css) {
|
|
287
|
+
return css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
288
|
+
}
|
|
289
|
+
function extractDryVariables(css) {
|
|
290
|
+
const cleaned = stripComments(css);
|
|
291
|
+
const vars = new Map;
|
|
292
|
+
const regex = /(--dry-[a-zA-Z0-9-]+)\s*:\s*([^;]*);/g;
|
|
293
|
+
let match;
|
|
294
|
+
const lineOffsets = buildLineOffsets(cleaned);
|
|
295
|
+
while ((match = regex.exec(cleaned)) !== null) {
|
|
296
|
+
const name = capture(match, 1);
|
|
297
|
+
const value = (match[2] ?? "").trim();
|
|
298
|
+
const line = lineAtOffset(lineOffsets, match.index);
|
|
299
|
+
vars.set(name, { value, line });
|
|
300
|
+
}
|
|
301
|
+
return vars;
|
|
302
|
+
}
|
|
303
|
+
function extractAllVariables(css) {
|
|
304
|
+
const cleaned = stripComments(css);
|
|
305
|
+
const vars = new Map;
|
|
306
|
+
const regex = /(--[a-zA-Z0-9-]+)\s*:\s*([^;]*);/g;
|
|
307
|
+
let match;
|
|
308
|
+
while ((match = regex.exec(cleaned)) !== null) {
|
|
309
|
+
const name = capture(match, 1);
|
|
310
|
+
const value = (match[2] ?? "").trim();
|
|
311
|
+
vars.set(name, value);
|
|
312
|
+
}
|
|
313
|
+
return vars;
|
|
314
|
+
}
|
|
315
|
+
function parseVarFunc(value) {
|
|
316
|
+
const prefix = /^var\(\s*/.exec(value);
|
|
317
|
+
if (!prefix)
|
|
318
|
+
return null;
|
|
319
|
+
let pos = prefix[0].length;
|
|
320
|
+
const nameMatch = /^(--[a-zA-Z0-9-]+)/.exec(value.slice(pos));
|
|
321
|
+
if (!nameMatch)
|
|
322
|
+
return null;
|
|
323
|
+
const refName = capture(nameMatch, 1);
|
|
324
|
+
pos += refName.length;
|
|
325
|
+
while (pos < value.length && /\s/.test(value[pos]))
|
|
326
|
+
pos++;
|
|
327
|
+
if (value[pos] === ")")
|
|
328
|
+
return { refName, fallback: null };
|
|
329
|
+
if (value[pos] !== ",")
|
|
330
|
+
return { refName, fallback: null };
|
|
331
|
+
pos++;
|
|
332
|
+
while (pos < value.length && /\s/.test(value[pos]))
|
|
333
|
+
pos++;
|
|
334
|
+
let depth = 1;
|
|
335
|
+
const fallbackStart = pos;
|
|
336
|
+
while (pos < value.length && depth > 0) {
|
|
337
|
+
if (value[pos] === "(")
|
|
338
|
+
depth++;
|
|
339
|
+
else if (value[pos] === ")")
|
|
340
|
+
depth--;
|
|
341
|
+
if (depth > 0)
|
|
342
|
+
pos++;
|
|
343
|
+
}
|
|
344
|
+
const fallback = value.slice(fallbackStart, pos).trim();
|
|
345
|
+
return { refName, fallback: fallback || null };
|
|
346
|
+
}
|
|
347
|
+
function resolveVarReferences(dryVars, allVars) {
|
|
348
|
+
const resolved = new Map;
|
|
349
|
+
for (const [name, entry] of dryVars) {
|
|
350
|
+
const parsed = parseVarFunc(entry.value);
|
|
351
|
+
if (!parsed) {
|
|
352
|
+
resolved.set(name, { original: entry.value, resolved: entry.value, line: entry.line });
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const { refName, fallback } = parsed;
|
|
356
|
+
const refValue = allVars.get(refName);
|
|
357
|
+
if (refValue !== undefined) {
|
|
358
|
+
resolved.set(name, { original: entry.value, resolved: refValue, line: entry.line });
|
|
359
|
+
} else if (fallback !== null) {
|
|
360
|
+
resolved.set(name, { original: entry.value, resolved: fallback, line: entry.line });
|
|
361
|
+
} else {
|
|
362
|
+
resolved.set(name, { original: entry.value, resolved: entry.value, line: entry.line });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return resolved;
|
|
366
|
+
}
|
|
367
|
+
function classifyValue(value) {
|
|
368
|
+
const v = value.trim();
|
|
369
|
+
if (!v)
|
|
370
|
+
return "other";
|
|
371
|
+
if (/^#([0-9a-fA-F]{3,8})$/.test(v))
|
|
372
|
+
return "color";
|
|
373
|
+
if (/^rgba?\s*\(/.test(v))
|
|
374
|
+
return "color";
|
|
375
|
+
if (/^hsla?\s*\(/.test(v))
|
|
376
|
+
return "color";
|
|
377
|
+
if (/^color-mix\s*\(/.test(v))
|
|
378
|
+
return "color";
|
|
379
|
+
if (NAMED_COLORS.has(v.toLowerCase()))
|
|
380
|
+
return "color";
|
|
381
|
+
if (/^(?:oklch|lch|lab|oklab|color)\s*\(/.test(v))
|
|
382
|
+
return "color";
|
|
383
|
+
if (/^-?[\d.]+(?:px|rem|em|%|vw|vh)$/.test(v))
|
|
384
|
+
return "length";
|
|
385
|
+
if (/^-?[\d.]+(?:ms|s)$/.test(v))
|
|
386
|
+
return "time";
|
|
387
|
+
if (/^-?[\d.]+(?:px|rem|em)\s+-?[\d.]+(?:px|rem|em)/.test(v))
|
|
388
|
+
return "shadow";
|
|
389
|
+
if (/^["']/.test(v))
|
|
390
|
+
return "font";
|
|
391
|
+
if (/,\s*["']?[a-zA-Z]/.test(v) && !/^(rgb|hsl)/.test(v))
|
|
392
|
+
return "font";
|
|
393
|
+
return "other";
|
|
394
|
+
}
|
|
395
|
+
function parseColor(value) {
|
|
396
|
+
const v = value.trim();
|
|
397
|
+
const hexMatch = v.match(/^#([0-9a-fA-F]{3,8})$/);
|
|
398
|
+
if (hexMatch) {
|
|
399
|
+
const hex = capture(hexMatch, 1);
|
|
400
|
+
if (hex.length === 3) {
|
|
401
|
+
return {
|
|
402
|
+
r: parseInt(hex.charAt(0) + hex.charAt(0), 16),
|
|
403
|
+
g: parseInt(hex.charAt(1) + hex.charAt(1), 16),
|
|
404
|
+
b: parseInt(hex.charAt(2) + hex.charAt(2), 16)
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
408
|
+
return {
|
|
409
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
410
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
411
|
+
b: parseInt(hex.slice(4, 6), 16)
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
const rgbMatch = v.match(/^rgba?\(\s*(\d+)\s*[,/]\s*(\d+)\s*[,/]\s*(\d+)/);
|
|
417
|
+
if (rgbMatch) {
|
|
418
|
+
return {
|
|
419
|
+
r: parseInt(capture(rgbMatch, 1), 10),
|
|
420
|
+
g: parseInt(capture(rgbMatch, 2), 10),
|
|
421
|
+
b: parseInt(capture(rgbMatch, 3), 10)
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const hslMatch = v.match(/^hsla?\(\s*(\d+)\s*[,/]\s*([\d.]+)%?\s*[,/]\s*([\d.]+)%?/);
|
|
425
|
+
if (hslMatch) {
|
|
426
|
+
const h = parseInt(capture(hslMatch, 1), 10);
|
|
427
|
+
const s = parseFloat(capture(hslMatch, 2)) / 100;
|
|
428
|
+
const l = parseFloat(capture(hslMatch, 3)) / 100;
|
|
429
|
+
return hslToRgb(h, s, l);
|
|
430
|
+
}
|
|
431
|
+
const modernRgbMatch = v.match(/^rgba?\(\s*(\d+)\s+(\d+)\s+(\d+)/);
|
|
432
|
+
if (modernRgbMatch) {
|
|
433
|
+
return {
|
|
434
|
+
r: parseInt(capture(modernRgbMatch, 1), 10),
|
|
435
|
+
g: parseInt(capture(modernRgbMatch, 2), 10),
|
|
436
|
+
b: parseInt(capture(modernRgbMatch, 3), 10)
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
const modernHslMatch = v.match(/^hsla?\(\s*(\d+)\s+([\d.]+)%?\s+([\d.]+)%?/);
|
|
440
|
+
if (modernHslMatch) {
|
|
441
|
+
const h = parseInt(capture(modernHslMatch, 1), 10);
|
|
442
|
+
const s = parseFloat(capture(modernHslMatch, 2)) / 100;
|
|
443
|
+
const l = parseFloat(capture(modernHslMatch, 3)) / 100;
|
|
444
|
+
return hslToRgb(h, s, l);
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
function hslToRgb(h, s, l) {
|
|
449
|
+
const hue = (h % 360 + 360) % 360;
|
|
450
|
+
if (s === 0) {
|
|
451
|
+
const val = Math.round(l * 255);
|
|
452
|
+
return { r: val, g: val, b: val };
|
|
453
|
+
}
|
|
454
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
455
|
+
const p = 2 * l - q;
|
|
456
|
+
function hueToChannel(t) {
|
|
457
|
+
let tc = t;
|
|
458
|
+
if (tc < 0)
|
|
459
|
+
tc += 1;
|
|
460
|
+
if (tc > 1)
|
|
461
|
+
tc -= 1;
|
|
462
|
+
if (tc < 1 / 6)
|
|
463
|
+
return p + (q - p) * 6 * tc;
|
|
464
|
+
if (tc < 1 / 2)
|
|
465
|
+
return q;
|
|
466
|
+
if (tc < 2 / 3)
|
|
467
|
+
return p + (q - p) * (2 / 3 - tc) * 6;
|
|
468
|
+
return p;
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
r: Math.round(hueToChannel(hue / 360 + 1 / 3) * 255),
|
|
472
|
+
g: Math.round(hueToChannel(hue / 360) * 255),
|
|
473
|
+
b: Math.round(hueToChannel(hue / 360 - 1 / 3) * 255)
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function brightness(r, g, b) {
|
|
477
|
+
return (r * 299 + g * 587 + b * 114) / 1000;
|
|
478
|
+
}
|
|
479
|
+
function extractAlpha(value) {
|
|
480
|
+
const v = value.trim();
|
|
481
|
+
const modernRgbMatch = v.match(/^rgba?\(\s*[\d.]+\s+[\d.]+\s+[\d.]+\s*\/\s*([\d.]+%?)\s*\)/);
|
|
482
|
+
if (modernRgbMatch) {
|
|
483
|
+
const val = capture(modernRgbMatch, 1);
|
|
484
|
+
return val.endsWith("%") ? parseFloat(val) / 100 : parseFloat(val);
|
|
485
|
+
}
|
|
486
|
+
const modernHslMatch = v.match(/^hsla?\(\s*[\d.]+\s+[\d.]+%?\s+[\d.]+%?\s*\/\s*([\d.]+%?)\s*\)/);
|
|
487
|
+
if (modernHslMatch) {
|
|
488
|
+
const val = capture(modernHslMatch, 1);
|
|
489
|
+
return val.endsWith("%") ? parseFloat(val) / 100 : parseFloat(val);
|
|
490
|
+
}
|
|
491
|
+
const rgbaMatch = v.match(/^rgba\(\s*\d+\s*[,/]\s*\d+\s*[,/]\s*\d+\s*[,/]\s*([\d.]+)\s*\)/);
|
|
492
|
+
if (rgbaMatch) {
|
|
493
|
+
return parseFloat(capture(rgbaMatch, 1));
|
|
494
|
+
}
|
|
495
|
+
const hslaMatch = v.match(/^hsla\(\s*\d+\s*[,/]\s*[\d.]+%?\s*[,/]\s*[\d.]+%?\s*[,/]\s*([\d.]+)\s*\)/);
|
|
496
|
+
if (hslaMatch) {
|
|
497
|
+
return parseFloat(capture(hslaMatch, 1));
|
|
498
|
+
}
|
|
499
|
+
const hex8Match = v.match(/^#[0-9a-fA-F]{8}$/);
|
|
500
|
+
if (hex8Match) {
|
|
501
|
+
const alphaHex = v.slice(7, 9);
|
|
502
|
+
return parseInt(alphaHex, 16) / 255;
|
|
503
|
+
}
|
|
504
|
+
return 1;
|
|
505
|
+
}
|
|
506
|
+
function checkMissingTokens(vars) {
|
|
507
|
+
const semanticCount = REQUIRED_TOKENS.filter((t) => vars.has(t)).length;
|
|
508
|
+
if (semanticCount < FULL_THEME_THRESHOLD)
|
|
509
|
+
return [];
|
|
510
|
+
const issues = [];
|
|
511
|
+
for (const token of REQUIRED_TOKENS) {
|
|
512
|
+
if (!vars.has(token)) {
|
|
513
|
+
issues.push({
|
|
514
|
+
severity: "error",
|
|
515
|
+
code: "missing-token",
|
|
516
|
+
variable: token,
|
|
517
|
+
message: `Required semantic token ${token} is not defined`,
|
|
518
|
+
fix: `Add ${token} with a color that fits your theme`
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return issues;
|
|
523
|
+
}
|
|
524
|
+
function checkValueTypes(vars) {
|
|
525
|
+
const issues = [];
|
|
526
|
+
for (const [name, entry] of vars) {
|
|
527
|
+
const val = entry.resolved;
|
|
528
|
+
if (/^var\(\s*--/.test(val))
|
|
529
|
+
continue;
|
|
530
|
+
const classified = classifyValue(val);
|
|
531
|
+
if (/^--dry-color-/.test(name) && classified !== "color" && classified !== "other") {
|
|
532
|
+
issues.push({
|
|
533
|
+
severity: "error",
|
|
534
|
+
code: "wrong-type",
|
|
535
|
+
variable: name,
|
|
536
|
+
value: entry.original,
|
|
537
|
+
message: `${name} expects a color value but got "${val}" (classified as ${classified})`,
|
|
538
|
+
fix: `Replace with a color value (e.g., #2563eb, rgb(37,99,235), hsl(217,91%,60%))`
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
if (/^--dry-space-/.test(name) && classified !== "length" && classified !== "other") {
|
|
542
|
+
issues.push({
|
|
543
|
+
severity: "error",
|
|
544
|
+
code: "wrong-type",
|
|
545
|
+
variable: name,
|
|
546
|
+
value: entry.original,
|
|
547
|
+
message: `${name} expects a length value but got "${val}" (classified as ${classified})`,
|
|
548
|
+
fix: `Replace with a length value (e.g., 0.5rem, 4px, 1em)`
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
if (/^--dry-radius-/.test(name) && classified !== "length" && classified !== "other") {
|
|
552
|
+
issues.push({
|
|
553
|
+
severity: "error",
|
|
554
|
+
code: "wrong-type",
|
|
555
|
+
variable: name,
|
|
556
|
+
value: entry.original,
|
|
557
|
+
message: `${name} expects a length value but got "${val}" (classified as ${classified})`,
|
|
558
|
+
fix: `Replace with a length value (e.g., 4px, 0.375rem)`
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
if (/^--dry-duration-/.test(name) && classified !== "time" && classified !== "other") {
|
|
562
|
+
issues.push({
|
|
563
|
+
severity: "error",
|
|
564
|
+
code: "wrong-type",
|
|
565
|
+
variable: name,
|
|
566
|
+
value: entry.original,
|
|
567
|
+
message: `${name} expects a time value but got "${val}" (classified as ${classified})`,
|
|
568
|
+
fix: `Replace with a time value (e.g., 100ms, 0.2s)`
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return issues;
|
|
573
|
+
}
|
|
574
|
+
function checkContrastHeuristics(vars) {
|
|
575
|
+
const issues = [];
|
|
576
|
+
for (const token of SURFACE_TOKENS) {
|
|
577
|
+
const entry = vars.get(token);
|
|
578
|
+
if (!entry)
|
|
579
|
+
continue;
|
|
580
|
+
const alpha = extractAlpha(entry.resolved);
|
|
581
|
+
if (alpha < 0.3) {
|
|
582
|
+
issues.push({
|
|
583
|
+
severity: "warning",
|
|
584
|
+
code: "transparent-surface",
|
|
585
|
+
variable: token,
|
|
586
|
+
value: entry.original,
|
|
587
|
+
message: `Surface color has very low opacity (${alpha}) — cards and elevated elements will be nearly invisible`,
|
|
588
|
+
fix: `Use a solid color (e.g., #1e293b for dark themes, #f8fafc for light themes)`
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const textStrongEntry = vars.get("--dry-color-text-strong");
|
|
593
|
+
const textWeakEntry = vars.get("--dry-color-text-weak");
|
|
594
|
+
const textBrandEntry = vars.get("--dry-color-text-brand");
|
|
595
|
+
const bgBaseEntry = vars.get("--dry-color-bg-base");
|
|
596
|
+
if (bgBaseEntry) {
|
|
597
|
+
const bgRgb = parseColor(bgBaseEntry.resolved);
|
|
598
|
+
if (bgRgb) {
|
|
599
|
+
const bgBrightness = brightness(bgRgb.r, bgRgb.g, bgRgb.b);
|
|
600
|
+
const textPairs = [
|
|
601
|
+
["--dry-color-text-strong", textStrongEntry],
|
|
602
|
+
["--dry-color-text-weak", textWeakEntry],
|
|
603
|
+
["--dry-color-text-brand", textBrandEntry]
|
|
604
|
+
];
|
|
605
|
+
for (const [tokenName, textEntry] of textPairs) {
|
|
606
|
+
if (!textEntry)
|
|
607
|
+
continue;
|
|
608
|
+
const textRgb = parseColor(textEntry.resolved);
|
|
609
|
+
if (!textRgb)
|
|
610
|
+
continue;
|
|
611
|
+
const textBrightness = brightness(textRgb.r, textRgb.g, textRgb.b);
|
|
612
|
+
if (Math.abs(textBrightness - bgBrightness) < 125) {
|
|
613
|
+
issues.push({
|
|
614
|
+
severity: "warning",
|
|
615
|
+
code: "low-contrast-text",
|
|
616
|
+
variable: tokenName,
|
|
617
|
+
value: textEntry.original,
|
|
618
|
+
message: `Low contrast between ${tokenName} and --dry-color-bg-base (brightness difference: ${Math.round(Math.abs(textBrightness - bgBrightness))})`,
|
|
619
|
+
fix: `Increase brightness difference between text and background to at least 125`
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const bgRaisedEntry = vars.get("--dry-color-bg-raised");
|
|
626
|
+
const bgOverlayEntry = vars.get("--dry-color-bg-overlay");
|
|
627
|
+
if (bgBaseEntry && bgRaisedEntry) {
|
|
628
|
+
const baseRgb = parseColor(bgBaseEntry.resolved);
|
|
629
|
+
const raisedRgb = parseColor(bgRaisedEntry.resolved);
|
|
630
|
+
if (baseRgb && raisedRgb) {
|
|
631
|
+
const baseBr = brightness(baseRgb.r, baseRgb.g, baseRgb.b);
|
|
632
|
+
const raisedBr = brightness(raisedRgb.r, raisedRgb.g, raisedRgb.b);
|
|
633
|
+
if (Math.abs(baseBr - raisedBr) < 3) {
|
|
634
|
+
issues.push({
|
|
635
|
+
severity: "warning",
|
|
636
|
+
code: "no-elevation",
|
|
637
|
+
variable: "--dry-color-bg-raised",
|
|
638
|
+
value: bgRaisedEntry.original,
|
|
639
|
+
message: `--dry-color-bg-base and --dry-color-bg-raised have near-identical brightness (difference: ${Math.round(Math.abs(baseBr - raisedBr))}) — raised surfaces won't have visual separation`,
|
|
640
|
+
fix: `Make --dry-color-bg-raised 1-2 steps lighter/darker than --dry-color-bg-base`
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (bgRaisedEntry && bgOverlayEntry) {
|
|
646
|
+
const raisedRgb = parseColor(bgRaisedEntry.resolved);
|
|
647
|
+
const overlayRgb = parseColor(bgOverlayEntry.resolved);
|
|
648
|
+
if (raisedRgb && overlayRgb) {
|
|
649
|
+
const raisedBr = brightness(raisedRgb.r, raisedRgb.g, raisedRgb.b);
|
|
650
|
+
const overlayBr = brightness(overlayRgb.r, overlayRgb.g, overlayRgb.b);
|
|
651
|
+
if (Math.abs(raisedBr - overlayBr) < 3) {
|
|
652
|
+
issues.push({
|
|
653
|
+
severity: "warning",
|
|
654
|
+
code: "no-elevation",
|
|
655
|
+
variable: "--dry-color-bg-overlay",
|
|
656
|
+
value: bgOverlayEntry.original,
|
|
657
|
+
message: `--dry-color-bg-raised and --dry-color-bg-overlay have near-identical brightness (difference: ${Math.round(Math.abs(raisedBr - overlayBr))}) — overlay surfaces won't have visual separation`,
|
|
658
|
+
fix: `Make --dry-color-bg-overlay 1-2 steps lighter/darker than --dry-color-bg-raised`
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
const pairings = [
|
|
664
|
+
["--dry-color-fill-brand", "--dry-color-on-brand"],
|
|
665
|
+
["--dry-color-fill-error", "--dry-color-on-error"],
|
|
666
|
+
["--dry-color-fill-warning", "--dry-color-on-warning"],
|
|
667
|
+
["--dry-color-fill-success", "--dry-color-on-success"],
|
|
668
|
+
["--dry-color-fill-info", "--dry-color-on-info"]
|
|
669
|
+
];
|
|
670
|
+
for (const [a, b] of pairings) {
|
|
671
|
+
if (vars.has(a) && !vars.has(b)) {
|
|
672
|
+
issues.push({
|
|
673
|
+
severity: "warning",
|
|
674
|
+
code: "missing-pairing",
|
|
675
|
+
variable: b,
|
|
676
|
+
message: `${a} is defined but its pair ${b} is missing`,
|
|
677
|
+
fix: `Add ${b} to complete the color pairing`
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
if (vars.has(b) && !vars.has(a)) {
|
|
681
|
+
issues.push({
|
|
682
|
+
severity: "warning",
|
|
683
|
+
code: "missing-pairing",
|
|
684
|
+
variable: a,
|
|
685
|
+
message: `${b} is defined but its pair ${a} is missing`,
|
|
686
|
+
fix: `Add ${a} to complete the color pairing`
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return issues;
|
|
691
|
+
}
|
|
692
|
+
function checkComponentTokens(vars, spec) {
|
|
693
|
+
const issues = [];
|
|
694
|
+
const validComponentVars = new Set;
|
|
695
|
+
for (const comp of Object.values(spec.components)) {
|
|
696
|
+
for (const varName of Object.keys(comp.cssVars)) {
|
|
697
|
+
validComponentVars.add(varName);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
for (const [name, entry] of vars) {
|
|
701
|
+
if (/^--dry-(?:color|space|radius|duration|shadow|text|font)-/.test(name))
|
|
702
|
+
continue;
|
|
703
|
+
if (!validComponentVars.has(name)) {
|
|
704
|
+
issues.push({
|
|
705
|
+
severity: "warning",
|
|
706
|
+
code: "unknown-component-token",
|
|
707
|
+
variable: name,
|
|
708
|
+
value: entry.original,
|
|
709
|
+
message: `${name} is not a recognized component token in the spec`,
|
|
710
|
+
fix: `Check spelling against the component's cssVars in the spec`
|
|
711
|
+
});
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
if (/-bg$/.test(name)) {
|
|
715
|
+
const lower = entry.resolved.toLowerCase();
|
|
716
|
+
if (lower === "transparent") {
|
|
717
|
+
issues.push({
|
|
718
|
+
severity: "warning",
|
|
719
|
+
code: "transparent-component-bg",
|
|
720
|
+
variable: name,
|
|
721
|
+
value: entry.original,
|
|
722
|
+
message: `${name} is set to transparent — the component background will be invisible`,
|
|
723
|
+
fix: `Use a solid color or reference a background token (e.g., var(--dry-color-bg-raised))`
|
|
724
|
+
});
|
|
725
|
+
} else {
|
|
726
|
+
const alpha = extractAlpha(entry.resolved);
|
|
727
|
+
if (alpha < 0.3) {
|
|
728
|
+
issues.push({
|
|
729
|
+
severity: "warning",
|
|
730
|
+
code: "transparent-component-bg",
|
|
731
|
+
variable: name,
|
|
732
|
+
value: entry.original,
|
|
733
|
+
message: `${name} has very low opacity (${alpha}) — the component background will be nearly invisible`,
|
|
734
|
+
fix: `Use a solid color or reference a background token (e.g., var(--dry-color-bg-raised))`
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return issues;
|
|
741
|
+
}
|
|
742
|
+
function detectDarkScheme(css, allVars) {
|
|
743
|
+
const issues = [];
|
|
744
|
+
const signals = [];
|
|
745
|
+
if (/color-scheme\s*:\s*dark/i.test(css)) {
|
|
746
|
+
signals.push("color-scheme: dark");
|
|
747
|
+
}
|
|
748
|
+
const bgVarNames = ["--bg", "--background", "--bg-color", "--background-color", "--bg-base"];
|
|
749
|
+
for (const name of bgVarNames) {
|
|
750
|
+
const value = allVars.get(name);
|
|
751
|
+
if (value) {
|
|
752
|
+
const rgb = parseColor(value);
|
|
753
|
+
if (rgb && brightness(rgb.r, rgb.g, rgb.b) < 50) {
|
|
754
|
+
signals.push(`${name}: ${value} (dark)`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
const bgPropMatch = css.match(/(?:html|body|\*|:root)\s*\{[^}]*background(?:-color)?\s*:\s*([^;]+)/i);
|
|
759
|
+
if (bgPropMatch) {
|
|
760
|
+
const val = (bgPropMatch[1] ?? "").trim();
|
|
761
|
+
const varRef = val.match(/var\(\s*(--[a-zA-Z0-9-]+)/);
|
|
762
|
+
const resolvedVal = varRef?.[1] ? allVars.get(varRef[1]) : undefined;
|
|
763
|
+
const colorStr = resolvedVal ?? val;
|
|
764
|
+
const rgb = parseColor(colorStr);
|
|
765
|
+
if (rgb && brightness(rgb.r, rgb.g, rgb.b) < 50) {
|
|
766
|
+
signals.push(`background: ${val} (dark)`);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
if (signals.length > 0) {
|
|
770
|
+
issues.push({
|
|
771
|
+
severity: "warning",
|
|
772
|
+
code: "dark-scheme-no-overrides",
|
|
773
|
+
variable: "--dry-color-*",
|
|
774
|
+
message: `Project uses a dark color scheme (${signals.join(", ")}) but has no --dry-color-* overrides. DryUI's default theme is light — components will have poor contrast on dark backgrounds. Either use theme: "dark" in the generate tool, or add --dry-color-* overrides to map DryUI tokens to your dark palette.`,
|
|
775
|
+
fix: 'Use theme: "dark" in dryui.page(), or override --dry-color-bg-base, --dry-color-bg-raised, --dry-color-text-strong, --dry-color-text-weak, and other semantic tokens with dark-appropriate values'
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
return issues;
|
|
779
|
+
}
|
|
780
|
+
function diagnoseTheme(css, spec) {
|
|
781
|
+
const dryVars = extractDryVariables(css);
|
|
782
|
+
const allVars = extractAllVariables(css);
|
|
783
|
+
const resolved = resolveVarReferences(dryVars, allVars);
|
|
784
|
+
const infoIssues = [];
|
|
785
|
+
for (const [name, entry] of resolved) {
|
|
786
|
+
if (/^var\(\s*--/.test(entry.resolved) && entry.resolved === entry.original) {
|
|
787
|
+
const varRefMatch = entry.original.match(/var\(\s*(--[a-zA-Z0-9-]+)/);
|
|
788
|
+
const refName = varRefMatch ? varRefMatch[1] : "unknown";
|
|
789
|
+
infoIssues.push({
|
|
790
|
+
severity: "info",
|
|
791
|
+
code: "unresolvable-var",
|
|
792
|
+
variable: name,
|
|
793
|
+
value: entry.original,
|
|
794
|
+
message: `${name} references ${refName} which is not defined in this CSS — type and contrast checks skipped`,
|
|
795
|
+
fix: null
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
const tier1 = checkMissingTokens(resolved);
|
|
800
|
+
const tier2 = checkValueTypes(resolved);
|
|
801
|
+
const tier3 = checkContrastHeuristics(resolved);
|
|
802
|
+
const tier4 = checkComponentTokens(resolved, spec);
|
|
803
|
+
const darkSchemeIssues = dryVars.size === 0 ? detectDarkScheme(css, allVars) : [];
|
|
804
|
+
const allIssues = [...tier1, ...tier2, ...tier3, ...tier4, ...darkSchemeIssues, ...infoIssues];
|
|
805
|
+
const severityOrder = { error: 0, warning: 1, info: 2 };
|
|
806
|
+
allIssues.sort((a, b) => (severityOrder[a.severity] ?? 2) - (severityOrder[b.severity] ?? 2));
|
|
807
|
+
const errors = allIssues.filter((i) => i.severity === "error").length;
|
|
808
|
+
const warnings = allIssues.filter((i) => i.severity === "warning").length;
|
|
809
|
+
const infos = allIssues.filter((i) => i.severity === "info").length;
|
|
810
|
+
const summary = allIssues.length === 0 ? "No issues found" : `${errors} error${errors !== 1 ? "s" : ""}, ${warnings} warning${warnings !== 1 ? "s" : ""}, ${infos} info`;
|
|
811
|
+
const found = dryVars.size;
|
|
812
|
+
const requiredSet = new Set(REQUIRED_TOKENS);
|
|
813
|
+
const requiredFound = [...dryVars.keys()].filter((k) => requiredSet.has(k)).length;
|
|
814
|
+
const extra = found - requiredFound;
|
|
815
|
+
return {
|
|
816
|
+
variables: { found, required: requiredFound, extra },
|
|
817
|
+
issues: allIssues,
|
|
818
|
+
summary
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
export {
|
|
822
|
+
diagnoseTheme
|
|
823
|
+
};
|