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
10 changes: 5 additions & 5 deletions src/commands/cloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions src/config/flags/device.flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <device>:<version>, 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 <device>:<version>, 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 <device>:<apiLevel> (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 <device>:<apiLevel> (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',
Expand Down
10 changes: 5 additions & 5 deletions src/utils/device-matrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
import { CliError } from './cli.js';

/**
* Parse repeated `--ios-config <device>:<version>` and
* `--android-config <device>:<apiLevel>[:play]` flags into an explicit device
* Parse repeated `--ios-device-matrix <device>:<version>` and
* `--android-device-matrix <device>:<apiLevel>[: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.
Expand All @@ -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.',
);
}

Expand All @@ -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 <device>:<version>, e.g. iphone-16:18.`,
`Invalid --ios-device-matrix "${raw}". Expected <device>:<version>, e.g. iphone-16:18.`,
);
}
configs.push({ iOSDevice: parts[0], iOSVersion: parts[1] });
Expand All @@ -49,7 +49,7 @@ export function parseDeviceMatrix(
(parts.length === 3 && parts[2] !== 'play')
) {
throw new CliError(
`Invalid --android-config "${raw}". Expected <device>:<apiLevel> or <device>:<apiLevel>:play, e.g. pixel-7:34 or pixel-7:34:play.`,
`Invalid --android-device-matrix "${raw}". Expected <device>:<apiLevel> or <device>:<apiLevel>:play, e.g. pixel-7:34 or pixel-7:34:play.`,
);
}
configs.push({
Expand Down
14 changes: 7 additions & 7 deletions test/integration/cloud.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,26 +163,26 @@ 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
// upload, not N uploads.
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');
Expand Down
8 changes: 4 additions & 4 deletions test/unit/device-matrix.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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'],
[],
Expand All @@ -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 },
Expand Down
Loading