Files
bun-mariadb/dist/commands/schema.js
T
tbenandClaude Opus 5.5 ddf431e866 Add tests, schema sync safeguards, and query/handler improvements
- Add bun test suite: SQL generator and value unit tests, plus MariaDB
  integration tests gated on BUN_MARIADB_TEST_DB
- schema: add --dry-run and require confirmation (or --yes) before
  dropping tables/columns; block unapproved destructive statements
- Always AND targetId with the query, regardless of searchOperator
- Initialize lazily on first call; init throws instead of exiting
- Generate IN (?, ?, ...) lists for IN / NOT IN
- Share value binding between insert and update (booleans, embeddings,
  function values, "" -> NULL for non-text columns)
- Keep Buffers and convert BigInt/Date without a JSON round-trip
- DbSQL: report success for empty SELECT results
- Run update() in a transaction with row locking
- Key the client cache on the full connection config
- Consolidate identifier quoting, drop dead code, merge column lookup
- Read CLI version from package.json, pin @types/bun, chmod CLI on compile
- Rebuild dist

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 02:27:59 +01:00

101 lines
4.5 KiB
JavaScript

import { Command } from "commander";
import grabDirNames from "../data/grab-dir-names";
import path from "path";
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
import chalk from "chalk";
import { writeLiveSchema } from "../functions/live-schema";
import createDBSchema from "../lib/schema/create-db-schema";
import { isDestructiveSchemaStatement, planSchemaQueries, runSchemaQueriesGuarded, } from "../lib/schema/run-schema-query";
import { confirm } from "@inquirer/prompts";
import { AppData } from "../data/app-data";
/** Silence progress logs while computing a plan */
async function quietly(fn) {
const { log, warn } = console;
console.log = () => { };
console.warn = () => { };
try {
return await fn();
}
finally {
console.log = log;
console.warn = warn;
}
}
export default function () {
return new Command("schema")
.description("Build DB From Schema")
.option("-t, --typedef", "Generate typescript type definitions")
.option("--dry-run", "Print the schema changes that would be applied, without applying them")
.option("-y, --yes", "Apply destructive changes (dropped tables / columns) without confirmation")
.action(async (opts) => {
console.log(`Starting process ...`);
const config = global.CONFIG;
const dbSchema = global.DB_SCHEMA;
const { ROOT_DIR } = grabDirNames();
try {
const isTypeDef = Boolean(opts.typedef || opts.t);
const finaldbSchema = appendDefaultFieldsToDbSchema({
dbSchema,
});
const params = { db_schema: finaldbSchema, config };
// Hide internal bookkeeping (schema manager rows, FK check toggles)
const plan = (await quietly(() => planSchemaQueries(() => createDBSchema(params)))).filter((statement) => !statement.includes(AppData.DbSchemaManagerTableName) &&
!/^SET FOREIGN_KEY_CHECKS/i.test(statement));
const destructive = plan.filter(isDestructiveSchemaStatement);
if (opts.dryRun) {
if (!plan.length) {
console.log(chalk.green(`Schema is up to date.`));
}
else {
console.log(chalk.bold(`Planned schema changes:\n`));
for (const statement of plan) {
console.log(isDestructiveSchemaStatement(statement)
? chalk.red(` ${statement}`)
: ` ${statement}`);
}
if (destructive.length) {
console.log(chalk.red(`\n${destructive.length} destructive change(s) (in red) would discard data.`));
}
}
process.exit(0);
}
if (destructive.length && !opts.yes) {
console.log(chalk.yellow(`\nThese changes will permanently discard data:\n`));
for (const statement of destructive) {
console.log(chalk.red(` ${statement}`));
}
console.log();
if (!process.stdin.isTTY) {
console.error(chalk.red(`Refusing to apply destructive changes non-interactively. Re-run with --yes to apply them.`));
process.exit(1);
}
const proceed = await confirm({
message: `Apply ${destructive.length} destructive change(s)?`,
default: false,
});
if (!proceed) {
console.log(chalk.dim(`Schema sync cancelled.`));
process.exit(1);
}
}
await runSchemaQueriesGuarded(() => createDBSchema(params), opts.yes ? true : new Set(destructive));
if (isTypeDef && config.typedef_file_path) {
const out_file = path.resolve(ROOT_DIR, config.typedef_file_path);
dbSchemaToTypeDef({
dbSchema: finaldbSchema,
dst_file: out_file,
config,
});
}
writeLiveSchema({ schema: finaldbSchema });
console.log(`${chalk.bold(chalk.green(`DB Schema setup success!`))}`);
process.exit(0);
}
catch (error) {
console.error(error);
process.exit(1);
}
});
}