@crewhaus/single-binary-cli 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -11
- package/src/index.test.ts +6 -0
- package/src/index.ts +1 -1
- package/src/runner.test.ts +136 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewhaus/single-binary-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "bun build --compile wrapper producing self-contained crewhaus binaries for linux/macos/windows × x64/arm64; auto-generated Homebrew/apt/Scoop/Winget manifests (Section 32)",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"build:binary": "bun src/cli.ts"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@crewhaus/errors": "0.
|
|
16
|
+
"@crewhaus/errors": "0.1.2"
|
|
17
17
|
},
|
|
18
18
|
"license": "Apache-2.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Max Meier",
|
|
21
|
-
"email": "max@
|
|
22
|
-
"url": "https://
|
|
21
|
+
"email": "max@crewhaus.ai",
|
|
22
|
+
"url": "https://crewhaus.ai"
|
|
23
23
|
},
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|
|
@@ -31,12 +31,7 @@
|
|
|
31
31
|
"url": "https://github.com/crewhaus/factory/issues"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
|
-
"access": "
|
|
34
|
+
"access": "public"
|
|
35
35
|
},
|
|
36
|
-
"files": [
|
|
37
|
-
"src",
|
|
38
|
-
"README.md",
|
|
39
|
-
"LICENSE",
|
|
40
|
-
"NOTICE"
|
|
41
|
-
]
|
|
36
|
+
"files": ["src", "README.md", "LICENSE", "NOTICE"]
|
|
42
37
|
}
|
package/src/index.test.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
bunCompileTarget,
|
|
16
16
|
formatTarget,
|
|
17
17
|
isBuildTarget,
|
|
18
|
+
packagingDir,
|
|
18
19
|
renderDebianControl,
|
|
19
20
|
renderHomebrewFormula,
|
|
20
21
|
renderScoopManifest,
|
|
@@ -200,6 +201,11 @@ describe("manifest rendering (T1)", () => {
|
|
|
200
201
|
'"hash": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"',
|
|
201
202
|
);
|
|
202
203
|
});
|
|
204
|
+
|
|
205
|
+
test("packagingDir() points at the package's packaging/ directory (default root)", () => {
|
|
206
|
+
// The default on-disk root used by writeAllManifests when no dir is given.
|
|
207
|
+
expect(packagingDir()).toMatch(/single-binary-cli[/\\]packaging$/);
|
|
208
|
+
});
|
|
203
209
|
});
|
|
204
210
|
|
|
205
211
|
describe("sha256OfFile", () => {
|
package/src/index.ts
CHANGED
|
@@ -216,7 +216,7 @@ Version: ${version}
|
|
|
216
216
|
Section: utils
|
|
217
217
|
Priority: optional
|
|
218
218
|
Architecture: any
|
|
219
|
-
Maintainer: CrewHaus Maintainers <maintainers@crewhaus.
|
|
219
|
+
Maintainer: CrewHaus Maintainers <maintainers@crewhaus.ai>
|
|
220
220
|
Depends: libc6 (>= 2.31)
|
|
221
221
|
Description: Modular meta-harness — compile a single spec into multiple agent runtimes
|
|
222
222
|
CrewHaus compiles a single high-level harness spec into multiple
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage for the default `child_process`-backed build runner.
|
|
3
|
+
*
|
|
4
|
+
* Every other test injects a fake `runner`, so the real `defaultRunner`
|
|
5
|
+
* (which spawns `bun build --compile`) is never exercised. Here we drive it
|
|
6
|
+
* by calling `buildBinary` WITHOUT a runner and mocking `node:child_process`
|
|
7
|
+
* so `spawn` returns a fake child emitter — no real process, no real timers.
|
|
8
|
+
*
|
|
9
|
+
* The fake child fires its stdout/stderr/close|error listeners on a
|
|
10
|
+
* microtask (queueMicrotask), which is deterministic and needs no fake clock.
|
|
11
|
+
* NOTE: `mock.restore()` does NOT undo `mock.module`, and Bun shares one
|
|
12
|
+
* module registry across all test files (nondeterministic order) — the
|
|
13
|
+
* `afterAll` below reinstalls the real `node:child_process` so the per-test
|
|
14
|
+
* spawn fakes can't leak into the sibling suite.
|
|
15
|
+
*/
|
|
16
|
+
import { afterAll, afterEach, describe, expect, mock, test } from "bun:test";
|
|
17
|
+
import { EventEmitter } from "node:events";
|
|
18
|
+
import { SingleBinaryError, buildBinary } from "./index";
|
|
19
|
+
|
|
20
|
+
// Captured before any mock.module call so afterAll can reinstall the real module.
|
|
21
|
+
const realChildProcess = require("node:child_process") as typeof import("node:child_process");
|
|
22
|
+
|
|
23
|
+
afterAll(() => {
|
|
24
|
+
mock.module("node:child_process", () => realChildProcess);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
type FakeOpts = {
|
|
28
|
+
readonly stdout?: string;
|
|
29
|
+
readonly stderr?: string;
|
|
30
|
+
readonly code?: number | null;
|
|
31
|
+
readonly emitError?: Error;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function fakeChild(opts: FakeOpts): EventEmitter & { stdout: EventEmitter; stderr: EventEmitter } {
|
|
35
|
+
const child = new EventEmitter() as EventEmitter & {
|
|
36
|
+
stdout: EventEmitter;
|
|
37
|
+
stderr: EventEmitter;
|
|
38
|
+
};
|
|
39
|
+
child.stdout = new EventEmitter();
|
|
40
|
+
child.stderr = new EventEmitter();
|
|
41
|
+
// Emit after the caller has attached its listeners (next microtask).
|
|
42
|
+
queueMicrotask(() => {
|
|
43
|
+
if (opts.stdout) child.stdout.emit("data", Buffer.from(opts.stdout, "utf8"));
|
|
44
|
+
if (opts.stderr) child.stderr.emit("data", Buffer.from(opts.stderr, "utf8"));
|
|
45
|
+
if (opts.emitError) {
|
|
46
|
+
child.emit("error", opts.emitError);
|
|
47
|
+
} else {
|
|
48
|
+
// Emit the code verbatim — including an explicit `null` — so the
|
|
49
|
+
// `code ?? 1` branch in defaultRunner is exercised, not masked here.
|
|
50
|
+
child.emit("close", opts.code === undefined ? 0 : opts.code);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return child;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe("defaultRunner (real child_process path, mocked spawn)", () => {
|
|
57
|
+
afterEach(() => mock.restore());
|
|
58
|
+
|
|
59
|
+
test("spawns `bun` with the compile argv and resolves on clean exit", async () => {
|
|
60
|
+
let captured: { head?: string; rest?: string[]; cwd?: string } = {};
|
|
61
|
+
mock.module("node:child_process", () => ({
|
|
62
|
+
spawn: (head: string, rest: string[], optsArg: { cwd: string }) => {
|
|
63
|
+
captured = { head, rest, cwd: optsArg.cwd };
|
|
64
|
+
return fakeChild({ stdout: "compiled ok", code: 0 });
|
|
65
|
+
},
|
|
66
|
+
}));
|
|
67
|
+
|
|
68
|
+
const result = await buildBinary({
|
|
69
|
+
target: { platform: "linux", arch: "x64" },
|
|
70
|
+
version: "9.9.9",
|
|
71
|
+
outDir: "/tmp/sbc-runner-dist",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(captured.head).toBe("bun");
|
|
75
|
+
expect(captured.rest).toContain("build");
|
|
76
|
+
expect(captured.rest).toContain("--compile");
|
|
77
|
+
expect(captured.rest).toContain("bun-linux-x64");
|
|
78
|
+
// cwd is REPO_ROOT (two levels above the package).
|
|
79
|
+
expect(captured.cwd).toMatch(/factory$/);
|
|
80
|
+
expect(result.outPath).toBe("/tmp/sbc-runner-dist/crewhaus-linux-x64-9.9.9");
|
|
81
|
+
expect(result.buildArgv[0]).toBe("bun");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("accumulates multiple stdout/stderr chunks before close", async () => {
|
|
85
|
+
mock.module("node:child_process", () => ({
|
|
86
|
+
spawn: () => {
|
|
87
|
+
const child = new EventEmitter() as EventEmitter & {
|
|
88
|
+
stdout: EventEmitter;
|
|
89
|
+
stderr: EventEmitter;
|
|
90
|
+
};
|
|
91
|
+
child.stdout = new EventEmitter();
|
|
92
|
+
child.stderr = new EventEmitter();
|
|
93
|
+
queueMicrotask(() => {
|
|
94
|
+
child.stdout.emit("data", Buffer.from("part1-", "utf8"));
|
|
95
|
+
child.stdout.emit("data", Buffer.from("part2", "utf8"));
|
|
96
|
+
child.stderr.emit("data", Buffer.from("warn", "utf8"));
|
|
97
|
+
child.emit("close", 0);
|
|
98
|
+
});
|
|
99
|
+
return child;
|
|
100
|
+
},
|
|
101
|
+
}));
|
|
102
|
+
// exit 0 → resolves; the multi-chunk buffers exercise the data handlers.
|
|
103
|
+
const result = await buildBinary({
|
|
104
|
+
target: { platform: "macos", arch: "x64" },
|
|
105
|
+
outDir: "/tmp/sbc-runner-dist",
|
|
106
|
+
});
|
|
107
|
+
expect(result.target.platform).toBe("macos");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("non-zero exit code surfaces stderr in a SingleBinaryError", async () => {
|
|
111
|
+
mock.module("node:child_process", () => ({
|
|
112
|
+
spawn: () => fakeChild({ stderr: "bun: compile failed", code: 7 }),
|
|
113
|
+
}));
|
|
114
|
+
await expect(
|
|
115
|
+
buildBinary({ target: { platform: "macos", arch: "arm64" }, outDir: "/tmp/sbc-runner-dist" }),
|
|
116
|
+
).rejects.toThrow(/exited with 7: bun: compile failed/);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("a null exit code is treated as failure (exitCode 1)", async () => {
|
|
120
|
+
mock.module("node:child_process", () => ({
|
|
121
|
+
spawn: () => fakeChild({ stderr: "killed", code: null }),
|
|
122
|
+
}));
|
|
123
|
+
await expect(
|
|
124
|
+
buildBinary({ target: { platform: "windows", arch: "x64" }, outDir: "/tmp/sbc-runner-dist" }),
|
|
125
|
+
).rejects.toBeInstanceOf(SingleBinaryError);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("a spawn `error` event resolves to exitCode 1 → SingleBinaryError", async () => {
|
|
129
|
+
mock.module("node:child_process", () => ({
|
|
130
|
+
spawn: () => fakeChild({ emitError: new Error("spawn ENOENT: bun not on PATH") }),
|
|
131
|
+
}));
|
|
132
|
+
await expect(
|
|
133
|
+
buildBinary({ target: { platform: "linux", arch: "arm64" }, outDir: "/tmp/sbc-runner-dist" }),
|
|
134
|
+
).rejects.toThrow(/spawn ENOENT: bun not on PATH/);
|
|
135
|
+
});
|
|
136
|
+
});
|