164 lines
5.1 KiB
TypeScript
164 lines
5.1 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
import { existsSync } from "node:fs";
|
|
import { mkdir, rm, readFile, readdir, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { injectOpenCode, updateOpenCode } from "../../src/adapters/opencode.ts";
|
|
|
|
const TMP_DIR = join(import.meta.dir, "__tmp_opencode_test__");
|
|
|
|
beforeEach(async () => {
|
|
await mkdir(TMP_DIR, { recursive: true });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(TMP_DIR, { recursive: true, force: true });
|
|
});
|
|
|
|
describe("injectOpenCode", () => {
|
|
it("生成 discuss、plan、build、archive 的 command 和 skill 文件", async () => {
|
|
await injectOpenCode(TMP_DIR);
|
|
|
|
const commands = await readdir(join(TMP_DIR, ".opencode", "commands"));
|
|
const skills = await readdir(join(TMP_DIR, ".opencode", "skills"));
|
|
|
|
for (const stage of ["discuss", "plan", "build", "archive"]) {
|
|
expect(commands).toContain(`rune-${stage}.md`);
|
|
expect(skills).toContain(`rune-${stage}`);
|
|
expect(
|
|
existsSync(join(TMP_DIR, ".opencode", "skills", `rune-${stage}`, "SKILL.md")),
|
|
).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("生成 rune-status command 和 skill", async () => {
|
|
await injectOpenCode(TMP_DIR);
|
|
|
|
const commands = await readdir(join(TMP_DIR, ".opencode", "commands"));
|
|
const skills = await readdir(join(TMP_DIR, ".opencode", "skills"));
|
|
|
|
expect(commands).toContain("rune-status.md");
|
|
expect(skills).toContain("rune-status");
|
|
expect(
|
|
existsSync(join(TMP_DIR, ".opencode", "skills", "rune-status", "SKILL.md")),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("command 文件包含 skill 调用指令", async () => {
|
|
await injectOpenCode(TMP_DIR);
|
|
const content = await readFile(
|
|
join(TMP_DIR, ".opencode", "commands", "rune-discuss.md"),
|
|
"utf-8",
|
|
);
|
|
expect(content).toContain("rune-discuss");
|
|
});
|
|
|
|
it("skill 文件包含 bash 命令", async () => {
|
|
await injectOpenCode(TMP_DIR);
|
|
const content = await readFile(
|
|
join(TMP_DIR, ".opencode", "skills", "rune-discuss", "SKILL.md"),
|
|
"utf-8",
|
|
);
|
|
expect(content).toContain("rune discuss");
|
|
expect(content).toContain("description");
|
|
expect(content).toContain("name: rune-discuss");
|
|
});
|
|
|
|
it("plan/build/archive skill 包含变更名称参数提示", async () => {
|
|
await injectOpenCode(TMP_DIR);
|
|
|
|
for (const stage of ["plan", "build", "archive"]) {
|
|
const content = await readFile(
|
|
join(TMP_DIR, ".opencode", "skills", `rune-${stage}`, "SKILL.md"),
|
|
"utf-8",
|
|
);
|
|
expect(content).toContain("变更名");
|
|
}
|
|
});
|
|
|
|
it("plan skill 包含运行 status 的引导", async () => {
|
|
await injectOpenCode(TMP_DIR);
|
|
const content = await readFile(
|
|
join(TMP_DIR, ".opencode", "skills", "rune-plan", "SKILL.md"),
|
|
"utf-8",
|
|
);
|
|
expect(content).toContain("rune status");
|
|
});
|
|
|
|
it("discuss skill 不包含 status 引导", async () => {
|
|
await injectOpenCode(TMP_DIR);
|
|
const content = await readFile(
|
|
join(TMP_DIR, ".opencode", "skills", "rune-discuss", "SKILL.md"),
|
|
"utf-8",
|
|
);
|
|
expect(content).not.toContain("rune status");
|
|
});
|
|
|
|
it("重复注入时不覆盖已存在的文件", async () => {
|
|
await injectOpenCode(TMP_DIR);
|
|
const originalContent = await readFile(
|
|
join(TMP_DIR, ".opencode", "commands", "rune-discuss.md"),
|
|
"utf-8",
|
|
);
|
|
|
|
await injectOpenCode(TMP_DIR);
|
|
const content = await readFile(
|
|
join(TMP_DIR, ".opencode", "commands", "rune-discuss.md"),
|
|
"utf-8",
|
|
);
|
|
expect(content).toBe(originalContent);
|
|
});
|
|
});
|
|
|
|
describe("updateOpenCode", () => {
|
|
it("文件不存在时创建", async () => {
|
|
await updateOpenCode(TMP_DIR);
|
|
expect(
|
|
existsSync(join(TMP_DIR, ".opencode", "commands", "rune-discuss.md")),
|
|
).toBe(true);
|
|
expect(
|
|
existsSync(join(TMP_DIR, ".opencode", "skills", "rune-discuss", "SKILL.md")),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("文件存在且内容一致时不覆盖", async () => {
|
|
await injectOpenCode(TMP_DIR);
|
|
const originalContent = await readFile(
|
|
join(TMP_DIR, ".opencode", "commands", "rune-discuss.md"),
|
|
"utf-8",
|
|
);
|
|
|
|
await updateOpenCode(TMP_DIR);
|
|
const content = await readFile(
|
|
join(TMP_DIR, ".opencode", "commands", "rune-discuss.md"),
|
|
"utf-8",
|
|
);
|
|
expect(content).toBe(originalContent);
|
|
});
|
|
|
|
it("文件存在但内容不一致时覆盖", async () => {
|
|
await injectOpenCode(TMP_DIR);
|
|
await writeFile(
|
|
join(TMP_DIR, ".opencode", "commands", "rune-discuss.md"),
|
|
"旧内容",
|
|
);
|
|
|
|
await updateOpenCode(TMP_DIR);
|
|
const content = await readFile(
|
|
join(TMP_DIR, ".opencode", "commands", "rune-discuss.md"),
|
|
"utf-8",
|
|
);
|
|
expect(content).not.toBe("旧内容");
|
|
expect(content).toContain("rune-discuss");
|
|
});
|
|
|
|
it("更新 status 命令和 skill", async () => {
|
|
await updateOpenCode(TMP_DIR);
|
|
expect(
|
|
existsSync(join(TMP_DIR, ".opencode", "commands", "rune-status.md")),
|
|
).toBe(true);
|
|
expect(
|
|
existsSync(join(TMP_DIR, ".opencode", "skills", "rune-status", "SKILL.md")),
|
|
).toBe(true);
|
|
});
|
|
});
|