Files
servers/scripts/lib/claude-cli.test.mjs
cliffhallandClaude Opus 5.5 a5c40bf838 feat: port the skills harness (verify:skills, verify:skills:cli, skills:eval)
Port the MCP Inspector's skills infrastructure for #4858 Part 3:

- scripts/verify-skills.mjs, verify-skills-cli.mjs, skill-eval.mjs and
  scripts/lib/{skill-manifest,claude-cli,npm-scripts,win-shell-args}.mjs,
  with all their *.test.mjs files
- root npm scripts verify:skills, verify:skills:cli, skills:eval and
  test:scripts; yaml as a root devDependency
- docs/skill-authoring.md, with paths and provenance adapted
- a root-guards job in typescript.yml running verify:skills and
  verify:skills:cli on every push and pull request
- .claude/skills/ (kept by a .gitkeep until the first skill lands)
- un-ignore scripts/lib/, which the Python `lib/` pattern swallowed

Adapted to this repo: checkWiring reads typescript.yml, and carries two
temporary allowances (validate -> verify:format-coverage, removed by
#4864; local:gate -> verify:skills:cli, removed by #4871) that excuse a
link only while its pending script is absent and fail as stale once it
exists. The "no skills found" failure is kept, with a temporary
EMPTY_SKILLS_BOOTSTRAP allowance that fails as stale once a skill exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
2026-09-29 00:10:03 -04:00

107 lines
3.3 KiB
JavaScript

// Tests for the one place that decides how `claude` is spawned (inspector#2163, Copilot).
//
// The behavior under test only differs ON Windows, and these run on whatever
// the contributor has — so the platform is injected rather than sniffed. A
// win32-only bug that no CI runner here executes is precisely the kind that
// ships.
import { test } from "node:test";
import assert from "node:assert/strict";
import {
claudeSpawnArgs,
cliSpawnArgs,
probeClaudeVersion,
probeCliVersion,
} from "./claude-cli.mjs";
test("spawns without a shell off Windows", () => {
const { command, args, options } = claudeSpawnArgs(
["plugin", "validate", ".claude/skills"],
{ cwd: "/repo" },
"linux",
);
assert.equal(command, "claude");
assert.deepEqual(args, ["plugin", "validate", ".claude/skills"]);
assert.equal(options.shell, false);
assert.equal(options.cwd, "/repo");
});
test("uses a shell on Windows, where the CLI is a .cmd shim", () => {
// Node has refused shell-free `.cmd` spawns since CVE-2024-27980, so without
// this the CLI reads as absent even when it is installed.
const { options } = claudeSpawnArgs(["--version"], {}, "win32");
assert.equal(options.shell, true);
});
test("quotes arguments that cmd.exe would otherwise re-parse", () => {
const { args } = claudeSpawnArgs(["-p", "does it? (yes & no)"], {}, "win32");
assert.deepEqual(args, ["-p", '"does it? (yes & no)"']);
// The same arguments are untouched off Windows.
assert.deepEqual(
claudeSpawnArgs(["-p", "does it? (yes & no)"], {}, "linux").args,
["-p", "does it? (yes & no)"],
);
});
test("probeClaudeVersion returns the parsed version on success", () => {
const version = probeClaudeVersion(
(t) => (t.trim() === "2.1.250" ? [2, 1, 250] : null),
{
spawn: () => ({ status: 0, stdout: "2.1.250" }),
platform: "linux",
},
);
assert.deepEqual(version, [2, 1, 250]);
});
test("probeClaudeVersion returns null for every unusable outcome", () => {
for (const res of [
{ error: new Error("ENOENT") },
{ status: 1, stdout: "" },
{ status: 0, stdout: "something else" },
]) {
assert.equal(
probeClaudeVersion(() => null, { spawn: () => res, platform: "linux" }),
null,
);
}
});
test("probeClaudeVersion asks for the shell on Windows", () => {
let seen;
probeClaudeVersion(() => [2, 1, 250], {
spawn: (_c, _a, options) => {
seen = options;
return { status: 0, stdout: "2.1.250" };
},
platform: "win32",
});
assert.equal(seen.shell, true);
assert.equal(seen.encoding, "utf8");
});
test("the generic helpers name the command they were given (inspector#2397)", () => {
// `copilot` is an npm `.cmd` shim on Windows as well, so it takes the same
// shell-and-quoting decision rather than a second copy of it.
const { command, options, args } = cliSpawnArgs(
"copilot",
["--prompt", "a & b"],
{},
"win32",
);
assert.equal(command, "copilot");
assert.equal(options.shell, true);
assert.deepEqual(args, ["--prompt", '"a & b"']);
let spawned;
const version = probeCliVersion("copilot", (t) => t.trim(), {
spawn: (c) => {
spawned = c;
return { status: 0, stdout: "GitHub Copilot CLI 1.0.85.\n" };
},
platform: "linux",
});
assert.equal(spawned, "copilot");
assert.equal(version, "GitHub Copilot CLI 1.0.85.");
});