feat: align with upstream 1.5.9.9

This commit is contained in:
2026-09-12 18:21:11 +08:00
commit 34f0679117
805 changed files with 376275 additions and 0 deletions
+450
View File
@@ -0,0 +1,450 @@
#!/usr/bin/env node
import { spawn } from 'child_process'
import { config } from 'dotenv'
import path from 'path'
config({ path: path.resolve(process.cwd(), '.env'), quiet: true })
const BUILD_MEMORY_MB = 6144
const DEFAULT_NODE_OPTIONS = `--max-old-space-size=${BUILD_MEMORY_MB}`
const HIDDEN_VALUE = '[已隐藏]'
const SUPPORTED_AGGREGATE_LOGIN_TYPES = [
'qq',
'wx',
'alipay',
'sina',
'baidu',
'douyin',
'huawei',
'xiaomi',
'gitee',
'gitea',
'bilibili',
'kuaishou'
]
const SUPPORTED_ENV_VARIABLES = [
'NODE_ENV',
'NODE_OPTIONS',
'NITRO_PRESET',
'VERCEL',
'VERCEL_ENV',
'NETLIFY',
'PREBUILT',
'SKIP_INSTALL',
'SKIP_BUILD',
'CI',
'DATABASE_URL',
'JWT_SECRET',
'REDIS_URL',
'DEBUG_SQL',
'WEBAUTHN_RP_ID',
'WEBAUTHN_ORIGIN',
'GITHUB_CLIENT_ID',
'GITHUB_CLIENT_SECRET',
'CASDOOR_CLIENT_ID',
'CASDOOR_CLIENT_SECRET',
'CASDOOR_ENDPOINT',
'CASDOOR_ORGANIZATION_NAME',
'GOOGLE_CLIENT_ID',
'GOOGLE_CLIENT_SECRET',
'AGGREGATE_OAUTH_APP_ID',
'AGGREGATE_OAUTH_APP_KEY',
'AGGREGATE_OAUTH_LOGIN_TYPE',
'AGGREGATE_OAUTH_ENDPOINT',
'OAUTH_REDIRECT_URI',
'OAUTH_STATE_SECRET',
'NUXT_PUBLIC_HOST',
'NUXT_PUBLIC_SITE_TITLE',
'NUXT_PUBLIC_SITE_DESCRIPTION',
'NUXT_PUBLIC_SITE_LOGO',
'NUXT_PUBLIC_SEO_CONFIG',
'SENTRY_DSN',
'SENTRY_ENVIRONMENT',
'SENTRY_RELEASE',
'SENTRY_TRACES_SAMPLE_RATE',
'NUXT_PUBLIC_SENTRY_DSN',
'NUXT_PUBLIC_SENTRY_ENVIRONMENT',
'NUXT_PUBLIC_SENTRY_RELEASE',
'NUXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE',
'NUXT_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE',
'NUXT_PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE',
'VERCEL_GIT_COMMIT_SHA',
'COMMIT_REF'
]
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
yellow: '\x1b[33m',
cyan: '\x1b[36m',
dim: '\x1b[2m'
}
function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`)
}
function isProvided(value) {
return typeof value === 'string' && value.trim().length > 0
}
function normalizeBlankEnvironment() {
for (const name of SUPPORTED_ENV_VARIABLES) {
const value = process.env[name]
if (typeof value === 'string' && value.length > 0 && value.trim().length === 0) {
delete process.env[name]
}
}
}
function providedState(value) {
return isProvided(value) ? '已配置' : '未配置'
}
function strictBoolean(value) {
return value === 'true'
}
function numberInUnitInterval(value, fallback) {
if (!isProvided(value)) return { value: fallback, source: '默认值' }
const parsed = Number(value)
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
return { value: fallback, source: `无效值 ${JSON.stringify(value)},已回退` }
}
return { value: parsed, source: `来自 ${JSON.stringify(value)}` }
}
function displayValue(value, fallback = '未配置') {
return isProvided(value) ? JSON.stringify(value) : fallback
}
function hiddenValue(value) {
return isProvided(value) ? HIDDEN_VALUE : '未配置'
}
function resolveAggregateLoginTypeState(value) {
if (!isProvided(value)) return '使用 qq(默认值)'
let values
try {
const parsed = JSON.parse(value)
values = Array.isArray(parsed) ? parsed : [parsed]
} catch {
values = value.split(',')
}
const normalized = [
...new Set(
values
.filter((item) => typeof item === 'string')
.map((item) => item.trim().toLowerCase())
.filter((item) => SUPPORTED_AGGREGATE_LOGIN_TYPES.includes(item))
)
]
return normalized.length > 0
? '配置有效(具体值已隐藏)'
: '未识别到受支持的登录方式,导入时回退到 qq'
}
function printItem(name, rawState, result) {
log(` ${name}: ${rawState} -> ${result}`)
}
function printSensitiveItem(name, value, result) {
printItem(name, hiddenValue(value), result ?? providedState(value))
}
function resolveNitroPreset() {
if (process.env.VERCEL) return 'vercel(由 VERCEL 检测)'
if (process.env.NETLIFY) return 'netlify(由 NETLIFY 检测)'
return `${process.env.NITRO_PRESET || 'node-server'}${process.env.NITRO_PRESET ? '' : '(默认值)'}`
}
function resolveNodeOptions(value) {
const options = isProvided(value) ? value.trim() : ''
const hasMemoryLimit = /(?:^|\s)--max[-_]old[-_]space[-_]size(?:=|\s|$)/.test(options)
if (hasMemoryLimit) return options
return options ? `${options} ${DEFAULT_NODE_OPTIONS}` : DEFAULT_NODE_OPTIONS
}
function resolveSeoConfig() {
if (!isProvided(process.env.NUXT_PUBLIC_SEO_CONFIG)) {
return { state: '未配置', result: '使用分项配置或默认值' }
}
try {
const parsed = JSON.parse(process.env.NUXT_PUBLIC_SEO_CONFIG)
const fields = ['title', 'shortName', 'description', 'logo'].filter((field) =>
isProvided(parsed?.[field])
)
return {
state: '已配置',
result: fields.length > 0 ? `解析成功:${fields.join(', ')}` : '解析成功,但未提供有效字段'
}
} catch {
return { state: '已配置', result: '解析失败,将回退到分项配置或默认值' }
}
}
function printBuildEnvironment(rawNodeOptions) {
const backendTraces = numberInUnitInterval(process.env.SENTRY_TRACES_SAMPLE_RATE, 0.1)
const frontendTraces = numberInUnitInterval(
process.env.NUXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE || process.env.SENTRY_TRACES_SAMPLE_RATE,
0.1
)
const sessionReplay = numberInUnitInterval(
process.env.NUXT_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE,
0
)
const errorReplay = numberInUnitInterval(
process.env.NUXT_PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE,
0
)
const production = process.env.NODE_ENV === 'production'
const seoConfig = resolveSeoConfig()
log('\n🔎 构建环境变量诊断', 'cyan')
log('仅列出 VoiceHub 支持的变量;敏感值始终隐藏。', 'dim')
log('\n构建与部署:', 'cyan')
printItem('NODE_ENV', displayValue(process.env.NODE_ENV), process.env.NODE_ENV || '由 Nuxt 决定')
printItem('NODE_OPTIONS', displayValue(rawNodeOptions), process.env.NODE_OPTIONS)
printItem('NITRO_PRESET', displayValue(process.env.NITRO_PRESET), resolveNitroPreset())
printItem('VERCEL', displayValue(process.env.VERCEL), process.env.VERCEL ? '已检测' : '未检测')
printItem('VERCEL_ENV', displayValue(process.env.VERCEL_ENV), process.env.VERCEL_ENV || '未检测')
printItem('NETLIFY', displayValue(process.env.NETLIFY), process.env.NETLIFY ? '已检测' : '未检测')
printItem(
'PREBUILT',
displayValue(process.env.PREBUILT),
strictBoolean(process.env.PREBUILT) ? '启用' : '停用'
)
printItem(
'SKIP_INSTALL',
displayValue(process.env.SKIP_INSTALL),
strictBoolean(process.env.SKIP_INSTALL) ? '启用' : '停用'
)
printItem(
'SKIP_BUILD',
displayValue(process.env.SKIP_BUILD),
strictBoolean(process.env.SKIP_BUILD) ? '启用' : '停用'
)
printItem('CI', displayValue(process.env.CI), process.env.CI ? 'CI 环境' : '非 CI 环境')
log('\n服务能力:', 'cyan')
printSensitiveItem('DATABASE_URL', process.env.DATABASE_URL)
printSensitiveItem('JWT_SECRET', process.env.JWT_SECRET)
printSensitiveItem(
'REDIS_URL',
process.env.REDIS_URL,
isProvided(process.env.REDIS_URL) ? 'Redis 启用' : 'Redis 停用'
)
printItem(
'DEBUG_SQL',
displayValue(process.env.DEBUG_SQL),
strictBoolean(process.env.DEBUG_SQL) ? '启用' : '停用'
)
printItem(
'WEBAUTHN_RP_ID',
displayValue(process.env.WEBAUTHN_RP_ID),
providedState(process.env.WEBAUTHN_RP_ID)
)
printItem(
'WEBAUTHN_ORIGIN',
displayValue(process.env.WEBAUTHN_ORIGIN),
providedState(process.env.WEBAUTHN_ORIGIN)
)
log('\nOAuth 环境变量(仅用于后台导入):', 'cyan')
printItem(
'GitHub CLIENT_ID',
hiddenValue(process.env.GITHUB_CLIENT_ID),
isProvided(process.env.GITHUB_CLIENT_ID) ? 'OAuth 启用' : 'OAuth 停用'
)
printSensitiveItem('GitHub CLIENT_SECRET', process.env.GITHUB_CLIENT_SECRET)
printItem(
'Casdoor CLIENT_ID',
hiddenValue(process.env.CASDOOR_CLIENT_ID),
isProvided(process.env.CASDOOR_CLIENT_ID) ? 'OAuth 启用' : 'OAuth 停用'
)
printSensitiveItem('Casdoor CLIENT_SECRET', process.env.CASDOOR_CLIENT_SECRET)
printItem(
'Google CLIENT_ID',
hiddenValue(process.env.GOOGLE_CLIENT_ID),
isProvided(process.env.GOOGLE_CLIENT_ID) ? 'OAuth 启用' : 'OAuth 停用'
)
printSensitiveItem('Google CLIENT_SECRET', process.env.GOOGLE_CLIENT_SECRET)
printItem(
'聚合登陆 APP_ID',
hiddenValue(process.env.AGGREGATE_OAUTH_APP_ID),
isProvided(process.env.AGGREGATE_OAUTH_APP_ID) ? 'OAuth 启用' : 'OAuth 停用'
)
printSensitiveItem('聚合登陆 APP_KEY', process.env.AGGREGATE_OAUTH_APP_KEY)
printSensitiveItem(
'AGGREGATE_OAUTH_LOGIN_TYPE',
process.env.AGGREGATE_OAUTH_LOGIN_TYPE,
resolveAggregateLoginTypeState(process.env.AGGREGATE_OAUTH_LOGIN_TYPE)
)
printSensitiveItem(
'AGGREGATE_OAUTH_ENDPOINT',
process.env.AGGREGATE_OAUTH_ENDPOINT,
isProvided(process.env.AGGREGATE_OAUTH_ENDPOINT)
? '使用自定义接口地址(值已隐藏)'
: '使用默认接口地址'
)
printItem(
'CASDOOR_ENDPOINT',
displayValue(process.env.CASDOOR_ENDPOINT),
providedState(process.env.CASDOOR_ENDPOINT)
)
printItem(
'CASDOOR_ORGANIZATION_NAME',
displayValue(process.env.CASDOOR_ORGANIZATION_NAME),
process.env.CASDOOR_ORGANIZATION_NAME || 'built-in(默认值)'
)
printSensitiveItem('OAUTH_REDIRECT_URI', process.env.OAUTH_REDIRECT_URI)
printSensitiveItem('OAUTH_STATE_SECRET', process.env.OAUTH_STATE_SECRET)
log('\n站点公开配置:', 'cyan')
printItem(
'NUXT_PUBLIC_HOST',
displayValue(process.env.NUXT_PUBLIC_HOST),
providedState(process.env.NUXT_PUBLIC_HOST)
)
printItem(
'NUXT_PUBLIC_SITE_TITLE',
displayValue(process.env.NUXT_PUBLIC_SITE_TITLE),
providedState(process.env.NUXT_PUBLIC_SITE_TITLE)
)
printItem(
'NUXT_PUBLIC_SITE_DESCRIPTION',
displayValue(process.env.NUXT_PUBLIC_SITE_DESCRIPTION),
providedState(process.env.NUXT_PUBLIC_SITE_DESCRIPTION)
)
printItem(
'NUXT_PUBLIC_SITE_LOGO',
displayValue(process.env.NUXT_PUBLIC_SITE_LOGO),
providedState(process.env.NUXT_PUBLIC_SITE_LOGO)
)
printItem('NUXT_PUBLIC_SEO_CONFIG', seoConfig.state, seoConfig.result)
log('\nSentry:', 'cyan')
printItem(
'运行状态',
displayValue(process.env.NODE_ENV),
production ? '启用(仍受后台遥测开关控制)' : '停用'
)
printSensitiveItem(
'SENTRY_DSN',
process.env.SENTRY_DSN,
isProvided(process.env.SENTRY_DSN) ? '使用自定义后端 DSN' : '使用内置后端 DSN'
)
printSensitiveItem(
'NUXT_PUBLIC_SENTRY_DSN',
process.env.NUXT_PUBLIC_SENTRY_DSN,
isProvided(process.env.NUXT_PUBLIC_SENTRY_DSN) ? '使用自定义前端 DSN' : '使用内置前端 DSN'
)
printItem('SENTRY_TRACES_SAMPLE_RATE', backendTraces.source, backendTraces.value)
printItem('NUXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE', frontendTraces.source, frontendTraces.value)
printItem(
'NUXT_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE',
sessionReplay.source,
sessionReplay.value
)
printItem(
'NUXT_PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE',
errorReplay.source,
errorReplay.value
)
printItem(
'SENTRY_ENVIRONMENT',
displayValue(process.env.SENTRY_ENVIRONMENT),
process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV || 'development'
)
printItem(
'NUXT_PUBLIC_SENTRY_ENVIRONMENT',
displayValue(process.env.NUXT_PUBLIC_SENTRY_ENVIRONMENT),
process.env.NUXT_PUBLIC_SENTRY_ENVIRONMENT ||
process.env.SENTRY_ENVIRONMENT ||
process.env.NODE_ENV ||
'development'
)
printItem(
'SENTRY_RELEASE',
providedState(process.env.SENTRY_RELEASE),
process.env.SENTRY_RELEASE ||
process.env.VERCEL_GIT_COMMIT_SHA ||
process.env.COMMIT_REF ||
'未设置'
)
printItem(
'NUXT_PUBLIC_SENTRY_RELEASE',
providedState(process.env.NUXT_PUBLIC_SENTRY_RELEASE),
process.env.NUXT_PUBLIC_SENTRY_RELEASE ||
process.env.SENTRY_RELEASE ||
process.env.VERCEL_GIT_COMMIT_SHA ||
process.env.COMMIT_REF ||
'未设置'
)
log('')
}
function runNuxtBuild() {
return new Promise((resolve) => {
let settled = false
const finish = (success) => {
if (settled) return
settled = true
resolve(success)
}
let child
try {
child = spawn(process.execPath, ['./node_modules/nuxt/bin/nuxt.mjs', 'build'], {
stdio: 'inherit',
env: process.env
})
} catch {
finish(false)
return
}
const forwardSignal = (signal) => {
if (child.exitCode === null && child.signalCode === null) child.kill(signal)
}
const onSigterm = () => forwardSignal('SIGTERM')
const onSigint = () => forwardSignal('SIGINT')
const cleanup = () => {
process.off('SIGTERM', onSigterm)
process.off('SIGINT', onSigint)
}
process.once('SIGTERM', onSigterm)
process.once('SIGINT', onSigint)
child.once('error', () => {
cleanup()
finish(false)
})
child.once('exit', (code) => {
cleanup()
finish(code === 0)
})
})
}
async function build() {
const rawNodeOptions = process.env.NODE_OPTIONS
normalizeBlankEnvironment()
process.env.NODE_OPTIONS = resolveNodeOptions(rawNodeOptions)
printBuildEnvironment(rawNodeOptions)
if (process.argv.includes('--diagnostics-only')) return
log('🔨 开始执行 Nuxt 构建...', 'cyan')
if (!(await runNuxtBuild())) throw new Error('Nuxt 构建失败')
log('✅ Nuxt 构建完成', 'green')
}
build().catch((error) => {
log(`❌ ${error.message}`, 'yellow')
process.exit(1)
})