From 2b453854c993b8ca051ef571fd3015eb58bed3cb Mon Sep 17 00:00:00 2001 From: finalerock44 Date: Fri, 6 Nov 2026 20:56:35 +0000 Subject: [PATCH] feat: drop iPhone 16 Pro Max and gpu1 from help text (removed 9 Nov 2026) iphone-16-pro-max and the gpu1 runner type are removed on 9 November 2026. iphone-16-pro stays supported. - --ios-device help no longer lists iphone-16-pro-max. Validation already follows the live compatibility data, so the API's answer is what a run that names it gets. - --runner-type help, its error for an unknown value, and the dcd_run_cloud_test runnerType schema list default, m4, m1 and cpu1 only. - gpu1 is still accepted by --runner-type and by dcd_run_cloud_test, so scripts and agents that pass it keep working: the API runs it on cpu1. validateEnum takes an optional list of accepted values it leaves out of its error message, and the MCP schema's enum is overridden through zod metadata. --- src/commands/cloud.ts | 8 ++- src/config/flags/execution.flags.ts | 12 ++++- src/mcp/tools/run-cloud-test.ts | 14 +++-- src/types/domain/device.types.ts | 1 - src/utils/cli.ts | 12 +++-- test/unit/runner-type.test.ts | 84 +++++++++++++++++++++++++++++ 6 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 test/unit/runner-type.test.ts diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index 13870b4..d1b5908 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -3,6 +3,10 @@ import { defineCommand } from 'citty'; import { existsSync } from 'node:fs'; import * as path from 'node:path'; +import { + RETIRED_RUNNER_TYPES, + RUNNER_TYPE_OPTIONS, +} from '../config/flags/execution.flags.js'; import { flags as allFlags } from '../constants.js'; import { ApiError, ApiGateway } from '../gateways/api-gateway.js'; import { @@ -83,7 +87,6 @@ process.on('warning', (warning) => { const DOWNLOAD_OPTIONS = ['ALL', 'FAILED'] as const; const REPORT_OPTIONS = ['allure', 'html', 'html-detailed', 'junit'] as const; const ORIENTATION_OPTIONS = ['0', '90'] as const; -const RUNNER_TYPE_OPTIONS = ['default', 'm4', 'm1', 'gpu1', 'cpu1'] as const; const PRIORITY_OPTIONS = ['low', 'normal', 'high'] as const; const RENDER_ENGINE_OPTIONS = ['lavapipe', 'swiftshader'] as const; @@ -252,6 +255,9 @@ export const cloudCommand = defineCommand({ args['runner-type'] as string | undefined, RUNNER_TYPE_OPTIONS, 'runner-type', + // gpu1 still validates so old scripts keep working; the API runs it + // on cpu1. + RETIRED_RUNNER_TYPES, ) ?? 'default'; const renderEngine = validateEnum( args['render-engine'] as string | undefined, diff --git a/src/config/flags/execution.flags.ts b/src/config/flags/execution.flags.ts index 3018962..7b619b7 100644 --- a/src/config/flags/execution.flags.ts +++ b/src/config/flags/execution.flags.ts @@ -1,5 +1,15 @@ import type { ArgsDef } from 'citty'; +/** The runner types offered in help text, error messages and the MCP schema. */ +export const RUNNER_TYPE_OPTIONS = ['default', 'm4', 'm1', 'cpu1'] as const; + +/** + * Runner types that were removed but are still accepted, so scripts that pass + * them keep working. gpu1 was removed on 9 November 2026 and the API runs it + * on cpu1. Never offered anywhere a user picks from. + */ +export const RETIRED_RUNNER_TYPES = ['gpu1'] as const; + /** * Test execution and flow management flags */ @@ -56,6 +66,6 @@ export const executionFlags = { type: 'string', default: 'default', description: - '[experimental] The type of runner to use (options: default, m4, m1, gpu1, cpu1) - note: anything other than default or cpu1 will incur premium pricing tiers; gpu1 is deprecated and will be removed on 9 November 2026 (gpu1 runs will then use cpu1). See https://docs.devicecloud.dev/configuration/runner-type for more information.', + '[experimental] The type of runner to use (options: default, m4, m1, cpu1) - note: anything other than default or cpu1 will incur premium pricing tiers. See https://docs.devicecloud.dev/configuration/runner-type for more information.', }, } as const satisfies ArgsDef; diff --git a/src/mcp/tools/run-cloud-test.ts b/src/mcp/tools/run-cloud-test.ts index 6801ed9..1910a82 100644 --- a/src/mcp/tools/run-cloud-test.ts +++ b/src/mcp/tools/run-cloud-test.ts @@ -3,6 +3,10 @@ import * as path from 'node:path'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; +import { + RETIRED_RUNNER_TYPES, + RUNNER_TYPE_OPTIONS, +} from '../../config/flags/execution.flags.js'; import { ApiGateway } from '../../gateways/api-gateway.js'; import { type IExecutionPlan, plan } from '../../services/execution-plan.service.js'; import { @@ -104,12 +108,14 @@ export function registerRunCloudTest(server: McpServer): void { excludeFlows: z.array(z.string()).optional().describe('Flow paths/patterns to exclude'), maestroVersion: z.string().optional().describe('Pin a specific Maestro version'), retry: z.number().int().min(0).max(2).optional().describe('Auto-retry failed tests (max 2)'), + // A retired runner type still validates, so an agent configured with + // one keeps working (the API runs gpu1 on cpu1), but the schema's + // `enum` lists only the current ones: zod metadata overrides it. runnerType: z - .enum(['default', 'm4', 'm1', 'gpu1', 'cpu1']) + .enum([...RUNNER_TYPE_OPTIONS, ...RETIRED_RUNNER_TYPES]) + .meta({ enum: [...RUNNER_TYPE_OPTIONS] }) .optional() - .describe( - 'Runner type (default "default"). gpu1 is deprecated and will be removed on 9 November 2026; prefer cpu1.', - ), + .describe('Runner type (default "default").'), priority: z .enum(['low', 'normal', 'high']) .optional() diff --git a/src/types/domain/device.types.ts b/src/types/domain/device.types.ts index 3cce15e..eadaf1b 100644 --- a/src/types/domain/device.types.ts +++ b/src/types/domain/device.types.ts @@ -19,7 +19,6 @@ export enum EiOSDevices { 'iphone-14' = 'iphone-14', 'iphone-16' = 'iphone-16', 'iphone-16-pro' = 'iphone-16-pro', - 'iphone-16-pro-max' = 'iphone-16-pro-max', 'iphone-17' = 'iphone-17', 'iphone-18-pro' = 'iphone-18-pro', 'iphone-18-pro-max' = 'iphone-18-pro-max', diff --git a/src/utils/cli.ts b/src/utils/cli.ts index 31b0033..da4aba7 100644 --- a/src/utils/cli.ts +++ b/src/utils/cli.ts @@ -126,19 +126,23 @@ export function wantsJson(rawArgs: string[]): boolean { /** * Validate that a string flag value is one of the allowed options. * Returns the value untouched on success; throws CliError otherwise. + * + * `unlisted` values are accepted too but left out of the error message: a + * retired option that old scripts may still pass, which nobody should pick. */ -export function validateEnum( +export function validateEnum( value: string | undefined, allowed: readonly T[], flagName: string, -): T | undefined { + unlisted: readonly U[] = [], +): T | U | undefined { if (value === undefined || value === null || value === '') return undefined; - if (!allowed.includes(value as T)) { + if (!allowed.includes(value as T) && !unlisted.includes(value as U)) { throw new CliError( `Invalid value for --${flagName}: "${value}". Expected one of: ${allowed.join(', ')}`, ); } - return value as T; + return value as T | U; } /** diff --git a/test/unit/runner-type.test.ts b/test/unit/runner-type.test.ts new file mode 100644 index 0000000..6999d06 --- /dev/null +++ b/test/unit/runner-type.test.ts @@ -0,0 +1,84 @@ +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { expect } from 'chai'; + +import { + executionFlags, + RETIRED_RUNNER_TYPES, + RUNNER_TYPE_OPTIONS, +} from '../../src/config/flags/execution.flags.js'; +import { registerRunCloudTest } from '../../src/mcp/tools/run-cloud-test.js'; +import { validateEnum } from '../../src/utils/cli.js'; + +/** + * gpu1 was removed on 9 November 2026 and the API runs it on cpu1. A script + * or agent that still passes it must keep working, but nothing may offer it. + */ +describe('retired runner types', () => { + describe('--runner-type', () => { + it('lists only the current runner types in its help', () => { + const help = executionFlags['runner-type'].description; + for (const retired of RETIRED_RUNNER_TYPES) { + expect(help).to.not.include(retired); + } + expect(help).to.include(`options: ${RUNNER_TYPE_OPTIONS.join(', ')}`); + }); + + it('still accepts a retired runner type', () => { + expect( + validateEnum('gpu1', RUNNER_TYPE_OPTIONS, 'runner-type', RETIRED_RUNNER_TYPES), + ).to.equal('gpu1'); + }); + + it('leaves retired runner types out of the error for an unknown one', () => { + expect(() => + validateEnum('gpu2', RUNNER_TYPE_OPTIONS, 'runner-type', RETIRED_RUNNER_TYPES), + ).to.throw( + 'Invalid value for --runner-type: "gpu2". Expected one of: default, m4, m1, cpu1', + ); + }); + }); + + describe('dcd_run_cloud_test runnerType', () => { + let client: Client; + + beforeEach(async () => { + const server = new McpServer({ name: 'test', version: '0.0.0' }); + registerRunCloudTest(server); + const [a, b] = InMemoryTransport.createLinkedPair(); + await server.connect(b); + client = new Client({ name: 'agent', version: '1' }); + await client.connect(a); + }); + + afterEach(async () => { + await client.close(); + }); + + it('advertises only the current runner types', async () => { + const { tools } = await client.listTools(); + const tool = tools.find((t) => t.name === 'dcd_run_cloud_test')!; + const runnerType = tool.inputSchema.properties!.runnerType as { enum: string[] }; + expect(runnerType.enum).to.deep.equal([...RUNNER_TYPE_OPTIONS]); + }); + + // Passing both appFile and appBinaryId fails inside the handler, before + // any network call, so reaching that error means the input validated. + const call = (runnerType: string) => + client.callTool({ + arguments: { appBinaryId: 'b', appFile: 'a.apk', flowFile: 'f.yaml', runnerType }, + name: 'dcd_run_cloud_test', + }); + const text = (result: Awaited>) => + (result.content as { text: string }[])[0].text; + + it('still accepts a retired runner type', async () => { + expect(text(await call('gpu1'))).to.include('Provide only one of appFile or appBinaryId'); + }); + + it('rejects an unknown runner type', async () => { + expect(text(await call('gpu2'))).to.include('Input validation error'); + }); + }); +});