From 197a6936b5a2d05bca9a3f59050c70e8815dde07 Mon Sep 17 00:00:00 2001 From: Alec Rust Date: Fri, 1 May 2026 11:43:12 +0100 Subject: [PATCH] fix(cli): add version flag --- src/cli.test.ts | 53 ++++++++++++++++++++++++++++++++++++++++++++ src/cli.ts | 23 +++++++++++++++++++ src/cli/args.test.ts | 7 ++++++ src/cli/args.ts | 5 +++++ src/cli/help.ts | 2 ++ 5 files changed, 90 insertions(+) create mode 100644 src/cli.test.ts diff --git a/src/cli.test.ts b/src/cli.test.ts new file mode 100644 index 0000000..877b4e1 --- /dev/null +++ b/src/cli.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from "bun:test"; + +const readStream = async (stream: ReadableStream | null): Promise => { + if (!stream) { + return ""; + } + return await new Response(stream).text(); +}; + +const packageVersion = async (): Promise => { + const packageJson = (await Bun.file(`${import.meta.dir}/../package.json`).json()) as { + version: string; + }; + return packageJson.version; +}; + +const runWorkbox = async (args: string[]) => { + const proc = Bun.spawn(["bun", "./src/cli.ts", ...args], { + cwd: `${import.meta.dir}/..`, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([ + readStream(proc.stdout), + readStream(proc.stderr), + proc.exited, + ]); + return { stdout, stderr, exitCode }; +}; + +describe("cli", () => { + it("prints the package version", async () => { + const version = await packageVersion(); + const result = await runWorkbox(["--version"]); + + expect(result.exitCode).toBe(0); + expect(result.stderr).toBe(""); + expect(result.stdout).toBe(`workbox ${version}\n`); + }); + + it("prints the package version as JSON", async () => { + const version = await packageVersion(); + const result = await runWorkbox(["--json", "--version"]); + + expect(result.exitCode).toBe(0); + expect(result.stderr).toBe(""); + expect(JSON.parse(result.stdout)).toEqual({ + ok: true, + message: `workbox ${version}`, + data: { version }, + }); + }); +}); diff --git a/src/cli.ts b/src/cli.ts index 5eefc75..d224df0 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -12,6 +12,13 @@ const TOOL_ALIAS = "wkb"; type OutputTarget = NodeJS.WritableStream; +const loadPackageVersion = async (): Promise => { + const packageJson = (await Bun.file(new URL("../package.json", import.meta.url)).json()) as { + version?: unknown; + }; + return typeof packageJson.version === "string" ? packageJson.version : "unknown"; +}; + const writeOutput = (output: string, stream: OutputTarget): void => { if (output.trim().length === 0) { return; @@ -27,6 +34,22 @@ export const runCli = async (argv: string[], cwd = process.cwd()): Promise { expect(result.command).toBe("list"); }); + it("parses version flag", () => { + const result = parseCliArgs(["--version"]); + expect(result.flags.version).toBe(true); + expect(result.command).toBeNull(); + expect(result.errors).toEqual([]); + }); + it("treats passthrough options as the command when no command is set", () => { const result = parseCliArgs(["--", "--do", "thing"]); expect(result.command).toBe("--do"); diff --git a/src/cli/args.ts b/src/cli/args.ts index 8959baa..39d22ec 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -4,6 +4,7 @@ type CliFlags = { help: boolean; json: boolean; nonInteractive: boolean; + version: boolean; }; type ParsedArgs = { @@ -17,6 +18,7 @@ const GLOBAL_OPTIONS = { help: { type: "boolean", short: "h" }, json: { type: "boolean" }, "non-interactive": { type: "boolean" }, + version: { type: "boolean" }, } as const; type ParsedToken = @@ -46,6 +48,8 @@ const flagFromOption = (name: string): keyof CliFlags | null => { return "json"; case "non-interactive": return "nonInteractive"; + case "version": + return "version"; default: return null; } @@ -74,6 +78,7 @@ export const parseCliArgs = (argv: string[]): ParsedArgs => { help: false, json: false, nonInteractive: false, + version: false, }; const errors: string[] = []; const commandArgs: string[] = []; diff --git a/src/cli/help.ts b/src/cli/help.ts index 7d734bd..c3de0af 100644 --- a/src/cli/help.ts +++ b/src/cli/help.ts @@ -20,6 +20,7 @@ export const renderGlobalHelp = (toolName: string, alias: string): string => { " --help Show help for a command", " --json Output machine-readable JSON", " --non-interactive Disable prompts and fail fast", + " --version Show the current version", ].join("\n"); }; @@ -37,4 +38,5 @@ export const renderCommandHelp = (toolName: string, command: CommandDefinition): " --help Show help for this command", " --json Output machine-readable JSON", " --non-interactive Disable prompts and fail fast", + " --version Show the current version", ].join("\n");