chore(deploy): add same-host staging stack next to production

dev deploys to /opt/dukang-staging (819x, *-test domains); main deploys to production. Staging uses full Mock with shared WeChat app ids.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-31 11:37:18 +08:00
parent 8d6c0f9ac0
commit 6a23e79f4c
20 changed files with 793 additions and 119 deletions
+25 -8
View File
@@ -5,19 +5,33 @@ import { resolve } from 'path';
/**
* 环境变量加载策略(后加载的文件覆盖先前的同名键):
*
* - 本地开发:仅 `.env` + `.env.local`
* `.env.development` 仅作参考模板,不自动加载
* - local`.env` + `.env.local`
* - staging`.env.staging` + `.env.staging.local`(同机测试栈,APP_ENV=staging
* - production`.env.production` + `.env.production.local`
*
* - 服务器(NODE_ENV=production):仅 `.env.production` + `.env.production.local`
* 不使用 `.env`,避免与本地开发配置混淆
* 判定:优先 `APP_ENV`;未设置时 `NODE_ENV=production` → production,否则 local
* staging 在 PM2 中仍设 `NODE_ENV=production`(构建产物),靠 `APP_ENV=staging` 区分。
*/
const apiRoot = resolve(__dirname, '..');
const nodeEnv = process.env.NODE_ENV ?? 'development';
const isProduction = nodeEnv === 'production';
const layers = isProduction
? [resolve(apiRoot, '.env.production'), resolve(apiRoot, '.env.production.local')]
: [resolve(apiRoot, '.env'), resolve(apiRoot, '.env.local')];
function resolveAppEnv(): 'local' | 'staging' | 'production' {
const raw = (process.env.APP_ENV ?? '').trim().toLowerCase();
if (raw === 'staging' || raw === 'stage' || raw === 'test') return 'staging';
if (raw === 'production' || raw === 'prod') return 'production';
if (raw === 'local' || raw === 'development' || raw === 'dev') return 'local';
if (nodeEnv === 'production') return 'production';
return 'local';
}
const appEnv = resolveAppEnv();
const layers =
appEnv === 'staging'
? [resolve(apiRoot, '.env.staging'), resolve(apiRoot, '.env.staging.local')]
: appEnv === 'production'
? [resolve(apiRoot, '.env.production'), resolve(apiRoot, '.env.production.local')]
: [resolve(apiRoot, '.env'), resolve(apiRoot, '.env.local')];
for (const file of layers) {
if (existsSync(file)) {
@@ -28,3 +42,6 @@ for (const file of layers) {
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = nodeEnv;
}
if (!process.env.APP_ENV) {
process.env.APP_ENV = appEnv;
}