@hanzo/design 0.4.4 → 0.4.6
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/docs/integrate.md +1 -1
- package/package.json +2 -2
- package/scripts/check-tokens.mjs +41 -0
- package/scripts/lint.mjs +25 -2
package/docs/integrate.md
CHANGED
|
@@ -22,7 +22,7 @@ every token below is a live CSS custom property.
|
|
|
22
22
|
```css
|
|
23
23
|
.panel{
|
|
24
24
|
background: var(--surface-card);
|
|
25
|
-
border: 1px solid var(--border
|
|
25
|
+
border: 1px solid var(--border);
|
|
26
26
|
border-radius: var(--radius-lg);
|
|
27
27
|
padding: var(--space-6);
|
|
28
28
|
color: var(--text-secondary);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/design",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"packageManager": "pnpm@11.17.0",
|
|
5
5
|
"description": "Hanzo Design System \u2014 monochrome, dark-default tokens + components + brand assets, the single source of truth for every Hanzo surface. CSS + typed programmatic tokens.",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"gen": "node scripts/gen-tokens.mjs",
|
|
35
|
-
"test": "node scripts/check-tokens.mjs && node scripts/lint.mjs
|
|
35
|
+
"test": "node scripts/check-tokens.mjs && node scripts/lint.mjs .",
|
|
36
36
|
"build": "pnpm gen && pnpm test && tsc -p tsconfig.json",
|
|
37
37
|
"prepublishOnly": "pnpm build",
|
|
38
38
|
"lint": "node scripts/lint.mjs"
|
package/scripts/check-tokens.mjs
CHANGED
|
@@ -59,6 +59,47 @@ const pass = (msg) => console.log(` ok ${msg}`)
|
|
|
59
59
|
: pass(`all ${urls.length} asset url()s resolve from the package root`)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// ── 1a. the bundles must PARSE ───────────────────────────────────────────
|
|
63
|
+
// 0.4.2 and 0.4.3 shipped a stray `*/` in styles.css AND tailwind.css: an
|
|
64
|
+
// edit added prose to the end of a comment that was already closed, so a
|
|
65
|
+
// paragraph of English sat in the stylesheet as raw CSS, terminated by a
|
|
66
|
+
// second `*/`. Browsers recover from that — they skip to the next thing that
|
|
67
|
+
// looks like a rule and carry on — so the whole kit rendered correctly in
|
|
68
|
+
// Chromium at three widths in both themes, and every screenshot looked right.
|
|
69
|
+
// PostCSS does not recover. It throws, which means those two versions could
|
|
70
|
+
// not be built against by any Vite/Next/Tailwind consumer at all.
|
|
71
|
+
//
|
|
72
|
+
// The lesson is specific: RENDERING IS NOT PARSING. A visual check cannot see
|
|
73
|
+
// this class of defect, because the browser's error recovery is what hides it.
|
|
74
|
+
// So it is checked structurally, here, on the artifacts a consumer receives.
|
|
75
|
+
//
|
|
76
|
+
// Done by hand rather than with postcss because the defect is lexical and the
|
|
77
|
+
// scan is fifteen lines — CSS comments do not nest, so one left-to-right pass
|
|
78
|
+
// that skips comment bodies finds every stray terminator and every unclosed
|
|
79
|
+
// opener. A parser dependency would be a heavier answer to a smaller question.
|
|
80
|
+
{
|
|
81
|
+
for (const f of ['styles.css', 'tailwind.css']) {
|
|
82
|
+
const s = read(join(root, f))
|
|
83
|
+
let i = 0, opened = 0, strays = [], unterminated = null
|
|
84
|
+
while (i < s.length - 1) {
|
|
85
|
+
if (s[i] === '/' && s[i + 1] === '*') {
|
|
86
|
+
opened++
|
|
87
|
+
const j = s.indexOf('*/', i + 2)
|
|
88
|
+
if (j < 0) { unterminated = s.slice(0, i).split('\n').length; break }
|
|
89
|
+
i = j + 2
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
if (s[i] === '*' && s[i + 1] === '/') { strays.push(s.slice(0, i).split('\n').length); i += 2; continue }
|
|
93
|
+
i++
|
|
94
|
+
}
|
|
95
|
+
const braces = [...s].reduce((n, c) => n + (c === '{') - (c === '}'), 0)
|
|
96
|
+
if (unterminated !== null) fail(`${f}: unterminated /* at line ${unterminated}`)
|
|
97
|
+
else if (strays.length) fail(`${f}: stray */ outside any comment at line ${strays.join(', ')} — a consumer's PostCSS build throws here`)
|
|
98
|
+
else if (braces !== 0) fail(`${f}: ${Math.abs(braces)} unbalanced ${braces > 0 ? '{' : '}'}`)
|
|
99
|
+
else pass(`${f} is lexically sound — ${opened} comments closed, braces balanced`)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
62
103
|
// ── 1b. the Tailwind bridge is complete and self-contained ───────────────
|
|
63
104
|
// An app should write ONE import and get working utilities. Each thing checked
|
|
64
105
|
// here failed silently in production before it was checked: a missing slot
|
package/scripts/lint.mjs
CHANGED
|
@@ -26,7 +26,15 @@ const TOKENS = new Set()
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// ── what we read ─────────────────────────────────────────────────────────
|
|
29
|
-
const EXT = new Set(['.css', '.scss', '.jsx', '.tsx', '.js', '.ts', '.vue', '.svelte', '.html'])
|
|
29
|
+
const EXT = new Set(['.css', '.scss', '.jsx', '.tsx', '.js', '.ts', '.vue', '.svelte', '.html', '.md'])
|
|
30
|
+
// Markdown is PROSE, and prose legitimately says `var(--x)` while explaining
|
|
31
|
+
// what a var is. Only its FENCED CSS BLOCKS are code, and only rule 1 runs on
|
|
32
|
+
// them. That is not a technicality — `docs/integrate.md` shipped the
|
|
33
|
+
// copy-paste snippet every new consumer starts from, and its `.panel` rule
|
|
34
|
+
// drew `1px solid var(--border-card)`, a token nothing declares. Anyone who
|
|
35
|
+
// followed the integration guide got a white hairline on black, from the
|
|
36
|
+
// document whose job is to prevent exactly that.
|
|
37
|
+
const FENCED_CSS = /```(?:css|scss)\n([\s\S]*?)```/g
|
|
30
38
|
const SKIP = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'out', 'coverage', 'vendor', '__pycache__'])
|
|
31
39
|
// Third-party marks keep their own hex by design (DESIGN.md §2.4); so do the
|
|
32
40
|
// token files themselves, which are where raw values are SUPPOSED to live, and
|
|
@@ -41,7 +49,13 @@ const SKIP = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'out', '
|
|
|
41
49
|
// An undefined custom property makes the declaration invalid, `border-color`
|
|
42
50
|
// falls back to `currentColor`, and the specimen cards for this system's own
|
|
43
51
|
// border scale had been painting near-white hairlines on black.
|
|
44
|
-
|
|
52
|
+
// The three GENERATED artifacts are on the list for the same reason `tokens/`
|
|
53
|
+
// is: each one is that directory, emitted. `src/tokens.gen.ts` carries every
|
|
54
|
+
// authored literal into TypeScript; `styles.css` and `tailwind.css` are the
|
|
55
|
+
// flattened bundles. Flagging any of them for raw colour is flagging them for
|
|
56
|
+
// working. They are still checked by rule 1 and by check-tokens.mjs, which is
|
|
57
|
+
// what actually governs them.
|
|
58
|
+
const EXEMPT = /(^|\/)(tokens|assets|logos|providers|ui_kits|guidelines)(\/|$)|\.card\.html$|(^|\/)(tokens\.gen\.ts|styles\.css|tailwind\.css)$/
|
|
45
59
|
|
|
46
60
|
const walk = (p, out = []) => {
|
|
47
61
|
const st = statSync(p)
|
|
@@ -95,6 +109,15 @@ function lintUnresolved(rel, src) {
|
|
|
95
109
|
function lintFile(abs, root) {
|
|
96
110
|
const rel = relative(root, abs).split(sep).join('/')
|
|
97
111
|
const raw = readFileSync(abs, 'utf8')
|
|
112
|
+
if (extname(rel) === '.md') {
|
|
113
|
+
// Line numbers stay true: replace everything outside a css fence with
|
|
114
|
+
// blanks rather than extracting the fences, so a reported line is the line.
|
|
115
|
+
let masked = raw.replace(/[^\n]/g, ' ')
|
|
116
|
+
for (const m of raw.matchAll(FENCED_CSS))
|
|
117
|
+
masked = masked.slice(0, m.index) + m[0] + masked.slice(m.index + m[0].length)
|
|
118
|
+
lintUnresolved(rel, strip(masked))
|
|
119
|
+
return
|
|
120
|
+
}
|
|
98
121
|
const src = strip(raw)
|
|
99
122
|
lintUnresolved(rel, src)
|
|
100
123
|
if (EXEMPT.test('/' + rel)) return
|