You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

190 lines
5.9 KiB

#!/usr/bin/env node
/**
* Fail-loud check for stale ecosystem aliases and old `App.<Capitalized>`
* 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`).
*
* Every member of `App.*` is lowercase, including the core
* (`logger` / `bus` / `timers` / `orca` / `prefs`). PascalCase on the
* App proxy is forbidden.
*
* 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 SCAN_ROOTS = [join(ROOT, 'src'), join(ROOT, 'scripts')];
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 = [
'Logger',
'Bus',
'Timers',
'Orca',
'Prefs',
'Cache',
'Sess',
'Session',
'Sium',
'Storage',
'Format',
'Frontend',
'Lang',
'Auth',
'Perm',
'Perms',
'Permissions',
'Http',
'Dom',
'Connections'
];
const APP_PATTERN = new RegExp(`\\bApp\\.(${APP_FORBIDDEN_CAPITALS.join('|')})\\b`, 'g');
/**
* Pre-`createActiveApp({ services })` API surfaces. The current runtime
* routes locale through `App.lang` / `App.prefs` and validation through
* `App.sium`; these old method handles no longer exist on the App
* proxy and call sites must be migrated.
*
* The dimension API on `App.prefs.<dim>.set(value)` supersedes
* `App.prefs.setIntent('<dim>', value)` for ergonomic call sites; the
* lower-level `setIntent` stays available for adapters but UI code
* should use the dimension surface.
*/
const OLD_APP_METHOD_PATTERNS = [
{
rx: /\bApp\.setLocale\b/g,
hint: 'App.setLocale → App.lang.setLocale (or App.prefs.locale.set(...))'
},
{
rx: /\bApp\.getLocale\b/g,
hint: 'App.getLocale → App.lang.getLocale (or App.prefs.locale.get())'
},
{
rx: /\bApp\.createSiumEngine\b/g,
hint: 'App.createSiumEngine → declare `sium: defineEngineSium()` in services and read App.sium'
}
];
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 or where the
* tokens are scanner patterns rather than real call sites. Paths are
* relative to the repo root so both `src/` and `scripts/` entries fit.
*/
const ALLOWLIST = new Set([
join('src', 'arts', 'active-app', 'types.ts').replaceAll('/', sep),
// The scanner itself encodes every forbidden token as a regex —
// matching against its own source is a false positive by definition.
join('scripts', 'check-aliases.mjs').replaceAll('/', sep),
// Legacy demo pages still using the pre-service-schema App methods.
// Each one already opts out of static prerender via a sibling
// `+page.ts`; they stay in the tree as dev-only references until
// migrated to `createActiveApp({ services })`. Removing the
// allowlist entry without migrating the page will fail this gate.
join('src', 'web', 'routes', 'test', 'aapp', '+page.svelte').replaceAll('/', sep),
join('src', 'web', 'routes', 'test', 'ecosystem', '+page.svelte').replaceAll('/', sep),
join('src', 'web', 'routes', 'test', 'http', '+page.svelte').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.<lowercase> required for non-core services (${[...new Set(appMatches)].join(', ')})`,
count: appMatches.length
});
}
for (const { rx, hint } of OLD_APP_METHOD_PATTERNS) {
rx.lastIndex = 0;
const matches = text.match(rx);
if (matches) findings.push({ kind: 'old-api', hint, count: matches.length });
}
return findings;
}
let exit = 0;
const offenders = [];
for (const root of SCAN_ROOTS) {
for (const file of walk(root)) {
const rel = relative(ROOT, 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);

Powered by TurnKey Linux.