relative-import-fixer
Version:
CLI tool for TypeScript projects that removes unnecessary relative imports and replaces them with absolute imports based on the paths configured
159 lines (149 loc) • 6.13 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// src/index.ts
var import_commander = require("commander");
// src/core/git-changes.ts
var import_simple_git = __toESM(require("simple-git"));
var git = (0, import_simple_git.default)();
async function checkGitStatusAndExitIfDirty() {
const isClean = await git.status().then((status) => status.isClean());
if (!isClean) {
console.error("\u26D4 You have uncommitted changes. Please commit or stash them before running this script.");
process.exit(1);
}
}
// src/core/has-absolute-paths.ts
var import_fs = __toESM(require("fs"));
var import_path = __toESM(require("path"));
function hasAbsoluteTsconfigPaths(tsconfigPath) {
const fullPath = import_path.default.resolve(tsconfigPath);
const config = JSON.parse(import_fs.default.readFileSync(fullPath, "utf8"));
const hasPaths = config?.compilerOptions?.paths && Object.keys(config.compilerOptions.paths).length > 0;
if (!hasPaths) {
console.error("\u26D4 No absolute paths defined in tsconfig.json (compilerOptions.paths is missing or empty)");
process.exit(1);
}
}
// src/core/transformer.ts
var import_ts_morph = require("ts-morph");
// src/core/package-dependencies.ts
var path2 = __toESM(require("path"));
var packageJsonPath = path2.resolve("package.json");
var text = require("fs").readFileSync(packageJsonPath, "utf-8");
var pkg = JSON.parse(text);
var allDeps = {
...pkg.dependencies,
...pkg.devDependencies,
...pkg.peerDependencies,
...pkg.optionalDependencies
};
var dependencyNames = Object.keys(allDeps);
// src/utils/import-utils.ts
var module2 = __toESM(require("module"));
function isRelativeImport(path3) {
return path3.startsWith(".");
}
function isNodeBuiltin(path3) {
return module2.builtinModules.includes(path3);
}
function isPackageJsonDependency(importName) {
return dependencyNames.includes(importName);
}
function isSideEffectImport(importDecl) {
const hasNoNamedImports = importDecl.getNamedImports().length === 0;
const hasNoDefaultImport = importDecl.getDefaultImport() === void 0;
const hasNoNamespaceImport = importDecl.getNamespaceImport() === void 0;
return hasNoNamedImports && hasNoDefaultImport && hasNoNamespaceImport;
}
// src/core/transformer.ts
async function fixRelativeImports({ globPattern, tsConfigPath }) {
const project = new import_ts_morph.Project({ tsConfigFilePath: tsConfigPath });
project.addSourceFilesAtPaths(globPattern);
for (const sourceFile of project.getSourceFiles()) {
let hasChanged = false;
const diagnostics = sourceFile.getPreEmitDiagnostics();
const hasErrrors = diagnostics.length > 0;
if (hasErrrors) {
console.log(`Errors in file ${sourceFile.getFilePath()}:`);
console.log(
sourceFile.getPreEmitDiagnostics().map((d) => d.getMessageText()).join("\n")
);
hasChanged = true;
}
sourceFile.getImportDeclarations().forEach((importDecl) => {
const moduleSpecifier = importDecl.getModuleSpecifierValue();
if (isSideEffectImport(importDecl)) return;
if (!isRelativeImport(moduleSpecifier)) return;
const resolvedFile = importDecl.getModuleSpecifierSourceFile();
if (!resolvedFile) return;
const importName = resolvedFile.getBaseNameWithoutExtension();
const isExternal = isNodeBuiltin(moduleSpecifier) || isPackageJsonDependency(importName);
if (!isExternal) {
importDecl.remove();
hasChanged = true;
}
});
if (!hasChanged || !hasErrrors) continue;
sourceFile.fixMissingImports(
{},
{
importModuleSpecifierPreference: "non-relative",
importModuleSpecifierEnding: "minimal"
}
);
sourceFile.organizeImports();
if (hasChanged) {
await sourceFile.save();
console.log("\u2705 Fixed and saved:", sourceFile.getFilePath());
}
}
console.log("\u{1F389} Local imports removed and repaired");
}
// src/core/verify-ts-config-file.ts
var import_fs2 = __toESM(require("fs"));
function verifyTsConfigFile(tsConfigPath) {
if (!import_fs2.default.existsSync(tsConfigPath)) {
console.error(`\u274C File not found ${tsConfigPath}`);
process.exit(1);
}
}
// src/index.ts
var program = new import_commander.Command();
program.name("relative-import-fixer").description("Fix and convert relative imports to absolute imports").option("-f, --force", "skip Git check and force execution").option("-t, --tsconfig <path>", "path to tsconfig.json", "tsconfig.json").option("-g, --glob <pattern>", "glob pattern to match files", "src/**/*.{ts,tsx}").parse(process.argv);
var options = program.opts();
(async () => {
if (!options.force) {
await checkGitStatusAndExitIfDirty();
}
const tsConfigPath = options.tsconfig;
const globPattern = options.glob;
verifyTsConfigFile(tsConfigPath);
hasAbsoluteTsconfigPaths(tsConfigPath);
await fixRelativeImports({ globPattern, tsConfigPath });
})();
/**
* @license MIT
* Copyright (c) 2025 Jhoan Hernández
*/