Files
obsidian-livesync/utilsdeno/normalise-imports.ts
T
2026-06-17 04:39:39 +01:00

178 lines
6.6 KiB
TypeScript

// Normalise import and export paths in the codebase to use @lib/ and @/ aliases correctly.
// Use this script by running `deno run --allow-read --allow-write normalise-imports.ts` from the utilsdeno directory.
// Set the --run flag to apply changes: `deno run --allow-read --allow-write normalise-imports.ts --run`
// Set the --all-alias flag to also normalise sibling/child imports (starting with ./): `deno run --allow-read --allow-write normalise-imports.ts --all-alias`
import { Project } from "npm:ts-morph";
import path from "node:path";
import { fileURLToPath } from "node:url";
const isDryRun = !Deno.args.includes("--run");
const allAlias = Deno.args.includes("--all-alias");
if (isDryRun) {
console.log("=== DRY RUN MODE ===");
console.log("To apply changes, run with: deno run --allow-read --allow-write normalise-imports.ts --run\n");
} else {
console.log("=== RUN MODE: WILL MODIFY FILES ===");
}
if (allAlias) {
console.log("Option --all-alias is active: Sibling imports (starting with ./) will also be normalised to @/.\n");
} else {
console.log("Sibling imports (starting with ./) will be left relative.\n");
}
const project = new Project({ tsConfigFilePath: "../tsconfig.json" });
// Manually add files under src/ to ensure those excluded by tsconfig.json (e.g. src/apps) are processed.
project.addSourceFilesAtPaths("../src/**/*.ts");
project.addSourceFilesAtPaths("../src/**/*.svelte");
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.resolve(__dirname, "..");
function toPosixPath(filePath: string): string {
return filePath.replace(/\\/g, "/");
}
const posixProjectRoot = toPosixPath(projectRoot);
const posixSrc = `${posixProjectRoot}/src`;
const posixLibSrc = `${posixProjectRoot}/src/lib/src`;
console.log(`Project Root: ${posixProjectRoot}`);
console.log(`Source Directory: ${posixSrc}`);
console.log(`Library Source Directory: ${posixLibSrc}`);
console.log("");
let modifiedFilesCount = 0;
for (const sourceFile of project.getSourceFiles()) {
const filePath = sourceFile.getFilePath();
const posixFilePath = toPosixPath(filePath);
const fileDir = path.dirname(posixFilePath);
// Only process files inside the project src directory.
if (!posixFilePath.startsWith(posixSrc)) {
// console.log(`Skipping outside src: ${posixFilePath}`);
continue;
}
// Skip files inside directories starting with an underscore (e.g., _tools, _test).
const dirSegments = fileDir.split("/");
const isUnderscore = dirSegments.some((segment) => segment.startsWith("_"));
if (isUnderscore) {
// console.log(`Skipping underscore dir: ${posixFilePath}`);
continue;
}
let fileModified = false;
const imports = sourceFile.getImportDeclarations();
const exports = sourceFile.getExportDeclarations();
const declarations = [...imports, ...exports];
for (const decl of declarations) {
let moduleSpecifier: string | undefined;
try {
moduleSpecifier = decl.getModuleSpecifierValue();
} catch (_err) {
// Gracefully skip declarations that do not have a standard string literal specifier.
continue;
}
if (!moduleSpecifier) {
continue;
}
// Determine if it is an internal import.
const isRelative = moduleSpecifier.startsWith(".");
const isAlias = moduleSpecifier.startsWith("@/") || moduleSpecifier.startsWith("@lib/");
if (!isRelative && !isAlias) {
// Skip external packages/modules.
continue;
}
// Keep relative sibling/child imports unchanged (e.g. ./utils) unless --all-alias is set.
const isSibling = isRelative && !moduleSpecifier.startsWith("..");
if (isSibling && !allAlias) {
continue;
}
// Resolve path to an absolute POSIX path.
let resolvedPath = "";
if (moduleSpecifier.startsWith("@lib/")) {
resolvedPath = `${posixLibSrc}/${moduleSpecifier.slice(5)}`;
} else if (moduleSpecifier.startsWith("@/")) {
resolvedPath = `${posixSrc}/${moduleSpecifier.slice(2)}`;
} else {
// Relative path.
resolvedPath = path.resolve(fileDir, moduleSpecifier);
}
resolvedPath = toPosixPath(path.normalize(resolvedPath));
// Determine correct normalised specifier.
let newSpecifier = "";
const hasExtension =
moduleSpecifier.endsWith(".ts") ||
moduleSpecifier.endsWith(".js") ||
moduleSpecifier.endsWith(".svelte") ||
moduleSpecifier.endsWith(".d.ts");
if (resolvedPath.startsWith(posixLibSrc + "/")) {
let rel = resolvedPath.slice(posixLibSrc.length + 1);
if (!hasExtension && (rel.endsWith(".ts") || rel.endsWith(".js"))) {
// Strip extension if the original import did not have one.
if (rel.endsWith(".ts") && !rel.endsWith(".d.ts")) {
rel = rel.slice(0, -3);
} else if (rel.endsWith(".js")) {
rel = rel.slice(0, -3);
}
}
newSpecifier = `@lib/${rel}`;
} else if (resolvedPath.startsWith(posixSrc + "/")) {
let rel = resolvedPath.slice(posixSrc.length + 1);
if (!hasExtension && (rel.endsWith(".ts") || rel.endsWith(".js"))) {
// Strip extension if the original import did not have one.
if (rel.endsWith(".ts") && !rel.endsWith(".d.ts")) {
rel = rel.slice(0, -3);
} else if (rel.endsWith(".js")) {
rel = rel.slice(0, -3);
}
}
newSpecifier = `@/${rel}`;
} else {
// Target is outside the src directory (e.g. root configs or tests).
continue;
}
// Update module specifier if different.
if (newSpecifier && newSpecifier !== moduleSpecifier) {
if (!fileModified) {
console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`);
fileModified = true;
}
console.log(` - "${moduleSpecifier}" -> "${newSpecifier}"`);
if (!isDryRun) {
decl.setModuleSpecifier(newSpecifier);
}
}
}
if (fileModified) {
modifiedFilesCount++;
}
}
console.log(`\nTotal files to modify: ${modifiedFilesCount}`);
if (!isDryRun) {
project.saveSync();
console.log("All changes successfully saved.");
} else {
console.log("Dry run complete. No changes were written to files.");
}