Compare commits

...
5 changed files with 222 additions and 22 deletions

No files matched your search

+1
View File
@@ -8,6 +8,7 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features
- [eas-cli] Add `--system-image-package` to `eas simulator` for selecting a validated x86_64 Android emulator system image. ([#4317](https://github.com/expo/eas-cli/pull/4317) by [@szdziedzic](https://github.com/szdziedzic))
- [eas-cli] Update workflow run logs in real time, instead of every 10 seconds. ([#4228](https://github.com/expo/eas-cli/pull/4228) by [@AHGIJMKLKKZNPJKQR](https://github.com/AHGIJMKLKKZNPJKQR))
- [build-tools] Add `expo-device-hub` web previews to Android web-preview-only, Agent Device, Argent, and Appium remote sessions. ([#4304](https://github.com/expo/eas-cli/pull/4304) by [@krystofwoldrich](https://github.com/krystofwoldrich))
- [eas-cli] Add `eas workflow:ssh <workflow-job-id> [command...]` to open an ssh session on the worker running a workflow job. ([#4032](https://github.com/expo/eas-cli/pull/4032) by [@gwdp](https://github.com/gwdp))
+30 -15
View File
@@ -2556,10 +2556,11 @@ _See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/exp
```
USAGE
$ eas sim [-p android|ios] [--name <value>] [--device <value>] [--build-id <value> |
--application-archive-url <value> | --expo-go] [--sdk-version <value>] [--launch-arg <value>...] [--open-url
<value>] [--type agent-device|appium|argent|web-preview-only] [--package-version <value>] [--max-duration-minutes
<value>] [--max-idle-time-minutes <value>] [--force] [--out-config-type env|dotenv] [--json] [--non-interactive]
$ eas sim [-p android|ios] [--name <value>] [--device <value>] [--system-image-package <value>]
[--build-id <value> | --application-archive-url <value> | --expo-go] [--sdk-version <value>] [--launch-arg
<value>...] [--open-url <value>] [--type agent-device|appium|argent|web-preview-only]
[--package-version <value>] [--max-duration-minutes <value>] [--max-idle-time-minutes <value>] [--force]
[--out-config-type env|dotenv] [--json] [--non-interactive]
FLAGS
-p, --platform=<option> Device platform
@@ -2569,7 +2570,7 @@ FLAGS
--build-id=<value> EAS Build to install and launch before the simulator session is ready.
--device=<value> Virtual device to start for the session. On iOS, a Simulator device name or
UDID (e.g. "iPhone 16 Pro"). On Android, an AVD hardware profile id (e.g.
"pixel_7"). Defaults to a device chosen by the runner.
"pixel_9"). Defaults to a device chosen by the runner.
--expo-go Install and launch Expo Go matching the current project's Expo SDK before the
simulator session is ready.
--[no-]force [default: true] Create a new simulator session even when an existing simulator
@@ -2596,6 +2597,10 @@ FLAGS
Defaults to "latest" when omitted.
--sdk-version=<value> Expo SDK version used to select Expo Go when --expo-go is passed. Defaults to
the current project SDK.
--system-image-package=<value> Android SDK system image package to use for the emulator (e.g.
"system-images;android-36;google_apis_playstore;x86_64"). Only supported for
Android and must use the x86_64 architecture required by EAS
nested-virtualization runners.
--type=<option> [default: agent-device] Type of simulator session to create. All session types
include a web preview. agent-device, appium, and argent also include an
automation interface; web-preview-only includes no automation interface.
@@ -2723,10 +2728,11 @@ ALIASES
```
USAGE
$ eas sim:start [-p android|ios] [--name <value>] [--device <value>] [--build-id <value> |
--application-archive-url <value> | --expo-go] [--sdk-version <value>] [--launch-arg <value>...] [--open-url
<value>] [--type agent-device|appium|argent|web-preview-only] [--package-version <value>] [--max-duration-minutes
<value>] [--max-idle-time-minutes <value>] [--force] [--out-config-type env|dotenv] [--json] [--non-interactive]
$ eas sim:start [-p android|ios] [--name <value>] [--device <value>] [--system-image-package <value>]
[--build-id <value> | --application-archive-url <value> | --expo-go] [--sdk-version <value>] [--launch-arg
<value>...] [--open-url <value>] [--type agent-device|appium|argent|web-preview-only]
[--package-version <value>] [--max-duration-minutes <value>] [--max-idle-time-minutes <value>] [--force]
[--out-config-type env|dotenv] [--json] [--non-interactive]
FLAGS
-p, --platform=<option> Device platform
@@ -2736,7 +2742,7 @@ FLAGS
--build-id=<value> EAS Build to install and launch before the simulator session is ready.
--device=<value> Virtual device to start for the session. On iOS, a Simulator device name or
UDID (e.g. "iPhone 16 Pro"). On Android, an AVD hardware profile id (e.g.
"pixel_7"). Defaults to a device chosen by the runner.
"pixel_9"). Defaults to a device chosen by the runner.
--expo-go Install and launch Expo Go matching the current project's Expo SDK before the
simulator session is ready.
--[no-]force [default: true] Create a new simulator session even when an existing simulator
@@ -2763,6 +2769,10 @@ FLAGS
Defaults to "latest" when omitted.
--sdk-version=<value> Expo SDK version used to select Expo Go when --expo-go is passed. Defaults to
the current project SDK.
--system-image-package=<value> Android SDK system image package to use for the emulator (e.g.
"system-images;android-36;google_apis_playstore;x86_64"). Only supported for
Android and must use the x86_64 architecture required by EAS
nested-virtualization runners.
--type=<option> [default: agent-device] Type of simulator session to create. All session types
include a web preview. agent-device, appium, and argent also include an
automation interface; web-preview-only includes no automation interface.
@@ -2803,10 +2813,11 @@ ALIASES
```
USAGE
$ eas simulator:start [-p android|ios] [--name <value>] [--device <value>] [--build-id <value> |
--application-archive-url <value> | --expo-go] [--sdk-version <value>] [--launch-arg <value>...] [--open-url
<value>] [--type agent-device|appium|argent|web-preview-only] [--package-version <value>] [--max-duration-minutes
<value>] [--max-idle-time-minutes <value>] [--force] [--out-config-type env|dotenv] [--json] [--non-interactive]
$ eas simulator:start [-p android|ios] [--name <value>] [--device <value>] [--system-image-package <value>]
[--build-id <value> | --application-archive-url <value> | --expo-go] [--sdk-version <value>] [--launch-arg
<value>...] [--open-url <value>] [--type agent-device|appium|argent|web-preview-only]
[--package-version <value>] [--max-duration-minutes <value>] [--max-idle-time-minutes <value>] [--force]
[--out-config-type env|dotenv] [--json] [--non-interactive]
FLAGS
-p, --platform=<option> Device platform
@@ -2816,7 +2827,7 @@ FLAGS
--build-id=<value> EAS Build to install and launch before the simulator session is ready.
--device=<value> Virtual device to start for the session. On iOS, a Simulator device name or
UDID (e.g. "iPhone 16 Pro"). On Android, an AVD hardware profile id (e.g.
"pixel_7"). Defaults to a device chosen by the runner.
"pixel_9"). Defaults to a device chosen by the runner.
--expo-go Install and launch Expo Go matching the current project's Expo SDK before the
simulator session is ready.
--[no-]force [default: true] Create a new simulator session even when an existing simulator
@@ -2843,6 +2854,10 @@ FLAGS
Defaults to "latest" when omitted.
--sdk-version=<value> Expo SDK version used to select Expo Go when --expo-go is passed. Defaults to
the current project SDK.
--system-image-package=<value> Android SDK system image package to use for the emulator (e.g.
"system-images;android-36;google_apis_playstore;x86_64"). Only supported for
Android and must use the x86_64 architecture required by EAS
nested-virtualization runners.
--type=<option> [default: agent-device] Type of simulator session to create. All session types
include a web preview. agent-device, appium, and argent also include an
automation interface; web-preview-only includes no automation interface.
@@ -508,7 +508,7 @@ describe(Simulator, () => {
);
});
it('forwards --device to the create mutation as deviceIdentifier', async () => {
it('forwards an iOS --device through the iOS-specific input', async () => {
const { command } = createCommand([
'--platform',
'ios',
@@ -520,7 +520,7 @@ describe(Simulator, () => {
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(
graphqlClient,
expect.objectContaining({ deviceIdentifier: 'iPhone 16 Pro' })
expect.objectContaining({ ios: { deviceIdentifier: 'iPhone 16 Pro' } })
);
});
@@ -536,7 +536,7 @@ describe(Simulator, () => {
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(
graphqlClient,
expect.objectContaining({ deviceIdentifier: 'iPhone 16 Pro' })
expect.objectContaining({ ios: { deviceIdentifier: 'iPhone 16 Pro' } })
);
});
@@ -544,10 +544,118 @@ describe(Simulator, () => {
const { command } = createCommand(['--platform', 'ios', '--non-interactive', '--device', ' ']);
await command.runAsync();
expect(mockCreateDeviceRunSessionAsync.mock.calls[0][1]).not.toHaveProperty('ios');
expect(mockCreateDeviceRunSessionAsync.mock.calls[0][1]).not.toHaveProperty('android');
});
it('forwards Android device and system image options through the Android-specific input', async () => {
const { command } = createCommand([
'--platform',
'android',
'--non-interactive',
'--device',
' pixel_8 ',
'--system-image-package',
' system-images;android-36;google_apis_playstore;x86_64 ',
]);
await command.runAsync();
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(
graphqlClient,
expect.objectContaining({ deviceIdentifier: undefined })
expect.objectContaining({
android: {
deviceIdentifier: 'pixel_8',
systemImagePackage: 'system-images;android-36;google_apis_playstore;x86_64',
},
})
);
expect(mockCreateDeviceRunSessionAsync.mock.calls[0][1]).not.toHaveProperty('deviceIdentifier');
});
it('forwards --system-image-package without requiring --device', async () => {
const { command } = createCommand([
'--platform',
'android',
'--non-interactive',
'--system-image-package',
'system-images;android-36-ext1;google_apis_ps16k;x86_64',
]);
await command.runAsync();
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(
graphqlClient,
expect.objectContaining({
android: {
systemImagePackage: 'system-images;android-36-ext1;google_apis_ps16k;x86_64',
},
})
);
});
it('rejects --system-image-package for iOS', async () => {
const { command } = createCommand([
'--platform',
'ios',
'--non-interactive',
'--system-image-package',
'system-images;android-36;google_apis_playstore;x86_64',
]);
await expect(command.runAsync()).rejects.toThrow(
'The --system-image-package flag is only supported for Android simulator sessions.'
);
expect(mockCreateDeviceRunSessionAsync).not.toHaveBeenCalled();
});
it('rejects a blank --system-image-package', async () => {
const { command } = createCommand([
'--platform',
'android',
'--non-interactive',
'--system-image-package',
' ',
]);
await expect(command.runAsync()).rejects.toThrow(
'The --system-image-package value cannot be blank.'
);
expect(mockCreateDeviceRunSessionAsync).not.toHaveBeenCalled();
});
it.each([
'system-images;android-36;google_apis_playstore',
'platforms;android-36;google_apis_playstore;x86_64',
'system-images;36;google_apis_playstore;x86_64',
'system-images;android-36;google apis;x86_64',
'system-images;android-36;google_apis_playstore;x86_64;extra',
])('rejects malformed --system-image-package %s', async systemImagePackage => {
const { command } = createCommand([
'--platform',
'android',
'--non-interactive',
'--system-image-package',
systemImagePackage,
]);
await expect(command.runAsync()).rejects.toThrow(
'Expected an Android SDK package coordinate in the form'
);
expect(mockCreateDeviceRunSessionAsync).not.toHaveBeenCalled();
});
it('rejects a non-x86_64 Android system image', async () => {
const { command } = createCommand([
'--platform',
'android',
'--non-interactive',
'--system-image-package',
'system-images;android-36;google_apis_playstore;arm64-v8a',
]);
await expect(command.runAsync()).rejects.toThrow(
'must be x86_64 because EAS Android simulator sessions use x86_64 nested-virtualization runners'
);
expect(mockCreateDeviceRunSessionAsync).not.toHaveBeenCalled();
});
it('omits resourceClass when --resource-class is not set', async () => {
@@ -54,6 +54,8 @@ const APP_PLATFORM_BY_FLAG_VALUE: Record<PlatformFlagValue, AppPlatform> = {
android: AppPlatform.Android,
ios: AppPlatform.Ios,
};
const ANDROID_SYSTEM_IMAGE_PACKAGE_EXAMPLE =
'system-images;android-36;google_apis_playstore;x86_64';
export default class Simulator extends EasCommand {
static override hidden = true;
@@ -73,7 +75,12 @@ export default class Simulator extends EasCommand {
}),
device: Flags.string({
description:
'Virtual device to start for the session. On iOS, a Simulator device name or UDID (e.g. "iPhone 16 Pro"). On Android, an AVD hardware profile id (e.g. "pixel_7"). Defaults to a device chosen by the runner.',
'Virtual device to start for the session. On iOS, a Simulator device name or UDID (e.g. "iPhone 16 Pro"). On Android, an AVD hardware profile id (e.g. "pixel_9"). Defaults to a device chosen by the runner.',
}),
'system-image-package': Flags.string({
description:
`Android SDK system image package to use for the emulator (e.g. "${ANDROID_SYSTEM_IMAGE_PACKAGE_EXAMPLE}"). ` +
'Only supported for Android and must use the x86_64 architecture required by EAS nested-virtualization runners.',
}),
'build-id': Flags.string({
description: 'EAS Build to install and launch before the simulator session is ready.',
@@ -212,6 +219,10 @@ export default class Simulator extends EasCommand {
}
const platform = await resolvePlatformAsync(flags.platform, nonInteractive);
const androidSystemImagePackage = resolveAndroidSystemImagePackage(
flags['system-image-package'],
platform
);
const expoGoSdkVersion = flags['expo-go']
? await resolveExpoGoSdkVersionAsync({ projectDir, sdkVersion: sdkVersionFromFlag })
: undefined;
@@ -236,7 +247,17 @@ export default class Simulator extends EasCommand {
platform,
type: DEVICE_RUN_SESSION_TYPE_BY_FLAG_VALUE[flags.type],
packageVersion: flags['package-version'],
deviceIdentifier,
...(platform === AppPlatform.Android && (deviceIdentifier || androidSystemImagePackage)
? {
android: {
...(deviceIdentifier ? { deviceIdentifier } : {}),
...(androidSystemImagePackage
? { systemImagePackage: androidSystemImagePackage }
: {}),
},
}
: {}),
...(platform === AppPlatform.Ios && deviceIdentifier ? { ios: { deviceIdentifier } } : {}),
...(buildId ? { buildId } : {}),
...(applicationArchiveUrlFromFlag
? { applicationArchiveUrl: applicationArchiveUrlFromFlag }
@@ -395,6 +416,48 @@ export default class Simulator extends EasCommand {
}
}
function resolveAndroidSystemImagePackage(
rawSystemImagePackage: string | undefined,
platform: AppPlatform
): string | undefined {
if (rawSystemImagePackage === undefined) {
return undefined;
}
const systemImagePackage = rawSystemImagePackage.trim();
if (!systemImagePackage) {
throw new EasCommandError(
`The --system-image-package value cannot be blank. Pass a package such as "${ANDROID_SYSTEM_IMAGE_PACKAGE_EXAMPLE}", or omit the flag to use the default.`
);
}
if (platform !== AppPlatform.Android) {
throw new EasCommandError(
'The --system-image-package flag is only supported for Android simulator sessions. Use --platform android or omit the flag.'
);
}
const [namespace, androidVersion, imageFlavor, architecture, ...extraSegments] =
systemImagePackage.split(';');
const isValidCoordinate =
namespace === 'system-images' &&
/^android-[A-Za-z0-9_.-]+$/.test(androidVersion ?? '') &&
/^[A-Za-z0-9_.-]+$/.test(imageFlavor ?? '') &&
architecture !== undefined &&
extraSegments.length === 0;
if (!isValidCoordinate) {
throw new EasCommandError(
`Invalid --system-image-package "${systemImagePackage}". Expected an Android SDK package coordinate in the form "system-images;android-<API>;image-flavor;x86_64", for example "${ANDROID_SYSTEM_IMAGE_PACKAGE_EXAMPLE}".`
);
}
if (architecture !== 'x86_64') {
throw new EasCommandError(
`The --system-image-package architecture must be x86_64 because EAS Android simulator sessions use x86_64 nested-virtualization runners; received "${architecture}".`
);
}
return systemImagePackage;
}
async function resolvePlatformAsync(
platform: PlatformFlagValue | undefined,
nonInteractive: boolean
@@ -3,13 +3,26 @@ import gql from 'graphql-tag';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { withErrorHandlingAsync } from '../client';
import {
CreateDeviceRunSessionInput,
CreateDeviceRunSessionMutation,
CreateDeviceRunSessionMutationVariables,
EnsureDeviceRunSessionStoppedMutation,
EnsureDeviceRunSessionStoppedMutationVariables,
CreateDeviceRunSessionInput as GeneratedCreateDeviceRunSessionInput,
} from '../generated';
export type CreateDeviceRunSessionInput = Omit<
GeneratedCreateDeviceRunSessionInput,
'deviceIdentifier'
> & {
android?: {
deviceIdentifier?: string;
systemImagePackage?: string;
};
ios?: {
deviceIdentifier?: string;
};
};
export const DeviceRunSessionMutation = {
async createDeviceRunSessionAsync(
graphqlClient: ExpoGraphqlClient,