Files
suaveplan/scripts/lint/fix-external-test-imports.ts
2026-05-27 23:47:53 +01:00

132 lines
3.8 KiB
TypeScript

#!/usr/bin/env bun
/**
* Walks tests/ directories at package roots (siblings of src/) and rewrites
* relative imports that now point to migrated feature folders.
*
* For each `../src/<X>.js` import that no longer resolves but
* `../src/<X>/<X>.ts` does, rewrites to `../src/<X>/<X>.js`.
* Falls back to `../src/<X>/index.js` if only the barrel exists.
*/
import {
existsSync,
readdirSync,
readFileSync,
statSync,
writeFileSync,
} from "node:fs";
import { dirname, join, relative } from "node:path";
const PACKAGES_DIR = "packages";
function walkTs(dir: string): string[] {
const out: string[] = [];
try {
for (const name of readdirSync(dir)) {
const full = join(dir, name);
const s = statSync(full);
if (s.isDirectory()) out.push(...walkTs(full));
else if (
(name.endsWith(".ts") || name.endsWith(".tsx")) &&
!name.endsWith(".d.ts")
) {
out.push(full);
}
}
} catch {}
return out;
}
function listPackages(): string[] {
const out: string[] = [];
for (const cat of readdirSync(PACKAGES_DIR)) {
const catDir = join(PACKAGES_DIR, cat);
if (!statSync(catDir).isDirectory()) continue;
for (const plugin of readdirSync(catDir)) {
const pluginDir = join(catDir, plugin);
if (!statSync(pluginDir).isDirectory()) continue;
for (const pkg of readdirSync(pluginDir)) {
const pkgDir = join(pluginDir, pkg);
if (!statSync(pkgDir).isDirectory()) continue;
if (existsSync(join(pkgDir, "src"))) out.push(pkgDir);
}
}
}
return out;
}
function fileExists(p: string): boolean {
try {
statSync(p);
return true;
} catch {
return false;
}
}
function isDirectory(p: string): boolean {
try {
return statSync(p).isDirectory();
} catch {
return false;
}
}
function fixFile(file: string): number {
const src = readFileSync(file, "utf8");
let updated = src;
let fixes = 0;
const re = /"((?:\.\.?\/)+[^"]+\.js)"/g;
const seen = new Set<string>();
for (const m of src.matchAll(re)) seen.add(m[1] as string);
for (const importPath of seen) {
const fileDir = dirname(file);
const stripped = importPath.replace(/\.js$/, "");
const resolved = join(fileDir, stripped);
if (fileExists(`${resolved}.ts`) || fileExists(`${resolved}.tsx`))
continue;
if (!isDirectory(resolved)) continue;
const baseName = relative(fileDir, resolved).split("/").pop() as string;
const innerImpl = join(resolved, `${baseName}.ts`);
const innerIndex = join(resolved, "index.ts");
let newImportPath: string | null = null;
if (fileExists(innerImpl)) {
newImportPath = importPath.replace(/\.js$/, `/${baseName}.js`);
} else if (fileExists(innerIndex)) {
newImportPath = importPath.replace(/\.js$/, "/index.js");
}
if (newImportPath === null) continue;
updated = updated.split(`"${importPath}"`).join(`"${newImportPath}"`);
fixes++;
}
if (updated !== src) writeFileSync(file, updated);
return fixes;
}
function main(): void {
let totalFiles = 0;
let totalFixes = 0;
for (const pkg of listPackages()) {
const testsDir = join(pkg, "tests");
if (!isDirectory(testsDir)) continue;
const files = walkTs(testsDir);
for (const f of files) {
const fixes = fixFile(f);
if (fixes > 0) {
totalFiles++;
totalFixes += fixes;
console.log(`FIX ${f} (${fixes})`);
}
}
}
console.log(`\nFixed ${totalFixes} imports across ${totalFiles} files.`);
}
main();