feat(HAI-076): add cross-compilation support for CLI executable builds
- Extend build.ts with cross-compilation targets for multiple platforms/architectures - Add build:exe:all npm script to package.json for building all targets - Add cross-compilation tests for build-exe-cross - Add cross-compilation documentation section to README - Clean up unused agent log, store, and route code across dashboard/core/engine packages
This commit is contained in:
134
packages/cli/src/__tests__/build-exe-cross.test.ts
Normal file
134
packages/cli/src/__tests__/build-exe-cross.test.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { describe, it, expect, beforeAll } from "vitest";
|
||||
import { execSync, spawnSync } from "node:child_process";
|
||||
import { existsSync, statSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const cliRoot = join(import.meta.dirname!, "..", "..");
|
||||
const distDir = join(cliRoot, "dist");
|
||||
const clientDir = join(distDir, "client");
|
||||
|
||||
const SUPPORTED_TARGETS = [
|
||||
"bun-linux-x64",
|
||||
"bun-linux-arm64",
|
||||
"bun-darwin-x64",
|
||||
"bun-darwin-arm64",
|
||||
"bun-windows-x64",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Map target → expected binary filename (mirrors build.ts logic).
|
||||
*/
|
||||
function expectedBinaryName(target: string): string {
|
||||
const suffix = target.replace(/^bun-/, "");
|
||||
const isWindows = target.includes("windows");
|
||||
return `hai-${suffix}${isWindows ? ".exe" : ""}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the native target for the current host so we can run it.
|
||||
*/
|
||||
function nativeTarget(): string | null {
|
||||
const platform = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : null;
|
||||
const arch = process.arch === "x64" ? "x64" : process.arch === "arm64" ? "arm64" : null;
|
||||
if (!platform || !arch) return null;
|
||||
return `bun-${platform}-${arch}`;
|
||||
}
|
||||
|
||||
describe("build-exe-cross: single target", () => {
|
||||
beforeAll(() => {
|
||||
// Build for linux-x64 specifically
|
||||
execSync("bun run build.ts --target bun-linux-x64", {
|
||||
cwd: cliRoot,
|
||||
stdio: "pipe",
|
||||
timeout: 120_000,
|
||||
});
|
||||
}, 180_000);
|
||||
|
||||
it("produces dist/hai-linux-x64", () => {
|
||||
const bin = join(distDir, "hai-linux-x64");
|
||||
expect(existsSync(bin)).toBe(true);
|
||||
expect(statSync(bin).size).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("copies client assets alongside the binary", () => {
|
||||
expect(existsSync(join(clientDir, "index.html"))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("build-exe-cross: windows target has .exe extension", () => {
|
||||
beforeAll(() => {
|
||||
execSync("bun run build.ts --target bun-windows-x64", {
|
||||
cwd: cliRoot,
|
||||
stdio: "pipe",
|
||||
timeout: 120_000,
|
||||
});
|
||||
}, 180_000);
|
||||
|
||||
it("produces dist/hai-windows-x64.exe", () => {
|
||||
const bin = join(distDir, "hai-windows-x64.exe");
|
||||
expect(existsSync(bin)).toBe(true);
|
||||
expect(statSync(bin).size).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("build-exe-cross: --all builds all platforms", () => {
|
||||
beforeAll(() => {
|
||||
execSync("bun run build.ts --all", {
|
||||
cwd: cliRoot,
|
||||
stdio: "pipe",
|
||||
timeout: 300_000,
|
||||
});
|
||||
}, 360_000);
|
||||
|
||||
for (const target of SUPPORTED_TARGETS) {
|
||||
const name = expectedBinaryName(target);
|
||||
it(`produces dist/${name}`, () => {
|
||||
const bin = join(distDir, name);
|
||||
expect(existsSync(bin)).toBe(true);
|
||||
expect(statSync(bin).size).toBeGreaterThan(0);
|
||||
});
|
||||
}
|
||||
|
||||
it("copies client assets", () => {
|
||||
expect(existsSync(join(clientDir, "index.html"))).toBe(true);
|
||||
});
|
||||
|
||||
it("native-platform binary runs --help", () => {
|
||||
const target = nativeTarget();
|
||||
if (!target) {
|
||||
// Skip on unsupported host (e.g. Windows in CI)
|
||||
return;
|
||||
}
|
||||
const name = expectedBinaryName(target);
|
||||
const bin = join(distDir, name);
|
||||
if (!existsSync(bin)) return;
|
||||
|
||||
const result = spawnSync(bin, ["--help"], {
|
||||
encoding: "utf-8",
|
||||
timeout: 15_000,
|
||||
});
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain("hai");
|
||||
});
|
||||
});
|
||||
|
||||
describe("build-exe-cross: default (no args) backward compatibility", () => {
|
||||
beforeAll(() => {
|
||||
execSync("bun run build.ts", {
|
||||
cwd: cliRoot,
|
||||
stdio: "pipe",
|
||||
timeout: 120_000,
|
||||
});
|
||||
}, 180_000);
|
||||
|
||||
it("produces dist/hai (no platform suffix)", () => {
|
||||
const defaultName = process.platform === "win32" ? "hai.exe" : "hai";
|
||||
const bin = join(distDir, defaultName);
|
||||
expect(existsSync(bin)).toBe(true);
|
||||
expect(statSync(bin).size).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("copies client assets", () => {
|
||||
expect(existsSync(join(clientDir, "index.html"))).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user