Skip to content

Commit 4b8a4cf

Browse files
authored
bundle-binary (#7)
* bundle-binary * knip
1 parent ad067cf commit 4b8a4cf

File tree

6 files changed

+109
-4
lines changed

6 files changed

+109
-4
lines changed

.github/workflows/unit-tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@ jobs:
2828

2929
- name: Run unit tests
3030
run: pnpm test:unit
31+
32+
- name: Run unit tests
33+
run: pnpm test:dist

knip.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"$schema": "https://unpkg.com/knip@5/schema.json",
3+
"entry": ["src/cli/bash-env.ts"],
34
"project": ["src/**/*.ts"],
45
"ignore": ["src/**/*.test.ts", "src/spec-tests/**"],
56
"ignoreBinaries": ["tsx"],

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,20 @@
3232
"README.md"
3333
],
3434
"bin": {
35-
"bash-env": "./dist/cli/bash-env.js",
35+
"bash-env": "./dist/bin/bash-env.js",
3636
"bash-env-shell": "./dist/cli/shell.js"
3737
},
3838
"scripts": {
39-
"build": "tsc",
40-
"validate": "pnpm build && pnpm typecheck && pnpm lint && pnpm knip && pnpm test:run",
39+
"build": "tsc && pnpm build:cli",
40+
"build:cli": "esbuild dist/cli/bash-env.js --bundle --splitting --platform=node --format=esm --minify --outdir=dist/bin --entry-names=[name] --chunk-names=chunks/[name]-[hash] --banner:js='#!/usr/bin/env node'",
41+
"validate": "pnpm build && pnpm typecheck && pnpm lint && pnpm knip && pnpm test:run && pnpm test:dist",
4142
"typecheck": "tsc --noEmit",
4243
"lint": "biome check .",
4344
"lint:fix": "biome check --write .",
4445
"knip": "knip",
4546
"test": "vitest",
4647
"test:run": "vitest run",
48+
"test:dist": "vitest run src/cli/bash-env.bundle.test.ts",
4749
"test:unit": "vitest run --config vitest.unit.config.ts",
4850
"test:comparison": "vitest run --config vitest.comparison.config.ts",
4951
"shell": "npx tsx src/cli/shell.ts",
@@ -58,6 +60,7 @@
5860
"@types/sprintf-js": "^1.1.4",
5961
"@types/turndown": "^5.0.6",
6062
"ai": "^6.0.3",
63+
"esbuild": "^0.27.2",
6164
"knip": "^5.41.1",
6265
"typescript": "^5.9.3",
6366
"vitest": "^4.0.16",

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cli/bash-env.bundle.test.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { execFile } from "node:child_process";
2+
import { resolve } from "node:path";
3+
import { promisify } from "node:util";
4+
import { describe, expect, it } from "vitest";
5+
6+
const execFileAsync = promisify(execFile);
7+
const binPath = resolve(__dirname, "../../dist/bin/bash-env.js");
8+
9+
async function runBin(
10+
args: string[],
11+
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
12+
try {
13+
const { stdout, stderr } = await execFileAsync("node", [binPath, ...args]);
14+
return { stdout, stderr, exitCode: 0 };
15+
} catch (error: unknown) {
16+
const e = error as { stdout?: string; stderr?: string; code?: number };
17+
return {
18+
stdout: e.stdout ?? "",
19+
stderr: e.stderr ?? "",
20+
exitCode: e.code ?? 1,
21+
};
22+
}
23+
}
24+
25+
describe("bash-env bundled binary", () => {
26+
it("should show version", async () => {
27+
const result = await runBin(["--version"]);
28+
expect(result.stdout).toContain("bash-env");
29+
expect(result.exitCode).toBe(0);
30+
});
31+
32+
it("should show help", async () => {
33+
const result = await runBin(["--help"]);
34+
expect(result.stdout).toContain("Usage:");
35+
expect(result.exitCode).toBe(0);
36+
});
37+
38+
it("should execute echo command", async () => {
39+
const result = await runBin(["-c", "echo hello world"]);
40+
expect(result.stdout).toBe("hello world\n");
41+
expect(result.exitCode).toBe(0);
42+
});
43+
44+
it("should execute pipes", async () => {
45+
const result = await runBin(["-c", 'echo "line1\nline2\nline3" | wc -l']);
46+
expect(result.stdout.trim()).toBe("3");
47+
expect(result.exitCode).toBe(0);
48+
});
49+
50+
it("should handle file operations with --allow-write", async () => {
51+
const result = await runBin([
52+
"-c",
53+
'echo "test" > /tmp/test.txt && cat /tmp/test.txt',
54+
"--allow-write",
55+
]);
56+
expect(result.stdout).toBe("test\n");
57+
expect(result.exitCode).toBe(0);
58+
});
59+
60+
it("should support JSON output", async () => {
61+
const result = await runBin(["-c", "echo hello", "--json"]);
62+
const json = JSON.parse(result.stdout);
63+
expect(json.stdout).toBe("hello\n");
64+
expect(json.stderr).toBe("");
65+
expect(json.exitCode).toBe(0);
66+
});
67+
68+
it("should lazy-load commands (grep)", async () => {
69+
const result = await runBin([
70+
"-c",
71+
'echo -e "foo\\nbar\\nbaz" | grep ba',
72+
"--allow-write",
73+
]);
74+
expect(result.stdout).toContain("bar");
75+
expect(result.stdout).toContain("baz");
76+
expect(result.exitCode).toBe(0);
77+
});
78+
79+
it("should lazy-load commands (sed)", async () => {
80+
const result = await runBin(["-c", "echo hello | sed 's/hello/world/'"]);
81+
expect(result.stdout).toBe("world\n");
82+
expect(result.exitCode).toBe(0);
83+
});
84+
85+
it("should lazy-load commands (awk)", async () => {
86+
const result = await runBin(["-c", "echo 'a b c' | awk '{print $2}'"]);
87+
expect(result.stdout).toBe("b\n");
88+
expect(result.exitCode).toBe(0);
89+
});
90+
91+
it("should handle errexit mode", async () => {
92+
const result = await runBin(["-e", "-c", "false; echo should not print"]);
93+
expect(result.stdout).not.toContain("should not print");
94+
expect(result.exitCode).toBe(1);
95+
});
96+
});

src/cli/bash-env.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#!/usr/bin/env node
21
/**
32
* bash-env CLI - A secure alternative to bash for AI agents
43
*

0 commit comments

Comments
 (0)