Skip to content

Commit f8bae97

Browse files
OfekwCopilot
andauthored
feat: complete native Windows 11 support (#285)
Co-authored-by: Ofek Wengrowicz <ofekw@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d6ed06f commit f8bae97

16 files changed

Lines changed: 255 additions & 103 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ All notable user-visible changes to Hunk are documented in this file.
88

99
- Added Windows x64 prebuilt artifact publishing to the release workflow.
1010
- Added Nix flake app outputs for `nix run` and a named `hunk` package output.
11+
- Documented native Windows support in the README and contributor guide.
1112

1213
### Changed
1314

15+
- Ported `build:npm`, `build:bin`, and `install:bin` from bash scripts to cross-platform Bun-runnable TypeScript so native Windows contributors no longer need Git Bash to build or install Hunk locally.
16+
1417
### Fixed
1518

1619
## [0.12.0-beta.1] - 2026-05-10

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Requirements:
1111
- Bun 1.3+
1212
- Node.js 18+
1313
- Git
14+
- macOS, Linux, or Windows. The Windows path uses native Node/Bun on Windows 10/11 (x64); no WSL or Git Bash required.
1415

1516
> Nix users can use `nix develop` or [direnv](https://direnv.net/) to enter a development shell.
1617

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ brew install modem-dev/tap/hunk
4242
Requirements:
4343

4444
- Node.js 18+
45-
- macOS or Linux
45+
- macOS, Linux, or Windows
4646
- Git recommended for most workflows
4747

4848
> Nix users can use the `default` package exported in `flake.nix` instead. See [nix/README.md](./nix/README.md) for details.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@
4646
"scripts": {
4747
"start": "bun run src/main.tsx",
4848
"dev": "bun --watch src/main.tsx",
49-
"build:npm": "bash ./scripts/build-npm.sh",
50-
"build:bin": "bash ./scripts/build-bin.sh",
49+
"build:npm": "bun run ./scripts/build-npm.ts",
50+
"build:bin": "bun run ./scripts/build-bin.ts",
5151
"build:prebuilt:npm": "bun run build:npm && bun run build:bin && bun run ./scripts/stage-prebuilt-npm.ts",
5252
"build:prebuilt:artifact": "bun run build:bin && bun run ./scripts/build-prebuilt-artifact.ts",
5353
"stage:prebuilt:release": "bun run build:npm && bun run ./scripts/stage-prebuilt-npm.ts --artifact-root ./dist/release/artifacts",
54-
"install:bin": "bash ./scripts/install-bin.sh",
54+
"install:bin": "bun run ./scripts/install-bin.ts",
5555
"typecheck": "tsc --noEmit",
5656
"format": "oxfmt --write .",
5757
"format:check": "oxfmt --check .",

scripts/build-bin.sh

Lines changed: 0 additions & 16 deletions
This file was deleted.

scripts/build-bin.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env bun
2+
3+
import { mkdirSync, rmSync } from "node:fs";
4+
import path from "node:path";
5+
6+
const repoRoot = path.resolve(import.meta.dir, "..");
7+
const distDir = path.join(repoRoot, "dist");
8+
const binaryName = process.platform === "win32" ? "hunk.exe" : "hunk";
9+
const outfile = path.join(distDir, binaryName);
10+
const legacyOutfile = path.join(distDir, process.platform === "win32" ? "otdiff.exe" : "otdiff");
11+
12+
mkdirSync(distDir, { recursive: true });
13+
rmSync(legacyOutfile, { force: true });
14+
15+
const proc = Bun.spawnSync(
16+
["bun", "build", "--compile", path.join(repoRoot, "src", "main.tsx"), "--outfile", outfile],
17+
{
18+
cwd: repoRoot,
19+
stdin: "inherit",
20+
stdout: "inherit",
21+
stderr: "inherit",
22+
env: {
23+
...process.env,
24+
BUN_TMPDIR: path.join(repoRoot, ".bun-tmp"),
25+
BUN_INSTALL: path.join(repoRoot, ".bun-install"),
26+
},
27+
},
28+
);
29+
30+
if (proc.exitCode !== 0) {
31+
throw new Error(`bun build --compile failed with exit ${proc.exitCode}`);
32+
}
33+
34+
console.log(`Built ${outfile}`);

scripts/build-npm.sh

Lines changed: 0 additions & 44 deletions
This file was deleted.

scripts/build-npm.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env bun
2+
3+
import { chmodSync, copyFileSync, mkdirSync, readdirSync, rmSync } from "node:fs";
4+
import path from "node:path";
5+
6+
const repoRoot = path.resolve(import.meta.dir, "..");
7+
const outdir = path.join(repoRoot, "dist", "npm");
8+
const typesOutdir = path.join(repoRoot, "dist", "npm-types");
9+
const opentuiOutdir = path.join(outdir, "opentui");
10+
const opentuiTypesDir = path.join(typesOutdir, "opentui");
11+
12+
const bunEnv = {
13+
...process.env,
14+
BUN_TMPDIR: path.join(repoRoot, ".bun-tmp"),
15+
BUN_INSTALL: path.join(repoRoot, ".bun-install"),
16+
};
17+
18+
function runBun(args: string[]) {
19+
const proc = Bun.spawnSync(["bun", ...args], {
20+
cwd: repoRoot,
21+
stdin: "inherit",
22+
stdout: "inherit",
23+
stderr: "inherit",
24+
env: bunEnv,
25+
});
26+
27+
if (proc.exitCode !== 0) {
28+
throw new Error(`bun ${args.join(" ")} failed with exit ${proc.exitCode}`);
29+
}
30+
}
31+
32+
rmSync(outdir, { recursive: true, force: true });
33+
rmSync(typesOutdir, { recursive: true, force: true });
34+
mkdirSync(opentuiOutdir, { recursive: true });
35+
36+
runBun([
37+
"build",
38+
path.join(repoRoot, "src", "main.tsx"),
39+
"--target",
40+
"bun",
41+
"--format",
42+
"esm",
43+
"--outdir",
44+
outdir,
45+
"--entry-naming",
46+
"main.js",
47+
]);
48+
49+
const mainJs = path.join(outdir, "main.js");
50+
// chmod is a no-op on Windows; preserve exec bits on Unix so the bin runs in npm-installed packages.
51+
if (process.platform !== "win32") {
52+
chmodSync(mainJs, 0o755);
53+
}
54+
55+
runBun([
56+
"build",
57+
path.join(repoRoot, "src", "opentui", "index.ts"),
58+
"--target",
59+
"node",
60+
"--format",
61+
"esm",
62+
"--external",
63+
"react",
64+
"--external",
65+
"react/jsx-runtime",
66+
"--external",
67+
"react/jsx-dev-runtime",
68+
"--external",
69+
"@opentui/core",
70+
"--external",
71+
"@opentui/react",
72+
"--external",
73+
"@opentui/react/jsx-runtime",
74+
"--external",
75+
"@opentui/react/jsx-dev-runtime",
76+
"--external",
77+
"@pierre/diffs",
78+
"--outdir",
79+
opentuiOutdir,
80+
"--entry-naming",
81+
"index.js",
82+
]);
83+
84+
runBun(["x", "tsc", "-p", path.join(repoRoot, "tsconfig.opentui.json")]);
85+
86+
for (const entry of readdirSync(opentuiTypesDir)) {
87+
if (entry.endsWith(".d.ts")) {
88+
copyFileSync(path.join(opentuiTypesDir, entry), path.join(opentuiOutdir, entry));
89+
}
90+
}
91+
92+
rmSync(typesOutdir, { recursive: true, force: true });
93+
94+
console.log(`Built ${mainJs}`);
95+
console.log(`Built ${path.join(opentuiOutdir, "index.js")}`);

scripts/check-pack.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env bun
22

3+
import { npmCommand } from "./script-helpers";
4+
35
interface PackedFile {
46
path: string;
57
size: number;
@@ -13,7 +15,7 @@ interface PackResult {
1315
files: PackedFile[];
1416
}
1517

16-
const proc = Bun.spawnSync(["npm", "pack", "--dry-run", "--json"], {
18+
const proc = Bun.spawnSync([npmCommand, "pack", "--dry-run", "--json"], {
1719
cwd: process.cwd(),
1820
stdin: "ignore",
1921
stdout: "pipe",

scripts/check-prebuilt-pack.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { existsSync, readdirSync } from "node:fs";
44
import path from "node:path";
55
import { releaseNpmDir } from "./prebuilt-package-helpers";
6+
import { npmCommand } from "./script-helpers";
67

78
interface PackedFile {
89
path: string;
@@ -15,7 +16,7 @@ interface PackResult {
1516
}
1617

1718
function runPackDryRun(cwd: string) {
18-
const proc = Bun.spawnSync(["npm", "pack", "--dry-run", "--json"], {
19+
const proc = Bun.spawnSync([npmCommand, "pack", "--dry-run", "--json"], {
1920
cwd,
2021
stdin: "ignore",
2122
stdout: "pipe",

0 commit comments

Comments
 (0)