#!/usr/bin/env node /** * Fail-loud check for stale ecosystem aliases and old `App.` * references in `src/`. The point is to keep documentation honest after * the rename to full-word aliases (`$cache` / `$session` / etc.) and * the move from capitalized service slots (`App.Cache`) to lowercase * declarable services (`App.cache`). * * Allowed capitalised names on `App.*` are the ecosystem core: * `Logger`, `Bus`, `Timers`, `Orca`. Everything else is lowercase. * * Run: `node scripts/check-aliases.mjs` * Exit code: 0 when clean, 1 when any forbidden token is found. */ import { readFileSync, readdirSync, statSync } from 'node:fs'; import { join, relative, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; const HERE = fileURLToPath(new URL('.', import.meta.url)); const ROOT = join(HERE, '..'); const SRC = join(ROOT, 'src'); const STALE_ALIAS_PATTERNS = [ { rx: /\$cach[^e]/g, hint: '$cach* → $cache*' }, { rx: /\$sess[^i]/g, hint: '$sess* → $session*' }, { rx: /\$conn[^e]/g, hint: '$conn* → $connection*' }, { rx: /\$timr\b/g, hint: '$timr → $timer' }, { rx: /\$logr\b/g, hint: '$logr → $logger' }, { rx: /\$fmts\b/g, hint: '$fmts → $format' }, { rx: /\$stor[^a]/g, hint: '$stor* → $storage*' }, { rx: /\$aapp\b/g, hint: '$aapp → $active-app' }, { rx: /\$buss\b/g, hint: '$buss → $bus' }, { rx: /\$libs\/buss\b/g, hint: '$libs/buss → $libs/bus' }, { rx: /\$libs\/logr\b/g, hint: '$libs/logr → $libs/logger' }, { rx: /\$libs\/timr\b/g, hint: '$libs/timr → $libs/timer' }, { rx: /\$libs\/cach[^e]/g, hint: '$libs/cach* → $libs/cache*' } ]; const APP_FORBIDDEN_CAPITALS = [ 'Cache', 'Sess', 'Sium', 'Storage', 'Format', 'Frontend', 'Lang', 'Auth', 'Perms', 'Http', 'Dom' ]; const APP_PATTERN = new RegExp(`\\bApp\\.(${APP_FORBIDDEN_CAPITALS.join('|')})\\b`, 'g'); const SKIP_DIRS = new Set(['node_modules', '.svelte-kit', '.git', 'build', 'dist']); const SCAN_EXTENSIONS = new Set([ '.ts', '.svelte', '.svelte.ts', '.js', '.mjs', '.md', '.txt' ]); /** Files where the references are deliberately historical. */ const ALLOWLIST = new Set([ join('arts', 'active-app', 'types.ts').replaceAll('/', sep) ]); function* walk(dir) { for (const entry of readdirSync(dir)) { if (SKIP_DIRS.has(entry)) continue; const full = join(dir, entry); const st = statSync(full); if (st.isDirectory()) { yield* walk(full); continue; } if (!st.isFile()) continue; const lower = entry.toLowerCase(); if (!Array.from(SCAN_EXTENSIONS).some((ext) => lower.endsWith(ext))) continue; yield full; } } function scanFile(file) { const text = readFileSync(file, 'utf8'); const findings = []; for (const { rx, hint } of STALE_ALIAS_PATTERNS) { rx.lastIndex = 0; const matches = text.match(rx); if (matches) findings.push({ kind: 'alias', hint, count: matches.length }); } APP_PATTERN.lastIndex = 0; const appMatches = text.match(APP_PATTERN); if (appMatches) { findings.push({ kind: 'app', hint: `App. required for non-core services (${[...new Set(appMatches)].join(', ')})`, count: appMatches.length }); } return findings; } let exit = 0; const offenders = []; for (const file of walk(SRC)) { const rel = relative(SRC, file); if (ALLOWLIST.has(rel)) continue; const findings = scanFile(file); if (findings.length === 0) continue; offenders.push({ rel, findings }); exit = 1; } if (offenders.length === 0) { console.log('aliases: clean'); } else { console.log('aliases: found stale references'); for (const { rel, findings } of offenders) { console.log(` ${rel}`); for (const f of findings) console.log(` - ${f.hint} (x${f.count})`); } console.log( '\nFix: run a search/replace using the canonical aliases in svelte.config.js' ); } process.exit(exit);