Skip to content

Commit 54890d4

Browse files
amankansal-ltclaude
andcommitted
fix(contracts): name a refused profile field's accepted aliases too
A refusal named only the canonical --provider-* flag, so a user who set the field through an alias such as --os-version was told to drop a flag they never typed. The flags bag keeps the field, not the spelling, so the message now lists every accepted spelling, for example "--provider-os-version (--os-version)". details.flags stays canonical. Contracts cannot depend on the command registry, so the aliases live in PROVIDER_PROFILE_FIELD_FLAG_ALIASES, and a command-registry test checks that map against the aliases the CLI parses. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent ba99a6d commit 54890d4

5 files changed

Lines changed: 51 additions & 9 deletions

File tree

‎packages/command-registry/src/__tests__/provider-profile-field-flags.test.ts‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { expect, test } from 'vitest';
22
import type { ProviderProfileField } from '@agent-device/contracts/provider-profile-fields';
3-
import { PROVIDER_PROFILE_FIELD_FLAGS } from '@agent-device/contracts/remote';
3+
import {
4+
PROVIDER_PROFILE_FIELD_FLAG_ALIASES,
5+
PROVIDER_PROFILE_FIELD_FLAGS,
6+
} from '@agent-device/contracts/remote';
47
import { getFlagDefinitionsForKey } from '../flag-registry.ts';
58

69
// Refusals and BrowserStack feature errors name flags from this map, so each must be the CLI's
@@ -12,3 +15,13 @@ test('every provider profile field flag is the canonical CLI flag for its field'
1215
expect(getFlagDefinitionsForKey(field)[0]?.names[0], field).toBe(flag);
1316
}
1417
});
18+
19+
// Contracts cannot depend on the command registry, so refusals carry their own copy of each field's
20+
// accepted aliases; it must match the aliases the CLI parses.
21+
test('every provider profile field lists the aliases the CLI accepts for it', () => {
22+
for (const field of Object.keys(PROVIDER_PROFILE_FIELD_FLAGS) as ProviderProfileField[]) {
23+
expect(PROVIDER_PROFILE_FIELD_FLAG_ALIASES[field] ?? [], field).toEqual(
24+
getFlagDefinitionsForKey(field)[0]?.names.slice(1),
25+
);
26+
}
27+
});

‎packages/contracts/src/facades/remote.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type {
1616
} from '../provider-connection.ts';
1717
export {
1818
PROVIDER_DEVICE_ORIENTATIONS,
19+
PROVIDER_PROFILE_FIELD_FLAG_ALIASES,
1920
PROVIDER_PROFILE_FIELD_FLAGS,
2021
} from '../remote-config-fields.ts';
2122
export type {

‎packages/contracts/src/provider-profile-fields.test.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ test('consumed, unset, empty, and false fields pass', () => {
4848
);
4949
});
5050

