Compare commits

...
10 changed files with 530 additions and 17 deletions

No files matched your search

@@ -0,0 +1,5 @@
---
'eas-cli': patch
---
Validate device names before starting a simulator session and show the available names when a device is not listed. iOS Simulator UDIDs remain supported.
+5
View File
@@ -0,0 +1,5 @@
---
'eas-cli': minor
---
Add `eas simulator:devices` to list the devices Expo suggests for a simulator session.
@@ -0,0 +1,177 @@
import { Config } from '@oclif/core';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import { DeviceRunSessionProfilesQuery } from '../../../graphql/generated';
import { DeviceRunSessionQuery } from '../../../graphql/queries/DeviceRunSessionQuery';
import Log from '../../../log';
import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json';
import SimulatorDevices from '../devices';
jest.mock('../../../graphql/queries/DeviceRunSessionQuery');
jest.mock('../../../log');
jest.mock('../../../ora', () => ({
ora: jest.fn(() => {
const spinner = {
fail: jest.fn(),
start: jest.fn(),
stop: jest.fn(),
};
spinner.start.mockReturnValue(spinner);
return spinner;
}),
}));
jest.mock('../../../utils/json');
jest.mock('chalk', () => ({
__esModule: true,
default: { bold: (text: string) => text, dim: (text: string) => text },
}));
const mockProfilesAsync = jest.mocked(DeviceRunSessionQuery.profilesAsync);
const mockEnableJsonOutput = jest.mocked(enableJsonOutput);
const mockPrintJsonOnlyOutput = jest.mocked(printJsonOnlyOutput);
const mockLog = jest.mocked(Log.log);
const profiles: DeviceRunSessionProfilesQuery['deviceRunSessions']['profiles'] = {
__typename: 'DeviceRunSessionProfiles',
ios: {
__typename: 'IosDeviceRunSessionProfiles',
devices: [
{
__typename: 'IosDeviceRunSessionDevice',
identifier: 'iPhone 17',
displayName: 'iPhone 17',
},
{
__typename: 'IosDeviceRunSessionDevice',
identifier: 'iPhone 17 Pro',
displayName: 'iPhone 17 Pro',
},
],
},
android: {
__typename: 'AndroidDeviceRunSessionProfiles',
devices: [
{
__typename: 'AndroidDeviceRunSessionDevice',
identifier: 'medium_phone',
displayName: 'Medium Phone',
},
{
__typename: 'AndroidDeviceRunSessionDevice',
identifier: 'pixel_9',
displayName: 'Pixel 9',
},
],
},
};
const iosDevices = [
{ identifier: 'iPhone 17', displayName: 'iPhone 17' },
{ identifier: 'iPhone 17 Pro', displayName: 'iPhone 17 Pro' },
];
const androidDevices = [
{ identifier: 'medium_phone', displayName: 'Medium Phone' },
{ identifier: 'pixel_9', displayName: 'Pixel 9' },
];
function getMockOclifConfig(): Config {
const config = new Config({ root: __dirname });
config.runHook = async () => ({
failures: [],
successes: [],
});
return config;
}
describe(SimulatorDevices, () => {
const graphqlClient = {} as ExpoGraphqlClient;
const mockConfig = getMockOclifConfig();
beforeEach(() => {
jest.clearAllMocks();
mockProfilesAsync.mockResolvedValue(profiles);
});
function createCommand(argv: string[]): SimulatorDevices {
const command = new SimulatorDevices(argv, mockConfig);
// @ts-expect-error getContextAsync is protected
jest.spyOn(command, 'getContextAsync').mockResolvedValue({
loggedIn: { graphqlClient },
});
return command;
}
it('does not need a project', () => {
expect(Object.keys(SimulatorDevices.contextDefinition)).toEqual(['loggedIn']);
});
it('logs the devices of both platforms', async () => {
await createCommand([]).runAsync();
expect(mockProfilesAsync).toHaveBeenCalledWith(graphqlClient);
expect(mockLog).toHaveBeenCalledWith(
[
'Platform Device --device value',
'iOS iPhone 17 "iPhone 17"',
'iOS iPhone 17 Pro "iPhone 17 Pro"',
'Android Medium Phone medium_phone',
'Android Pixel 9 pixel_9',
].join('\n')
);
expect(mockLog).toHaveBeenCalledWith(
'Start a session with: eas simulator:start --platform ios --device "iPhone 17"'
);
expect(mockPrintJsonOnlyOutput).not.toHaveBeenCalled();
});
it('logs the devices of one platform with --platform', async () => {
await createCommand(['--platform', 'android']).runAsync();
expect(mockLog).toHaveBeenCalledWith(
[
'Platform Device --device value',
'Android Medium Phone medium_phone',
'Android Pixel 9 pixel_9',
].join('\n')
);
expect(mockLog).toHaveBeenCalledWith(
'Start a session with: eas simulator:start --platform android --device medium_phone'
);
});
it('says so when the service lists no devices', async () => {
mockProfilesAsync.mockResolvedValue({
...profiles,
ios: { ...profiles.ios, devices: [] },
android: { ...profiles.android, devices: [] },
});
await createCommand([]).runAsync();
expect(mockLog).toHaveBeenCalledTimes(1);
expect(mockLog).toHaveBeenCalledWith('No simulator devices found.');
});
it('emits JSON for both platforms', async () => {
await createCommand(['--json']).runAsync();
expect(mockEnableJsonOutput).toHaveBeenCalled();
expect(mockPrintJsonOnlyOutput).toHaveBeenCalledWith({
ios: iosDevices,
android: androidDevices,
});
expect(mockLog).not.toHaveBeenCalled();
});
it('emits JSON for one platform with --platform', async () => {
await createCommand(['--json', '-p', 'ios']).runAsync();
expect(mockPrintJsonOnlyOutput).toHaveBeenCalledWith({ ios: iosDevices });
});
it('propagates a query failure', async () => {
mockProfilesAsync.mockRejectedValue(new Error('network down'));
await expect(createCommand([]).runAsync()).rejects.toThrow('network down');
});
});
@@ -113,6 +113,7 @@ const mockEnsureDeviceRunSessionStoppedAsync = jest.mocked(
DeviceRunSessionMutation.ensureDeviceRunSessionStoppedAsync
);
const mockAvailabilityByAppIdAsync = jest.mocked(DeviceRunSessionAvailabilityQuery.byAppIdAsync);
const mockProfilesAsync = jest.mocked(DeviceRunSessionQuery.profilesAsync);
const mockByIdAsync = jest.mocked(DeviceRunSessionQuery.byIdAsync);
const mockJobRunErrorsByIdAsync = jest.mocked(DeviceRunSessionQuery.jobRunErrorsByIdAsync);
const mockFetch = jest.mocked(fetch);
@@ -205,6 +206,20 @@ describe(Simulator, () => {
delete process.env[EAS_SIMULATOR_SESSION_ID];
mockJobRunErrorsByIdAsync.mockResolvedValue(null);
mockAvailabilityByAppIdAsync.mockResolvedValue({ accountName: 'testuser', available: true });
mockProfilesAsync.mockReset().mockResolvedValue({
ios: {
devices: [
{ identifier: 'iPhone 17', displayName: 'iPhone 17' },
{ identifier: 'iPhone 17 Pro', displayName: 'iPhone 17 Pro' },
],
},
android: {
devices: [
{ identifier: 'medium_phone', displayName: 'Medium Phone' },
{ identifier: 'pixel_9', displayName: 'Pixel 9' },
],
},
});
mockCreateDeviceRunSessionAsync.mockResolvedValue(makeCreatedDeviceRunSession());
mockEnsureDeviceRunSessionStoppedAsync.mockResolvedValue({
id: 'session-123',
@@ -287,12 +302,21 @@ describe(Simulator, () => {
expect(mockCreateDeviceRunSessionAsync).not.toHaveBeenCalled();
});
it('rejects an unlisted imported device before creating a session', async () => {
config({ platform: 'ios', device: 'iPhnoe 17' });
const { command } = createCommand(['--import', configPath, '--json']);
await expect(command.runAsync()).rejects.toThrow('Unknown iOS device "iPhnoe 17".');
expect(mockCreateDeviceRunSessionAsync).not.toHaveBeenCalled();
expect(applyStartConfigAsync).not.toHaveBeenCalled();
});
it('creates from saved flags with explicit CLI overrides and unchanged output without actions', async () => {
config({
platform: 'ios',
name: 'Saved name',
type: 'web-preview-only',
device: 'Saved phone',
device: 'iPhone 17',
tag: ['saved'],
});
const { command } = createCommand(['--import', configPath, '--name', 'CLI name', '--json']);
@@ -302,7 +326,7 @@ describe(Simulator, () => {
expect.objectContaining({
name: 'CLI name',
type: DeviceRunSessionType.WebPreviewOnly,
ios: { deviceIdentifier: 'Saved phone' },
ios: { deviceIdentifier: 'iPhone 17' },
tags: ['saved'],
})
);
@@ -328,7 +352,7 @@ describe(Simulator, () => {
platform,
type: 'web-preview-only',
name: 'Startup regression',
device: platform === 'ios' ? 'iPhone 17 Pro' : 'pixel_7',
device: platform === 'ios' ? 'iPhone 17 Pro' : 'pixel_9',
'out-config-type': output,
...(egress ? { egress: 'local' } : {}),
};
@@ -1952,19 +1976,109 @@ describe(Simulator, () => {
expect(input).not.toHaveProperty('agentSessionId');
});
it.each([
{
platform: 'ios',
device: 'pixel_9',
message:
'Unknown iOS device "pixel_9".\n' +
'Available --device values:\n "iPhone 17"\n "iPhone 17 Pro"\n' +
'Run eas simulator:devices --platform ios to list devices.',
},
{
platform: 'android',
device: 'iPhone 17',
message:
'Unknown Android device "iPhone 17".\n' +
'Available --device values:\n "medium_phone" (Medium Phone)\n "pixel_9" (Pixel 9)\n' +
'Run eas simulator:devices --platform android to list devices.',
},
])(
'rejects an unlisted $platform device and lists that platform',
async ({ platform, device, message }) => {
const { command } = createCommand(['--platform', platform, '--device', device, '--json']);
await expect(command.runAsync()).rejects.toThrow(message);
expect(mockProfilesAsync).toHaveBeenCalledWith(graphqlClient);
expect(mockCreateDeviceRunSessionAsync).not.toHaveBeenCalled();
expect(fs.writeFile).not.toHaveBeenCalled();
expect(Log.log).not.toHaveBeenCalled();
expect(mockPrintJsonOnlyOutput).not.toHaveBeenCalled();
}
);
it('validates against the platform selected interactively', async () => {
mockPromptAsync.mockResolvedValueOnce({ selectedPlatform: AppPlatform.Android });
const { command } = createCommand(['--device', 'iPhone 17']);
await expect(command.runAsync()).rejects.toThrow('Unknown Android device "iPhone 17".');
expect(mockCreateDeviceRunSessionAsync).not.toHaveBeenCalled();
});
it('does not create a session if device profiles cannot be fetched', async () => {
mockProfilesAsync.mockRejectedValueOnce(new Error('network down'));
const { command } = createCommand(['--platform', 'ios', '--device', 'iPhone 17', '--json']);
await expect(command.runAsync()).rejects.toThrow('network down');
expect(mockCreateDeviceRunSessionAsync).not.toHaveBeenCalled();
});
it('handles an empty device list before creating a session', async () => {
mockProfilesAsync.mockResolvedValueOnce({
ios: { devices: [] },
android: { devices: [] },
});
const { command } = createCommand(['--platform', 'ios', '--device', 'iPhone 17', '--json']);
await expect(command.runAsync()).rejects.toThrow('No devices are currently listed.');
expect(mockCreateDeviceRunSessionAsync).not.toHaveBeenCalled();
});
it.each([
'123e4567-e89b-42d3-a456-426614174000',
'12345678-1234-1234-1234-123456789abc',
'12345678-1234-1234-1234-123456789ABC',
])('forwards iOS UDID %s without fetching device profiles', async udid => {
const { command } = createCommand(['--platform', 'ios', '--device', udid, '--json']);
await command.runAsync();
expect(mockProfilesAsync).not.toHaveBeenCalled();
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(
graphqlClient,
expect.objectContaining({ ios: { deviceIdentifier: udid } })
);
});
it('rejects an arbitrary hash as an unlisted iOS device name', async () => {
const hash = '12345678123412341234123456789abc';
const { command } = createCommand(['--platform', 'ios', '--device', hash, '--json']);
await expect(command.runAsync()).rejects.toThrow(`Unknown iOS device "${hash}".`);
expect(mockProfilesAsync).toHaveBeenCalledWith(graphqlClient);
expect(mockCreateDeviceRunSessionAsync).not.toHaveBeenCalled();
});
it('does not fetch device profiles when no device is requested', async () => {
const { command } = createCommand(['--platform', 'ios', '--json']);
await command.runAsync();
expect(mockProfilesAsync).not.toHaveBeenCalled();
expect(mockCreateDeviceRunSessionAsync.mock.calls[0][1]).not.toHaveProperty('ios');
});
it('forwards --device in the iOS create options', async () => {
const { command } = createCommand([
'--platform',
'ios',
'--non-interactive',
'--device',
'iPhone 16 Pro',
'iPhone 17 Pro',
]);
await command.runAsync();
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(
graphqlClient,
expect.objectContaining({ ios: { deviceIdentifier: 'iPhone 16 Pro' } })
expect.objectContaining({ ios: { deviceIdentifier: 'iPhone 17 Pro' } })
);
});
@@ -2060,13 +2174,13 @@ describe(Simulator, () => {
'ios',
'--non-interactive',
'--device',
' iPhone 16 Pro ',
' iPhone 17 Pro ',
]);
await command.runAsync();
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(
graphqlClient,
expect.objectContaining({ ios: { deviceIdentifier: 'iPhone 16 Pro' } })
expect.objectContaining({ ios: { deviceIdentifier: 'iPhone 17 Pro' } })
);
});
@@ -2075,6 +2189,7 @@ describe(Simulator, () => {
await command.runAsync();
expect(mockCreateDeviceRunSessionAsync.mock.calls[0][1]).not.toHaveProperty('ios');
expect(mockProfilesAsync).not.toHaveBeenCalled();
});
it('omits resourceClass when --resource-class is not set', async () => {
@@ -0,0 +1,123 @@
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import EasCommand from '../../commandUtils/EasCommand';
import {
EasNonInteractiveAndJsonFlags,
resolveNonInteractiveAndJsonFlags,
} from '../../commandUtils/flags';
import { DeviceRunSessionQuery } from '../../graphql/queries/DeviceRunSessionQuery';
import Log from '../../log';
import { ora } from '../../ora';
import { appPlatformDisplayNames } from '../../platform';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import {
getSimulatorCommandAliases,
getSimulatorCommandHiddenAliases,
} from '../../simulator/commandAliases';
import {
DEVICE_RUN_SESSION_PLATFORM_BY_FLAG_VALUE,
DEVICE_RUN_SESSION_PLATFORM_FLAG_VALUES,
DeviceRunSessionPlatformFlagValue,
} from '../../simulator/utils';
const PLATFORM_ORDER: readonly DeviceRunSessionPlatformFlagValue[] = ['ios', 'android'];
function formatDeviceFlagValue(identifier: string): string {
return /^[\w-]+$/.test(identifier) ? identifier : JSON.stringify(identifier);
}
export default class SimulatorDevices extends EasCommand {
static override aliases = getSimulatorCommandAliases('devices');
static override hiddenAliases = getSimulatorCommandHiddenAliases('devices');
static override description = 'list available devices for a remote simulator session';
static override flags = {
platform: Flags.option({
char: 'p',
description: 'Show the devices of one platform',
options: DEVICE_RUN_SESSION_PLATFORM_FLAG_VALUES,
})(),
...EasNonInteractiveAndJsonFlags,
};
static override contextDefinition = {
...this.ContextOptions.LoggedIn,
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(SimulatorDevices);
const { json: jsonFlag, nonInteractive } = resolveNonInteractiveAndJsonFlags(flags);
if (jsonFlag) {
enableJsonOutput();
}
const {
loggedIn: { graphqlClient },
} = await this.getContextAsync(SimulatorDevices, {
nonInteractive,
});
const fetchSpinner = jsonFlag ? null : ora('Fetching simulator devices').start();
let profiles;
try {
profiles = await DeviceRunSessionQuery.profilesAsync(graphqlClient);
fetchSpinner?.stop();
} catch (err) {
fetchSpinner?.fail('Failed to fetch simulator devices');
throw err;
}
const platforms = PLATFORM_ORDER.filter(
platform => !flags.platform || flags.platform === platform
);
if (jsonFlag) {
printJsonOnlyOutput(
Object.fromEntries(
platforms.map(platform => [
platform,
profiles[platform].devices.map(({ identifier, displayName }) => ({
identifier,
displayName,
})),
])
)
);
return;
}
const devices = platforms.flatMap(platform =>
profiles[platform].devices.map(({ identifier, displayName }) => ({
platform,
displayName,
flagValue: formatDeviceFlagValue(identifier),
}))
);
if (devices.length === 0) {
Log.log('No simulator devices found.');
return;
}
const header = ['Platform', 'Device', '--device value'];
const rows = devices.map(({ platform, displayName, flagValue }) => [
appPlatformDisplayNames[DEVICE_RUN_SESSION_PLATFORM_BY_FLAG_VALUE[platform]],
displayName,
flagValue,
]);
const widths = header.map((title, column) =>
Math.max(title.length, ...rows.map(row => row[column].length))
);
const formatRow = (row: string[]): string =>
row
.map((cell, column) => cell.padEnd(widths[column]))
.join(' ')
.trimEnd();
Log.log([chalk.dim(formatRow(header)), ...rows.map(formatRow)].join('\n'));
Log.newLine();
Log.log(
`Start a session with: eas simulator:start --platform ${devices[0].platform} --device ${devices[0].flagValue}`
);
}
}
@@ -63,11 +63,14 @@ import {
simulatorRequestProperties,
} from '../../simulator/requestAnalytics';
import {
DEVICE_RUN_SESSION_PLATFORM_BY_FLAG_VALUE,
DEVICE_RUN_SESSION_PLATFORM_FLAG_VALUES,
DEVICE_RUN_SESSION_RESOURCE_CLASS_BY_FLAG_VALUE,
DEVICE_RUN_SESSION_RESOURCE_CLASS_FLAG_VALUES,
DEVICE_RUN_SESSION_TYPE_BY_FLAG_VALUE,
DEVICE_RUN_SESSION_TYPE_FLAG_OPTIONS,
DEVICE_RUN_SESSION_TYPE_FLAG_VALUES,
DeviceRunSessionPlatformFlagValue,
DeviceRunSessionRemoteConfig,
formatRemoteSessionInstructions,
formatSimulatorUnavailableMessage,
@@ -88,13 +91,9 @@ const OUT_CONFIG_TYPE_VALUES = {
Env: 'env',
Dotenv: 'dotenv',
} as const;
const PLATFORM_FLAG_VALUES = ['android', 'ios'] as const;
type PlatformFlagValue = (typeof PLATFORM_FLAG_VALUES)[number];
const EGRESS_FLAG_VALUES = ['local'] as const;
const APP_PLATFORM_BY_FLAG_VALUE: Record<PlatformFlagValue, AppPlatform> = {
android: AppPlatform.Android,
ios: AppPlatform.Ios,
};
const IOS_SIMULATOR_UDID_PATTERN =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
export default class Simulator extends EasCommand {
static override aliases = ['simulator:start', ...getSimulatorCommandAliases('start')];
@@ -131,7 +130,7 @@ With local egress, actions wait for the connection and require interactive mode.
platform: Flags.option({
char: 'p',
description: 'Device platform',
options: PLATFORM_FLAG_VALUES,
options: DEVICE_RUN_SESSION_PLATFORM_FLAG_VALUES,
})(),
name: Flags.string({
description:
@@ -144,7 +143,7 @@ With local egress, actions wait for the connection and require interactive mode.
}),
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 listed Simulator device name or UDID (e.g. "iPhone 17 Pro"). On Android, a listed AVD hardware profile id (e.g. "pixel_9"). Defaults to a device chosen by the runner. Run eas simulator:devices to list device names.',
}),
'system-image': Flags.string({
description:
@@ -399,6 +398,10 @@ With local egress, actions wait for the connection and require interactive mode.
);
}
if (deviceIdentifier) {
await validateDeviceIdentifierAsync(graphqlClient, platform, deviceIdentifier);
}
const expoGoSdkVersion = flags['expo-go']
? await resolveExpoGoSdkVersionAsync({ projectDir, sdkVersion: sdkVersionFromFlag })
: undefined;
@@ -896,12 +899,44 @@ async function formatJobRunErrorsAsync(
return errors.map(error => `\n${formatSimulatorSessionError(error)}`).join('');
}
async function validateDeviceIdentifierAsync(
graphqlClient: ExpoGraphqlClient,
platform: AppPlatform,
deviceIdentifier: string
): Promise<void> {
if (platform === AppPlatform.Ios && IOS_SIMULATOR_UDID_PATTERN.test(deviceIdentifier)) {
return;
}
const platformName = platform === AppPlatform.Ios ? 'iOS' : 'Android';
const platformFlag = platformName.toLowerCase() as DeviceRunSessionPlatformFlagValue;
const profiles = await DeviceRunSessionQuery.profilesAsync(graphqlClient);
const devices = profiles[platformFlag].devices;
if (devices.some(({ identifier }) => identifier === deviceIdentifier)) {
return;
}
const deviceList = devices
.map(
({ identifier, displayName }) =>
` ${JSON.stringify(identifier)}${identifier === displayName ? '' : ` (${displayName})`}`
)
.join('\n');
throw new EasCommandError(
`Unknown ${platformName} device ${JSON.stringify(deviceIdentifier)}.\n` +
(deviceList
? `Available --device values:\n${deviceList}\n`
: 'No devices are currently listed.\n') +
`Run eas simulator:devices --platform ${platformFlag} to list devices.`
);
}
async function resolvePlatformAsync(
platform: PlatformFlagValue | undefined,
platform: DeviceRunSessionPlatformFlagValue | undefined,
nonInteractive: boolean
): Promise<AppPlatform> {
if (platform) {
return APP_PLATFORM_BY_FLAG_VALUE[platform];
return DEVICE_RUN_SESSION_PLATFORM_BY_FLAG_VALUE[platform];
}
if (nonInteractive) {
+5
View File
@@ -16432,6 +16432,11 @@ export type DeviceRunSessionByIdQuery = { __typename?: 'RootQuery', deviceRunSes
| { __typename: 'WebPreviewOnlyRunSessionRemoteConfig', egressUrl?: string | null, egressToken?: string | null, egressFingerprint?: string | null, egressPort?: number | null, previewApiUrl?: string | null, previewUrl: string, previewToken?: string | null }
| null, turtleJobRun?: { __typename?: 'JobRun', id: string, status: JobRunStatus } | null } } };
export type DeviceRunSessionProfilesQueryVariables = Exact<{ [key: string]: never; }>;
export type DeviceRunSessionProfilesQuery = { __typename?: 'RootQuery', deviceRunSessions: { __typename?: 'DeviceRunSessionQuery', profiles: { __typename?: 'DeviceRunSessionProfiles', ios: { __typename?: 'IosDeviceRunSessionProfiles', devices: Array<{ __typename?: 'IosDeviceRunSessionDevice', identifier: string, displayName: string }> }, android: { __typename?: 'AndroidDeviceRunSessionProfiles', devices: Array<{ __typename?: 'AndroidDeviceRunSessionDevice', identifier: string, displayName: string }> } } } };
export type DeviceRunSessionsByAppIdQueryVariables = Exact<{
appId: Scalars['String']['input'];
first?: InputMaybe<Scalars['Int']['input']>;
@@ -11,6 +11,8 @@ import {
DeviceRunSessionFilterInput,
DeviceRunSessionJobRunErrorsByIdQuery,
DeviceRunSessionJobRunErrorsByIdQueryVariables,
DeviceRunSessionProfilesQuery,
DeviceRunSessionProfilesQueryVariables,
DeviceRunSessionsByAppIdQuery,
DeviceRunSessionsByAppIdQueryVariables,
} from '../generated';
@@ -200,6 +202,39 @@ export const DeviceRunSessionQuery = {
);
return data.deviceRunSessions.byId;
},
async profilesAsync(
graphqlClient: ExpoGraphqlClient
): Promise<DeviceRunSessionProfilesQuery['deviceRunSessions']['profiles']> {
const data = await withErrorHandlingAsync(
graphqlClient
.query<DeviceRunSessionProfilesQuery, DeviceRunSessionProfilesQueryVariables>(
gql`
query DeviceRunSessionProfilesQuery {
deviceRunSessions {
profiles {
ios {
devices {
identifier
displayName
}
}
android {
devices {
identifier
displayName
}
}
}
}
}
`,
{},
{ requestPolicy: 'network-only' }
)
.toPromise()
);
return data.deviceRunSessions.profiles;
},
async listByAppIdAsync(
graphqlClient: ExpoGraphqlClient,
{
@@ -1,5 +1,6 @@
import SimulatorAvailability from '../../commands/simulator/availability';
import SimulatorControl from '../../commands/simulator/control';
import SimulatorDevices from '../../commands/simulator/devices';
import SimulatorEgress from '../../commands/simulator/egress';
import SimulatorEvents from '../../commands/simulator/events';
import SimulatorExec from '../../commands/simulator/exec';
@@ -14,6 +15,7 @@ describe('simulator command aliases', () => {
it.each([
['availability', SimulatorAvailability],
['control', SimulatorControl],
['devices', SimulatorDevices],
['egress', SimulatorEgress],
['events', SimulatorEvents],
['exec', SimulatorExec],
+11
View File
@@ -56,6 +56,17 @@ export const DEVICE_RUN_SESSION_TYPE_FLAG_OPTIONS = [
...new Set(Object.values(DEVICE_RUN_SESSION_TYPE_FLAG_VALUES)),
];
export const DEVICE_RUN_SESSION_PLATFORM_FLAG_VALUES = ['android', 'ios'] as const;
export type DeviceRunSessionPlatformFlagValue =
(typeof DEVICE_RUN_SESSION_PLATFORM_FLAG_VALUES)[number];
export const DEVICE_RUN_SESSION_PLATFORM_BY_FLAG_VALUE: Record<
DeviceRunSessionPlatformFlagValue,
AppPlatform
> = {
android: AppPlatform.Android,
ios: AppPlatform.Ios,
};
// Every enum value behind a flag value, so filtering by web-preview-only also
// matches sessions created with the legacy ServeSim type.
export function deviceRunSessionTypesForFlagValue(value: string): DeviceRunSessionType[] {