UNPKG

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
#!/usr/bin/env node "use strict"; 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 */