Skip to content

Commit e3017d0

Browse files
authored
feat: prototype prebuilt npm binary packaging (#12)
1 parent e9a68a1 commit e3017d0

10 files changed

Lines changed: 815 additions & 20 deletions

.github/workflows/ci.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,41 @@ jobs:
9696
fi
9797
hunk --help | grep 'Usage: hunk'
9898
99+
prebuilt-npm:
100+
name: Verify prebuilt npm package (${{ matrix.os }})
101+
runs-on: ${{ matrix.os }}
102+
strategy:
103+
fail-fast: false
104+
matrix:
105+
os:
106+
- ubuntu-latest
107+
- macos-latest
108+
steps:
109+
- name: Check out repository
110+
uses: actions/checkout@v4
111+
112+
- name: Set up Bun
113+
uses: oven-sh/setup-bun@v2
114+
with:
115+
bun-version: 1.3.10
116+
117+
- name: Set up Node
118+
uses: actions/setup-node@v4
119+
with:
120+
node-version: 22
121+
122+
- name: Install dependencies
123+
run: bun install --frozen-lockfile
124+
125+
- name: Stage prebuilt npm packages
126+
run: bun run build:prebuilt:npm
127+
128+
- name: Verify staged prebuilt packs
129+
run: bun run check:prebuilt-pack
130+
131+
- name: Smoke test prebuilt global install
132+
run: bun run smoke:prebuilt-install
133+
99134
build-bin:
100135
name: Build compiled binary
101136
runs-on: ubuntu-latest

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,14 @@ bun run build:npm
242242
bun run check:pack
243243
```
244244

245+
Stage the prototype prebuilt npm packages for the current host and smoke test the install path without Bun on `PATH`:
246+
247+
```bash
248+
bun run build:prebuilt:npm
249+
bun run check:prebuilt-pack
250+
bun run smoke:prebuilt-install
251+
```
252+
245253
## License
246254

247255
[MIT](LICENSE)

bin/hunk.cjs

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,103 @@
11
#!/usr/bin/env node
22

3-
const { spawnSync } = require("node:child_process");
3+
const childProcess = require("node:child_process");
4+
const fs = require("node:fs");
5+
const os = require("node:os");
46
const path = require("node:path");
57

6-
const entrypoint = path.join(__dirname, "..", "dist", "npm", "main.js");
8+
function run(target, args) {
9+
const result = childProcess.spawnSync(target, args, {
10+
stdio: "inherit",
11+
env: process.env,
12+
});
713

8-
let bunBinary;
14+
if (result.error) {
15+
console.error(result.error.message);
16+
process.exit(1);
17+
}
18+
19+
process.exit(typeof result.status === "number" ? result.status : 1);
20+
}
21+
22+
function hostCandidates() {
23+
const platformMap = {
24+
darwin: "darwin",
25+
linux: "linux",
26+
win32: "windows",
27+
};
28+
const archMap = {
29+
x64: "x64",
30+
arm64: "arm64",
31+
};
32+
33+
const platform = platformMap[os.platform()] || os.platform();
34+
const arch = archMap[os.arch()] || os.arch();
35+
const binary = platform === "windows" ? "hunk.exe" : "hunk";
936

10-
try {
11-
bunBinary = require.resolve("bun/bin/bun.exe");
12-
} catch (error) {
13-
console.error(
14-
"Failed to resolve the bundled Bun runtime. Try reinstalling hunkdiff.",
15-
);
16-
if (error && error.message) {
17-
console.error(error.message);
37+
if (platform === "darwin") {
38+
if (arch === "arm64") return [{ packageName: "hunkdiff-darwin-arm64", binary }];
39+
if (arch === "x64") return [{ packageName: "hunkdiff-darwin-x64", binary }];
1840
}
19-
process.exit(1);
41+
42+
if (platform === "linux") {
43+
if (arch === "arm64") return [{ packageName: "hunkdiff-linux-arm64", binary }];
44+
if (arch === "x64") return [{ packageName: "hunkdiff-linux-x64", binary }];
45+
}
46+
47+
return [];
2048
}
2149

22-
const result = spawnSync(bunBinary, [entrypoint, ...process.argv.slice(2)], {
23-
stdio: "inherit",
24-
env: process.env,
25-
});
50+
function findInstalledBinary(startDir) {
51+
let current = startDir;
52+
53+
for (;;) {
54+
const modulesDir = path.join(current, "node_modules");
55+
if (fs.existsSync(modulesDir)) {
56+
for (const candidate of hostCandidates()) {
57+
const resolved = path.join(modulesDir, candidate.packageName, "bin", candidate.binary);
58+
if (fs.existsSync(resolved)) {
59+
return resolved;
60+
}
61+
}
62+
}
63+
64+
const parent = path.dirname(current);
65+
if (parent === current) {
66+
return null;
67+
}
68+
current = parent;
69+
}
70+
}
71+
72+
function bundledBunRuntime() {
73+
try {
74+
return require.resolve("bun/bin/bun.exe");
75+
} catch {
76+
return null;
77+
}
78+
}
79+
80+
const overrideBinary = process.env.HUNK_BIN_PATH;
81+
if (overrideBinary) {
82+
run(overrideBinary, process.argv.slice(2));
83+
}
84+
85+
const scriptDir = path.dirname(fs.realpathSync(__filename));
86+
const prebuiltBinary = findInstalledBinary(scriptDir);
87+
if (prebuiltBinary) {
88+
run(prebuiltBinary, process.argv.slice(2));
89+
}
2690

27-
if (result.error) {
28-
console.error(result.error.message);
29-
process.exit(1);
91+
const bunBinary = bundledBunRuntime();
92+
if (bunBinary) {
93+
const entrypoint = path.join(__dirname, "..", "dist", "npm", "main.js");
94+
run(bunBinary, [entrypoint, ...process.argv.slice(2)]);
3095
}
3196

32-
process.exit(typeof result.status === "number" ? result.status : 1);
97+
const printablePackages = hostCandidates().map((candidate) => `"${candidate.packageName}"`).join(" or ");
98+
console.error(
99+
printablePackages.length > 0
100+
? `Failed to locate a matching prebuilt Hunk binary. Try reinstalling hunkdiff or manually installing ${printablePackages}.`
101+
: `Unsupported platform for prebuilt Hunk binaries: ${os.platform()} ${os.arch()}`,
102+
);
103+
process.exit(1);

0 commit comments

Comments
 (0)