feat(FN-2618): merge fusion/fn-2618

- test(FN-2618): harden process-manager fs/tmpdir mocks
- fix(cli): mark react-devtools-core external in bun compile
- fix(pty): switch to @homebridge/node-pty-prebuilt-multiarch fork
This commit is contained in:
Fusion
2026-04-26 15:33:39 -07:00
committed by gsxdsm
parent 14e41b0ebe
commit dfd82b144d
2 changed files with 54 additions and 17 deletions

View File

@@ -338,6 +338,10 @@ function compileBinary(outFile: string, target: string, isCrossCompile: boolean)
target, target,
"--minify", "--minify",
"--conditions=source", "--conditions=source",
// ink imports react-devtools-core dynamically only when DEV=true; mark
// external so Bun's static bundler doesn't try to resolve it at compile.
"--external",
"react-devtools-core",
], ],
cwd: workspaceRoot, cwd: workspaceRoot,
stdout: "inherit", stdout: "inherit",

View File

@@ -23,11 +23,27 @@ vi.mock("node:child_process", () => ({
execSync: vi.fn(), execSync: vi.fn(),
})); }));
const mocks = vi.hoisted(() => ({
writeFileSync: vi.fn(),
unlinkSync: vi.fn(),
existsSync: vi.fn(),
readFileSync: vi.fn(),
tmpdir: vi.fn(() => "/mock-tmp"),
}));
vi.mock("node:fs", () => ({
writeFileSync: mocks.writeFileSync,
unlinkSync: mocks.unlinkSync,
existsSync: mocks.existsSync,
readFileSync: mocks.readFileSync,
}));
vi.mock("node:os", () => ({
tmpdir: mocks.tmpdir,
}));
import spawn from "cross-spawn"; import spawn from "cross-spawn";
import { execSync } from "node:child_process"; import { execSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { import {
spawnClaude, spawnClaude,
writeUserMessage, writeUserMessage,
@@ -44,6 +60,11 @@ import {
describe("spawnClaude", () => { describe("spawnClaude", () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
mocks.writeFileSync.mockReset();
mocks.existsSync.mockReset();
mocks.readFileSync.mockReset();
mocks.tmpdir.mockReset();
mocks.tmpdir.mockReturnValue("/mock-tmp");
}); });
it("spawns claude with all required CLI flags", () => { it("spawns claude with all required CLI flags", () => {
@@ -94,20 +115,27 @@ describe("spawnClaude", () => {
it("writes system prompt to temp file and passes path via --append-system-prompt", () => { it("writes system prompt to temp file and passes path via --append-system-prompt", () => {
spawnClaude("claude-sonnet-4-5-20250929", "You are a helpful assistant."); spawnClaude("claude-sonnet-4-5-20250929", "You are a helpful assistant.");
const args = (spawn as any).mock.calls[0][1] as string[]; const args = (spawn as any).mock.calls[0][1] as string[];
const expectedTmpFile = `/mock-tmp/pi-claude-cli-sysprompt-${process.pid}.txt`;
expect(mocks.writeFileSync).toHaveBeenCalledWith(
expectedTmpFile,
"You are a helpful assistant.",
"utf-8",
);
expect(args).toContain("--append-system-prompt"); expect(args).toContain("--append-system-prompt");
const idx = args.indexOf("--append-system-prompt"); const idx = args.indexOf("--append-system-prompt");
expect(args[idx + 1]).toContain("pi-claude-cli-sysprompt-"); expect(args[idx + 1]).toContain("pi-claude-cli-sysprompt-");
expect(args[idx + 1]).toBe(expectedTmpFile);
}); });
it("temp file contains the system prompt text", () => { it("temp file contains the system prompt text", () => {
spawnClaude("claude-sonnet-4-5-20250929", "You are a helpful assistant."); spawnClaude("claude-sonnet-4-5-20250929", "You are a helpful assistant.");
const tmpFile = join(
tmpdir(), expect(mocks.writeFileSync).toHaveBeenCalledWith(
`pi-claude-cli-sysprompt-${process.pid}.txt`, `/mock-tmp/pi-claude-cli-sysprompt-${process.pid}.txt`,
"You are a helpful assistant.",
"utf-8",
); );
expect(existsSync(tmpFile)).toBe(true);
expect(readFileSync(tmpFile, "utf-8")).toBe("You are a helpful assistant.");
}); });
it("does not include --append-system-prompt when no system prompt", () => { it("does not include --append-system-prompt when no system prompt", () => {
@@ -599,21 +627,26 @@ describe("resume session flag", () => {
}); });
describe("cleanupSystemPromptFile", () => { describe("cleanupSystemPromptFile", () => {
const tmpFile = join(tmpdir(), `pi-claude-cli-sysprompt-${process.pid}.txt`); beforeEach(() => {
vi.clearAllMocks();
mocks.unlinkSync.mockReset();
mocks.tmpdir.mockReset();
mocks.tmpdir.mockReturnValue("/mock-tmp");
});
it("deletes the temp file when it exists", () => { it("deletes the temp file when it exists", () => {
// Create the file by spawning with a system prompt
spawnClaude("claude-sonnet-4-5-20250929", "test prompt");
expect(existsSync(tmpFile)).toBe(true);
cleanupSystemPromptFile(); cleanupSystemPromptFile();
expect(existsSync(tmpFile)).toBe(false);
expect(mocks.unlinkSync).toHaveBeenCalledWith(
`/mock-tmp/pi-claude-cli-sysprompt-${process.pid}.txt`,
);
}); });
it("does not throw when file does not exist", () => { it("does not throw when file does not exist", () => {
// Ensure file doesn't exist mocks.unlinkSync.mockImplementation(() => {
cleanupSystemPromptFile(); throw new Error("ENOENT");
// Call again — should not throw });
expect(() => cleanupSystemPromptFile()).not.toThrow(); expect(() => cleanupSystemPromptFile()).not.toThrow();
}); });
}); });