mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
329ddccd61 | ||
|
|
1db7281e8b | ||
|
|
1e8a0acabe | ||
|
|
6bfb8af57f | ||
|
|
231f29ecf1 |
No files matched your search
@@ -8,6 +8,8 @@ This is the log of notable changes to EAS CLI and related packages.
|
|||||||
|
|
||||||
### 🎉 New features
|
### 🎉 New features
|
||||||
|
|
||||||
|
- Auto-generate `ios.bundleIdentifier` and `android.package` in non-interactive mode when not set in app config. ([#3399](https://github.com/expo/eas-cli/pull/3399) by [@evanbacon](https://github.com/evanbacon))
|
||||||
|
- Use authorization code flow with PKCE for browser-based login. ([#3398](https://github.com/expo/eas-cli/pull/3398) by [@byronkarlen](https://github.com/byronkarlen))
|
||||||
- [eas-cli] Add `--non-interactive` flag to `metadata:push` and `metadata:pull` commands with ASC API Key auth support. ([#3548](https://github.com/expo/eas-cli/pull/3548) by [@EvanBacon](https://github.com/EvanBacon))
|
- [eas-cli] Add `--non-interactive` flag to `metadata:push` and `metadata:pull` commands with ASC API Key auth support. ([#3548](https://github.com/expo/eas-cli/pull/3548) by [@EvanBacon](https://github.com/EvanBacon))
|
||||||
|
|
||||||
### 🐛 Bug fixes
|
### 🐛 Bug fixes
|
||||||
|
|||||||
@@ -137,21 +137,81 @@ describe(ensureApplicationIdIsDefinedForManagedProjectAsync, () => {
|
|||||||
})
|
})
|
||||||
).rejects.toThrowError(/we can't update this file programmatically/);
|
).rejects.toThrowError(/we can't update this file programmatically/);
|
||||||
});
|
});
|
||||||
it('throws an error in non-interactive mode', async () => {
|
it('auto-generates application id in non-interactive mode when suggestion is available', async () => {
|
||||||
|
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||||
|
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||||
|
id: '1234',
|
||||||
|
slug: 'testing-123',
|
||||||
|
name: 'testing-123',
|
||||||
|
fullName: '@jester/testing-123',
|
||||||
|
ownerAccount: jester.accounts[0],
|
||||||
|
});
|
||||||
|
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'app.json': '{ "expo": {} }',
|
||||||
|
},
|
||||||
|
'/app'
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await ensureApplicationIdIsDefinedForManagedProjectAsync({
|
||||||
|
graphqlClient,
|
||||||
|
projectDir: '/app',
|
||||||
|
projectId: '1234',
|
||||||
|
exp: { slug: 'myapp' } as any,
|
||||||
|
vcsClient,
|
||||||
|
nonInteractive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('com.jester.myapp');
|
||||||
|
expect(promptAsync).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
const appJson = JSON.parse(await fs.readFile('/app/app.json', 'utf-8'));
|
||||||
|
expect(appJson).toMatchObject({
|
||||||
|
expo: { android: { package: 'com.jester.myapp' } },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses ios.bundleIdentifier as application id in non-interactive mode when available', async () => {
|
||||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||||
|
|
||||||
vol.fromJSON(
|
vol.fromJSON(
|
||||||
{
|
{
|
||||||
'app.json': '{ "blah": {} }',
|
'app.json': '{ "expo": {} }',
|
||||||
},
|
},
|
||||||
'/app'
|
'/app'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const result = await ensureApplicationIdIsDefinedForManagedProjectAsync({
|
||||||
|
graphqlClient,
|
||||||
|
projectDir: '/app',
|
||||||
|
projectId: '1234',
|
||||||
|
exp: { slug: 'myapp', ios: { bundleIdentifier: 'com.example.myapp' } } as any,
|
||||||
|
vcsClient,
|
||||||
|
nonInteractive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('com.example.myapp');
|
||||||
|
expect(promptAsync).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error in non-interactive mode when no valid suggestion can be generated', async () => {
|
||||||
|
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||||
|
jest.mocked(AppQuery.byIdAsync).mockRejectedValue(new Error('Not found'));
|
||||||
|
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'app.json': '{ "expo": {} }',
|
||||||
|
},
|
||||||
|
'/app'
|
||||||
|
);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
ensureApplicationIdIsDefinedForManagedProjectAsync({
|
ensureApplicationIdIsDefinedForManagedProjectAsync({
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
projectDir: '/app',
|
projectDir: '/app',
|
||||||
projectId: '',
|
projectId: '1234',
|
||||||
exp: {} as any,
|
exp: { slug: '' } as any,
|
||||||
vcsClient,
|
vcsClient,
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -148,15 +148,8 @@ async function configureApplicationIdAsync({
|
|||||||
exp: ExpoConfig;
|
exp: ExpoConfig;
|
||||||
nonInteractive: boolean;
|
nonInteractive: boolean;
|
||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
if (nonInteractive) {
|
|
||||||
throw new Error(
|
|
||||||
`The "android.package" is required to be set in app config when running in non-interactive mode. ${learnMore(
|
|
||||||
'https://docs.expo.dev/versions/latest/config/app/#package'
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const paths = getConfigFilePaths(projectDir);
|
const paths = getConfigFilePaths(projectDir);
|
||||||
|
|
||||||
// we can't automatically update app.config.js
|
// we can't automatically update app.config.js
|
||||||
if (paths.dynamicConfigPath) {
|
if (paths.dynamicConfigPath) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -166,25 +159,50 @@ async function configureApplicationIdAsync({
|
|||||||
|
|
||||||
assert(paths.staticConfigPath, 'app.json must exist');
|
assert(paths.staticConfigPath, 'app.json must exist');
|
||||||
|
|
||||||
Log.addNewLineIfNone();
|
let packageName: string;
|
||||||
Log.log(
|
|
||||||
`${chalk.bold(`📝 Android application id`)} ${chalk.dim(
|
|
||||||
learnMore('https://expo.fyi/android-package')
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const suggestedAndroidApplicationId = await getSuggestedApplicationIdAsync(
|
if (nonInteractive) {
|
||||||
graphqlClient,
|
let suggestedAndroidApplicationId: string | undefined;
|
||||||
exp,
|
try {
|
||||||
projectId
|
suggestedAndroidApplicationId = await getSuggestedApplicationIdAsync(
|
||||||
);
|
graphqlClient,
|
||||||
const { packageName } = await promptAsync({
|
exp,
|
||||||
name: 'packageName',
|
projectId
|
||||||
type: 'text',
|
);
|
||||||
message: `What would you like your Android application id to be?`,
|
} catch {
|
||||||
initial: suggestedAndroidApplicationId,
|
// If we can't get a suggestion, we'll throw the non-interactive error below
|
||||||
validate: value => (isApplicationIdValid(value) ? true : INVALID_APPLICATION_ID_MESSAGE),
|
}
|
||||||
});
|
if (!suggestedAndroidApplicationId) {
|
||||||
|
throw new Error(
|
||||||
|
`The "android.package" is required to be set in app config when running in non-interactive mode. ${learnMore(
|
||||||
|
'https://docs.expo.dev/versions/latest/config/app/#package'
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
packageName = suggestedAndroidApplicationId;
|
||||||
|
Log.log(`Using automatically generated Android application id: ${chalk.bold(packageName)}`);
|
||||||
|
} else {
|
||||||
|
const suggestedAndroidApplicationId = await getSuggestedApplicationIdAsync(
|
||||||
|
graphqlClient,
|
||||||
|
exp,
|
||||||
|
projectId
|
||||||
|
);
|
||||||
|
Log.addNewLineIfNone();
|
||||||
|
Log.log(
|
||||||
|
`${chalk.bold(`📝 Android application id`)} ${chalk.dim(
|
||||||
|
learnMore('https://expo.fyi/android-package')
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await promptAsync({
|
||||||
|
name: 'packageName',
|
||||||
|
type: 'text',
|
||||||
|
message: `What would you like your Android application id to be?`,
|
||||||
|
initial: suggestedAndroidApplicationId,
|
||||||
|
validate: value => (isApplicationIdValid(value) ? true : INVALID_APPLICATION_ID_MESSAGE),
|
||||||
|
});
|
||||||
|
packageName = result.packageName;
|
||||||
|
}
|
||||||
|
|
||||||
const rawStaticConfig = readAppJson(paths.staticConfigPath);
|
const rawStaticConfig = readAppJson(paths.staticConfigPath);
|
||||||
rawStaticConfig.expo = {
|
rawStaticConfig.expo = {
|
||||||
|
|||||||
@@ -115,20 +115,81 @@ describe(ensureBundleIdentifierIsDefinedForManagedProjectAsync, () => {
|
|||||||
})
|
})
|
||||||
).rejects.toThrowError(/we can't update this file programmatically/);
|
).rejects.toThrowError(/we can't update this file programmatically/);
|
||||||
});
|
});
|
||||||
it('throws an error in non-interactive mode', async () => {
|
it('auto-generates bundle identifier in non-interactive mode when suggestion is available', async () => {
|
||||||
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||||
|
jest.mocked(AppQuery.byIdAsync).mockResolvedValue({
|
||||||
|
id: '1234',
|
||||||
|
slug: 'testing-123',
|
||||||
|
name: 'testing-123',
|
||||||
|
fullName: '@jester/testing-123',
|
||||||
|
ownerAccount: jester.accounts[0],
|
||||||
|
});
|
||||||
|
|
||||||
vol.fromJSON(
|
vol.fromJSON(
|
||||||
{
|
{
|
||||||
'app.json': '{ "blah": {} }',
|
'app.json': '{ "expo": {} }',
|
||||||
},
|
},
|
||||||
'/app'
|
'/app'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const result = await ensureBundleIdentifierIsDefinedForManagedProjectAsync({
|
||||||
|
graphqlClient,
|
||||||
|
projectDir: '/app',
|
||||||
|
projectId: '1234',
|
||||||
|
exp: { slug: 'myapp' } as any,
|
||||||
|
vcsClient,
|
||||||
|
nonInteractive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('com.jester.myapp');
|
||||||
|
expect(promptAsync).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
const appJson = JSON.parse(await fs.readFile('/app/app.json', 'utf-8'));
|
||||||
|
expect(appJson).toMatchObject({
|
||||||
|
expo: { ios: { bundleIdentifier: 'com.jester.myapp' } },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses android.package as bundle identifier in non-interactive mode when available', async () => {
|
||||||
|
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||||
|
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'app.json': '{ "expo": {} }',
|
||||||
|
},
|
||||||
|
'/app'
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await ensureBundleIdentifierIsDefinedForManagedProjectAsync({
|
||||||
|
graphqlClient,
|
||||||
|
projectDir: '/app',
|
||||||
|
projectId: '1234',
|
||||||
|
exp: { slug: 'myapp', android: { package: 'com.example.myapp' } } as any,
|
||||||
|
vcsClient,
|
||||||
|
nonInteractive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('com.example.myapp');
|
||||||
|
expect(promptAsync).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error in non-interactive mode when no valid suggestion can be generated', async () => {
|
||||||
|
const graphqlClient = instance(mock<ExpoGraphqlClient>());
|
||||||
|
jest.mocked(AppQuery.byIdAsync).mockRejectedValue(new Error('Not found'));
|
||||||
|
|
||||||
|
vol.fromJSON(
|
||||||
|
{
|
||||||
|
'app.json': '{ "expo": {} }',
|
||||||
|
},
|
||||||
|
'/app'
|
||||||
|
);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
ensureBundleIdentifierIsDefinedForManagedProjectAsync({
|
ensureBundleIdentifierIsDefinedForManagedProjectAsync({
|
||||||
graphqlClient,
|
graphqlClient,
|
||||||
projectDir: '/app',
|
projectDir: '/app',
|
||||||
projectId: '1234',
|
projectId: '1234',
|
||||||
exp: {} as any,
|
exp: { slug: '' } as any,
|
||||||
vcsClient,
|
vcsClient,
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -137,15 +137,8 @@ async function configureBundleIdentifierAsync({
|
|||||||
exp: ExpoConfig;
|
exp: ExpoConfig;
|
||||||
nonInteractive: boolean;
|
nonInteractive: boolean;
|
||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
if (nonInteractive) {
|
|
||||||
throw new Error(
|
|
||||||
`The "ios.bundleIdentifier" is required to be set in app config when running in non-interactive mode. ${learnMore(
|
|
||||||
'https://docs.expo.dev/versions/latest/config/app/#bundleidentifier'
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const paths = getConfigFilePaths(projectDir);
|
const paths = getConfigFilePaths(projectDir);
|
||||||
|
|
||||||
// we can't automatically update app.config.js
|
// we can't automatically update app.config.js
|
||||||
if (paths.dynamicConfigPath) {
|
if (paths.dynamicConfigPath) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -155,26 +148,51 @@ async function configureBundleIdentifierAsync({
|
|||||||
|
|
||||||
assert(paths.staticConfigPath, 'app.json must exist');
|
assert(paths.staticConfigPath, 'app.json must exist');
|
||||||
|
|
||||||
Log.addNewLineIfNone();
|
let bundleIdentifier: string;
|
||||||
Log.log(
|
|
||||||
`${chalk.bold(`📝 iOS Bundle Identifier`)} ${chalk.dim(
|
|
||||||
learnMore('https://expo.fyi/bundle-identifier')
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const suggestedBundleIdentifier = await getSuggestedBundleIdentifierAsync(
|
if (nonInteractive) {
|
||||||
graphqlClient,
|
let suggestedBundleIdentifier: string | undefined;
|
||||||
exp,
|
try {
|
||||||
projectId
|
suggestedBundleIdentifier = await getSuggestedBundleIdentifierAsync(
|
||||||
);
|
graphqlClient,
|
||||||
|
exp,
|
||||||
|
projectId
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// If we can't get a suggestion, we'll throw the non-interactive error below
|
||||||
|
}
|
||||||
|
if (!suggestedBundleIdentifier) {
|
||||||
|
throw new Error(
|
||||||
|
`The "ios.bundleIdentifier" is required to be set in app config when running in non-interactive mode. ${learnMore(
|
||||||
|
'https://docs.expo.dev/versions/latest/config/app/#bundleidentifier'
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
bundleIdentifier = suggestedBundleIdentifier;
|
||||||
|
Log.log(`Using automatically generated iOS bundle identifier: ${chalk.bold(bundleIdentifier)}`);
|
||||||
|
} else {
|
||||||
|
const suggestedBundleIdentifier = await getSuggestedBundleIdentifierAsync(
|
||||||
|
graphqlClient,
|
||||||
|
exp,
|
||||||
|
projectId
|
||||||
|
);
|
||||||
|
Log.addNewLineIfNone();
|
||||||
|
Log.log(
|
||||||
|
`${chalk.bold(`📝 iOS Bundle Identifier`)} ${chalk.dim(
|
||||||
|
learnMore('https://expo.fyi/bundle-identifier')
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
const { bundleIdentifier } = await promptAsync({
|
const result = await promptAsync({
|
||||||
name: 'bundleIdentifier',
|
name: 'bundleIdentifier',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
message: `What would you like your iOS bundle identifier to be?`,
|
message: `What would you like your iOS bundle identifier to be?`,
|
||||||
initial: suggestedBundleIdentifier,
|
initial: suggestedBundleIdentifier,
|
||||||
validate: value => (isBundleIdentifierValid(value) ? true : INVALID_BUNDLE_IDENTIFIER_MESSAGE),
|
validate: value =>
|
||||||
});
|
isBundleIdentifierValid(value) ? true : INVALID_BUNDLE_IDENTIFIER_MESSAGE,
|
||||||
|
});
|
||||||
|
bundleIdentifier = result.bundleIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
const rawStaticConfig = readAppJson(paths.staticConfigPath);
|
const rawStaticConfig = readAppJson(paths.staticConfigPath);
|
||||||
rawStaticConfig.expo = {
|
rawStaticConfig.expo = {
|
||||||
|
|||||||
Reference in new issue
Block a user