mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare 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
|
||||
|
||||
- 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))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
@@ -137,21 +137,81 @@ describe(ensureApplicationIdIsDefinedForManagedProjectAsync, () => {
|
||||
})
|
||||
).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>());
|
||||
|
||||
vol.fromJSON(
|
||||
{
|
||||
'app.json': '{ "blah": {} }',
|
||||
'app.json': '{ "expo": {} }',
|
||||
},
|
||||
'/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(
|
||||
ensureApplicationIdIsDefinedForManagedProjectAsync({
|
||||
graphqlClient,
|
||||
projectDir: '/app',
|
||||
projectId: '',
|
||||
exp: {} as any,
|
||||
projectId: '1234',
|
||||
exp: { slug: '' } as any,
|
||||
vcsClient,
|
||||
nonInteractive: true,
|
||||
})
|
||||
|
||||
@@ -148,15 +148,8 @@ async function configureApplicationIdAsync({
|
||||
exp: ExpoConfig;
|
||||
nonInteractive: boolean;
|
||||
}): 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);
|
||||
|
||||
// we can't automatically update app.config.js
|
||||
if (paths.dynamicConfigPath) {
|
||||
throw new Error(
|
||||
@@ -166,25 +159,50 @@ async function configureApplicationIdAsync({
|
||||
|
||||
assert(paths.staticConfigPath, 'app.json must exist');
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
`${chalk.bold(`📝 Android application id`)} ${chalk.dim(
|
||||
learnMore('https://expo.fyi/android-package')
|
||||
)}`
|
||||
);
|
||||
let packageName: string;
|
||||
|
||||
const suggestedAndroidApplicationId = await getSuggestedApplicationIdAsync(
|
||||
graphqlClient,
|
||||
exp,
|
||||
projectId
|
||||
);
|
||||
const { packageName } = 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),
|
||||
});
|
||||
if (nonInteractive) {
|
||||
let suggestedAndroidApplicationId: string | undefined;
|
||||
try {
|
||||
suggestedAndroidApplicationId = await getSuggestedApplicationIdAsync(
|
||||
graphqlClient,
|
||||
exp,
|
||||
projectId
|
||||
);
|
||||
} catch {
|
||||
// If we can't get a suggestion, we'll throw the non-interactive error below
|
||||
}
|
||||
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);
|
||||
rawStaticConfig.expo = {
|
||||
|
||||
@@ -115,20 +115,81 @@ describe(ensureBundleIdentifierIsDefinedForManagedProjectAsync, () => {
|
||||
})
|
||||
).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>());
|
||||
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': '{ "blah": {} }',
|
||||
'app.json': '{ "expo": {} }',
|
||||
},
|
||||
'/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(
|
||||
ensureBundleIdentifierIsDefinedForManagedProjectAsync({
|
||||
graphqlClient,
|
||||
projectDir: '/app',
|
||||
projectId: '1234',
|
||||
exp: {} as any,
|
||||
exp: { slug: '' } as any,
|
||||
vcsClient,
|
||||
nonInteractive: true,
|
||||
})
|
||||
|
||||
@@ -137,15 +137,8 @@ async function configureBundleIdentifierAsync({
|
||||
exp: ExpoConfig;
|
||||
nonInteractive: boolean;
|
||||
}): 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);
|
||||
|
||||
// we can't automatically update app.config.js
|
||||
if (paths.dynamicConfigPath) {
|
||||
throw new Error(
|
||||
@@ -155,26 +148,51 @@ async function configureBundleIdentifierAsync({
|
||||
|
||||
assert(paths.staticConfigPath, 'app.json must exist');
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
`${chalk.bold(`📝 iOS Bundle Identifier`)} ${chalk.dim(
|
||||
learnMore('https://expo.fyi/bundle-identifier')
|
||||
)}`
|
||||
);
|
||||
let bundleIdentifier: string;
|
||||
|
||||
const suggestedBundleIdentifier = await getSuggestedBundleIdentifierAsync(
|
||||
graphqlClient,
|
||||
exp,
|
||||
projectId
|
||||
);
|
||||
if (nonInteractive) {
|
||||
let suggestedBundleIdentifier: string | undefined;
|
||||
try {
|
||||
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({
|
||||
name: 'bundleIdentifier',
|
||||
type: 'text',
|
||||
message: `What would you like your iOS bundle identifier to be?`,
|
||||
initial: suggestedBundleIdentifier,
|
||||
validate: value => (isBundleIdentifierValid(value) ? true : INVALID_BUNDLE_IDENTIFIER_MESSAGE),
|
||||
});
|
||||
const result = await promptAsync({
|
||||
name: 'bundleIdentifier',
|
||||
type: 'text',
|
||||
message: `What would you like your iOS bundle identifier to be?`,
|
||||
initial: suggestedBundleIdentifier,
|
||||
validate: value =>
|
||||
isBundleIdentifierValid(value) ? true : INVALID_BUNDLE_IDENTIFIER_MESSAGE,
|
||||
});
|
||||
bundleIdentifier = result.bundleIdentifier;
|
||||
}
|
||||
|
||||
const rawStaticConfig = readAppJson(paths.staticConfigPath);
|
||||
rawStaticConfig.expo = {
|
||||
|
||||
Reference in new issue
Block a user