feat: init 命令检测包管理器并写入 metadata.command

This commit is contained in:
2026-06-09 20:18:04 +08:00
parent a5c8263412
commit 589eaa120e
2 changed files with 39 additions and 5 deletions

View File

@@ -1,10 +1,12 @@
import { existsSync } from "node:fs"; import { existsSync } from "node:fs";
import { mkdir, writeFile } from "node:fs/promises"; import { mkdir, writeFile, readFile, appendFile } from "node:fs/promises";
import { join } from "node:path"; import { join } from "node:path";
import { CHANGES_DIR, ARCHIVE_DIR, RUNE_DIR, CONFIG_FILE } from "../types.ts"; import { CHANGES_DIR, ARCHIVE_DIR, RUNE_DIR, CONFIG_FILE } from "../types.ts";
import { injectOpenCode } from "../adapters/opencode.ts"; import { injectOpenCode } from "../adapters/opencode.ts";
import { injectClaudeCode } from "../adapters/claude-code.ts"; import { injectClaudeCode } from "../adapters/claude-code.ts";
import { CommandError } from "../cli/errors.ts"; import { CommandError } from "../cli/errors.ts";
import { detectCommandPrefix, getPmPrefix } from "../core/pm.ts";
import { parse as parseYaml } from "yaml";
const CONFIG_TEMPLATE = `# Rune 配置文件 const CONFIG_TEMPLATE = `# Rune 配置文件
# #
@@ -38,11 +40,29 @@ const CONFIG_TEMPLATE = `# Rune 配置文件
# # {{change-name}} 任务清单 # # {{change-name}} 任务清单
`; `;
export const SUPPORTED_TOOLS: Record<string, (root: string) => Promise<void>> = { export const SUPPORTED_TOOLS: Record<string, (root: string, command?: string) => Promise<void>> = {
opencode: injectOpenCode, opencode: injectOpenCode,
"claude-code": injectClaudeCode, "claude-code": injectClaudeCode,
}; };
async function ensureMetadataCommand(configPath: string, command: string): Promise<void> {
const content = await readFile(configPath, "utf-8");
const parsed = parseYaml(content) as { metadata?: { command?: string } } | null;
if (parsed?.metadata?.command) return;
if (parsed?.metadata) {
const lines = content.split("\n");
const metaIdx = lines.findIndex((l) => l.trim() === "metadata:");
if (metaIdx >= 0) {
lines.splice(metaIdx + 1, 0, ` command: "${command}"`);
await writeFile(configPath, lines.join("\n"), "utf-8");
return;
}
}
await appendFile(configPath, `\nmetadata:\n command: "${command}"\n`);
}
export async function runInit(projectRoot: string, tools: string[]): Promise<void> { export async function runInit(projectRoot: string, tools: string[]): Promise<void> {
for (const tool of tools) { for (const tool of tools) {
if (!SUPPORTED_TOOLS[tool]) { if (!SUPPORTED_TOOLS[tool]) {
@@ -62,7 +82,13 @@ export async function runInit(projectRoot: string, tools: string[]): Promise<voi
await writeFile(configPath, CONFIG_TEMPLATE, "utf-8"); await writeFile(configPath, CONFIG_TEMPLATE, "utf-8");
} }
const command = await detectCommandPrefix();
if (command) {
await ensureMetadataCommand(configPath, command);
}
const prefix = command ?? getPmPrefix();
for (const tool of tools) { for (const tool of tools) {
await SUPPORTED_TOOLS[tool](projectRoot); await SUPPORTED_TOOLS[tool](projectRoot, prefix === "rune" ? undefined : prefix);
} }
} }

View File

@@ -41,13 +41,13 @@ describe("runInit", () => {
expect(existsSync(join(TMP_DIR, ".opencode", "skills", "rune-discuss", "SKILL.md"))).toBe(true); expect(existsSync(join(TMP_DIR, ".opencode", "skills", "rune-discuss", "SKILL.md"))).toBe(true);
}); });
it("重复 init 不覆盖 config.yaml", async () => { it("重复 init 不覆盖 config.yaml 已有内容", async () => {
await runInit(TMP_DIR, ["opencode"]); await runInit(TMP_DIR, ["opencode"]);
await writeFile(join(TMP_DIR, ".rune", "config.yaml"), "自定义内容"); await writeFile(join(TMP_DIR, ".rune", "config.yaml"), "自定义内容");
await runInit(TMP_DIR, ["opencode"]); await runInit(TMP_DIR, ["opencode"]);
const content = await readFile(join(TMP_DIR, ".rune", "config.yaml"), "utf-8"); const content = await readFile(join(TMP_DIR, ".rune", "config.yaml"), "utf-8");
expect(content).toBe("自定义内容"); expect(content).toContain("自定义内容");
}); });
it("不支持的工具名抛出 CommandError", async () => { it("不支持的工具名抛出 CommandError", async () => {
@@ -59,4 +59,12 @@ describe("runInit", () => {
expect((e as CommandError).message).toContain("不支持的工具: unknown-tool"); expect((e as CommandError).message).toContain("不支持的工具: unknown-tool");
} }
}); });
it("首次 init 时 config.yaml 包含 metadata.command", async () => {
await runInit(TMP_DIR, ["opencode"]);
const content = await readFile(join(TMP_DIR, ".rune", "config.yaml"), "utf-8");
expect(content).toContain("metadata:");
expect(content).toContain("command:");
});
}); });