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
5 changes: 5 additions & 0 deletions .changeset/windows-arm64-launcher-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hunkdiff": patch
---

Report that Windows ARM64 is not supported by the current bundled runtime instead of crashing with a low-context Bun FFI error.
17 changes: 17 additions & 0 deletions packages/hunk/bin/hunk.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ function bundledBunRuntime() {
}
}

// Exits on Windows ARM64 before bundled Bun starts OpenTUI. Bun disables TinyCC there
// (oven-sh/bun#28055). Callers honor HUNK_BIN_PATH and any installed prebuilt first.
function rejectUnsupportedWindowsArm64Runtime() {
if (os.platform() !== "win32" || os.arch() !== "arm64") {
return;
}

console.error(
"hunk: Windows ARM64 is not supported by the current bundled runtime. " +
"Bun disables TinyCC on this platform, so OpenTUI cannot load. " +
"Tracking: oven-sh/bun#28055",
);
process.exit(1);
}

const forwardedArgs = process.argv.slice(2);
if (forwardedArgs.length === 2 && forwardedArgs[0] === "skill" && forwardedArgs[1] === "path") {
const skillPath = bundledSkillPath();
Expand All @@ -125,6 +140,8 @@ if (prebuiltBinary) {
run(prebuiltBinary, forwardedArgs);
}

rejectUnsupportedWindowsArm64Runtime();

const bunBinary = bundledBunRuntime();
if (bunBinary) {
const entrypoint = path.join(__dirname, "..", "dist", "npm", "main.js");
Expand Down
56 changes: 56 additions & 0 deletions test/cli/entrypoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,62 @@ describe("CLI entrypoint contracts", () => {
}
});

test("bin wrapper reports Windows ARM64 before the bundled Bun fallback", () => {
const tempDir = mkdtempSync(join(tmpdir(), "hunk-wrapper-win-arm64-"));
const packageRoot = join(tempDir, "package");
const tempWrapperPath = join(packageRoot, "bin", "hunk.cjs");
const preloadPath = join(tempDir, "os-preload.cjs");
const bunMarkerPath = join(tempDir, "bun-invoked");
const fakeBunDir = join(packageRoot, "node_modules", "bun", "bin");
const fakeBunPath = join(fakeBunDir, "bun.exe");

try {
mkdirSync(join(packageRoot, "bin"), { recursive: true });
mkdirSync(fakeBunDir, { recursive: true });
copyFileSync(join(process.cwd(), "packages", "hunk", "bin", "hunk.cjs"), tempWrapperPath);
writeFileSync(
join(packageRoot, "node_modules", "bun", "package.json"),
JSON.stringify({ name: "bun", version: "0.0.0" }),
);
// Resolvable bundled runtime. Records a marker only if the wrapper actually spawns it.
writeFileSync(
fakeBunPath,
`#!/usr/bin/env node\nrequire("node:fs").writeFileSync(${JSON.stringify(bunMarkerPath)}, "invoked\\n");\n`,
{ mode: 0o755 },
);
writeFileSync(
preloadPath,
'const os = require("node:os");\nos.platform = () => "win32";\nos.arch = () => "arm64";\n',
);

const env = { ...process.env };
delete env.HUNK_BIN_PATH;

const proc = Bun.spawnSync(["node", "-r", preloadPath, tempWrapperPath, "diff"], {
cwd: packageRoot,
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
env,
});

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

expect(proc.exitCode).toBe(1);
expect(stdout).toBe("");
expect(stderr).toContain("Windows ARM64 is not supported by the current bundled runtime");
expect(stderr).toContain("oven-sh/bun#28055");
expect(stderr).not.toContain("bun:ffi");
expect(stderr).not.toContain("dlopen");
expect(stderr).not.toContain("Bun v");
expect(stderr).not.toContain("Unsupported platform for prebuilt Hunk binaries");
expect(existsSync(bunMarkerPath)).toBe(false);
} finally {
rmSync(tempDir, { recursive: true, force: true });
}
});

test("general pager mode falls back to plain text for non-diff stdin", () => {
const proc = Bun.spawnSync(["bun", "run", "packages/hunk/src/main.tsx", "pager"], {
cwd: process.cwd(),
Expand Down
Loading