@walkeros/explorer 4.7.2 → 4.8.0-next-1791441510508

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 CHANGED
@@ -1,5 +1,47 @@
1
1
  # @walkeros/explorer
2
2
 
3
+ ## 4.8.0-next-1791441510508
4
+
5
+ ### Minor Changes
6
+
7
+ - bf6221f: Explorer now ships the walkerOS design system. Import
8
+ `@walkeros/explorer/design/tokens.css` for colour, type, spacing, radius and
9
+ layer tokens (dark by default, light under `data-theme="light"`), the opt-in
10
+ `design/base.css`, the Tailwind v4 bridge `design/tailwind.css`, and typed
11
+ colour and font constants from `@walkeros/explorer/design`. A
12
+ `walkeros-design-check` command lints consuming code. Existing exports are
13
+ unchanged.
14
+
15
+ ### Patch Changes
16
+
17
+ - d5cbc9c: New `@walkeros/explorer/design/components` export with `Button`,
18
+ `InlineCode` and `EventLegend`, styled by `@walkeros/explorer/styles.css`.
19
+ `Button` forwards tagging attributes to its element and accepts a router link
20
+ component.
21
+ - d5cbc9c: `@walkeros/explorer/design/components` gains the building blocks of
22
+ the walkerOS home page: `Icon`, `InstallCommand`, `Card`, `Eyebrow`, `Stat`,
23
+ `Text`, `TextLink`, `CheckList`, `SectionHeading`, `ProblemCard`,
24
+ `FeatureItem`, `FaqItem`, `PlanCard`, `CaseCard`, `HighlightCard`, `Section`,
25
+ `CardGrid`, `Split`, `Cluster`, `Hero`, and the three home page demos
26
+ (`HeroTaggingViz`, `ArticleTeaserTracking`, `DestinationMappingViz`). Every
27
+ design component forwards tagging attributes to its root.
28
+ - 01d86a5: Explorer components now read the walkerOS design tokens: import
29
+ `@walkeros/explorer/design/tokens.css` beside `styles.css`. The old theme
30
+ variables (`--bg-box`, `--color-text` and the rest) are removed. Code panels
31
+ are dark in both page themes. Removed exports: `lighthouseTheme`,
32
+ `registerLighthouseTheme`, `palenightTheme`, `registerPalenightTheme`, type
33
+ `ExplorerTheme`, and `registerAllThemes`, replaced by `registerTheme`.
34
+ `ELB_THEME_DARK` is now exported.
35
+ - d5cbc9c: `@walkeros/explorer/design/tokens.css` now declares every type style
36
+ as CSS variables: `--type-<style>-size`, `-line-height`, `-weight`, `-family`
37
+ and, when the style sets one, `-tracking`. Components can take sizes and
38
+ weights from the design system instead of fixed values.
39
+ - Updated dependencies [5593d7b]
40
+ - @walkeros/core@4.8.0-next-1791441510508
41
+ - @walkeros/collector@4.8.0-next-1791441510508
42
+ - @walkeros/web-core@4.8.0-next-1791441510508
43
+ - @walkeros/web-source-browser@4.8.0-next-1791441510508
44
+
3
45
  ## 4.7.2
4
46
 
5
47
  ### Patch Changes
package/README.md CHANGED
@@ -21,21 +21,50 @@ npm install @walkeros/explorer
21
21
 
22
22
  ## Quick start
23
23
 
24
- Import a component and the stylesheet:
24
+ Import the design tokens once per page, then explorer's styles and a component:
25
25
 
26
26
  ```tsx
27
- import { MappingDemo } from '@walkeros/explorer';
27
+ import '@walkeros/explorer/design/tokens.css';
28
28
  import '@walkeros/explorer/styles.css';
29
+ import { CodeBox } from '@walkeros/explorer';
29
30
 
30
- <MappingDemo
31
- input='{"name": "example"}'
32
- config='{"transform": "uppercase"}'
33
- fn={async (input, config) => transform(input, config)}
34
- />;
31
+ <CodeBox code={'{ "name": "page view" }'} language="json" label="Event" />;
35
32
  ```
