From 474420ac1eacbbaeed4396f40753de8b18940841 Mon Sep 17 00:00:00 2001 From: Michael Wolf Date: Sat, 24 Jan 2026 15:12:11 -0600 Subject: [PATCH] Add development command to reset the database and rerun migrations --- develop | 27 +++++++++++++++++++ express/database.ts | 10 +++++--- express/develop/reset-db.ts | 50 ++++++++++++++++++++++++++++++++++++ framework/develop.d/reset-db | 9 +++++++ 4 files changed, 93 insertions(+), 3 deletions(-) create mode 100755 develop create mode 100644 express/develop/reset-db.ts create mode 100755 framework/develop.d/reset-db diff --git a/develop b/develop new file mode 100755 index 0000000..5592ec1 --- /dev/null +++ b/develop @@ -0,0 +1,27 @@ +#!/bin/bash + +# This file belongs to the framework. You are not expected to modify it. + +# Development command runner - parallel to ./mgmt for development tasks +# Usage: ./develop [args...] + +set -eu + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +if [ $# -lt 1 ]; then + echo "Usage: ./develop [args...]" + echo "" + echo "Available commands:" + for cmd in "$DIR"/framework/develop.d/*; do + if [ -x "$cmd" ]; then + basename "$cmd" + fi + done + exit 1 +fi + +subcmd="$1" +shift + +exec "$DIR"/framework/develop.d/"$subcmd" "$@" diff --git a/express/database.ts b/express/database.ts index 49fbabd..999cdc4 100644 --- a/express/database.ts +++ b/express/database.ts @@ -137,6 +137,8 @@ async function runMigration(filename: string): Promise { const filepath = path.join(MIGRATIONS_DIR, filename); const content = fs.readFileSync(filepath, "utf-8"); + process.stdout.write(` Migration: ${filename}...`); + // Run migration in a transaction const client = await pool.connect(); try { @@ -147,8 +149,11 @@ async function runMigration(filename: string): Promise { [filename], ); await client.query("COMMIT"); - console.log(`Applied migration: ${filename}`); + console.log(" ✓"); } catch (err) { + console.log(" ✗"); + const message = err instanceof Error ? err.message : String(err); + console.error(` Error: ${message}`); await client.query("ROLLBACK"); throw err; } finally { @@ -169,11 +174,10 @@ async function migrate(): Promise { return; } - console.log(`Running ${pending.length} migration(s)...`); + console.log(`Applying ${pending.length} migration(s):`); for (const file of pending) { await runMigration(file); } - console.log("Migrations complete"); } // List migration status diff --git a/express/develop/reset-db.ts b/express/develop/reset-db.ts new file mode 100644 index 0000000..abd7dd2 --- /dev/null +++ b/express/develop/reset-db.ts @@ -0,0 +1,50 @@ +// reset-db.ts +// Development command to wipe the database and apply all migrations from scratch + +import { migrate, pool, connectionConfig } from "../database"; + +async function main(): Promise { + const args = process.argv.slice(2); + + // Require explicit confirmation unless --force is passed + if (!args.includes("--force")) { + console.error("This will DROP ALL TABLES in the database!"); + console.error(` Database: ${connectionConfig.database}`); + console.error(` Host: ${connectionConfig.host}:${connectionConfig.port}`); + console.error(""); + console.error("Run with --force to proceed."); + process.exit(1); + } + + try { + console.log("Dropping all tables..."); + + // Get all table names in the public schema + const result = await pool.query<{ tablename: string }>(` + SELECT tablename FROM pg_tables + WHERE schemaname = 'public' + `); + + if (result.rows.length > 0) { + // Drop all tables with CASCADE to handle foreign key constraints + const tableNames = result.rows.map((r) => `"${r.tablename}"`).join(", "); + await pool.query(`DROP TABLE IF EXISTS ${tableNames} CASCADE`); + console.log(`Dropped ${result.rows.length} table(s)`); + } else { + console.log("No tables to drop"); + } + + console.log(""); + await migrate(); + + console.log(""); + console.log("Database reset complete."); + } finally { + await pool.end(); + } +} + +main().catch((err) => { + console.error("Failed to reset database:", err.message); + process.exit(1); +}); diff --git a/framework/develop.d/reset-db b/framework/develop.d/reset-db new file mode 100755 index 0000000..ac11200 --- /dev/null +++ b/framework/develop.d/reset-db @@ -0,0 +1,9 @@ +#!/bin/bash + +set -eu + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$DIR/../.." + +cd "$ROOT/express" +"$DIR"/../cmd.d/tsx develop/reset-db.ts "$@"