diff --git a/src/cli.ts b/src/cli.ts index b396cddf..692aeae7 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,12 +1,52 @@ +import {readFileSync} from "node:fs"; import Server from "./server.js"; -if (process.argv.length > 2) { - process.stderr.write("\n"); - process.stderr.write("Unexpected arguments: This command does not accept any arguments.\n"); - process.stderr.write("Usage: ui5mcp\n"); - process.exit(2); -} else { - const server = new Server(); +const args = process.argv.slice(2); +const usage = `Usage: ui5mcp - await server.connect(); +Starts the UI5 MCP server over stdio. + +Options: + -h, --help Show this help message. + -v, --version Show the version. +`; + +function getPackageVersion() { + const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as unknown; + const hasVersion = typeof packageJson === "object" && packageJson !== null && "version" in packageJson; + + if (hasVersion && typeof packageJson.version === "string") { + return packageJson.version; + } + + throw new Error("Unable to determine package version"); +} + +function handleMetadataFlags() { + if (args.length === 1 && ["--help", "-h"].includes(args[0])) { + process.stdout.write(usage); + process.exit(0); + return true; + } + + if (args.length === 1 && ["--version", "-v"].includes(args[0])) { + process.stdout.write(`${getPackageVersion()}\n`); + process.exit(0); + return true; + } + + return false; +} + +if (!handleMetadataFlags()) { + if (args.length > 0) { + process.stderr.write("\n"); + process.stderr.write("Unexpected arguments: This command does not accept any arguments.\n"); + process.stderr.write("Usage: ui5mcp\n"); + process.exit(2); + } else { + const server = new Server(); + + await server.connect(); + } } diff --git a/test/lib/cli.ts b/test/lib/cli.ts index e54ce545..f065dbcc 100644 --- a/test/lib/cli.ts +++ b/test/lib/cli.ts @@ -16,7 +16,7 @@ test.beforeEach((t) => { t.context.sinon = sinonGlobal.createSandbox(); // Store and mock process.argv - t.context.originalArgv = process.argv; + t.context.originalArgv = process.argv.slice(); // Create a mock Server instance with a connect method const serverInstance = { @@ -122,3 +122,41 @@ test.serial("CLI exits with error when arguments are provided", async (t) => { // Verify Server constructor was not called t.false(t.context.ServerConstructor.called); }); + +test.serial("CLI prints help and exits without starting the server", async (t) => { + const {sinon, exitStub} = t.context; + + const stdoutWriteStub = sinon.stub(process.stdout, "write"); + + process.argv.push("--help"); + + await esmock("../../src/cli.js", { + "../../src/server.js": { + default: t.context.ServerConstructor, + }, + }); + + t.true(exitStub.calledOnceWith(0)); + t.true(stdoutWriteStub.calledOnce); + t.regex(stdoutWriteStub.firstCall.firstArg as string, /Usage: ui5mcp/); + t.regex(stdoutWriteStub.firstCall.firstArg as string, /--version/); + t.false(t.context.ServerConstructor.called); +}); + +test.serial("CLI prints version and exits without starting the server", async (t) => { + const {sinon, exitStub} = t.context; + + const stdoutWriteStub = sinon.stub(process.stdout, "write"); + + process.argv.push("--version"); + + await esmock("../../src/cli.js", { + "../../src/server.js": { + default: t.context.ServerConstructor, + }, + }); + + t.true(exitStub.calledOnceWith(0)); + t.true(stdoutWriteStub.calledOnceWith("0.2.12\n")); + t.false(t.context.ServerConstructor.called); +});