feat: init 命令检测包管理器并写入 metadata.command
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user