@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 +42 -0
- package/README.md +38 -9
- package/bin/walkeros-design-check.mjs +15 -0
- package/dist/design/base.css +55 -0
- package/dist/design/check.mjs +455 -0
- package/dist/design/components/index.cjs +2626 -0
- package/dist/design/components/index.cjs.map +1 -0
- package/dist/design/components/index.d.cts +325 -0
- package/dist/design/components/index.d.ts +325 -0
- package/dist/design/components/index.mjs +2611 -0
- package/dist/design/components/index.mjs.map +1 -0
- package/dist/design/index.cjs +201 -0
- package/dist/design/index.cjs.map +1 -0
- package/dist/design/index.d.cts +293 -0
- package/dist/design/index.d.ts +293 -0
- package/dist/design/index.mjs +180 -0
- package/dist/design/index.mjs.map +1 -0
- package/dist/design/tailwind.css +207 -0
- package/dist/design/tokens.css +286 -0
- package/dist/index.d.cts +24 -387
- package/dist/index.d.ts +24 -387
- 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 +2049 -800
- package/package.json +26 -9
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
|
|
24
|
+
Import the design tokens once per page, then explorer's styles and a component:
|
|
25
25
|
|
|
26
26
|
```tsx
|
|
27
|
-
import
|
|
27
|
+
import '@walkeros/explorer/design/tokens.css';
|
|
28
28
|
import '@walkeros/explorer/styles.css';
|
|
29
|
+
import { CodeBox } from '@walkeros/explorer';
|
|
29
30
|
|
|
30
|
-
<
|
|
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
|
-
|
|
38
|
-
`
|
|
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
|
+
}
|