36
33
 
37
- Switch themes via the `data-theme` attribute on a parent element (`light` or
38
- `dark`).
34
+ In a Tailwind v4 build, import `@walkeros/explorer/design/tailwind.css` after
35
+ `@import "tailwindcss"` instead of `tokens.css`: it brings the tokens and a
36
+ utility for every design colour and type style.
37
+ `@walkeros/explorer/design/base.css` adds optional page-wide base rules (font,
38
+ colours, focus ring, selection).
39
+
40
+ ## Themes
41
+
42
+ Set `data-theme="dark"` (the default) or `data-theme="light"` on the page root.
43
+ Code surfaces (`CodeBox`, `CodeView`, `Code`, the preview) stay dark in both
44
+ themes. Monaco uses one code theme; `Code` and `CodeBox` register it themselves.
45
+ A Monaco editor of your own registers it with:
46
+
47
+ ```ts
48
+ import { registerTheme, ELB_THEME_DARK } from '@walkeros/explorer';
49
+
50
+ registerTheme(monaco);
51
+ monaco.editor.setTheme(ELB_THEME_DARK);
52
+ ```
53
+
54
+ ## Design system
55
+
56
+ Explorer ships the walkerOS design system:
57
+
58
+ | Import | What |
59
+ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
60
+ | `@walkeros/explorer/design/tokens.css` | The design tokens as CSS custom properties |
61
+ | `@walkeros/explorer/design/tailwind.css` | The tokens as a Tailwind v4 theme |
62
+ | `@walkeros/explorer/design/base.css` | Optional base rules for a whole page |
63
+ | `@walkeros/explorer/design` | The token values as constants, for code that cannot read CSS variables |
64
+ | `@walkeros/explorer/design/components` | React building blocks of walkerOS pages (`Button`, `InlineCode`, `EventLegend` and more), with no Monaco or walkerOS runtime |
65
+
66
+ The `walkeros-design-check` command checks a package's own styles against the
67
+ design system.
39
68
 
40
69
  ## Documentation
