Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
"url": "https://github.com/synergycodes/overflow-ui/issues"
},
"scripts": {
"build": "vite build",
"build": "vite build && pnpm check:built-css",
"dev": "vite build --watch",
"check:built-css": "tsx ./scripts/check-built-css.ts",
"preview": "vite --config preview-page/vite.preview.config.ts",
"lint": "eslint",
"lint:fix": "eslint --fix",
Expand Down
66 changes: 66 additions & 0 deletions packages/ui/scripts/check-built-css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Guard against the WB-222 bug class in built CSS output.
*
* Mirrors the `local/no-invalid-var` stylelint rule but runs after `vite
* build`, so the release fails even if the source-level check is bypassed
* (disabled rule, --no-verify commit, a build-tool transform that
* regresses, etc.). The rule: `var()`'s first argument must be a
* `<custom-property-name>` (a dashed-ident) — never another function.
* Browsers silently invalidate `var(var(--foo))` and fall back, which is
* how WB-222 shipped a wrong snackbar icon color.
*
* Run after `vite build`. Exits non-zero on any match.
*/
import { globSync, readFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const distDir = resolve(__dirname, '../dist');

// Matches `var(` followed by optional whitespace and another `var(` —
// covers the minified `var(var(` and the unminified `var( var(` /
// `var(\n var(`. Comments inside CSS values are uncommon enough that
// false positives aren't a real concern.
const pattern = /var\(\s*var\(/g;

type Hit = { line: number; column: number; snippet: string };

const failures: Array<{ file: string; hits: Hit[] }> = [];

for (const file of globSync('**/*.css', { cwd: distDir })) {
const content = readFileSync(resolve(distDir, file), 'utf8');
const hits: Hit[] = [];

for (const match of content.matchAll(pattern)) {
const upTo = content.slice(0, match.index);
const line = upTo.split('\n').length;
const column = match.index - upTo.lastIndexOf('\n');
const snippet = content
.slice(Math.max(0, match.index - 12), match.index + 48)
.replace(/\s+/g, ' ');
hits.push({ line, column, snippet });
}

if (hits.length > 0) failures.push({ file, hits });
}

if (failures.length === 0) {
console.log('✔ Built CSS: no invalid var(var(...)) calls');
process.exit(0);
}

const total = failures.reduce((n, f) => n + f.hits.length, 0);
console.error(
`\n✖ Found ${total} invalid var(var(...)) call(s) in built CSS output:\n`,
);
for (const { file, hits } of failures) {
console.error(` packages/ui/dist/${file}`);
for (const { line, column, snippet } of hits) {
console.error(` ${line}:${column} …${snippet}…`);
}
}
console.error(
'\nThe first argument of var() must be a --custom-property name. See WB-222.\n',
);
process.exit(1);
4 changes: 1 addition & 3 deletions packages/ui/src/components/snackbar/snackbar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@
var(--ax-snackbar-bg-information) 100%
),
var(--ax-ui-bg-primary-default);
--ax-public-snackbar-info-icon-color: var(
var(--ax-colors-blue-400)
); /* missing token */
--ax-public-snackbar-info-icon-color: var(--ax-colors-blue-400);
}

.container {
Expand Down
Loading