Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
183 changes: 153 additions & 30 deletions packages/eas-json/src/__tests__/buildProfiles-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> = T extends object
? {
[P in keyof T]?: DeepPartial<T[P]>;
}
: T;

beforeEach(async () => {
vol.reset();
await fs.mkdirp('/project');
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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<EasJson>);

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<EasJson>);

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<EasJson>);

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'],
},
});
});
});
28 changes: 27 additions & 1 deletion packages/eas-json/src/build/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<EasJsonBuildProfile, 'extends'>;

Expand Down Expand Up @@ -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,
Expand Down