Files
BYW-VoiceHub/scripts/deploy.js
T
Henry_Du 1718542228
构建并部署 VoiceHub / 构建 Docker 镜像 (push) Canceled after 0s
构建并部署 VoiceHub / 部署到服务器 (push) Canceled after 0s
chore(deploy): 修复 Docker 启动链 + 补 compose + Gitea Actions
2026-09-06 08:23:39 +08:00

203 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
import { execSync } from 'child_process'
import fs from 'fs'
import { config } from 'dotenv'
import path from 'path'
// 加载环境变量(从项目根目录)
config({ path: path.resolve(process.cwd(), '.env') })
// 如果设置了 PREBUILT=true,则自动跳过安装和构建
if (process.env.PREBUILT === 'true') {
process.env.SKIP_INSTALL = 'true'
process.env.SKIP_BUILD = 'true'
}
// 颜色输出函数
const colors = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
cyan: '\x1b[36m'
}
const BUILD_MEMORY_MB = 1024
const DEFAULT_NODE_OPTIONS = `--max-old-space-size=${BUILD_MEMORY_MB}`
function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`)
}
function logStep(step, message) {
log(`${step} ${message}`, 'cyan')
}
function logSuccess(message) {
log(`✅ ${message}`, 'green')
}
function logWarning(message) {
log(`⚠️ ${message}`, 'yellow')
}
function logError(message) {
log(`❌ ${message}`, 'red')
}
// 检查文件是否存在
function fileExists(filePath) {
try {
return fs.existsSync(filePath)
} catch {
return false
}
}
// 安全执行命令
function safeExec(command, options = {}) {
try {
execSync(command, { stdio: 'inherit', ...options })
return true
} catch (error) {
return false
}
}
// 检查环境变量
function checkEnvironment() {
logStep('🔍', '检查环境配置...')
const requiredEnvVars = ['DATABASE_URL']
const missingVars = []
requiredEnvVars.forEach((varName) => {
if (!process.env[varName]) {
missingVars.push(varName)
}
})
if (missingVars.length > 0) {
logError(`缺少必需的环境变量: ${missingVars.join(', ')}`)
logError('请通过以下方式之一设置环境变量:')
logError('1. 创建 .env 文件并配置 DATABASE_URL')
logError('2. 使用 docker run -e DATABASE_URL=xxx 传递环境变量')
logError('3. 在 docker-compose.yml 中配置 environment')
throw new Error('环境变量配置不完整')
} else {
logSuccess('环境变量检查通过')
}
return true
}
// 主部署流程
async function deploy() {
log('🚀 开始部署...', 'cyan')
try {
// 0. 检查环境(必须通过)
checkEnvironment()
// 1. 安装依赖
if (process.env.SKIP_INSTALL === 'true') {
logStep('📦', '跳过依赖安装 (SKIP_INSTALL=true)...')
} else {
logStep('📦', '安装依赖...')
let installed = false
// 优先使用 npm(用户要求:禁止 pnpm)—— 优先 package-lock.json 冻结安装
if (fileExists('package-lock.json')) {
log('尝试使用 npm ci(基于 package-lock.json 冻结安装)...', 'cyan')
if (safeExec('npm ci --no-audit --no-fund')) {
installed = true
logSuccess('依赖安装完成 (npm ci)')
} else {
logWarning('npm ci 失败,准备回退到 npm install...')
}
} else {
logWarning('未检测到 package-lock.json,直接使用 npm install...')
}
// 兜底用 npm install
if (!installed) {
log('正在使用 npm install 安装...', 'cyan')
if (!safeExec('npm install --no-audit --no-fund')) {
throw new Error('依赖安装失败')
}
logSuccess('依赖安装完成 (npm install)')
}
}
// 2. 检查 Drizzle 配置
if (
!fileExists('drizzle.config.ts') ||
!fileExists('app/drizzle/schema.ts') ||
!fileExists('app/drizzle/db.ts')
) {
throw new Error('Drizzle 配置文件不完整')
}
// 2.1. 确保迁移目录存在
if (!fileExists('app/drizzle/migrations')) {
fs.mkdirSync('app/drizzle/migrations', { recursive: true })
}
// 3. 数据库同步
logStep('🗄️', '同步数据库...')
let dbSyncSuccess = false
if (process.env.DATABASE_URL) {
const nonInteractiveEnv = {
...process.env,
DRIZZLE_KIT_FORCE: 'true',
CI: 'true',
NODE_ENV: 'production'
}
if (safeExec('node scripts/db-sync.js', { env: nonInteractiveEnv })) {
logSuccess('数据库同步成功')
dbSyncSuccess = true
} else {
logWarning('数据库同步失败')
}
} else {
logWarning('未设置 DATABASE_URL,跳过数据库迁移')
}
// 4. 创建管理员账户
if (fileExists('scripts/create-admin.js') && dbSyncSuccess) {
logStep('👤', '检查管理员账户...')
// 优先 npm run create-admin,兜底直接用 node_modules/.bin/tsx 调用(用户禁用 pnpm)
if (!safeExec('npm run create-admin')) {
safeExec('./node_modules/.bin/tsx scripts/create-admin.js')
}
}
// 5. 构建应用
if (process.env.SKIP_BUILD === 'true') {
logStep('🔨', '跳过应用构建 (SKIP_BUILD=true)...')
} else {
logStep('🔨', '构建应用...')
const buildEnv = {
...process.env,
NODE_OPTIONS: process.env.NODE_OPTIONS || DEFAULT_NODE_OPTIONS
}
// 用 npm run build(= nuxt build),避免用 pnpm exec
if (!safeExec('npm run build', { env: buildEnv })) {
throw new Error('应用构建失败')
}
logSuccess('应用构建完成')
}
log('🎉 部署完成!', 'green')
} catch (error) {
logError(`部署失败: ${error.message}`)
process.exit(1)
}
}
// 运行部署
deploy().catch((error) => {
logError(`未预期的错误: ${error.message}`)
process.exit(1)
})