178 lines
5.0 KiB
JavaScript
178 lines
5.0 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { execSync } from 'child_process'
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
import { config } from 'dotenv'
|
|
config({ path: path.resolve(process.cwd(), '.env') })
|
|
|
|
const colors = {
|
|
reset: '\x1b[0m',
|
|
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 NON_INTERACTIVE_ENV = {
|
|
...process.env,
|
|
CI: 'true',
|
|
DRIZZLE_KIT_FORCE: 'true',
|
|
DRIZZLE_KIT_NON_INTERACTIVE: 'true',
|
|
NODE_ENV: process.env.NODE_ENV || 'production'
|
|
}
|
|
|
|
function safeExec(command, options = {}) {
|
|
try {
|
|
execSync(command, { stdio: 'inherit', ...options })
|
|
return true
|
|
} catch (e) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
function fileExists(p) {
|
|
try {
|
|
return fs.existsSync(p)
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
function ensureDrizzleFiles() {
|
|
if (!fileExists('drizzle.config.ts')) throw new Error('Drizzle 配置文件不存在')
|
|
if (!fileExists('app/drizzle/schema.ts')) throw new Error('Schema 文件不存在')
|
|
}
|
|
|
|
function isEmptyDatabase() {
|
|
try {
|
|
const output = execSync('npx drizzle-kit introspect --config=drizzle.config.ts', {
|
|
stdio: 'pipe',
|
|
env: NON_INTERACTIVE_ENV,
|
|
encoding: 'utf8'
|
|
})
|
|
const tablesMatch = output.match(/(\d+)\s+tables/i)
|
|
const hasTablesCount = tablesMatch && Number(tablesMatch[1]) > 0
|
|
const listsTables = /\bcolumns\b|\bindexes\b|\bfks\b/i.test(output)
|
|
return !(hasTablesCount || listsTables)
|
|
} catch {
|
|
warn('无法检测数据库状态,按非空库处理')
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 检查数据库schema是否与代码中的schema一致
|
|
function checkSchemaConsistency() {
|
|
try {
|
|
// 使用 drizzle-kit introspect 获取当前数据库schema
|
|
const output = execSync('npx drizzle-kit introspect --config=drizzle.config.ts', {
|
|
stdio: 'pipe',
|
|
env: NON_INTERACTIVE_ENV,
|
|
encoding: 'utf8'
|
|
})
|
|
|
|
// 检查关键表和字段是否存在
|
|
const checks = [
|
|
{ pattern: /"status".*user_status/i, name: 'user_status enum type' },
|
|
{ pattern: /"api_keys"/i, name: 'api_keys table' }
|
|
]
|
|
|
|
const missing = []
|
|
for (const check of checks) {
|
|
if (!check.pattern.test(output)) {
|
|
missing.push(check.name)
|
|
}
|
|
}
|
|
|
|
if (missing.length > 0) {
|
|
warn(`检测到数据库schema不完整,缺少: ${missing.join(', ')}`)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
} catch (e) {
|
|
warn('无法检测数据库schema一致性')
|
|
return true // 出错时不阻止继续
|
|
}
|
|
}
|
|
|
|
function main() {
|
|
log('🔄 数据库同步', 'cyan')
|
|
|
|
if (!process.env.DATABASE_URL) {
|
|
warn('未设置 DATABASE_URL')
|
|
process.exit(0)
|
|
}
|
|
|
|
ensureDrizzleFiles()
|
|
|
|
const emptyDb = isEmptyDatabase()
|
|
if (emptyDb) {
|
|
log('🆕 检测到空库,执行迁移 (migrate)...', 'cyan')
|
|
if (!safeExec('npx drizzle-kit migrate', { env: NON_INTERACTIVE_ENV })) {
|
|
err('数据库迁移失败')
|
|
process.exit(1)
|
|
}
|
|
ok('空库迁移完成')
|
|
} else {
|
|
log('🔁 检测到非空库,检查schema一致性...', 'cyan')
|
|
|
|
// 检查数据库schema是否与代码一致
|
|
const schemaConsistent = checkSchemaConsistency()
|
|
|
|
if (!schemaConsistent) {
|
|
warn('数据库schema不完整,尝试使用 push --force 进行修复...', 'cyan')
|
|
const pushCommand = 'npx drizzle-kit push --force --config=drizzle.config.ts'
|
|
if (
|
|
!safeExec(pushCommand, {
|
|
env: { ...NON_INTERACTIVE_ENV, DRIZZLE_KIT_NON_INTERACTIVE: 'true' }
|
|
})
|
|
) {
|
|
err('数据库schema修复失败')
|
|
process.exit(1)
|
|
}
|
|
ok('schema修复成功')
|
|
} else {
|
|
log('🔁 数据库schema一致,尝试执行 migrate 同步...', 'cyan')
|
|
|
|
// 尝试执行 migrate
|
|
const migrateSuccess = safeExec('npx drizzle-kit migrate', {
|
|
env: { ...NON_INTERACTIVE_ENV, DRIZZLE_KIT_NON_INTERACTIVE: 'true' }
|
|
})
|
|
|
|
if (migrateSuccess) {
|
|
ok('migrate 同步成功')
|
|
} else {
|
|
warn('migrate 同步失败,可能是由于数据库结构与迁移记录不一致。')
|
|
log('🔄 尝试使用 push --force 进行强制同步...', 'cyan')
|
|
|
|
// 在 CI 环境下,push 命令如果遇到重命名等歧义可能会弹出交互式提示
|
|
// 确保 DRIZZLE_KIT_NON_INTERACTIVE 已设置
|
|
const pushCommand = 'npx drizzle-kit push --force --config=drizzle.config.ts'
|
|
if (
|
|
!safeExec(pushCommand, {
|
|
env: { ...NON_INTERACTIVE_ENV, DRIZZLE_KIT_NON_INTERACTIVE: 'true' }
|
|
})
|
|
) {
|
|
err('数据库同步完全失败。请检查数据库连接或手动运行 npx drizzle-kit push 以解决歧义。')
|
|
process.exit(1)
|
|
}
|
|
ok('强制同步 (push) 成功')
|
|
}
|
|
}
|
|
}
|
|
|
|
ok('数据库同步流程完成')
|
|
}
|
|
|
|
try {
|
|
main()
|
|
} catch (e) {
|
|
err(`同步异常: ${e.message || e}`)
|
|
process.exit(1)
|
|
}
|