Skip to content

Commit 0203393

Browse files
authored
Prepare Hunk for npm packaging and install verification (#8)
* build: prepare npm packaging and verification * fix: unblock standalone npm package verification
1 parent 95bd096 commit 0203393

4 files changed

Lines changed: 167 additions & 5 deletions

File tree

.github/workflows/ci.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,41 @@ jobs:
5555
- name: TTY smoke tests
5656
run: bun run test:tty-smoke
5757

58+
pack-npm:
59+
name: Verify npm package
60+
runs-on: ubuntu-latest
61+
steps:
62+
- name: Check out repository
63+
uses: actions/checkout@v4
64+
65+
- name: Set up Bun
66+
uses: oven-sh/setup-bun@v2
67+
with:
68+
bun-version: 1.3.10
69+
70+
- name: Set up Node
71+
uses: actions/setup-node@v4
72+
with:
73+
node-version: 22
74+
75+
- name: Install dependencies
76+
run: bun install --frozen-lockfile
77+
78+
- name: Build npm runtime bundle
79+
run: bun run build:npm
80+
81+
- name: Verify npm pack output
82+
run: bun run check:pack
83+
84+
- name: Simulate global npm install
85+
run: |
86+
pkg_dir="$(mktemp -d)"
87+
install_dir="$(mktemp -d)"
88+
npm pack --pack-destination "$pkg_dir" >/dev/null
89+
pkg="$(find "$pkg_dir" -maxdepth 1 -name 'hunkdiff-*.tgz' | head -n1)"
90+
npm install -g --prefix "$install_dir" "$pkg"
91+
PATH="$install_dir/bin:$PATH" hunk --help | grep 'Usage: hunk'
92+
5893
build-bin:
5994
name: Build compiled binary
6095
runs-on: ubuntu-latest

package.json

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,56 @@
11
{
2-
"name": "hunk",
2+
"name": "hunkdiff",
33
"version": "0.1.0",
4-
"description": "Desktop-inspired terminal diff viewer for agent-authored changesets.",
5-
"module": "src/main.tsx",
4+
"description": "Desktop-inspired terminal diff viewer for understanding agent-authored changesets.",
65
"type": "module",
7-
"private": true,
6+
"packageManager": "bun@1.3.10",
87
"bin": {
9-
"hunk": "./src/main.tsx"
8+
"hunk": "dist/npm/main.js"
109
},
10+
"files": [
11+
"dist/npm",
12+
"README.md",
13+
"LICENSE",
14+
"CONTRIBUTING.md",
15+
"SECURITY.md"
16+
],
1117
"scripts": {
1218
"start": "bun run src/main.tsx",
1319
"dev": "bun --watch src/main.tsx",
20+
"build:npm": "bash ./scripts/build-npm.sh",
1421
"build:bin": "bash ./scripts/build-bin.sh",
1522
"install:bin": "bash ./scripts/install-bin.sh",
1623
"typecheck": "tsc --noEmit",
1724
"test": "bun test",
1825
"test:tty-smoke": "bun test test/tty-render-smoke.test.ts",
26+
"check:pack": "bun run ./scripts/check-pack.ts",
27+
"prepack": "bun run build:npm",
1928
"bench:bootstrap-load": "bun run test/bootstrap-load-benchmark.ts",
2029
"bench:highlight-prefetch": "bun run test/adjacent-highlight-prefetch-benchmark.ts",
2130
"bench:large-stream": "bun run test/large-stream-windowing-benchmark.ts"
2231
},
32+
"keywords": [
33+
"diff",
34+
"git",
35+
"tui",
36+
"terminal",
37+
"code-review",
38+
"ai"
39+
],
40+
"repository": {
41+
"type": "git",
42+
"url": "git+https://github.com/modem-dev/hunk.git"
43+
},
44+
"homepage": "https://github.com/modem-dev/hunk#readme",
45+
"bugs": {
46+
"url": "https://github.com/modem-dev/hunk/issues"
47+
},
48+
"engines": {
49+
"bun": ">=1.3.10"
50+
},
51+
"publishConfig": {
52+
"access": "public"
53+
},
2354
"devDependencies": {
2455
"@types/bun": "latest",
2556
"@types/react": "^19.2.14",

scripts/build-npm.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env bash
2+
set -Eeuo pipefail
3+
4+
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5+
outdir="${repo_root}/dist/npm"
6+
7+
rm -rf "${outdir}"
8+
mkdir -p "${outdir}"
9+
10+
BUN_TMPDIR="${repo_root}/.bun-tmp" \
11+
BUN_INSTALL="${repo_root}/.bun-install" \
12+
bun build "${repo_root}/src/main.tsx" \
13+
--target bun \
14+
--format esm \
15+
--outdir "${outdir}" \
16+
--entry-naming main.js
17+
18+
chmod 0755 "${outdir}/main.js"
19+
20+
printf 'Built %s\n' "${outdir}/main.js"

scripts/check-pack.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env bun
2+
3+
import { existsSync } from "node:fs";
4+
5+
interface PackedFile {
6+
path: string;
7+
size: number;
8+
}
9+
10+
interface PackResult {
11+
name: string;
12+
version: string;
13+
filename: string;
14+
entryCount: number;
15+
files: PackedFile[];
16+
}
17+
18+
const proc = Bun.spawnSync(["npm", "pack", "--dry-run", "--json"], {
19+
cwd: process.cwd(),
20+
stdin: "ignore",
21+
stdout: "pipe",
22+
stderr: "pipe",
23+
env: process.env,
24+
});
25+
26+
const stdout = Buffer.from(proc.stdout).toString("utf8").trim();
27+
const stderr = Buffer.from(proc.stderr).toString("utf8").trim();
28+
29+
if (proc.exitCode !== 0) {
30+
throw new Error(stderr || stdout || "npm pack --dry-run failed");
31+
}
32+
33+
const jsonMatch = stdout.match(/(\[\s*\{[\s\S]*\}\s*\])\s*$/);
34+
const jsonText = jsonMatch?.[1];
35+
36+
if (!jsonText) {
37+
throw new Error(`Could not find npm pack JSON output. Full stdout:\n${stdout}`);
38+
}
39+
40+
const parsed = JSON.parse(jsonText) as PackResult[];
41+
const pack = parsed[0];
42+
43+
if (!pack) {
44+
throw new Error("npm pack --dry-run returned no pack result.");
45+
}
46+
47+
const publishedPaths = new Set(pack.files.map((file) => file.path));
48+
const requiredPaths = ["dist/npm/main.js", "README.md", "LICENSE", "package.json"];
49+
const optionalPaths = ["CONTRIBUTING.md", "SECURITY.md"];
50+
51+
for (const path of requiredPaths) {
52+
if (!publishedPaths.has(path)) {
53+
throw new Error(`Expected npm package to include ${path}.`);
54+
}
55+
}
56+
57+
for (const path of optionalPaths) {
58+
if (existsSync(path) && !publishedPaths.has(path)) {
59+
throw new Error(`Expected npm package to include ${path} when it exists in the repo.`);
60+
}
61+
}
62+
63+
const forbiddenPrefixes = [".github/", "src/", "test/", "scripts/", "tmp/"];
64+
const forbiddenPaths = ["AGENTS.md", "autoresearch.checks.sh", "autoresearch.sh", "bun.lock"];
65+
66+
for (const file of pack.files) {
67+
if (forbiddenPrefixes.some((prefix) => file.path.startsWith(prefix)) || forbiddenPaths.includes(file.path)) {
68+
throw new Error(`Unexpected file in npm package: ${file.path}`);
69+
}
70+
}
71+
72+
if (pack.name !== "hunkdiff") {
73+
throw new Error(`Expected npm package name to be hunkdiff, got ${pack.name}.`);
74+
}
75+
76+
console.log(`Verified npm pack output for ${pack.name}@${pack.version} (${pack.entryCount} files).`);

0 commit comments

Comments
 (0)