From 8fcdf0fc74d4d8be930e9e1288d91de19f7f739f Mon Sep 17 00:00:00 2001 From: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com> Date: Wed, 20 May 2026 18:46:28 +0200 Subject: [PATCH 1/3] feat(simulator): add get --json output --- .../commands/simulator/__tests__/get.test.ts | 111 ++++++++++++++++++ .../eas-cli/src/commands/simulator/get.ts | 31 ++++- .../eas-cli/src/commands/simulator/start.ts | 16 +-- packages/eas-cli/src/simulator/utils.ts | 20 +++- 4 files changed, 159 insertions(+), 19 deletions(-) create mode 100644 packages/eas-cli/src/commands/simulator/__tests__/get.test.ts diff --git a/packages/eas-cli/src/commands/simulator/__tests__/get.test.ts b/packages/eas-cli/src/commands/simulator/__tests__/get.test.ts new file mode 100644 index 0000000000..b1c6510084 --- /dev/null +++ b/packages/eas-cli/src/commands/simulator/__tests__/get.test.ts @@ -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 { + 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, + }); + }); +}); diff --git a/packages/eas-cli/src/commands/simulator/get.ts b/packages/eas-cli/src/commands/simulator/get.ts index c85faea417..6a829b4c5e 100644 --- a/packages/eas-cli/src/commands/simulator/get.ts +++ b/packages/eas-cli/src/commands/simulator/get.ts @@ -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; @@ -19,7 +26,7 @@ export default class SimulatorGet extends EasCommand { description: 'Device run session ID', required: true, }), - ...EASNonInteractiveFlag, + ...EasNonInteractiveAndJsonFlags, }; static override contextDefinition = { @@ -28,11 +35,16 @@ export default class SimulatorGet extends EasCommand { async runAsync(): Promise { 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(); @@ -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}`); diff --git a/packages/eas-cli/src/commands/simulator/start.ts b/packages/eas-cli/src/commands/simulator/start.ts index d46b9a47ab..d7a435b1ee 100644 --- a/packages/eas-cli/src/commands/simulator/start.ts +++ b/packages/eas-cli/src/commands/simulator/start.ts @@ -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'; @@ -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 -// 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.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; - export default class SimulatorStart extends EasCommand { static override hidden = true; static override description = diff --git a/packages/eas-cli/src/simulator/utils.ts b/packages/eas-cli/src/simulator/utils.ts index 34665d960b..fde3ac5247 100644 --- a/packages/eas-cli/src/simulator/utils.ts +++ b/packages/eas-cli/src/simulator/utils.ts @@ -1,8 +1,26 @@ -import { DeviceRunSessionByIdQuery } from '../graphql/generated'; +import { DeviceRunSessionByIdQuery, DeviceRunSessionType } from '../graphql/generated'; type DeviceRunSessionByIdResult = DeviceRunSessionByIdQuery['deviceRunSessions']['byId']; export type DeviceRunSessionRemoteConfig = NonNullable; +// Mapping enum -> CLI flag value. Declared as Record +// 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.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; + +export function deviceRunSessionTypeToFlagValue(type: DeviceRunSessionType): string { + return DEVICE_RUN_SESSION_TYPE_FLAG_VALUES[type]; +} + export function formatRemoteSessionInstructions( remoteConfig: DeviceRunSessionRemoteConfig ): string { From 6c0636bdbb735a667fdd7baf318f28f979a64694 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com> Date: Wed, 20 May 2026 22:29:53 +0200 Subject: [PATCH 2/3] docs: add simulator get changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfe36d3cd2..caba9adcc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ This is the log of notable changes to EAS CLI and related packages. ### ๐ŸŽ‰ New features +- [eas-cli] Add `--json` flag to `eas simulator:get`. ([#3765](https://github.com/expo/eas-cli/pull/3765) by [@krystofwoldrich](https://github.com/krystofwoldrich)) + ### ๐Ÿ› Bug fixes ### ๐Ÿงน Chores From 182f84390872cf70797ccf0ffff7bd16e6fc2e1e Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 21 May 2026 17:46:48 +0200 Subject: [PATCH 3/3] Revert "docs: add simulator get changelog entry" This reverts commit 6c0636bdbb735a667fdd7baf318f28f979a64694. --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caba9adcc5..dfe36d3cd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,6 @@ This is the log of notable changes to EAS CLI and related packages. ### ๐ŸŽ‰ New features -- [eas-cli] Add `--json` flag to `eas simulator:get`. ([#3765](https://github.com/expo/eas-cli/pull/3765) by [@krystofwoldrich](https://github.com/krystofwoldrich)) - ### ๐Ÿ› Bug fixes ### ๐Ÿงน Chores