From 8432cc4dd75e7f8464111191c8dc7e387fead3a2 Mon Sep 17 00:00:00 2001 From: Will Schurman Date: Thu, 15 Feb 2024 13:24:07 -0700 Subject: [PATCH] [eas-cli] fix expo-updates fingerprinting during update --- packages/eas-cli/src/commands/update/index.ts | 8 +- .../commands/update/roll-back-to-embedded.ts | 8 +- packages/eas-cli/src/project/publish.ts | 120 +++++++++++++----- packages/eas-cli/src/utils/expoUpdatesCli.ts | 40 ++++++ 4 files changed, 133 insertions(+), 43 deletions(-) create mode 100644 packages/eas-cli/src/utils/expoUpdatesCli.ts diff --git a/packages/eas-cli/src/commands/update/index.ts b/packages/eas-cli/src/commands/update/index.ts index 20d678a5f6..1a55b66d1f 100644 --- a/packages/eas-cli/src/commands/update/index.ts +++ b/packages/eas-cli/src/commands/update/index.ts @@ -359,12 +359,12 @@ export default class UpdatePublish extends EasCommand { throw e; } - const runtimeVersions = await getRuntimeVersionObjectAsync( + const runtimeVersions = await getRuntimeVersionObjectAsync({ exp, - realizedPlatforms, + platforms: realizedPlatforms, projectDir, - vcsClient - ); + vcsClient, + }); const runtimeToPlatformMapping = getRuntimeToPlatformMappingFromRuntimeVersions(runtimeVersions); diff --git a/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts b/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts index 520a0114d4..2aba171e74 100644 --- a/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts +++ b/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts @@ -201,12 +201,12 @@ export default class UpdateRollBackToEmbedded extends EasCommand { const gitCommitHash = await vcsClient.getCommitHashAsync(); const isGitWorkingTreeDirty = await vcsClient.hasUncommittedChangesAsync(); - const runtimeVersions = await getRuntimeVersionObjectAsync( + const runtimeVersions = await getRuntimeVersionObjectAsync({ exp, - realizedPlatforms, + platforms: realizedPlatforms, projectDir, - vcsClient - ); + vcsClient, + }); let newUpdates: UpdatePublishMutation['updateBranch']['publishUpdateGroups']; const publishSpinner = ora('Publishing...').start(); diff --git a/packages/eas-cli/src/project/publish.ts b/packages/eas-cli/src/project/publish.ts index 0122a3268d..4fee6853a8 100644 --- a/packages/eas-cli/src/project/publish.ts +++ b/packages/eas-cli/src/project/publish.ts @@ -31,6 +31,7 @@ import { shouldUseVersionedExpoCLI, shouldUseVersionedExpoCLIWithExplicitPlatforms, } from '../utils/expoCli'; +import { expoUpdatesCommandAsync } from '../utils/expoUpdatesCli'; import chunk from '../utils/expodash/chunk'; import { truthy } from '../utils/expodash/filter'; import uniqBy from '../utils/expodash/uniqBy'; @@ -675,49 +676,98 @@ export function getRequestedPlatform( } /** Get runtime versions grouped by platform. Runtime version is always `null` on web where the platform is always backwards compatible. */ -export async function getRuntimeVersionObjectAsync( - exp: ExpoConfig, - platforms: Platform[], - projectDir: string, - vcsClient: Client -): Promise<{ platform: string; runtimeVersion: string }[]> { - for (const platform of platforms) { - if (platform === 'web') { - continue; - } - const isPolicy = typeof (exp[platform]?.runtimeVersion ?? exp.runtimeVersion) === 'object'; - if (isPolicy) { - const isManaged = - (await resolveWorkflowAsync(projectDir, platform as EASBuildJobPlatform, vcsClient)) === - Workflow.MANAGED; - if (!isManaged) { - throw new Error( - `You're currently using the bare workflow, where runtime version policies are not supported. You must set your runtime version manually. For example, define your runtime version as "1.0.0", not {"policy": "appVersion"} in your app config. ${learnMore( - 'https://docs.expo.dev/eas-update/runtime-versions' - )}` - ); - } - } - } - +export async function getRuntimeVersionObjectAsync({ + exp, + platforms, + projectDir, + vcsClient, +}: { + exp: ExpoConfig; + platforms: Platform[]; + projectDir: string; + vcsClient: Client; +}): Promise<{ platform: string; runtimeVersion: string }[]> { return await Promise.all( - [...new Set(platforms)].map(async platform => { - if (platform === 'web') { - return { platform: 'web', runtimeVersion: 'UNVERSIONED' }; - } + platforms.map(async platform => { return { platform, - runtimeVersion: nullthrows( - await Updates.getRuntimeVersionAsync(projectDir, exp, platform), - `Unable to determine runtime version for ${ - requestedPlatformDisplayNames[platform] - }. ${learnMore('https://docs.expo.dev/eas-update/runtime-versions/')}` - ), + runtimeVersion: await getRuntimeVersionForPlatformAsync({ + exp, + platform, + projectDir, + vcsClient, + }), }; }) ); } +async function getRuntimeVersionForPlatformAsync({ + exp, + platform, + projectDir, + vcsClient, +}: { + exp: ExpoConfig; + platform: Platform; + projectDir: string; + vcsClient: Client; +}): Promise { + if (platform === 'web') { + 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; + } + + const workflow = await resolveWorkflowAsync( + projectDir, + platform as EASBuildJobPlatform, + vcsClient + ); + if (workflow !== Workflow.MANAGED) { + throw new Error( + `You're currently using the bare workflow, where runtime version policies are not supported. You must set your runtime version manually. For example, define your runtime version as "1.0.0", not {"policy": "appVersion"} in your app config. ${learnMore( + 'https://docs.expo.dev/eas-update/runtime-versions' + )}` + ); + } + } + + const resolvedRuntimeVersion = await Updates.getRuntimeVersionAsync(projectDir, exp, platform); + if (!resolvedRuntimeVersion) { + throw new Error( + `Unable to determine runtime version for ${ + requestedPlatformDisplayNames[platform] + }. ${learnMore('https://docs.expo.dev/eas-update/runtime-versions/')}` + ); + } + + return resolvedRuntimeVersion; +} + export function getRuntimeToPlatformMappingFromRuntimeVersions( runtimeVersions: { platform: string; runtimeVersion: string }[] ): { runtimeVersion: string; platforms: string[] }[] { diff --git a/packages/eas-cli/src/utils/expoUpdatesCli.ts b/packages/eas-cli/src/utils/expoUpdatesCli.ts new file mode 100644 index 0000000000..0119d31df2 --- /dev/null +++ b/packages/eas-cli/src/utils/expoUpdatesCli.ts @@ -0,0 +1,40 @@ +import spawnAsync from '@expo/spawn-async'; +import chalk from 'chalk'; +import resolveFrom, { silent as silentResolveFrom } from 'resolve-from'; + +import Log, { link } from '../log'; + +export async function expoUpdatesCommandAsync(projectDir: string, args: string[]): Promise { + 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 Error( + `The \`expo-updates\` package was not found. Follow the installation directions at ${link( + 'https://docs.expo.dev/bare/installing-expo-modules/' + )}` + ); + } + 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}`); + } + }); + const result = await spawnPromise; + return result.stdout; +}