Skip to content
Open
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
11 changes: 11 additions & 0 deletions dev/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ export function createProgram(): Command {
program
.addOption(new Option('-v, --version', 'Get ADK CLI version'))
.action((options: {version?: boolean}) => {
const [unknownCommand] = program.args;
if (unknownCommand !== undefined) {
program.error(`error: unknown command '${unknownCommand}'`, {
code: 'commander.unknownCommand',
});
}
if (options.version) {
console.log(version);
return;
Expand Down Expand Up @@ -551,5 +557,10 @@ export function createProgram(): Command {
});
});

// Must stay after every .command() call: commander copies this setting into
// subcommands as they are created, which would make them accept excess
// arguments too.
program.allowExcessArguments(true);

return program;
}
85 changes: 83 additions & 2 deletions dev/test/cli/cli_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import {LogLevel, setLogLevel} from '@google/adk';
import {CommanderError} from 'commander';
import {Command, CommanderError} from 'commander';
import {afterEach, beforeEach, describe, expect, it, Mock, vi} from 'vitest';
import {createProgram} from '../../src/cli/cli.js';
import {createAgent} from '../../src/cli/cli_create.js';
Expand Down Expand Up @@ -152,12 +152,93 @@ describe('CLI Entrypoint', () => {
expect.fail('expected commander to reject an unknown subcommand');
}

expect(err.code).toBe('commander.excessArguments');
expect(err.code).toBe('commander.unknownCommand');
expect(err.exitCode).toBe(1);
expect(stdout).toBe('');
});
});

describe('unknown subcommand', () => {
let stdout: string;
let stderr: string;

const captureOutputAndExit = (cmd: Command) => {
cmd.exitOverride();
cmd.configureOutput({
writeOut: (str) => {
stdout += str;
},
writeErr: (str) => {
stderr += str;
},
});
cmd.commands.forEach(captureOutputAndExit);
};

beforeEach(() => {
stdout = '';
stderr = '';
captureOutputAndExit(program);
});

const parseExpectingError = async (
args: string[],
): Promise<CommanderError | undefined> => {
try {
await program.parseAsync(['node', 'cli_entrypoint.js', ...args]);
} catch (e: unknown) {
if (e instanceof CommanderError) {
return e;
}
throw e;
}
return undefined;
};

it('should name the unrecognised subcommand and skip the root action', async () => {
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});

const err = await parseExpectingError(['bogus']);
if (!err) {
expect.fail('expected commander to reject an unknown subcommand');
}

expect(stderr).toContain("error: unknown command 'bogus'");
expect(err.code).toBe('commander.unknownCommand');
expect(err.exitCode).toBe(1);
expect(logSpy).not.toHaveBeenCalledWith('1.0.0-test');
});

it('should report only the first operand of an unrecognised subcommand', async () => {
const err = await parseExpectingError(['bogus', 'extra']);
if (!err) {
expect.fail('expected commander to reject an unknown subcommand');
}

expect(stderr).toContain("error: unknown command 'bogus'");
expect(stderr).not.toContain('extra');
expect(err.code).toBe('commander.unknownCommand');
expect(err.exitCode).toBe(1);
});

it('should leave a bare invocation on the non-error path', async () => {
expect(await parseExpectingError([])).toBeUndefined();

expect(stderr).toBe('');
expect(stdout).toContain('Usage: adk');
});

it('should still reject excess arguments on a subcommand', async () => {
const err = await parseExpectingError(['create', 'x', 'y']);
if (!err) {
expect.fail('expected commander to reject excess subcommand arguments');
}

expect(err.code).toBe('commander.excessArguments');
expect(err.exitCode).toBe(1);
});
});

describe('command: web', () => {
it('should start AdkApiServer with default options', async () => {
await parse(['web']);
Expand Down