mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 17:46:06 +00:00
153 lines
4.4 KiB
TypeScript
153 lines
4.4 KiB
TypeScript
import 'dotenv/config';
|
|
import { Client } from 'pg';
|
|
import { readdirSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { spawn } from 'node:child_process';
|
|
|
|
type MigrationRow = {
|
|
migration_name: string;
|
|
finished_at: Date | null;
|
|
rolled_back_at: Date | null;
|
|
};
|
|
|
|
async function runPrisma(args: string[]) {
|
|
await new Promise<void>((resolve, reject) => {
|
|
const proc = spawn('./node_modules/.bin/prisma', args, {
|
|
stdio: 'inherit',
|
|
env: process.env,
|
|
});
|
|
|
|
proc.on('error', reject);
|
|
proc.on('exit', (code) => {
|
|
if (code === 0) {
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
reject(new Error(`Prisma command failed: prisma ${args.join(' ')}`));
|
|
});
|
|
});
|
|
}
|
|
|
|
async function tableExists(client: Client, tableName: string) {
|
|
const result = await client.query<{ exists: boolean }>(
|
|
`
|
|
SELECT EXISTS (
|
|
SELECT 1
|
|
FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_name = $1
|
|
) AS "exists"
|
|
`,
|
|
[tableName]
|
|
);
|
|
|
|
return result.rows[0]?.exists ?? false;
|
|
}
|
|
|
|
async function getMigrationRows(client: Client) {
|
|
const hasMigrationsTable = await tableExists(client, '_prisma_migrations');
|
|
if (!hasMigrationsTable) return [];
|
|
|
|
const result = await client.query<MigrationRow>(
|
|
`
|
|
SELECT migration_name, finished_at, rolled_back_at
|
|
FROM "_prisma_migrations"
|
|
ORDER BY started_at ASC
|
|
`
|
|
);
|
|
|
|
return result.rows;
|
|
}
|
|
|
|
function getMigrationDirectories() {
|
|
return readdirSync(join(process.cwd(), 'prisma', 'migrations'), { withFileTypes: true })
|
|
.filter((entry) => entry.isDirectory())
|
|
.map((entry) => entry.name)
|
|
.sort();
|
|
}
|
|
|
|
async function getPublicTables(client: Client) {
|
|
const result = await client.query<{ table_name: string }>(
|
|
`
|
|
SELECT table_name
|
|
FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_type = 'BASE TABLE'
|
|
ORDER BY table_name ASC
|
|
`
|
|
);
|
|
|
|
return result.rows.map((row) => row.table_name);
|
|
}
|
|
|
|
async function main() {
|
|
if (!process.env.DATABASE_URL) {
|
|
throw new Error('DATABASE_URL is required');
|
|
}
|
|
|
|
const client = new Client({ connectionString: process.env.DATABASE_URL });
|
|
await client.connect();
|
|
|
|
try {
|
|
const [publicTables, migrationRows] = await Promise.all([
|
|
getPublicTables(client),
|
|
getMigrationRows(client),
|
|
]);
|
|
|
|
const appTables = publicTables.filter((table) => table !== '_prisma_migrations');
|
|
const hasUsersTable = appTables.includes('users');
|
|
const hasWorkspacesTable = appTables.includes('workspaces');
|
|
const hasProjectsTable = appTables.includes('projects');
|
|
const hasCoreTables = hasUsersTable || hasWorkspacesTable || hasProjectsTable;
|
|
const failedMigrations = migrationRows.filter((row) => !row.finished_at && !row.rolled_back_at);
|
|
const shouldBootstrapFreshSchema = !hasCoreTables;
|
|
|
|
console.log(`Detected public tables: ${appTables.length > 0 ? appTables.join(', ') : '(none)'}`);
|
|
|
|
if (shouldBootstrapFreshSchema) {
|
|
if (failedMigrations.length > 0) {
|
|
console.log('Detected failed migration state on a fresh database. Marking failed migrations as rolled back.');
|
|
for (const migration of failedMigrations) {
|
|
await runPrisma(['migrate', 'resolve', '--rolled-back', migration.migration_name]);
|
|
}
|
|
}
|
|
|
|
console.log('Fresh self-hosted database detected. Synchronizing schema baseline.');
|
|
await runPrisma(['db', 'push']);
|
|
|
|
const appliedMigrationNames = new Set(
|
|
migrationRows
|
|
.filter((row) => row.finished_at && !row.rolled_back_at)
|
|
.map((row) => row.migration_name)
|
|
);
|
|
|
|
for (const migrationName of getMigrationDirectories()) {
|
|
if (appliedMigrationNames.has(migrationName)) continue;
|
|
await runPrisma(['migrate', 'resolve', '--applied', migrationName]);
|
|
}
|
|
|
|
console.log('Fresh database bootstrap complete');
|
|
return;
|
|
}
|
|
|
|
if (failedMigrations.length > 0) {
|
|
throw new Error(
|
|
`Detected failed Prisma migrations on a non-empty database: ${failedMigrations
|
|
.map((migration) => migration.migration_name)
|
|
.join(', ')}`
|
|
);
|
|
}
|
|
|
|
console.log('Running Prisma migrations');
|
|
await runPrisma(['migrate', 'deploy']);
|
|
} finally {
|
|
await client.end();
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error('Docker database bootstrap failed:', error);
|
|
process.exit(1);
|
|
});
|