import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; const fsMocks = vi.hoisted(() => ({ access: vi.fn(), realpath: vi.fn(), })); vi.mock("node:fs/promises", () => ({ default: { access: fsMocks.access, realpath: fsMocks.realpath }, access: fsMocks.access, realpath: fsMocks.realpath, })); import { resolveGatewayProgramArguments } from "./program-args.js"; const originalArgv = [...process.argv]; afterEach(() => { process.argv = [...originalArgv]; vi.resetAllMocks(); }); describe("resolveGatewayProgramArguments", () => { it("uses realpath-resolved dist entry when running via npx shim", async () => { const argv1 = path.resolve("/tmp/.npm/_npx/63c3/node_modules/.bin/clawdbot"); const entryPath = path.resolve("/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/entry.js"); process.argv = ["node", argv1]; fsMocks.realpath.mockResolvedValue(entryPath); fsMocks.access.mockImplementation(async (target: string) => { if (target === entryPath) { return; } throw new Error("missing"); }); const result = await resolveGatewayProgramArguments({ port: 18789 }); expect(result.programArguments).toEqual([ process.execPath, entryPath, "gateway", "--port", "18789", ]); }); it("prefers symlinked path over realpath for stable service config", async () => { // Simulates pnpm global install where node_modules/clawdbot is a symlink // to .pnpm/clawdbot@X.Y.Z/node_modules/clawdbot const symlinkPath = path.resolve( "/Users/test/Library/pnpm/global/5/node_modules/clawdbot/dist/entry.js", ); const realpathResolved = path.resolve( "/Users/test/Library/pnpm/global/5/node_modules/.pnpm/clawdbot@2026.1.21-2/node_modules/clawdbot/dist/entry.js", ); process.argv = ["node", symlinkPath]; fsMocks.realpath.mockResolvedValue(realpathResolved); fsMocks.access.mockResolvedValue(undefined); // Both paths exist const result = await resolveGatewayProgramArguments({ port: 18789 }); // Should use the symlinked path, not the realpath-resolved versioned path expect(result.programArguments[1]).toBe(symlinkPath); expect(result.programArguments[1]).not.toContain("@2026.1.21-2"); }); it("falls back to node_modules package dist when .bin path is not resolved", async () => { const argv1 = path.resolve("/tmp/.npm/_npx/63c3/node_modules/.bin/clawdbot"); const indexPath = path.resolve("/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/index.js"); process.argv = ["node", argv1]; fsMocks.realpath.mockRejectedValue(new Error("no realpath")); fsMocks.access.mockImplementation(async (target: string) => { if (target === indexPath) { return; } throw new Error("missing"); }); const result = await resolveGatewayProgramArguments({ port: 18789 }); expect(result.programArguments).toEqual([ process.execPath, indexPath, "gateway", "--port", "18789", ]); }); });