diff --git a/packages/build-tools/src/steps/functions/configureEASUpdateIfInstalled.ts b/packages/build-tools/src/steps/functions/configureEASUpdateIfInstalled.ts index ab645e41..18281f5d 100644 --- a/packages/build-tools/src/steps/functions/configureEASUpdateIfInstalled.ts +++ b/packages/build-tools/src/steps/functions/configureEASUpdateIfInstalled.ts @@ -74,6 +74,7 @@ export function configureEASUpdateIfInstalledFunction(): BuildFunction { } await configureEASUpdateAsync({ + expoUpdatesPackageVersion, job, workingDirectory: stepCtx.workingDirectory, logger: stepCtx.logger, diff --git a/packages/build-tools/src/steps/utils/__tests__/expoUpdates.test.ts b/packages/build-tools/src/steps/utils/__tests__/expoUpdates.test.ts index 0e67b2de..3979faa9 100644 --- a/packages/build-tools/src/steps/utils/__tests__/expoUpdates.test.ts +++ b/packages/build-tools/src/steps/utils/__tests__/expoUpdates.test.ts @@ -22,6 +22,7 @@ describe(configureEASUpdateAsync, () => { it('aborts if updates.url (app config) is set but updates.channel (eas.json) is not', async () => { await configureEASUpdateAsync({ + expoUpdatesPackageVersion: '1.0', job: { platform: Platform.IOS } as unknown as Job, workingDirectory: '/app', logger: createLogger({ @@ -43,6 +44,7 @@ describe(configureEASUpdateAsync, () => { it('configures for EAS if updates.channel (eas.json) and updates.url (app config) are set', async () => { await configureEASUpdateAsync({ + expoUpdatesPackageVersion: '1.0', job: { updates: { channel: 'main', @@ -69,6 +71,7 @@ describe(configureEASUpdateAsync, () => { it('configures for EAS if the updates.channel and releaseChannel are both set', async () => { await configureEASUpdateAsync({ + expoUpdatesPackageVersion: '1.0', job: { updates: { channel: 'main' }, releaseChannel: 'default', diff --git a/packages/build-tools/src/steps/utils/expoUpdates.ts b/packages/build-tools/src/steps/utils/expoUpdates.ts index ff277fd1..33a1170f 100644 --- a/packages/build-tools/src/steps/utils/expoUpdates.ts +++ b/packages/build-tools/src/steps/utils/expoUpdates.ts @@ -1,7 +1,8 @@ import { Job, Platform } from '@expo/eas-build-job'; import { bunyan } from '@expo/logger'; import { ExpoConfig } from '@expo/config'; -import { getRuntimeVersionNullableAsync } from '@expo/config-plugins/build/utils/Updates'; + +import { resolveRuntimeVersionAsync } from '../../utils/resolveRuntimeVersionAsync'; import { iosGetNativelyDefinedChannelAsync, @@ -20,6 +21,7 @@ export async function configureEASUpdateAsync({ logger, inputs, appConfig, + expoUpdatesPackageVersion, }: { job: Job; workingDirectory: string; @@ -29,11 +31,18 @@ export async function configureEASUpdateAsync({ channel?: string; }; appConfig: ExpoConfig; + expoUpdatesPackageVersion: string; }): Promise { const runtimeVersion = - inputs.channel ?? + inputs.runtimeVersion ?? job.version?.runtimeVersion ?? - (await getRuntimeVersionNullableAsync(workingDirectory, appConfig, job.platform)); + (await resolveRuntimeVersionAsync({ + expoUpdatesPackageVersion, + projectDir: workingDirectory, + exp: appConfig, + platform: job.platform, + logger, + })); const jobOrInputChannel = inputs.channel ?? job.updates?.channel; diff --git a/packages/build-tools/src/utils/expoUpdates.ts b/packages/build-tools/src/utils/expoUpdates.ts index bb9abe24..040a9080 100644 --- a/packages/build-tools/src/utils/expoUpdates.ts +++ b/packages/build-tools/src/utils/expoUpdates.ts @@ -1,7 +1,6 @@ import assert from 'assert'; import { Platform, Job } from '@expo/eas-build-job'; -import { getRuntimeVersionNullableAsync } from '@expo/config-plugins/build/utils/Updates'; import semver from 'semver'; import { @@ -23,6 +22,7 @@ import { import { BuildContext } from '../context'; import getExpoUpdatesPackageVersionIfInstalledAsync from './getExpoUpdatesPackageVersionIfInstalledAsync'; +import { resolveRuntimeVersionAsync } from './resolveRuntimeVersionAsync'; export async function setRuntimeVersionNativelyAsync( ctx: BuildContext, @@ -139,20 +139,22 @@ export async function configureEASExpoUpdatesAsync(ctx: BuildContext): Prom } export async function configureExpoUpdatesIfInstalledAsync(ctx: BuildContext): Promise { - const expoUpdatesVersion = await getExpoUpdatesPackageVersionIfInstalledAsync( + const expoUpdatesPackageVersion = await getExpoUpdatesPackageVersionIfInstalledAsync( ctx.getReactNativeProjectDirectory() ); - if (expoUpdatesVersion === null) { + if (expoUpdatesPackageVersion === null) { return; } const appConfigRuntimeVersion = ctx.job.version?.runtimeVersion ?? - (await getRuntimeVersionNullableAsync( - ctx.getReactNativeProjectDirectory(), - ctx.appConfig, - ctx.job.platform - )); + (await resolveRuntimeVersionAsync({ + projectDir: ctx.getReactNativeProjectDirectory(), + exp: ctx.appConfig, + platform: ctx.job.platform, + logger: ctx.logger, + expoUpdatesPackageVersion, + })); if (ctx.metadata?.runtimeVersion && ctx.metadata?.runtimeVersion !== appConfigRuntimeVersion) { ctx.markBuildPhaseHasWarnings(); ctx.logger.warn( @@ -199,7 +201,7 @@ export async function configureExpoUpdatesIfInstalledAsync(ctx: BuildContext { + let expoUpdatesCli; + try { + expoUpdatesCli = + silentResolveFrom(projectDir, 'expo-updates/bin/cli') ?? + resolveFrom(projectDir, 'expo-updates/bin/cli.js'); + } catch (e: any) { + if (e.code === 'MODULE_NOT_FOUND') { + throw new ExpoUpdatesCLIModuleNotFoundError( + `The \`expo-updates\` package was not found. Follow the installation directions at https://docs.expo.dev/bare/installing-expo-modules/` + ); + } + throw e; + } + + try { + return (await spawnAsync(expoUpdatesCli, args)).stdout; + } catch (e: any) { + if (e.stderr && typeof e.stderr === 'string' && e.stderr.includes('Invalid command')) { + throw new ExpoUpdatesCLIInvalidCommandError( + `The command specified by ${args} was not valid in the \`expo-updates\` CLI.` + ); + } + throw e; + } +} diff --git a/packages/build-tools/src/utils/resolveRuntimeVersionAsync.ts b/packages/build-tools/src/utils/resolveRuntimeVersionAsync.ts new file mode 100644 index 00000000..56bc3a99 --- /dev/null +++ b/packages/build-tools/src/utils/resolveRuntimeVersionAsync.ts @@ -0,0 +1,46 @@ +import { ExpoConfig } from '@expo/config'; +import { Updates } from '@expo/config-plugins'; +import { bunyan } from '@expo/logger'; + +import { ExpoUpdatesCLIModuleNotFoundError, expoUpdatesCommandAsync } from './expoUpdatesCli'; +import { isModernExpoUpdatesCLIWithRuntimeVersionCommandSupported } from './expoUpdates'; + +export async function resolveRuntimeVersionAsync({ + exp, + platform, + projectDir, + logger, + expoUpdatesPackageVersion, +}: { + exp: ExpoConfig; + platform: 'ios' | 'android'; + projectDir: string; + logger: bunyan; + expoUpdatesPackageVersion: string; +}): Promise { + if (!isModernExpoUpdatesCLIWithRuntimeVersionCommandSupported(expoUpdatesPackageVersion)) { + // fall back to the previous behavior (using the @expo/config-plugins eas-cli dependency rather + // than the versioned @expo/config-plugins dependency in the project) + return await Updates.getRuntimeVersionNullableAsync(projectDir, exp, platform); + } + + try { + const resolvedRuntimeVersionJSONResult = await expoUpdatesCommandAsync(projectDir, [ + 'runtimeversion:resolve', + '--platform', + platform, + ]); + const runtimeVersionResult = JSON.parse(resolvedRuntimeVersionJSONResult); + if (runtimeVersionResult.fingerprintSources) { + logger.debug(`Resolved fingeprint runtime version for platform "${platform}". Sources:`); + logger.debug(runtimeVersionResult.fingerprintSources); + } + return runtimeVersionResult.runtimeVersion ?? null; + } catch (e: any) { + // if expo-updates is not installed, there's no need for a runtime version in the build + if (e instanceof ExpoUpdatesCLIModuleNotFoundError) { + return null; + } + throw e; + } +}