forked from wangziqi/gongxue-base
174 lines
6.3 KiB
JavaScript
174 lines
6.3 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import process from 'node:process';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const scriptPath = fileURLToPath(import.meta.url);
|
|
const repoRoot = path.resolve(path.dirname(scriptPath), '..');
|
|
const taroRoot = path.join(repoRoot, 'apps', 'taro');
|
|
const distProjectConfigPath = path.join(taroRoot, 'dist', 'weapp-student', 'project.config.json');
|
|
|
|
const placeholderAppIds = new Set([
|
|
'wx0000000000000000',
|
|
'wx0123456789abcdef',
|
|
'wx1234567890abcdef',
|
|
'wxabcdef0123456789',
|
|
]);
|
|
|
|
const placeholderTenantCodes = new Set([
|
|
'changeme',
|
|
'demo',
|
|
'example',
|
|
'placeholder',
|
|
'production-tenant',
|
|
'replace-with-tenant-code',
|
|
'smoke',
|
|
'tenant-production',
|
|
'test',
|
|
]);
|
|
|
|
function stringValue(value) {
|
|
return String(value || '').trim();
|
|
}
|
|
|
|
function normalizedHostname(value) {
|
|
return stringValue(value).toLowerCase().replace(/^\[|\]$/g, '').replace(/\.$/, '');
|
|
}
|
|
|
|
export function isLoopbackHostname(value) {
|
|
const hostname = normalizedHostname(value);
|
|
return hostname === 'localhost'
|
|
|| hostname.endsWith('.localhost')
|
|
|| hostname === '::1'
|
|
|| hostname === '0.0.0.0'
|
|
|| /^127(?:\.\d{1,3}){3}$/.test(hostname);
|
|
}
|
|
|
|
export function isPlaceholderHostname(value) {
|
|
const hostname = normalizedHostname(value);
|
|
return ['example', 'test', 'local'].some(suffix => hostname === suffix || hostname.endsWith(`.${suffix}`));
|
|
}
|
|
|
|
export function validateProductionApiBaseUrl(value) {
|
|
const apiBaseUrl = stringValue(value);
|
|
if (!apiBaseUrl) throw new Error('TARO_APP_API_BASE_URL is required for a production WeApp build');
|
|
|
|
let parsed;
|
|
try {
|
|
parsed = new URL(apiBaseUrl);
|
|
} catch {
|
|
throw new Error('TARO_APP_API_BASE_URL must be an absolute HTTPS URL for a production WeApp build');
|
|
}
|
|
if (parsed.protocol !== 'https:' || !parsed.hostname) {
|
|
throw new Error('TARO_APP_API_BASE_URL must use HTTPS for a production WeApp build');
|
|
}
|
|
if (isLoopbackHostname(parsed.hostname)) {
|
|
throw new Error('TARO_APP_API_BASE_URL must not use localhost or a loopback address for a production WeApp build');
|
|
}
|
|
if (isPlaceholderHostname(parsed.hostname)) {
|
|
throw new Error('TARO_APP_API_BASE_URL must not use a .example, .test, or .local host for a production WeApp build');
|
|
}
|
|
return apiBaseUrl.replace(/\/+$/, '');
|
|
}
|
|
|
|
export function validateProductionWechatAppId(value) {
|
|
const appId = stringValue(value);
|
|
if (!appId) throw new Error('WECHAT_MINIAPP_APP_ID is required for a production WeApp build');
|
|
if (!/^wx[0-9a-f]{16}$/i.test(appId)) {
|
|
throw new Error('WECHAT_MINIAPP_APP_ID must be a real 18-character WeChat AppID');
|
|
}
|
|
const normalized = appId.toLowerCase();
|
|
if (placeholderAppIds.has(normalized) || /^wx([0-9a-f])\1{15}$/i.test(normalized)) {
|
|
throw new Error('WECHAT_MINIAPP_APP_ID must not use a placeholder AppID');
|
|
}
|
|
return appId;
|
|
}
|
|
|
|
export function validateTenantCodeFormat(value) {
|
|
const tenantCode = stringValue(value);
|
|
if (!tenantCode) throw new Error('TARO_APP_TENANT_CODE is required when TARO_APP_WEAPP_TENANT_MODE=fixed');
|
|
if (!/^[A-Za-z0-9._-]{2,64}$/.test(tenantCode)) throw new Error('TARO_APP_TENANT_CODE has an invalid format');
|
|
return tenantCode;
|
|
}
|
|
|
|
export function validateProductionTenantCode(value) {
|
|
const tenantCode = validateTenantCodeFormat(value);
|
|
const normalized = tenantCode.toLowerCase();
|
|
if (placeholderTenantCodes.has(normalized)
|
|
|| /^(?:tenant[-_])?(?:example|test|demo|smoke|production|placeholder)(?:[-_]tenant)?$/.test(normalized)) {
|
|
throw new Error('TARO_APP_TENANT_CODE must not use a placeholder tenant code');
|
|
}
|
|
return tenantCode;
|
|
}
|
|
|
|
export function resolveWeappTenantMode(env = {}, production = false) {
|
|
const configuredMode = stringValue(env.TARO_APP_WEAPP_TENANT_MODE).toLowerCase();
|
|
if (configuredMode && configuredMode !== 'fixed' && configuredMode !== 'launch') {
|
|
throw new Error('TARO_APP_WEAPP_TENANT_MODE must be fixed or launch');
|
|
}
|
|
if (configuredMode) return configuredMode;
|
|
return production || stringValue(env.TARO_APP_TENANT_CODE) ? 'fixed' : 'launch';
|
|
}
|
|
|
|
export function resolveWeappBuildConfig(env = {}, { production = false } = {}) {
|
|
const tenantMode = resolveWeappTenantMode(env, production);
|
|
const configuredTenantCode = stringValue(env.TARO_APP_TENANT_CODE);
|
|
let tenantCode = '';
|
|
|
|
if (tenantMode === 'fixed') {
|
|
tenantCode = production
|
|
? validateProductionTenantCode(configuredTenantCode)
|
|
: validateTenantCodeFormat(configuredTenantCode);
|
|
} else if (production && configuredTenantCode) {
|
|
throw new Error('TARO_APP_TENANT_CODE must be empty when TARO_APP_WEAPP_TENANT_MODE=launch');
|
|
}
|
|
|
|
return {
|
|
production,
|
|
tenantMode,
|
|
tenantCode,
|
|
apiBaseUrl: production
|
|
? validateProductionApiBaseUrl(env.TARO_APP_API_BASE_URL)
|
|
: stringValue(env.TARO_APP_API_BASE_URL),
|
|
appId: production ? validateProductionWechatAppId(env.WECHAT_MINIAPP_APP_ID) : '',
|
|
};
|
|
}
|
|
|
|
export function runWeappBuild({ argv = process.argv.slice(2), env = process.env } = {}) {
|
|
const production = argv.includes('--production');
|
|
const buildConfig = resolveWeappBuildConfig(env, { production });
|
|
const result = spawnSync(process.execPath, [path.join(repoRoot, 'node_modules', '@tarojs', 'cli', 'bin', 'taro'), 'build', '--type', 'weapp'], {
|
|
cwd: taroRoot,
|
|
env: {
|
|
...env,
|
|
TARO_ENV: 'weapp',
|
|
TARO_APP_PORTAL: 'student',
|
|
TARO_APP_RELEASE_MODE: production ? 'production' : 'preview',
|
|
TARO_APP_API_BASE_URL: buildConfig.apiBaseUrl,
|
|
TARO_APP_WEAPP_TENANT_MODE: buildConfig.tenantMode,
|
|
TARO_APP_TENANT_CODE: buildConfig.tenantCode,
|
|
},
|
|
stdio: 'inherit',
|
|
});
|
|
if (result.error) throw result.error;
|
|
if (result.status !== 0) return result.status || 1;
|
|
|
|
if (production) {
|
|
const projectConfig = JSON.parse(fs.readFileSync(distProjectConfigPath, 'utf8'));
|
|
projectConfig.appid = buildConfig.appId;
|
|
projectConfig.setting = { ...(projectConfig.setting || {}), urlCheck: true };
|
|
fs.writeFileSync(distProjectConfigPath, `${JSON.stringify(projectConfig, null, 2)}\n`, 'utf8');
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (process.argv[1] && path.resolve(process.argv[1]) === scriptPath) {
|
|
try {
|
|
process.exitCode = runWeappBuild();
|
|
} catch (error) {
|
|
console.error(error instanceof Error ? error.message : String(error));
|
|
process.exitCode = 1;
|
|
}
|
|
}
|