@walkeros/explorer 4.7.2 → 4.8.0-next-1791496487198
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/CHANGELOG.md +75 -0
- package/README.md +38 -9
- package/bin/walkeros-design-check.mjs +15 -0
- package/dist/design/base.css +54 -0
- package/dist/design/check.mjs +628 -0
- package/dist/design/components/index.cjs +2872 -0
- package/dist/design/components/index.cjs.map +1 -0
- package/dist/design/components/index.d.cts +382 -0
- package/dist/design/components/index.d.ts +382 -0
- package/dist/design/components/index.mjs +2857 -0
- package/dist/design/components/index.mjs.map +1 -0
- package/dist/design/index.cjs +263 -0
- package/dist/design/index.cjs.map +1 -0
- package/dist/design/index.d.cts +524 -0
- package/dist/design/index.d.ts +524 -0
- package/dist/design/index.mjs +242 -0
- package/dist/design/index.mjs.map +1 -0
- package/dist/design/tailwind.css +226 -0
- package/dist/design/tokens.css +316 -0
- package/dist/index.d.cts +176 -406
- package/dist/index.d.ts +176 -406
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +2904 -827
- package/package.json +26 -9
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/design/check/bin.ts
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
// src/design/check/cli.ts
|
|
8
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
9
|
+
import { extname, join, relative, resolve, sep } from "node:path";
|
|
10
|
+
|
|
11
|
+
// src/design/names.ts
|
|
12
|
+
var DECLARATION = /(?<![A-Za-z0-9_-])--([A-Za-z0-9_-]+)['"]?\s*:/g;
|
|
13
|
+
var SET_PROPERTY = /setProperty\(\s*['"`]--([A-Za-z0-9_-]+)/g;
|
|
14
|
+
function forEachMatch(pattern, text, visit) {
|
|
15
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
16
|
+
const re = new RegExp(pattern.source, flags);
|
|
17
|
+
for (let match = re.exec(text); match !== null; match = re.exec(text))
|
|
18
|
+
visit(match);
|
|
19
|
+
}
|
|
20
|
+
function collectDeclaredNames(text) {
|
|
21
|
+
const names = /* @__PURE__ */ new Set();
|
|
22
|
+
forEachMatch(DECLARATION, text, (match) => names.add(match[1]));
|
|
23
|
+
forEachMatch(SET_PROPERTY, text, (match) => names.add(match[1]));
|
|
24
|
+
return names;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/design/check/glob.ts
|
|
28
|
+
var escape = (text) => text.replace(/[.+^$()|[\]\\{}*?]/g, "\\$&");
|
|
29
|
+
function globToRegExp(glob) {
|
|
30
|
+
let source = "";
|
|
31
|
+
for (let i = 0; i < glob.length; i++) {
|
|
32
|
+
const char = glob[i];
|
|
33
|
+
if (char === "*" && glob[i + 1] === "*") {
|
|
34
|
+
const slash = glob[i + 2] === "/";
|
|
35
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
36
|
+
i += slash ? 2 : 1;
|
|
37
|
+
} else if (char === "*") {
|
|
38
|
+
source += "[^/]*";
|
|
39
|
+
} else if (char === "?") {
|
|
40
|
+
source += "[^/]";
|
|
41
|
+
} else if (char === "{") {
|
|
42
|
+
const end = glob.indexOf("}", i);
|
|
43
|
+
if (end === -1) return void 0;
|
|
44
|
+
source += `(?:${glob.slice(i + 1, end).split(",").map(escape).join("|")})`;
|
|
45
|
+
i = end;
|
|
46
|
+
} else {
|
|
47
|
+
source += escape(char);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return new RegExp(`^${source}$`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/design/tailwind-namespaces.ts
|
|
54
|
+
var TAILWIND_RESET_NAMESPACES = [
|
|
55
|
+
"color",
|
|
56
|
+
"radius",
|
|
57
|
+
"container",
|
|
58
|
+
"font",
|
|
59
|
+
"text",
|
|
60
|
+
"shadow",
|
|
61
|
+
"inset-shadow",
|
|
62
|
+
"drop-shadow",
|
|
63
|
+
"text-shadow"
|
|
64
|
+
];
|
|
65
|
+
var TAILWIND_KEPT_NAMESPACES = [
|
|
66
|
+
"animate",
|
|
67
|
+
"aspect",
|
|
68
|
+
"blur",
|
|
69
|
+
"breakpoint",
|
|
70
|
+
"default",
|
|
71
|
+
"ease",
|
|
72
|
+
"font-weight",
|
|
73
|
+
"leading",
|
|
74
|
+
"max-width",
|
|
75
|
+
"perspective",
|
|
76
|
+
"tracking"
|
|
77
|
+
];
|
|
78
|
+
var TAILWIND_OTHER_NAMESPACES = ["tw", "spacing"];
|
|
79
|
+
var TAILWIND_NESTED_NAMESPACES = [
|
|
80
|
+
"text-color",
|
|
81
|
+
"text-decoration-color",
|
|
82
|
+
"text-decoration-thickness",
|
|
83
|
+
"text-indent",
|
|
84
|
+
"text-underline-offset",
|
|
85
|
+
"font-size"
|
|
86
|
+
];
|
|
87
|
+
var TAILWIND_NAMESPACES = [
|
|
88
|
+
...TAILWIND_RESET_NAMESPACES,
|
|
89
|
+
...TAILWIND_KEPT_NAMESPACES,
|
|
90
|
+
...TAILWIND_OTHER_NAMESPACES,
|
|
91
|
+
...TAILWIND_NESTED_NAMESPACES
|
|
92
|
+
];
|
|
93
|
+
var TAILWIND_VARIABLES = [
|
|
94
|
+
"tw-",
|
|
95
|
+
...TAILWIND_KEPT_NAMESPACES.map((namespace) => `${namespace}-`)
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
// src/design/check/rules.ts
|
|
99
|
+
var RULES = [
|
|
100
|
+
"color-literal",
|
|
101
|
+
"named-color",
|
|
102
|
+
"palette-class",
|
|
103
|
+
"white-black-class",
|
|
104
|
+
"dark-variant",
|
|
105
|
+
"shadow-class",
|
|
106
|
+
"z-class",
|
|
107
|
+
"text-size-class",
|
|
108
|
+
"motion-class",
|
|
109
|
+
"radius-class",
|
|
110
|
+
"var-fallback",
|
|
111
|
+
"undeclared-var",
|
|
112
|
+
"color-scheme",
|
|
113
|
+
"z-index",
|
|
114
|
+
"motion-literal",
|
|
115
|
+
"font-literal"
|
|
116
|
+
];
|
|
117
|
+
var NAMED_COLORS = new Set(
|
|
118
|
+
`aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown
|
|
119
|
+
burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan
|
|
120
|
+
darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid
|
|
121
|
+
darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet
|
|
122
|
+
deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro
|
|
123
|
+
ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki
|
|
124
|
+
lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow
|
|
125
|
+
lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray
|
|
126
|
+
lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine
|
|
127
|
+
mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise
|
|
128
|
+
mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab
|
|
129
|
+
orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru
|
|
130
|
+
pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown
|
|
131
|
+
seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan
|
|
132
|
+
teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen`.split(
|
|
133
|
+
/\s+/
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
var PALETTE = "red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|slate|gray|zinc|neutral|stone|mauve|olive|mist|taupe";
|
|
137
|
+
var COLOR_UTILITY = "bg|text|border(?:-[xytrblse])?|outline|ring(?:-offset)?|divide|fill|stroke|from|via|to|placeholder|caret|accent|decoration|shadow|inset-shadow|drop-shadow|text-shadow";
|
|
138
|
+
var START = "(?<![A-Za-z0-9_-])";
|
|
139
|
+
var END = "(?![A-Za-z0-9_-])";
|
|
140
|
+
var SIDE = "(?:-(?:t|r|b|l|s|e|tl|tr|br|bl|ss|se|es|ee))?";
|
|
141
|
+
var LITERAL_ARBITRARY = "\\[(?![^\\]]*var\\()[^\\]\\s]+\\]";
|
|
142
|
+
var CLASS_RULES = [
|
|
143
|
+
[
|
|
144
|
+
"palette-class",
|
|
145
|
+
new RegExp(
|
|
146
|
+
`${START}(?:${COLOR_UTILITY})-(?:${PALETTE})-(?:50|[1-9]00|950)(?:/\\d+)?${END}`,
|
|
147
|
+
"g"
|
|
148
|
+
)
|
|
149
|
+
],
|
|
150
|
+
[
|
|
151
|
+
"white-black-class",
|
|
152
|
+
new RegExp(
|
|
153
|
+
`${START}(?:${COLOR_UTILITY})-(?:white|black)(?:/\\d+)?${END}`,
|
|
154
|
+
"g"
|
|
155
|
+
)
|
|
156
|
+
],
|
|
157
|
+
["dark-variant", new RegExp(`${START}dark:(?=[A-Za-z0-9_\\[!(-])`, "g")],
|
|
158
|
+
[
|
|
159
|
+
"shadow-class",
|
|
160
|
+
// Bare drop-shadow is flagged too (the reset removes it), but not a CSS
|
|
161
|
+
// drop-shadow() call. Bare shadow is not: "shadow root" prose would fire.
|
|
162
|
+
new RegExp(
|
|
163
|
+
`${START}(?:(?:inset-|drop-|text-)?shadow-(?:2xs|xs|sm|md|lg|xl|2xl|inner)${END}|drop-shadow(?![A-Za-z0-9_(-]))`,
|
|
164
|
+
"g"
|
|
165
|
+
)
|
|
166
|
+
],
|
|
167
|
+
["z-class", new RegExp(`${START}-?z-(?:\\d+|\\[-?\\d+\\])${END}`, "g")],
|
|
168
|
+
// An arbitrary size that reads a variable (`text-[length:var(--type-x-size)]`) is not flagged.
|
|
169
|
+
[
|
|
170
|
+
"text-size-class",
|
|
171
|
+
new RegExp(
|
|
172
|
+
`${START}text-(?:xs|sm|base|lg|xl|[2-9]xl|\\[(?:length:)?(?![^\\]]*var\\()(?:[\\d.]|(?:calc|clamp|min|max)\\()[^\\]\\s]*\\])${END}`,
|
|
173
|
+
"g"
|
|
174
|
+
)
|
|
175
|
+
],
|
|
176
|
+
// Tailwind's transition utilities read --motion and --ease; a zero duration, delay-* and animate-* are not flagged.
|
|
177
|
+
[
|
|
178
|
+
"motion-class",
|
|
179
|
+
new RegExp(
|
|
180
|
+
`${START}(?:duration-(?:[1-9]\\d*|(?!\\[0m?s\\])${LITERAL_ARBITRARY})|ease-(?:in-out|in|out|linear|${LITERAL_ARBITRARY}))${END}`,
|
|
181
|
+
"g"
|
|
182
|
+
)
|
|
183
|
+
],
|
|
184
|
+
// An arbitrary value that reads a design radius (`rounded-[calc(var(--radius-md)-1px)]`) is not flagged.
|
|
185
|
+
[
|
|
186
|
+
"radius-class",
|
|
187
|
+
new RegExp(
|
|
188
|
+
`${START}rounded${SIDE}(?:-(?:2xl|3xl|4xl|\\[(?![^\\]]*var\\(--radius-)[^\\]\\s]+\\]))?${END}`,
|
|
189
|
+
"g"
|
|
190
|
+
)
|
|
191
|
+
]
|
|
192
|
+
];
|
|
193
|
+
var STYLE_FILE = /\.(?:css|scss)$/;
|
|
194
|
+
var HEX = /(?<![A-Za-z0-9&#-])#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4})(?![A-Za-z0-9-])/g;
|
|
195
|
+
var COLOR_FUNCTION = /(?<![A-Za-z0-9-])(?:rgba?|hsla?|hwb|oklab|oklch|lab|lch)\([^)]*\)/g;
|
|
196
|
+
var CSS_COLOR_FUNCTION = /(?<![A-Za-z0-9_-])color\([^)]*\)/g;
|
|
197
|
+
var STYLE_DECLARATION = /(?<![A-Za-z0-9_-])([a-z-]*(?:color|background|border|outline|fill|stroke|shadow|caret|accent|decoration|column-rule)[a-z-]*)\s*:\s*([^;{}]+)/g;
|
|
198
|
+
var SCRIPT_COLOR_PROPERTY = /(?<![A-Za-z0-9_-])(?:color|background|backgroundColor|borderColor|outlineColor|fill|stroke|caretColor|accentColor)\s*[:=]\s*\{?\s*['"]([A-Za-z]+)['"]/g;
|
|
199
|
+
var WORD = /(?<![A-Za-z0-9_-])[A-Za-z]+(?![A-Za-z0-9_-])/g;
|
|
200
|
+
var URL_FUNCTION = /url\([^)]*\)/g;
|
|
201
|
+
var VAR_REFERENCE = /var\(\s*--([A-Za-z0-9_-]+)\s*(,)?/g;
|
|
202
|
+
var TAILWIND_VAR = /-\(--([A-Za-z0-9_-]+)\)/g;
|
|
203
|
+
var COLOR_SCHEME = /prefers-color-scheme/g;
|
|
204
|
+
var Z_INDEX_CSS = /(?<![A-Za-z0-9_-])z-index\s*:\s*-?\d+/g;
|
|
205
|
+
var Z_INDEX_SCRIPT = /(?<![A-Za-z0-9_-])zIndex\s*:\s*['"]?-?\d+/g;
|
|
206
|
+
var CSS_STYLE = /(?<![A-Za-z0-9_-])(font-size|font-family|font|transition-duration|transition-timing-function|transition)\s*:\s*((?:\$\{[^}]*\}|[^;{}\]])+)/g;
|
|
207
|
+
var SCRIPT_STYLE = /(?<![A-Za-z0-9_-])(fontSize|fontFamily|transitionDuration|transitionTimingFunction|transition)\s*(?::|=\{?)\s*('(?:\\.|[^'\\\n])*'|"(?:\\.|[^"\\\n])*"|`(?:\\.|[^`\\])*`|\d*\.?\d+(?![\w.]))/g;
|
|
208
|
+
var NUMBER = /^\d*\.?\d+$/;
|
|
209
|
+
var QUOTED = /^(['"`])([\s\S]*)\1$/;
|
|
210
|
+
var MOTION_LITERAL = /(?<![A-Za-z0-9-])(?:cubic-bezier|steps|linear)\([^)]*\)|(?<![A-Za-z0-9.-])(?:(?:\d+(?:\.\d+)?|\.\d+)m?s|ease-in-out|ease-in|ease-out|ease|linear|step-start|step-end)(?![A-Za-z0-9-])/g;
|
|
211
|
+
var LENGTH = "(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:px|rem|em|pt|%|vw|vh|vmin|vmax|ch|ex|lh|rlh|cqw|cqh|cqi)";
|
|
212
|
+
var SIZE_LITERAL = new RegExp(
|
|
213
|
+
`(?<![A-Za-z0-9.-])(?:${LENGTH}|xxx-large|xx-large|x-large|xx-small|x-small|smaller|small|medium|larger|large)(?![A-Za-z0-9-])`,
|
|
214
|
+
"g"
|
|
215
|
+
);
|
|
216
|
+
var VARIABLE_DECLARATION = /(?<![A-Za-z0-9_-])(--[A-Za-z0-9_-]+|\$[A-Za-z_][\w-]*)['"]?\s*:\s*['"`]?([^;{}'"`\n\]]+)/g;
|
|
217
|
+
var VARIABLE_READ = /var\(\s*(--[A-Za-z0-9_-]+)|(?<![\w-])(\$[A-Za-z_][\w-]*)/g;
|
|
218
|
+
var MOTION_TOKEN = new RegExp(
|
|
219
|
+
`var\\(--motion\\)|${MOTION_LITERAL.source}`,
|
|
220
|
+
"g"
|
|
221
|
+
);
|
|
222
|
+
var FONT_KEYWORD = /(?<![\w-])(?:normal|italic|oblique|small-caps|bold|bolder|lighter|(?:ultra-|extra-|semi-)?(?:condensed|expanded))(?![\w-])/g;
|
|
223
|
+
var FLUID_TYPE = /clamp\((?:[^()]|\([^()]*\))*,\s*var\(--type-[a-z0-9-]+-size\)\s*\)/g;
|
|
224
|
+
var COMMENT = /\/\*[\s\S]*?\*\/|(?<=^|\s)\/\/[^\n]*/g;
|
|
225
|
+
var FONT_FACE = /@font-face\s*\{[^}]*\}/g;
|
|
226
|
+
var CSS_WIDE = /* @__PURE__ */ new Set([
|
|
227
|
+
"inherit",
|
|
228
|
+
"initial",
|
|
229
|
+
"unset",
|
|
230
|
+
"revert",
|
|
231
|
+
"revert-layer"
|
|
232
|
+
]);
|
|
233
|
+
var STRING_LITERAL = /'(?:\\.|[^'\\\n])*'|"(?:\\.|[^"\\\n])*"|`(?:\\.|[^`\\])*`/g;
|
|
234
|
+
var APPLY = /@apply\s+[^;}]+/g;
|
|
235
|
+
var SCRIPT_TOKEN = new RegExp(
|
|
236
|
+
`${STRING_LITERAL.source}|\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\/`,
|
|
237
|
+
"g"
|
|
238
|
+
);
|
|
239
|
+
var within = (regions, index) => regions.some(
|
|
240
|
+
(region) => index >= region.offset && index < region.offset + region.text.length
|
|
241
|
+
);
|
|
242
|
+
function literalFamily(value) {
|
|
243
|
+
let rest = value.replace(/\$\{[^}]*\}/g, " ").replace(/!important/g, " ");
|
|
244
|
+
for (let previous = ""; previous !== rest; ) {
|
|
245
|
+
previous = rest;
|
|
246
|
+
rest = rest.replace(/var\([^()]*\)/g, " ");
|
|
247
|
+
}
|
|
248
|
+
return (rest.match(/[A-Za-z0-9_-]+/g) ?? []).some(
|
|
249
|
+
(word) => !CSS_WIDE.has(word)
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
function shorthandFamily(value) {
|
|
253
|
+
return literalFamily(
|
|
254
|
+
value.replace(SIZE_LITERAL, " ").replace(/(?<![\w.-])\d*\.?\d+(?![\w.])/g, " ").replace(FONT_KEYWORD, " ").replace(/(?<![\w-])(?:calc|clamp|min|max)\(/g, "(").replace(/\//g, " ")
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
var blank = (text) => text.replace(/[^\n]/g, " ");
|
|
258
|
+
function commaItems(value) {
|
|
259
|
+
const commas = [];
|
|
260
|
+
let depth = 0;
|
|
261
|
+
for (let i = 0; i < value.length; i++) {
|
|
262
|
+
if (value[i] === "(") depth++;
|
|
263
|
+
else if (value[i] === ")") depth--;
|
|
264
|
+
else if (value[i] === "," && depth === 0) commas.push(i);
|
|
265
|
+
}
|
|
266
|
+
return (index) => commas.filter((comma) => comma < index).length;
|
|
267
|
+
}
|
|
268
|
+
function localLiteralLengths(file, text) {
|
|
269
|
+
const code = STYLE_FILE.test(file) ? text.replace(COMMENT, blank) : text.replace(
|
|
270
|
+
SCRIPT_TOKEN,
|
|
271
|
+
(token) => token.startsWith("/") ? blank(token) : token
|
|
272
|
+
);
|
|
273
|
+
const names = /* @__PURE__ */ new Set();
|
|
274
|
+
forEachMatch(VARIABLE_DECLARATION, code, (match) => {
|
|
275
|
+
if (new RegExp(LENGTH).test(match[2]) && !/var\(|\$/.test(match[2]))
|
|
276
|
+
names.add(match[1]);
|
|
277
|
+
});
|
|
278
|
+
return names;
|
|
279
|
+
}
|
|
280
|
+
function styleValue(property, value, index, { add, motionValues, literalLengths }) {
|
|
281
|
+
const scanned = value.replace(/\$\{[^}]*\}/g, blank);
|
|
282
|
+
const bare = QUOTED.exec(value)?.[2] ?? value;
|
|
283
|
+
const asWritten = value.replace(/\s+/g, " ").trim();
|
|
284
|
+
if (property.startsWith("transition")) {
|
|
285
|
+
motionValues.push({ offset: index, text: value });
|
|
286
|
+
if (NUMBER.test(bare)) {
|
|
287
|
+
if (property === "transitionDuration")
|
|
288
|
+
add("motion-literal", index, value);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const item = commaItems(value);
|
|
292
|
+
const timed = /* @__PURE__ */ new Set();
|
|
293
|
+
forEachMatch(MOTION_TOKEN, scanned, (match) => {
|
|
294
|
+
const time = /^[\d.]/.test(match[0]);
|
|
295
|
+
if (time || match[0] === "var(--motion)") {
|
|
296
|
+
const n = item(match.index);
|
|
297
|
+
if (property === "transition" && timed.has(n)) return;
|
|
298
|
+
timed.add(n);
|
|
299
|
+
if (!time || parseFloat(match[0]) === 0) return;
|
|
300
|
+
}
|
|
301
|
+
add("motion-literal", index + match.index, match[0]);
|
|
302
|
+
});
|
|
303
|
+
} else if (property === "font-family" || property === "fontFamily") {
|
|
304
|
+
if (literalFamily(value)) add("font-literal", index, asWritten);
|
|
305
|
+
} else if (NUMBER.test(bare)) {
|
|
306
|
+
add("font-literal", index, value);
|
|
307
|
+
} else {
|
|
308
|
+
const fluid = [];
|
|
309
|
+
forEachMatch(
|
|
310
|
+
FLUID_TYPE,
|
|
311
|
+
scanned,
|
|
312
|
+
(match) => fluid.push({ offset: match.index, text: match[0] })
|
|
313
|
+
);
|
|
314
|
+
forEachMatch(SIZE_LITERAL, scanned, (match) => {
|
|
315
|
+
if (!within(fluid, match.index))
|
|
316
|
+
add("font-literal", index + match.index, match[0]);
|
|
317
|
+
});
|
|
318
|
+
forEachMatch(VARIABLE_READ, scanned, (match) => {
|
|
319
|
+
if (literalLengths.has(match[1] ?? match[2]))
|
|
320
|
+
add("font-literal", index + match.index, match[0]);
|
|
321
|
+
});
|
|
322
|
+
if (property === "font" && shorthandFamily(value))
|
|
323
|
+
add("font-literal", index, asWritten);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function classRegions(style, text) {
|
|
327
|
+
const regions = [];
|
|
328
|
+
forEachMatch(
|
|
329
|
+
style ? APPLY : STRING_LITERAL,
|
|
330
|
+
text,
|
|
331
|
+
(match) => regions.push({ offset: match.index, text: match[0] })
|
|
332
|
+
);
|
|
333
|
+
return regions;
|
|
334
|
+
}
|
|
335
|
+
function positions(text) {
|
|
336
|
+
const starts = [0];
|
|
337
|
+
for (let i = 0; i < text.length; i++)
|
|
338
|
+
if (text[i] === "\n") starts.push(i + 1);
|
|
339
|
+
return (index) => {
|
|
340
|
+
let low = 0;
|
|
341
|
+
let high = starts.length - 1;
|
|
342
|
+
while (low < high) {
|
|
343
|
+
const mid = low + high + 1 >> 1;
|
|
344
|
+
if (starts[mid] <= index) low = mid;
|
|
345
|
+
else high = mid - 1;
|
|
346
|
+
}
|
|
347
|
+
return { line: low + 1, column: index - starts[low] + 1 };
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function scanFile(file, text, context) {
|
|
351
|
+
const style = STYLE_FILE.test(file);
|
|
352
|
+
const position = positions(text);
|
|
353
|
+
const findings = [];
|
|
354
|
+
const add = (rule, index, match) => {
|
|
355
|
+
findings.push({ file, ...position(index), rule, match });
|
|
356
|
+
};
|
|
357
|
+
forEachMatch(
|
|
358
|
+
HEX,
|
|
359
|
+
text,
|
|
360
|
+
(match) => add("color-literal", match.index, match[0])
|
|
361
|
+
);
|
|
362
|
+
forEachMatch(
|
|
363
|
+
COLOR_FUNCTION,
|
|
364
|
+
text,
|
|
365
|
+
(match) => add("color-literal", match.index, match[0])
|
|
366
|
+
);
|
|
367
|
+
if (style) {
|
|
368
|
+
forEachMatch(
|
|
369
|
+
CSS_COLOR_FUNCTION,
|
|
370
|
+
text,
|
|
371
|
+
(match) => add("color-literal", match.index, match[0])
|
|
372
|
+
);
|
|
373
|
+
forEachMatch(STYLE_DECLARATION, text, (match) => {
|
|
374
|
+
forEachMatch(WORD, match[2].replace(URL_FUNCTION, " "), (word) => {
|
|
375
|
+
if (NAMED_COLORS.has(word[0].toLowerCase()))
|
|
376
|
+
add("named-color", match.index, word[0]);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
} else {
|
|
380
|
+
forEachMatch(SCRIPT_COLOR_PROPERTY, text, (match) => {
|
|
381
|
+
if (NAMED_COLORS.has(match[1].toLowerCase()))
|
|
382
|
+
add("named-color", match.index, match[1]);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
const regions = classRegions(style, text);
|
|
386
|
+
const fontFaces = [];
|
|
387
|
+
forEachMatch(
|
|
388
|
+
FONT_FACE,
|
|
389
|
+
text,
|
|
390
|
+
(match) => fontFaces.push({ offset: match.index, text: match[0] })
|
|
391
|
+
);
|
|
392
|
+
const scan = {
|
|
393
|
+
add,
|
|
394
|
+
motionValues: [],
|
|
395
|
+
literalLengths: context.literalLengths
|
|
396
|
+
};
|
|
397
|
+
const cssRegions = style ? [{ offset: 0, text }] : regions;
|
|
398
|
+
for (const region of cssRegions) {
|
|
399
|
+
const css = region.text.replace(COMMENT, blank);
|
|
400
|
+
forEachMatch(CSS_STYLE, css, (match) => {
|
|
401
|
+
const index = region.offset + match.index + match[0].length - match[2].length;
|
|
402
|
+
if (!within(fontFaces, index))
|
|
403
|
+
styleValue(match[1], match[2], index, scan);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
if (!style)
|
|
407
|
+
forEachMatch(
|
|
408
|
+
SCRIPT_STYLE,
|
|
409
|
+
text.replace(
|
|
410
|
+
SCRIPT_TOKEN,
|
|
411
|
+
(token) => token.startsWith("/") ? blank(token) : token
|
|
412
|
+
),
|
|
413
|
+
(match) => styleValue(
|
|
414
|
+
match[1],
|
|
415
|
+
match[2],
|
|
416
|
+
match.index + match[0].length - match[2].length,
|
|
417
|
+
scan
|
|
418
|
+
)
|
|
419
|
+
);
|
|
420
|
+
for (const region of regions) {
|
|
421
|
+
for (const [rule, pattern] of CLASS_RULES) {
|
|
422
|
+
forEachMatch(pattern, region.text, (match) => {
|
|
423
|
+
const index = region.offset + match.index;
|
|
424
|
+
if (rule !== "motion-class" || !within(scan.motionValues, index))
|
|
425
|
+
add(rule, index, match[0]);
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const declared = (name) => context.declaredNames.has(name) || TAILWIND_VARIABLES.some((prefix) => name.startsWith(prefix)) || context.allowVarPrefixes.some((prefix) => name.startsWith(prefix));
|
|
430
|
+
forEachMatch(VAR_REFERENCE, text, (match) => {
|
|
431
|
+
const name = match[1];
|
|
432
|
+
if (context.designNames.has(name)) {
|
|
433
|
+
if (match[2] === ",") add("var-fallback", match.index, match[0]);
|
|
434
|
+
} else if (!declared(name)) {
|
|
435
|
+
add("undeclared-var", match.index, match[0]);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
forEachMatch(TAILWIND_VAR, text, (match) => {
|
|
439
|
+
if (!context.designNames.has(match[1]) && !declared(match[1])) {
|
|
440
|
+
add("undeclared-var", match.index + 1, match[0].slice(1));
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
forEachMatch(
|
|
444
|
+
COLOR_SCHEME,
|
|
445
|
+
text,
|
|
446
|
+
(match) => add("color-scheme", match.index, match[0])
|
|
447
|
+
);
|
|
448
|
+
forEachMatch(
|
|
449
|
+
Z_INDEX_CSS,
|
|
450
|
+
text,
|
|
451
|
+
(match) => add("z-index", match.index, match[0])
|
|
452
|
+
);
|
|
453
|
+
if (!style)
|
|
454
|
+
forEachMatch(
|
|
455
|
+
Z_INDEX_SCRIPT,
|
|
456
|
+
text,
|
|
457
|
+
(match) => add("z-index", match.index, match[0])
|
|
458
|
+
);
|
|
459
|
+
return findings.sort(
|
|
460
|
+
(a, b) => a.line - b.line || a.column - b.column || a.rule.localeCompare(b.rule)
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/design/check/cli.ts
|
|
465
|
+
var SCANNED = /* @__PURE__ */ new Set([
|
|
466
|
+
".ts",
|
|
467
|
+
".tsx",
|
|
468
|
+
".js",
|
|
469
|
+
".jsx",
|
|
470
|
+
".mjs",
|
|
471
|
+
".cjs",
|
|
472
|
+
".css",
|
|
473
|
+
".scss",
|
|
474
|
+
".mdx",
|
|
475
|
+
".html",
|
|
476
|
+
".svg"
|
|
477
|
+
]);
|
|
478
|
+
var SKIPPED_DIRS = /* @__PURE__ */ new Set([
|
|
479
|
+
"node_modules",
|
|
480
|
+
"dist",
|
|
481
|
+
"build",
|
|
482
|
+
"coverage",
|
|
483
|
+
"storybook-static"
|
|
484
|
+
]);
|
|
485
|
+
var DESIGN_CSS = ["tokens.css", "tailwind.css", "base.css"];
|
|
486
|
+
var USAGE = "usage: walkeros-design-check [--allow [<rule>:]<glob>]... [--allow-var <prefix>]... <path>...";
|
|
487
|
+
var SCOPED = /^([a-z-]+):(.+)$/;
|
|
488
|
+
var isRule = (value) => RULES.some((rule) => rule === value);
|
|
489
|
+
function parseAllow(value) {
|
|
490
|
+
const scoped = SCOPED.exec(value);
|
|
491
|
+
if (scoped !== null) {
|
|
492
|
+
const rule = scoped[1];
|
|
493
|
+
if (!isRule(rule)) return `unknown rule "${rule}" in --allow ${value}`;
|
|
494
|
+
const pattern2 = globToRegExp(scoped[2]);
|
|
495
|
+
return pattern2 === void 0 ? `invalid glob in --allow ${value}` : { rule, pattern: pattern2 };
|
|
496
|
+
}
|
|
497
|
+
const pattern = globToRegExp(value);
|
|
498
|
+
return pattern === void 0 ? `invalid glob in --allow ${value}` : { pattern };
|
|
499
|
+
}
|
|
500
|
+
function usage(io, problem) {
|
|
501
|
+
io.err(`walkeros-design-check: ${problem}`);
|
|
502
|
+
io.err(USAGE);
|
|
503
|
+
return 2;
|
|
504
|
+
}
|
|
505
|
+
function walk(path, into) {
|
|
506
|
+
if (statSync(path).isFile()) {
|
|
507
|
+
if (SCANNED.has(extname(path))) into.push(path);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const entries = readdirSync(path, { withFileTypes: true }).sort(
|
|
511
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
512
|
+
);
|
|
513
|
+
for (const entry of entries) {
|
|
514
|
+
if (entry.name.startsWith(".") || SKIPPED_DIRS.has(entry.name)) continue;
|
|
515
|
+
const child = join(path, entry.name);
|
|
516
|
+
if (entry.isDirectory()) walk(child, into);
|
|
517
|
+
else if (entry.isFile() && SCANNED.has(extname(entry.name)))
|
|
518
|
+
into.push(child);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function main(argv, io) {
|
|
522
|
+
const allows = [];
|
|
523
|
+
const allowVarPrefixes = [];
|
|
524
|
+
const roots = [];
|
|
525
|
+
for (let i = 0; i < argv.length; i++) {
|
|
526
|
+
const arg = argv[i];
|
|
527
|
+
if (arg === "--allow" || arg === "--allow-var") {
|
|
528
|
+
const value = argv[i + 1];
|
|
529
|
+
i++;
|
|
530
|
+
if (value === void 0 || value === "" || value.startsWith("--"))
|
|
531
|
+
return usage(io, `${arg} needs a value`);
|
|
532
|
+
if (arg === "--allow-var") {
|
|
533
|
+
allowVarPrefixes.push(value);
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
const allow = parseAllow(value);
|
|
537
|
+
if (typeof allow === "string") return usage(io, allow);
|
|
538
|
+
allows.push(allow);
|
|
539
|
+
} else if (arg.startsWith("--")) {
|
|
540
|
+
return usage(io, `unknown option ${arg}`);
|
|
541
|
+
} else {
|
|
542
|
+
roots.push(arg);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (roots.length === 0) return usage(io, "no path given");
|
|
546
|
+
const missing = DESIGN_CSS.filter(
|
|
547
|
+
(file) => !existsSync(join(io.designDir, file))
|
|
548
|
+
);
|
|
549
|
+
if (missing.length > 0) {
|
|
550
|
+
io.err(
|
|
551
|
+
`walkeros-design-check: ${missing.join(", ")} missing in ${io.designDir}; build @walkeros/explorer first`
|
|
552
|
+
);
|
|
553
|
+
return 2;
|
|
554
|
+
}
|
|
555
|
+
const designNames = collectDeclaredNames(
|
|
556
|
+
DESIGN_CSS.map(
|
|
557
|
+
(file) => readFileSync(join(io.designDir, file), "utf8")
|
|
558
|
+
).join("\n")
|
|
559
|
+
);
|
|
560
|
+
const files = [];
|
|
561
|
+
for (const root of roots) {
|
|
562
|
+
const path = resolve(io.cwd, root);
|
|
563
|
+
if (!existsSync(path)) {
|
|
564
|
+
io.err(`walkeros-design-check: ${root} does not exist`);
|
|
565
|
+
return 2;
|
|
566
|
+
}
|
|
567
|
+
walk(path, files);
|
|
568
|
+
}
|
|
569
|
+
const sources = files.map((path) => ({
|
|
570
|
+
file: relative(io.cwd, path).split(sep).join("/"),
|
|
571
|
+
text: readFileSync(path, "utf8")
|
|
572
|
+
}));
|
|
573
|
+
const fileAllowed = (file) => allows.some(
|
|
574
|
+
(allow) => allow.rule === void 0 && allow.pattern.test(file)
|
|
575
|
+
);
|
|
576
|
+
const ruleAllowed = (finding) => allows.some(
|
|
577
|
+
(allow) => allow.rule === finding.rule && allow.pattern.test(finding.file)
|
|
578
|
+
);
|
|
579
|
+
const scanned = sources.filter((source) => !fileAllowed(source.file));
|
|
580
|
+
if (scanned.length === 0) {
|
|
581
|
+
io.err(
|
|
582
|
+
"walkeros-design-check: no file scanned; check the paths and --allow globs"
|
|
583
|
+
);
|
|
584
|
+
return 2;
|
|
585
|
+
}
|
|
586
|
+
const context = {
|
|
587
|
+
designNames,
|
|
588
|
+
declaredNames: collectDeclaredNames(
|
|
589
|
+
sources.map((source) => source.text).join("\n")
|
|
590
|
+
),
|
|
591
|
+
allowVarPrefixes,
|
|
592
|
+
// A literal behind a local name: scanned files only, never a design name.
|
|
593
|
+
literalLengths: new Set(
|
|
594
|
+
scanned.flatMap((source) => [...localLiteralLengths(source.file, source.text)]).filter((name) => !designNames.has(name.replace(/^--/, "")))
|
|
595
|
+
)
|
|
596
|
+
};
|
|
597
|
+
const findings = scanned.flatMap((source) => scanFile(source.file, source.text, context)).filter((finding) => !ruleAllowed(finding));
|
|
598
|
+
for (const finding of findings) {
|
|
599
|
+
io.out(
|
|
600
|
+
`${finding.file}:${finding.line}:${finding.column} ${finding.rule} ${finding.match}`
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
io.err(
|
|
604
|
+
`walkeros-design-check: ${findings.length} finding(s) in ${scanned.length} file(s)`
|
|
605
|
+
);
|
|
606
|
+
return findings.length > 0 ? 1 : 0;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// src/design/check/bin.ts
|
|
610
|
+
var write = (stream) => (line) => {
|
|
611
|
+
stream.write(`${line}
|
|
612
|
+
`);
|
|
613
|
+
};
|
|
614
|
+
try {
|
|
615
|
+
process.exitCode = main(process.argv.slice(2), {
|
|
616
|
+
cwd: process.cwd(),
|
|
617
|
+
// dist/design: the bin ships beside tokens.css, tailwind.css and base.css.
|
|
618
|
+
designDir: dirname(fileURLToPath(import.meta.url)),
|
|
619
|
+
out: write(process.stdout),
|
|
620
|
+
err: write(process.stderr)
|
|
621
|
+
});
|
|
622
|
+
} catch (error) {
|
|
623
|
+
process.stderr.write(
|
|
624
|
+
`walkeros-design-check: ${error instanceof Error ? error.message : String(error)}
|
|
625
|
+
`
|
|
626
|
+
);
|
|
627
|
+
process.exitCode = 2;
|
|
628
|
+
}
|