fix: resolve npx gateway daemon install
This commit is contained in:
@@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Onboarding: resolve CLI entrypoint when running via `npx` so gateway daemon install works without a build step.
|
||||||
|
|
||||||
## 2026.1.5
|
## 2026.1.5
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|||||||
71
src/daemon/program-args.test.ts
Normal file
71
src/daemon/program-args.test.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const access = vi.fn();
|
||||||
|
const realpath = vi.fn();
|
||||||
|
|
||||||
|
vi.mock("node:fs/promises", () => ({
|
||||||
|
default: { access, realpath },
|
||||||
|
access,
|
||||||
|
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 () => {
|
||||||
|
process.argv = [
|
||||||
|
"node",
|
||||||
|
"/tmp/.npm/_npx/63c3/node_modules/.bin/clawdbot",
|
||||||
|
];
|
||||||
|
realpath.mockResolvedValue(
|
||||||
|
"/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/entry.js",
|
||||||
|
);
|
||||||
|
access.mockImplementation(async (target: string) => {
|
||||||
|
if (target === "/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/entry.js") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error("missing");
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await resolveGatewayProgramArguments({ port: 18789 });
|
||||||
|
|
||||||
|
expect(result.programArguments).toEqual([
|
||||||
|
process.execPath,
|
||||||
|
"/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/entry.js",
|
||||||
|
"gateway-daemon",
|
||||||
|
"--port",
|
||||||
|
"18789",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to node_modules package dist when .bin path is not resolved", async () => {
|
||||||
|
process.argv = [
|
||||||
|
"node",
|
||||||
|
"/tmp/.npm/_npx/63c3/node_modules/.bin/clawdbot",
|
||||||
|
];
|
||||||
|
realpath.mockRejectedValue(new Error("no realpath"));
|
||||||
|
access.mockImplementation(async (target: string) => {
|
||||||
|
if (target === "/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/index.js") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error("missing");
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await resolveGatewayProgramArguments({ port: 18789 });
|
||||||
|
|
||||||
|
expect(result.programArguments).toEqual([
|
||||||
|
process.execPath,
|
||||||
|
"/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/index.js",
|
||||||
|
"gateway-daemon",
|
||||||
|
"--port",
|
||||||
|
"18789",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -16,18 +16,14 @@ async function resolveCliEntrypointPathForService(): Promise<string> {
|
|||||||
if (!argv1) throw new Error("Unable to resolve CLI entrypoint path");
|
if (!argv1) throw new Error("Unable to resolve CLI entrypoint path");
|
||||||
|
|
||||||
const normalized = path.resolve(argv1);
|
const normalized = path.resolve(argv1);
|
||||||
const looksLikeDist = /[/\\]dist[/\\].+\.(cjs|js|mjs)$/.test(normalized);
|
const resolvedPath = await resolveRealpathSafe(normalized);
|
||||||
|
const looksLikeDist = /[/\\]dist[/\\].+\.(cjs|js|mjs)$/.test(resolvedPath);
|
||||||
if (looksLikeDist) {
|
if (looksLikeDist) {
|
||||||
await fs.access(normalized);
|
await fs.access(resolvedPath);
|
||||||
return normalized;
|
return resolvedPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
const distCandidates = [
|
const distCandidates = buildDistCandidates(resolvedPath, normalized);
|
||||||
path.resolve(path.dirname(normalized), "..", "dist", "index.js"),
|
|
||||||
path.resolve(path.dirname(normalized), "..", "dist", "index.mjs"),
|
|
||||||
path.resolve(path.dirname(normalized), "dist", "index.js"),
|
|
||||||
path.resolve(path.dirname(normalized), "dist", "index.mjs"),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const candidate of distCandidates) {
|
for (const candidate of distCandidates) {
|
||||||
try {
|
try {
|
||||||
@@ -43,6 +39,63 @@ async function resolveCliEntrypointPathForService(): Promise<string> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resolveRealpathSafe(inputPath: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
return await fs.realpath(inputPath);
|
||||||
|
} catch {
|
||||||
|
return inputPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDistCandidates(...inputs: string[]): string[] {
|
||||||
|
const candidates: string[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
|
||||||
|
for (const inputPath of inputs) {
|
||||||
|
if (!inputPath) continue;
|
||||||
|
const baseDir = path.dirname(inputPath);
|
||||||
|
appendDistCandidates(candidates, seen, path.resolve(baseDir, ".."));
|
||||||
|
appendDistCandidates(candidates, seen, baseDir);
|
||||||
|
appendNodeModulesBinCandidates(candidates, seen, inputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendDistCandidates(
|
||||||
|
candidates: string[],
|
||||||
|
seen: Set<string>,
|
||||||
|
baseDir: string,
|
||||||
|
): void {
|
||||||
|
const distDir = path.resolve(baseDir, "dist");
|
||||||
|
const distEntries = [
|
||||||
|
path.join(distDir, "index.js"),
|
||||||
|
path.join(distDir, "index.mjs"),
|
||||||
|
path.join(distDir, "entry.js"),
|
||||||
|
path.join(distDir, "entry.mjs"),
|
||||||
|
];
|
||||||
|
for (const entry of distEntries) {
|
||||||
|
if (seen.has(entry)) continue;
|
||||||
|
seen.add(entry);
|
||||||
|
candidates.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendNodeModulesBinCandidates(
|
||||||
|
candidates: string[],
|
||||||
|
seen: Set<string>,
|
||||||
|
inputPath: string,
|
||||||
|
): void {
|
||||||
|
const parts = inputPath.split(path.sep);
|
||||||
|
const binIndex = parts.lastIndexOf(".bin");
|
||||||
|
if (binIndex <= 0) return;
|
||||||
|
if (parts[binIndex - 1] !== "node_modules") return;
|
||||||
|
const binName = path.basename(inputPath);
|
||||||
|
const nodeModulesDir = parts.slice(0, binIndex).join(path.sep);
|
||||||
|
const packageRoot = path.join(nodeModulesDir, binName);
|
||||||
|
appendDistCandidates(candidates, seen, packageRoot);
|
||||||
|
}
|
||||||
|
|
||||||
function resolveRepoRootForDev(): string {
|
function resolveRepoRootForDev(): string {
|
||||||
const argv1 = process.argv[1];
|
const argv1 = process.argv[1];
|
||||||
if (!argv1) throw new Error("Unable to resolve repo root");
|
if (!argv1) throw new Error("Unable to resolve repo root");
|
||||||
|
|||||||
Reference in New Issue
Block a user