Compare commits

...
4 changed files with 159 additions and 19 deletions

No files matched your search

@@ -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,
});
});
});
+27 -4
View File
@@ -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<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();
@@ -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}`);
@@ -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<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 =
+19 -1
View File
@@ -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 {