Skip to content
Merged
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
111 changes: 111 additions & 0 deletions packages/eas-cli/src/commands/simulator/__tests__/get.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Config } from '@oclif/core';

import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import {
DeviceRunSessionByIdQuery,
DeviceRunSessionStatus,
DeviceRunSessionType,
JobRunStatus,
} from '../../../graphql/generated';
import { DeviceRunSessionQuery } from '../../../graphql/queries/DeviceRunSessionQuery';
import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json';
import SimulatorGet from '../get';

jest.mock('../../../graphql/queries/DeviceRunSessionQuery');
jest.mock('../../../log');
jest.mock('../../../ora', () => ({
ora: jest.fn(() => {
const spinner = {
fail: jest.fn(),
start: jest.fn(),
succeed: jest.fn(),
};
spinner.start.mockReturnValue(spinner);
return spinner;
}),
}));
jest.mock('../../../utils/json');

type DeviceRunSessionById = DeviceRunSessionByIdQuery['deviceRunSessions']['byId'];

const mockByIdAsync = jest.mocked(DeviceRunSessionQuery.byIdAsync);
const mockEnableJsonOutput = jest.mocked(enableJsonOutput);
const mockPrintJsonOnlyOutput = jest.mocked(printJsonOnlyOutput);

function makeDeviceRunSession(overrides: Partial<DeviceRunSessionById> = {}): DeviceRunSessionById {
return {
id: 'session-123',
status: DeviceRunSessionStatus.InProgress,
type: DeviceRunSessionType.AgentDevice,
app: {
id: 'app-123',
slug: 'testapp',
ownerAccount: {
id: 'account-123',
name: 'testuser',
},
},
remoteConfig: {
__typename: 'AgentDeviceRunSessionRemoteConfig',
agentDeviceRemoteSessionUrl: 'https://agent.example.com',
agentDeviceRemoteSessionToken: 'token-123',
webPreviewUrl: 'https://preview.example.com',
},
turtleJobRun: {
id: 'job-123',
status: JobRunStatus.InProgress,
},
...overrides,
};
}

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

describe(SimulatorGet, () => {
const graphqlClient = {} as ExpoGraphqlClient;
const mockConfig = getMockOclifConfig();

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

function createCommand(argv: string[]): {
command: SimulatorGet;
getContextAsync: jest.SpyInstance;
} {
const command = new SimulatorGet(argv, mockConfig);
// @ts-expect-error getContextAsync is protected
const getContextAsync = jest.spyOn(command, 'getContextAsync').mockResolvedValue({
loggedIn: { graphqlClient },
});
return { command, getContextAsync };
}

it('emits JSON when --json is passed', async () => {
const session = makeDeviceRunSession();
mockByIdAsync.mockResolvedValue(session);

const { command, getContextAsync } = createCommand(['--id', 'session-123', '--json']);
await command.runAsync();

expect(mockEnableJsonOutput).toHaveBeenCalled();
expect(getContextAsync).toHaveBeenCalledWith(SimulatorGet, {
nonInteractive: true,
});
expect(mockByIdAsync).toHaveBeenCalledWith(graphqlClient, 'session-123');
expect(mockPrintJsonOnlyOutput).toHaveBeenCalledWith({
id: 'session-123',
type: 'agent-device',
status: DeviceRunSessionStatus.InProgress,
jobRunUrl: 'https://expo.dev/accounts/testuser/projects/testapp/job-runs/job-123',
remoteConfig: session.remoteConfig,
});
});
});
31 changes: 27 additions & 4 deletions packages/eas-cli/src/commands/simulator/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ import { Flags } from '@oclif/core';

import { getBareJobRunUrl } from '../../build/utils/url';
import EasCommand from '../../commandUtils/EasCommand';
import { EASNonInteractiveFlag } from '../../commandUtils/flags';
import {
EasNonInteractiveAndJsonFlags,
resolveNonInteractiveAndJsonFlags,
} from '../../commandUtils/flags';
import { DeviceRunSessionStatus } from '../../graphql/generated';
import { DeviceRunSessionQuery } from '../../graphql/queries/DeviceRunSessionQuery';
import Log, { link } from '../../log';
import { ora } from '../../ora';
import { formatRemoteSessionInstructions } from '../../simulator/utils';
import {
deviceRunSessionTypeToFlagValue,
formatRemoteSessionInstructions,
} from '../../simulator/utils';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';

export default class SimulatorGet extends EasCommand {
static override hidden = true;
Expand All @@ -19,7 +26,7 @@ export default class SimulatorGet extends EasCommand {
description: 'Device run session ID',
required: true,
}),
...EASNonInteractiveFlag,
...EasNonInteractiveAndJsonFlags,
};

