Skip to content

Commit 52a7e6b

Browse files
committed
Tighten parser positional handling and ref flag validation
1 parent 98cd033 commit 52a7e6b

3 files changed

Lines changed: 83 additions & 1 deletion

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import test from 'node:test';
2+
import assert from 'node:assert/strict';
3+
import { unsupportedRefSnapshotFlags } from '../interaction.ts';
4+
5+
test('unsupportedRefSnapshotFlags returns unsupported snapshot flags for @ref flows', () => {
6+
const unsupported = unsupportedRefSnapshotFlags({
7+
snapshotDepth: 2,
8+
snapshotScope: 'Login',
9+
snapshotRaw: true,
10+
snapshotBackend: 'ax',
11+
});
12+
assert.deepEqual(unsupported, ['--depth', '--scope', '--raw', '--backend']);
13+
});
14+
15+
test('unsupportedRefSnapshotFlags returns empty when no ref-unsupported flags are present', () => {
16+
const unsupported = unsupportedRefSnapshotFlags({
17+
platform: 'ios',
18+
session: 'default',
19+
verbose: true,
20+
});
21+
assert.deepEqual(unsupported, []);
22+
});

src/daemon/handlers/interaction.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ export async function handleInteractionCommands(params: {
4141
}
4242
const refInput = req.positionals?.[0] ?? '';
4343
if (refInput.startsWith('@')) {
44+
const unsupported = unsupportedRefSnapshotFlags(req.flags);
45+
if (unsupported.length > 0) {
46+
return {
47+
ok: false,
48+
error: {
49+
code: 'INVALID_ARGS',
50+
message: `click @ref does not support ${unsupported.join(', ')}.`,
51+
},
52+
};
53+
}
4454
if (!session.snapshot) {
4555
return { ok: false, error: { code: 'INVALID_ARGS', message: 'No snapshot in session. Run snapshot first.' } };
4656
}
@@ -126,6 +136,16 @@ export async function handleInteractionCommands(params: {
126136
if (command === 'fill') {
127137
const session = sessionStore.get(sessionName);
128138
if (req.positionals?.[0]?.startsWith('@')) {
139+
const unsupported = unsupportedRefSnapshotFlags(req.flags);
140+
if (unsupported.length > 0) {
141+
return {
142+
ok: false,
143+
error: {
144+
code: 'INVALID_ARGS',
145+
message: `fill @ref does not support ${unsupported.join(', ')}.`,
146+
},
147+
};
148+
}
129149
if (!session?.snapshot) {
130150
return { ok: false, error: { code: 'INVALID_ARGS', message: 'No snapshot in session. Run snapshot first.' } };
131151
}
@@ -258,6 +278,16 @@ export async function handleInteractionCommands(params: {
258278
}
259279
const refInput = req.positionals?.[1] ?? '';
260280
if (refInput.startsWith('@')) {
281+
const unsupported = unsupportedRefSnapshotFlags(req.flags);
282+
if (unsupported.length > 0) {
283+
return {
284+
ok: false,
285+
error: {
286+
code: 'INVALID_ARGS',
287+
message: `get @ref does not support ${unsupported.join(', ')}.`,
288+
},
289+
};
290+
}
261291
if (!session.snapshot) {
262292
return { ok: false, error: { code: 'INVALID_ARGS', message: 'No snapshot in session. Run snapshot first.' } };
263293
}
@@ -511,3 +541,19 @@ async function captureSnapshotForSession(
511541
sessionStore.set(session.name, session);
512542
return session.snapshot;
513543
}
544+
545+
const REF_UNSUPPORTED_FLAG_MAP: ReadonlyArray<[keyof CommandFlags, string]> = [
546+
['snapshotDepth', '--depth'],
547+
['snapshotScope', '--scope'],
548+
['snapshotRaw', '--raw'],
549+
['snapshotBackend', '--backend'],
550+
];
551+
552+
export function unsupportedRefSnapshotFlags(flags: CommandFlags | undefined): string[] {
553+
if (!flags) return [];
554+
const unsupported: string[] = [];
555+
for (const [key, label] of REF_UNSUPPORTED_FLAG_MAP) {
556+
if (flags[key] !== undefined) unsupported.push(label);
557+
}
558+
return unsupported;
559+
}

src/utils/__tests__/args.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import test from 'node:test';
22
import assert from 'node:assert/strict';
33
import { parseArgs, usage } from '../args.ts';
44
import { AppError } from '../errors.ts';
5-
import { getCliCommandNames } from '../command-schema.ts';
5+
import { getCliCommandNames, getSchemaCapabilityKeys } from '../command-schema.ts';
66
import { listCapabilityCommands } from '../../core/capabilities.ts';
77

88
test('parseArgs recognizes --relaunch', () => {
@@ -75,6 +75,10 @@ test('every capability command has a parser schema entry', () => {
7575
}
7676
});
7777

78+
test('schema capability mappings match capability source-of-truth', () => {
79+
assert.deepEqual(getSchemaCapabilityKeys(), listCapabilityCommands());
80+
});
81+
7882
test('compat mode warns and strips unsupported pilot-command flags', () => {
7983
const parsed = parseArgs(['press', '10', '20', '--depth', '2'], { strictFlags: false });
8084
assert.equal(parsed.command, 'press');
@@ -109,6 +113,16 @@ test('unknown short flags are rejected', () => {
109113
);
110114
});
111115

116+
test('negative numeric positionals are accepted without -- separator', () => {
117+
const typed = parseArgs(['type', '-123'], { strictFlags: true });
118+
assert.equal(typed.command, 'type');
119+
assert.deepEqual(typed.positionals, ['-123']);
120+
121+
const pressed = parseArgs(['press', '-10', '20'], { strictFlags: true });
122+
assert.equal(pressed.command, 'press');
123+
assert.deepEqual(pressed.positionals, ['-10', '20']);
124+
});
125+
112126
test('all commands participate in strict command-flag validation', () => {
113127
assert.throws(
114128
() => parseArgs(['open', 'Settings', '--depth', '1'], { strictFlags: true }),

0 commit comments

Comments
 (0)