初次提交
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
#!/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('pnpm exec 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('pnpm exec 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('pnpm run db: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 = 'pnpm exec 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('pnpm run db: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 = 'pnpm exec drizzle-kit push --force --config=drizzle.config.ts'
|
||||
if (
|
||||
!safeExec(pushCommand, {
|
||||
env: { ...NON_INTERACTIVE_ENV, DRIZZLE_KIT_NON_INTERACTIVE: 'true' }
|
||||
})
|
||||
) {
|
||||
err('数据库同步完全失败。请检查数据库连接或手动运行 pnpm exec drizzle-kit push 以解决歧义。')
|
||||
process.exit(1)
|
||||
}
|
||||
ok('强制同步 (push) 成功')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ok('数据库同步流程完成')
|
||||
}
|
||||
|
||||
try {
|
||||
main()
|
||||
} catch (e) {
|
||||
err(`同步异常: ${e.message || e}`)
|
||||
process.exit(1)
|
||||
}
|
||||
Reference in New Issue
Block a user