387a1c26e3
清理已跟踪原始日志与个人本地配置 修复前后端有效门禁、测试和构建问题 补齐生产 Jenkins、SpacetimeDB 本地命令与文档约束 收紧图片编辑器状态、附件与媒体引用测试 排除已下线旧创作入口测试并清理 warning
156 lines
5.3 KiB
JavaScript
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');
|