41
70
 
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ // Committed so npm links the bin at install time, before the first build. The
3
+ // checker itself is built into dist/design, beside the design CSS it reads.
4
+ import { existsSync } from 'node:fs';
5
+
6
+ const checker = new URL('../dist/design/check.mjs', import.meta.url);
7
+
8
+ if (existsSync(checker)) {
9
+ await import(checker.href);
10
+ } else {
11
+ process.stderr.write(
12
+ 'walkeros-design-check: dist/design/check.mjs is missing, run `npm run build` in apps/explorer\n',
13
+ );
14
+ process.exitCode = 2;
15
+ }
@@ -0,0 +1,55 @@
1
+ /*
2
+ * walkerOS base rules. Opt-in and global: import once per page after
3
+ * tokens.css. Explorer's own reset stays scoped to .elb-explorer.
4
+ */
5
+ @layer base {
6
+ :root,
7
+ :host,
8
+ [data-theme] {
9
+ --motion: 180ms;
10
+ --ease: cubic-bezier(0.2, 0, 0, 1);
11
+ }
12
+
13
+ /* The one media query of the design system. */
14
+ @media (prefers-reduced-motion: reduce) {
15
+ :root,
16
+ :host,
17
+ [data-theme] {
18
+ --motion: 0ms;
19
+ }
20
+ }
21
+
22
+ :root,
23
+ :host,
24
+ [data-theme='dark'] {
25
+ color-scheme: dark;
26
+ }
27
+
28
+ [data-theme='light'] {
29
+ color-scheme: light;
30
+ }
31
+
32
+ /* Islands recompute these from their own theme's tokens. */
33
+ html,
34
+ :host,
35
+ [data-theme] {
36
+ font-family: var(--font-sans);
37
+ color: var(--fg);
38
+ scrollbar-color: var(--border-strong) transparent;
39
+ }
40
+
41
+ /* Only the page paints a ground; a background on :host would cover the customer page. */
42
+ html {
43
+ background-color: var(--bg);
44
+ }
45
+
46
+ :focus-visible {
47
+ outline: 2px solid var(--focus);
48
+ outline-offset: 3px;
49
+ }
50
+
51
+ ::selection {
52
+ background-color: var(--primary);
53
+ color: var(--on-primary);
54
+ }
55
+ }
@@ -0,0 +1,455 @@
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
+ "font",
58
+ "text",
59
+ "shadow",
60
+ "inset-shadow",
61
+ "drop-shadow",
62
+ "text-shadow"
63
+ ];
64
+ var TAILWIND_KEPT_NAMESPACES = [
65
+ "animate",
66
+ "aspect",
67
+ "blur",
68
+ "breakpoint",
69
+ "container",
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
+ "radius-class",
109
+ "var-fallback",
110
+ "undeclared-var",
111
+ "color-scheme",
112
+ "z-index"
113
+ ];
114
+ var NAMED_COLORS = new Set(
115
+ `aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown
116
+ burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan
117
+ darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid
118
+ darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet
119
+ deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro
120
+ ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki
121
+ lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow
122
+ lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray
123
+ lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine
124
+ mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise
125
+ mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab
126
+ orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru
127
+ pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown
128
+ seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan
129
+ teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen`.split(
130
+ /\s+/
131
+ )
132
+ );
133
+ 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";
134
+ 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";
135
+ var START = "(?<![A-Za-z0-9_-])";
136
+ var END = "(?![A-Za-z0-9_-])";
137
+ var SIDE = "(?:-(?:t|r|b|l|s|e|tl|tr|br|bl|ss|se|es|ee))?";
138
+ var CLASS_RULES = [
139
+ [
140
+ "palette-class",
141
+ new RegExp(
142
+ `${START}(?:${COLOR_UTILITY})-(?:${PALETTE})-(?:50|[1-9]00|950)(?:/\\d+)?${END}`,
143
+ "g"
144
+ )
145
+ ],
146
+ [
147
+ "white-black-class",
148
+ new RegExp(
149
+ `${START}(?:${COLOR_UTILITY})-(?:white|black)(?:/\\d+)?${END}`,
150
+ "g"
151
+ )
152
+ ],
153
+ ["dark-variant", new RegExp(`${START}dark:(?=[A-Za-z0-9_\\[!(-])`, "g")],
154
+ [
155
+ "shadow-class",
156
+ // Bare drop-shadow is flagged too (the reset removes it), but not a CSS
157
+ // drop-shadow() call. Bare shadow is not: "shadow root" prose would fire.
158
+ new RegExp(
159
+ `${START}(?:(?:inset-|drop-|text-)?shadow-(?:2xs|xs|sm|md|lg|xl|2xl|inner)${END}|drop-shadow(?![A-Za-z0-9_(-]))`,
160
+ "g"
161
+ )
162
+ ],
163
+ ["z-class", new RegExp(`${START}-?z-(?:\\d+|\\[-?\\d+\\])${END}`, "g")],
164
+ [
165
+ "text-size-class",
166
+ new RegExp(`${START}text-(?:xs|sm|base|lg|xl|[2-9]xl)${END}`, "g")
167
+ ],
168
+ // An arbitrary value that reads a design radius (`rounded-[calc(var(--radius-md)-1px)]`) is not flagged.
169
+ [
170
+ "radius-class",
171
+ new RegExp(
172
+ `${START}rounded${SIDE}(?:-(?:2xl|3xl|4xl|\\[(?![^\\]]*var\\(--radius-)[^\\]\\s]+\\]))?${END}`,
173
+ "g"
174
+ )
175
+ ]
176
+ ];
177
+ var STYLE_FILE = /\.(?:css|scss)$/;
178
+ 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;
179
+ var COLOR_FUNCTION = /(?<![A-Za-z0-9-])(?:rgba?|hsla?|hwb|oklab|oklch|lab|lch)\([^)]*\)/g;
180
+ var CSS_COLOR_FUNCTION = /(?<![A-Za-z0-9_-])color\([^)]*\)/g;
181
+ 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;
182
+ var SCRIPT_COLOR_PROPERTY = /(?<![A-Za-z0-9_-])(?:color|background|backgroundColor|borderColor|outlineColor|fill|stroke|caretColor|accentColor)\s*[:=]\s*\{?\s*['"]([A-Za-z]+)['"]/g;
183
+ var WORD = /(?<![A-Za-z0-9_-])[A-Za-z]+(?![A-Za-z0-9_-])/g;
184
+ var URL_FUNCTION = /url\([^)]*\)/g;
185
+ var VAR_REFERENCE = /var\(\s*--([A-Za-z0-9_-]+)\s*(,)?/g;
186
+ var TAILWIND_VAR = /-\(--([A-Za-z0-9_-]+)\)/g;
187
+ var COLOR_SCHEME = /prefers-color-scheme/g;
188
+ var Z_INDEX_CSS = /(?<![A-Za-z0-9_-])z-index\s*:\s*-?\d+/g;
189
+ var Z_INDEX_SCRIPT = /(?<![A-Za-z0-9_-])zIndex\s*:\s*['"]?-?\d+/g;
190
+ var STRING_LITERAL = /'(?:\\.|[^'\\\n])*'|"(?:\\.|[^"\\\n])*"|`(?:\\.|[^`\\])*`/g;
191
+ var APPLY = /@apply\s+[^;}]+/g;
192
+ function classRegions(style, text) {
193
+ const regions = [];
194
+ forEachMatch(
195
+ style ? APPLY : STRING_LITERAL,
196
+ text,
197
+ (match) => regions.push({ offset: match.index, text: match[0] })
198
+ );
199
+ return regions;
200
+ }
201
+ function positions(text) {
202
+ const starts = [0];
203
+ for (let i = 0; i < text.length; i++)
204
+ if (text[i] === "\n") starts.push(i + 1);
205
+ return (index) => {
206
+ let low = 0;
207
+ let high = starts.length - 1;
208
+ while (low < high) {
209
+ const mid = low + high + 1 >> 1;
210
+ if (starts[mid] <= index) low = mid;
211
+ else high = mid - 1;
212
+ }
213
+ return { line: low + 1, column: index - starts[low] + 1 };
214
+ };
215
+ }
216
+ function scanFile(file, text, context) {
217
+ const style = STYLE_FILE.test(file);
218
+ const position = positions(text);
219
+ const findings = [];
220
+ const add = (rule, index, match) => {
221
+ findings.push({ file, ...position(index), rule, match });
222
+ };
223
+ forEachMatch(
224
+ HEX,
225
+ text,
226
+ (match) => add("color-literal", match.index, match[0])
227
+ );
228
+ forEachMatch(
229
+ COLOR_FUNCTION,
230
+ text,
231
+ (match) => add("color-literal", match.index, match[0])
232
+ );
233
+ if (style) {
234
+ forEachMatch(
235
+ CSS_COLOR_FUNCTION,
236
+ text,
237
+ (match) => add("color-literal", match.index, match[0])
238
+ );
239
+ forEachMatch(STYLE_DECLARATION, text, (match) => {
240
+ forEachMatch(WORD, match[2].replace(URL_FUNCTION, " "), (word) => {
241
+ if (NAMED_COLORS.has(word[0].toLowerCase()))
242
+ add("named-color", match.index, word[0]);
243
+ });
244
+ });
245
+ } else {
246
+ forEachMatch(SCRIPT_COLOR_PROPERTY, text, (match) => {
247
+ if (NAMED_COLORS.has(match[1].toLowerCase()))
248
+ add("named-color", match.index, match[1]);
249
+ });
250
+ }
251
+ for (const region of classRegions(style, text)) {
252
+ for (const [rule, pattern] of CLASS_RULES) {
253
+ forEachMatch(
254
+ pattern,
255
+ region.text,
256
+ (match) => add(rule, region.offset + match.index, match[0])
257
+ );
258
+ }
259
+ }
260
+ const declared = (name) => context.declaredNames.has(name) || TAILWIND_VARIABLES.some((prefix) => name.startsWith(prefix)) || context.allowVarPrefixes.some((prefix) => name.startsWith(prefix));
261
+ forEachMatch(VAR_REFERENCE, text, (match) => {
262
+ const name = match[1];
263
+ if (context.designNames.has(name)) {
264
+ if (match[2] === ",") add("var-fallback", match.index, match[0]);
265
+ } else if (!declared(name)) {
266
+ add("undeclared-var", match.index, match[0]);
267
+ }
268
+ });
269
+ forEachMatch(TAILWIND_VAR, text, (match) => {
270
+ if (!context.designNames.has(match[1]) && !declared(match[1])) {
271
+ add("undeclared-var", match.index + 1, match[0].slice(1));
272
+ }
273
+ });
274
+ forEachMatch(
275
+ COLOR_SCHEME,
276
+ text,
277
+ (match) => add("color-scheme", match.index, match[0])
278
+ );
279
+ forEachMatch(
280
+ Z_INDEX_CSS,
281
+ text,
282
+ (match) => add("z-index", match.index, match[0])
283
+ );
284
+ if (!style)
285
+ forEachMatch(
286
+ Z_INDEX_SCRIPT,
287
+ text,
288
+ (match) => add("z-index", match.index, match[0])
289
+ );
290
+ return findings.sort(
291
+ (a, b) => a.line - b.line || a.column - b.column || a.rule.localeCompare(b.rule)
292
+ );
293
+ }
294
+
295
+ // src/design/check/cli.ts
296
+ var SCANNED = /* @__PURE__ */ new Set([
297
+ ".ts",
298
+ ".tsx",
299
+ ".js",
300
+ ".jsx",
301
+ ".mjs",
302
+ ".cjs",
303
+ ".css",
304
+ ".scss",
305
+ ".mdx",
306
+ ".html",
307
+ ".svg"
308
+ ]);
309
+ var SKIPPED_DIRS = /* @__PURE__ */ new Set([
310
+ "node_modules",
311
+ "dist",
312
+ "build",
313
+ "coverage",
314
+ "storybook-static"
315
+ ]);
316
+ var DESIGN_CSS = ["tokens.css", "tailwind.css", "base.css"];
317
+ var USAGE = "usage: walkeros-design-check [--allow [<rule>:]<glob>]... [--allow-var <prefix>]... <path>...";
318
+ var SCOPED = /^([a-z-]+):(.+)$/;
319
+ var isRule = (value) => RULES.some((rule) => rule === value);
320
+ function parseAllow(value) {
321
+ const scoped = SCOPED.exec(value);
322
+ if (scoped !== null) {
323
+ const rule = scoped[1];
324
+ if (!isRule(rule)) return `unknown rule "${rule}" in --allow ${value}`;
325
+ const pattern2 = globToRegExp(scoped[2]);
326
+ return pattern2 === void 0 ? `invalid glob in --allow ${value}` : { rule, pattern: pattern2 };
327
+ }
328
+ const pattern = globToRegExp(value);
329
+ return pattern === void 0 ? `invalid glob in --allow ${value}` : { pattern };
330
+ }
331
+ function usage(io, problem) {
332
+ io.err(`walkeros-design-check: ${problem}`);
333
+ io.err(USAGE);
334
+ return 2;
335
+ }
336
+ function walk(path, into) {
337
+ if (statSync(path).isFile()) {
338
+ if (SCANNED.has(extname(path))) into.push(path);
339
+ return;
340
+ }
341
+ const entries = readdirSync(path, { withFileTypes: true }).sort(
342
+ (a, b) => a.name.localeCompare(b.name)
343
+ );
344
+ for (const entry of entries) {
345
+ if (entry.name.startsWith(".") || SKIPPED_DIRS.has(entry.name)) continue;
346
+ const child = join(path, entry.name);
347
+ if (entry.isDirectory()) walk(child, into);
348
+ else if (entry.isFile() && SCANNED.has(extname(entry.name)))
349
+ into.push(child);
350
+ }
351
+ }
352
+ function main(argv, io) {
353
+ const allows = [];
354
+ const allowVarPrefixes = [];
355
+ const roots = [];
356
+ for (let i = 0; i < argv.length; i++) {
357
+ const arg = argv[i];
358
+ if (arg === "--allow" || arg === "--allow-var") {
359
+ const value = argv[i + 1];
360
+ i++;
361
+ if (value === void 0 || value === "" || value.startsWith("--"))
362
+ return usage(io, `${arg} needs a value`);
363
+ if (arg === "--allow-var") {
364
+ allowVarPrefixes.push(value);
365
+ continue;
366
+ }
367
+ const allow = parseAllow(value);
368
+ if (typeof allow === "string") return usage(io, allow);
369
+ allows.push(allow);
370
+ } else if (arg.startsWith("--")) {
371
+ return usage(io, `unknown option ${arg}`);
372
+ } else {
373
+ roots.push(arg);
374
+ }
375
+ }
376
+ if (roots.length === 0) return usage(io, "no path given");
377
+ const missing = DESIGN_CSS.filter(
378
+ (file) => !existsSync(join(io.designDir, file))
379
+ );
380
+ if (missing.length > 0) {
381
+ io.err(
382
+ `walkeros-design-check: ${missing.join(", ")} missing in ${io.designDir}; build @walkeros/explorer first`
383
+ );
384
+ return 2;
385
+ }
386
+ const designNames = collectDeclaredNames(
387
+ DESIGN_CSS.map(
388
+ (file) => readFileSync(join(io.designDir, file), "utf8")
389
+ ).join("\n")
390
+ );
391
+ const files = [];
392
+ for (const root of roots) {
393
+ const path = resolve(io.cwd, root);
394
+ if (!existsSync(path)) {
395
+ io.err(`walkeros-design-check: ${root} does not exist`);
396
+ return 2;
397
+ }
398
+ walk(path, files);
399
+ }
400
+ const sources = files.map((path) => ({
401
+ file: relative(io.cwd, path).split(sep).join("/"),
402
+ text: readFileSync(path, "utf8")
403
+ }));
404
+ const fileAllowed = (file) => allows.some(
405
+ (allow) => allow.rule === void 0 && allow.pattern.test(file)
406
+ );
407
+ const ruleAllowed = (finding) => allows.some(
408
+ (allow) => allow.rule === finding.rule && allow.pattern.test(finding.file)
409
+ );
410
+ const scanned = sources.filter((source) => !fileAllowed(source.file));
411
+ if (scanned.length === 0) {
412
+ io.err(
413
+ "walkeros-design-check: no file scanned; check the paths and --allow globs"
414
+ );
415
+ return 2;
416
+ }
417
+ const context = {
418
+ designNames,
419
+ declaredNames: collectDeclaredNames(
420
+ sources.map((source) => source.text).join("\n")
421
+ ),
422
+ allowVarPrefixes
423
+ };
424
+ const findings = scanned.flatMap((source) => scanFile(source.file, source.text, context)).filter((finding) => !ruleAllowed(finding));
425
+ for (const finding of findings) {
426
+ io.out(
427
+ `${finding.file}:${finding.line}:${finding.column} ${finding.rule} ${finding.match}`
428
+ );
429
+ }
430
+ io.err(
431
+ `walkeros-design-check: ${findings.length} finding(s) in ${scanned.length} file(s)`
432
+ );
433
+ return findings.length > 0 ? 1 : 0;
434
+ }
435
+
436
+ // src/design/check/bin.ts
437
+ var write = (stream) => (line) => {
438
+ stream.write(`${line}
439
+ `);
440
+ };
441
+ try {
442
+ process.exitCode = main(process.argv.slice(2), {
443
+ cwd: process.cwd(),
444
+ // dist/design: the bin ships beside tokens.css, tailwind.css and base.css.
445
+ designDir: dirname(fileURLToPath(import.meta.url)),
446
+ out: write(process.stdout),
447
+ err: write(process.stderr)
448
+ });
449
+ } catch (error) {
450
+ process.stderr.write(
451
+ `walkeros-design-check: ${error instanceof Error ? error.message : String(error)}
452
+ `
453
+ );
454
+ process.exitCode = 2;
455
+ }