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, index) { 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, 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) { console.error(chalk.red(`Import ERROR => ${error.message}`)); process.exit(1); } }); }