01394ed238
- 新增 scripts/check-generated-bindings.mjs 并接入 lint 与 ai-game-creator-shell:check:rust:crates:重跑 export_bindings 前后逐字节比对共享契约绑定。 - check:native-shells 的 AGC 边界改为负向门禁:渲染源码不得出现 fetch(/XHR/EventSource/sendBeacon/WebSocket/plugin-http,客户端 capability 不得授予 http:default。 - check-pingora-gateway-smoke 在 Windows 上找 pingora-gateway.exe,并修掉「API 并发保护」先关客户端连接再读响应的竞态。 - mobile-shell 三个 smoke 改成固定命令串启动 npm(绕开 npm.cmd EINVAL 与 DEP0190),desktop-shell 的 stage-release-binary 改用 fileURLToPath 修掉 F:\F:\… 路径拼接。 - vitest.config.ts 白名单补回现役根用例;三份 nginx 模板的 SPA allowlist 补回 /components 与 /design-system。
193 lines
5.6 KiB
JavaScript
193 lines
5.6 KiB
JavaScript
import { spawnSync } from 'node:child_process';
|
||
import fs from 'node:fs';
|
||
|
||
const shellRoot = new URL('../', import.meta.url);
|
||
const easConfigPath = new URL('eas.json', shellRoot);
|
||
const packagePath = new URL('package.json', shellRoot);
|
||
|
||
const easConfig = JSON.parse(fs.readFileSync(easConfigPath, 'utf8'));
|
||
const packageConfig = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
||
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||
const androidBuildOutputPath =
|
||
'../../build/native/mobile/genarrative-mobile-android.apk';
|
||
const iosSimulatorBuildOutputPath =
|
||
'../../build/native/mobile/genarrative-mobile-ios-simulator.tar.gz';
|
||
|
||
function assertObject(value, label) {
|
||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||
throw new Error(`${label} must be an object`);
|
||
}
|
||
}
|
||
|
||
function assertNoKeys(value, blockedKeys, label) {
|
||
for (const key of blockedKeys) {
|
||
if (key in value) {
|
||
throw new Error(`${label} must not configure ${key}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
assertObject(easConfig.cli, 'EAS cli config');
|
||
assertObject(easConfig.build, 'EAS build config');
|
||
|
||
if (easConfig.cli.version !== '>= 20.3.0') {
|
||
throw new Error(
|
||
'mobile shell EAS CLI version gate must stay pinned to the checked devDependency floor',
|
||
);
|
||
}
|
||
|
||
if (packageConfig.devDependencies?.['eas-cli'] !== '^20.3.0') {
|
||
throw new Error(
|
||
'mobile shell package must declare the checked EAS CLI devDependency',
|
||
);
|
||
}
|
||
|
||
// 同 check-expo-config.mjs:Windows 上启动 npm.cmd 必须有 shell,这里改用固定命令串,
|
||
// 既避开 EINVAL,也避开 Node 对「shell + args 数组」的 DEP0190 警告。
|
||
const easVersionResult = spawnSync(`${npmCommand} exec eas -- --version`, {
|
||
cwd: shellRoot,
|
||
encoding: 'utf8',
|
||
shell: true,
|
||
});
|
||
|
||
if (easVersionResult.error) {
|
||
throw new Error(
|
||
`failed to start local EAS CLI version check: ${easVersionResult.error.message}`,
|
||
);
|
||
}
|
||
|
||
if (easVersionResult.signal) {
|
||
throw new Error(
|
||
`local EAS CLI version check was terminated by signal ${easVersionResult.signal}`,
|
||
);
|
||
}
|
||
|
||
if ((easVersionResult.status ?? 0) !== 0) {
|
||
process.stdout.write(easVersionResult.stdout ?? '');
|
||
process.stderr.write(easVersionResult.stderr ?? '');
|
||
process.exit(easVersionResult.status ?? 1);
|
||
}
|
||
|
||
if (!(easVersionResult.stdout ?? '').trim().startsWith('eas-cli/20.3.0 ')) {
|
||
throw new Error('mobile shell local EAS CLI version drifted');
|
||
}
|
||
|
||
if (easConfig.cli.appVersionSource !== 'local') {
|
||
throw new Error(
|
||
'mobile shell EAS builds must use local app.json version fields',
|
||
);
|
||
}
|
||
|
||
for (const scriptName of ['build:android', 'build:ios', 'build-config:smoke']) {
|
||
if (typeof packageConfig.scripts?.[scriptName] !== 'string') {
|
||
throw new Error(`mobile shell package missing ${scriptName} script`);
|
||
}
|
||
}
|
||
|
||
if (
|
||
packageConfig.scripts['build:android'] !==
|
||
`eas build --local --profile production --platform android --output ${androidBuildOutputPath}`
|
||
) {
|
||
throw new Error('mobile shell Android package build script drifted');
|
||
}
|
||
|
||
if (
|
||
packageConfig.scripts['build:ios'] !==
|
||
`eas build --local --profile production-simulator --platform ios --output ${iosSimulatorBuildOutputPath}`
|
||
) {
|
||
throw new Error('mobile shell iOS package build script drifted');
|
||
}
|
||
|
||
for (const [label, outputPath, extension] of [
|
||
['Android', androidBuildOutputPath, '.apk'],
|
||
['iOS simulator', iosSimulatorBuildOutputPath, '.tar.gz'],
|
||
]) {
|
||
if (!outputPath.startsWith('../../build/native/mobile/')) {
|
||
throw new Error(
|
||
`mobile shell ${label} local build output must stay under root build/native/mobile`,
|
||
);
|
||
}
|
||
if (!outputPath.endsWith(extension)) {
|
||
throw new Error(
|
||
`mobile shell ${label} local build output must end with ${extension}`,
|
||
);
|
||
}
|
||
}
|
||
|
||
if (
|
||
packageConfig.scripts['build-config:smoke'] !==
|
||
'node scripts/check-eas-build-config.mjs'
|
||
) {
|
||
throw new Error('mobile shell EAS config smoke script drifted');
|
||
}
|
||
|
||
const production = easConfig.build.production;
|
||
const productionSimulator = easConfig.build['production-simulator'];
|
||
assertObject(production, 'EAS Android production profile');
|
||
assertObject(productionSimulator, 'EAS iOS simulator production profile');
|
||
|
||
if (
|
||
production.distribution !== 'internal' ||
|
||
production.channel !== 'production'
|
||
) {
|
||
throw new Error(
|
||
'mobile shell Android production profile must build an internal production package',
|
||
);
|
||
}
|
||
|
||
if (production.android?.buildType !== 'apk') {
|
||
throw new Error(
|
||
'mobile shell Android production profile must produce an installable APK',
|
||
);
|
||
}
|
||
|
||
if (production.env?.EXPO_NO_DOTENV !== '1') {
|
||
throw new Error(
|
||
'mobile shell Android production profile must ignore local dotenv files',
|
||
);
|
||
}
|
||
|
||
if (
|
||
productionSimulator.distribution !== 'internal' ||
|
||
productionSimulator.channel !== 'production'
|
||
) {
|
||
throw new Error(
|
||
'mobile shell iOS simulator production profile must stay internal production',
|
||
);
|
||
}
|
||
|
||
if (productionSimulator.ios?.simulator !== true) {
|
||
throw new Error(
|
||
'mobile shell iOS production smoke profile must target simulator builds until signing is configured',
|
||
);
|
||
}
|
||
|
||
if (productionSimulator.env?.EXPO_NO_DOTENV !== '1') {
|
||
throw new Error(
|
||
'mobile shell iOS simulator production profile must ignore local dotenv files',
|
||
);
|
||
}
|
||
|
||
for (const [profileName, profile] of Object.entries(easConfig.build)) {
|
||
assertObject(profile, `EAS ${profileName} profile`);
|
||
assertNoKeys(
|
||
profile,
|
||
[
|
||
'credentialsSource',
|
||
'autoIncrement',
|
||
'submit',
|
||
'runtimeVersion',
|
||
'releaseChannel',
|
||
],
|
||
`EAS ${profileName} profile`,
|
||
);
|
||
}
|
||
|
||
if ('submit' in easConfig) {
|
||
throw new Error(
|
||
'mobile shell EAS config must not include store submit profiles yet',
|
||
);
|
||
}
|
||
|
||
console.log('[mobile-shell:eas-build-config] OK');
|