diff --git a/CHANGELOG.md b/CHANGELOG.md index f85b16fdf3..4e7e88e892 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This is the log of notable changes to EAS CLI and related packages. ### 🛠 Breaking changes +- Resolve versioned expo config using `npx expo config` command instead of using fixed `@expo/config` version shipped with EAS CLI, if available. ([#2529](https://github.com/expo/eas-cli/pull/2529) by [@szdziedzic](https://github.com/szdziedzic)) + ### 🎉 New features ### 🐛 Bug fixes diff --git a/packages/eas-cli/src/commandUtils/context/DynamicProjectConfigContextField.ts b/packages/eas-cli/src/commandUtils/context/DynamicProjectConfigContextField.ts index 07ce38ea94..0e65790dd5 100644 --- a/packages/eas-cli/src/commandUtils/context/DynamicProjectConfigContextField.ts +++ b/packages/eas-cli/src/commandUtils/context/DynamicProjectConfigContextField.ts @@ -7,8 +7,8 @@ import { getProjectIdAsync } from './contextUtils/getProjectIdAsync'; import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync'; import { ExpoConfigOptions, - getPrivateExpoConfig, - getPublicExpoConfig, + getPrivateExpoConfigAsync, + getPublicExpoConfigAsync, } from '../../project/expoConfig'; export type DynamicConfigContextFn = (options?: ExpoConfigOptions) => Promise<{ @@ -25,7 +25,7 @@ export class DynamicPublicProjectConfigContextField extends ContextField { const projectDir = await findProjectDirAndVerifyProjectSetupAsync(); return async (options?: ExpoConfigOptions) => { - const expBefore = getPublicExpoConfig(projectDir, options); + const expBefore = await getPublicExpoConfigAsync(projectDir, options); const projectId = await getProjectIdAsync(sessionManager, expBefore, { nonInteractive, env: options?.env, @@ -48,7 +48,7 @@ export class DynamicPublicProjectConfigContextField extends ContextField { const projectDir = await findProjectDirAndVerifyProjectSetupAsync(); return async (options?: ExpoConfigOptions) => { - const expBefore = getPrivateExpoConfig(projectDir, options); + const expBefore = await getPrivateExpoConfigAsync(projectDir, options); const projectId = await getProjectIdAsync(sessionManager, expBefore, { nonInteractive, env: options?.env, @@ -89,7 +89,7 @@ export class DynamicPrivateProjectConfigContextField extends ContextField { const projectDir = await findProjectDirAndVerifyProjectSetupAsync(); - const expBefore = getPrivateExpoConfig(projectDir); + const expBefore = await getPrivateExpoConfigAsync(projectDir); const projectId = await getProjectIdAsync(sessionManager, expBefore, { nonInteractive, }); @@ -39,7 +39,7 @@ export class PrivateProjectConfigContextField extends ContextField<{ }); serverSideEnvVars = serverSideEnvironmentVariables; } - const exp = getPrivateExpoConfig(projectDir, { env: serverSideEnvVars }); + const exp = await getPrivateExpoConfigAsync(projectDir, { env: serverSideEnvVars }); return { projectId, diff --git a/packages/eas-cli/src/commandUtils/context/ProjectIdContextField.ts b/packages/eas-cli/src/commandUtils/context/ProjectIdContextField.ts index 63a0670804..613c373159 100644 --- a/packages/eas-cli/src/commandUtils/context/ProjectIdContextField.ts +++ b/packages/eas-cli/src/commandUtils/context/ProjectIdContextField.ts @@ -1,12 +1,12 @@ import ContextField, { ContextOptions } from './ContextField'; import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync'; import { getProjectIdAsync } from './contextUtils/getProjectIdAsync'; -import { getPrivateExpoConfig } from '../../project/expoConfig'; +import { getPrivateExpoConfigAsync } from '../../project/expoConfig'; export class ProjectIdContextField extends ContextField { async getValueAsync({ nonInteractive, sessionManager }: ContextOptions): Promise { const projectDir = await findProjectDirAndVerifyProjectSetupAsync(); - const expBefore = getPrivateExpoConfig(projectDir); + const expBefore = await getPrivateExpoConfigAsync(projectDir); const projectId = await getProjectIdAsync(sessionManager, expBefore, { nonInteractive, }); diff --git a/packages/eas-cli/src/commandUtils/context/ServerSideEnvironmentVariablesContextField.ts b/packages/eas-cli/src/commandUtils/context/ServerSideEnvironmentVariablesContextField.ts index 5977fe3a12..169bde33c6 100644 --- a/packages/eas-cli/src/commandUtils/context/ServerSideEnvironmentVariablesContextField.ts +++ b/packages/eas-cli/src/commandUtils/context/ServerSideEnvironmentVariablesContextField.ts @@ -3,7 +3,7 @@ import { createGraphqlClient } from './contextUtils/createGraphqlClient'; import { findProjectDirAndVerifyProjectSetupAsync } from './contextUtils/findProjectDirAndVerifyProjectSetupAsync'; import { getProjectIdAsync } from './contextUtils/getProjectIdAsync'; import { loadServerSideEnvironmentVariablesAsync } from './contextUtils/loadServerSideEnvironmentVariablesAsync'; -import { getPublicExpoConfig } from '../../project/expoConfig'; +import { getPublicExpoConfigAsync } from '../../project/expoConfig'; type GetServerSideEnvironmentVariablesFn = ( maybeEnv?: Record @@ -22,7 +22,7 @@ export class ServerSideEnvironmentVariablesContextField extends ContextField ({ }), })); jest.mock('../../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync'); +jest.mock('../../../../project/projectUtils'); describe(getProjectIdAsync, () => { let sessionManager: SessionManager; @@ -71,6 +73,7 @@ describe(getProjectIdAsync, () => { ); jest.mocked(findProjectRootAsync).mockResolvedValue('/app'); + jest.mocked(isExpoInstalled).mockReturnValue(true); }); it('gets the project ID from app config if exists', async () => { diff --git a/packages/eas-cli/src/commandUtils/context/contextUtils/getProjectIdAsync.ts b/packages/eas-cli/src/commandUtils/context/contextUtils/getProjectIdAsync.ts index d559cbfbf2..465a05f453 100644 --- a/packages/eas-cli/src/commandUtils/context/contextUtils/getProjectIdAsync.ts +++ b/packages/eas-cli/src/commandUtils/context/contextUtils/getProjectIdAsync.ts @@ -7,7 +7,10 @@ import { findProjectRootAsync } from './findProjectDirAndVerifyProjectSetupAsync import { AppQuery } from '../../../graphql/queries/AppQuery'; import Log, { learnMore } from '../../../log'; import { ora } from '../../../ora'; -import { createOrModifyExpoConfigAsync, getPrivateExpoConfig } from '../../../project/expoConfig'; +import { + createOrModifyExpoConfigAsync, + getPrivateExpoConfigAsync, +} from '../../../project/expoConfig'; import { fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync'; import { toAppPrivacy } from '../../../project/projectUtils'; import SessionManager from '../../../user/SessionManager'; @@ -25,7 +28,7 @@ export async function saveProjectIdToAppConfigAsync( options: { env?: Env } = {} ): Promise { // NOTE(cedric): we disable plugins to avoid writing plugin-generated content to `expo.extra` - const exp = getPrivateExpoConfig(projectDir, { skipPlugins: true, ...options }); + const exp = await getPrivateExpoConfigAsync(projectDir, { skipPlugins: true, ...options }); const result = await createOrModifyExpoConfigAsync( projectDir, { diff --git a/packages/eas-cli/src/commands/project/__tests__/init.test.ts b/packages/eas-cli/src/commands/project/__tests__/init.test.ts index 7e9fd7c1f6..ebf81566eb 100644 --- a/packages/eas-cli/src/commands/project/__tests__/init.test.ts +++ b/packages/eas-cli/src/commands/project/__tests__/init.test.ts @@ -14,6 +14,7 @@ import { AppMutation } from '../../../graphql/mutations/AppMutation'; import { AppQuery } from '../../../graphql/queries/AppQuery'; import { createOrModifyExpoConfigAsync } from '../../../project/expoConfig'; import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync'; +import { isExpoInstalled } from '../../../project/projectUtils'; import { confirmAsync, promptAsync } from '../../../prompts'; import ProjectInit from '../init'; @@ -34,6 +35,7 @@ jest.mock('../../../ora', () => ({ })); jest.mock('../../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync'); jest.mock('../../../commandUtils/context/contextUtils/getProjectIdAsync'); +jest.mock('../../../project/projectUtils'); let originalProcessArgv: string[]; @@ -92,6 +94,7 @@ function mockTestProject(options: { graphqlClient, authenticationInfo: { accessToken: null, sessionSecret: '1234' }, }); + jest.mocked(isExpoInstalled).mockReturnValue(true); } const commandOptions = { root: '/test-project' } as any; diff --git a/packages/eas-cli/src/commands/project/init.ts b/packages/eas-cli/src/commands/project/init.ts index 837d72280a..9a31a5777f 100644 --- a/packages/eas-cli/src/commands/project/init.ts +++ b/packages/eas-cli/src/commands/project/init.ts @@ -13,7 +13,7 @@ import { AppMutation } from '../../graphql/mutations/AppMutation'; import { AppQuery } from '../../graphql/queries/AppQuery'; import Log, { link } from '../../log'; import { ora } from '../../ora'; -import { createOrModifyExpoConfigAsync, getPrivateExpoConfig } from '../../project/expoConfig'; +import { createOrModifyExpoConfigAsync, getPrivateExpoConfigAsync } from '../../project/expoConfig'; import { findProjectIdByAccountNameAndSlugNullableAsync } from '../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync'; import { toAppPrivacy } from '../../project/projectUtils'; import { Choice, confirmAsync, promptAsync } from '../../prompts'; @@ -106,7 +106,7 @@ export default class ProjectInit extends EasCommand { projectDir: string, { force, nonInteractive }: InitializeMethodOptions ): Promise { - const exp = getPrivateExpoConfig(projectDir); + const exp = await getPrivateExpoConfigAsync(projectDir); const appForProjectId = await AppQuery.byIdAsync(graphqlClient, projectId); const correctOwner = appForProjectId.ownerAccount.name; const correctSlug = appForProjectId.slug; @@ -161,7 +161,7 @@ export default class ProjectInit extends EasCommand { projectDir: string, { force, nonInteractive }: InitializeMethodOptions ): Promise { - const exp = getPrivateExpoConfig(projectDir); + const exp = await getPrivateExpoConfigAsync(projectDir); const existingProjectId = exp.extra?.eas?.projectId; if (projectId === existingProjectId) { @@ -218,7 +218,7 @@ export default class ProjectInit extends EasCommand { projectDir: string, { force, nonInteractive }: InitializeMethodOptions ): Promise { - const exp = getPrivateExpoConfig(projectDir); + const exp = await getPrivateExpoConfigAsync(projectDir); const existingProjectId = exp.extra?.eas?.projectId; if (existingProjectId) { diff --git a/packages/eas-cli/src/commands/project/onboarding.ts b/packages/eas-cli/src/commands/project/onboarding.ts index ae9b3e39d9..c2a0e4285e 100644 --- a/packages/eas-cli/src/commands/project/onboarding.ts +++ b/packages/eas-cli/src/commands/project/onboarding.ts @@ -31,7 +31,7 @@ import { import { installDependenciesAsync } from '../../onboarding/installDependencies'; import { runCommandAsync } from '../../onboarding/runCommand'; import { RequestedPlatform } from '../../platform'; -import { ExpoConfigOptions, getPrivateExpoConfig } from '../../project/expoConfig'; +import { ExpoConfigOptions, getPrivateExpoConfigAsync } from '../../project/expoConfig'; import { promptAsync } from '../../prompts'; import { Actor } from '../../user/User'; import { easCliVersion } from '../../utils/easCli'; @@ -339,7 +339,7 @@ async function getPrivateExpoConfigWithProjectIdAsync({ actor: Actor; options?: ExpoConfigOptions; }): Promise { - const expBefore = getPrivateExpoConfig(projectDir, options); + const expBefore = await getPrivateExpoConfigAsync(projectDir, options); const projectId = await validateOrSetProjectIdAsync({ exp: expBefore, graphqlClient, @@ -349,7 +349,7 @@ async function getPrivateExpoConfigWithProjectIdAsync({ }, cwd: projectDir, }); - const exp = getPrivateExpoConfig(projectDir, options); + const exp = await getPrivateExpoConfigAsync(projectDir, options); return { exp, projectId, diff --git a/packages/eas-cli/src/commands/update/__tests__/index.test.ts b/packages/eas-cli/src/commands/update/__tests__/index.test.ts index 14ba52a483..aaad94b3ab 100644 --- a/packages/eas-cli/src/commands/update/__tests__/index.test.ts +++ b/packages/eas-cli/src/commands/update/__tests__/index.test.ts @@ -140,18 +140,7 @@ describe(UpdatePublish.name, () => { const flags = ['--non-interactive', '--branch=branch123', '--message=abc']; // Add configuration to the project that should not be included in the update - const { appJson } = mockTestProject({ - expoConfig: { - hooks: { - postPublish: [ - { - file: 'custom-hook.js', - config: { some: 'config' }, - }, - ], - }, - }, - }); + const { appJson } = mockTestProject(); const { platforms, runtimeVersion } = mockTestExport({ platforms: ['ios'] }); diff --git a/packages/eas-cli/src/commands/update/__tests__/roll-back-to-embedded.test.ts b/packages/eas-cli/src/commands/update/__tests__/roll-back-to-embedded.test.ts index b7e6a1f932..e7ebea2481 100644 --- a/packages/eas-cli/src/commands/update/__tests__/roll-back-to-embedded.test.ts +++ b/packages/eas-cli/src/commands/update/__tests__/roll-back-to-embedded.test.ts @@ -145,18 +145,7 @@ describe(UpdateRollBackToEmbedded.name, () => { const flags = ['--non-interactive', '--branch=branch123', '--message=abc']; // Add configuration to the project that should not be included in the update - mockTestProject({ - expoConfig: { - hooks: { - postPublish: [ - { - file: 'custom-hook.js', - config: { some: 'config' }, - }, - ], - }, - }, - }); + mockTestProject(); const platforms = ['ios']; const runtimeVersion = 'exposdk:47.0.0'; diff --git a/packages/eas-cli/src/credentials/__tests__/fixtures-context.ts b/packages/eas-cli/src/credentials/__tests__/fixtures-context.ts index 67f01b8d0b..14dbcecd3f 100644 --- a/packages/eas-cli/src/credentials/__tests__/fixtures-context.ts +++ b/packages/eas-cli/src/credentials/__tests__/fixtures-context.ts @@ -21,8 +21,9 @@ export function createCtxMock(mockOverride: Record = {}): Credentia }, hasAppleCtx: jest.fn(() => true), hasProjectContext: true, - exp: testAppJson, + getExpoConfigAsync: async () => testAppJson, projectDir: '.', + getProjectIdAsync: async () => 'test-project-id', }; return merge(defaultMock, mockOverride) as any; } diff --git a/packages/eas-cli/src/credentials/android/actions/BuildCredentialsUtils.ts b/packages/eas-cli/src/credentials/android/actions/BuildCredentialsUtils.ts index 71a8632c38..bd4494975e 100644 --- a/packages/eas-cli/src/credentials/android/actions/BuildCredentialsUtils.ts +++ b/packages/eas-cli/src/credentials/android/actions/BuildCredentialsUtils.ts @@ -101,14 +101,14 @@ export async function getAppLookupParamsFromContextAsync( ctx: CredentialsContext, gradleContext?: GradleBuildContext ): Promise { - ctx.ensureProjectContext(); - const projectName = ctx.exp.slug; - const projectId = ctx.projectId; + const exp = await ctx.getExpoConfigAsync(); + const projectName = exp.slug; + const projectId = await ctx.getProjectIdAsync(); const account = await getOwnerAccountForProjectIdAsync(ctx.graphqlClient, projectId); const androidApplicationIdentifier = await getApplicationIdAsync( ctx.projectDir, - ctx.exp, + exp, ctx.vcsClient, gradleContext ); diff --git a/packages/eas-cli/src/credentials/android/actions/CreateKeystore.ts b/packages/eas-cli/src/credentials/android/actions/CreateKeystore.ts index 3953b8a1e1..9da141f7fb 100644 --- a/packages/eas-cli/src/credentials/android/actions/CreateKeystore.ts +++ b/packages/eas-cli/src/credentials/android/actions/CreateKeystore.ts @@ -16,7 +16,7 @@ export class CreateKeystore { throw new Error(`New keystore cannot be created in non-interactive mode.`); } - const projectId = ctx.projectId; + const projectId = await ctx.getProjectIdAsync(); const keystore = await this.provideOrGenerateAsync(ctx.graphqlClient, ctx.analytics, projectId); const keystoreFragment = await ctx.android.createKeystoreAsync( ctx.graphqlClient, diff --git a/packages/eas-cli/src/credentials/context.ts b/packages/eas-cli/src/credentials/context.ts index f6cfaaee77..827273564a 100644 --- a/packages/eas-cli/src/credentials/context.ts +++ b/packages/eas-cli/src/credentials/context.ts @@ -10,7 +10,7 @@ import { AuthenticationMode } from './ios/appstore/authenticateTypes'; import { Analytics } from '../analytics/AnalyticsManager'; import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient'; import Log from '../log'; -import { getPrivateExpoConfig } from '../project/expoConfig'; +import { getPrivateExpoConfigAsync } from '../project/expoConfig'; import { confirmAsync } from '../prompts'; import { Actor } from '../user/User'; import { Client } from '../vcs/vcs'; @@ -67,22 +67,22 @@ export class CredentialsContext { return !!this.projectInfo; } - get exp(): ExpoConfig { - this.ensureProjectContext(); + public async getExpoConfigAsync(): Promise { + await this.ensureProjectContextAsync(); return this.projectInfo!.exp; } - get projectId(): string { - this.ensureProjectContext(); + public async getProjectIdAsync(): Promise { + await this.ensureProjectContextAsync(); return this.projectInfo!.projectId; } - public ensureProjectContext(): void { + public async ensureProjectContextAsync(): Promise { if (this.hasProjectContext) { return; } // trigger getConfig error - getPrivateExpoConfig(this.options.projectDir); + await getPrivateExpoConfigAsync(this.options.projectDir); } async bestEffortAppStoreAuthenticateAsync(): Promise { diff --git a/packages/eas-cli/src/credentials/ios/actions/BuildCredentialsUtils.ts b/packages/eas-cli/src/credentials/ios/actions/BuildCredentialsUtils.ts index 5ed178edca..ee4466ec11 100644 --- a/packages/eas-cli/src/credentials/ios/actions/BuildCredentialsUtils.ts +++ b/packages/eas-cli/src/credentials/ios/actions/BuildCredentialsUtils.ts @@ -91,9 +91,9 @@ export async function assignBuildCredentialsAsync( } export async function getAppFromContextAsync(ctx: CredentialsContext): Promise { - ctx.ensureProjectContext(); - const projectName = ctx.exp.slug; - const projectId = ctx.projectId; + const exp = await ctx.getExpoConfigAsync(); + const projectName = exp.slug; + const projectId = await ctx.getProjectIdAsync(); const account = await getOwnerAccountForProjectIdAsync(ctx.graphqlClient, projectId); return { account, diff --git a/packages/eas-cli/src/credentials/manager/ManageIos.ts b/packages/eas-cli/src/credentials/manager/ManageIos.ts index c48eefe937..36c1865cb0 100644 --- a/packages/eas-cli/src/credentials/manager/ManageIos.ts +++ b/packages/eas-cli/src/credentials/manager/ManageIos.ts @@ -94,7 +94,7 @@ export class ManageIos { }; const account = ctx.hasProjectContext - ? await getAccountForProjectAsync(ctx.projectId) + ? await getAccountForProjectAsync(await ctx.getProjectIdAsync()) : ensureActorHasPrimaryAccount(ctx.user); let app = null; @@ -195,18 +195,19 @@ export class ManageIos { }> { assert(ctx.hasProjectContext, 'createProjectContextAsync: must have project context.'); - const app = { account, projectName: ctx.exp.slug }; + const exp = await ctx.getExpoConfigAsync(); + const app = { account, projectName: exp.slug }; const xcodeBuildContext = await resolveXcodeBuildContextAsync( { projectDir: ctx.projectDir, nonInteractive: ctx.nonInteractive, - exp: ctx.exp, + exp, vcsClient: ctx.vcsClient, }, buildProfile ); const targets = await resolveTargetsAsync({ - exp: ctx.exp, + exp, projectDir: ctx.projectDir, xcodeBuildContext, env: buildProfile.env, diff --git a/packages/eas-cli/src/credentials/manager/SetUpIosBuildCredentials.ts b/packages/eas-cli/src/credentials/manager/SetUpIosBuildCredentials.ts index d659bdc0eb..3681d90117 100644 --- a/packages/eas-cli/src/credentials/manager/SetUpIosBuildCredentials.ts +++ b/packages/eas-cli/src/credentials/manager/SetUpIosBuildCredentials.ts @@ -55,7 +55,7 @@ export class SetUpIosBuildCredentials extends ManageIos { }; const account = ctx.hasProjectContext - ? await getAccountForProjectAsync(ctx.projectId) + ? await getAccountForProjectAsync(await ctx.getProjectIdAsync()) : ensureActorHasPrimaryAccount(ctx.user); let app = null; diff --git a/packages/eas-cli/src/project/expoConfig.ts b/packages/eas-cli/src/project/expoConfig.ts index 77811d4d29..d44f00cfcc 100644 --- a/packages/eas-cli/src/project/expoConfig.ts +++ b/packages/eas-cli/src/project/expoConfig.ts @@ -1,9 +1,13 @@ import { ExpoConfig, getConfig, getConfigFilePaths, modifyConfigAsync } from '@expo/config'; import { Env } from '@expo/eas-build-job'; +import spawnAsync from '@expo/spawn-async'; import fs from 'fs-extra'; import Joi from 'joi'; import path from 'path'; +import { isExpoInstalled } from './projectUtils'; +import Log from '../log'; + export type PublicExpoConfig = Omit< ExpoConfig, '_internal' | 'hooks' | 'ios' | 'android' | 'updates' @@ -37,21 +41,57 @@ export async function createOrModifyExpoConfigAsync( } } -function getExpoConfigInternal( +let wasExpoConfigWarnPrinted = false; + +async function getExpoConfigInternalAsync( projectDir: string, opts: ExpoConfigOptionsInternal = {} -): ExpoConfig { +): Promise { const originalProcessEnv: NodeJS.ProcessEnv = process.env; try { process.env = { ...process.env, ...opts.env, }; - const { exp } = getConfig(projectDir, { - skipSDKVersionRequirement: true, - ...(opts.isPublicConfig ? { isPublicConfig: true } : {}), - ...(opts.skipPlugins ? { skipPlugins: true } : {}), - }); + + let exp: ExpoConfig; + if (isExpoInstalled(projectDir)) { + try { + const { stdout } = await spawnAsync( + 'npx', + ['expo', 'config', '--json', ...(opts.isPublicConfig ? ['--type', 'public'] : [])], + + { + cwd: projectDir, + env: { + ...process.env, + ...opts.env, + EXPO_NO_DOTENV: '1', + }, + } + ); + exp = JSON.parse(stdout); + } catch (err: any) { + if (!wasExpoConfigWarnPrinted) { + Log.warn( + `Failed to read the app config from the project using "npx expo config" command: ${err.message}.` + ); + Log.warn('Falling back to the version of "@expo/config" shipped with the EAS CLI.'); + wasExpoConfigWarnPrinted = true; + } + exp = getConfig(projectDir, { + skipSDKVersionRequirement: true, + ...(opts.isPublicConfig ? { isPublicConfig: true } : {}), + ...(opts.skipPlugins ? { skipPlugins: true } : {}), + }).exp; + } + } else { + exp = getConfig(projectDir, { + skipSDKVersionRequirement: true, + ...(opts.isPublicConfig ? { isPublicConfig: true } : {}), + ...(opts.skipPlugins ? { skipPlugins: true } : {}), + }).exp; + } const { error } = MinimalAppConfigSchema.validate(exp, { allowUnknown: true, @@ -78,10 +118,13 @@ const MinimalAppConfigSchema = Joi.object({ }), }); -export function getPrivateExpoConfig(projectDir: string, opts: ExpoConfigOptions = {}): ExpoConfig { +export async function getPrivateExpoConfigAsync( + projectDir: string, + opts: ExpoConfigOptions = {} +): Promise { ensureExpoConfigExists(projectDir); - return getExpoConfigInternal(projectDir, { ...opts, isPublicConfig: false }); + return await getExpoConfigInternalAsync(projectDir, { ...opts, isPublicConfig: false }); } export function ensureExpoConfigExists(projectDir: string): void { @@ -97,11 +140,11 @@ export function isUsingStaticExpoConfig(projectDir: string): boolean { return !!(paths.staticConfigPath?.endsWith('app.json') && !paths.dynamicConfigPath); } -export function getPublicExpoConfig( +export async function getPublicExpoConfigAsync( projectDir: string, opts: ExpoConfigOptions = {} -): PublicExpoConfig { +): Promise { ensureExpoConfigExists(projectDir); - return getExpoConfigInternal(projectDir, { ...opts, isPublicConfig: true }); + return await getExpoConfigInternalAsync(projectDir, { ...opts, isPublicConfig: true }); } diff --git a/packages/eas-cli/src/project/ios/entitlements.ts b/packages/eas-cli/src/project/ios/entitlements.ts index 9beec7f749..918990347b 100644 --- a/packages/eas-cli/src/project/ios/entitlements.ts +++ b/packages/eas-cli/src/project/ios/entitlements.ts @@ -1,7 +1,9 @@ -import { IOSConfig, compileModsAsync } from '@expo/config-plugins'; +import { ExportedConfig, IOSConfig, compileModsAsync } from '@expo/config-plugins'; import { JSONObject } from '@expo/json-file'; import { getPrebuildConfigAsync } from '@expo/prebuild-config'; +import spawnAsync from '@expo/spawn-async'; +import Log from '../../log'; import { readPlistAsync } from '../../utils/plist'; import { Client } from '../../vcs/vcs'; import { hasIgnoredIosProjectAsync } from '../workflow'; @@ -10,6 +12,9 @@ interface Target { buildConfiguration?: string; targetName: string; } + +let wasExpoConfigPluginsWarnPrinted = false; + export async function getManagedApplicationTargetEntitlementsAsync( projectDir: string, env: Record, @@ -22,14 +27,39 @@ export async function getManagedApplicationTargetEntitlementsAsync( ...process.env, ...env, }; - const { exp } = await getPrebuildConfigAsync(projectDir, { platforms: ['ios'] }); - - const expWithMods = await compileModsAsync(exp, { - projectRoot: projectDir, - platforms: ['ios'], - introspect: true, - ignoreExistingNativeFiles: await hasIgnoredIosProjectAsync(projectDir, vcsClient), - }); + + let expWithMods: ExportedConfig; + try { + const { stdout } = await spawnAsync( + 'npx', + ['expo', 'config', '--json', '--type', 'introspect'], + + { + cwd: projectDir, + env: { + ...process.env, + ...env, + EXPO_NO_DOTENV: '1', + }, + } + ); + expWithMods = JSON.parse(stdout); + } catch (err: any) { + if (!wasExpoConfigPluginsWarnPrinted) { + Log.warn( + `Failed to read the app config from the project using "npx expo config" command: ${err.message}.` + ); + Log.warn('Falling back to the version of "@expo/config" shipped with the EAS CLI.'); + wasExpoConfigPluginsWarnPrinted = true; + } + const { exp } = await getPrebuildConfigAsync(projectDir, { platforms: ['ios'] }); + expWithMods = await compileModsAsync(exp, { + projectRoot: projectDir, + platforms: ['ios'], + introspect: true, + ignoreExistingNativeFiles: await hasIgnoredIosProjectAsync(projectDir, vcsClient), + }); + } return expWithMods.ios?.entitlements ?? {}; } finally { process.env = originalProcessEnv; diff --git a/packages/eas-cli/src/project/projectUtils.ts b/packages/eas-cli/src/project/projectUtils.ts index 82289feb21..18610c4bf7 100644 --- a/packages/eas-cli/src/project/projectUtils.ts +++ b/packages/eas-cli/src/project/projectUtils.ts @@ -72,6 +72,11 @@ export function isExpoNotificationsInstalled(projectDir: string): boolean { return !!(packageJson.dependencies && 'expo-notifications' in packageJson.dependencies); } +export function isExpoInstalled(projectDir: string): boolean { + const packageJson = getPackageJson(projectDir); + return !!(packageJson.dependencies && 'expo' in packageJson.dependencies); +} + export function isExpoUpdatesInstalledAsDevDependency(projectDir: string): boolean { const packageJson = getPackageJson(projectDir); return !!(packageJson.devDependencies && 'expo-updates' in packageJson.devDependencies);