Skip to content
Merged
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
39 changes: 38 additions & 1 deletion src/core/cli.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { existsSync, statSync } from "node:fs";
import { existsSync, readFileSync, statSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { Command } from "commander";
import type { CommonOptions, HelpCommandInput, LayoutMode, PagerCommandInput, ParsedCliInput } from "./types";

Expand Down Expand Up @@ -68,6 +69,37 @@ function applyCommonOptions(command: Command) {
.option("--no-agent-notes", "hide agent notes by default");
}

/** Resolve the CLI version from the nearest shipped package manifest. */
function resolveCliVersion() {
const candidatePaths = [
resolve(import.meta.dir, "..", "..", "package.json"),
resolve(dirname(process.execPath), "..", "package.json"),
resolve(dirname(process.execPath), "..", "..", "package.json"),
];

for (const candidatePath of candidatePaths) {
if (!existsSync(candidatePath)) {
continue;
}

try {
const parsed = JSON.parse(readFileSync(candidatePath, "utf8")) as { version?: unknown };
if (typeof parsed.version === "string" && parsed.version.length > 0) {
return parsed.version;
}
} catch {
continue;
}
}

return "0.0.0-unknown";
}

/** Render plain-text version output for `hunk --version`. */
function renderCliVersion() {
return `${resolveCliVersion()}\n`;
}

/** Build the top-level help text shown by bare `hunk` and `hunk --help`. */
function renderCliHelp() {
return [
Expand All @@ -87,6 +119,7 @@ function renderCliHelp() {
"",
"Options:",
" -h, --help show help",
" -v, --version show version",
"",
"Examples:",
" hunk diff",
Expand Down Expand Up @@ -360,6 +393,10 @@ export async function parseCli(argv: string[]): Promise<ParsedCliInput> {
return { kind: "help", text: renderCliHelp() };
}

if (commandName === "--version" || commandName === "-v" || commandName === "version") {
return { kind: "help", text: renderCliVersion() };
}

switch (commandName) {
case "diff":
return parseDiffCommand(rest, argv);
Expand Down
9 changes: 9 additions & 0 deletions test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ describe("parseCli", () => {
expect(explicit).toEqual(bare);
});

test("prints the package version for --version and version", async () => {
const expectedVersion = require("../package.json").version;
const flag = await parseCli(["bun", "hunk", "--version"]);
const command = await parseCli(["bun", "hunk", "version"]);

expect(flag).toEqual({ kind: "help", text: `${expectedVersion}\n` });
expect(command).toEqual(flag);
});

test("parses git-style diff mode with shared options", async () => {
const parsed = await parseCli([
"bun",
Expand Down
18 changes: 18 additions & 0 deletions test/help-output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,24 @@ describe("CLI help output", () => {
expect(stdout).not.toContain("\u001b[?1049h");
});

test("prints the package version for --version without terminal takeover sequences", () => {
const expectedVersion = require("../package.json").version;
const proc = Bun.spawnSync(["bun", "run", "src/main.tsx", "--version"], {
cwd: process.cwd(),
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
});

const stdout = Buffer.from(proc.stdout).toString("utf8");
const stderr = Buffer.from(proc.stderr).toString("utf8");

expect(proc.exitCode).toBe(0);
expect(stderr).toBe("");
expect(stdout).toBe(`${expectedVersion}\n`);
expect(stdout).not.toContain("\u001b[?1049h");
});

test("general pager mode falls back to plain text for non-diff stdin", () => {
const proc = Bun.spawnSync(["bash", "-lc", "printf '* main\\n feature/demo\\n' | HUNK_TEXT_PAGER=cat bun run src/main.tsx pager"], {
cwd: process.cwd(),
Expand Down
Loading