UNPKG

kysely-ctl

Version:
1,281 lines (1,220 loc) 36.6 kB
#!/usr/bin/env node import { FileSeedProvider, Seeder, TSFileMigrationProvider, __dirname, __filename, assertDefined, getCWD, getJiti, getMillisPrefix } from "./chunk-HQU22GLO.js"; // src/bin.mts import { process as process3 } from "std-env"; // src/cli.mts import { createMain } from "citty"; // src/commands/root.mts import { showUsage as showUsage3 } from "citty"; import { consola as consola20, LogLevels } from "consola"; // src/arguments/cwd.mts var CWDArg = { cwd: { description: "The current working directory to use for relative paths.", type: "string" } }; // src/arguments/debug.mts var DebugArg = { debug: { default: false, description: "Show debug information.", type: "boolean" } }; // src/arguments/environment.mts var EnvironmentArg = { environment: { alias: "e", description: "Apply environment-specific overrides to the configuration. See https://github.com/unjs/c12#environment-specific-configuration for more information.", type: "string", valueHint: "production | development | test | ..." } }; // src/arguments/jiti.mts var ExperimentalResolveTSConfigPathsArg = { "experimental-resolve-tsconfig-paths": { default: false, description: "Attempts to resolve path aliases using the tsconfig.json file.", type: "boolean" } }; var NoFilesystemCachingArg = { "no-filesystem-caching": { description: "Will not write cache files to disk. See https://github.com/unjs/jiti#fscache for more information.", type: "boolean" } }; var JitiArgs = { ...ExperimentalResolveTSConfigPathsArg, ...NoFilesystemCachingArg }; // src/arguments/no-outdated-check.mts var NoOutdatedCheckArg = { "no-outdated-check": { description: "Will not check for latest kysely/kysely-ctl versions and notice newer versions exist.", type: "boolean" } }; // src/arguments/common.mts var CommonArgs = { ...CWDArg, ...DebugArg, ...EnvironmentArg, ...JitiArgs, ...NoOutdatedCheckArg }; // src/utils/is-in-subcommand.mts function isInSubcommand(context) { const { cmd: { subCommands }, rawArgs } = context; if (!subCommands) { return false; } const potentialSubCommand = rawArgs.find((arg) => !arg.startsWith("-")); return potentialSubCommand !== void 0 && potentialSubCommand in subCommands; } // src/utils/version.mts import { consola } from "consola"; import { ofetch } from "ofetch"; import { isCI } from "std-env"; // src/utils/package-manager.mts import { detectPackageManager } from "nypm"; import { isBun, isDeno } from "std-env"; async function getPackageManager(args13) { const packageManager = await detectPackageManager(getCWD(args13), { ignoreArgv: true, includeParentDirs: true }); if (packageManager) { return { ...packageManager, inProject: true }; } const name = isDeno ? "deno" : isBun ? "bun" : "npm"; return { name, command: name, inProject: false }; } // src/utils/pkg-json.mts import { readPackageJSON } from "pkg-types"; var PACKAGE_JSONS = {}; async function getConsumerPackageJSON(args13) { return await getPackageJSON({ startingFrom: getCWD(args13) }); } async function getCTLPackageJSON() { return await getPackageJSON({ id: "kysely-ctl" }); } async function getPackageJSON(options) { const { id, startingFrom = __dirname } = options; return PACKAGE_JSONS[`${String(id)}_${startingFrom}`] ||= await readPackageJSON(id, { from: startingFrom }); } // src/utils/version.mts async function getKyselyInstalledVersion(args13) { try { const pkgJSON = await getConsumerPackageJSON(args13); return getVersionFromPackageJSON("kysely", pkgJSON); } catch { return null; } } async function getCTLInstalledVersion() { try { const pkgJSON = await getCTLPackageJSON(); return getVersionFromPackageJSON("kysely-ctl", pkgJSON); } catch { return null; } } function getVersionFromPackageJSON(name, pkgJSON) { if (pkgJSON.name === name) { return pkgJSON.version || null; } const rawVersion = pkgJSON.dependencies?.[name] || pkgJSON.devDependencies?.[name]; return rawVersion?.replace(/^[\^~]?(.+)$/, "$1") || null; } async function printInstalledVersions(args13) { const [cliVersion, kyselyVersion] = await Promise.all([ getCTLInstalledVersion(), getKyselyInstalledVersion(args13) ]); consola.log( `kysely ${kyselyVersion ? `v${kyselyVersion}` : "[not installed]"}` ); consola.log(`kysely-ctl v${cliVersion}`); } async function getKyselyLatestVersion() { return await getPackageLatestVersion("kysely"); } async function getCTLLatestVersion() { return await getPackageLatestVersion("kysely-ctl"); } async function getPackageLatestVersion(packageName) { const response = await ofetch( `https://registry.npmjs.org/${packageName}` ); return response["dist-tags"].latest; } async function printUpgradeNotice(args13) { if (args13["outdated-check"] === false || isCI) { return; } const [ kyselyInstalledVersion, kyselyLatestVersion, ctlInstalledVersion, ctlLatestVersion ] = await Promise.all([ getKyselyInstalledVersion(args13), getKyselyLatestVersion(), getCTLInstalledVersion(), getCTLLatestVersion() ]); const notices = []; if (kyselyInstalledVersion && kyselyInstalledVersion !== kyselyLatestVersion) { notices.push(["Kysely", "kysely", kyselyLatestVersion]); } if (ctlInstalledVersion !== ctlLatestVersion) { notices.push(["KyselyCTL", "kysely-ctl", ctlLatestVersion]); } if (!notices.length) { return; } const packageManager = await getPackageManager(args13); const installCommand = { [packageManager.name]: "install", bun: "add", pnpm: "add", yarn: "add" }[packageManager.name]; consola.box( notices.map( ([prettyName, name, latestVersion]) => `A new version of ${prettyName} is available: v${latestVersion} Run \`${packageManager.command} ${installCommand} ${packageManager.name === "deno" ? "npm:" : ""}${name}@latest\` to upgrade.` ).join("\n\n") ); } // src/commands/init.mts import { copyFile, mkdir } from "node:fs/promises"; import { consola as consola3 } from "consola"; import { join } from "pathe"; // src/arguments/extension.mts var TS_EXTENSIONS = ["ts", "mts", "cts"]; var JS_EXTENSIONS = ["js", "mjs", "cjs"]; var ALL_EXTENSIONS = [...TS_EXTENSIONS, ...JS_EXTENSIONS]; var ExtensionArg = { extension: { alias: "x", default: "ts", description: "The file extension to use.", type: "string", valueHint: ALL_EXTENSIONS.map((extension) => `"${extension}"`).join(" | ") } }; function assertExtension(thing, config, context) { const allowJS = config?.[context || "migrations"].allowJS ?? true; if (!allowJS && JS_EXTENSIONS.includes(thing)) { throw new Error( `Invalid file extension "${thing}"! Expected ${TS_EXTENSIONS.map( (extension) => `"${extension}"` ).join(" | ")}. To use JS extensions, set "${context}.allowJS" to true.` ); } const extensions = allowJS ? ALL_EXTENSIONS : TS_EXTENSIONS; if (!extensions.includes(thing)) { throw new Error( `Invalid file extension "${thing}"! Expected ${ExtensionArg.extension.valueHint}` ); } } // src/config/get-config.mts import { loadConfig } from "c12"; import { consola as consola2 } from "consola"; async function getConfig(args13) { const cwd = getCWD(args13); const jiti = await getJiti({ debug: args13.debug, filesystemCaching: args13["filesystem-caching"], experimentalResolveTSConfigPaths: args13["experimental-resolve-tsconfig-paths"] }); const loadedConfig = await loadConfig({ cwd, dotenv: true, envName: args13.environment, jiti, globalRc: false, name: "kysely", packageJson: false, rcFile: false }); consola2.debug(loadedConfig); const { config, ...configMetadata } = loadedConfig; return { ...config, args: args13, configMetadata, cwd, migrations: { allowJS: false, getMigrationPrefix: getMillisPrefix, migrationFolder: "migrations", ...config.migrations, disableTransactions: args13.transaction === false || config.migrations?.disableTransactions }, seeds: { allowJS: false, getSeedPrefix: getMillisPrefix, seedFolder: "seeds", ...config.seeds } }; } function configFileExists(config) { const { configFile } = config.configMetadata; return configFile !== void 0 && configFile !== "kysely.config"; } async function getConfigOrFail(args13) { const config = await getConfig(args13); if (!configFileExists(config)) { throw new Error("No config file found"); } return config; } // src/utils/get-template-extension.mts async function getTemplateExtension(extension) { if (extension === "js") { const pkgJSON = await getConsumerPackageJSON(); return pkgJSON.type === "module" ? "mjs" : "cjs"; } return extension === "cjs" || extension === "mjs" ? extension : "ts"; } // src/commands/init.mts var args = { ...CWDArg, ...DebugArg, ...ExtensionArg, ...NoOutdatedCheckArg }; var InitCommand = { init: { meta: { name: "init", description: "Create a sample `kysely.config` file" }, args, async run(context) { const { args: args13 } = context; const { extension } = args13; consola3.debug(context, []); const config = await getConfig(args13); if (configFileExists(config)) { return consola3.warn( `Init skipped: config file already exists at ${config.configMetadata.configFile}` ); } assertExtension(extension); const configFolderPath = join(config.cwd, ".config"); consola3.debug("Config folder path:", configFolderPath); const wasConfigFolderCreated = Boolean( await mkdir(configFolderPath, { recursive: true }) ); if (wasConfigFolderCreated) { consola3.debug("Config folder created"); } const filePath = join(configFolderPath, `kysely.config.${extension}`); consola3.debug("File path:", filePath); const templateExtension = await getTemplateExtension(extension); const templatePath = join( __dirname, `templates/config-template.${templateExtension}` ); consola3.debug("Template path:", templatePath); await copyFile(templatePath, filePath); consola3.success(`Config file created at ${filePath}`); } } }; // src/commands/migrate/down.mts import { consola as consola7 } from "consola"; // src/arguments/migrate.mts var NoTransactionArg = { "no-transaction": { description: "Don't use a transaction when running the migrations. This will not work for now if you've provided your own Migrator factory.", type: "boolean" } }; var MigrateArgs = { ...NoTransactionArg }; // src/arguments/migration-name.mts var createMigrationNameArg = (required = false) => ({ migration_name: { description: "Migration name to run/undo.", required, type: "positional" } }); // src/kysely/is-wrong-direction.mts async function isWrongDirection(migrationName, expectedDirection, migrator) { if (!migrationName) { return false; } const migrations2 = await migrator.getMigrations(); return migrations2.find( (migration) => migration.name === migrationName && (expectedDirection === "up" && migration.executedAt !== void 0 || expectedDirection === "down" && !migration.executedAt) ) !== void 0; } // src/kysely/process-migration-result-set.mts import { consola as consola5 } from "consola"; import { colorize } from "consola/utils"; // src/utils/error.mts import { consola as consola4 } from "consola"; import { process } from "std-env"; function exitWithError(error) { if (error instanceof AggregateError) { for (const subError of error.errors) { consola4.error(subError); } } else { consola4.error(error); } process.exit?.(1); throw error; } // src/kysely/get-migrations.mts var migrations; async function getMigrations(migrator) { return migrations ||= await migrator.getMigrations(); } // src/kysely/process-migration-result-set.mts async function processMigrationResultSet(resultSet, direction, migrator) { consola5.debug(resultSet); let { error, results } = resultSet; if (error) { const failedMigration = results?.find((result) => result.status === "Error"); consola5.fail( `Migration failed with \`${error}\`${failedMigration ? ` @ "${failedMigration.migrationName}"` : ""}` ); exitWithError(error); } if (!results?.length) { return consola5.info( `Migration skipped: no ${direction === "up" ? "new" : "completed"} migrations found` ); } consola5.success("Migration complete"); consola5.info( `${direction === "up" ? "Ran" : "Undone"} ${results.length} migration${results.length > 1 ? "s" : ""}:` ); if (direction === "down") { results = [...results].reverse(); } const migrations2 = await getMigrations(migrator); const firstResult = results[0]; assertDefined(firstResult); const { migrationName: firstResultMigrationName } = firstResult; const untouchedMigrationsBefore = migrations2.slice( 0, migrations2.findIndex( (migration) => migration.name === firstResultMigrationName ) ); const lastResult = results.at(-1); assertDefined(lastResult); const { migrationName: lastResultMigrationName } = lastResult; const untouchedMigrationsAfter = migrations2.slice( migrations2.findIndex( (migration) => migration.name === lastResultMigrationName ) + 1 ); for (const migration of untouchedMigrationsBefore) { consola5.log(`[\u2713] ${migration.name}`); } for (const result of results) { consola5.log( `[${colorize("green", result.direction === "Up" ? "\u2713" : "\u237B")}] ${result.migrationName}` ); } for (const migration of untouchedMigrationsAfter) { consola5.log(`[ ] ${migration.name}`); } } // src/kysely/get-migrator.mts import { Migrator } from "kysely"; import { join as join2 } from "pathe"; // src/utils/hydrate.mts async function hydrate(factory, parameters, defaultValue) { if (factory == null) { return defaultValue?.() ?? factory; } if (typeof factory !== "function") { return factory; } return await factory(...parameters); } // src/kysely/get-migrator.mts async function getMigrator(config) { const { args: args13, kysely, migrations: migrations2 } = config; const { allowJS, migrationFolder, migrator, ...migratorOptions } = migrations2; if (migrator) { return await hydrate(migrator, [kysely]); } const provider = await hydrate( migrations2.provider, [], () => new TSFileMigrationProvider({ allowJS, debug: args13.debug, experimentalResolveTSConfigPaths: args13["experimental-resolve-tsconfig-paths"], filesystemCaching: args13["filesystem-caching"], migrationFolder: join2(config.cwd, migrationFolder) }) ); return new Migrator({ ...migratorOptions, db: kysely, provider }); } // src/kysely/get-kysely.mts import { consola as consola6 } from "consola"; import { Kysely } from "kysely"; // src/kysely/get-dialect.mts import { MysqlDialect, PostgresDialect, SqliteDialect } from "kysely"; async function getDialect(config) { const { dialect } = config; if (!dialect) { throw new Error("No dialect provided"); } if (typeof dialect !== "string") { return await hydrate(dialect, []); } const dialectConfig = await hydrate(config.dialectConfig, []); if (dialect === "pg") { return new PostgresDialect(dialectConfig); } if (dialect === "mysql2") { return new MysqlDialect(dialectConfig); } if (dialect === "tedious") { return new (await import("kysely")).MssqlDialect(dialectConfig); } if (dialect === "better-sqlite3") { return new SqliteDialect(dialectConfig); } if (dialect === "postgres") { return new (await import("kysely-postgres-js")).PostgresJSDialect( dialectConfig ); } throw new Error(`Unknown dialect: ${dialect}`); } // src/kysely/get-kysely.mts async function getKysely(config, debug = false) { const { kysely } = config; if (kysely) { return await hydrate(kysely, []); } const [dialect, plugins] = await Promise.all([ getDialect(config), hydrate(config.plugins, []) ]); return new Kysely({ dialect, log: debug ? (event) => { if (event.level === "error") { return consola6.error(event.error); } return consola6.log( `executed \`${event.query.sql}\` in ${event.queryDurationMillis}ms` ); } : [], plugins }); } // src/kysely/using-kysely.mts async function usingKysely(config, callback) { const kysely = await getKysely(config); try { return await callback(kysely); } finally { if (config.destroyOnExit !== false) { await kysely.destroy(); } } } // src/kysely/using-migrator.mts async function usingMigrator(args13, callback) { const config = await getConfigOrFail(args13); return await usingKysely(config, async (kysely) => { const migrator = await getMigrator({ ...config, kysely }); return await callback(migrator); }); } // src/utils/create-subcommand.mts function createSubcommand(name, def) { return { [name]: { ...def, meta: { ...def.meta, name } } // biome-ignore lint/suspicious/noExplicitAny: this is perfectly fine }; } // src/commands/migrate/down.mts var args2 = { ...CommonArgs, ...MigrateArgs, ...createMigrationNameArg() }; var BaseDownCommand = { meta: { name: "down", description: "Undo the last/specified migration that was run" }, args: args2, async run(context) { const { args: args13 } = context; const { migration_name } = context.args; consola7.debug(context, []); await usingMigrator(args13, async (migrator) => { if (await isWrongDirection(migration_name, "down", migrator)) { return consola7.info( `Migration skipped: "${migration_name}" has not been run yet` ); } consola7.start("Starting migration down"); const resultSet = migration_name ? await migrator.migrateTo(migration_name) : await migrator.migrateDown(); await processMigrationResultSet(resultSet, "down", migrator); }); } }; var DownCommand = createSubcommand("down", BaseDownCommand); var LegacyDownCommand = createSubcommand( "migrate:down", BaseDownCommand ); // src/commands/migrate/latest.mts import { consola as consola8 } from "consola"; var args3 = { ...CommonArgs, ...MigrateArgs }; var BaseLatestCommand = { meta: { name: "latest", description: "Update the database schema to the latest version" }, args: args3, async run(context) { consola8.debug(context, []); await usingMigrator(context.args, async (migrator) => { consola8.start("Starting migration to latest"); const resultSet = await migrator.migrateToLatest(); await processMigrationResultSet(resultSet, "up", migrator); }); } }; var LatestCommand = createSubcommand("latest", BaseLatestCommand); var LegacyLatestCommand = createSubcommand( "migrate:latest", BaseLatestCommand ); // src/commands/migrate/list.mts import { consola as consola9 } from "consola"; var args4 = { ...CommonArgs, "fail-on-pending": { type: "boolean", default: false, required: false } }; var BaseListCommand = { meta: { name: "list", description: "List both completed and pending migrations" }, args: args4, async run(context) { consola9.debug(context, []); const migrations2 = await usingMigrator(context.args, getMigrations); consola9.debug(migrations2); if (!migrations2.length) { return consola9.info("No migrations found."); } consola9.info( `Found ${migrations2.length} migration${migrations2.length > 1 ? "s" : ""}:` ); for (const migration of migrations2) { consola9.log(`[${migration.executedAt ? "`\u2713`" : " "}] ${migration.name}`); } if (!context.args["fail-on-pending"]) { return; } const hasPending = migrations2.some( (migration) => migration.executedAt == null ); if (hasPending) { exitWithError("Failed due to pending migrations."); } } }; var ListCommand = createSubcommand("list", BaseListCommand); var LegacyListCommand = createSubcommand( "migrate:list", BaseListCommand ); // src/commands/migrate/make.mts import { copyFile as copyFile2, mkdir as mkdir2 } from "node:fs/promises"; import { consola as consola10 } from "consola"; import { join as join3 } from "pathe"; var args5 = { ...CommonArgs, ...createMigrationNameArg(true), ...ExtensionArg }; var BaseMakeCommand = { meta: { description: "Create a new migration file" }, args: args5, async run(context) { const { args: args13 } = context; const { extension } = args13; consola10.debug(context, []); const config = await getConfigOrFail(args13); assertExtension(extension, config, "migrations"); const migrationsFolderPath = join3( config.cwd, config.migrations.migrationFolder ); consola10.debug("Migrations folder path:", migrationsFolderPath); const wasMigrationsFolderCreated = Boolean( await mkdir2(migrationsFolderPath, { recursive: true }) ); if (wasMigrationsFolderCreated) { consola10.debug("Migrations folder created"); } const filename = `${await config.migrations.getMigrationPrefix()}${args13.migration_name}.${extension}`; consola10.debug("Filename:", filename); const filePath = join3(migrationsFolderPath, filename); consola10.debug("File path:", filePath); const templateExtension = await getTemplateExtension(extension); const templatePath = join3( __dirname, `templates/migration-template.${templateExtension}` ); consola10.debug("Template path:", templatePath); await copyFile2(templatePath, filePath); consola10.success(`Created migration file at ${filePath}`); } }; var MakeCommand = createSubcommand("make", BaseMakeCommand); var LegacyMakeCommand = createSubcommand( "migrate:make", BaseMakeCommand ); // src/commands/migrate/rollback.mts import { consola as consola11 } from "consola"; import { NO_MIGRATIONS } from "kysely"; var args6 = { all: { description: "Rollback all completed migrations", required: true, // remove this if and when Migrator supports migration batches. type: "boolean" }, ...CommonArgs, ...MigrateArgs }; var BaseRollbackCommand = { meta: { name: "rollback", description: "Rollback all the completed migrations" }, args: args6, async run(context) { consola11.debug(context, []); await usingMigrator(context.args, async (migrator) => { consola11.start("Starting migration rollback"); const resultSet = await migrator.migrateTo(NO_MIGRATIONS); await processMigrationResultSet(resultSet, "down", migrator); }); } }; var RollbackCommand = createSubcommand("rollback", BaseRollbackCommand); var LegacyRollbackCommand = createSubcommand( "migrate:rollback", BaseRollbackCommand ); // src/commands/migrate/root.mts import { showUsage } from "citty"; import { consola as consola13 } from "consola"; // src/commands/migrate/up.mts import { consola as consola12 } from "consola"; var args7 = { ...CommonArgs, ...MigrateArgs, ...createMigrationNameArg() }; var BaseUpCommand = { meta: { name: "up", description: "Run the next migration that has not yet been run" }, args: args7, async run(context) { const { args: args13 } = context; const { migration_name } = args13; consola12.debug(context, []); await usingMigrator(args13, async (migrator) => { if (await isWrongDirection(migration_name, "up", migrator)) { return consola12.info( `Migration skipped: migration "${migration_name}" has already been run` ); } consola12.start("Starting migration up"); const resultSet = migration_name ? await migrator.migrateTo(migration_name) : await migrator.migrateUp(); await processMigrationResultSet(resultSet, "up", migrator); }); } }; var UpCommand = createSubcommand("up", BaseUpCommand); var LegacyUpCommand = createSubcommand("migrate:up", BaseUpCommand); // src/commands/migrate/root.mts var MigrateCommand = { migrate: { meta: { name: "migrate", description: "Migrate the database schema" }, args: CommonArgs, subCommands: { ...DownCommand, ...LatestCommand, ...ListCommand, ...MakeCommand, ...RollbackCommand, ...UpCommand }, async run(context) { if (!isInSubcommand(context)) { consola13.debug(context, []); await showUsage(context.cmd, RootCommand); } } } }; // src/commands/seed/make.mts import { copyFile as copyFile3, mkdir as mkdir3 } from "node:fs/promises"; import { consola as consola14 } from "consola"; import { join as join4 } from "pathe"; var args8 = { ...CommonArgs, ...ExtensionArg, seed_name: { description: "Seed file name to create", required: true, type: "positional" } }; var BaseMakeCommand2 = { meta: { description: "Create a new seed file" }, args: args8, async run(context) { const { args: args13 } = context; const { extension } = args13; consola14.debug(context, []); const config = await getConfigOrFail(args13); assertExtension(extension, config, "seeds"); const seedsFolderPath = join4(config.cwd, config.seeds.seedFolder); consola14.debug("Seeds folder path:", seedsFolderPath); const wasSeedsFolderCreated = Boolean( await mkdir3(seedsFolderPath, { recursive: true }) ); if (wasSeedsFolderCreated) { consola14.debug("Seeds folder created"); } const filename = `${await config.seeds.getSeedPrefix()}${args13.seed_name}.${extension}`; consola14.debug("Filename:", filename); const filePath = join4(seedsFolderPath, filename); consola14.debug("File path:", filePath); const templateExtension = await getTemplateExtension(extension); const templatePath = join4( __dirname, `templates/seed-template.${templateExtension}` ); consola14.debug("Template path:", templatePath); await copyFile3(templatePath, filePath); consola14.success(`Created seed file at ${filePath}`); } }; var MakeCommand2 = createSubcommand("make", BaseMakeCommand2); var LegacyMakeCommand2 = createSubcommand("seed:make", BaseMakeCommand2); // src/commands/seed/root.mts import { showUsage as showUsage2 } from "citty"; import { consola as consola17 } from "consola"; // src/commands/seed/list.mts import { consola as consola15 } from "consola"; // src/seeds/get-seeder.mts import { join as join5 } from "pathe"; async function getSeeder(config) { const { args: args13, kysely, seeds } = config; const { allowJS, seedFolder, seeder, ...seederOptions } = seeds; if (seeder) { return await hydrate(seeder, [kysely]); } const provider = await hydrate( seeds.provider, [], () => new FileSeedProvider({ allowJS, debug: args13.debug, filesystemCaching: args13["filesystem-caching"], experimentalResolveTSConfigPaths: args13["experimental-resolve-tsconfig-paths"], seedFolder: join5(config.cwd, seedFolder) }) ); return new Seeder({ ...seederOptions, db: kysely, provider }); } // src/seeds/using-seeder.mts async function usingSeeder(args13, callback) { const config = await getConfigOrFail(args13); return await usingKysely(config, async (kysely) => { const seeder = await getSeeder({ ...config, kysely }); return await callback(seeder); }); } // src/commands/seed/list.mts var args9 = { ...CommonArgs }; var ListCommand2 = { list: { meta: { name: "list", description: "List seeds" }, args: args9, async run(context) { consola15.debug(context, []); const seeds = await usingSeeder( context.args, (seeder) => seeder.getSeeds() ); consola15.debug(seeds); if (!seeds.length) { return consola15.info("No seeds found."); } consola15.info(`Found ${seeds.length} seed${seeds.length > 1 ? "s" : ""}:`); for (const seed of seeds) { consola15.log(seed.name); } } } }; // src/commands/seed/run.mts import { consola as consola16 } from "consola"; import { colorize as colorize2 } from "consola/utils"; var args10 = { ...CommonArgs, specific: { description: "Run seed file/s with given name/s", type: "string" } }; var BaseRunCommand = { meta: { description: "Run seed files", name: "run" }, args: args10, async run(context) { const { args: args13 } = context; const { specific } = args13; consola16.debug(context, []); consola16.start("Starting seed run"); const resultSet = await usingSeeder(args13, (seeder) => seeder.run(specific)); consola16.debug(resultSet); const { error, results } = resultSet; if (!results.length) { return consola16.info("No seeds found."); } if (!error) { consola16.success("Seed successful"); } const actuallyRan = error ? results.filter((result) => result.status !== "NotExecuted") : results; consola16.info( `Ran ${actuallyRan.length} seed${actuallyRan.length > 1 ? "s" : ""}:` ); for (const result of results) { consola16.log( `[${{ Error: colorize2("red", "\u2717"), NotExecuted: " ", Success: colorize2("green", "\u2713") }[result.status]}] ${result.seedName}${error && result.status === "Error" ? ` - ${error}` : ""}` ); } if (error) { exitWithError(error); } } }; var RunCommand = createSubcommand("run", BaseRunCommand); var LegacyRunCommand = createSubcommand("seed:run", BaseRunCommand); // src/commands/seed/root.mts var SeedCommand = { seed: { meta: { name: "seed", description: "Populate your database with test or seed data independent of your migration files" }, args: CommonArgs, subCommands: { ...ListCommand2, ...MakeCommand2, ...RunCommand }, async run(context) { if (!isInSubcommand(context)) { consola17.debug(context, []); await showUsage2(context.cmd, RootCommand); } } } }; // src/commands/sql.mts import { createInterface } from "node:readline/promises"; import { consola as consola19 } from "consola"; import { colorize as colorize3 } from "consola/utils"; import { isCI as isCI2, process as process2 } from "std-env"; // src/kysely/execute-query.mts import { CompiledQuery } from "kysely"; async function executeQuery(query, config) { return await config.kysely.executeQuery( CompiledQuery.raw(query.sql, query.parameters) ); } // src/kysely/infer-dialect-name.mts var CAPTURE_COMMON_CLASS_SUFFIXES = /adapter|dialect/i; function inferDialectName(kysely) { return kysely.getExecutor().adapter.constructor.name.replaceAll(CAPTURE_COMMON_CLASS_SUFFIXES, "").toLowerCase(); } // src/utils/print-csv.mts import { consola as consola18 } from "consola"; function printCSV(rows) { const [row0] = rows; if (!row0) { return; } consola18.log(`"${Object.keys(row0).join('","')}"`); for (const row of rows) { const transformedValues = []; for (const value of Object.values(row)) { transformedValues.push(typeof value === "string" ? `"${value}"` : value); } consola18.log(transformedValues.join(",")); } } // src/commands/sql.mts var args11 = { ...CommonArgs, format: { alias: "f", default: "csv", description: "The format to output the result in.", required: false, type: "string", valueHint: "csv | json" }, query: { description: "The SQL query to execute. When not provided, and not in CI, will open an interactive SQL shell.", required: isCI2, type: "positional" } }; var SqlCommand = { sql: { meta: { name: "sql", description: "Execute SQL queries" }, args: args11, subCommands: {}, async run(context) { const { args: args13 } = context; const { format, query } = args13; consola19.debug(context, []); assertQuery(query); assertFormat(format); const config = await getConfigOrFail(args13); await usingKysely(config, async (kysely) => { const hydratedConfig = { ...config, kysely }; if (query) { return await executeQueryAndPrint(args13, hydratedConfig); } await startInteractiveExecution(args13, hydratedConfig); }); } } }; function assertQuery(thing) { if (!isCI2 && typeof thing !== "string" || typeof thing === "string" && thing.length > 0) { return; } throw new Error("Query must be a non-empty string!"); } var FORMATS = ["csv", "json"]; function assertFormat(thing) { if (thing == null || FORMATS.includes(thing)) { return; } throw new Error( `Invalid format "${thing}"! Expected ${FORMATS.map( (format) => `"${format}"` ).join(" | ")}` ); } async function executeQueryAndPrint(argz, config) { const result = await executeQuery({ sql: argz.query }, config); if (argz.format === "json") { return consola19.log(JSON.stringify(result, null, 2)); } const { insertId, numAffectedRows, numChangedRows, rows } = result; const [row0] = rows; if (!row0 && (insertId != null || numAffectedRows != null || numChangedRows != null)) { const summary = { "Affected Rows": numAffectedRows, "Changed Rows": numChangedRows, "Insert ID": insertId, rowCount: rows.length }; return printCSV([summary]); } return printCSV(rows); } async function startInteractiveExecution(argz, config) { while (true) { let query = await consola19.prompt(getPrompt(argz, config), { cancel: "null", placeholder: "select 1", required: true, type: "text" }); if (query == null) { return; } query = query.trim(); if (isSafeword(query)) { return; } if (!query.endsWith(";")) { const readline = createInterface({ // biome-ignore lint/style/noNonNullAssertion: yolo input: process2.stdin, output: process2.stdout }); do { const moreQuery = await readline.question(""); query += ` ${moreQuery.trim()}`; } while (!query.endsWith(";")); readline.close(); } try { await executeQueryAndPrint({ ...argz, query }, config); } catch (error) { consola19.error(error instanceof Error ? error.message : error); } } } var SAFEWORDS = ["exit", "quit", "bye", ":q"]; function isSafeword(thing) { return SAFEWORDS.includes(thing); } function getPrompt(argz, config) { const { environment } = argz; const { dialect } = config; return [ typeof dialect === "string" ? dialect : inferDialectName(config.kysely), environment ? colorize3("gray", `(${environment})`) : null, colorize3("cyan", "\u276F") ].filter(Boolean).join(" "); } // src/commands/root.mts var args12 = { ...CommonArgs, version: { alias: "v", default: false, description: "Show version number", type: "boolean" } }; var RootCommand = { meta: { name: "kysely", description: "A command-line tool for Kysely" }, args: args12, subCommands: { ...InitCommand, ...LegacyDownCommand, ...LegacyLatestCommand, ...LegacyListCommand, ...LegacyMakeCommand, ...LegacyRollbackCommand, ...LegacyRunCommand, ...LegacyMakeCommand2, ...LegacyUpCommand, ...MigrateCommand, ...SeedCommand, ...SqlCommand }, setup(context) { const { args: args13 } = context; if (args13.debug) { consola20.level = LogLevels.debug; } consola20.options.formatOptions.date = false; getCWD(args13); }, async run(context) { const { args: args13 } = context; if (!isInSubcommand(context)) { consola20.debug(context, []); if (args13.version) { return await printInstalledVersions(args13); } await showUsage3(context.cmd); } await printUpgradeNotice(args13); consola20.debug(`finished running from "${__filename}"`); } }; // src/cli.mts function buildCLI() { const runCLI = createMain(RootCommand); return { parse: async (argv) => { await runCLI({ rawArgs: argv }); } }; } // src/bin.mts var cli = buildCLI(); cli.parse(process3.argv.slice(2));