Files
2026-09-03 08:38:17 +08:00

73 lines
4.0 KiB
JavaScript

import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { loadEnv } from "vite";
const projectRoot = fileURLToPath(new URL("../", import.meta.url));
function validOrigin(value, protocol) {
try {
const url = new URL(value);
const host = url.hostname.toLowerCase();
const loopback = host === 'localhost' || host.endsWith('.localhost') || /^127\./.test(host) || host === '[::1]' || host === '0.0.0.0';
return url.protocol === protocol && !!host && !loopback && !url.username && !url.password && !url.search && !url.hash && url.pathname === '/';
} catch {
return false;
}
}
export function nativeConfigFailures(manifest, env) {
const failures = [];
// DCloud 分配的 AppID 包括 7 位后缀,不能固定要求 8 位。
// 此处仅检查格式;AppID 的归属由 HBuilderX/DCloud 打包服务校验。
if (!/^__UNI__[0-9A-F]{7,8}$/i.test(String(manifest.appid || ""))) {
failures.push("src/manifest.json 的 appid 必须填写 DCloud 分配的 AppID,请在 HBuilderX 基础配置中获取");
}
if (!manifest["app-plus"]?.distribute?.ios?.privacyDescription?.NSCameraUsageDescription) {
failures.push("缺少 iOS 相机隐私用途说明");
}
for (const moduleName of ["Camera", "Geolocation", "OAuth", "Payment", "Push", "Record"]) {
if (!(moduleName in (manifest["app-plus"]?.modules || {}))) failures.push(`缺少原生模块:${moduleName}`);
}
if (!validOrigin(env.VITE_API_ORIGIN, "https:")) {
failures.push("App 必须通过 VITE_API_ORIGIN 配置可从手机访问的 HTTPS 接口域名,不能使用 localhost/127.0.0.1,也不能带 /api 路径");
}
if (!validOrigin(env.VITE_WS_ORIGIN, "wss:")) {
failures.push("App 必须通过 VITE_WS_ORIGIN 配置可从手机访问的 WSS 消息域名,不能使用本机回环地址或带 /ws 路径");
}
const oauth = manifest['app-plus']?.distribute?.sdkConfigs?.oauth || {};
if (oauth.weixin) {
if (!/^wx[0-9a-z]+$/i.test(oauth.weixin.appid || '')) failures.push('微信登录 SDK 必须填写移动应用 AppID');
if (oauth.weixin.appsecret) failures.push('禁止把微信 AppSecret 打入客户端,请只在后台 App 微信密钥中配置');
}
if (oauth.qq && !/^\d+$/.test(oauth.qq.appid || '')) failures.push('QQ 登录 SDK 必须填写移动应用 AppID');
if (oauth.google?.clientid && !String(oauth.google.clientid).endsWith('.apps.googleusercontent.com')) {
failures.push('Google 登录 SDK 的 iOS Client ID 格式不正确,请参见 docs/app-oauth.md');
}
return failures;
}
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
const manifestPath = resolve(projectRoot, "src/manifest.json");
const source = readFileSync(manifestPath, "utf8").replace(/\/\*[\s\S]*?\*\//g, "");
const modeIndex = process.argv.indexOf('--mode');
const mode = modeIndex < 0 ? 'production' : process.argv[modeIndex + 1];
if (!mode || !/^[a-z][a-z0-9_-]*$/i.test(mode) || mode === 'local') {
console.error('请通过 --mode development 或 --mode production 指定构建环境');
process.exit(1);
}
// Check the same mode that the compiler uses; checking production before a
// development build previously missed its absent API and WebSocket origins.
const env = { ...loadEnv(mode, projectRoot, "VITE_"), ...process.env };
const failures = nativeConfigFailures(JSON.parse(source), env);
if (failures.length) {
console.error(`原生打包配置未完成:\n- ${failures.join("\n- ")}`);
process.exit(1);
}
console.log("原生打包基础配置校验通过(证书和 AppID 归属仍需在 HBuilderX 中校验)");
console.log(`[App network] mode=${mode} API=${env.VITE_API_ORIGIN} WS=${env.VITE_WS_ORIGIN}`);
if (!Object.keys(JSON.parse(source)['app-plus']?.distribute?.sdkConfigs?.oauth || {}).length) {
console.warn('尚未打包微信/QQ/Google SDK:这些原生登录入口会隐藏。请按 docs/app-oauth.md 配置;GitHub 使用系统浏览器。');
}
}