Files
LWLT-AIBOT/control-plane/src/migrate.ts
2026-08-25 10:33:29 +08:00

66 lines
2.3 KiB
TypeScript

import { readdir, readFile } from 'node:fs/promises';
import { basename, dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { loadConfig } from './config.js';
import { getPool, closePool, quoteIdentifier, withTransaction } from './db.js';
const here = dirname(fileURLToPath(import.meta.url));
async function resolveMigrationsDir(): Promise<string> {
const candidates = [
resolve(here, '../migrations'),
resolve(process.cwd(), 'control-plane/migrations')
];
for (const candidate of candidates) {
try {
await readdir(candidate);
return candidate;
} catch {
// The compiled layout does not copy SQL files into dist; continue with
// the project-root source migration directory.
}
}
throw new Error('迁移目录不存在。');
}
export async function migrateDatabase(): Promise<string[]> {
const config = loadConfig();
const migrationsDir = await resolveMigrationsDir();
await getPool(config).query(`CREATE SCHEMA IF NOT EXISTS ${quoteIdentifier(config.DATABASE_SCHEMA)}`);
await getPool(config).query(`
CREATE TABLE IF NOT EXISTS schema_migrations (
version text PRIMARY KEY,
applied_at timestamptz NOT NULL DEFAULT now()
)
`);
const files = (await readdir(migrationsDir))
.filter((file) => /^\d+_.+\.sql$/.test(file))
.sort();
const applied: string[] = [];
for (const file of files) {
const version = basename(file, '.sql');
const result = await withTransaction(config, async (client) => {
const existing = await client.query('SELECT 1 FROM schema_migrations WHERE version = $1', [version]);
if (existing.rowCount) return false;
const sql = await readFile(resolve(migrationsDir, file), 'utf8');
await client.query(sql);
await client.query('INSERT INTO schema_migrations (version) VALUES ($1)', [version]);
return true;
});
if (result) applied.push(version);
}
return applied;
}
if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
migrateDatabase()
.then((applied) => {
console.log(JSON.stringify({ ok: true, applied }));
})
.catch((error) => {
console.error(JSON.stringify({ ok: false, error: error.message || String(error) }));
process.exitCode = 1;
})
.finally(() => closePool());
}