From be8d7775980c4a18684255522916698a3ce94887 Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Wed, 13 Aug 2025 15:16:19 +0200 Subject: [PATCH 1/3] [eas-cli] fix disabling cache when extending eas config --- .../src/__tests__/buildProfiles-test.ts | 154 ++++++++++++++---- packages/eas-json/src/build/resolver.ts | 11 +- 2 files changed, 134 insertions(+), 31 deletions(-) diff --git a/packages/eas-json/src/__tests__/buildProfiles-test.ts b/packages/eas-json/src/__tests__/buildProfiles-test.ts index d4f7986228..e2986304ec 100644 --- a/packages/eas-json/src/__tests__/buildProfiles-test.ts +++ b/packages/eas-json/src/__tests__/buildProfiles-test.ts @@ -6,6 +6,7 @@ import { vol } from 'memfs'; import { EasJsonAccessor } from '../accessor'; import { InvalidEasJsonError } from '../errors'; import { EasJsonUtils } from '../utils'; +import { EasJson } from '../types'; jest.mock('fs'); @@ -830,21 +831,15 @@ test('valid build profile with caching without paths', async () => { 'production' ); - expect(androidProfile).toEqual({ - distribution: 'store', - credentialsSource: 'remote', - cache: { - disabled: false, - }, - }); - - expect(iosProfile).toEqual({ + const expected = { distribution: 'store', credentialsSource: 'remote', cache: { disabled: false, }, - }); + }; + expect(androidProfile).toEqual(expected); + expect(iosProfile).toEqual(expected); }); test('valid build profile with caching with paths', async () => { @@ -867,23 +862,17 @@ test('valid build profile with caching with paths', async () => { 'production' ); - expect(androidProfile).toEqual({ + const expected = { distribution: 'store', credentialsSource: 'remote', cache: { disabled: false, paths: ['index.ts'], }, - }); + }; + expect(androidProfile).toEqual(expected); - expect(iosProfile).toEqual({ - distribution: 'store', - credentialsSource: 'remote', - cache: { - disabled: false, - paths: ['index.ts'], - }, - }); + expect(iosProfile).toEqual(expected); }); test('valid build profile with caching with customPaths - moved into paths and customPaths removed', async () => { @@ -906,23 +895,17 @@ test('valid build profile with caching with customPaths - moved into paths and c 'production' ); - expect(androidProfile).toEqual({ + const expected = { distribution: 'store', credentialsSource: 'remote', cache: { disabled: false, paths: ['index.ts'], }, - }); + }; - expect(iosProfile).toEqual({ - distribution: 'store', - credentialsSource: 'remote', - cache: { - disabled: false, - paths: ['index.ts'], - }, - }); + expect(androidProfile).toEqual(expected); + expect(iosProfile).toEqual(expected); }); test('invalid build profile with caching with both paths and customPaths - error thrown', async () => { @@ -952,3 +935,114 @@ test('invalid build profile with caching with both paths and customPaths - error await EasJsonUtils.getBuildProfileAsync(accessor, Platform.ANDROID, 'production'); }).rejects.toThrow(expectedError); }); + +describe('extensions can disable cache which is present in base', () => { + type DeepPartial = T extends object + ? { + [P in keyof T]?: DeepPartial; + } + : T; + + it.each([ + { + testName: 'platform-specific setting can be disabled', + baseConfig: { + ios: { + cache: { + paths: ['ios-path'], + }, + }, + android: { + cache: { + paths: ['android-path'], + }, + }, + }, + }, + { + testName: 'platform-common setting can be disabled', + baseConfig: { + cache: { + paths: ['common-path'], + }, + }, + }, + ])('$testName', async ({ baseConfig }) => { + await fs.writeJson('/project/eas.json', { + build: { + base: baseConfig, + extension1: { + extends: 'base', + cache: { + disabled: true, + }, + }, + }, + } satisfies DeepPartial); + + const accessor = EasJsonAccessor.fromProjectPath('/project'); + const extendedProfileIos1 = await EasJsonUtils.getBuildProfileAsync( + accessor, + Platform.IOS, + 'extension1' + ); + const extendedProfileAndroid1 = await EasJsonUtils.getBuildProfileAsync( + accessor, + Platform.ANDROID, + 'extension1' + ); + + const expected = { + cache: { + disabled: true, + }, + credentialsSource: 'remote', + distribution: 'store', + }; + expect(extendedProfileIos1).toEqual(expected); + expect(extendedProfileAndroid1).toEqual(expected); + }); + + test('common setting can be disabled per platform', async () => { + await fs.writeJson('/project/eas.json', { + build: { + base: { + cache: { + paths: ['common-path'], + }, + }, + extension1: { + extends: 'base', + ios: { + cache: { + disabled: true, + }, + }, + }, + }, + } satisfies DeepPartial); + + const accessor = EasJsonAccessor.fromProjectPath('/project'); + const extendedProfileIos1 = await EasJsonUtils.getBuildProfileAsync( + accessor, + Platform.IOS, + 'extension1' + ); + const extendedProfileAndroid1 = await EasJsonUtils.getBuildProfileAsync( + accessor, + Platform.ANDROID, + 'extension1' + ); + + expect(extendedProfileIos1).toMatchObject({ + cache: { + disabled: true, + }, + }); + expect(extendedProfileAndroid1).toMatchObject({ + cache: { + paths: ['common-path'], + }, + }); + }); +}); diff --git a/packages/eas-json/src/build/resolver.ts b/packages/eas-json/src/build/resolver.ts index b31ecdff83..a858f8c544 100644 --- a/packages/eas-json/src/build/resolver.ts +++ b/packages/eas-json/src/build/resolver.ts @@ -3,7 +3,7 @@ import { Platform } from '@expo/eas-build-job'; import { MissingParentProfileError, MissingProfileError } from '../errors'; import { EasJson } from '../types'; import { BuildProfileSchema } from './schema'; -import { BuildProfile, EasJsonBuildProfile } from './types'; +import { BuildProfile, CommonBuildProfile, EasJsonBuildProfile } from './types'; type EasJsonBuildProfileResolved = Omit; @@ -87,6 +87,15 @@ function mergeProfiles( ...update.env, }; } + + if (update?.cache?.disabled) { + delete result.ios?.cache; + delete result.android?.cache; + result.cache = { + disabled: true, + } as CommonBuildProfile['cache']; + } + if (base.android && update.android) { result.android = mergeProfiles( base.android as EasJsonBuildProfileResolved, From 640cbb5283a22e4f6301b61460dbe204ff342253 Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Wed, 13 Aug 2025 16:11:45 +0200 Subject: [PATCH 2/3] throw when overriding platform-specific base values --- CHANGELOG.md | 3 +- .../src/__tests__/buildProfiles-test.ts | 45 +++++++++++++++---- packages/eas-json/src/build/resolver.ts | 17 +++++++ 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8116a497f3..7855d0ca13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ This is the log of notable changes to EAS CLI and related packages. ### ๐Ÿ› Bug fixes -- Make EXPO_PUBLIC_ env vars plain text, rest sensitive ([#3121](https://github.com/expo/eas-cli/pull/3121) by [@kadikraman](https://github.com/kadikraman)) +- Make EXPO*PUBLIC* env vars plain text, rest sensitive ([#3121](https://github.com/expo/eas-cli/pull/3121) by [@kadikraman](https://github.com/kadikraman)) +- improve `eas.json` config extension handling ([#3143](https://github.com/expo/eas-cli/pull/3143) by [@vonovak](https://github.com/vonovak)) ### ๐Ÿงน Chores diff --git a/packages/eas-json/src/__tests__/buildProfiles-test.ts b/packages/eas-json/src/__tests__/buildProfiles-test.ts index e2986304ec..c175355868 100644 --- a/packages/eas-json/src/__tests__/buildProfiles-test.ts +++ b/packages/eas-json/src/__tests__/buildProfiles-test.ts @@ -5,11 +5,17 @@ import { vol } from 'memfs'; import { EasJsonAccessor } from '../accessor'; import { InvalidEasJsonError } from '../errors'; -import { EasJsonUtils } from '../utils'; import { EasJson } from '../types'; +import { EasJsonUtils } from '../utils'; jest.mock('fs'); +type DeepPartial = T extends object + ? { + [P in keyof T]?: DeepPartial; + } + : T; + beforeEach(async () => { vol.reset(); await fs.mkdirp('/project'); @@ -936,14 +942,37 @@ test('invalid build profile with caching with both paths and customPaths - error }).rejects.toThrow(expectedError); }); -describe('extensions can disable cache which is present in base', () => { - type DeepPartial = T extends object - ? { - [P in keyof T]?: DeepPartial; - } - : T; +test('platform-specific setting from base can _not_ be overridden by a setting on the common level', async () => { + const baseConfig = { + ios: { + prebuildCommand: 'ios prebuild', + }, + android: { + prebuildCommand: 'android prebuild', + }, + }; - it.each([ + await fs.writeJson('/project/eas.json', { + build: { + base: baseConfig, + extension1: { + extends: 'base', + prebuildCommand: 'new great prebuild', + }, + }, + } satisfies DeepPartial); + + const accessor = EasJsonAccessor.fromProjectPath('/project'); + + await expect(() => + EasJsonUtils.getBuildProfileAsync(accessor, Platform.ANDROID, 'extension1') + ).rejects.toThrow( + 'Cannot override platform-specific base value "android prebuild" by value "new great prebuild" for key "prebuildCommand". Move the entry out of the "android" object, into the common properties, if you want to override it.' + ); +}); + +describe('extensions can disable cache which is present in base', () => { + test.each([ { testName: 'platform-specific setting can be disabled', baseConfig: { diff --git a/packages/eas-json/src/build/resolver.ts b/packages/eas-json/src/build/resolver.ts index a858f8c544..3074a7480b 100644 --- a/packages/eas-json/src/build/resolver.ts +++ b/packages/eas-json/src/build/resolver.ts @@ -96,6 +96,23 @@ function mergeProfiles( } as CommonBuildProfile['cache']; } + for (const [key, newValue] of Object.entries(update ?? [])) { + for (const platform of [Platform.ANDROID, Platform.IOS]) { + const platformConfig = base?.[platform]; + // @ts-expect-error 'string' can't be used to index type... + const existingValue = platformConfig?.[key]; + if (existingValue !== undefined) { + throw new Error( + `Cannot override platform-specific base value ${JSON.stringify( + existingValue + )} by value ${JSON.stringify( + newValue + )} for key "${key}". Move the entry out of the "${platform}" object, into the common properties, if you want to override it.` + ); + } + } + } + if (base.android && update.android) { result.android = mergeProfiles( base.android as EasJsonBuildProfileResolved, From 8ab71f638043cb01debd04341edd842540ddd2ed Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Wed, 13 Aug 2025 18:05:43 +0200 Subject: [PATCH 3/3] Update CHANGELOG.md Co-authored-by: Kadi Kraman --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7855d0ca13..7c1604d34a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ This is the log of notable changes to EAS CLI and related packages. ### ๐Ÿ› Bug fixes -- Make EXPO*PUBLIC* env vars plain text, rest sensitive ([#3121](https://github.com/expo/eas-cli/pull/3121) by [@kadikraman](https://github.com/kadikraman)) +- Make `EXPO_PUBLIC_` env vars plain text, rest sensitive ([#3121](https://github.com/expo/eas-cli/pull/3121) by [@kadikraman](https://github.com/kadikraman)) - improve `eas.json` config extension handling ([#3143](https://github.com/expo/eas-cli/pull/3143) by [@vonovak](https://github.com/vonovak)) ### ๐Ÿงน Chores