Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ This is the log of notable changes to EAS CLI and related packages.

### 🎉 New features

- [eas-cli] Add `eas simulator:profiles` to list available EAS Simulator device and system image profiles. ([#4323](https://github.com/expo/eas-cli/pull/4323) by [@krystofwoldrich-agent](https://github.com/krystofwoldrich-agent))

### 🐛 Bug fixes

### 🧹 Chores
Expand Down
125 changes: 125 additions & 0 deletions packages/eas-cli/src/commands/simulator/__tests__/profiles.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { Config } from '@oclif/core';
import chalk from 'chalk';

import Log from '../../../log';
import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json';
import SimulatorProfiles from '../profiles';

jest.mock('../../../log');
jest.mock('../../../utils/json');

const DEVICES = [
'pixel_6',
'pixel_6_pro',
'pixel_6a',
'pixel_7',
'pixel_7_pro',
'pixel_7a',
'pixel_8',
'pixel_8_pro',
'pixel_8a',
'pixel_9',
'pixel_9_pro',
'pixel_9_pro_xl',
'pixel_9a',
];
const IMAGES = [
'system-images;android-30;default;x86_64',
'system-images;android-30;google_apis;x86_64',
'system-images;android-32;default;x86_64',
'system-images;android-32;google_apis;x86_64',
'system-images;android-34;default;x86_64',
'system-images;android-34;google_apis;x86_64',
'system-images;android-35;default;x86_64',
'system-images;android-35;google_apis;x86_64',
'system-images;android-36;default;x86_64',
'system-images;android-36;google_apis;x86_64',
];
const NO_IOS_PROFILES_MESSAGE = 'No simulator profiles are available for iOS yet.';

const mockEnableJsonOutput = jest.mocked(enableJsonOutput);
const mockPrintJsonOnlyOutput = jest.mocked(printJsonOnlyOutput);
const mockLog = jest.mocked(Log.log);

function getMockOclifConfig(): Config {
const config = new Config({ root: __dirname });
config.runHook = async () => ({
failures: [],
successes: [],
});
return config;
}

describe(SimulatorProfiles, () => {
const mockConfig = getMockOclifConfig();

beforeEach(() => {
jest.clearAllMocks();
});

it('emits Android profiles as JSON', async () => {
const command = new SimulatorProfiles(['--platform', 'android', '--json'], mockConfig);

await command.runAsync();

expect(mockEnableJsonOutput).toHaveBeenCalledTimes(1);
expect(mockPrintJsonOnlyOutput).toHaveBeenCalledWith({ devices: DEVICES, images: IMAGES });
expect(mockLog).not.toHaveBeenCalled();
});

it('formats Android profiles and marks the default device', async () => {
const command = new SimulatorProfiles(['--platform', 'android'], mockConfig);

await command.runAsync();

expect(mockEnableJsonOutput).not.toHaveBeenCalled();
expect(mockPrintJsonOnlyOutput).not.toHaveBeenCalled();
expect(mockLog).toHaveBeenCalledWith(
[
chalk.bold('Devices:'),
...DEVICES.map(
device =>
` ${device === 'pixel_9' ? chalk.green(device) : device}${
device === 'pixel_9' ? ` ${chalk.green('(default)')}` : ''
}`
),
'',
chalk.bold('System images:'),
...IMAGES.map(image => ` ${image}`),
].join('\n')
);
});

it('reports that iOS profiles are unavailable', async () => {
const command = new SimulatorProfiles(['--platform', 'ios'], mockConfig);

await command.runAsync();

expect(mockLog).toHaveBeenCalledWith(NO_IOS_PROFILES_MESSAGE);
expect(mockPrintJsonOnlyOutput).not.toHaveBeenCalled();
});

it('reports unavailable iOS profiles as a JSON error', async () => {
const command = new SimulatorProfiles(['--platform', 'ios', '--json'], mockConfig);

await command.runAsync();

expect(mockEnableJsonOutput).toHaveBeenCalledTimes(1);
expect(mockPrintJsonOnlyOutput).toHaveBeenCalledWith({ error: NO_IOS_PROFILES_MESSAGE });
expect(mockLog).not.toHaveBeenCalled();
});

it('requires --platform', async () => {
const command = new SimulatorProfiles([], mockConfig);

await expect(command.runAsync()).rejects.toThrow('Missing required flag platform');
});

it('rejects unsupported platforms', async () => {
const command = new SimulatorProfiles(['--platform', 'web'], mockConfig);

await expect(command.runAsync()).rejects.toThrow(
'Expected --platform=web to be one of: android, ios'
);
});
});
93 changes: 93 additions & 0 deletions packages/eas-cli/src/commands/simulator/profiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Flags } from '@oclif/core';
import chalk from 'chalk';

import EasCommand from '../../commandUtils/EasCommand';
import { EasJsonOnlyFlag } from '../../commandUtils/flags';
import Log from '../../log';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';

const PLATFORM_FLAG_VALUES = ['android', 'ios'] as const;
const DEVICES = [
'pixel_6',
'pixel_6_pro',
'pixel_6a',
'pixel_7',
'pixel_7_pro',
'pixel_7a',
'pixel_8',
'pixel_8_pro',
'pixel_8a',
'pixel_9',
'pixel_9_pro',
'pixel_9_pro_xl',
'pixel_9a',
] as const;
const IMAGES = [
'system-images;android-30;default;x86_64',
'system-images;android-30;google_apis;x86_64',
'system-images;android-32;default;x86_64',
'system-images;android-32;google_apis;x86_64',
'system-images;android-34;default;x86_64',
'system-images;android-34;google_apis;x86_64',
'system-images;android-35;default;x86_64',
'system-images;android-35;google_apis;x86_64',
'system-images;android-36;default;x86_64',
'system-images;android-36;google_apis;x86_64',
] as const;
const DEFAULT_DEVICE: (typeof DEVICES)[number] = 'pixel_9';
const NO_IOS_PROFILES_MESSAGE = 'No simulator profiles are available for iOS yet.';

export default class SimulatorProfiles extends EasCommand {
static override hidden = true;
static override aliases = ['sim:profiles'];
static override description =
'[EXPERIMENTAL] list available device and system image profiles for EAS Simulator';

static override flags = {
platform: Flags.option({
char: 'p',
description: 'Device platform',
options: PLATFORM_FLAG_VALUES,
required: true,
})(),
...EasJsonOnlyFlag,
};

async runAsync(): Promise<void> {
const { flags } = await this.parse(SimulatorProfiles);

if (flags.json) {
enableJsonOutput();
}

if (flags.platform === 'ios') {
if (flags.json) {
printJsonOnlyOutput({ error: NO_IOS_PROFILES_MESSAGE });
} else {
Log.log(NO_IOS_PROFILES_MESSAGE);
}
return;
}

if (flags.json) {
printJsonOnlyOutput({ devices: DEVICES, images: IMAGES });
return;
}

Log.log(
[
chalk.bold('Devices:'),
...DEVICES.map(formatDevice),
'',
chalk.bold('System images:'),
...IMAGES.map(image => ` ${image}`),
].join('\n')
);
}
}

function formatDevice(device: (typeof DEVICES)[number]): string {
const formattedDevice = device === DEFAULT_DEVICE ? chalk.green(device) : device;
const defaultLabel = device === DEFAULT_DEVICE ? ` ${chalk.green('(default)')}` : '';
return ` ${formattedDevice}${defaultLabel}`;
}
Loading