diff --git a/CHANGELOG.md b/CHANGELOG.md index 8116a497f3..7c1604d34a 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 d4f7986228..c175355868 100644 --- a/packages/eas-json/src/__tests__/buildProfiles-test.ts +++ b/packages/eas-json/src/__tests__/buildProfiles-test.ts @@ -5,10 +5,17 @@ import { vol } from 'memfs'; import { EasJsonAccessor } from '../accessor'; import { InvalidEasJsonError } from '../errors'; +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'); @@ -830,21 +837,15 @@ test('valid build profile with caching without paths', async () => { 'production' ); - expect(androidProfile).toEqual({ + const expected = { distribution: 'store', credentialsSource: 'remote', cache: { disabled: false, }, - }); - - expect(iosProfile).toEqual({ - 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 +868,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 +901,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 +941,137 @@ test('invalid build profile with caching with both paths and customPaths - error await EasJsonUtils.getBuildProfileAsync(accessor, Platform.ANDROID, 'production'); }).rejects.toThrow(expectedError); }); + +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', + }, + }; + + 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: { + 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..3074a7480b 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,32 @@ function mergeProfiles( ...update.env, }; } + + if (update?.cache?.disabled) { + delete result.ios?.cache; + delete result.android?.cache; + result.cache = { + disabled: true, + } 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,