Skip to content

Commit f48c9b4

Browse files
committed
Fix strict no-command flags and negative positional parsing
1 parent 52a7e6b commit f48c9b4

3 files changed

Lines changed: 42 additions & 5 deletions

File tree

src/utils/__tests__/args.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,33 @@ test('negative numeric positionals are accepted without -- separator', () => {
118118
assert.equal(typed.command, 'type');
119119
assert.deepEqual(typed.positionals, ['-123']);
120120

121+
const typedMulti = parseArgs(['type', '-123', '-456'], { strictFlags: true });
122+
assert.equal(typedMulti.command, 'type');
123+
assert.deepEqual(typedMulti.positionals, ['-123', '-456']);
124+
121125
const pressed = parseArgs(['press', '-10', '20'], { strictFlags: true });
122126
assert.equal(pressed.command, 'press');
123127
assert.deepEqual(pressed.positionals, ['-10', '20']);
124128
});
125129

130+
test('command-specific flags without command fail in strict mode', () => {
131+
assert.throws(
132+
() => parseArgs(['--depth', '3'], { strictFlags: true }),
133+
(error) =>
134+
error instanceof AppError &&
135+
error.code === 'INVALID_ARGS' &&
136+
error.message.includes('requires a command that supports it'),
137+
);
138+
});
139+
140+
test('command-specific flags without command warn and strip in compat mode', () => {
141+
const parsed = parseArgs(['--depth', '3'], { strictFlags: false });
142+
assert.equal(parsed.command, null);
143+
assert.equal(parsed.flags.snapshotDepth, undefined);
144+
assert.equal(parsed.warnings.length, 1);
145+
assert.match(parsed.warnings[0], /requires a command that supports/);
146+
});
147+
126148
test('all commands participate in strict command-flag validation', () => {
127149
assert.throws(
128150
() => parseArgs(['open', 'Settings', '--depth', '1'], { strictFlags: true }),

src/utils/args.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,9 @@ export function parseArgs(argv: string[], options?: ParseArgsOptions): ParsedArg
7777
...(commandSchema?.allowedFlags ?? []),
7878
]);
7979
const disallowed = providedFlags.filter((entry) => !allowedFlagKeys.has(entry.key));
80-
if (disallowed.length > 0 && command) {
80+
if (disallowed.length > 0) {
8181
const unsupported = disallowed.map((entry) => entry.token);
82-
const message =
83-
unsupported.length === 1
84-
? `Flag ${unsupported[0]} is not supported for command ${command}.`
85-
: `Flags ${unsupported.join(', ')} are not supported for command ${command}.`;
82+
const message = formatUnsupportedFlagMessage(command, unsupported);
8683
if (strictFlags) {
8784
throw new AppError('INVALID_ARGS', message);
8885
}
@@ -179,6 +176,7 @@ function shouldTreatUnknownDashTokenAsPositional(
179176
if (!command) return false;
180177
const schema = getCommandSchema(command);
181178
if (!schema) return true;
179+
if (schema.allowsExtraPositionals) return true;
182180
if (schema.positionalArgs.length === 0) return false;
183181
if (positionals.length < schema.positionalArgs.length) return true;
184182
return schema.positionalArgs.some((entry) => entry.includes('?'));
@@ -188,6 +186,17 @@ function isNegativeNumericToken(value: string): boolean {
188186
return /^-\d+(\.\d+)?$/.test(value);
189187
}
190188

189+
function formatUnsupportedFlagMessage(command: string | null, unsupported: string[]): string {
190+
if (!command) {
191+
return unsupported.length === 1
192+
? `Flag ${unsupported[0]} requires a command that supports it.`
193+
: `Flags ${unsupported.join(', ')} require a command that supports them.`;
194+
}
195+
return unsupported.length === 1
196+
? `Flag ${unsupported[0]} is not supported for command ${command}.`
197+
: `Flags ${unsupported.join(', ')} are not supported for command ${command}.`;
198+
}
199+
191200
export function toDaemonFlags(flags: CliFlags): Omit<CliFlags, 'json' | 'help' | 'version'> {
192201
const { json: _json, help: _help, version: _version, ...daemonFlags } = flags;
193202
return daemonFlags;

src/utils/command-schema.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export type CommandSchema = {
5353
description: string;
5454
details?: readonly string[];
5555
positionalArgs: readonly string[];
56+
allowsExtraPositionals?: boolean;
5657
allowedFlags: readonly FlagKey[];
5758
defaults?: Partial<CliFlags>;
5859
};
@@ -457,6 +458,7 @@ export const COMMAND_SCHEMAS: Record<string, CommandSchema> = {
457458
usage: 'wait <ms>|text <text>|@ref|<selector> [timeoutMs]',
458459
description: 'Wait for duration, text, ref, or selector to appear',
459460
positionalArgs: ['durationOrSelector', 'timeoutMs?'],
461+
allowsExtraPositionals: true,
460462
allowedFlags: [...SELECTOR_SNAPSHOT_FLAGS],
461463
},
462464
alert: {
@@ -529,6 +531,7 @@ export const COMMAND_SCHEMAS: Record<string, CommandSchema> = {
529531
usage: 'type <text>',
530532
description: 'Type text in focused field',
531533
positionalArgs: ['text'],
534+
allowsExtraPositionals: true,
532535
allowedFlags: [],
533536
},
534537
fill: {
@@ -537,6 +540,7 @@ export const COMMAND_SCHEMAS: Record<string, CommandSchema> = {
537540
usage: 'fill <x> <y> <text> | fill <@ref|selector> <text>',
538541
description: 'Tap then type',
539542
positionalArgs: ['targetOrX', 'yOrText', 'text?'],
543+
allowsExtraPositionals: true,
540544
allowedFlags: [...SELECTOR_SNAPSHOT_FLAGS],
541545
},
542546
scroll: {
@@ -600,6 +604,7 @@ export const COMMAND_SCHEMAS: Record<string, CommandSchema> = {
600604
'find id <id> <action> [value]',
601605
],
602606
positionalArgs: ['query', 'action', 'value?'],
607+
allowsExtraPositionals: true,
603608
allowedFlags: [...FIND_SNAPSHOT_FLAGS],
604609
},
605610
is: {
@@ -608,6 +613,7 @@ export const COMMAND_SCHEMAS: Record<string, CommandSchema> = {
608613
usage: 'is <predicate> <selector> [value]',
609614
description: 'Assert UI state (visible|hidden|exists|editable|selected|text)',
610615
positionalArgs: ['predicate', 'selector', 'value?'],
616+
allowsExtraPositionals: true,
611617
allowedFlags: [...SELECTOR_SNAPSHOT_FLAGS],
612618
},
613619
settings: {

0 commit comments

Comments
 (0)