cca-migration-generator
Version:
PostgreSQL TypeORM migration generator and schema builder with advanced TypeScript support
461 lines (444 loc) • 15.3 kB
JavaScript
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