UNPKG

cca-migration-generator

Version:

PostgreSQL TypeORM migration generator and schema builder with advanced TypeScript support

461 lines (444 loc) 15.3 kB
#!/usr/bin/env node var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/services/config-finder.ts import path from "path"; import fs2 from "fs"; // src/utils/path-utils.ts import fs from "fs"; var IGNORED_DIRECTORIES = ["node_modules", "dist", ".git"]; function isValidDirectory(fullPath, itemName) { try { return fs.statSync(fullPath).isDirectory() && !IGNORED_DIRECTORIES.includes(itemName) && !itemName.startsWith("."); } catch { return false; } } __name(isValidDirectory, "isValidDirectory"); function sanitizeMigrationName(name) { return name.replace(/[^a-zA-Z0-9]/g, "_"); } __name(sanitizeMigrationName, "sanitizeMigrationName"); // src/services/config-finder.ts var _ConfigFinder = class _ConfigFinder { constructor() { this.configFileName = "config.ts"; } findConfig(startDir) { const result = this.searchConfigRecursive(startDir); if (!result?.configPath) { console.warn("Could not find config.ts in project"); } return { configPath: result?.configPath ?? null }; } searchConfigRecursive(dir) { try { const items = fs2.readdirSync(dir); const configPath = items.includes(this.configFileName) ? path.join(dir, this.configFileName) : null; if (configPath) { return { configPath }; } for (const item of items) { const fullPath = path.join(dir, item); if (!isValidDirectory(fullPath, item)) { continue; } const subResult = this.searchConfigRecursive(fullPath); if (subResult?.configPath) { return subResult; } } return { configPath: null }; } catch (error) { if (error instanceof Error) { console.error(`Error accessing ${dir}: ${error.message}`); } return null; } } }; __name(_ConfigFinder, "ConfigFinder"); var ConfigFinder = _ConfigFinder; // src/services/migrations-finder.ts import path2 from "path"; import fs3 from "fs"; var _MigrationsFinder = class _MigrationsFinder { constructor() { this.migrationsFolder = "migrations"; } findMigrations(startDir) { const result = this.searchMigrationsRecursive(startDir); if (!result?.migrationsPath) { console.warn("Could not find migrations folder in project"); } return { migrationsPath: result?.migrationsPath ?? null }; } searchMigrationsRecursive(dir) { try { const items = fs3.readdirSync(dir); const migrationsDir = items.find( (item) => item === this.migrationsFolder && fs3.statSync(path2.join(dir, item)).isDirectory() ); const migrationsPath = migrationsDir ? path2.join(dir, migrationsDir) : null; if (migrationsPath) { return { migrationsPath }; } for (const item of items) { const fullPath = path2.join(dir, item); if (!isValidDirectory(fullPath, item)) { continue; } const subResult = this.searchMigrationsRecursive(fullPath); if (subResult?.migrationsPath) { return subResult; } } return { migrationsPath: null }; } catch (error) { if (error instanceof Error) { console.error(`Error accessing ${dir}: ${error.message}`); } return null; } } }; __name(_MigrationsFinder, "MigrationsFinder"); var MigrationsFinder = _MigrationsFinder; // src/services/combined-finder.ts var _Pathfinder = class _Pathfinder { constructor() { this.findConfigAndMigrations = /* @__PURE__ */ __name((startDir) => { const configFinder = new ConfigFinder(); const migrationsFinder = new MigrationsFinder(); const { configPath } = configFinder.findConfig(startDir); const { migrationsPath } = migrationsFinder.findMigrations(startDir); return { configPath, migrationsPath }; }, "findConfigAndMigrations"); } }; __name(_Pathfinder, "Pathfinder"); var Pathfinder = _Pathfinder; // src/services/migration-file.ts import fs4 from "fs"; import path3 from "path"; var _MigrationFile = class _MigrationFile { constructor(migrationsPath) { this.migrationsPath = migrationsPath; } async findMigrationFile(migrationName) { try { const files = fs4.readdirSync(this.migrationsPath); const file = files.find((file2) => file2.includes(migrationName)); return file ? path3.join(this.migrationsPath, file) : null; } catch (error) { throw new Error(`Error reading migration files: ${error.message}`); } } async readMigrationContent(filePath) { try { return fs4.readFileSync(filePath, "utf-8"); } catch (error) { throw new Error(`Error reading file at ${filePath}: ${error.message}`); } } async verifyMigrationContent(filePath) { const content = await this.readMigrationContent(filePath); if (!content.includes("export class") || !content.includes("implements MigrationInterface")) { throw new Error("Invalid migration file format"); } const convertedContent = this.convertExportToModuleExports(content); try { await fs4.promises.writeFile(filePath, convertedContent, "utf-8"); } catch (error) { throw new Error(`Error writing converted content to file ${filePath}: ${error.message}`); } } convertExportToModuleExports(content) { return content.replace( /import\s*{\s*MigrationInterface\s*,\s*QueryRunner\s*}\s*from\s*["']typeorm["']\s*;/, 'const { MigrationInterface, QueryRunner } = require("typeorm");' ).replace(/\s+implements\s+MigrationInterface/, "").replace(/:\s*QueryRunner/g, "").replace(/:\s*Promise<void>/g, "").replace(/public\s+/g, "").replace( /export\s+class\s+(\w+)/, "module.exports = class $1" ); } }; __name(_MigrationFile, "MigrationFile"); var MigrationFile = _MigrationFile; // src/services/migration-generator.ts import { execSync } from "child_process"; import path5 from "path"; // src/utils/command-utils.ts import path4 from "path"; function getTypeOrmCommand(rootDir, outputType = "js") { const isWindows = process.platform === "win32"; const typeormPath = path4.join(rootDir, "node_modules", "typeorm", "cli.js"); const tsNodeBin = path4.join(rootDir, "node_modules", ".bin", "ts-node"); if (isWindows) { return `cross-env TYPEORM_MIGRATION_EXTENSION=${outputType} "${tsNodeBin}" --esm "${typeormPath}"`; } const baseCommand = `${tsNodeBin} --esm ${typeormPath}`; if (outputType === "js") { return `cross-env TYPEORM_MIGRATION_EXTENSION=js ${baseCommand}`; } return baseCommand; } __name(getTypeOrmCommand, "getTypeOrmCommand"); function escapePathForPlatform(filePath) { const isWindows = process.platform === "win32"; return isWindows ? `"${filePath}"` : `'${filePath}'`; } __name(escapePathForPlatform, "escapePathForPlatform"); // src/services/migration-generator.ts var _MigrationGenerator = class _MigrationGenerator { constructor(options) { this.options = options; } async generate() { const command = getTypeOrmCommand(process.cwd(), this.options.outputType); const escapedConfigPath = escapePathForPlatform(this.options.configPath); const escapedMigrationPath = escapePathForPlatform( path5.join(this.options.migrationsPath, this.options.migrationName) ); execSync( `${command} migration:generate -d ${escapedConfigPath} ${escapedMigrationPath}`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], cwd: process.cwd() } ); } }; __name(_MigrationGenerator, "MigrationGenerator"); var MigrationGenerator = _MigrationGenerator; // src/database/initialize-connection.ts var initializeConnection = /* @__PURE__ */ __name(async (dataSourcePromise) => { try { const dataSource = dataSourcePromise; if (!dataSource.isInitialized) { console.log("Initializing DataSource..."); await dataSource.initialize(); } return dataSource.createQueryRunner(); } catch (error) { console.error("Error initializing connection:", error); throw error; } }, "initializeConnection"); var cleanup = /* @__PURE__ */ __name(async (dataSource, queryRunner) => { if (queryRunner) { await queryRunner.release(); } if (dataSource && dataSource.isInitialized) { await dataSource.destroy(); } }, "cleanup"); // src/services/migration-runner.ts var _MigrationRunner = class _MigrationRunner { constructor() { this.migrationRun = /* @__PURE__ */ __name(async (dataSource) => { let queryRunner; try { queryRunner = await initializeConnection(dataSource); const initializedDataSource = queryRunner.manager.connection; const pendingMigrations = await initializedDataSource.showMigrations(); console.log("Pending migrations:", pendingMigrations); console.log("Running migrations..."); const migrations = await initializedDataSource.runMigrations({ transaction: "all" }); console.log("Applied migrations:", migrations.length); console.log("Migrations completed successfully!"); } catch (error) { console.error("Migration error:", error); throw error; } finally { await cleanup(dataSource, queryRunner); } }, "migrationRun"); } }; __name(_MigrationRunner, "MigrationRunner"); var MigrationRunner = _MigrationRunner; // src/database/config.ts import { DataSource } from "typeorm"; import fs5 from "fs"; var AppDataSource = /* @__PURE__ */ __name(async (migrationsPath, configPath) => { try { if (!configPath || !migrationsPath) { throw new Error("Configuration path or migrations path is missing"); } const configContent = fs5.readFileSync(await configPath, "utf8"); const config = JSON.parse(configContent); return new DataSource({ name: "postgres", type: config.type, host: config.host, port: config.port, username: config.username, password: config.password, database: config.database, synchronize: false, logging: ["query", "error", "schema", "migration"], logger: "advanced-console", entities: [], migrations: [`${migrationsPath}/*.ts`], migrationsTableName: "migrations", migrationsRun: false, metadataTableName: "typeorm_metadata" }); } catch (error) { console.error("Error initializing DataSource:"); console.error("Error message:", error.message); console.error("Error stack:", error.stack); throw error; } }, "AppDataSource"); // src/utils/errors.ts var _ConfigNotFoundException = class _ConfigNotFoundException extends Error { constructor(message) { super(message); this.name = "ConfigNotFoundException"; } }; __name(_ConfigNotFoundException, "ConfigNotFoundException"); var ConfigNotFoundException = _ConfigNotFoundException; // src/utils/find-config-file-recursive.ts import { readdir } from "fs/promises"; import { join, dirname } from "path"; var _ConfigFinder2 = class _ConfigFinder2 { constructor({ fileName = "cca.config.json", maxDepth = 10, startPath = process.cwd() } = {}) { this.fileName = fileName; this.maxDepth = maxDepth; this.startPath = startPath; } async findConfig() { const configPath = await this.searchConfigRecursive(this.startPath, this.maxDepth); if (!configPath) { throw new ConfigNotFoundException( `Could not find ${this.fileName} in any parent directory of ${this.startPath}` ); } return configPath; } async searchConfigRecursive(currentPath, depth) { if (depth < 0) { return null; } try { const files = await readdir(currentPath); const configFile = files.find((file) => file === this.fileName); if (configFile) { return join(currentPath, configFile); } const parentDir = dirname(currentPath); if (parentDir === currentPath) { return null; } return this.searchConfigRecursive(parentDir, depth - 1); } catch (error) { if (error instanceof Error) { console.error(`Error accessing ${currentPath}: ${error.message}`); } return null; } } }; __name(_ConfigFinder2, "ConfigFinder"); var ConfigFinder2 = _ConfigFinder2; // src/utils/find-config-file.ts var findConfigFile = /* @__PURE__ */ __name(async () => { const configFinder = new ConfigFinder2(); try { return await configFinder.findConfig(); } catch (error) { if (error instanceof ConfigNotFoundException) { console.error(error.message); process.exit(1); } throw error; } }, "findConfigFile"); // src/services/handle-migration.ts var _MigrationHandler = class _MigrationHandler { constructor(options) { this.validateOptions(options); this.options = this.buildMigrationOptions(options); this.generator = new MigrationGenerator(this.options); this.fileService = new MigrationFile(this.options.migrationsPath); this.searchResult = new Pathfinder().findConfigAndMigrations(process.cwd()); this.config = findConfigFile(); } validateOptions(options) { if (!options.migrationName) { throw new Error("Please provide a migration name"); } } buildMigrationOptions(options) { return { migrationName: sanitizeMigrationName(options.migrationName), outputType: options.outputType || "ts", configPath: options.configPath, migrationsPath: options.migrationsPath }; } async processMigrationFile() { const migrationFile = await this.fileService.findMigrationFile(this.options.migrationName); if (!migrationFile) { console.log("No changes in database schema were found or migration file was not generated"); return; } await this.fileService.verifyMigrationContent(migrationFile); } async runMigration() { const dataSource = await AppDataSource(this.searchResult.migrationsPath, this.config); await new MigrationRunner().migrationRun(dataSource); } async execute() { try { await this.generator.generate(); await this.processMigrationFile(); await this.runMigration(); } catch (error) { this.handleError(error); } } handleError(error) { console.error("Error in migration process:", error.message); if (error.stdout) console.log("stdout:", error.stdout); if (error.stderr) console.error("stderr:", error.stderr); process.exit(1); } }; __name(_MigrationHandler, "MigrationHandler"); var MigrationHandler = _MigrationHandler; // src/cli.ts async function run() { try { const args = process.argv.slice(2); if (args.length === 0) { console.error("Usage: npx cca-migration-generator <migrationName>"); process.exit(1); } const migrationName = args[0]; const searchResult = new Pathfinder().findConfigAndMigrations(process.cwd()); const options = { migrationName, outputType: "ts", configPath: searchResult.configPath ?? "", migrationsPath: searchResult.migrationsPath ?? "" }; await new MigrationHandler(options).execute(); } catch (error) { console.error("Error:", error.message); process.exit(1); } } __name(run, "run"); run().catch(console.error); //# sourceMappingURL=cli.js.map