Updates
This commit is contained in:
+4
-62
@@ -5,30 +5,7 @@ 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";
|
||||
}
|
||||
import { dumpDatabase } from "../utils/mariadb-dump-restore";
|
||||
|
||||
export default function () {
|
||||
return new Command("backup")
|
||||
@@ -46,40 +23,9 @@ export default function () {
|
||||
const backup_file_name = grabDBBackupFileName({ config });
|
||||
const backup_path = path.join(backup_dir, backup_file_name);
|
||||
|
||||
const dumpBin = resolveDumpBinary();
|
||||
const args = [
|
||||
dumpBin,
|
||||
...mariadbCliConnectionArgs(),
|
||||
"--single-transaction",
|
||||
"--routines",
|
||||
"--triggers",
|
||||
"--events",
|
||||
config.db_name,
|
||||
];
|
||||
|
||||
try {
|
||||
const proc = Bun.spawn(args, {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: mariadbCliEnv(),
|
||||
});
|
||||
|
||||
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");
|
||||
const sql = await dumpDatabase(config);
|
||||
fs.writeFileSync(backup_path, sql, "utf-8");
|
||||
trimBackups({ config });
|
||||
|
||||
console.log(
|
||||
@@ -87,11 +33,7 @@ export default function () {
|
||||
);
|
||||
process.exit(0);
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Backup ERROR => ${error.message}. Ensure \`mariadb-dump\` or \`mysqldump\` is installed.`,
|
||||
),
|
||||
);
|
||||
console.error(chalk.red(`Backup ERROR => ${error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Command } from "commander";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import chalk from "chalk";
|
||||
import grabDBDir from "../utils/grab-db-dir";
|
||||
import { AppData } from "../data/app-data";
|
||||
import { dumpDatabase } from "../utils/mariadb-dump-restore";
|
||||
import { writeExportArchive } from "../utils/export-archive";
|
||||
import trimExports from "../utils/trim-exports";
|
||||
|
||||
function defaultExportFileName(
|
||||
dbName: string,
|
||||
format: "tar.gz" | "zip",
|
||||
): string {
|
||||
return `${dbName}-${Date.now()}.${format}`;
|
||||
}
|
||||
|
||||
export default function () {
|
||||
return new Command("export")
|
||||
.description(
|
||||
"Export database SQL dump + schema.ts into a portable archive",
|
||||
)
|
||||
.option(
|
||||
"-o, --output <path>",
|
||||
"Output archive path (.tar.gz or .zip). Defaults to db exports dir",
|
||||
)
|
||||
.option(
|
||||
"-f, --format <format>",
|
||||
"Archive format when --output is omitted: tar.gz | zip",
|
||||
"tar.gz",
|
||||
)
|
||||
.action(async (opts) => {
|
||||
console.log(`Exporting database ...`);
|
||||
|
||||
const config = global.CONFIG;
|
||||
const { db_dir, export_dir } = grabDBDir({ config });
|
||||
|
||||
if (!fs.existsSync(export_dir)) {
|
||||
fs.mkdirSync(export_dir, { recursive: true });
|
||||
}
|
||||
|
||||
const schemaPath = path.join(db_dir, AppData.DbSchemaFileName);
|
||||
if (!fs.existsSync(schemaPath)) {
|
||||
console.error(
|
||||
chalk.red(`Schema file not found: ${schemaPath}`),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const formatRaw = String(opts.format || "tar.gz").toLowerCase();
|
||||
const format: "tar.gz" | "zip" =
|
||||
formatRaw === "zip" ? "zip" : "tar.gz";
|
||||
|
||||
let outPath: string;
|
||||
if (opts.output) {
|
||||
outPath = path.resolve(opts.output);
|
||||
const parent = path.dirname(outPath);
|
||||
if (!fs.existsSync(parent)) {
|
||||
fs.mkdirSync(parent, { recursive: true });
|
||||
}
|
||||
} else {
|
||||
outPath = path.join(
|
||||
export_dir,
|
||||
defaultExportFileName(config.db_name, format),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const sql = await dumpDatabase(config);
|
||||
const schemaTs = fs.readFileSync(schemaPath, "utf-8");
|
||||
|
||||
await writeExportArchive({
|
||||
contents: { sql, schemaTs },
|
||||
outPath,
|
||||
});
|
||||
|
||||
if (path.dirname(outPath) === export_dir) {
|
||||
trimExports({ config });
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${chalk.bold(chalk.green(`DB Export Success!`))} → ${outPath}`,
|
||||
);
|
||||
console.log(
|
||||
chalk.dim(
|
||||
`Contains: dump.sql + ${AppData.DbSchemaFileName}`,
|
||||
),
|
||||
);
|
||||
process.exit(0);
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red(`Export ERROR => ${error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import { Command } from "commander";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import chalk from "chalk";
|
||||
import { select } from "@inquirer/prompts";
|
||||
import grabDBDir from "../utils/grab-db-dir";
|
||||
import grabSortedExports from "../utils/grab-sorted-exports";
|
||||
import grabBackupData from "../utils/grab-backup-data";
|
||||
import { AppData } from "../data/app-data";
|
||||
import { restoreDatabase } from "../utils/mariadb-dump-restore";
|
||||
import {
|
||||
isArchivePath,
|
||||
isSqlPath,
|
||||
readExportArchive,
|
||||
} from "../utils/export-archive";
|
||||
|
||||
function formatChoice(name: string, index: number): string {
|
||||
const { backup_date } = grabBackupData({ backup_name: name });
|
||||
const time = Number.isNaN(backup_date.getTime())
|
||||
? "unknown date"
|
||||
: `${backup_date.toDateString()} ${backup_date.getHours()}:${backup_date.getMinutes()}:${backup_date.getSeconds().toString().padStart(2, "0")}`;
|
||||
return `#${index + 1}: ${name} (${time})`;
|
||||
}
|
||||
|
||||
export default function () {
|
||||
return new Command("import")
|
||||
.description(
|
||||
"Import an SQL dump, or a full export archive (SQL + schema.ts)",
|
||||
)
|
||||
.argument(
|
||||
"[file]",
|
||||
"Path to .sql file or export archive (.tar.gz / .tar / .zip)",
|
||||
)
|
||||
.option(
|
||||
"--sql-only",
|
||||
"When importing an archive, restore SQL only (skip writing schema.ts)",
|
||||
)
|
||||
.option(
|
||||
"--schema-only",
|
||||
"When importing an archive, write schema.ts only (skip SQL restore)",
|
||||
)
|
||||
.action(async (fileArg: string | undefined, opts) => {
|
||||
console.log(`Importing database ...`);
|
||||
|
||||
const config = global.CONFIG;
|
||||
const { db_dir, export_dir } = grabDBDir({ config });
|
||||
|
||||
try {
|
||||
let filePath = fileArg ? path.resolve(fileArg) : "";
|
||||
|
||||
if (!filePath) {
|
||||
const candidates = grabSortedExports({ config }).filter(
|
||||
(b) => isArchivePath(b),
|
||||
);
|
||||
|
||||
if (!candidates[0]) {
|
||||
console.error(
|
||||
`No export archives found in \`${export_dir}\`. Use \`export\`, or pass a file path.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const selected = await select({
|
||||
message: "Select an export to import:",
|
||||
choices: candidates.map((b, i) => ({
|
||||
name: formatChoice(b, i),
|
||||
value: b,
|
||||
})),
|
||||
});
|
||||
|
||||
filePath = path.join(export_dir, selected);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error(chalk.red(`File not found: ${filePath}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (isSqlPath(filePath)) {
|
||||
if (opts.schemaOnly) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
`--schema-only requires an export archive, not a plain .sql file.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const sql = fs.readFileSync(filePath, "utf-8");
|
||||
await restoreDatabase(config, sql);
|
||||
|
||||
console.log(
|
||||
`${chalk.bold(chalk.green(`DB Import Success!`))} ← ${filePath}`,
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!isArchivePath(filePath)) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Unsupported file type: ${filePath}. Use .sql, .tar.gz, .tar, or .zip.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (opts.sqlOnly && opts.schemaOnly) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Cannot combine --sql-only and --schema-only.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { sql, schemaTs } = await readExportArchive(filePath);
|
||||
|
||||
if (!opts.schemaOnly) {
|
||||
await restoreDatabase(config, sql);
|
||||
console.log(chalk.green(`SQL restored from archive`));
|
||||
}
|
||||
|
||||
if (!opts.sqlOnly) {
|
||||
const schemaPath = path.join(
|
||||
db_dir,
|
||||
AppData.DbSchemaFileName,
|
||||
);
|
||||
fs.writeFileSync(schemaPath, schemaTs, "utf-8");
|
||||
console.log(
|
||||
chalk.green(`Schema written → ${schemaPath}`),
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${chalk.bold(chalk.green(`DB Import Success!`))} ← ${filePath}`,
|
||||
);
|
||||
process.exit(0);
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red(`Import ERROR => ${error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import schema from "./schema";
|
||||
import typedef from "./typedef";
|
||||
import backup from "./backup";
|
||||
import restore from "./restore";
|
||||
import exportCmd from "./export";
|
||||
import importCmd from "./import";
|
||||
import admin from "./admin";
|
||||
import init from "../functions/init";
|
||||
|
||||
@@ -25,6 +27,8 @@ program.addCommand(schema());
|
||||
program.addCommand(typedef());
|
||||
program.addCommand(backup());
|
||||
program.addCommand(restore());
|
||||
program.addCommand(exportCmd());
|
||||
program.addCommand(importCmd());
|
||||
program.addCommand(admin());
|
||||
|
||||
/**
|
||||
|
||||
+2
-52
@@ -6,30 +6,7 @@ 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";
|
||||
}
|
||||
import { restoreDatabase } from "../utils/mariadb-dump-restore";
|
||||
|
||||
export default function () {
|
||||
return new Command("restore")
|
||||
@@ -71,35 +48,8 @@ export default function () {
|
||||
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);
|
||||
}
|
||||
await restoreDatabase(config, sql);
|
||||
|
||||
console.log(
|
||||
`${chalk.bold(chalk.green(`DB Restore Success!`))} ← ${selected_backup}`,
|
||||
|
||||
Reference in New Issue
Block a user