diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index 081ea17..8eb2106 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -215,9 +215,9 @@ export const cloudCommand = defineCommand({ ); const androidNoSnapshot = Boolean(args['android-no-snapshot']); // Repeatable device-matrix flags: one validated cell each, no cross-product. - const iosConfigFlags = collectRepeatedFlag(rawArgs, ['--ios-config']); - const androidConfigFlags = collectRepeatedFlag(rawArgs, ['--android-config']); - const deviceMatrix = parseDeviceMatrix(iosConfigFlags, androidConfigFlags); + const iosMatrixFlags = collectRepeatedFlag(rawArgs, ['--ios-device-matrix']); + const androidMatrixFlags = collectRepeatedFlag(rawArgs, ['--android-device-matrix']); + const deviceMatrix = parseDeviceMatrix(iosMatrixFlags, androidMatrixFlags); const json = Boolean(args.json); const jsonFileFlag = Boolean(args['json-file']); const jsonFileName = args['json-file-name'] as string | undefined; @@ -668,8 +668,8 @@ export const cloudCommand = defineCommand({ 'include-tags': includeTags, 'exclude-tags': excludeTags, 'exclude-flows': excludeFlows, - 'ios-config': iosConfigFlags, - 'android-config': androidConfigFlags, + 'ios-device-matrix': iosMatrixFlags, + 'android-device-matrix': androidMatrixFlags, }; for (const [k, v] of Object.entries(args)) { if (!canonicalFlagKeys.has(k)) continue; diff --git a/src/config/flags/device.flags.ts b/src/config/flags/device.flags.ts index 1fa81df..62df3de 100644 --- a/src/config/flags/device.flags.ts +++ b/src/config/flags/device.flags.ts @@ -42,13 +42,13 @@ export const deviceFlags = { type: 'string', description: `[iOS only] iOS version to run your flow against (options: ${iosVersions})`, }, - 'ios-config': { + 'ios-device-matrix': { type: 'string', - description: `[iOS only] Device-matrix cell as :, e.g. iphone-16:18. Repeatable — every flow runs once per cell (no cross-product). Cannot be combined with --android-config.`, + description: `[iOS only] Device-matrix cell as :, e.g. iphone-16:18. Repeatable — every flow runs once per cell (no cross-product). Cannot be combined with --android-device-matrix.`, }, - 'android-config': { + 'android-device-matrix': { type: 'string', - description: `[Android only] Device-matrix cell as : (append :play for Google Play), e.g. pixel-7:34 or pixel-7:34:play. Repeatable — every flow runs once per cell (no cross-product). Cannot be combined with --ios-config.`, + description: `[Android only] Device-matrix cell as : (append :play for Google Play), e.g. pixel-7:34 or pixel-7:34:play. Repeatable — every flow runs once per cell (no cross-product). Cannot be combined with --ios-device-matrix.`, }, orientation: { type: 'string', diff --git a/src/utils/device-matrix.ts b/src/utils/device-matrix.ts index 18e26cc..8e9e5f9 100644 --- a/src/utils/device-matrix.ts +++ b/src/utils/device-matrix.ts @@ -5,8 +5,8 @@ import { import { CliError } from './cli.js'; /** - * Parse repeated `--ios-config :` and - * `--android-config :[:play]` flags into an explicit device + * Parse repeated `--ios-device-matrix :` and + * `--android-device-matrix :[:play]` flags into an explicit device * matrix. Each entry is one validated cell — there is NO cross-product, because * the compatibility matrix is ragged and a cross-product would invent cells the * user never asked for. @@ -22,7 +22,7 @@ export function parseDeviceMatrix( ): DeviceMatrixConfig[] { if (iosConfigs.length > 0 && androidConfigs.length > 0) { throw new CliError( - 'A device matrix cannot mix platforms: use either --ios-config or --android-config, not both. One upload runs one binary.', + 'A device matrix cannot mix platforms: use either --ios-device-matrix or --android-device-matrix, not both. One upload runs one binary.', ); } @@ -32,7 +32,7 @@ export function parseDeviceMatrix( const parts = raw.split(':'); if (parts.length !== 2 || !parts[0] || !parts[1]) { throw new CliError( - `Invalid --ios-config "${raw}". Expected :, e.g. iphone-16:18.`, + `Invalid --ios-device-matrix "${raw}". Expected :, e.g. iphone-16:18.`, ); } configs.push({ iOSDevice: parts[0], iOSVersion: parts[1] }); @@ -49,7 +49,7 @@ export function parseDeviceMatrix( (parts.length === 3 && parts[2] !== 'play') ) { throw new CliError( - `Invalid --android-config "${raw}". Expected : or ::play, e.g. pixel-7:34 or pixel-7:34:play.`, + `Invalid --android-device-matrix "${raw}". Expected : or ::play, e.g. pixel-7:34 or pixel-7:34:play.`, ); } configs.push({ diff --git a/test/integration/cloud.integration.test.ts b/test/integration/cloud.integration.test.ts index ab1de5d..7d052af 100644 --- a/test/integration/cloud.integration.test.ts +++ b/test/integration/cloud.integration.test.ts @@ -163,10 +163,10 @@ appId: com.example.app }); }); - // #1105 device matrix: repeated --ios-config / --android-config cells. + // #1105 device matrix: repeated --ios-device-matrix / --android-device-matrix cells. describe('device matrix', () => { - it('accepts a repeated --ios-config matrix and still yields one upload', async () => { - const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-config iphone-16:18 --ios-config iphone-16-pro:26 --async --json`; + it('accepts a repeated --ios-device-matrix matrix and still yields one upload', async () => { + const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-device-matrix iphone-16:18 --ios-device-matrix iphone-16-pro:26 --async --json`; const { stdout } = await exec(command, { timeout: 30_000 }); // Still one upload with one uploadId — the matrix is a property of the @@ -174,15 +174,15 @@ appId: com.example.app expectAsyncRunJson(stdout); }); - it('rejects mixing --ios-config and --android-config before any upload', async () => { - const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-config iphone-16:18 --android-config pixel-7:34`; + it('rejects mixing --ios-device-matrix and --android-device-matrix before any upload', async () => { + const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-device-matrix iphone-16:18 --android-device-matrix pixel-7:34`; const { output } = await runExpectingFailure(command); expect(output.toLowerCase()).to.include('cannot mix platforms'); }); - it('rejects a malformed --ios-config, naming the value', async () => { - const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-config iphone-16`; + it('rejects a malformed --ios-device-matrix, naming the value', async () => { + const command = `${CLI} cloud ${iosAppFile} ${testFlowFile} --api-key ${mockApiKey} --api-url ${mockApiUrl} --ios-device-matrix iphone-16`; const { output } = await runExpectingFailure(command); expect(output).to.include('iphone-16'); diff --git a/test/unit/device-matrix.test.ts b/test/unit/device-matrix.test.ts index 7e8c497..8a4cb11 100644 --- a/test/unit/device-matrix.test.ts +++ b/test/unit/device-matrix.test.ts @@ -7,8 +7,8 @@ import { } from '../../src/utils/device-matrix.js'; /** - * The device matrix is the load-bearing part of #1105: each --ios-config / - * --android-config names exactly one cell, there is no cross-product, and a + * The device matrix is the load-bearing part of #1105: each --ios-device-matrix / + * --android-device-matrix names exactly one cell, there is no cross-product, and a * matrix is single-platform. These are pure and worth pinning precisely. */ describe('parseDeviceMatrix', () => { @@ -17,7 +17,7 @@ describe('parseDeviceMatrix', () => { expect(matrixIsIos([])).to.equal(false); }); - it('parses each --ios-config as exactly one cell (no cross-product)', () => { + it('parses each --ios-device-matrix as exactly one cell (no cross-product)', () => { const matrix = parseDeviceMatrix( ['iphone-16:18', 'iphone-16-pro:26'], [], @@ -29,7 +29,7 @@ describe('parseDeviceMatrix', () => { expect(matrixIsIos(matrix)).to.equal(true); }); - it('parses --android-config, with :play marking a Google Play cell', () => { + it('parses --android-device-matrix, with :play marking a Google Play cell', () => { expect(parseDeviceMatrix([], ['pixel-7:34', 'pixel-7:34:play'])).to.deep.equal([ { androidDevice: 'pixel-7', androidApiLevel: '34' }, { androidDevice: 'pixel-7', androidApiLevel: '34', googlePlay: true },