updates
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { Command } from "commander";
|
||||
import grabDBDir from "../../utils/grab-db-dir";
|
||||
import chalk from "chalk";
|
||||
import { select } from "@inquirer/prompts";
|
||||
import listTables from "./list-tables";
|
||||
@@ -9,9 +8,6 @@ export default function () {
|
||||
return new Command("admin")
|
||||
.description("View Tables and Data, Run SQL Queries, Etc.")
|
||||
.action(async () => {
|
||||
const config = global.CONFIG;
|
||||
const { db_file_path } = grabDBDir({ config });
|
||||
|
||||
console.log(chalk.bold(chalk.blue("\nBun MariaDB Admin\n")));
|
||||
|
||||
try {
|
||||
|
||||
@@ -4,24 +4,16 @@ import { AppData } from "../../data/app-data";
|
||||
import showEntries from "./show-entries";
|
||||
import showFields from "./show-fields";
|
||||
import dbHandler from "../../lib/db-handler";
|
||||
import MariaDBQuoteGen from "../../lib/schema/mariadb-quote-gen";
|
||||
|
||||
type Params = {};
|
||||
|
||||
type ColumnInfo = {
|
||||
cid: number;
|
||||
name: string;
|
||||
type: string;
|
||||
notnull: number;
|
||||
dflt_value: string | null;
|
||||
pk: number;
|
||||
};
|
||||
|
||||
export default async function listTables(
|
||||
params?: Params,
|
||||
): Promise<"__exit__" | void> {
|
||||
const tables = (
|
||||
await dbHandler({
|
||||
query: `SELECT table_name FROM ${AppData["DbSchemaManagerTableName"]}`,
|
||||
await dbHandler<{ table_name: string }>({
|
||||
query: `SELECT table_name FROM ${MariaDBQuoteGen(AppData["DbSchemaManagerTableName"])}`,
|
||||
})
|
||||
).payload;
|
||||
|
||||
@@ -30,7 +22,6 @@ export default async function listTables(
|
||||
return;
|
||||
}
|
||||
|
||||
// Level 1: table selection loop
|
||||
while (true) {
|
||||
const tableName = await select({
|
||||
message: "Select a table:",
|
||||
@@ -47,7 +38,6 @@ export default async function listTables(
|
||||
if (tableName === "__back__") break;
|
||||
if (tableName === "__exit__") return "__exit__";
|
||||
|
||||
// Level 2: action loop — stays here until "Go Back"
|
||||
while (true) {
|
||||
const action = await select({
|
||||
message: `"${tableName}" — choose an action:`,
|
||||
@@ -73,24 +63,40 @@ export default async function listTables(
|
||||
if (result === "__exit__") return "__exit__";
|
||||
}
|
||||
|
||||
// if (action === "schema") {
|
||||
// const columns = db
|
||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
||||
// .all();
|
||||
if (action === "schema") {
|
||||
const columns = (
|
||||
await dbHandler<{
|
||||
ORDINAL_POSITION: number;
|
||||
COLUMN_NAME: string;
|
||||
COLUMN_TYPE: string;
|
||||
IS_NULLABLE: string;
|
||||
COLUMN_DEFAULT: string | null;
|
||||
COLUMN_KEY: string;
|
||||
EXTRA: string;
|
||||
}>({
|
||||
query: `SELECT ORDINAL_POSITION, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
|
||||
values: [tableName],
|
||||
})
|
||||
).payload;
|
||||
|
||||
// console.log(`\n${chalk.bold(`Schema for "${tableName}":`)} \n`);
|
||||
// console.table(
|
||||
// columns.map((c) => ({
|
||||
// "#": c.cid,
|
||||
// Name: c.name,
|
||||
// Type: c.type,
|
||||
// "Not Null": c.notnull ? "YES" : "NO",
|
||||
// Default: c.dflt_value ?? "(none)",
|
||||
// "Primary Key": c.pk ? "YES" : "NO",
|
||||
// })),
|
||||
// );
|
||||
// console.log();
|
||||
// }
|
||||
console.log(`\n${chalk.bold(`Schema for "${tableName}":`)} \n`);
|
||||
if (columns?.length) {
|
||||
console.table(
|
||||
columns.map((c) => ({
|
||||
"#": c.ORDINAL_POSITION,
|
||||
Name: c.COLUMN_NAME,
|
||||
Type: c.COLUMN_TYPE,
|
||||
Nullable: c.IS_NULLABLE,
|
||||
Default: c.COLUMN_DEFAULT ?? "(none)",
|
||||
Key: c.COLUMN_KEY || "",
|
||||
Extra: c.EXTRA || "",
|
||||
})),
|
||||
);
|
||||
} else {
|
||||
console.log(chalk.yellow("No columns found."));
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,19 @@ export default async function runSQL(params?: Params) {
|
||||
try {
|
||||
const res = await dbHandler({ query: sql });
|
||||
|
||||
if (res.payload) {
|
||||
if (res.payload?.length) {
|
||||
console.log(
|
||||
`\n${chalk.bold(`Result (${res.payload.length} row${res.payload.length !== 1 ? "s" : ""}):`)} \n`,
|
||||
);
|
||||
if (res.payload.length) console.table(res.payload);
|
||||
else console.log(chalk.yellow("No res returned.\n"));
|
||||
} else if (res.single_res) {
|
||||
console.table(res.payload);
|
||||
} else if (res.success) {
|
||||
console.log(
|
||||
chalk.green(
|
||||
`\nSuccess! Affected rows: ${res.single_res.changes}, Last insert ID: ${res.single_res.lastInsertRowid}\n`,
|
||||
`\nSuccess! Affected rows: ${res.insert_return?.affected_rows ?? res.count ?? 0}, Last insert ID: ${res.insert_return?.last_insert_id ?? "—"}\n`,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.error(chalk.red(`\nSQL Error: ${res.error || res.msg}\n`));
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red(`\nSQL Error: ${error.message}\n`));
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import chalk from "chalk";
|
||||
import { select, input } from "@inquirer/prompts";
|
||||
import dbHandler from "../../lib/db-handler";
|
||||
import MariaDBQuoteGen from "../../lib/schema/mariadb-quote-gen";
|
||||
|
||||
type Params = {
|
||||
tableName: string;
|
||||
};
|
||||
type ColumnInfo = { cid: number; name: string };
|
||||
|
||||
const LIMIT = 50;
|
||||
|
||||
@@ -14,36 +14,38 @@ export default async function showEntries({ tableName }: Params) {
|
||||
let searchField: string | null = null;
|
||||
let searchTerm: string | null = null;
|
||||
|
||||
const quotedTable = MariaDBQuoteGen(tableName);
|
||||
|
||||
while (true) {
|
||||
const offset = page * LIMIT;
|
||||
|
||||
const rows = searchTerm
|
||||
? (
|
||||
await dbHandler({
|
||||
query: `SELECT * FROM "${tableName}" WHERE "${searchField}" LIKE ? LIMIT ${LIMIT} OFFSET ${offset}`,
|
||||
query: `SELECT * FROM ${quotedTable} WHERE ${MariaDBQuoteGen(searchField!)} LIKE ? LIMIT ${LIMIT} OFFSET ${offset}`,
|
||||
values: [`%${searchTerm}%`],
|
||||
})
|
||||
).payload
|
||||
: (
|
||||
await dbHandler({
|
||||
query: `SELECT * FROM "${tableName}" LIMIT ${LIMIT} OFFSET ${offset}`,
|
||||
query: `SELECT * FROM ${quotedTable} LIMIT ${LIMIT} OFFSET ${offset}`,
|
||||
})
|
||||
).payload;
|
||||
|
||||
const countRow = searchTerm
|
||||
? (
|
||||
await dbHandler({
|
||||
query: `SELECT COUNT(*) as count FROM "${tableName}" WHERE "${searchField}" LIKE ?`,
|
||||
await dbHandler<{ count: number }>({
|
||||
query: `SELECT COUNT(*) as count FROM ${quotedTable} WHERE ${MariaDBQuoteGen(searchField!)} LIKE ?`,
|
||||
values: [`%${searchTerm}%`],
|
||||
})
|
||||
).payload
|
||||
: (
|
||||
await dbHandler({
|
||||
query: `SELECT COUNT(*) as count FROM "${tableName}"`,
|
||||
await dbHandler<{ count: number }>({
|
||||
query: `SELECT COUNT(*) as count FROM ${quotedTable}`,
|
||||
})
|
||||
).payload;
|
||||
|
||||
const total = countRow?.[0]?.count;
|
||||
const total = Number(countRow?.[0]?.count || 0);
|
||||
const searchInfo = searchTerm
|
||||
? chalk.dim(` · searching "${searchField}" = "${searchTerm}"`)
|
||||
: "";
|
||||
@@ -57,8 +59,7 @@ export default async function showEntries({ tableName }: Params) {
|
||||
);
|
||||
|
||||
if (rows.length) {
|
||||
console.log(rows);
|
||||
// if (rows.length) console.table(rows);
|
||||
console.table(rows);
|
||||
} else {
|
||||
console.log(chalk.yellow("No rows found."));
|
||||
console.log();
|
||||
@@ -85,19 +86,31 @@ export default async function showEntries({ tableName }: Params) {
|
||||
searchTerm = null;
|
||||
page = 0;
|
||||
}
|
||||
// if (action === "search") {
|
||||
// const columns = db
|
||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
||||
// .all();
|
||||
// searchField = await select({
|
||||
// message: "Search by field:",
|
||||
// choices: columns.map((c) => ({ name: c.name, value: c.name })),
|
||||
// });
|
||||
// searchTerm = await input({
|
||||
// message: `Search term for "${searchField}":`,
|
||||
// validate: (v) => v.trim().length > 0 || "Cannot be empty",
|
||||
// });
|
||||
// page = 0;
|
||||
// }
|
||||
if (action === "search") {
|
||||
const columns = (
|
||||
await dbHandler<{ COLUMN_NAME: string }>({
|
||||
query: `SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
|
||||
values: [tableName],
|
||||
})
|
||||
).payload;
|
||||
|
||||
if (!columns?.length) {
|
||||
console.log(chalk.yellow("No columns found for search."));
|
||||
continue;
|
||||
}
|
||||
|
||||
searchField = await select({
|
||||
message: "Search by field:",
|
||||
choices: columns.map((c) => ({
|
||||
name: c.COLUMN_NAME,
|
||||
value: c.COLUMN_NAME,
|
||||
})),
|
||||
});
|
||||
searchTerm = await input({
|
||||
message: `Search term for "${searchField}":`,
|
||||
validate: (v) => v.trim().length > 0 || "Cannot be empty",
|
||||
});
|
||||
page = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +1,109 @@
|
||||
import chalk from "chalk";
|
||||
import { select } from "@inquirer/prompts";
|
||||
import dbHandler from "../../lib/db-handler";
|
||||
|
||||
type Params = {
|
||||
tableName: string;
|
||||
};
|
||||
|
||||
type ColumnInfo = {
|
||||
cid: number;
|
||||
name: string;
|
||||
type: string;
|
||||
notnull: number;
|
||||
dflt_value: string | null;
|
||||
pk: number;
|
||||
};
|
||||
|
||||
type IndexInfo = { name: string; unique: number; origin: string };
|
||||
type IndexColumn = { name: string };
|
||||
type ForeignKey = {
|
||||
id: number;
|
||||
from: string;
|
||||
table: string;
|
||||
to: string;
|
||||
on_update: string;
|
||||
on_delete: string;
|
||||
};
|
||||
|
||||
export default async function showFields({
|
||||
tableName,
|
||||
}: Params): Promise<"__exit__" | void> {
|
||||
// const columns = db
|
||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
||||
// .all();
|
||||
// const indexes = db
|
||||
// .query<IndexInfo, []>(`PRAGMA index_list("${tableName}")`)
|
||||
// .all();
|
||||
// const foreignKeys = db
|
||||
// .query<ForeignKey, []>(`PRAGMA foreign_key_list("${tableName}")`)
|
||||
// .all();
|
||||
// const indexedFields = new Map<string, { unique: boolean }>();
|
||||
// for (const idx of indexes) {
|
||||
// const cols = db
|
||||
// .query<IndexColumn, []>(`PRAGMA index_info("${idx.name}")`)
|
||||
// .all();
|
||||
// for (const col of cols) {
|
||||
// indexedFields.set(col.name, { unique: idx.unique === 1 });
|
||||
// }
|
||||
// }
|
||||
// while (true) {
|
||||
// const fieldName = await select({
|
||||
// message: `"${tableName}" — select a field:`,
|
||||
// choices: [
|
||||
// ...columns.map((c) => ({ name: c.name, value: c.name })),
|
||||
// { name: chalk.dim("← Go Back"), value: "__back__" },
|
||||
// { name: chalk.dim("✕ Exit"), value: "__exit__" },
|
||||
// ],
|
||||
// });
|
||||
// if (fieldName === "__back__") break;
|
||||
// if (fieldName === "__exit__") return "__exit__";
|
||||
// const col = columns.find((c) => c.name === fieldName)!;
|
||||
// const idx = indexedFields.get(fieldName);
|
||||
// const fk = foreignKeys.find((f) => f.from === fieldName);
|
||||
// console.log(`\n${chalk.bold(`Field: "${fieldName}"`)}\n`);
|
||||
// console.log(` ${chalk.dim("Table")} ${tableName}`);
|
||||
// console.log(` ${chalk.dim("Column #")} ${col.cid}`);
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Type")} ${col.type || chalk.italic("(none)")}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Primary Key")} ${col.pk ? chalk.green("YES") : "NO"}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Not Null")} ${col.notnull ? chalk.yellow("YES") : "NO"}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Default")} ${col.dflt_value ?? chalk.italic("(none)")}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Indexed")} ${idx ? chalk.cyan("YES") : "NO"}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Unique")} ${idx?.unique ? chalk.cyan("YES") : "NO"}`,
|
||||
// );
|
||||
// if (fk) {
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Foreign Key")} ${chalk.magenta(`${fk.table}(${fk.to})`)}`,
|
||||
// );
|
||||
// console.log(` ${chalk.dim("On Update")} ${fk.on_update}`);
|
||||
// console.log(` ${chalk.dim("On Delete")} ${fk.on_delete}`);
|
||||
// } else {
|
||||
// console.log(` ${chalk.dim("Foreign Key")} NO`);
|
||||
// }
|
||||
// console.log();
|
||||
// }
|
||||
const columns = (
|
||||
await dbHandler<{
|
||||
ORDINAL_POSITION: number;
|
||||
COLUMN_NAME: string;
|
||||
COLUMN_TYPE: string;
|
||||
IS_NULLABLE: string;
|
||||
COLUMN_DEFAULT: string | null;
|
||||
COLUMN_KEY: string;
|
||||
EXTRA: string;
|
||||
COLUMN_COMMENT: string;
|
||||
}>({
|
||||
query: `SELECT ORDINAL_POSITION, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
|
||||
values: [tableName],
|
||||
})
|
||||
).payload;
|
||||
|
||||
if (!columns?.length) {
|
||||
console.log(chalk.yellow(`\nNo columns found for "${tableName}".\n`));
|
||||
return;
|
||||
}
|
||||
|
||||
const indexes = (
|
||||
await dbHandler<{
|
||||
INDEX_NAME: string;
|
||||
COLUMN_NAME: string;
|
||||
NON_UNIQUE: number;
|
||||
INDEX_TYPE: string;
|
||||
}>({
|
||||
query: `SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE, INDEX_TYPE FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY INDEX_NAME, SEQ_IN_INDEX`,
|
||||
values: [tableName],
|
||||
})
|
||||
).payload;
|
||||
|
||||
const foreignKeys = (
|
||||
await dbHandler<{
|
||||
CONSTRAINT_NAME: string;
|
||||
COLUMN_NAME: string;
|
||||
REFERENCED_TABLE_NAME: string;
|
||||
REFERENCED_COLUMN_NAME: string;
|
||||
}>({
|
||||
query: `SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND REFERENCED_TABLE_NAME IS NOT NULL`,
|
||||
values: [tableName],
|
||||
})
|
||||
).payload;
|
||||
|
||||
while (true) {
|
||||
const fieldName = await select({
|
||||
message: `"${tableName}" — select a field:`,
|
||||
choices: [
|
||||
...columns.map((c) => ({
|
||||
name: c.COLUMN_NAME,
|
||||
value: c.COLUMN_NAME,
|
||||
})),
|
||||
{ name: chalk.dim("← Go Back"), value: "__back__" },
|
||||
{ name: chalk.dim("✕ Exit"), value: "__exit__" },
|
||||
],
|
||||
});
|
||||
|
||||
if (fieldName === "__back__") break;
|
||||
if (fieldName === "__exit__") return "__exit__";
|
||||
|
||||
const col = columns.find((c) => c.COLUMN_NAME === fieldName)!;
|
||||
const colIndexes =
|
||||
indexes?.filter((i) => i.COLUMN_NAME === fieldName) || [];
|
||||
const fk = foreignKeys?.find((f) => f.COLUMN_NAME === fieldName);
|
||||
|
||||
console.log(`\n${chalk.bold(`Field: "${fieldName}"`)}\n`);
|
||||
console.log(` ${chalk.dim("Table")} ${tableName}`);
|
||||
console.log(` ${chalk.dim("Column #")} ${col.ORDINAL_POSITION}`);
|
||||
console.log(` ${chalk.dim("Type")} ${col.COLUMN_TYPE}`);
|
||||
console.log(
|
||||
` ${chalk.dim("Primary Key")} ${col.COLUMN_KEY === "PRI" ? chalk.green("YES") : "NO"}`,
|
||||
);
|
||||
console.log(
|
||||
` ${chalk.dim("Not Null")} ${col.IS_NULLABLE === "NO" ? chalk.yellow("YES") : "NO"}`,
|
||||
);
|
||||
console.log(
|
||||
` ${chalk.dim("Default")} ${col.COLUMN_DEFAULT ?? chalk.italic("(none)")}`,
|
||||
);
|
||||
console.log(
|
||||
` ${chalk.dim("Extra")} ${col.EXTRA || chalk.italic("(none)")}`,
|
||||
);
|
||||
console.log(
|
||||
` ${chalk.dim("Indexed")} ${colIndexes.length ? chalk.cyan(colIndexes.map((i) => i.INDEX_NAME).join(", ")) : "NO"}`,
|
||||
);
|
||||
if (fk) {
|
||||
console.log(
|
||||
` ${chalk.dim("Foreign Key")} ${chalk.magenta(`${fk.REFERENCED_TABLE_NAME}(${fk.REFERENCED_COLUMN_NAME})`)}`,
|
||||
);
|
||||
} else {
|
||||
console.log(` ${chalk.dim("Foreign Key")} NO`);
|
||||
}
|
||||
if (col.COLUMN_COMMENT) {
|
||||
console.log(` ${chalk.dim("Comment")} ${col.COLUMN_COMMENT}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
|
||||
+79
-9
@@ -1,28 +1,98 @@
|
||||
import { Command } from "commander";
|
||||
import path from "path";
|
||||
import grabDBDir from "../utils/grab-db-dir";
|
||||
import fs from "fs";
|
||||
import grabDBDir from "../utils/grab-db-dir";
|
||||
import grabDBBackupFileName from "../utils/grab-db-backup-file-name";
|
||||
import chalk from "chalk";
|
||||
import trimBackups from "../utils/trim-backups";
|
||||
import mariadbCliEnv, {
|
||||
mariadbCliConnectionArgs,
|
||||
} from "../utils/mariadb-cli-env";
|
||||
|
||||
/**
|
||||
* Prefer mariadb-dump, fall back to mysqldump.
|
||||
*/
|
||||
function resolveDumpBinary(): string {
|
||||
const candidates = ["mariadb-dump", "mysqldump"];
|
||||
for (const bin of candidates) {
|
||||
try {
|
||||
const result = Bun.spawnSync(["which", bin], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
if (result.exitCode === 0) {
|
||||
return new TextDecoder().decode(result.stdout).trim() || bin;
|
||||
}
|
||||
} catch {
|
||||
// try next
|
||||
}
|
||||
}
|
||||
return "mariadb-dump";
|
||||
}
|
||||
|
||||
export default function () {
|
||||
return new Command("backup")
|
||||
.description("Backup Database")
|
||||
.action(async (opts) => {
|
||||
.description("Backup MariaDB database (SQL dump)")
|
||||
.action(async () => {
|
||||
console.log(`Backing up database ...`);
|
||||
|
||||
const config = global.CONFIG;
|
||||
const { backup_dir } = grabDBDir({ config });
|
||||
|
||||
const { backup_dir, db_file_path } = grabDBDir({ config });
|
||||
if (!fs.existsSync(backup_dir)) {
|
||||
fs.mkdirSync(backup_dir, { recursive: true });
|
||||
}
|
||||
|
||||
const new_db_file_name = grabDBBackupFileName({ config });
|
||||
const backup_file_name = grabDBBackupFileName({ config });
|
||||
const backup_path = path.join(backup_dir, backup_file_name);
|
||||
|
||||
fs.cpSync(db_file_path, path.join(backup_dir, new_db_file_name));
|
||||
const dumpBin = resolveDumpBinary();
|
||||
const args = [
|
||||
dumpBin,
|
||||
...mariadbCliConnectionArgs(),
|
||||
"--single-transaction",
|
||||
"--routines",
|
||||
"--triggers",
|
||||
"--events",
|
||||
config.db_name,
|
||||
];
|
||||
|
||||
trimBackups({ config });
|
||||
try {
|
||||
const proc = Bun.spawn(args, {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: mariadbCliEnv(),
|
||||
});
|
||||
|
||||
console.log(`${chalk.bold(chalk.green(`DB Backup Success!`))}`);
|
||||
process.exit();
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
if (exitCode !== 0) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Backup failed (exit ${exitCode}): ${stderr || "unknown error"}`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
fs.writeFileSync(backup_path, stdout, "utf-8");
|
||||
trimBackups({ config });
|
||||
|
||||
console.log(
|
||||
`${chalk.bold(chalk.green(`DB Backup Success!`))} → ${backup_path}`,
|
||||
);
|
||||
process.exit(0);
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Backup ERROR => ${error.message}. Ensure \`mariadb-dump\` or \`mysqldump\` is installed.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+70
-12
@@ -6,22 +6,47 @@ import grabSortedBackups from "../utils/grab-sorted-backups";
|
||||
import { select } from "@inquirer/prompts";
|
||||
import grabBackupData from "../utils/grab-backup-data";
|
||||
import path from "path";
|
||||
import mariadbCliEnv, {
|
||||
mariadbCliConnectionArgs,
|
||||
} from "../utils/mariadb-cli-env";
|
||||
|
||||
/**
|
||||
* Prefer mariadb client, fall back to mysql.
|
||||
*/
|
||||
function resolveClientBinary(): string {
|
||||
const candidates = ["mariadb", "mysql"];
|
||||
for (const bin of candidates) {
|
||||
try {
|
||||
const result = Bun.spawnSync(["which", bin], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
if (result.exitCode === 0) {
|
||||
return new TextDecoder().decode(result.stdout).trim() || bin;
|
||||
}
|
||||
} catch {
|
||||
// try next
|
||||
}
|
||||
}
|
||||
return "mariadb";
|
||||
}
|
||||
|
||||
export default function () {
|
||||
return new Command("restore")
|
||||
.description("Restore Database")
|
||||
.action(async (opts) => {
|
||||
console.log(`Restoring up database ...`);
|
||||
.description("Restore MariaDB database from an SQL dump")
|
||||
.action(async () => {
|
||||
console.log(`Restoring database ...`);
|
||||
|
||||
const config = global.CONFIG;
|
||||
const { backup_dir } = grabDBDir({ config });
|
||||
|
||||
const { backup_dir, db_file_path } = grabDBDir({ config });
|
||||
|
||||
const backups = grabSortedBackups({ config });
|
||||
const backups = grabSortedBackups({ config }).filter((b) =>
|
||||
b.endsWith(".sql"),
|
||||
);
|
||||
|
||||
if (!backups?.[0]) {
|
||||
console.error(
|
||||
`No Backups to restore. Use the \`backup\` command to create a backup`,
|
||||
`No SQL backups to restore. Use the \`backup\` command to create a backup.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -40,16 +65,49 @@ export default function () {
|
||||
}),
|
||||
});
|
||||
|
||||
fs.cpSync(path.join(backup_dir, selected_backup), db_file_path);
|
||||
const backup_path = path.join(backup_dir, selected_backup);
|
||||
if (!fs.existsSync(backup_path)) {
|
||||
console.error(`Backup file not found: ${backup_path}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const clientBin = resolveClientBinary();
|
||||
const sql = fs.readFileSync(backup_path, "utf-8");
|
||||
|
||||
const args = [
|
||||
clientBin,
|
||||
...mariadbCliConnectionArgs(),
|
||||
config.db_name,
|
||||
];
|
||||
|
||||
const proc = Bun.spawn(args, {
|
||||
stdin: new Blob([sql]),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: mariadbCliEnv(),
|
||||
});
|
||||
|
||||
const [stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
if (exitCode !== 0) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Restore failed (exit ${exitCode}): ${stderr || "unknown error"}`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${chalk.bold(chalk.green(`DB Restore Success!`))}`,
|
||||
`${chalk.bold(chalk.green(`DB Restore Success!`))} ← ${selected_backup}`,
|
||||
);
|
||||
|
||||
process.exit();
|
||||
process.exit(0);
|
||||
} catch (error: any) {
|
||||
console.error(`Backup Restore ERROR => ${error.message}`);
|
||||
process.exit();
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+6
-16
@@ -1,9 +1,7 @@
|
||||
import { Command } from "commander";
|
||||
import { MariaDBSchemaManager } from "../lib/mariadb/db-schema-manager";
|
||||
import grabDirNames from "../data/grab-dir-names";
|
||||
import path from "path";
|
||||
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
|
||||
import _ from "lodash";
|
||||
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
|
||||
import chalk from "chalk";
|
||||
import { writeLiveSchema } from "../functions/live-schema";
|
||||
@@ -12,10 +10,6 @@ import createDBSchema from "../lib/schema/create-db-schema";
|
||||
export default function () {
|
||||
return new Command("schema")
|
||||
.description("Build DB From Schema")
|
||||
.option(
|
||||
"-v, --vector",
|
||||
"Recreate Vector Tables. This will drop and rebuild all vector tables",
|
||||
)
|
||||
.option("-t, --typedef", "Generate typescript type definitions")
|
||||
.action(async (opts) => {
|
||||
console.log(`Starting process ...`);
|
||||
@@ -32,14 +26,10 @@ export default function () {
|
||||
dbSchema,
|
||||
});
|
||||
|
||||
// const manager = new MariaDBSchemaManager({
|
||||
// schema: finaldbSchema,
|
||||
// });
|
||||
|
||||
// await manager.syncSchema();
|
||||
// manager.close();
|
||||
|
||||
await createDBSchema({ db_schema: finaldbSchema });
|
||||
await createDBSchema({
|
||||
db_schema: finaldbSchema,
|
||||
config,
|
||||
});
|
||||
|
||||
if (isTypeDef && config.typedef_file_path) {
|
||||
const out_file = path.resolve(
|
||||
@@ -59,9 +49,9 @@ export default function () {
|
||||
console.log(
|
||||
`${chalk.bold(chalk.green(`DB Schema setup success!`))}`,
|
||||
);
|
||||
process.exit();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
+13
-15
@@ -7,8 +7,8 @@ import chalk from "chalk";
|
||||
|
||||
export default function () {
|
||||
return new Command("typedef")
|
||||
.description("Build DB From Schema")
|
||||
.action(async (opts) => {
|
||||
.description("Generate TypeScript type definitions from the schema")
|
||||
.action(async () => {
|
||||
console.log(`Creating Type Definition From DB Schema ...`);
|
||||
|
||||
const config = global.CONFIG;
|
||||
@@ -18,23 +18,21 @@ export default function () {
|
||||
|
||||
const finaldbSchema = appendDefaultFieldsToDbSchema({ dbSchema });
|
||||
|
||||
if (config.typedef_file_path) {
|
||||
const out_file = path.resolve(
|
||||
ROOT_DIR,
|
||||
config.typedef_file_path,
|
||||
if (!config.typedef_file_path) {
|
||||
console.error(
|
||||
`\`typedef_file_path\` is required in bun-mariadb.config.ts to generate types.`,
|
||||
);
|
||||
dbSchemaToTypeDef({
|
||||
dbSchema: finaldbSchema,
|
||||
dst_file: out_file,
|
||||
config,
|
||||
});
|
||||
} else {
|
||||
console.error(``);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`${chalk.bold(chalk.green(`Typedef gen success!`))}`);
|
||||
const out_file = path.resolve(ROOT_DIR, config.typedef_file_path);
|
||||
dbSchemaToTypeDef({
|
||||
dbSchema: finaldbSchema,
|
||||
dst_file: out_file,
|
||||
config,
|
||||
});
|
||||
|
||||
process.exit();
|
||||
console.log(`${chalk.bold(chalk.green(`Typedef gen success!`))}`);
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user