ts-migrate-mongoose
Version:
A migration framework for Mongoose, built with TypeScript.
173 lines (170 loc) • 6.94 kB
JavaScript
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.js';
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();