The first `pnpm -r exec eslint .` run was bailing at the very first
package (design-tokens), hiding any lint state in the rest of the 69
workspace packages. This commit fixes the structural blockers so the
pipeline runs end-to-end, then sweeps the small, low-risk lint errors
in the next 4 packages it surfaces. Real lint debt that remains
(85 errors, mostly @typescript-eslint/no-unused-vars across many
unrelated packages) is cataloged in docs/AUDIT_PLATFORM.md for follow-
up by package owners.
Structural fixes (eslint config):
- eslint.config.js (root):
• New flat-config block for **/*.cjs and **/scripts/**/*.{js,cjs}
with Node globals (process, console, require, module, __dirname)
and no-console disabled. CLI scripts legitimately print to
stdout. This alone clears the 45 errors in design-tokens'
validate-tokens.cjs.
• Added XMLHttpRequest + ProgressEvent to browser globals so
feedback-client compiles.
- packages/ui/eslint.config.js:
• Added @typescript-eslint/parser — the package-local override
replaced (didn't merge with) the root config, so TS syntax was
being parsed by espree and erroring on every `interface` /
type import.
• Added ignores for dist/** (root's ignores aren't inherited).
• Extended the files glob to .storybook/**/*.{ts,tsx}.
Mechanical lint fixes (no behaviour change):
- design-tokens/scripts/{validate,token-coverage}.cjs: empty catch
binding (catch (e) → catch).
- feedback-client/src/index.ts:
• captureScreen(): preserve caught error via `{ cause: err }`
on the rethrown Error (preserve-caught-error rule, real bug —
previous chain dropped the original stack).
• captureElement(): rename unused parity params mimeType/quality
to _mimeType/_quality and document why they exist.
- logger/__tests__/logger.test.ts: drop unused `LoggerConfig` import.
- extraction-service/{lib/circuit-breaker,modules/extract/{sidecar-
monitor,usage}}.test.ts: drop 3 unused vitest/type imports.
- tracker-web/__tests__/tracker-proxy.test.ts: rename unused local
`url` → `_url`.
New: docs/AUDIT_PLATFORM.md
Tooling-backed audit summary (pnpm install / typecheck / test / lint
results), classification of remaining lint debt by rule, and an
ordered hand-off plan for package owners to clear the rest with
`pnpm --filter <pkg> lint:fix` followed by an eyeball review.
Verified before commit:
- `pnpm typecheck` → pass (all 69 packages compile)
- `pnpm test` → pass (~2,200 tests across 18+ suites)
- `pnpm lint` → 85 pre-existing errors surfaced (none introduced
by this commit; all in unrelated packages — see AUDIT_PLATFORM.md
section P).
Out of scope (left untouched in working tree):
- In-progress nomgap-on-Vercel migration: docker-compose.ecosystem.yml,
products/nomgap/product.json, services/platform-service/src/
modules/flags/seed.ts.
- pnpm-lock.yaml: my `pnpm install -r` regenerated it (+2.9k/-8.5k
lines) — not part of the audit, owner should commit deliberately.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
114 lines
3.6 KiB
JavaScript
Executable File
114 lines
3.6 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* Token validation script — checks for hardcoded colors in source files
|
|
* and reports token coverage per product.
|
|
*
|
|
* Usage: node scripts/validate-tokens.js [product-path]
|
|
*/
|
|
|
|
const { readFileSync, readdirSync, statSync } = require('fs');
|
|
const { join, resolve } = require('path');
|
|
|
|
const HARD_COLOR_REGEX = /#[0-9A-Fa-f]{3,8}\b|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)/g;
|
|
const EXCLUDED_DIRS = ['node_modules', 'dist', 'build', '.git', 'generated', '__mocks__'];
|
|
const INCLUDED_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.swift', '.kt'];
|
|
|
|
function findFiles(dir, files = []) {
|
|
try {
|
|
const items = readdirSync(dir);
|
|
for (const item of items) {
|
|
const fullPath = join(dir, item);
|
|
if (EXCLUDED_DIRS.some(ex => fullPath.includes(ex))) continue;
|
|
|
|
const stat = statSync(fullPath);
|
|
if (stat.isDirectory()) {
|
|
findFiles(fullPath, files);
|
|
} else if (INCLUDED_EXTENSIONS.some(ext => item.endsWith(ext))) {
|
|
files.push(fullPath);
|
|
}
|
|
}
|
|
} catch {
|
|
// Directory might not exist or be accessible
|
|
}
|
|
return files;
|
|
}
|
|
|
|
function analyzeFile(filePath) {
|
|
const content = readFileSync(filePath, 'utf-8');
|
|
const lines = content.split('\n');
|
|
const issues = [];
|
|
|
|
lines.forEach((line, index) => {
|
|
// Skip comments
|
|
if (line.trim().startsWith('//') || line.trim().startsWith('*') || line.trim().startsWith('/*'))
|
|
return;
|
|
|
|
const matches = line.match(HARD_COLOR_REGEX);
|
|
if (matches) {
|
|
// Filter out legitimate uses (like transparency values)
|
|
const suspicious = matches.filter(m => {
|
|
if (m.startsWith('#') && (m.length === 9 || m.length === 5)) return false; // Skip alpha hex
|
|
if (m.includes('0.0') || m.includes('1.0')) return false; // Skip clear/opaque
|
|
// Skip CSS-variable token usage (already tokenized)
|
|
if ((m.startsWith('hsl(') || m.startsWith('rgb(') || m.startsWith('rgba(')) && m.includes('var(--'))
|
|
return false;
|
|
return true;
|
|
});
|
|
|
|
if (suspicious.length > 0) {
|
|
issues.push({
|
|
line: index + 1,
|
|
colors: suspicious,
|
|
content: line.trim().slice(0, 80),
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return issues;
|
|
}
|
|
|
|
function main() {
|
|
const targetPath = process.argv[2] || '.';
|
|
const absolutePath = resolve(targetPath);
|
|
|
|
console.log(`🔍 Scanning ${absolutePath} for hardcoded colors...\n`);
|
|
|
|
const files = findFiles(absolutePath);
|
|
let totalIssues = 0;
|
|
let filesWithIssues = 0;
|
|
|
|
for (const file of files) {
|
|
const issues = analyzeFile(file);
|
|
if (issues.length > 0) {
|
|
filesWithIssues++;
|
|
totalIssues += issues.length;
|
|
const relativePath = file.replace(absolutePath, '').slice(1);
|
|
console.log(`\n📄 ${relativePath}`);
|
|
issues.forEach(issue => {
|
|
console.log(` Line ${issue.line}: ${issue.colors.join(', ')}`);
|
|
console.log(` ${issue.content}`);
|
|
});
|
|
}
|
|
}
|
|
|
|
console.log(`\n${'='.repeat(60)}`);
|
|
console.log(`📊 Summary:`);
|
|
console.log(` Files scanned: ${files.length}`);
|
|
console.log(` Files with hardcoded colors: ${filesWithIssues}`);
|
|
console.log(` Total hardcoded colors found: ${totalIssues}`);
|
|
|
|
if (totalIssues > 0) {
|
|
console.log(`\n⚠️ Consider replacing hardcoded colors with design tokens:`);
|
|
console.log(` Web: var(--ml-<token>) from @bytelyst/design-tokens`);
|
|
console.log(` iOS: MindLystColors.dark<Token> / MindLystColors.light<Token>`);
|
|
console.log(` KMP: MindLystTokens.Dark.<TOKEN> / MindLystTokens.Light.<TOKEN>`);
|
|
process.exit(1);
|
|
} else {
|
|
console.log(`\n✅ No hardcoded colors found! All colors use design tokens.`);
|
|
process.exit(0);
|
|
}
|
|
}
|
|
|
|
main();
|