server/cli-probe.ts
Rama main · réplica de lectura del repositorio original.
// Settings → engines "Test": run a CLI's `--version` with only PATH, never inherited credentials.
import { PROVIDER_CREDENTIAL_ENV, stripWorkspaceCredentialEnv } from "./config.ts";
import { clientInstall } from "./contracts.ts";
import type { BUILT_IN_DRIVERS } from "./drivers/builtIn.ts";
import { notThisCli } from "./drivers/acp/version-probe.ts";
import { augmentedPath } from "./env-path.ts";
import { describeSpawnFailure, execCli } from "./procs.ts";
export async function testCliBinary(
cli: string,
driver: (typeof BUILT_IN_DRIVERS)[number] | undefined,
): Promise<{ ok: boolean; version?: string; message?: string; install?: (typeof BUILT_IN_DRIVERS)[number]["install"] }> {
return new Promise((resolve) => {
execCli(
cli,
["--version"],
{
timeout: 10_000,
// SIGKILL, not SIGTERM: a child that traps TERM would never fire the callback and would pin the HTTP socket.
killSignal: "SIGKILL",
maxBuffer: 1024 * 64,
env: cliProbeEnvironment(),
},
(err, stdout) => {
if (err) {
const e = err as NodeJS.ErrnoException & { killed?: boolean };
// Node's err.code is an errno string only for spawn failures; a non-zero exit gives the status, a timeout null with killed.
const exceededBuffer = e.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
const isSpawnError = typeof e.code === "string" && !exceededBuffer;
const message = exceededBuffer
? "CLI test produced more than 64 KiB of output"
: isSpawnError
? describeSpawnFailure(e, cli).message
: e.killed
? "CLI test timed out after 10s"
: `CLI exited with error ${String(e.code)}: ${(stderrOf(err) || "").slice(0, 200) || err.message.split("\n")[0]}`;
resolve({ ok: false, message, ...(driver?.install && isSpawnError ? { install: clientInstall(driver.install) } : {}) });
return;
}
const version = stdout.trim().split("\n")[0];
// An install command pasted as the path runs curl, which answers --version too: that is not this engine.
const expected = driver?.metadata.cliVersion;
if (expected && !expected.test(version)) return resolve({ ok: false, message: notThisCli(cli, driver.metadata.displayName) });
resolve({ ok: true, version });
},
);
});
}
/** Only PATH: never hand inherited credentials to an arbitrary wrapper selected through Settings. */
export function cliProbeEnvironment(): NodeJS.ProcessEnv {
const env: NodeJS.ProcessEnv = { ...process.env, PATH: augmentedPath() };
stripWorkspaceCredentialEnv(env);
for (const key of PROVIDER_CREDENTIAL_ENV) delete env[key];
return env;
}
function stderrOf(err: unknown): string {
const s = (err as { stderr?: unknown }).stderr;
return typeof s === "string" ? s : Buffer.isBuffer(s) ? s.toString("utf8") : "";
}