UNPKG

@phren/cli

Version:

Knowledge layer for AI agents - CLI, MCP server, and data layer

86 lines (85 loc) • 4.23 kB
/** * Vitest globalSetup — runs once in the main process before any test workers spawn. * * Builds mcp/dist if it is missing so every fork sees a complete, consistent * dist artifact before tests begin. Individual subprocess helpers can still * repair a missing artifact later under a lock if some test mutates dist. * * `pretest` in package.json already calls `npm run build`, so in normal `npm test` * runs this is a fast no-op check. It is the safety net for: * - `vitest run` called directly (no pretest hook) * - Watch mode re-runs where pretest does not re-fire * - CI environments that skip npm lifecycle scripts */ import { execFileSync } from "child_process"; import * as fs from "fs"; import * as os from "os"; import * as path from "path"; import { fileURLToPath } from "url"; import { removeSandboxHome } from "./test-sandbox.js"; import { sweepTestServers } from "./bridge/tmux-test-server.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const REPO_ROOT = path.resolve(__dirname, "../../.."); const CLI_PATH = path.join(REPO_ROOT, "packages", "cli", "dist", "index.js"); let sandboxHome = null; /** * Point HOME (and USERPROFILE) at a throwaway directory for the whole run. * * `pool: "forks"` workers inherit this process's env, and on Linux/macOS * `os.homedir()` reads $HOME too, so this covers every route the CLI uses to * find the user's home. Without it, isolation depends on each test * remembering to override HOME — and the ones that forgot (e.g. the web-ui * readiness test) re-pointed the developer's real ~/.claude/CLAUDE.md and * ~/.claude/skills/phren-* at temp dirs that were deleted seconds later. */ function sandboxHomeDir() { sandboxHome = fs.mkdtempSync(path.join(os.tmpdir(), "phren-test-home-")); process.env.HOME = sandboxHome; process.env.USERPROFILE = sandboxHome; // findPhrenPath walks up from the cwd before it tries $HOME/.phren. The // checkout usually sits under the developer's real home, so that walk found // their real ~/.phren and the logger appended every test's errors to its // .runtime/debug.log. Naming the sandbox's own store skips the walk and // resolves exactly as on a clean CI machine: the sandbox store if a test // makes one, otherwise no store. A test that needs the walk clears this. process.env.PHREN_PATH = path.join(sandboxHome, ".phren"); // The Hook writes folder trust into Claude's and Codex's own config // (bridge/folder-trust.ts). A developer's CLAUDE_CONFIG_DIR or CODEX_HOME // would point that at their real files, so both fall back to the sandbox. delete process.env.CLAUDE_CONFIG_DIR; delete process.env.CODEX_HOME; // A run inside a Hook-launched worker inherits the Hook's own service // variables. PHREN_BRIDGE_HOME pointed every install and init test at the // real Hook, and `phren init` on a scratch store ran a real // `systemctl --user stop phren-hook.service`. Unset, each falls back to // the sandbox home, as on a clean machine. for (const name of ["PHREN_BRIDGE_HOME", "PHREN_HERDR_HOME", "PHREN_PROFILE", "PHREN_DISPATCH_ID", "PHREN_CODEX_SERVER"]) delete process.env[name]; } export async function teardown() { if (sandboxHome) removeSandboxHome(sandboxHome); // Workers are gone now: any tmux test server still up was leaked by one. if (process.platform !== "win32") sweepTestServers(); } export async function setup() { sandboxHomeDir(); // Private tmux servers and sockets earlier, killed runs left behind. if (process.platform !== "win32") sweepTestServers(); if (fs.existsSync(CLI_PATH)) { // Dist already present — skip build. This is the common path when // `npm test` is used (pretest already built it) or during watch mode // re-runs where the artifact is still fresh. return; } process.stdout.write("[test-global-setup] packages/cli/dist missing — building...\n"); execFileSync("pnpm", ["build"], { cwd: REPO_ROOT, stdio: "inherit", timeout: 60_000, }); process.stdout.write("[test-global-setup] build complete.\n"); }