diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a5322178..b32be95389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,13 @@ This is the log of notable changes to EAS CLI and related packages. - [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)) +- [eas-cli] Use an isolated env for local builds. ([#4244](https://github.com/expo/eas-cli/pull/4244) by [@ramonclaudio](https://github.com/ramonclaudio)) ### ๐ŸŽ‰ New features - [build-tools] Add `ios_signing_backend` option to the repack step. ([#4239](https://github.com/expo/eas-cli/pull/4239) by [@gabrieldonadel](https://github.com/gabrieldonadel)) - [build-tools] Support an optional `package_version` input on `eas/start_serve_sim_remote_session`, so a simulator session can pin the `@expo/serve-sim` version instead of always running `latest`. ([#4253](https://github.com/expo/eas-cli/pull/4253) by [@gwdp](https://github.com/gwdp)) -### ๐Ÿ› Bug fixes - ### ๐Ÿงน Chores ## [22.2.0](https://github.com/expo/eas-cli/releases/tag/v22.2.0) - 2026-08-20 diff --git a/packages/eas-cli/src/build/__tests__/local-test.ts b/packages/eas-cli/src/build/__tests__/local-test.ts index 8be670c4eb..b9bb307017 100644 --- a/packages/eas-cli/src/build/__tests__/local-test.ts +++ b/packages/eas-cli/src/build/__tests__/local-test.ts @@ -51,6 +51,57 @@ describe(runLocalBuildAsync, () => { expect(decodeInput(input)).toEqual({ job, metadata }); }); + it('starts the local-build plugin with an isolated env', async () => { + const originalEnv = process.env; + const loadedEnvMarker = '["ANDROID_HOME","EAS_LOCAL_BUILD_WORKINGDIR"]'; + const runtimeEnv = { + ANDROID_NDK_HOME: '/local/android-ndk', + ANDROID_SDK_ROOT: '/local/android-sdk', + DEVELOPER_DIR: '/Applications/Xcode.app/Contents/Developer', + GEM_HOME: '/local/gems', + GEM_PATH: '/local/gems:/system/gems', + HOME: '/local/home', + JAVA_HOME: '/local/jdk', + LANG: 'en_US.UTF-8', + LC_ALL: 'en_US.UTF-8', + LC_CTYPE: 'UTF-8', + NVM_NODEJS_ORG_MIRROR: 'https://node.example.test', + TEMP: '/local/temp', + TMP: '/local/tmp', + TMPDIR: '/local/tmpdir', + }; + process.env = { + ...runtimeEnv, + EAS_LOCAL_BUILD_PLUGIN_PATH: '/path/to/plugin', + PATH: '/local/bin', + ANDROID_HOME: '/dotenv/android', + SHELL_ONLY_VALUE: 'from-shell', + EAS_LOCAL_BUILD_WORKINGDIR: '/dotenv/workingdir', + EAS_LOCAL_BUILD_LOGGER_LEVEL: 'debug', + __EXPO_ENV_LOADED: loadedEnvMarker, + }; + const env = { BUILD_ENV_VALUE: 'from-eas', PATH: '/eas/bin' }; + + try { + await runLocalBuildAsync(job, metadata, { verbose: true }, env); + + const spawnEnv = mockSpawnAsync.mock.calls[0][2]?.env; + expect(spawnEnv?.BUILD_ENV_VALUE).toBe('from-eas'); + expect(spawnEnv?.PATH).toBe('/eas/bin'); + expect(spawnEnv?.ANDROID_HOME).toBeUndefined(); + expect(spawnEnv).toEqual(expect.objectContaining(runtimeEnv)); + expect(spawnEnv?.EAS_LOCAL_BUILD_WORKINGDIR).toBeUndefined(); + expect(spawnEnv?.EAS_LOCAL_BUILD_LOGGER_LEVEL).toBe('debug'); + expect(spawnEnv?.SHELL_ONLY_VALUE).toBeUndefined(); + expect(spawnEnv?.__EXPO_ENV_LOADED).toBeUndefined(); + expect(env).toEqual({ BUILD_ENV_VALUE: 'from-eas', PATH: '/eas/bin' }); + expect(process.env.SHELL_ONLY_VALUE).toBe('from-shell'); + expect(process.env.__EXPO_ENV_LOADED).toBe(loadedEnvMarker); + } finally { + process.env = originalEnv; + } + }); + it('logs a non-secret build context summary and re-throws on failure', async () => { const richJob = { type: 'managed', diff --git a/packages/eas-cli/src/build/local.ts b/packages/eas-cli/src/build/local.ts index 790f92527d..fa555453a9 100644 --- a/packages/eas-cli/src/build/local.ts +++ b/packages/eas-cli/src/build/local.ts @@ -7,10 +7,32 @@ import semver from 'semver'; import { getExpoApiBaseUrl } from '../api'; import Log from '../log'; import { ora } from '../ora'; +import { getEnvWithoutInheritedDotenvValues } from '../utils/originalEnv'; const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin'; const PLUGIN_PACKAGE_VERSION = version; // should match version of @expo/eas-build-job +// The plugin starts with an isolated env, so keep the runtime vars it needs from the user's +// machine. +const LOCAL_BUILD_RUNTIME_ENV_NAMES = [ + 'ANDROID_HOME', + 'ANDROID_NDK_HOME', + 'ANDROID_SDK_ROOT', + 'DEVELOPER_DIR', + 'GEM_HOME', + 'GEM_PATH', + 'HOME', + 'JAVA_HOME', + 'LANG', + 'LC_ALL', + 'LC_CTYPE', + 'NVM_NODEJS_ORG_MIRROR', + 'PATH', + 'TEMP', + 'TMP', + 'TMPDIR', +] as const; + export enum LocalBuildMode { /** * Local build that users can run on their own machines. Instead @@ -63,18 +85,25 @@ export async function runLocalBuildAsync( }; process.on('SIGINT', interruptHandler); try { + const processEnv = getEnvWithoutInheritedDotenvValues(process.env); const mergedEnv = { + ...getLocalBuildRuntimeEnv(processEnv), ...env, - ...process.env, EAS_LOCAL_BUILD_PLUGIN_INPUT: pluginInput, - EAS_LOCAL_BUILD_WORKINGDIR: options.workingdir ?? process.env.EAS_LOCAL_BUILD_WORKINGDIR, + EAS_LOCAL_BUILD_WORKINGDIR: options.workingdir ?? processEnv.EAS_LOCAL_BUILD_WORKINGDIR, + EAS_LOCAL_BUILD_LOGGER_LEVEL: processEnv.EAS_LOCAL_BUILD_LOGGER_LEVEL, __API_SERVER_URL: getExpoApiBaseUrl(), - ...(options.skipCleanup || options.skipNativeBuild - ? { EAS_LOCAL_BUILD_SKIP_CLEANUP: '1' } - : {}), - ...(options.skipNativeBuild ? { EAS_LOCAL_BUILD_SKIP_NATIVE_BUILD: '1' } : {}), - ...(options.artifactsDir ? { EAS_LOCAL_BUILD_ARTIFACTS_DIR: options.artifactsDir } : {}), - ...(options.artifactPath ? { EAS_LOCAL_BUILD_ARTIFACT_PATH: options.artifactPath } : {}), + EAS_LOCAL_BUILD_SKIP_CLEANUP: + options.skipCleanup || options.skipNativeBuild + ? '1' + : processEnv.EAS_LOCAL_BUILD_SKIP_CLEANUP, + EAS_LOCAL_BUILD_SKIP_NATIVE_BUILD: options.skipNativeBuild + ? '1' + : processEnv.EAS_LOCAL_BUILD_SKIP_NATIVE_BUILD, + EAS_LOCAL_BUILD_ARTIFACTS_DIR: + options.artifactsDir ?? processEnv.EAS_LOCAL_BUILD_ARTIFACTS_DIR, + EAS_LOCAL_BUILD_ARTIFACT_PATH: + options.artifactPath ?? processEnv.EAS_LOCAL_BUILD_ARTIFACT_PATH, }; // log command execution to assist in debugging local builds; redact the job // input since it contains build credentials. @@ -102,6 +131,17 @@ export async function runLocalBuildAsync( } } +function getLocalBuildRuntimeEnv(processEnv: NodeJS.ProcessEnv): Env { + const env: Env = {}; + for (const name of LOCAL_BUILD_RUNTIME_ENV_NAMES) { + const value = processEnv[name]; + if (value !== undefined) { + env[name] = value; + } + } + return env; +} + /** * Logs an allowlisted, non-secret summary of the build's job/metadata to help * a user debug a failed local build. Only known-safe fields are included โ€”