Files
skills-vault/test/cli.test.ts
T

226 lines
7.3 KiB
TypeScript

import { expect, test } from "bun:test";
import { existsSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { runCli } from "../src/cli";
const repoRoot = resolve(import.meta.dir, "..");
const packageJson = JSON.parse(
readFileSync(resolve(repoRoot, "package.json"), "utf8"),
) as {
bin?: Record<string, string>;
version?: string;
};
function normalizeBinTarget(target: string | undefined) {
return target?.replace(/^[.][/\\]/, "");
}
const ansiPattern = /\x1b\[[0-9;]*m/g;
function stripAnsi(text: string) {
return text.replace(ansiPattern, "");
}
test("declares a working skvlt bin target", async () => {
expect(normalizeBinTarget(packageJson.bin?.skvlt)).toBe("src/cli.ts");
const targetPath = resolve(repoRoot, packageJson.bin!.skvlt);
expect(existsSync(targetPath)).toBe(true);
const result = Bun.spawnSync(
[process.execPath, targetPath, "backup", "--help"],
{
cwd: repoRoot,
},
);
expect(result.exitCode).toBe(0);
});
test("dispatches the restore command", async () => {
const result = await runCli(["restore", "--help"]);
expect(result.exitCode).toBe(0);
});
test("dispatches the backup command", async () => {
const result = await runCli(["backup", "--help"]);
expect(result.exitCode).toBe(0);
});
test("dispatches the doctor command", async () => {
const result = await runCli(["doctor", "--help"]);
expect(result.exitCode).toBe(0);
});
test("dispatches the completion command", async () => {
const result = await runCli(["completion", "--help"]);
expect(result.exitCode).toBe(0);
});
test("prints top-level help", async () => {
const result = await runCli(["--help"]);
const plainStdout = stripAnsi(result.stdout);
expect(result.exitCode).toBe(0);
expect(plainStdout.startsWith("\n")).toBe(true);
expect(result.stdout).toContain(
"The CLI for backing up and restoring Agent Skills",
);
expect(result.stdout).toContain("Usage:");
expect(result.stdout).toContain("bunx skvlt <command> [options]");
expect(result.stdout).toContain("backup");
expect(result.stdout).toContain("completion");
expect(result.stdout).toContain("doctor");
expect(result.stdout).toContain("restore");
expect(result.stdout).toContain(
"bunx skvlt restore --only-source xixu-me/skills",
);
expect(result.stdout).not.toContain("bunx skvlt backup --dry-run");
expect(result.stdout).not.toContain("bunx skvlt restore --dry-run");
expect(result.stdout).not.toContain("anthropics/skills");
expect(result.stdout).toContain("\x1b[1mUsage:\x1b[0m");
expect(result.stdout).toContain("\x1b[1mExamples:\x1b[0m");
expect(result.stdout).toContain("\x1b[38;5;102m$\x1b[0m");
expect(plainStdout).toContain(
"\n\nExplore the open-source repo at https://github.com/xixu-me/skills-vault",
);
expect(
plainStdout.endsWith(
"Explore the open-source repo at https://github.com/xixu-me/skills-vault\n\n",
),
).toBe(true);
expect(result.stdout).toContain(
"Explore the open-source repo at https://github.com/xixu-me/skills-vault",
);
expect(result.stderr).toBe("");
});
test("prints the package version", async () => {
const result = await runCli(["--version"]);
expect(result.exitCode).toBe(0);
expect(result.stdout.trim()).toBe(packageJson.version);
expect(result.stderr).toBe("");
});
test("prints the package version as JSON", async () => {
const result = await runCli(["--json", "--version"]);
const payload = JSON.parse(result.stdout) as {
ok: boolean;
command: string;
data: { version: string };
};
expect(result.exitCode).toBe(0);
expect(result.stderr).toBe("");
expect(payload).toEqual({
ok: true,
command: "version",
data: { version: packageJson.version! },
});
});
test("rejects unknown commands with guidance", async () => {
const result = await runCli(["sync"]);
const plainStderr = stripAnsi(result.stderr);
expect(result.exitCode).toBe(1);
expect(result.stdout).toBe("");
expect(plainStderr.startsWith("\n")).toBe(true);
expect(plainStderr).toContain("Unknown command: sync");
expect(plainStderr).toContain("Run bunx skvlt --help for usage.");
expect(plainStderr.endsWith("\n\n")).toBe(true);
expect(result.stderr).toContain("Unknown command: sync");
expect(result.stderr).toContain("\x1b[1mbunx skvlt --help\x1b[0m");
});
test("returns JSON errors for unknown commands", async () => {
const result = await runCli(["sync", "--json"]);
const payload = JSON.parse(result.stdout) as {
ok: boolean;
command: string;
error: { code: string; message: string };
};
expect(result.exitCode).toBe(1);
expect(result.stderr).toBe("");
expect(payload.ok).toBe(false);
expect(payload.command).toBe("sync");
expect(payload.error.code).toBe("UNKNOWN_COMMAND");
expect(payload.error.message).toContain("Unknown command: sync");
expect(result.stdout).not.toMatch(ansiPattern);
});
test("uses process argv when invoked like the CLI", async () => {
const { main } = await import("../src/cli");
const processRef = process as unknown as { argv: string[] };
const consoleRef = console as unknown as {
error: (...args: unknown[]) => void;
};
const stdoutRef = process.stdout as unknown as {
write: (chunk: string | Uint8Array) => boolean;
};
const originalArgv = processRef.argv;
const originalError = consoleRef.error;
const originalWrite = stdoutRef.write;
let stdoutText = "";
try {
processRef.argv = ["bun", "/tmp/skvlt/src/cli.ts"];
consoleRef.error = () => {};
stdoutRef.write = (chunk: string | Uint8Array) => {
stdoutText += String(chunk);
return true;
};
const exitCode = await main();
const plainStdout = stripAnsi(stdoutText);
expect(exitCode).toBe(0);
expect(plainStdout).toContain(
"The CLI for backing up and restoring Agent Skills",
);
expect(plainStdout).toContain(
"Explore the open-source repo at https://github.com/xixu-me/skills-vault",
);
expect(plainStdout).not.toContain("try:");
expect(stdoutText).toContain("\x1b[38;5;102m");
expect(stdoutText).toContain("\x1b[38;5;145m");
expect(stdoutText).not.toContain("\x1b[36m");
expect(stdoutText).not.toContain("\x1b[38;5;250m");
} finally {
processRef.argv = originalArgv;
consoleRef.error = originalError;
stdoutRef.write = originalWrite;
}
});
test("shows a landing page when no command is provided", async () => {
const result = await runCli([]);
const plainStdout = stripAnsi(result.stdout);
expect(result.exitCode).toBe(0);
expect(result.stderr).toBe("");
expect(plainStdout.startsWith("\n")).toBe(true);
expect(plainStdout).toContain(
"The CLI for backing up and restoring Agent Skills",
);
expect(plainStdout).toContain(
"Explore the open-source repo at https://github.com/xixu-me/skills-vault",
);
expect(plainStdout).toContain(
"\n\nExplore the open-source repo at https://github.com/xixu-me/skills-vault",
);
expect(
plainStdout.endsWith(
"Explore the open-source repo at https://github.com/xixu-me/skills-vault\n\n",
),
).toBe(true);
expect(result.stdout).toContain("\x1b[38;5;102m");
expect(result.stdout).toContain("\x1b[38;5;145m");
expect(result.stdout).not.toContain("\x1b[36m");
expect(result.stdout).not.toContain("\x1b[38;5;250m");
expect(result.stdout).not.toContain("\x1b[1mUsage:\x1b[0m");
expect(result.stdout).not.toContain("Run");
});