mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 14:19:56 +00:00
fix(cli): improve cliproxy target parsing edge cases
- support POSIX -- terminator so dash-prefixed variant names remain positional - export variant parser for direct unit tests - add --target parsing coverage for API and cliproxy arg parsers
This commit is contained in:
@@ -48,17 +48,24 @@ function parseTargetValue(rawValue: string): TargetType | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseProfileArgs(args: string[]): CliproxyProfileArgs {
|
export function parseProfileArgs(args: string[]): CliproxyProfileArgs {
|
||||||
const result: CliproxyProfileArgs = { errors: [] };
|
const result: CliproxyProfileArgs = { errors: [] };
|
||||||
|
let parseOptions = true;
|
||||||
|
|
||||||
for (let i = 0; i < args.length; i++) {
|
for (let i = 0; i < args.length; i++) {
|
||||||
const arg = args[i];
|
const arg = args[i];
|
||||||
if (arg === '--provider' && args[i + 1]) {
|
if (parseOptions && arg === '--') {
|
||||||
|
parseOptions = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseOptions && arg === '--provider' && args[i + 1]) {
|
||||||
result.provider = args[++i] as CLIProxyProfileName;
|
result.provider = args[++i] as CLIProxyProfileName;
|
||||||
} else if (arg === '--model' && args[i + 1]) {
|
} else if (parseOptions && arg === '--model' && args[i + 1]) {
|
||||||
result.model = args[++i];
|
result.model = args[++i];
|
||||||
} else if (arg === '--account' && args[i + 1]) {
|
} else if (parseOptions && arg === '--account' && args[i + 1]) {
|
||||||
result.account = args[++i];
|
result.account = args[++i];
|
||||||
} else if (arg === '--target') {
|
} else if (parseOptions && arg === '--target') {
|
||||||
const rawValue = args[i + 1];
|
const rawValue = args[i + 1];
|
||||||
if (!rawValue || rawValue.startsWith('-')) {
|
if (!rawValue || rawValue.startsWith('-')) {
|
||||||
result.errors.push('Missing value for --target');
|
result.errors.push('Missing value for --target');
|
||||||
@@ -71,7 +78,7 @@ function parseProfileArgs(args: string[]): CliproxyProfileArgs {
|
|||||||
result.target = parsedTarget;
|
result.target = parsedTarget;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (arg.startsWith('--target=')) {
|
} else if (parseOptions && arg.startsWith('--target=')) {
|
||||||
const rawValue = arg.slice('--target='.length);
|
const rawValue = arg.slice('--target='.length);
|
||||||
const parsedTarget = parseTargetValue(rawValue);
|
const parsedTarget = parseTargetValue(rawValue);
|
||||||
if (!parsedTarget) {
|
if (!parsedTarget) {
|
||||||
@@ -79,13 +86,13 @@ function parseProfileArgs(args: string[]): CliproxyProfileArgs {
|
|||||||
} else {
|
} else {
|
||||||
result.target = parsedTarget;
|
result.target = parsedTarget;
|
||||||
}
|
}
|
||||||
} else if (arg === '--force') {
|
} else if (parseOptions && arg === '--force') {
|
||||||
result.force = true;
|
result.force = true;
|
||||||
} else if (arg === '--yes' || arg === '-y') {
|
} else if (parseOptions && (arg === '--yes' || arg === '-y')) {
|
||||||
result.yes = true;
|
result.yes = true;
|
||||||
} else if (arg === '--composite') {
|
} else if (parseOptions && arg === '--composite') {
|
||||||
result.composite = true;
|
result.composite = true;
|
||||||
} else if (!arg.startsWith('-') && !result.name) {
|
} else if ((!parseOptions || !arg.startsWith('-')) && !result.name) {
|
||||||
result.name = arg;
|
result.name = arg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,10 +44,40 @@ describe('api-command arg parser', () => {
|
|||||||
expect(parsed.errors).toEqual([]);
|
expect(parsed.errors).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('parses --target=value for default profile target', () => {
|
||||||
|
const parsed = parseApiCommandArgs(['my-api', '--target=droid']);
|
||||||
|
|
||||||
|
expect(parsed.name).toBe('my-api');
|
||||||
|
expect(parsed.target).toBe('droid');
|
||||||
|
expect(parsed.errors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
test('validates invalid --target values', () => {
|
test('validates invalid --target values', () => {
|
||||||
const parsed = parseApiCommandArgs(['my-api', '--target', 'invalid-target']);
|
const parsed = parseApiCommandArgs(['my-api', '--target', 'invalid-target']);
|
||||||
|
|
||||||
expect(parsed.target).toBeUndefined();
|
expect(parsed.target).toBeUndefined();
|
||||||
expect(parsed.errors).toEqual(['Invalid --target value "invalid-target". Use: claude or droid']);
|
expect(parsed.errors).toEqual(['Invalid --target value "invalid-target". Use: claude or droid']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('collects missing-value error for --target with no value', () => {
|
||||||
|
const parsed = parseApiCommandArgs(['my-api', '--target']);
|
||||||
|
|
||||||
|
expect(parsed.target).toBeUndefined();
|
||||||
|
expect(parsed.errors).toEqual(['Missing value for --target']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('treats empty --target=value as missing value', () => {
|
||||||
|
const parsed = parseApiCommandArgs(['my-api', '--target=']);
|
||||||
|
|
||||||
|
expect(parsed.target).toBeUndefined();
|
||||||
|
expect(parsed.errors).toEqual(['Missing value for --target']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses last --target value when repeated', () => {
|
||||||
|
const parsed = parseApiCommandArgs(['my-api', '--target', 'claude', '--target=droid']);
|
||||||
|
|
||||||
|
expect(parsed.name).toBe('my-api');
|
||||||
|
expect(parsed.target).toBe('droid');
|
||||||
|
expect(parsed.errors).toEqual([]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
|
||||||
|
import { parseProfileArgs } from '../../../src/commands/cliproxy/variant-subcommand';
|
||||||
|
|
||||||
|
describe('cliproxy variant arg parser', () => {
|
||||||
|
test('parses --target value form', () => {
|
||||||
|
const parsed = parseProfileArgs(['variant-a', '--target', 'droid']);
|
||||||
|
|
||||||
|
expect(parsed.name).toBe('variant-a');
|
||||||
|
expect(parsed.target).toBe('droid');
|
||||||
|
expect(parsed.errors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parses --target=value form', () => {
|
||||||
|
const parsed = parseProfileArgs(['variant-a', '--target=droid']);
|
||||||
|
|
||||||
|
expect(parsed.name).toBe('variant-a');
|
||||||
|
expect(parsed.target).toBe('droid');
|
||||||
|
expect(parsed.errors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('collects missing value error for --target with no value', () => {
|
||||||
|
const parsed = parseProfileArgs(['variant-a', '--target']);
|
||||||
|
|
||||||
|
expect(parsed.target).toBeUndefined();
|
||||||
|
expect(parsed.errors).toEqual(['Missing value for --target']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses last --target value when repeated', () => {
|
||||||
|
const parsed = parseProfileArgs(['variant-a', '--target', 'claude', '--target=droid']);
|
||||||
|
|
||||||
|
expect(parsed.target).toBe('droid');
|
||||||
|
expect(parsed.errors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports option terminator for variant names that start with dash', () => {
|
||||||
|
const parsed = parseProfileArgs(['--yes', '--', '-variant-a']);
|
||||||
|
|
||||||
|
expect(parsed.yes).toBe(true);
|
||||||
|
expect(parsed.name).toBe('-variant-a');
|
||||||
|
expect(parsed.errors).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not parse flags after option terminator', () => {
|
||||||
|
const parsed = parseProfileArgs(['--', '--target', 'droid']);
|
||||||
|
|
||||||
|
expect(parsed.target).toBeUndefined();
|
||||||
|
expect(parsed.name).toBe('--target');
|
||||||
|
expect(parsed.errors).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user