From 7d16f890a2796a4709c419bdff776eedbad66584 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Aug 2026 11:43:43 -0400 Subject: [PATCH 1/6] Use prod in cli --- .../eas-cli/src/project/resolveRuntimeVersionAsync.ts | 2 +- packages/eas-cli/src/update/android/UpdatesModule.ts | 2 +- packages/eas-cli/src/update/ios/UpdatesModule.ts | 2 +- packages/eas-cli/src/utils/expoUpdatesCli.ts | 11 +++++++++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/eas-cli/src/project/resolveRuntimeVersionAsync.ts b/packages/eas-cli/src/project/resolveRuntimeVersionAsync.ts index dbab6360fc..ed1e0d9b8e 100644 --- a/packages/eas-cli/src/project/resolveRuntimeVersionAsync.ts +++ b/packages/eas-cli/src/project/resolveRuntimeVersionAsync.ts @@ -38,7 +38,7 @@ export async function resolveRuntimeVersionUsingCLIAsync({ const resolvedRuntimeVersionJSONResult = await expoUpdatesCommandAsync( projectDir, ['runtimeversion:resolve', '--platform', platform, '--workflow', workflow, ...extraArgs], - { env, cwd } + { env, cwd, mode: 'production' } ); const runtimeVersionResult = JSON.parse(resolvedRuntimeVersionJSONResult); diff --git a/packages/eas-cli/src/update/android/UpdatesModule.ts b/packages/eas-cli/src/update/android/UpdatesModule.ts index 63ead61b17..28c63d5f81 100644 --- a/packages/eas-cli/src/update/android/UpdatesModule.ts +++ b/packages/eas-cli/src/update/android/UpdatesModule.ts @@ -30,7 +30,7 @@ export async function syncUpdatesConfigurationAsync({ await expoUpdatesCommandAsync( projectDir, ['configuration:syncnative', '--platform', 'android', '--workflow', workflow], - { env } + { env, mode: 'production' } ); return; } diff --git a/packages/eas-cli/src/update/ios/UpdatesModule.ts b/packages/eas-cli/src/update/ios/UpdatesModule.ts index 493cb84789..f3d17bb42e 100644 --- a/packages/eas-cli/src/update/ios/UpdatesModule.ts +++ b/packages/eas-cli/src/update/ios/UpdatesModule.ts @@ -31,7 +31,7 @@ export async function syncUpdatesConfigurationAsync({ await expoUpdatesCommandAsync( projectDir, ['configuration:syncnative', '--platform', 'ios', '--workflow', workflow], - { env } + { env, mode: 'production' } ); return; } diff --git a/packages/eas-cli/src/utils/expoUpdatesCli.ts b/packages/eas-cli/src/utils/expoUpdatesCli.ts index 0a8087b777..ef82517a09 100644 --- a/packages/eas-cli/src/utils/expoUpdatesCli.ts +++ b/packages/eas-cli/src/utils/expoUpdatesCli.ts @@ -8,10 +8,12 @@ export class ExpoUpdatesCLIModuleNotFoundError extends Error {} export class ExpoUpdatesCLIInvalidCommandError extends Error {} export class ExpoUpdatesCLICommandFailedError extends Error {} +type EnvMode = 'development' | 'production'; + export async function expoUpdatesCommandAsync( projectDir: string, args: string[], - options: { env: Env | undefined; cwd?: string } + options: { env: Env | undefined; cwd?: string; mode: EnvMode } ): Promise { let expoUpdatesCli; try { @@ -33,7 +35,12 @@ export async function expoUpdatesCommandAsync( return ( await spawnAsync(expoUpdatesCli, args, { stdio: 'pipe', - env: { ...process.env, ...options.env }, + env: { + ...process.env, + ...options.env, + NODE_ENV: options.mode, + __EXPO_CONFIG_MODE: options.mode, + }, cwd: options.cwd, }) ).stdout; From bbab3e5a69a07917d054c0a7ec327b8446a920cf Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Aug 2026 11:43:57 -0400 Subject: [PATCH 2/6] Add tests --- .../utils/__tests__/expoUpdatesCli-test.ts | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 packages/eas-cli/src/utils/__tests__/expoUpdatesCli-test.ts diff --git a/packages/eas-cli/src/utils/__tests__/expoUpdatesCli-test.ts b/packages/eas-cli/src/utils/__tests__/expoUpdatesCli-test.ts new file mode 100644 index 0000000000..209614de31 --- /dev/null +++ b/packages/eas-cli/src/utils/__tests__/expoUpdatesCli-test.ts @@ -0,0 +1,62 @@ +import spawnAsync from '@expo/spawn-async'; +import { silent as silentResolveFrom } from 'resolve-from'; + +import { expoUpdatesCommandAsync } from '../expoUpdatesCli'; + +jest.mock('@expo/spawn-async', () => ({ + __esModule: true, + default: jest.fn().mockResolvedValue({ stdout: '' }), +})); + +jest.mock('resolve-from', () => ({ + __esModule: true, + default: jest.fn(), + silent: jest.fn(), +})); + +describe(expoUpdatesCommandAsync, () => { + it('passes production mode to the Expo Updates child process and keeps the input env unchanged', async () => { + jest.mocked(silentResolveFrom).mockReturnValue('/project/node_modules/expo-updates/bin/cli'); + const originalProcessEnv = process.env; + const env = { + NODE_ENV: 'staging', + __EXPO_CONFIG_MODE: 'staging', + FROM_COMMAND: 'true', + }; + process.env = { + NODE_ENV: 'development', + __EXPO_CONFIG_MODE: 'development', + FROM_PROCESS: 'true', + }; + + try { + await expoUpdatesCommandAsync('/project', ['runtimeversion:resolve'], { + env, + cwd: '/working-directory', + mode: 'production', + }); + } finally { + process.env = originalProcessEnv; + } + + expect(spawnAsync).toHaveBeenCalledWith( + '/project/node_modules/expo-updates/bin/cli', + ['runtimeversion:resolve'], + { + stdio: 'pipe', + env: { + NODE_ENV: 'production', + __EXPO_CONFIG_MODE: 'production', + FROM_PROCESS: 'true', + FROM_COMMAND: 'true', + }, + cwd: '/working-directory', + } + ); + expect(env).toEqual({ + NODE_ENV: 'staging', + __EXPO_CONFIG_MODE: 'staging', + FROM_COMMAND: 'true', + }); + }); +}); From 2906a1fdbeb4323ebf78efe72bc02535b5ca0e94 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Aug 2026 12:22:33 -0400 Subject: [PATCH 3/6] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 195cb01b74..79a5322178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This is the log of notable changes to EAS CLI and related packages. ### 🛠 Breaking changes - [build-tools] Use production mode for app config, prebuild, Expo Doctor, and Expo Updates commands. ([#4180](https://github.com/expo/eas-cli/pull/4180) by [@ramonclaudio](https://github.com/ramonclaudio)) +- [eas-cli] Use production mode for runtime version resolution and Expo Updates config sync. ([#4229](https://github.com/expo/eas-cli/pull/4229) by [@ramonclaudio](https://github.com/ramonclaudio)) ### 🎉 New features From b268997c1d94baf0e5d95a98a7cf398186540832 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 21 Aug 2026 09:45:39 -0400 Subject: [PATCH 4/6] Add env helper --- packages/eas-cli/src/utils/originalEnv.ts | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 packages/eas-cli/src/utils/originalEnv.ts diff --git a/packages/eas-cli/src/utils/originalEnv.ts b/packages/eas-cli/src/utils/originalEnv.ts new file mode 100644 index 0000000000..1863c42da9 --- /dev/null +++ b/packages/eas-cli/src/utils/originalEnv.ts @@ -0,0 +1,34 @@ +import { LOADED_ENV_NAME } from '@expo/env'; + +// TODO(@ramonclaudio): Use `getOriginalEnv()` from `@expo/env` after EAS CLI +// requires Node 20.12.0 or newer. +/** Remove dotenv values listed by an inherited `__EXPO_ENV_LOADED` marker. */ +export function getEnvWithoutInheritedDotenvValues(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { + const result = { ...env }; + const loadedKeys = getLoadedEnvKeys(result[LOADED_ENV_NAME]); + const unsafeAllowedKeys = loadedKeys.includes('EXPO_UNSAFE_DOTENV_KEYS') + ? new Set() + : new Set(result.EXPO_UNSAFE_DOTENV_KEYS?.split(',').filter(key => key.length > 0)); + + for (const key of loadedKeys) { + if (!unsafeAllowedKeys.has(key)) { + delete result[key]; + } + } + delete result[LOADED_ENV_NAME]; + return result; +} + +function getLoadedEnvKeys(marker: string | undefined): string[] { + if (!marker) { + return []; + } + try { + const loadedKeys = JSON.parse(marker); + return Array.isArray(loadedKeys) + ? loadedKeys.filter((key): key is string => typeof key === 'string') + : []; + } catch { + return []; + } +} From fc7f268dc8d647cbd463359653b40f8d43d52c1e Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 21 Aug 2026 09:45:46 -0400 Subject: [PATCH 5/6] Use local env helper --- packages/eas-cli/src/utils/expoUpdatesCli.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/eas-cli/src/utils/expoUpdatesCli.ts b/packages/eas-cli/src/utils/expoUpdatesCli.ts index ef82517a09..da7f491727 100644 --- a/packages/eas-cli/src/utils/expoUpdatesCli.ts +++ b/packages/eas-cli/src/utils/expoUpdatesCli.ts @@ -3,6 +3,7 @@ import spawnAsync from '@expo/spawn-async'; import resolveFrom, { silent as silentResolveFrom } from 'resolve-from'; import { link } from '../log'; +import { getEnvWithoutInheritedDotenvValues } from './originalEnv'; export class ExpoUpdatesCLIModuleNotFoundError extends Error {} export class ExpoUpdatesCLIInvalidCommandError extends Error {} @@ -32,12 +33,17 @@ export async function expoUpdatesCommandAsync( } try { + const commandEnv = { + ...getEnvWithoutInheritedDotenvValues(process.env), + ...options.env, + }; + delete commandEnv.__EXPO_ENV_LOADED; + return ( await spawnAsync(expoUpdatesCli, args, { stdio: 'pipe', env: { - ...process.env, - ...options.env, + ...commandEnv, NODE_ENV: options.mode, __EXPO_CONFIG_MODE: options.mode, }, From b0bb4d2ca339fd204d8e0b5fac0a47ac3b7f95f4 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 21 Aug 2026 09:45:51 -0400 Subject: [PATCH 6/6] Update tests --- .../utils/__tests__/expoUpdatesCli-test.ts | 13 +++++++++- .../src/utils/__tests__/originalEnv-test.ts | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 packages/eas-cli/src/utils/__tests__/originalEnv-test.ts diff --git a/packages/eas-cli/src/utils/__tests__/expoUpdatesCli-test.ts b/packages/eas-cli/src/utils/__tests__/expoUpdatesCli-test.ts index 209614de31..db62297b78 100644 --- a/packages/eas-cli/src/utils/__tests__/expoUpdatesCli-test.ts +++ b/packages/eas-cli/src/utils/__tests__/expoUpdatesCli-test.ts @@ -21,13 +21,18 @@ describe(expoUpdatesCommandAsync, () => { const env = { NODE_ENV: 'staging', __EXPO_CONFIG_MODE: 'staging', + DOTENV_VALUE: 'from-command', FROM_COMMAND: 'true', + __EXPO_ENV_LOADED: '["DOTENV_VALUE"]', }; - process.env = { + const processEnv = { NODE_ENV: 'development', __EXPO_CONFIG_MODE: 'development', FROM_PROCESS: 'true', + PARENT_DOTENV_VALUE: 'from-parent', + __EXPO_ENV_LOADED: '["PARENT_DOTENV_VALUE"]', }; + process.env = processEnv; try { await expoUpdatesCommandAsync('/project', ['runtimeversion:resolve'], { @@ -35,6 +40,9 @@ describe(expoUpdatesCommandAsync, () => { cwd: '/working-directory', mode: 'production', }); + expect(process.env).toBe(processEnv); + expect(process.env.PARENT_DOTENV_VALUE).toBe('from-parent'); + expect(process.env.__EXPO_ENV_LOADED).toBe('["PARENT_DOTENV_VALUE"]'); } finally { process.env = originalProcessEnv; } @@ -48,6 +56,7 @@ describe(expoUpdatesCommandAsync, () => { NODE_ENV: 'production', __EXPO_CONFIG_MODE: 'production', FROM_PROCESS: 'true', + DOTENV_VALUE: 'from-command', FROM_COMMAND: 'true', }, cwd: '/working-directory', @@ -56,7 +65,9 @@ describe(expoUpdatesCommandAsync, () => { expect(env).toEqual({ NODE_ENV: 'staging', __EXPO_CONFIG_MODE: 'staging', + DOTENV_VALUE: 'from-command', FROM_COMMAND: 'true', + __EXPO_ENV_LOADED: '["DOTENV_VALUE"]', }); }); }); diff --git a/packages/eas-cli/src/utils/__tests__/originalEnv-test.ts b/packages/eas-cli/src/utils/__tests__/originalEnv-test.ts new file mode 100644 index 0000000000..04e493a60a --- /dev/null +++ b/packages/eas-cli/src/utils/__tests__/originalEnv-test.ts @@ -0,0 +1,26 @@ +import { getEnvWithoutInheritedDotenvValues } from '../originalEnv'; + +describe(getEnvWithoutInheritedDotenvValues, () => { + it('keeps a dotenv value allowed by the shell', () => { + const env = { + EXPO_UNSAFE_DOTENV_KEYS: 'ALLOWED_VALUE', + ALLOWED_VALUE: 'keep', + __EXPO_ENV_LOADED: '["ALLOWED_VALUE"]', + }; + + expect(getEnvWithoutInheritedDotenvValues(env)).toEqual({ + EXPO_UNSAFE_DOTENV_KEYS: 'ALLOWED_VALUE', + ALLOWED_VALUE: 'keep', + }); + }); + + it('removes an unsafe-key list inherited from the parent process', () => { + const env = { + EXPO_UNSAFE_DOTENV_KEYS: 'DOTENV_VALUE', + DOTENV_VALUE: 'remove', + __EXPO_ENV_LOADED: '["EXPO_UNSAFE_DOTENV_KEYS","DOTENV_VALUE"]', + }; + + expect(getEnvWithoutInheritedDotenvValues(env)).toEqual({}); + }); +});