181 lines
4.5 KiB
JavaScript
181 lines
4.5 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
/**
|
||
* 数据库完全重置脚本
|
||
* 删除所有表(含迁移记录表),然后从头执行迁移
|
||
* 使用方式: pnpm run db:reset
|
||
*/
|
||
import { execSync } from 'child_process'
|
||
import path from 'path'
|
||
import { config } from 'dotenv'
|
||
import postgres from 'postgres'
|
||
|
||
const colors = {
|
||
reset: '\x1b[0m',
|
||
bright: '\x1b[1m',
|
||
red: '\x1b[31m',
|
||
green: '\x1b[32m',
|
||
yellow: '\x1b[33m',
|
||
cyan: '\x1b[36m'
|
||
}
|
||
const log = (msg, color = 'reset') => console.log(`${colors[color]}${msg}${colors.reset}`)
|
||
const ok = (msg) => log(`✅ ${msg}`, 'green')
|
||
const warn = (msg) => log(`⚠️ ${msg}`, 'yellow')
|
||
const err = (msg) => log(`❌ ${msg}`, 'red')
|
||
|
||
const args = process.argv.slice(2)
|
||
const forceFlag = args.includes('--force')
|
||
|
||
// 禁止在 CI 中执行(防止误触发导致生产库被清空)
|
||
if (process.env.CI && !forceFlag) {
|
||
err('拒绝在 CI 环境中执行数据库重置;如确需强制,请使用 --force 标志并确认 CI 配置正确')
|
||
process.exit(1)
|
||
}
|
||
|
||
if (!forceFlag) {
|
||
err('危险操作:此脚本将清空整库数据')
|
||
err('请添加 --force 标志确认执行:pnpm run db:reset -- --force')
|
||
process.exit(1)
|
||
}
|
||
|
||
config({ path: path.resolve(process.cwd(), '.env') })
|
||
|
||
const NON_INTERACTIVE_ENV = {
|
||
...process.env,
|
||
CI: 'true',
|
||
DRIZZLE_KIT_FORCE: 'true',
|
||
NODE_ENV: process.env.NODE_ENV || 'production'
|
||
}
|
||
|
||
function safeExec(command, options = {}) {
|
||
try {
|
||
execSync(command, { stdio: 'inherit', ...options })
|
||
return true
|
||
} catch {
|
||
return false
|
||
}
|
||
}
|
||
|
||
function createSqlClient() {
|
||
return postgres(process.env.DATABASE_URL, { max: 1 })
|
||
}
|
||
|
||
async function dropAllTables(sql) {
|
||
log('获取 public schema 下所有表...', 'cyan')
|
||
|
||
const tables = await sql`
|
||
SELECT table_name
|
||
FROM information_schema.tables
|
||
WHERE table_schema = 'public'
|
||
AND table_type = 'BASE TABLE'
|
||
ORDER BY table_name
|
||
`
|
||
|
||
if (tables.length === 0) {
|
||
warn('没有找到任何表')
|
||
return
|
||
}
|
||
|
||
log(`发现 ${tables.length} 张表,准备逐个删除(含 __drizzle_migrations__)...`, 'cyan')
|
||
|
||
for (const row of tables) {
|
||
const tableName = row.table_name
|
||
await sql.unsafe(`DROP TABLE IF EXISTS "${tableName}" CASCADE`)
|
||
log(` - ${tableName}`, 'reset')
|
||
}
|
||
|
||
ok(`已删除 ${tables.length} 张表`)
|
||
}
|
||
|
||
async function dropAllEnums(sql) {
|
||
log('清理枚举类型...', 'cyan')
|
||
|
||
const enums = await sql`
|
||
SELECT t.typname
|
||
FROM pg_type t
|
||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||
WHERE n.nspname = 'public'
|
||
AND t.typtype = 'e'
|
||
ORDER BY t.typname
|
||
`
|
||
|
||
if (enums.length === 0) {
|
||
return
|
||
}
|
||
|
||
for (const row of enums) {
|
||
await sql.unsafe(`DROP TYPE IF EXISTS "${row.typname}" CASCADE`)
|
||
log(` - ${row.typname}`, 'reset')
|
||
}
|
||
|
||
ok(`已清理 ${enums.length} 个枚举类型`)
|
||
}
|
||
|
||
async function dropAllSequences(sql) {
|
||
log('清理序列...', 'cyan')
|
||
|
||
const sequences = await sql`
|
||
SELECT sequence_name
|
||
FROM information_schema.sequences
|
||
WHERE sequence_schema = 'public'
|
||
ORDER BY sequence_name
|
||
`
|
||
|
||
if (sequences.length === 0) {
|
||
return
|
||
}
|
||
|
||
for (const row of sequences) {
|
||
await sql.unsafe(`DROP SEQUENCE IF EXISTS "${row.sequence_name}"`)
|
||
log(` - ${row.sequence_name}`, 'reset')
|
||
}
|
||
|
||
ok(`已清理 ${sequences.length} 个序列`)
|
||
}
|
||
|
||
async function main() {
|
||
if (!process.env.DATABASE_URL) {
|
||
err('未设置 DATABASE_URL 环境变量')
|
||
process.exit(1)
|
||
}
|
||
|
||
log('⚠️ 此操作将删除数据库所有表(含迁移记录),并从头重建', 'red')
|
||
const dbUrl = (process.env.DATABASE_URL || '')
|
||
.replace(/(postgresql:\/\/[^:@]+):[^@]+@/, '$1:***@')
|
||
.replace(/(postgres:\/\/[^:@]+):[^@]+@/, '$1:***@')
|
||
log(`目标数据库: ${dbUrl}`, 'cyan')
|
||
log('🔄 开始数据库完全重置...', 'bright')
|
||
|
||
const sql = createSqlClient()
|
||
|
||
try {
|
||
// 1. 删除所有表
|
||
await dropAllTables(sql)
|
||
|
||
// 2. 删除所有枚举
|
||
await dropAllEnums(sql)
|
||
|
||
// 3. 删除所有序列
|
||
await dropAllSequences(sql)
|
||
|
||
// 4. 从头执行迁移
|
||
log('📋 从头执行数据库迁移...', 'cyan')
|
||
if (!safeExec('pnpm run db:migrate', { env: NON_INTERACTIVE_ENV })) {
|
||
err('数据库迁移失败')
|
||
process.exit(1)
|
||
}
|
||
|
||
ok('数据库完全重置并迁移成功!')
|
||
ok('数据库现已处于全新初始状态')
|
||
} catch (error) {
|
||
err(`重置失败: ${error.message || error}`)
|
||
process.exit(1)
|
||
} finally {
|
||
await sql.end()
|
||
}
|
||
}
|
||
|
||
main().catch((e) => {
|
||
err(`未预期的错误: ${e.message || e}`)
|
||
process.exit(1)
|
||
}) |