UNPKG

@iarayan/ch-orm

Version:

A Developer-First ClickHouse ORM with Powerful CLI Tools

328 lines 14.6 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const chalk_1 = __importDefault(require("chalk")); const commander_1 = require("commander"); const dotenv = __importStar(require("dotenv")); const figlet_1 = __importDefault(require("figlet")); const fs = __importStar(require("fs")); const ora_1 = __importDefault(require("ora")); const path = __importStar(require("path")); const Connection_1 = require("../connection/Connection"); const MigrationRecord_1 = require("../schema/models/MigrationRecord"); const commands_1 = require("./commands"); /** * Load configuration from environment variables or .env file */ function loadConfig() { // Load environment variables from .env file if it exists const envPath = path.resolve(process.cwd(), ".env"); if (fs.existsSync(envPath)) { dotenv.config({ path: envPath }); } // Get configuration from environment variables const config = { host: process.env.CLICKHOUSE_HOST || "localhost", port: parseInt(process.env.CLICKHOUSE_PORT || "8123", 10), database: process.env.CLICKHOUSE_DATABASE || "default", username: process.env.CLICKHOUSE_USERNAME || "default", password: process.env.CLICKHOUSE_PASSWORD || "", protocol: process.env.CLICKHOUSE_PROTOCOL || "http", debug: process.env.CLICKHOUSE_DEBUG === "true", migrationsPath: process.env.CLICKHOUSE_MIGRATIONS_PATH || "./migrations", modelsPath: process.env.CLICKHOUSE_MODELS_PATH || "./models", seedersPath: process.env.CLICKHOUSE_SEEDERS_PATH || "./seeders", }; return config; } /** * Create a ClickHouse connection from configuration */ function createConnection(config) { return new Connection_1.Connection({ host: config.host, port: config.port, database: config.database, username: config.username, password: config.password, debug: config.debug, }); } /** * Main CLI program */ async function main() { console.log(chalk_1.default.cyan(figlet_1.default.textSync("CH-ORM", { horizontalLayout: "full" }))); console.log(chalk_1.default.yellow("A Developer-First ClickHouse ORM\n")); const program = new commander_1.Command(); const config = loadConfig(); program.version("1.0.0").description("CH-ORM CLI for database management"); // Migrations const migrationsCommand = program .command("migrations") .description("Manage database migrations"); migrationsCommand .command("create <name>") .description("Create a new migration file") .option("-p, --path <path>", "Path to migrations directory", config.migrationsPath) .action(async (name, options) => { const spinner = (0, ora_1.default)("Creating migration...").start(); try { const makeMigration = new commands_1.MakeMigrationCommand(options.path); const filePath = makeMigration.execute(name); spinner.succeed(`Migration created: ${chalk_1.default.green(filePath)}`); } catch (error) { spinner.fail(`Failed to create migration: ${chalk_1.default.red(error.message)}`); process.exit(1); } }); migrationsCommand .command("run") .description("Run all pending migrations") .option("-p, --path <path>", "Path to migrations directory", config.migrationsPath) .action(async (options) => { const spinner = (0, ora_1.default)("Running migrations...").start(); try { const connection = createConnection(config); const runner = new commands_1.MigrationRunnerCommand(connection, options.path); const status = await runner.getMigrationStatus(); const pending = status.filter((s) => s.status === "Pending"); if (pending.length === 0) { spinner.succeed("No pending migrations"); return; } spinner.info(`Found ${pending.length} pending migrations`); await runner.run(); spinner.succeed(`Successfully ran ${pending.length} migrations`); } catch (error) { spinner.fail(`Migration failed: ${chalk_1.default.red(error.message)}`); process.exit(1); } }); migrationsCommand .command("rollback") .description("Rollback the last batch of migrations") .option("-p, --path <path>", "Path to migrations directory", config.migrationsPath) .action(async (options) => { const spinner = (0, ora_1.default)("Rolling back migrations...").start(); try { const connection = createConnection(config); const runner = new commands_1.MigrationRunnerCommand(connection, options.path); // Get the last batch number const lastBatchResult = await MigrationRecord_1.MigrationRecord.query() .select("batch") .orderBy("batch", "DESC") .first(); if (!lastBatchResult) { spinner.succeed("No migrations to rollback"); return; } const lastBatch = lastBatchResult.batch; // Get all migrations from the last batch const migrationsToRollback = await MigrationRecord_1.MigrationRecord.query() .where("batch", lastBatch) .orderBy("created_at", "DESC") .get(); if (migrationsToRollback.length === 0) { spinner.succeed("No migrations to rollback"); return; } spinner.info(`Rolling back batch #${lastBatch} with ${migrationsToRollback.length} migrations:`); migrationsToRollback.forEach((record) => { console.log(` - ${chalk_1.default.yellow(record.name)}`); }); spinner.start("Executing rollback..."); await runner.rollback(); // Verify batch was removed from migrations table const verifyBatchRemoved = await MigrationRecord_1.MigrationRecord.query() .where("batch", lastBatch) .count(); if (verifyBatchRemoved > 0) { spinner.warn(`Warning: ${verifyBatchRemoved} migration records still exist in the migrations table`); } spinner.succeed(`Successfully rolled back ${migrationsToRollback.length} migrations from batch #${lastBatch}`); } catch (error) { spinner.fail(`Rollback failed: ${chalk_1.default.red(error.message)}`); process.exit(1); } }); migrationsCommand .command("status") .description("Show migration status") .option("-p, --path <path>", "Path to migrations directory", config.migrationsPath) .action(async (options) => { const spinner = (0, ora_1.default)("Checking migration status...").start(); try { const connection = createConnection(config); const runner = new commands_1.MigrationRunnerCommand(connection, options.path); const status = await runner.getMigrationStatus(); spinner.stop(); if (status.length === 0) { console.log(chalk_1.default.yellow("No migrations found")); return; } console.log(chalk_1.default.yellow("\nMigration Status:")); console.log(chalk_1.default.cyan("Migration".padEnd(40)) + chalk_1.default.cyan("Status")); console.log("-".repeat(50)); status.forEach(({ migration, status }) => { console.log(migration.padEnd(40) + (status === "Completed" ? chalk_1.default.green(status) : chalk_1.default.yellow(status))); }); } catch (error) { spinner.fail(`Failed to check status: ${chalk_1.default.red(error.message)}`); process.exit(1); } }); migrationsCommand .command("reset") .description("Rollback all migrations") .option("-p, --path <path>", "Path to migrations directory", config.migrationsPath) .action(async (options) => { const spinner = (0, ora_1.default)("Rolling back all migrations...").start(); try { const connection = createConnection(config); const runner = new commands_1.MigrationRunnerCommand(connection, options.path); const completedMigrations = await runner.getCompletedMigrations(); if (completedMigrations.length === 0) { spinner.succeed("No migrations to reset"); return; } spinner.info(`Found ${completedMigrations.length} migrations to reset:`); completedMigrations.forEach((filename) => { console.log(` - ${chalk_1.default.yellow(filename)}`); }); spinner.start("Rolling back migrations..."); await runner.reset(); spinner.succeed(`Successfully reset ${completedMigrations.length} migrations`); } catch (error) { spinner.fail(`Reset failed: ${chalk_1.default.red(error.message)}`); process.exit(1); } }); migrationsCommand .command("fresh") .description("Drop all tables and re-run all migrations from scratch") .option("-p, --path <path>", "Path to migrations directory", config.migrationsPath) .action(async (options) => { const spinner = (0, ora_1.default)("Starting fresh installation...").start(); try { const connection = createConnection(config); const runner = new commands_1.MigrationRunnerCommand(connection, options.path); const status = await runner.getMigrationStatus(); // Show information about what we're about to drop spinner.info(`Found ${status.length} migrations in total`); spinner.info("Starting fresh installation - this will drop all database objects"); // This will drop all database objects and run migrations from scratch await runner.fresh(); spinner.succeed(`Successfully completed fresh installation with ${status.length} migrations`); } catch (error) { spinner.fail(`Fresh installation failed: ${chalk_1.default.red(error.message)}`); process.exit(1); } }); // Models const modelsCommand = program.command("models").description("Manage models"); modelsCommand .command("create <name>") .description("Create a new model file") .option("-p, --path <path>", "Path to models directory", config.modelsPath) .action(async (name, options) => { const spinner = (0, ora_1.default)("Creating model...").start(); try { const modelCommand = new commands_1.ModelCommand(options.path); const filePath = modelCommand.execute(name); spinner.succeed(`Model created: ${chalk_1.default.green(filePath)}`); } catch (error) { spinner.fail(`Failed to create model: ${chalk_1.default.red(error.message)}`); process.exit(1); } }); // Seeders const seedersCommand = program .command("seeders") .description("Manage database seeders"); seedersCommand .command("create <name>") .description("Create a new seeder file") .option("-p, --path <path>", "Path to seeders directory", config.seedersPath) .action(async (name, options) => { const spinner = (0, ora_1.default)("Creating seeder...").start(); try { const seederCommand = new commands_1.SeederCommand(createConnection(config), options.path); const filePath = seederCommand.createSeeder(name); spinner.succeed(`Seeder created: ${chalk_1.default.green(filePath)}`); } catch (error) { spinner.fail(`Failed to create seeder: ${chalk_1.default.red(error.message)}`); process.exit(1); } }); seedersCommand .command("run") .description("Run database seeders") .option("-p, --path <path>", "Path to seeders directory", config.seedersPath) .action(async (options) => { const spinner = (0, ora_1.default)("Running seeders...").start(); try { const seederCommand = new commands_1.SeederCommand(createConnection(config), options.path); await seederCommand.run(); spinner.succeed("Database seeded successfully"); } catch (error) { spinner.fail(`Seeding failed: ${chalk_1.default.red(error.message)}`); process.exit(1); } }); await program.parseAsync(process.argv); } // Run the CLI main().catch((error) => { console.error(chalk_1.default.red("Fatal error:"), error); process.exit(1); }); //# sourceMappingURL=index.js.map