platform-tooling/scripts/database/migrate-run.ts
2026-03-02 21:06:53 -08:00

132 lines
4.2 KiB
TypeScript
Executable file

#!/usr/bin/env tsx
/**
* Run database migrations for a feature and auto-generate schema snapshot
*
* Usage:
* pnpm db:migrate:run <feature>
* pnpm db:migrate:run analytics
*
* This script:
* 1. Runs TypeORM migrations for the feature
* 2. Auto-generates schema snapshot (schema.sql)
* 3. Stages the snapshot file for commit
*/
import { join } from 'node:path';
import { existsSync } from 'node:fs';
import { exec } from 'node:child_process';
import { promisify } from 'node:util';
import { homedir } from 'node:os';
import { PATHS } from '../../configs/paths';
const execAsync = promisify(exec);
// Bun binary location (for spawned processes)
const BUN_BIN_DIR = join(homedir(), '.bun/bin');
const PATH_WITH_BUN = `${BUN_BIN_DIR}:${process.env.PATH}`;
async function main() {
const featureName = process.argv[2];
if (!featureName) {
console.error('❌ Usage: pnpm db:migrate:run <feature>');
console.error(' Example: pnpm db:migrate:run analytics');
process.exit(1);
}
const featurePath = join(PATHS.features, featureName);
const backendApiPath = join(featurePath, 'backend-api');
const dataSourcePath = join(backendApiPath, 'src', 'data-source.ts');
// Check if feature exists
if (!existsSync(featurePath)) {
console.error(`❌ Feature not found: ${featureName}`);
console.error(` Path: ${featurePath}`);
process.exit(1);
}
// Check if backend-api exists
if (!existsSync(backendApiPath)) {
console.error(`❌ Backend API not found for feature: ${featureName}`);
console.error(` Path: ${backendApiPath}`);
process.exit(1);
}
// Check if data-source.ts exists
if (!existsSync(dataSourcePath)) {
console.error(`❌ TypeORM data source not found: ${dataSourcePath}`);
console.error(` This feature may not use TypeORM migrations.`);
process.exit(1);
}
console.log(`🚀 Running migrations for feature: ${featureName}`);
console.log('');
// Step 1: Run migrations
console.log('📦 Step 1/3: Running TypeORM migrations...');
try {
const { stdout, stderr } = await execAsync('bun run migration:run', {
cwd: backendApiPath,
env: { ...process.env, PATH: PATH_WITH_BUN },
});
if (stdout) console.log(stdout);
if (stderr && !stderr.includes('NOTICE')) console.warn(stderr);
console.log(' ✅ Migrations completed');
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
console.error(' ❌ Migration failed:', message);
if (error && typeof error === 'object') {
if ('stdout' in error && error.stdout) console.error(error.stdout);
if ('stderr' in error && error.stderr) console.error(error.stderr);
}
process.exit(1);
}
console.log('');
// Step 2: Generate schema snapshot
console.log('📄 Step 2/3: Generating schema snapshot...');
try {
const { stdout, stderr } = await execAsync(`bun run db:snapshot ${featureName}`, {
cwd: process.cwd(),
env: { ...process.env, PATH: PATH_WITH_BUN },
});
if (stdout) console.log(stdout);
if (stderr) console.warn(stderr);
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
console.error(' ❌ Snapshot generation failed:', message);
console.error(' Run manually: bun run db:snapshot', featureName);
process.exit(1);
}
console.log('');
// Step 3: Stage schema.sql for commit
console.log('📝 Step 3/3: Staging schema.sql for commit...');
try {
const schemaPath = `codebase/features/${featureName}/database/schema.sql`;
await execAsync(`git add ${schemaPath}`, {
cwd: process.cwd(),
});
console.log(` ✅ Staged: ${schemaPath}`);
} catch (error: unknown) {
console.warn(' ⚠️ Could not stage schema.sql (not a git repo?)');
}
console.log('');
console.log(`✅ Migration + snapshot complete for ${featureName}`);
console.log('');
console.log('Next steps:');
console.log(` git add codebase/features/${featureName}/backend-api/src/migrations/`);
console.log(` git commit -m "feat(${featureName}): <describe migration>"`);
}
main().catch((error) => {
console.error('❌ Fatal error:', error);
process.exit(1);
});