mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3202a7003 | ||
|
|
cc19ec8ecf | ||
|
|
ffc24c92c5 | ||
|
|
a8240eb7ce | ||
|
|
c8e1799100 | ||
|
|
262f8fb01b | ||
|
|
b3baf0ad7c | ||
|
|
6defce7119 | ||
|
|
4880dce0ab | ||
|
|
48b0e70af1 | ||
|
|
93fc345630 |
No files matched your search
@@ -13,6 +13,8 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- [eas-cli] Print an actionable error message that says to install the project's dependencies when they aren't installed and EAS CLI needs them to read the app config, instead of a confusing module resolution error. Reading the config with the copy of `@expo/config` bundled with EAS CLI is now reserved for old SDK versions whose `expo` package doesn't include Expo CLI; projects without the `expo` package get an error directing them to install it. ([#4080](https://github.com/expo/eas-cli/pull/4080) by [@ide](https://github.com/ide))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [21.2.0](https://github.com/expo/eas-cli/releases/tag/v21.2.0) - 2026-07-24
|
||||
|
||||
+10
@@ -1,5 +1,6 @@
|
||||
import { getConfig, getConfigFilePaths, modifyConfigAsync } from '@expo/config';
|
||||
import { vol } from 'memfs';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import { anything, instance, mock, when } from 'ts-mockito';
|
||||
|
||||
import { Role } from '../../../../graphql/generated';
|
||||
@@ -14,6 +15,11 @@ import { getProjectIdAsync } from '../getProjectIdAsync';
|
||||
|
||||
jest.mock('@expo/config');
|
||||
jest.mock('fs');
|
||||
jest.mock('resolve-from', () => {
|
||||
const resolveFrom: any = jest.fn();
|
||||
resolveFrom.silent = jest.fn();
|
||||
return resolveFrom;
|
||||
});
|
||||
|
||||
jest.mock('../../../../graphql/queries/AppQuery');
|
||||
jest.mock('../../contextUtils/findProjectDirAndVerifyProjectSetupAsync');
|
||||
@@ -36,6 +42,10 @@ describe(getProjectIdAsync, () => {
|
||||
.mocked(getConfigFilePaths)
|
||||
.mockReturnValue({ staticConfigPath: null, dynamicConfigPath: null });
|
||||
|
||||
// Simulate an old SDK project: `expo` is installed but doesn't include Expo CLI, so the
|
||||
// config is read with the copy of `@expo/config` bundled with EAS CLI.
|
||||
jest.mocked(resolveFrom.silent).mockReturnValue('/app/node_modules/expo/package.json');
|
||||
|
||||
const sessionManagerMock = mock<SessionManager>();
|
||||
when(sessionManagerMock.ensureLoggedInAsync(anything())).thenResolve({
|
||||
actor: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AppJSONConfig, PackageJSONConfig, getConfig } from '@expo/config';
|
||||
import chalk from 'chalk';
|
||||
import { vol } from 'memfs';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import { instance, mock } from 'ts-mockito';
|
||||
|
||||
import { getMockOclifConfig } from '../../../__tests__/commands/utils';
|
||||
@@ -21,6 +22,11 @@ import ProjectInit from '../init';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('@expo/config');
|
||||
jest.mock('resolve-from', () => {
|
||||
const resolveFrom: any = jest.fn();
|
||||
resolveFrom.silent = jest.fn();
|
||||
return resolveFrom;
|
||||
});
|
||||
jest.mock('../../../project/expoConfig', () => ({
|
||||
...jest.requireActual('../../../project/expoConfig'),
|
||||
createOrModifyExpoConfigAsync: jest.fn(),
|
||||
@@ -99,6 +105,9 @@ function mockTestProject(options: {
|
||||
// NOTE(@kitten): Updating this test is easiest by letting it fallback to `@expo/config`
|
||||
// This isn't a great solution, but the test is pretty involved
|
||||
jest.mocked(isExpoInstalled).mockReturnValue(false);
|
||||
// Simulate an old SDK project: `expo` is installed but doesn't include Expo CLI, so the config
|
||||
// is read with the copy of `@expo/config` bundled with EAS CLI.
|
||||
jest.mocked(resolveFrom.silent).mockReturnValue(projectRoot + '/node_modules/expo/package.json');
|
||||
}
|
||||
|
||||
const commandOptions = getMockOclifConfig({ root: '/test-project' });
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import { getConfigFilePaths, modifyConfigAsync } from '@expo/config';
|
||||
import { getConfig, getConfigFilePaths, getPackageJson, modifyConfigAsync } from '@expo/config';
|
||||
import JsonFile from '@expo/json-file';
|
||||
import { writeFileSync } from 'fs-extra';
|
||||
import resolveFrom from 'resolve-from';
|
||||
|
||||
import { createOrModifyExpoConfigAsync } from '../expoConfig';
|
||||
import { createOrModifyExpoConfigAsync, getPrivateExpoConfigAsync } from '../expoConfig';
|
||||
import { isExpoInstalled } from '../projectUtils';
|
||||
|
||||
jest.mock('fs-extra');
|
||||
jest.mock('@expo/config');
|
||||
jest.mock('@expo/json-file');
|
||||
jest.mock('resolve-from', () => {
|
||||
const resolveFrom: any = jest.fn();
|
||||
resolveFrom.silent = jest.fn();
|
||||
return resolveFrom;
|
||||
});
|
||||
jest.mock('../projectUtils');
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
@@ -47,4 +55,34 @@ describe('expoConfig', () => {
|
||||
expect(modifyConfigAsync).toHaveBeenCalledWith('/app', { owner: 'ccheever' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPrivateExpoConfigAsync when Expo CLI is not resolvable', () => {
|
||||
beforeEach(() => {
|
||||
jest.mocked(getConfigFilePaths).mockReturnValue({
|
||||
staticConfigPath: '/app/app.json',
|
||||
dynamicConfigPath: null,
|
||||
});
|
||||
jest.mocked(isExpoInstalled).mockReturnValue(false);
|
||||
});
|
||||
|
||||
it('throws an actionable error when expo is declared in package.json but not installed', async () => {
|
||||
jest.mocked(resolveFrom.silent).mockReturnValue(undefined);
|
||||
jest.mocked(getPackageJson).mockReturnValue({ dependencies: { expo: '~53.0.0' } } as any);
|
||||
|
||||
await expect(getPrivateExpoConfigAsync('/app')).rejects.toThrow(
|
||||
/dependencies to be installed/
|
||||
);
|
||||
expect(getConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('throws an actionable error when expo is not declared in package.json', async () => {
|
||||
jest.mocked(resolveFrom.silent).mockReturnValue(undefined);
|
||||
jest.mocked(getPackageJson).mockReturnValue({ dependencies: {} } as any);
|
||||
|
||||
await expect(getPrivateExpoConfigAsync('/app')).rejects.toThrow(
|
||||
/The "expo" package was not found/
|
||||
);
|
||||
expect(getConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,20 @@
|
||||
import { ExpoConfig, getConfig, getConfigFilePaths, modifyConfigAsync } from '@expo/config';
|
||||
import {
|
||||
ExpoConfig,
|
||||
getConfig,
|
||||
getConfigFilePaths,
|
||||
getPackageJson,
|
||||
modifyConfigAsync,
|
||||
} from '@expo/config';
|
||||
import { Env } from '@expo/eas-build-job';
|
||||
import { resolvePackageManager } from '@expo/package-manager';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs-extra';
|
||||
import Joi from 'joi';
|
||||
import path from 'path';
|
||||
import resolveFrom from 'resolve-from';
|
||||
|
||||
import { isExpoInstalled } from './projectUtils';
|
||||
import { link } from '../log';
|
||||
import { spawnExpoCommand } from '../utils/expoCli';
|
||||
|
||||
export type PublicExpoConfig = Omit<
|
||||
@@ -63,12 +73,28 @@ async function getExpoConfigInternalAsync(
|
||||
}
|
||||
);
|
||||
exp = JSON.parse(stdout);
|
||||
} else {
|
||||
} else if (resolveFrom.silent(projectDir, 'expo/package.json')) {
|
||||
// The `expo` package is installed but Expo CLI is not part of it. This happens with old
|
||||
// SDK versions, which predate the local CLI. Since we can't run `expo config`, read the app
|
||||
// config with the copy of `@expo/config` that ships with EAS CLI.
|
||||
exp = getConfig(projectDir, {
|
||||
skipSDKVersionRequirement: true,
|
||||
...(opts.isPublicConfig ? { isPublicConfig: true } : {}),
|
||||
...(opts.skipPlugins ? { skipPlugins: true } : {}),
|
||||
}).exp;
|
||||
} else if (getPackageJson(projectDir)?.dependencies?.expo) {
|
||||
const installCommand = `${resolvePackageManager(projectDir) ?? 'npm'} install`;
|
||||
throw new Error(
|
||||
`EAS CLI needs your project's dependencies to be installed to read your app config. Run ${chalk.bold(
|
||||
installCommand
|
||||
)} in your project directory and run this command again.`
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
`The "expo" package was not found in your project's dependencies, needed to read your app config. Add "expo" to your dependencies and install it. Refer to the version compatibility table at: ${link(
|
||||
'https://docs.expo.dev/versions/latest/'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
const { error } = MinimalAppConfigSchema.validate(exp, {
|
||||
|
||||
Reference in new issue
Block a user