veza/apps/web/scripts/report-arbitrary-values.mjs
senke 39b2b642d2 feat(web): UI premium Discord/Spotify-like — tokens, shadows, focus, layout
Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y):

- Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source
  for layout/shell (index.css), shadows (design-system.css), durations/easing.
- Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height
  (max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500
  replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes.
- Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls,
  AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item,
  TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable.
- ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary.
- Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts.
- Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories.
- .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification.
- apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 17:15:58 +01:00

107 lines
3.3 KiB
JavaScript

#!/usr/bin/env node
/**
* Rapport des valeurs arbitraires Tailwind (w-[...], h-[...], gap-[...], etc.)
* pour prioriser les migrations vers le design system.
* Usage: node scripts/report-arbitrary-values.mjs [--json] [--dir src/components]
* Sortie: rapport (fichier, ligne, pattern) — pas de remplacement automatique.
*/
import fs from 'fs';
import path from 'path';
const ARBITRARY_PATTERNS = [
{ name: 'width', regex: /(?:^|\s)(w-|min-w-|max-w-)\[[^\]]+\]/g },
{ name: 'height', regex: /(?:^|\s)(h-|min-h-|max-h-)\[[^\]]+\]/g },
{ name: 'gap', regex: /(?:^|\s)gap-\[[^\]]+\]/g },
{ name: 'padding', regex: /(?:^|\s)(p-|px-|py-|pt-|pb-|pl-|pr-)\[[^\]]+\]/g },
{ name: 'margin', regex: /(?:^|\s)(m-|mx-|my-|mt-|mb-|ml-|mr-)\[[^\]]+\]/g },
{ name: 'space', regex: /(?:^|\s)space-[xy]-\[[^\]]+\]/g },
{ name: 'rounded', regex: /(?:^|\s)rounded-\[[^\]]+\]/g },
{ name: 'shadow', regex: /(?:^|\s)shadow-\[[^\]]+\]/g },
];
const DEFAULT_DIRS = ['src/components', 'src/features'];
const IGNORE_DIRS = ['node_modules', 'dist', '.storybook', 'dist_verification'];
const EXTENSIONS = new Set(['.tsx', '.jsx', '.ts', '.js']);
function parseArgs() {
const args = process.argv.slice(2);
let dirs = DEFAULT_DIRS;
let json = false;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--json') json = true;
else if (args[i] === '--dir' && args[i + 1]) {
dirs = [args[++i]];
}
}
return { dirs, json };
}
function* walkFiles(dir, base = dir) {
if (!fs.existsSync(dir)) return;
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const e of entries) {
const full = path.join(dir, e.name);
const rel = path.relative(base, full);
if (e.isDirectory()) {
if (!IGNORE_DIRS.includes(e.name)) yield* walkFiles(full, base);
} else if (EXTENSIONS.has(path.extname(e.name))) {
yield { full, rel };
}
}
}
function scanFile(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
const hits = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
for (const { name, regex } of ARBITRARY_PATTERNS) {
regex.lastIndex = 0;
let m;
while ((m = regex.exec(line)) !== null) {
hits.push({
pattern: name,
match: m[0].trim(),
line: i + 1,
});
}
}
}
return hits;
}
function main() {
const { dirs, json } = parseArgs();
const cwd = process.cwd();
const report = [];
for (const dir of dirs) {
const absDir = path.isAbsolute(dir) ? dir : path.join(cwd, dir);
for (const { full, rel } of walkFiles(absDir)) {
const hits = scanFile(full);
if (hits.length > 0) {
report.push({ file: rel, hits });
}
}
}
if (json) {
console.log(JSON.stringify(report, null, 2));
return;
}
console.log('# Rapport des valeurs arbitraires Tailwind\n');
console.log('Prioriser les migrations vers tokens / scale (voir docs/DESIGN_TOKENS.md).\n');
for (const { file, hits } of report) {
console.log(`## ${file}`);
for (const { pattern, match, line } of hits) {
console.log(` ${line}: [${pattern}] ${match}`);
}
console.log('');
}
console.log(`Total: ${report.length} fichiers, ${report.reduce((s, r) => s + r.hits.length, 0)} occurrences.`);
}
main();