51-
test('a refused field fails with its flag and the provider named', () => {
51+
test('a refused field fails with its flag, its aliases, and the provider named', () => {
5252
assert.throws(
5353
() => rejectRefusedProviderProfileFields({ providerOsVersion: '18' }, DECLARATION),
5454
(error: unknown) =>
5555
error instanceof AppError &&
5656
error.code === 'INVALID_ARGS' &&
57-
error.message === '--provider-os-version is not supported by Fake Cloud.' &&
57+
error.message === '--provider-os-version (--os-version) is not supported by Fake Cloud.' &&
5858
error.details?.provider === 'fake' &&
5959
JSON.stringify(error.details?.flags) === '["--provider-os-version"]',
6060
);
@@ -67,6 +67,11 @@ test('every refused field is reported at once', () => {
6767
{ providerGeoLocation: 'US', providerNoResignApp: true, providerOsVersion: '18' },
6868
DECLARATION,
6969
),
70-
/--provider-os-version, --provider-geo-location, --provider-no-resign-app are not supported by Fake Cloud\./,
70+
(error: unknown) =>
71+
error instanceof AppError &&
72+
error.message ===
73+
'--provider-os-version (--os-version), --provider-geo-location (--geo-location), --provider-no-resign-app are not supported by Fake Cloud.' &&
74+
JSON.stringify(error.details?.flags) ===
75+
'["--provider-os-version","--provider-geo-location","--provider-no-resign-app"]',
7176
);
7277
});

‎packages/contracts/src/provider-profile-fields.ts‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AppError } from '@agent-device/kernel/errors';
22
import {
3+
PROVIDER_PROFILE_FIELD_FLAG_ALIASES,
34
PROVIDER_PROFILE_FIELD_FLAGS,
45
type CloudProviderProfileFields,
56
} from './remote-config-fields.ts';
@@ -25,22 +26,29 @@ export function rejectRefusedProviderProfileFields(
2526
flags: Readonly<Record<string, unknown>> | undefined,
2627
declaration: ProviderProfileFieldDeclaration,
2728
): void {
28-
const refused = (Object.keys(declaration.fields) as ProviderProfileField[])
29-
.filter((field) => declaration.fields[field] === 'refused' && isSet(flags?.[field]))
30-
.map((field) => PROVIDER_PROFILE_FIELD_FLAGS[field]);
29+
const refused = (Object.keys(declaration.fields) as ProviderProfileField[]).filter(
30+
(field) => declaration.fields[field] === 'refused' && isSet(flags?.[field]),
31+
);
3132
if (refused.length === 0) return;
3233
const plural = refused.length !== 1;
3334
throw new AppError(
3435
'INVALID_ARGS',
35-
`${refused.join(', ')} ${plural ? 'are' : 'is'} not supported by ${declaration.label}.`,
36+
`${refused.map(describeFlag).join(', ')} ${plural ? 'are' : 'is'} not supported by ${declaration.label}.`,
3637
{
3738
hint: `Drop ${plural ? 'those flags' : 'the flag'} or use a provider that supports ${plural ? 'them' : 'it'}.`,
3839
provider: declaration.provider,
39-
flags: refused,
40+
flags: refused.map((field) => PROVIDER_PROFILE_FIELD_FLAGS[field]),
4041
},
4142
);
4243
}
4344

45+
// The flags bag keeps the field, not the spelling, so name every spelling the user could have typed.
46+
function describeFlag(field: ProviderProfileField): string {
47+
const aliases = PROVIDER_PROFILE_FIELD_FLAG_ALIASES[field];
48+
const flag = PROVIDER_PROFILE_FIELD_FLAGS[field];
49+
return aliases?.length ? `${flag} (${aliases.join(', ')})` : flag;
50+
}
51+
4452
function isSet(value: unknown): boolean {
4553
return value !== undefined && value !== null && value !== false && value !== '';
4654
}

‎packages/contracts/src/remote-config-fields.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,21 @@ export const PROVIDER_PROFILE_FIELD_FLAGS: Readonly<
6767
awsInteractionMode: '--aws-interaction-mode',
6868
};
6969

70+
/** Other spellings the CLI accepts for a profile field, so an error also names the one typed. */
71+
export const PROVIDER_PROFILE_FIELD_FLAG_ALIASES: Readonly<
72+
Partial<Record<keyof CloudProviderProfileFields, readonly string[]>>
73+
> = {
74+
providerOsVersion: ['--os-version'],
75+
providerDeviceOrientation: ['--device-orientation'],
76+
providerGeoLocation: ['--geo-location'],
77+
providerTimezone: ['--timezone'],
78+
providerAppiumVersion: ['--appium-version'],
79+
providerLanguage: ['--language'],
80+
providerLocale: ['--locale'],
81+
providerNetworkProfile: ['--network-profile'],
82+
providerCustomNetwork: ['--custom-network'],
83+
};
84+
7085
export type RemoteConfigMetroOptions = {
7186
metroProjectRoot?: string;
7287
metroKind?: MetroPrepareKind;

0 commit comments

Comments
 (0)