UNPKG

ts-migrate-mongoose

Version:

A migration framework for Mongoose, built with TypeScript.

172 lines (169 loc) 6.92 kB
import fs from 'node:fs'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; import { Command } from 'commander'; import { config } from 'dotenv'; import { c as chalk, M as Migrator, E as Env, d as defaults, l as loader } from './index-DI_Yu1c1.mjs'; import '@inquirer/prompts'; import 'mongoose'; const fileExists = async (filePath) => { return fs.promises.access(filePath).then(() => true).catch(() => false); }; const resolveConfigPath = async (configPath) => { const validExtensions = [".ts", ".js", ".json"]; const message = `Config file must have an extension of ${validExtensions.join(", ")}`; const extension = path.extname(configPath); if (extension) { if (!validExtensions.includes(extension)) { throw new Error(message); } return path.resolve(configPath); } for (const ext of validExtensions) { const configFilePath = path.resolve(configPath + ext); const exists = await fileExists(configFilePath); if (exists) { console.log(`Found config file: ${configFilePath}`); return configFilePath; } } throw new Error(message); }; const loadModule = async (configPath) => { const config2 = await resolveConfigPath(configPath); const fileUrl = pathToFileURL(config2).href; await loader(); const extension = path.extname(config2); if (extension === ".ts") { return await import(fileUrl); } return await import(fileUrl); }; const extractOptions = (module) => { if (module.default) { return "default" in module.default ? module.default.default : module.default; } return module; }; const logError = (error) => { if (error instanceof Error) { console.log(chalk.red(error.message)); } }; const getConfig = async (configPath) => { let configOptions = {}; if (configPath) { try { const configFilePath = path.resolve(configPath); const module = await loadModule(configFilePath); const fileOptions = extractOptions(module); if (fileOptions) { configOptions = fileOptions; } } catch (error) { logError(error); configOptions = {}; } } return configOptions; }; const toCamelCase = (str) => { return str.toLocaleLowerCase().replace(/_([a-z])/g, (g) => g[1] ? g[1].toUpperCase() : ""); }; const getEnv = (key) => { return process.env[key] ?? process.env[toCamelCase(key)]; }; const getEnvBoolean = (key) => { const value = getEnv(key); return value === "true" ? true : void 0; }; const getMigrator = async (options) => { config({ path: ".env" }); config({ path: ".env.local", override: true }); const mode = options.mode ?? getEnv(Env.MIGRATE_MODE); if (mode) { config({ path: `.env.${mode}`, override: true }); config({ path: `.env.${mode}.local`, override: true }); } const configPath = options.configPath ?? getEnv(Env.MIGRATE_CONFIG_PATH) ?? defaults.MIGRATE_CONFIG_PATH; const fileOptions = await getConfig(configPath); const uri = options.uri ?? getEnv(Env.MIGRATE_MONGO_URI) ?? fileOptions.uri; const connectOptions = fileOptions.connectOptions; const collection = options.collection ?? getEnv(Env.MIGRATE_MONGO_COLLECTION) ?? fileOptions.collection ?? defaults.MIGRATE_MONGO_COLLECTION; const migrationsPath = options.migrationsPath ?? getEnv(Env.MIGRATE_MIGRATIONS_PATH) ?? fileOptions.migrationsPath ?? defaults.MIGRATE_MIGRATIONS_PATH; const templatePath = options.templatePath ?? getEnv(Env.MIGRATE_TEMPLATE_PATH) ?? fileOptions.templatePath; const autosync = Boolean(options.autosync ?? getEnvBoolean(Env.MIGRATE_AUTOSYNC) ?? fileOptions.autosync ?? defaults.MIGRATE_AUTOSYNC); if (!uri) { const message = chalk.red("You need to provide the MongoDB Connection URI to persist migration status.\nUse option --uri / -d to provide the URI."); throw new Error(message); } const migratorOptions = { migrationsPath, uri, collection, autosync, cli: true }; if (templatePath) { migratorOptions.templatePath = templatePath; } if (connectOptions) { migratorOptions.connectOptions = connectOptions; } return Migrator.connect(migratorOptions); }; class Migrate { constructor() { this.program = new Command(); this.program.name("migrate").description(chalk.cyan("CLI migration tool for mongoose")).option("-f, --config-path <path>", "path to the config file").option("-d, --uri <string>", chalk.yellow("mongo connection string")).option("-c, --collection <string>", "collection name to use for the migrations").option("-a, --autosync <boolean>", "automatically sync new migrations without prompt").option("-m, --migrations-path <path>", "path to the migration files").option("-t, --template-path <path>", "template file to use when creating a migration").option("--mode <string>", "environment mode to use .env.[mode] file").hook("preAction", async () => { const options = this.program.opts(); this.migrator = await getMigrator(options); }); this.program.command("list").description("list all migrations").action(async () => { console.log(chalk.cyan("Listing migrations")); await this.migrator.list(); }); this.program.command("create <migration-name>").description("create a new migration file").action(async (migrationName) => { await this.migrator.create(migrationName); const migrateUp = chalk.cyan(`migrate up ${migrationName}`); console.log(`Migration created. Run ${migrateUp} to apply the migration`); }); this.program.command("up [migration-name]").description("run all migrations or a specific migration if name provided").option("-s, --single", "run single migration", false).action(async (migrationName, options) => { await this.migrator.run("up", migrationName, options?.single); }); this.program.command("down <migration-name>").description("roll back migrations down to given name").option("-s, --single", "run single migration", false).action(async (migrationName, options) => { await this.migrator.run("down", migrationName, options?.single); }); this.program.command("prune").description("delete extraneous migrations from migration folder or database").action(async () => { await this.migrator.prune(); }); } /** * Finish the CLI process */ async finish(exit, error) { if (this.migrator instanceof Migrator) { await this.migrator.close(); } if (error) { console.error(chalk.red(error.message)); if (exit) process.exit(1); throw error; } if (exit) process.exit(0); return this.program.opts(); } /** * Run the CLI command */ async run(exit = true) { try { await this.program.parseAsync(process.argv); return await this.finish(exit); } catch (error) { return await this.finish(exit, error instanceof Error ? error : new Error("An unknown error occurred")); } } } const migrate = new Migrate(); migrate.run();