Files
Genarrative/apps/mobile-shell/scripts/check-eas-build-config.mjs
T
kdletters 387a1c26e3 完成全量检查整改
清理已跟踪原始日志与个人本地配置
修复前后端有效门禁、测试和构建问题
补齐生产 Jenkins、SpacetimeDB 本地命令与文档约束
收紧图片编辑器状态、附件与媒体引用测试
排除已下线旧创作入口测试并清理 warning
2026-07-17 16:56:46 +08:00

156 lines
5.3 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');
}
const easVersionResult = spawnSync(npmCommand, ['exec', 'eas', '--', '--version'], {
cwd: shellRoot,
encoding: 'utf8',
});
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');