From fe7d10389d4f9835a3591027e79f3c00953b7d04 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 7 Nov 2025 14:57:58 +0000 Subject: [PATCH] Fix global help and version flags --- src/cli.ts | 60 +++++++++++++++++++++++++++------ tests/cli-global-flags.test.ts | 61 ++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 tests/cli-global-flags.test.ts diff --git a/src/cli.ts b/src/cli.ts index 294725f..b49f72f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -27,15 +27,15 @@ export { handleInspectCli } from './cli/inspect-cli-command.js'; export { extractListFlags, handleList } from './cli/list-command.js'; export { resolveCallTimeout } from './cli/timeouts.js'; -// main parses CLI flags and dispatches to list/call commands. -async function main(): Promise { - const argv = process.argv.slice(2); - if (argv.length === 0) { +export async function runCli(argv: string[]): Promise { + const args = [...argv]; + if (args.length === 0) { printHelp(); process.exit(1); + return; } - const globalFlags = extractFlags(argv, ['--config', '--root', '--log-level']); + const globalFlags = extractFlags(args, ['--config', '--root', '--log-level']); if (globalFlags['--log-level']) { try { const parsedLevel = parseLogLevel(globalFlags['--log-level'], getActiveLogLevel()); @@ -44,22 +44,35 @@ async function main(): Promise { const message = error instanceof Error ? error.message : String(error); logError(message, error instanceof Error ? error : undefined); process.exit(1); + return; } } - const command = argv.shift(); + const command = args.shift(); if (!command) { printHelp(); process.exit(1); + return; + } + + if (isHelpToken(command)) { + printHelp(); + process.exitCode = 0; + return; + } + + if (isVersionToken(command)) { + await printVersion(); + return; } if (command === 'generate-cli') { - await handleGenerateCli(argv, globalFlags); + await handleGenerateCli(args, globalFlags); return; } if (command === 'inspect-cli') { - await handleInspectCli(argv); + await handleInspectCli(args); return; } @@ -70,7 +83,7 @@ async function main(): Promise { logger: getActiveLogger(), }); try { - await handleEmitTs(runtime, argv); + await handleEmitTs(runtime, args); } finally { await runtime.close().catch(() => {}); } @@ -83,7 +96,7 @@ async function main(): Promise { logger: getActiveLogger(), }); - const inference = inferCommandRouting(command, argv, runtime.getDefinitions()); + const inference = inferCommandRouting(command, args, runtime.getDefinitions()); if (inference.kind === 'abort') { process.exitCode = inference.exitCode; return; @@ -143,11 +156,15 @@ async function main(): Promise { } } } - printHelp(`Unknown command '${resolvedCommand}'.`); process.exit(1); } +// main parses CLI flags and dispatches to list/call commands. +async function main(): Promise { + await runCli(process.argv.slice(2)); +} + // printHelp explains available commands and global flags. function printHelp(message?: string): void { if (message) { @@ -195,6 +212,27 @@ Examples: `); } +async function printVersion(): Promise { + try { + const packageJsonPath = new URL('../package.json', import.meta.url); + const buffer = await fsPromises.readFile(packageJsonPath, 'utf8'); + const pkg = JSON.parse(buffer) as { version?: string }; + console.log(pkg.version ?? '0.0.0'); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logError(`Failed to read mcporter version: ${message}`, error instanceof Error ? error : undefined); + process.exit(1); + } +} + +function isHelpToken(token: string): boolean { + return token === '--help' || token === '-h' || token === 'help'; +} + +function isVersionToken(token: string): boolean { + return token === '--version' || token === '-v' || token === '-V'; +} + if (process.env.MCPORTER_DISABLE_AUTORUN !== '1') { main().catch((error) => { if (error instanceof CliUsageError) { diff --git a/tests/cli-global-flags.test.ts b/tests/cli-global-flags.test.ts new file mode 100644 index 0000000..eb160b3 --- /dev/null +++ b/tests/cli-global-flags.test.ts @@ -0,0 +1,61 @@ +import fsPromises from 'node:fs/promises'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +process.env.MCPORTER_DISABLE_AUTORUN = '1'; +const cliModulePromise = import('../src/cli.js'); + +const packageJsonPath = new URL('../package.json', import.meta.url); +async function readPackageVersion(): Promise { + const buffer = await fsPromises.readFile(packageJsonPath, 'utf8'); + const pkg = JSON.parse(buffer) as { version?: string }; + return pkg.version ?? '0.0.0'; +} + +describe('mcporter global shortcuts', () => { + afterEach(() => { + vi.restoreAllMocks(); + process.exitCode = undefined; + }); + + it('prints global help before inference when --help is provided', async () => { + const { runCli } = await cliModulePromise; + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + + await runCli(['--help']); + + expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Usage: mcporter')); + expect(process.exitCode).toBe(0); + }); + + it('prints global help when the bare help token is provided', async () => { + const { runCli } = await cliModulePromise; + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + + await runCli(['help']); + + expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Usage: mcporter')); + expect(process.exitCode).toBe(0); + }); + + it('prints the package version when --version is provided', async () => { + const { runCli } = await cliModulePromise; + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + const expectedVersion = await readPackageVersion(); + + await runCli(['--version']); + + expect(logSpy).toHaveBeenCalledWith(expectedVersion); + expect(process.exitCode).toBeUndefined(); + }); + + it('prints the package version when -v is provided', async () => { + const { runCli } = await cliModulePromise; + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + const expectedVersion = await readPackageVersion(); + + await runCli(['-v']); + + expect(logSpy).toHaveBeenCalledWith(expectedVersion); + expect(process.exitCode).toBeUndefined(); + }); +});