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:
@@ -0,0 +1,123 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const apiRoot = path.join(__dirname, '..');
|
||||
const prod = fs.readFileSync(path.join(apiRoot, '.env.production'), 'utf8');
|
||||
const map = new Map();
|
||||
for (const line of prod.split(/\r?\n/)) {
|
||||
const m = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
||||
if (m) map.set(m[1], m[2]);
|
||||
}
|
||||
|
||||
function unq(v) {
|
||||
if (!v) return '';
|
||||
if (
|
||||
(v.startsWith('"') && v.endsWith('"')) ||
|
||||
(v.startsWith("'") && v.endsWith("'"))
|
||||
) {
|
||||
return v.slice(1, -1);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
function q(v) {
|
||||
return JSON.stringify(String(v ?? ''));
|
||||
}
|
||||
|
||||
const db = unq(map.get('DATABASE_URL') || '');
|
||||
if (!db.includes('dukang_prod')) {
|
||||
console.error('prod DATABASE_URL unexpected, refuse to rewrite');
|
||||
process.exit(1);
|
||||
}
|
||||
const stagingDb = db.replace('/dukang_prod', '/dukang_staging');
|
||||
const jwt = `${unq(map.get('JWT_SECRET') || 'prod')}-staging`;
|
||||
|
||||
const fixed = {
|
||||
APP_ENV: 'staging',
|
||||
NODE_ENV: 'production',
|
||||
DATABASE_URL: stagingDb,
|
||||
REDIS_URL: 'redis://localhost:6379/1',
|
||||
JWT_SECRET: jwt,
|
||||
JWT_EXPIRES_IN: unq(map.get('JWT_EXPIRES_IN') || '7d'),
|
||||
PORT: '8190',
|
||||
MOCK_SMS: 'true',
|
||||
MOCK_PAY: 'true',
|
||||
MOCK_DELIVERY_AUTO: 'true',
|
||||
MOCK_WECHAT: 'true',
|
||||
AUTO_APPROVE_STORE: 'true',
|
||||
TRUST_PROXY: 'true',
|
||||
USER_H5_URL: 'https://user-test.dukanghaoke.com/user',
|
||||
WX_PAY_NOTIFY_URL: 'https://api-test.dukanghaoke.com/api/v1/callbacks/wechat/pay',
|
||||
OSS_UPLOAD_PREFIX: 'staging/uploads',
|
||||
WECOM_AIBOT_ENABLED: 'false',
|
||||
};
|
||||
|
||||
const preferFromProd = [
|
||||
'ALIYUN_SMS_SIGN_NAME',
|
||||
'ALIYUN_SMS_TEMPLATE_CODE',
|
||||
'ALIYUN_SMS_TEMPLATE_CONFIRM_REDEEM',
|
||||
'ALIYUN_SMS_TEMPLATE_PROXY_ORDER',
|
||||
'ALIYUN_SMS_ACCESS_KEY_ID',
|
||||
'ALIYUN_SMS_ACCESS_KEY_SECRET',
|
||||
'WX_APP_ID',
|
||||
'WX_APP_SECRET',
|
||||
'WX_MINI_APP_ID',
|
||||
'WX_MINI_APP_SECRET',
|
||||
'WX_MINI_PROMO_PAGE',
|
||||
'WX_MCH_ID',
|
||||
'WX_MCH_SERIAL_NO',
|
||||
'WX_MCH_PRIVATE_KEY',
|
||||
'WX_API_V3_KEY',
|
||||
'WX_PLATFORM_CERT',
|
||||
'WX_MINI_MSG_TOKEN',
|
||||
'WX_MINI_MSG_AES_KEY',
|
||||
'OSS_ACCESS_KEY_ID',
|
||||
'OSS_ACCESS_KEY_SECRET',
|
||||
'OSS_BUCKET',
|
||||
'OSS_REGION',
|
||||
'OSS_CDN_BASE',
|
||||
'OSS_ENDPOINT',
|
||||
'OSS_UPLOAD_EXPIRE_SECONDS',
|
||||
'OSS_MAX_UPLOAD_BYTES',
|
||||
'COURIER_PROVIDER',
|
||||
'XIAOFEIXIA_API_URL',
|
||||
'XIAOFEIXIA_MCH_ID',
|
||||
'XIAOFEIXIA_API_KEY',
|
||||
'XIAOFEIXIA_SIGN_TYPE',
|
||||
'SHIP_FROM_NAME',
|
||||
'SHIP_FROM_MOBILE',
|
||||
'SHIP_FROM_ADDRESS',
|
||||
'SHIP_FROM_ADDRESS_DETAIL',
|
||||
'SHIP_FROM_LNG',
|
||||
'SHIP_FROM_LAT',
|
||||
'TENCENT_LBS_KEY',
|
||||
'TENCENT_LBS_SECRET_KEY',
|
||||
'DEPLOY_WEBHOOK_URL',
|
||||
'DEPLOY_WEBHOOK_SECRET',
|
||||
];
|
||||
|
||||
const out = [
|
||||
'# Generated from .env.production for staging (full Mock + *-test domains)',
|
||||
'# Do not commit. Sync: bash deploy/sync-api-env.sh staging',
|
||||
'',
|
||||
];
|
||||
|
||||
for (const [k, v] of Object.entries(fixed)) {
|
||||
out.push(`${k}=${q(v)}`);
|
||||
}
|
||||
out.push('');
|
||||
for (const k of preferFromProd) {
|
||||
if (map.has(k) && !(k in fixed)) {
|
||||
out.push(`${k}=${q(unq(map.get(k)))}`);
|
||||
}
|
||||
}
|
||||
|
||||
const skip = new Set([...Object.keys(fixed), ...preferFromProd]);
|
||||
for (const [k, raw] of map.entries()) {
|
||||
if (skip.has(k)) continue;
|
||||
out.push(`${k}=${q(unq(raw))}`);
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(apiRoot, '.env.staging'), `${out.join('\n')}\n`);
|
||||
const host = (stagingDb.match(/@([^/:]+)/) || [])[1] || '?';
|
||||
console.log(`ok db=dukang_staging host=${host} redis=/1 mocks=on`);
|
||||
@@ -4,7 +4,8 @@ const { existsSync } = require('fs');
|
||||
const { resolve } = require('path');
|
||||
|
||||
const apiRoot = resolve(__dirname, '..');
|
||||
const envFile = process.env.DUKANG_ENV_FILE ?? '.env.production';
|
||||
const envFile = process.env.DUKANG_ENV_FILE
|
||||
?? (process.env.APP_ENV === 'staging' ? '.env.staging' : '.env.production');
|
||||
const envPath = resolve(apiRoot, envFile);
|
||||
|
||||
if (!existsSync(envPath)) {
|
||||
|
||||
Reference in New Issue
Block a user