From 624ab7e09d3fb1d11f0a1d75ae08f4305d2666c9 Mon Sep 17 00:00:00 2001 From: Lawrence Davis Date: Sun, 21 Sep 2025 12:49:05 -0500 Subject: [PATCH] Use deploy script for release_command --- Dockerfile | 1 - fly.toml | 4 +++ scripts/migrate.js | 85 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 scripts/migrate.js diff --git a/Dockerfile b/Dockerfile index c6f8a3e..70c1e12 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,6 @@ COPY . . RUN nx run-many -t build -p spin-cycle spin-cycle-client --prod # Compile TypeORM migrations to JS for production runtime and run migrations RUN npx tsc -p apps/spin-cycle/tsconfig.migrations.json -RUN npm run db:migrate:run RUN cp -R dist/apps/spin-cycle-client dist/apps/spin-cycle/assets/ diff --git a/fly.toml b/fly.toml index 23bb1a2..30cfecf 100644 --- a/fly.toml +++ b/fly.toml @@ -19,3 +19,7 @@ primary_region = 'ord' memory = '1gb' cpu_kind = 'shared' cpus = 1 + +[deploy] + # Run DB migrations before releasing a new version + release_command = "node scripts/migrate.js" diff --git a/scripts/migrate.js b/scripts/migrate.js new file mode 100644 index 0000000..76c8b91 --- /dev/null +++ b/scripts/migrate.js @@ -0,0 +1,85 @@ +/* + Robust migration runner for Fly release_command. + - Waits for Postgres to be reachable (with retries) + - Runs TypeORM migrations from compiled JS in dist + - Exits cleanly so Fly can proceed with deploy +*/ + +/* eslint-disable no-console */ +const { Client } = require('pg'); +const { DataSource } = require('typeorm'); + +const DATABASE_URL = process.env.DATABASE_URL; +if (!DATABASE_URL) { + console.error('DATABASE_URL is not set. Aborting migrations.'); + process.exit(1); +} + +const MAX_ATTEMPTS = parseInt(process.env.DB_WAIT_ATTEMPTS || '20', 10); // ~100s by default +const DELAY_MS = parseInt(process.env.DB_WAIT_DELAY_MS || '5000', 10); + +async function waitForPostgres() { + for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { + const client = new Client({ + connectionString: DATABASE_URL, + connectionTimeoutMillis: 5000, + }); + try { + await client.connect(); + await client.end(); + console.log('Postgres is reachable.'); + return; + } catch (err) { + const msg = err && err.message ? err.message : String(err); + console.log(`Postgres not ready (attempt ${attempt}/${MAX_ATTEMPTS}): ${msg}`); + if (attempt === MAX_ATTEMPTS) { + throw new Error('Timed out waiting for Postgres'); + } + await new Promise((res) => setTimeout(res, DELAY_MS)); + } + } +} + +async function runMigrations() { + // Use compiled JS migrations inside the Docker image + const migrationsGlob = 'dist/apps/spin-cycle/migrations/*.js'; + console.log(`Running migrations from: ${migrationsGlob}`); + + const dataSource = new DataSource({ + type: 'postgres', + url: DATABASE_URL, + synchronize: false, + logging: true, + migrationsTableName: 'migrations', + migrations: [migrationsGlob], + }); + + try { + await dataSource.initialize(); + const results = await dataSource.runMigrations(); + if (!results.length) { + console.log('No migrations to run.'); + } else { + for (const r of results) { + console.log(`Applied migration: ${r.name}`); + } + } + } finally { + await dataSource.destroy().catch(() => {}); + } +} + +(async () => { + try { + console.log('Waiting for Postgres to be ready...'); + await waitForPostgres(); + console.log('Running TypeORM migrations...'); + await runMigrations(); + console.log('Migrations complete.'); + process.exit(0); + } catch (err) { + console.error('Migration failed:', err); + process.exit(1); + } +})(); +