ec3a187dd7
Project CI / AI game creator shell Rust crates (push) Successful in 1m11s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m34s
Project CI / Backend tests (push) Successful in 4m46s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m31s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 9m41s
Project CI / Frontend tests (push) Successful in 2m19s
Project CI / Native shell tests (push) Successful in 7m5s
Project CI / AI game creator shell web tests (push) Successful in 1m54s
Project CI / Repository checks (push) Successful in 2m36s
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/503 Co-authored-by: Linghong <ink29535@proton.me> Co-committed-by: Linghong <ink29535@proton.me>
199 lines
5.7 KiB
JavaScript
199 lines
5.7 KiB
JavaScript
import { spawnSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import { createRequire } from 'node:module';
|
|
import { dirname, resolve } from 'node:path';
|
|
|
|
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 shellRequire = createRequire(packagePath);
|
|
const easPackagePath = shellRequire.resolve('eas-cli/package.json');
|
|
const easPackage = JSON.parse(fs.readFileSync(easPackagePath, 'utf8'));
|
|
const easCliPath = resolve(dirname(easPackagePath), easPackage.bin.eas);
|
|
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(
|
|
process.execPath,
|
|
[easCliPath, '--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');
|