@crewhaus/single-binary-cli 0.1.2 → 0.1.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewhaus/single-binary-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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",
@@ -10,10 +10,11 @@
10
10
  },
11
11
  "scripts": {
12
12
  "test": "bun test src",
13
- "build:binary": "bun src/cli.ts"
13
+ "build:binary": "bun src/cli.ts",
14
+ "manifests": "bun src/manifests.ts"
14
15
  },
15
16
  "dependencies": {
16
- "@crewhaus/errors": "0.1.2"
17
+ "@crewhaus/errors": "0.1.3"
17
18
  },
18
19
  "license": "Apache-2.0",
19
20
  "author": {
package/src/index.test.ts CHANGED
@@ -100,9 +100,25 @@ describe("buildBinary() (T2 dry-run)", () => {
100
100
  expect(captured).toContain("bun-linux-x64");
101
101
  expect(captured).toContain("--outfile");
102
102
  expect(captured).toContain("/tmp/dist/crewhaus-linux-x64-1.2.3");
103
+ expect(captured).toContain("--define");
104
+ expect(captured).toContain('CREWHAUS_EMBEDDED_VERSION="1.2.3"');
103
105
  expect(result.outPath).toBe("/tmp/dist/crewhaus-linux-x64-1.2.3");
104
106
  });
105
107
 
108
+ test("no version → no --define (the CLI falls back to its package.json read)", async () => {
109
+ let captured: readonly string[] = [];
110
+ const runner: BuildBinaryRunner = async (argv) => {
111
+ captured = argv;
112
+ return { exitCode: 0, stdout: "", stderr: "" };
113
+ };
114
+ await buildBinary({
115
+ target: { platform: "linux", arch: "x64" },
116
+ outDir: "/tmp/dist",
117
+ runner,
118
+ });
119
+ expect(captured).not.toContain("--define");
120
+ });
121
+
106
122
  test("rejects unsupported target windows-arm64", async () => {
107
123
  await expect(
108
124
  buildBinary({
@@ -148,6 +164,7 @@ describe("manifest rendering (T1)", () => {
148
164
  expect(formula).toContain('version "1.0.0"');
149
165
  expect(formula).toContain("on_macos");
150
166
  expect(formula).toContain("on_linux");
167
+ expect(formula).toContain('license "Apache-2.0"');
151
168
  expect(formula).toContain(sha256["macos-arm64"]);
152
169
  expect(formula).toContain(sha256["macos-x64"]);
153
170
  expect(formula).toContain(sha256["linux-arm64"]);
@@ -168,9 +185,11 @@ describe("manifest rendering (T1)", () => {
168
185
  test("Scoop manifest exposes 64bit url + hash", () => {
169
186
  const scoop = renderScoopManifest(inputs) as {
170
187
  version: string;
188
+ license: string;
171
189
  architecture: { "64bit": { url: string; hash: string } };
172
190
  };
173
191
  expect(scoop.version).toBe("1.0.0");
192
+ expect(scoop.license).toBe("Apache-2.0");
174
193
  expect(scoop.architecture["64bit"].hash).toBe(sha256["windows-x64"]);
175
194
  expect(scoop.architecture["64bit"].url).toContain("crewhaus-windows-x64-1.0.0.exe");
176
195
  });
@@ -179,6 +198,7 @@ describe("manifest rendering (T1)", () => {
179
198
  const winget = renderWingetManifest(inputs);
180
199
  expect(winget).toContain("PackageIdentifier: CrewHaus.CLI");
181
200
  expect(winget).toContain("PackageVersion: 1.0.0");
201
+ expect(winget).toContain("License: Apache-2.0");
182
202
  expect(winget).toContain(sha256["windows-x64"].toUpperCase());
183
203
  });
184
204
 
package/src/index.ts CHANGED
@@ -105,6 +105,10 @@ export async function buildBinary(opts: BuildBinaryOptions): Promise<BuildBinary
105
105
  "--compile",
106
106
  "--target",
107
107
  bunCompileTarget(t),
108
+ // Embed the version: inside a compiled binary import.meta.url points
109
+ // into the virtual /$bunfs tree, so the CLI's runtime ../package.json
110
+ // read cannot work — `crewhaus --version` uses this constant instead.
111
+ ...(version ? ["--define", `CREWHAUS_EMBEDDED_VERSION=${JSON.stringify(version)}`] : []),
108
112
  CLI_ENTRYPOINT_REL,
109
113
  "--outfile",
110
114
  outPath,
@@ -174,7 +178,7 @@ export function renderHomebrewFormula(inputs: ManifestInputs): string {
174
178
  desc "Modular meta-harness — compile a single spec into multiple agent runtimes"
175
179
  homepage "${homepage}"
176
180
  version "${version}"
177
- license "MIT"
181
+ license "Apache-2.0"
178
182
 
179
183
  on_macos do
180
184
  on_arm do
@@ -233,7 +237,7 @@ export function renderScoopManifest(inputs: ManifestInputs): unknown {
233
237
  version,
234
238
  description: "Modular meta-harness — compile a single spec into multiple agent runtimes",
235
239
  homepage,
236
- license: "MIT",
240
+ license: "Apache-2.0",
237
241
  architecture: {
238
242
  "64bit": {
239
243
  url: `${downloadBaseUrl}/crewhaus-windows-x64-${version}.exe`,
@@ -254,7 +258,7 @@ Publisher: CrewHaus
254
258
  Author: CrewHaus
255
259
  PackageName: crewhaus
256
260
  PackageUrl: ${homepage}
257
- License: MIT
261
+ License: Apache-2.0
258
262
  ShortDescription: Modular meta-harness — compile a single spec into multiple agent runtimes
259
263
  Description: |
260
264
  CrewHaus compiles a single high-level harness spec into multiple runtime targets.
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Generate the Homebrew / apt / Scoop / Winget package manifests from the
4
+ * binaries already built into `dist/` by `build:binary`. Run AFTER the build.
5
+ * Used by the release workflow (.github/workflows/release.yml) so the manifest
6
+ * generation is a single, unit-tested command rather than inline YAML.
7
+ *
8
+ * bun src/manifests.ts --version 1.2.3 \
9
+ * --download-base-url https://github.com/crewhaus/factory/releases/download/v1.2.3 \
10
+ * --dist <repo>/dist --out <repo>/packaging
11
+ *
12
+ * Prints a JSON summary ({version, downloadBaseUrl, sha256, written}) to stdout
13
+ * so CI can capture the sha map and the written paths.
14
+ */
15
+ import { join, resolve } from "node:path";
16
+ import {
17
+ BUILD_MATRIX,
18
+ type ShaByTarget,
19
+ binaryName,
20
+ formatTarget,
21
+ packagingDir,
22
+ sha256OfFile,
23
+ writeAllManifests,
24
+ } from "./index";
25
+
26
+ function flag(name: string, argv: string[]): string | undefined {
27
+ const i = argv.indexOf(`--${name}`);
28
+ return i >= 0 ? argv[i + 1] : undefined;
29
+ }
30
+
31
+ async function main() {
32
+ const argv = process.argv.slice(2);
33
+ const version = flag("version", argv);
34
+ if (!version) {
35
+ console.error("✗ --version is required (e.g. --version 1.2.3)");
36
+ process.exit(1);
37
+ }
38
+ const homepage = flag("homepage", argv) ?? "https://github.com/crewhaus/factory";
39
+ const downloadBaseUrl =
40
+ flag("download-base-url", argv) ??
41
+ `https://github.com/crewhaus/factory/releases/download/v${version}`;
42
+ const distDir = resolve(flag("dist", argv) ?? join(import.meta.dir, "..", "..", "..", "dist"));
43
+ const outArg = flag("out", argv);
44
+ const outDir = outArg ? resolve(outArg) : packagingDir();
45
+
46
+ // sha256 every built binary. A missing file is a hard error — the manifests
47
+ // must never reference an asset that was not actually built and uploaded.
48
+ const sha256: Record<string, string> = {};
49
+ for (const t of BUILD_MATRIX) {
50
+ const file = join(distDir, binaryName(t, version));
51
+ sha256[formatTarget(t)] = await sha256OfFile(file);
52
+ }
53
+
54
+ const inputs = { version, homepage, downloadBaseUrl, sha256: sha256 as ShaByTarget };
55
+ const written = writeAllManifests(inputs, outDir);
56
+
57
+ console.log(JSON.stringify({ version, downloadBaseUrl, sha256, written }, null, 2));
58
+ }
59
+
60
+ main().catch((err) => {
61
+ console.error(`✗ manifest generation failed: ${(err as Error).message}`);
62
+ process.exit(1);
63
+ });
@@ -15,8 +15,14 @@
15
15
  */
16
16
  import { afterAll, afterEach, describe, expect, mock, test } from "bun:test";
17
17
  import { EventEmitter } from "node:events";
18
+ import { resolve } from "node:path";
18
19
  import { SingleBinaryError, buildBinary } from "./index";
19
20
 
21
+ // Mirror REPO_ROOT from ./index — two levels above the package root,
22
+ // whatever the checkout or worktree happens to be named. (`tsc -b` also
23
+ // runs this file from dist/, hence the src|dist strip.)
24
+ const REPO_ROOT = resolve(import.meta.dir.replace(/[/\\](src|dist)$/, ""), "..", "..");
25
+
20
26
  // Captured before any mock.module call so afterAll can reinstall the real module.
21
27
  const realChildProcess = require("node:child_process") as typeof import("node:child_process");
22
28
 
@@ -76,7 +82,7 @@ describe("defaultRunner (real child_process path, mocked spawn)", () => {
76
82
  expect(captured.rest).toContain("--compile");
77
83
  expect(captured.rest).toContain("bun-linux-x64");
78
84
  // cwd is REPO_ROOT (two levels above the package).
79
- expect(captured.cwd).toMatch(/factory$/);
85
+ expect(captured.cwd).toBe(REPO_ROOT);
80
86
  expect(result.outPath).toBe("/tmp/sbc-runner-dist/crewhaus-linux-x64-9.9.9");
81
87
  expect(result.buildArgv[0]).toBe("bun");
82
88
  });