补齐移动壳原生包构建配置

移动壳新增 EAS Android 和 iOS 构建 profile

移动壳增加原生包构建配置 smoke 门禁

原生壳总验收接入移动 EAS 配置检查

宿主壳文档同步移动原生包构建口径
This commit is contained in:
2026-06-20 06:11:53 +08:00
parent 8f29ddf8e0
commit c4193e27db
11 changed files with 7893 additions and 15 deletions
+52 -1
View File
@@ -89,6 +89,8 @@ const sharedContractPath = new URL(
const sharedContractSource = fs.readFileSync(sharedContractPath, 'utf8');
const packagePath = new URL('../package.json', import.meta.url);
const packageConfig = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
const easConfigPath = new URL('../eas.json', import.meta.url);
const easConfig = JSON.parse(fs.readFileSync(easConfigPath, 'utf8'));
const rootPackagePath = new URL('../../../package.json', import.meta.url);
const rootPackageConfig = JSON.parse(fs.readFileSync(rootPackagePath, 'utf8'));
const rootPackageLockPath = new URL('../../../package-lock.json', import.meta.url);
@@ -438,7 +440,7 @@ function collectProductionSourceFiles(entry) {
if (!productionFileExtensions.has(extension)) {
return [];
}
if (path.includes('.test.') || path.endsWith('/scripts/check-config.mjs')) {
if (path.includes('.test.') || path.includes('/scripts/check-')) {
return [];
}
@@ -524,6 +526,9 @@ for (const [scriptName, expected] of Object.entries({
android: 'expo run:android',
ios: 'expo run:ios',
test: 'vitest run -c vitest.config.ts',
'build:android': 'eas build --local --profile production --platform android',
'build:ios': 'eas build --local --profile production-simulator --platform ios',
'build-config:smoke': 'node scripts/check-eas-build-config.mjs',
'config:smoke': 'node scripts/check-expo-config.mjs',
'export:smoke': 'node scripts/check-expo-export.mjs',
typecheck: 'tsc -p tsconfig.json --noEmit && node scripts/check-config.mjs',
@@ -535,6 +540,9 @@ for (const [scriptName, expected] of Object.entries({
'mobile-shell:dev': 'npm --prefix apps/mobile-shell run dev',
'mobile-shell:typecheck': 'npm --prefix apps/mobile-shell run typecheck',
'mobile-shell:test': 'npm --prefix apps/mobile-shell run test',
'mobile-shell:build-config': 'npm --prefix apps/mobile-shell run build-config:smoke',
'mobile-shell:build:android': 'npm --prefix apps/mobile-shell run build:android',
'mobile-shell:build:ios': 'npm --prefix apps/mobile-shell run build:ios',
'mobile-shell:config': 'npm --prefix apps/mobile-shell run config:smoke',
'mobile-shell:export': 'npm --prefix apps/mobile-shell run export:smoke',
})) {
@@ -618,6 +626,15 @@ for (const [dependency, expected] of Object.entries({
);
}
assertPackageDependencyVersion(
rootPackageConfig,
'root package',
'devDependencies',
'eas-cli',
'^20.3.0',
);
assertPackageLockVersion('eas-cli', '20.3.0');
for (const [dependency, expected] of Object.entries({
typescript: '5.8.3',
vitest: '0.34.6',
@@ -983,6 +1000,40 @@ if ('releaseChannel' in appConfig || 'channel' in appConfig) {
throw new Error('mobile shell must not configure an app release channel without a real release process');
}
if (easConfig.cli?.version !== '>= 20.3.0' || easConfig.cli?.appVersionSource !== 'local') {
throw new Error('mobile shell EAS builds must pin CLI floor and use local app version fields');
}
if (
easConfig.build?.production?.distribution !== 'internal' ||
easConfig.build?.production?.channel !== 'production' ||
easConfig.build?.production?.android?.buildType !== 'apk' ||
easConfig.build?.production?.env?.EXPO_NO_DOTENV !== '1'
) {
throw new Error('mobile shell EAS Android production profile must build an internal APK without local dotenv');
}
if (
easConfig.build?.['production-simulator']?.distribution !== 'internal' ||
easConfig.build?.['production-simulator']?.channel !== 'production' ||
easConfig.build?.['production-simulator']?.ios?.simulator !== true ||
easConfig.build?.['production-simulator']?.env?.EXPO_NO_DOTENV !== '1'
) {
throw new Error('mobile shell EAS iOS production smoke profile must build an internal simulator package without local dotenv');
}
for (const [profileName, profile] of Object.entries(easConfig.build ?? {})) {
for (const blockedKey of ['credentialsSource', 'autoIncrement', 'submit', 'releaseChannel']) {
if (blockedKey in profile) {
throw new Error(`mobile shell EAS ${profileName} profile must not configure ${blockedKey}`);
}
}
}
if ('submit' in easConfig) {
throw new Error('mobile shell EAS config must not include store submit profiles yet');
}
assertSameList(
appConfig.ios?.associatedDomains ?? [],
[sharedPublicWebAssociatedDomain],
@@ -0,0 +1,104 @@
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'));
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 (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'
) {
throw new Error('mobile shell Android package build script drifted');
}
if (
packageConfig.scripts['build:ios'] !==
'eas build --local --profile production-simulator --platform ios'
) {
throw new Error('mobile shell iOS package build script drifted');
}
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', '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');