static override contextDefinition = {
Expand All @@ -28,11 +35,16 @@ export default class SimulatorGet extends EasCommand {

async runAsync(): Promise<void> {
const { flags } = await this.parse(SimulatorGet);
const { json: jsonFlag, nonInteractive } = resolveNonInteractiveAndJsonFlags(flags);

if (jsonFlag) {
enableJsonOutput();
}

const {
loggedIn: { graphqlClient },
} = await this.getContextAsync(SimulatorGet, {
nonInteractive: flags['non-interactive'],
nonInteractive,
});

const fetchSpinner = ora(`Fetching device run session ${flags.id}`).start();
Expand All @@ -49,6 +61,17 @@ export default class SimulatorGet extends EasCommand {
? getBareJobRunUrl(session.app.ownerAccount.name, session.app.slug, session.turtleJobRun.id)
: '';

if (jsonFlag) {
printJsonOnlyOutput({
id: session.id,
type: deviceRunSessionTypeToFlagValue(session.type),
status: session.status,
jobRunUrl: jobRunUrl || undefined,
remoteConfig: session.remoteConfig,
});
return;
}

Log.newLine();
Log.log(`ID: ${session.id}`);
Log.log(`Type: ${session.type}`);
Expand Down
16 changes: 2 additions & 14 deletions packages/eas-cli/src/commands/simulator/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { DeviceRunSessionQuery } from '../../graphql/queries/DeviceRunSessionQue
import Log, { link } from '../../log';
import { ora } from '../../ora';
import {
DEVICE_RUN_SESSION_TYPE_BY_FLAG_VALUE,
DEVICE_RUN_SESSION_TYPE_FLAG_VALUES,
DeviceRunSessionRemoteConfig,
formatRemoteSessionInstructions,
} from '../../simulator/utils';
Expand All @@ -28,20 +30,6 @@ import nullthrows from 'nullthrows';
const POLL_INTERVAL_MS = 5_000; // 5 seconds
const POLL_TIMEOUT_MS = 15 * 60 * 1_000; // 15 minutes

// Mapping enum → CLI flag value. Declared as Record<DeviceRunSessionType, string>
// so adding a new enum value in codegen fails the build until it is wired up here.
const DEVICE_RUN_SESSION_TYPE_FLAG_VALUES: Record<DeviceRunSessionType, string> = {
[DeviceRunSessionType.AgentDevice]: 'agent-device',
[DeviceRunSessionType.Argent]: 'argent',
[DeviceRunSessionType.ServeSim]: 'serve-sim',
};

const DEVICE_RUN_SESSION_TYPE_BY_FLAG_VALUE = Object.fromEntries(
(Object.entries(DEVICE_RUN_SESSION_TYPE_FLAG_VALUES) as [DeviceRunSessionType, string][]).map(
([type, value]) => [value, type]
)
) as Record<string, DeviceRunSessionType>;

export default class SimulatorStart extends EasCommand {
static override hidden = true;
static override description =
Expand Down
20 changes: 19 additions & 1 deletion packages/eas-cli/src/simulator/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
import { DeviceRunSessionByIdQuery } from '../graphql/generated';
import { DeviceRunSessionByIdQuery, DeviceRunSessionType } from '../graphql/generated';

type DeviceRunSessionByIdResult = DeviceRunSessionByIdQuery['deviceRunSessions']['byId'];
export type DeviceRunSessionRemoteConfig = NonNullable<DeviceRunSessionByIdResult['remoteConfig']>;

// Mapping enum -> CLI flag value. Declared as Record<DeviceRunSessionType, string>
// so adding a new enum value in codegen fails the build until it is wired up here.
export const DEVICE_RUN_SESSION_TYPE_FLAG_VALUES: Record<DeviceRunSessionType, string> = {
[DeviceRunSessionType.AgentDevice]: 'agent-device',
[DeviceRunSessionType.Argent]: 'argent',
[DeviceRunSessionType.ServeSim]: 'serve-sim',
};

export const DEVICE_RUN_SESSION_TYPE_BY_FLAG_VALUE = Object.fromEntries(
(Object.entries(DEVICE_RUN_SESSION_TYPE_FLAG_VALUES) as [DeviceRunSessionType, string][]).map(
([type, value]) => [value, type]
)
) as Record<string, DeviceRunSessionType>;

export function deviceRunSessionTypeToFlagValue(type: DeviceRunSessionType): string {
return DEVICE_RUN_SESSION_TYPE_FLAG_VALUES[type];
}

export function formatRemoteSessionInstructions(
remoteConfig: DeviceRunSessionRemoteConfig
): string {
Expand Down
Loading