From b71f324c83a60ee53fafff29dc420416b9bfc97a Mon Sep 17 00:00:00 2001 From: Kudo Chien Date: Thu, 19 Dec 2024 00:11:52 +0800 Subject: [PATCH 1/3] [eas-cli] support platform version --- packages/eas-cli/src/build/android/version.ts | 21 ++- packages/eas-cli/src/build/ios/version.ts | 18 ++- .../src/build/utils/__tests__/version-test.ts | 147 ++++++++++++++++++ packages/eas-cli/src/build/utils/version.ts | 61 +++++++- .../eas-cli/src/commands/build/version/set.ts | 4 +- 5 files changed, 236 insertions(+), 15 deletions(-) create mode 100644 packages/eas-cli/src/build/utils/__tests__/version-test.ts diff --git a/packages/eas-cli/src/build/android/version.ts b/packages/eas-cli/src/build/android/version.ts index b89e5562d6..5b5725aeb1 100644 --- a/packages/eas-cli/src/build/android/version.ts +++ b/packages/eas-cli/src/build/android/version.ts @@ -20,7 +20,11 @@ import { getNextVersionCode } from '../../project/android/versions'; import { resolveWorkflowAsync } from '../../project/workflow'; import { Client } from '../../vcs/vcs'; import { updateAppJsonConfigAsync } from '../utils/appJson'; -import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version'; +import { + bumpAppVersionAsync, + ensureStaticConfigExists, + getVersionConfigTarget, +} from '../utils/version'; export enum BumpStrategy { APP_VERSION, @@ -53,9 +57,11 @@ export async function bumpVersionAsync({ await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp }); Log.log('Updated versions in app.json'); + const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.ANDROID }); + const version = versionGetter(exp); await updateNativeVersionsAsync({ projectDir, - version: exp.version, + version, versionCode: exp.android?.versionCode, }); Log.log('Synchronized versions with build gradle'); @@ -79,7 +85,7 @@ export async function bumpVersionInAppJsonAsync({ if (bumpStrategy === BumpStrategy.APP_VERSION) { const appVersion = AndroidConfig.Version.getVersionName(exp) ?? '1.0.0'; - await bumpAppVersionAsync({ appVersion, projectDir, exp }); + await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.ANDROID }); } else { const versionCode = AndroidConfig.Version.getVersionCode(exp); const bumpedVersionCode = getNextVersionCode(versionCode); @@ -118,9 +124,11 @@ export async function maybeResolveVersionsAsync( return {}; } } else { + const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.ANDROID }); + const appVersion = versionGetter(exp); return { appBuildVersion: String(AndroidConfig.Version.getVersionCode(exp)), - appVersion: exp.version, + appVersion, }; } } @@ -202,6 +210,7 @@ export async function resolveRemoteVersionCodeAsync( applicationId ); + const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.ANDROID }); const localVersions = await maybeResolveVersionsAsync(projectDir, exp, buildProfile, vcsClient); let currentBuildVersion: string; if (remoteVersions?.buildVersion) { @@ -230,7 +239,7 @@ export async function resolveRemoteVersionCodeAsync( appId: projectId, platform: AppPlatform.Android, applicationIdentifier: applicationId, - storeVersion: localVersions.appVersion ?? exp.version ?? '1.0.0', + storeVersion: localVersions.appVersion ?? versionGetter(exp) ?? '1.0.0', buildVersion: currentBuildVersion, runtimeVersion: (await Updates.getRuntimeVersionNullableAsync(projectDir, exp, Platform.ANDROID)) ?? @@ -254,7 +263,7 @@ export async function resolveRemoteVersionCodeAsync( appId: projectId, platform: AppPlatform.Android, applicationIdentifier: applicationId, - storeVersion: localVersions.appVersion ?? exp.version ?? '1.0.0', + storeVersion: localVersions.appVersion ?? versionGetter(exp) ?? '1.0.0', buildVersion: String(nextBuildVersion), runtimeVersion: (await Updates.getRuntimeVersionNullableAsync(projectDir, exp, Platform.ANDROID)) ?? diff --git a/packages/eas-cli/src/build/ios/version.ts b/packages/eas-cli/src/build/ios/version.ts index 69a8be03f8..3971c5485e 100644 --- a/packages/eas-cli/src/build/ios/version.ts +++ b/packages/eas-cli/src/build/ios/version.ts @@ -21,7 +21,11 @@ import uniqBy from '../../utils/expodash/uniqBy'; import { readPlistAsync, writePlistAsync } from '../../utils/plist'; import { Client } from '../../vcs/vcs'; import { updateAppJsonConfigAsync } from '../utils/appJson'; -import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version'; +import { + bumpAppVersionAsync, + ensureStaticConfigExists, + getVersionConfigTarget, +} from '../utils/version'; const SHORT_VERSION_REGEX = /^\d+(\.\d+){0,2}$/; @@ -48,9 +52,11 @@ export async function bumpVersionAsync({ ensureStaticConfigExists(projectDir); await bumpVersionInAppJsonAsync({ bumpStrategy, projectDir, exp }); Log.log('Updated versions in app.json'); + const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.IOS }); + const version = versionGetter(exp); await updateNativeVersionsAsync({ projectDir, - version: exp.version, + version, buildNumber: exp.ios?.buildNumber, targets, }); @@ -73,7 +79,7 @@ export async function bumpVersionInAppJsonAsync({ Log.addNewLineIfNone(); if (bumpStrategy === BumpStrategy.APP_VERSION) { const appVersion = IOSConfig.Version.getVersion(exp); - await bumpAppVersionAsync({ appVersion, projectDir, exp }); + await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.IOS }); } else { const buildNumber = IOSConfig.Version.getBuildNumber(exp); if (isValidBuildNumber(buildNumber)) { @@ -141,8 +147,10 @@ export async function readShortVersionAsync( validateShortVersion({ shortVersion, workflow }); return shortVersion; } else { - validateShortVersion({ shortVersion: exp.version, workflow }); - return exp.version; + const { versionGetter } = getVersionConfigTarget({ exp, platform: Platform.IOS }); + const shortVersion = versionGetter(exp); + validateShortVersion({ shortVersion, workflow }); + return shortVersion; } } diff --git a/packages/eas-cli/src/build/utils/__tests__/version-test.ts b/packages/eas-cli/src/build/utils/__tests__/version-test.ts new file mode 100644 index 0000000000..5b402b468c --- /dev/null +++ b/packages/eas-cli/src/build/utils/__tests__/version-test.ts @@ -0,0 +1,147 @@ +import { bumpAppVersionAsync, getVersionConfigTarget } from '../version'; +import { ExpoConfig } from '@expo/config'; +import { Platform } from '@expo/eas-build-job'; +import { updateAppJsonConfigAsync } from '../appJson'; + +jest.mock('../appJson', () => ({ + __esModule: true, + updateAppJsonConfigAsync: jest.fn().mockImplementation( + async ( + { + exp, + }: { + projectDir: string; + exp: ExpoConfig; + }, + modifyConfig: (config: any) => void + ) => { + // a mocked implementation that only mutates the config object without writing to disk + modifyConfig(exp); + } + ), +})); + +describe(bumpAppVersionAsync, () => { + const name = 'test'; + const slug = 'test'; + const projectDir = '/app'; + const mockUpdateAppJsonConfigAsync = updateAppJsonConfigAsync as jest.MockedFunction< + typeof updateAppJsonConfigAsync + >; + + it('should bump expo.version for valid semver', async () => { + const appVersion = '1.0.0'; + const exp: ExpoConfig = { + name, + slug, + version: appVersion, + }; + + await bumpAppVersionAsync({ appVersion, projectDir, exp, platform: Platform.IOS }); + expect(mockUpdateAppJsonConfigAsync).toHaveBeenCalled(); + expect(exp.version).toBe('1.0.1'); + }); + + it('should bump expo.android.version if the expo.android.version exists', async () => { + const exp: ExpoConfig = { + name, + slug, + version: '0.0.0', + android: { + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + version: '1.0.0', + }, + ios: { + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + version: '2.0.0', + }, + }; + + await bumpAppVersionAsync({ appVersion: '1.0.0', projectDir, exp, platform: Platform.ANDROID }); + expect(mockUpdateAppJsonConfigAsync).toHaveBeenCalled(); + expect(exp.version).toBe('0.0.0'); + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + expect(exp.android.version).toBe('1.0.1'); + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + expect(exp.ios.version).toBe('2.0.0'); + }); + + it('should bump expo.ios.version if the expo.ios.version exists', async () => { + const exp: ExpoConfig = { + name, + slug, + version: '0.0.0', + android: { + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + version: '1.0.0', + }, + ios: { + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + version: '2.0.0', + }, + }; + + await bumpAppVersionAsync({ appVersion: '2.0.0', projectDir, exp, platform: Platform.IOS }); + expect(mockUpdateAppJsonConfigAsync).toHaveBeenCalled(); + expect(exp.version).toBe('0.0.0'); + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + expect(exp.android.version).toBe('1.0.0'); + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + expect(exp.ios.version).toBe('2.0.1'); + }); +}); + +describe(getVersionConfigTarget, () => { + const exp: ExpoConfig = { + name: 'test', + slug: 'test', + version: '0.0.0', + android: { + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + version: '1.0.0', + }, + ios: { + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + version: '2.0.0', + }, + }; + + it('should return the correct config target for the android platform', () => { + const { fieldName, versionGetter, versionUpdater } = getVersionConfigTarget({ + exp, + platform: Platform.ANDROID, + }); + expect(fieldName).toBe('expo.android.version'); + expect(versionGetter(exp)).toBe('1.0.0'); + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + expect(versionUpdater(exp, '3.3.3').android.version).toBe('3.3.3'); + expect(versionUpdater(exp, '0.0.0').version).toBe('0.0.0'); + }); + + it('should return the correct config target for the ios platform', () => { + const { fieldName, versionGetter, versionUpdater } = getVersionConfigTarget({ + exp, + platform: Platform.IOS, + }); + expect(fieldName).toBe('expo.ios.version'); + expect(versionGetter(exp)).toBe('2.0.0'); + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + expect(versionUpdater(exp, '3.3.3').ios.version).toBe('3.3.3'); + expect(versionUpdater(exp, '0.0.0').version).toBe('0.0.0'); + }); + + it('should return the correct config target for common version', () => { + const exp: ExpoConfig = { + name: 'test', + slug: 'test', + version: '0.0.0', + }; + const { fieldName, versionGetter, versionUpdater } = getVersionConfigTarget({ + exp, + platform: Platform.IOS, + }); + expect(fieldName).toBe('expo.version'); + expect(versionGetter(exp)).toBe('0.0.0'); + expect(versionUpdater(exp, '3.3.3').version).toBe('3.3.3'); + }); +}); diff --git a/packages/eas-cli/src/build/utils/version.ts b/packages/eas-cli/src/build/utils/version.ts index 03eabe8d0b..c4731366b4 100644 --- a/packages/eas-cli/src/build/utils/version.ts +++ b/packages/eas-cli/src/build/utils/version.ts @@ -2,6 +2,7 @@ import { ExpoConfig, getConfigFilePaths } from '@expo/config'; import chalk from 'chalk'; import nullthrows from 'nullthrows'; import semver from 'semver'; +import { Platform } from '@expo/eas-build-job'; import { updateAppJsonConfigAsync } from './appJson'; import Log from '../../log'; @@ -18,21 +19,25 @@ export async function bumpAppVersionAsync({ appVersion, projectDir, exp, + platform, }: { appVersion: string; projectDir: string; exp: ExpoConfig; + platform: Platform; }): Promise { + const { fieldName, versionUpdater } = getVersionConfigTarget({ exp, platform }); + let bumpedAppVersion: string; if (semver.valid(appVersion)) { bumpedAppVersion = nullthrows(semver.inc(appVersion, 'patch')); Log.log( - `Bumping ${chalk.bold('expo.version')} from ${chalk.bold(appVersion)} to ${chalk.bold( + `Bumping ${chalk.bold(fieldName)} from ${chalk.bold(appVersion)} to ${chalk.bold( bumpedAppVersion )}` ); } else { - Log.log(`${chalk.bold('expo.version')} = ${chalk.bold(appVersion)} is not a valid semver`); + Log.log(`${chalk.bold(fieldName)} = ${chalk.bold(appVersion)} is not a valid semver`); bumpedAppVersion = ( await promptAsync({ type: 'text', @@ -42,6 +47,56 @@ export async function bumpAppVersionAsync({ ).bumpedAppVersion; } await updateAppJsonConfigAsync({ projectDir, exp }, config => { - config.version = bumpedAppVersion; + versionUpdater(config, bumpedAppVersion); }); } + +/** + * Get the target version field from ExpoConfig based on the platform. + */ +export function getVersionConfigTarget({ + exp, + platform, +}: { + exp: ExpoConfig; + platform: Platform; +}): { + fieldName: string; + versionGetter: (config: ExpoConfig) => string | undefined; + versionUpdater: (config: ExpoConfig, version: string) => ExpoConfig; +} { + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + if (platform === Platform.ANDROID && typeof exp.android?.version === 'string') { + return { + fieldName: 'expo.android.version', + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + versionGetter: config => config.android?.version, + versionUpdater: (config, version) => { + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + config.android = { ...config.android, version }; + return config; + }, + }; + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + } else if (platform === Platform.IOS && typeof exp.ios?.version === 'string') { + return { + fieldName: 'expo.ios.version', + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + versionGetter: config => config.ios?.version, + versionUpdater: (config, version) => { + // @ts-expect-error: Resolve type errors after upgrading `@expo/config` + config.ios = { ...config.ios, version }; + return config; + }, + }; + } + + return { + fieldName: 'expo.version', + versionGetter: config => config.version, + versionUpdater: (config, version) => { + config.version = version; + return config; + }, + }; +} diff --git a/packages/eas-cli/src/commands/build/version/set.ts b/packages/eas-cli/src/commands/build/version/set.ts index 92604120f6..9dbb747479 100644 --- a/packages/eas-cli/src/commands/build/version/set.ts +++ b/packages/eas-cli/src/commands/build/version/set.ts @@ -5,6 +5,7 @@ import { Flags } from '@oclif/core'; import chalk from 'chalk'; import { evaluateConfigWithEnvVarsAsync } from '../../../build/evaluateConfigWithEnvVarsAsync'; +import { getVersionConfigTarget } from '../../../build/utils/version'; import EasCommand from '../../../commandUtils/EasCommand'; import { AppVersionMutation } from '../../../graphql/mutations/AppVersionMutation'; import { AppVersionQuery } from '../../../graphql/queries/AppVersionQuery'; @@ -111,6 +112,7 @@ export default class BuildVersionSetView extends EasCommand { : `What version would you like to initialize it with?`; Log.log(currentStateMessage); + const { versionGetter } = getVersionConfigTarget({ exp, platform }); const { version } = await promptAsync({ type: platform === Platform.ANDROID ? 'number' : 'text', name: 'version', @@ -124,7 +126,7 @@ export default class BuildVersionSetView extends EasCommand { appId: projectId, platform: toAppPlatform(platform), applicationIdentifier, - storeVersion: exp.version ?? '1.0.0', + storeVersion: versionGetter(exp) ?? '1.0.0', buildVersion: String(version), runtimeVersion: (await getRuntimeVersionNullableAsync(projectDir, exp, platform)) ?? undefined, From ec439060055829614eeccd5496a1de1c47a64096 Mon Sep 17 00:00:00 2001 From: Kudo Chien Date: Thu, 19 Dec 2024 00:19:03 +0800 Subject: [PATCH 2/3] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dddf7c0b4d..7cf882cc06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ This is the log of notable changes to EAS CLI and related packages. ### 🎉 New features +- Added support to read platform version from `app.json`. ([#2778](https://github.com/expo/eas-cli/pull/2778) by [@kudo](https://github.com/kudo)) + ### 🐛 Bug fixes - Show `eas deploy` upload error messages. ([#2771](https://github.com/expo/eas-cli/pull/2771) by [@kadikraman](https://github.com/kadikraman)) From e7b7eacd4e05093a4accae968861db70e173ad63 Mon Sep 17 00:00:00 2001 From: Kudo Chien Date: Thu, 19 Dec 2024 00:20:26 +0800 Subject: [PATCH 3/3] fix lint errors --- packages/eas-cli/src/build/utils/__tests__/version-test.ts | 3 ++- packages/eas-cli/src/build/utils/version.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/eas-cli/src/build/utils/__tests__/version-test.ts b/packages/eas-cli/src/build/utils/__tests__/version-test.ts index 5b402b468c..4dd55ef730 100644 --- a/packages/eas-cli/src/build/utils/__tests__/version-test.ts +++ b/packages/eas-cli/src/build/utils/__tests__/version-test.ts @@ -1,7 +1,8 @@ -import { bumpAppVersionAsync, getVersionConfigTarget } from '../version'; import { ExpoConfig } from '@expo/config'; import { Platform } from '@expo/eas-build-job'; + import { updateAppJsonConfigAsync } from '../appJson'; +import { bumpAppVersionAsync, getVersionConfigTarget } from '../version'; jest.mock('../appJson', () => ({ __esModule: true, diff --git a/packages/eas-cli/src/build/utils/version.ts b/packages/eas-cli/src/build/utils/version.ts index c4731366b4..00daf77f83 100644 --- a/packages/eas-cli/src/build/utils/version.ts +++ b/packages/eas-cli/src/build/utils/version.ts @@ -1,8 +1,8 @@ import { ExpoConfig, getConfigFilePaths } from '@expo/config'; +import { Platform } from '@expo/eas-build-job'; import chalk from 'chalk'; import nullthrows from 'nullthrows'; import semver from 'semver'; -import { Platform } from '@expo/eas-build-job'; import { updateAppJsonConfigAsync } from './appJson'; import Log from '../../log';