From 561e3bbb5ac2e5211d92a4f1f6a4c9bb8f807dc9 Mon Sep 17 00:00:00 2001 From: Will Schurman Date: Wed, 28 Feb 2024 09:27:14 -0800 Subject: [PATCH] [eas-cli] Use expo-updates runtime version CLI to generate runtime versions (#2251) --- CHANGELOG.md | 1 + packages/eas-cli/src/build/metadata.ts | 6 +-- packages/eas-cli/src/project/publish.ts | 52 ++++++++++--------- .../src/project/resolveRuntimeVersionAsync.ts | 44 ++++++++++++++++ .../src/rollout/actions/CreateRollout.ts | 4 +- packages/eas-cli/src/utils/expoUpdatesCli.ts | 34 ++++++------ 6 files changed, 93 insertions(+), 48 deletions(-) create mode 100644 packages/eas-cli/src/project/resolveRuntimeVersionAsync.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 39119222f1..3d2540e4d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This is the log of notable changes to EAS CLI and related packages. - Fix expo-updates package version detection for canaries. ([#2243](https://github.com/expo/eas-cli/pull/2243) by [@wschurman](https://github.com/wschurman)) - Add missing `config` property to `eas.json` schema. ([#2248](https://github.com/expo/eas-cli/pull/2248) by [@sjchmiela](https://github.com/sjchmiela)) +- Use expo-updates runtime version CLI to generate runtime versions. ([#2251](https://github.com/expo/eas-cli/pull/2251) by [@wschurman](https://github.com/wschurman)) ### 🧹 Chores diff --git a/packages/eas-cli/src/build/metadata.ts b/packages/eas-cli/src/build/metadata.ts index 4393ce2d1c..34497ec1f4 100644 --- a/packages/eas-cli/src/build/metadata.ts +++ b/packages/eas-cli/src/build/metadata.ts @@ -1,4 +1,3 @@ -import { Updates } from '@expo/config-plugins'; import { Metadata, Platform, sanitizeMetadata } from '@expo/eas-build-job'; import { IosEnterpriseProvisioning } from '@expo/eas-json'; import fs from 'fs-extra'; @@ -15,6 +14,7 @@ import { isClassicUpdatesSupportedAsync, isExpoUpdatesInstalled, } from '../project/projectUtils'; +import { resolveRuntimeVersionAsync } from '../project/resolveRuntimeVersionAsync'; import { readChannelSafelyAsync as readAndroidChannelSafelyAsync, readReleaseChannelSafelyAsync as readAndroidReleaseChannelSafelyAsync, @@ -37,9 +37,7 @@ export async function collectMetadataAsync( workflow: ctx.workflow, credentialsSource: ctx.buildProfile.credentialsSource, sdkVersion: ctx.exp.sdkVersion, - runtimeVersion: - (await Updates.getRuntimeVersionNullableAsync(ctx.projectDir, ctx.exp, ctx.platform)) ?? - undefined, + runtimeVersion: (await resolveRuntimeVersionAsync(ctx)) ?? undefined, reactNativeVersion: await getReactNativeVersionAsync(ctx.projectDir), ...channelOrReleaseChannel, distribution, diff --git a/packages/eas-cli/src/project/publish.ts b/packages/eas-cli/src/project/publish.ts index 4fee6853a8..71a652012f 100644 --- a/packages/eas-cli/src/project/publish.ts +++ b/packages/eas-cli/src/project/publish.ts @@ -31,7 +31,11 @@ import { shouldUseVersionedExpoCLI, shouldUseVersionedExpoCLIWithExplicitPlatforms, } from '../utils/expoCli'; -import { expoUpdatesCommandAsync } from '../utils/expoUpdatesCli'; +import { + ExpoUpdatesCLIInvalidCommandError, + ExpoUpdatesCLIModuleNotFoundError, + expoUpdatesCommandAsync, +} from '../utils/expoUpdatesCli'; import chunk from '../utils/expodash/chunk'; import { truthy } from '../utils/expodash/filter'; import uniqBy from '../utils/expodash/uniqBy'; @@ -717,31 +721,31 @@ async function getRuntimeVersionForPlatformAsync({ return 'UNVERSIONED'; } - const runtimeVersion = exp[platform]?.runtimeVersion ?? exp.runtimeVersion; - if (typeof runtimeVersion === 'object') { - const policy = runtimeVersion.policy; - - if (policy === 'fingerprintExperimental') { - // log to inform the user that the fingerprint has been calculated - Log.warn( - `Calculating native fingerprint for platform ${platform} using current state of the "${platform}" directory. ` + - `If the fingerprint differs from the build's fingerint, ensure the state of your project is consistent ` + - `(repository is clean, ios and android native directories are in the same state as the build if applicable).` - ); - - const fingerprintRawString = await expoUpdatesCommandAsync(projectDir, [ - 'fingerprint:generate', - '--platform', - platform, - ]); - const fingerprintObject = JSON.parse(fingerprintRawString); - const hash = nullthrows( - fingerprintObject.hash, - 'invalid response from expo-update CLI for fingerprint generation' - ); - return hash; + try { + const resolvedRuntimeVersionJSONResult = await expoUpdatesCommandAsync(projectDir, [ + 'runtimeversion:resolve', + '--platform', + platform, + ]); + const runtimeVersionResult = JSON.parse(resolvedRuntimeVersionJSONResult); + if (runtimeVersionResult.fingerprintSources) { + Log.debug(`Resolved fingeprint runtime version for platform "${platform}". Sources:`); + Log.debug(runtimeVersionResult.fingerprintSources); + } + return nullthrows(runtimeVersionResult.runtimeVersion); + } catch (e: any) { + // if it's a known set of errors thrown by the CLI it means that we need to default back to the + // previous behavior, otherwise we throw the error since something is wrong + if ( + !(e instanceof ExpoUpdatesCLIModuleNotFoundError) && + !(e instanceof ExpoUpdatesCLIInvalidCommandError) + ) { + throw e; } + } + const runtimeVersion = exp[platform]?.runtimeVersion ?? exp.runtimeVersion; + if (typeof runtimeVersion === 'object') { const workflow = await resolveWorkflowAsync( projectDir, platform as EASBuildJobPlatform, diff --git a/packages/eas-cli/src/project/resolveRuntimeVersionAsync.ts b/packages/eas-cli/src/project/resolveRuntimeVersionAsync.ts new file mode 100644 index 0000000000..0068a92c9b --- /dev/null +++ b/packages/eas-cli/src/project/resolveRuntimeVersionAsync.ts @@ -0,0 +1,44 @@ +import { ExpoConfig } from '@expo/config'; +import { Updates } from '@expo/config-plugins'; + +import Log from '../log'; +import { + ExpoUpdatesCLIInvalidCommandError, + ExpoUpdatesCLIModuleNotFoundError, + expoUpdatesCommandAsync, +} from '../utils/expoUpdatesCli'; + +export async function resolveRuntimeVersionAsync({ + exp, + platform, + projectDir, +}: { + exp: ExpoConfig; + platform: 'ios' | 'android'; + projectDir: string; +}): Promise { + try { + const resolvedRuntimeVersionJSONResult = await expoUpdatesCommandAsync(projectDir, [ + 'runtimeversion:resolve', + '--platform', + platform, + ]); + const runtimeVersionResult = JSON.parse(resolvedRuntimeVersionJSONResult); + if (runtimeVersionResult.fingerprintSources) { + Log.debug(`Resolved fingeprint runtime version for platform "${platform}". Sources:`); + Log.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; + } else if (e instanceof ExpoUpdatesCLIInvalidCommandError) { + // 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); + } + + throw e; + } +} diff --git a/packages/eas-cli/src/rollout/actions/CreateRollout.ts b/packages/eas-cli/src/rollout/actions/CreateRollout.ts index c104a988ef..85f56ba959 100644 --- a/packages/eas-cli/src/rollout/actions/CreateRollout.ts +++ b/packages/eas-cli/src/rollout/actions/CreateRollout.ts @@ -1,4 +1,3 @@ -import { Updates } from '@expo/config-plugins'; import assert from 'assert'; import { SelectRuntime } from './SelectRuntime'; @@ -24,6 +23,7 @@ import { } from '../../graphql/queries/ChannelQuery'; import { UpdateQuery } from '../../graphql/queries/UpdateQuery'; import Log from '../../log'; +import { resolveRuntimeVersionAsync } from '../../project/resolveRuntimeVersionAsync'; import { confirmAsync, promptAsync } from '../../prompts'; import { truthy } from '../../utils/expodash/filter'; import { @@ -275,7 +275,7 @@ export class CreateRollout implements EASUpdateAction - Updates.getRuntimeVersionAsync(ctx.app.projectDir, ctx.app.exp, platform) + resolveRuntimeVersionAsync({ projectDir: ctx.app.projectDir, exp: ctx.app.exp, platform }) ) ) ).filter(truthy); diff --git a/packages/eas-cli/src/utils/expoUpdatesCli.ts b/packages/eas-cli/src/utils/expoUpdatesCli.ts index 0119d31df2..e50fed024f 100644 --- a/packages/eas-cli/src/utils/expoUpdatesCli.ts +++ b/packages/eas-cli/src/utils/expoUpdatesCli.ts @@ -1,8 +1,10 @@ import spawnAsync from '@expo/spawn-async'; -import chalk from 'chalk'; import resolveFrom, { silent as silentResolveFrom } from 'resolve-from'; -import Log, { link } from '../log'; +import { link } from '../log'; + +export class ExpoUpdatesCLIModuleNotFoundError extends Error {} +export class ExpoUpdatesCLIInvalidCommandError extends Error {} export async function expoUpdatesCommandAsync(projectDir: string, args: string[]): Promise { let expoUpdatesCli; @@ -12,7 +14,7 @@ export async function expoUpdatesCommandAsync(projectDir: string, args: string[] resolveFrom(projectDir, 'expo-updates/bin/cli.js'); } catch (e: any) { if (e.code === 'MODULE_NOT_FOUND') { - throw new Error( + throw new ExpoUpdatesCLIModuleNotFoundError( `The \`expo-updates\` package was not found. Follow the installation directions at ${link( 'https://docs.expo.dev/bare/installing-expo-modules/' )}` @@ -21,20 +23,16 @@ export async function expoUpdatesCommandAsync(projectDir: string, args: string[] throw e; } - const spawnPromise = spawnAsync(expoUpdatesCli, args, { - stdio: ['inherit', 'pipe', 'pipe'], // inherit stdin so user can install a missing expo-cli from inside this command - }); - const { - child: { stderr }, - } = spawnPromise; - if (!stderr) { - throw new Error('Failed to spawn expo-updates cli'); - } - stderr.on('data', data => { - for (const line of data.toString().trim().split('\n')) { - Log.warn(`${chalk.gray('[expo-cli]')} ${line}`); + try { + return (await spawnAsync(expoUpdatesCli, args)).stdout; + } catch (e: any) { + if (e.stderr) { + if ((e.stderr as string).includes('Invalid command')) { + throw new ExpoUpdatesCLIInvalidCommandError( + `The command specified by ${args} was not valid in the \`expo-updates\` CLI.` + ); + } } - }); - const result = await spawnPromise; - return result.stdout; + throw e; + } }