refactor: 修复代码审查发现的问题
- Bug修复: formatChangeStatus 使用实际配置而非 defaultConfig - 统一 assembler 中所有错误抛出为 CommandError - 提取 writeIfChanged 到 adapters/utils.ts,消除 claude-code/opencode 重复代码 - 导出 SUPPORTED_TOOLS,cli.ts update 命令复用同一工具注册表 - 提取 mapError/mapCacError 函数,支持单元测试 - 补充 claude-code 适配器测试(10 个用例) - 补充 validateChangeName、formatChangeStatus、suggestNextStep、mapError 单元测试(18 个用例) - 共新增 3 个测试文件,测试从 96 增至 133,全部通过
This commit is contained in:
73
src/cli.ts
73
src/cli.ts
@@ -15,8 +15,8 @@ import { scanChanges } from "./core/scanner.ts";
|
||||
import { UsageError, ConfigError, CommandError, InternalError, CliError } from "./cli/errors.ts";
|
||||
import { printError } from "./cli/output.ts";
|
||||
import { showGlobalHelp, showCommandHelp } from "./cli/help.ts";
|
||||
import type { ChangeStatus } from "./types.ts";
|
||||
import { defaultConfig } from "./defaults/config.ts";
|
||||
import type { ChangeStatus, RuneConfig } from "./types.ts";
|
||||
|
||||
|
||||
function requireProjectRoot(): string {
|
||||
const root = findProjectRoot();
|
||||
@@ -26,7 +26,7 @@ function requireProjectRoot(): string {
|
||||
return root;
|
||||
}
|
||||
|
||||
function validateChangeName(name: string): void {
|
||||
export function validateChangeName(name: string): void {
|
||||
if (!/^[\u4e00-\u9fa5a-zA-Z-]+$/.test(name)) {
|
||||
throw new CommandError(
|
||||
`变更名 "${name}" 包含不支持的字符`,
|
||||
@@ -35,16 +35,17 @@ function validateChangeName(name: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
function formatChangeStatus(change: ChangeStatus): string {
|
||||
export function formatChangeStatus(change: ChangeStatus, config?: RuneConfig): string {
|
||||
const lines: string[] = [];
|
||||
lines.push(`变更:${change.name}`);
|
||||
|
||||
lines.push(" 规划阶段:");
|
||||
const planDocs = config?.stages.plan?.documents;
|
||||
for (const doc of change.documents) {
|
||||
if (doc.completed) {
|
||||
lines.push(` ${doc.name}.md ✓ 已完成`);
|
||||
} else {
|
||||
const docConfig = defaultConfig.stages.plan?.documents.find((d) => d.name === doc.name);
|
||||
const docConfig = planDocs?.find((d) => d.name === doc.name);
|
||||
const depInfo = !doc.dependMet && docConfig?.depend?.length
|
||||
? `(依赖 ${docConfig.depend.map((d) => `${d}.md`).join("、")})`
|
||||
: "";
|
||||
@@ -71,7 +72,7 @@ function formatChangeStatus(change: ChangeStatus): string {
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function suggestNextStep(change: ChangeStatus): string {
|
||||
export function suggestNextStep(change: ChangeStatus): string {
|
||||
if (!change.planCompleted) {
|
||||
const nextDoc = change.documents.find((d) => !d.completed && d.dependMet);
|
||||
if (nextDoc) {
|
||||
@@ -140,19 +141,20 @@ cli.command("update [...tools]", "更新已注入的工具配置").action(
|
||||
const root = requireProjectRoot();
|
||||
const { updateOpenCode } = await import("./adapters/opencode.ts");
|
||||
const { updateClaudeCode } = await import("./adapters/claude-code.ts");
|
||||
const validators: Record<string, (root: string) => Promise<void>> = {
|
||||
const { SUPPORTED_TOOLS } = await import("./commands/init.ts");
|
||||
const updaters: Record<string, (root: string) => Promise<void>> = {
|
||||
opencode: updateOpenCode,
|
||||
"claude-code": updateClaudeCode,
|
||||
};
|
||||
for (const tool of tools) {
|
||||
if (!validators[tool]) {
|
||||
if (!SUPPORTED_TOOLS[tool]) {
|
||||
throw new CommandError(`不支持的工具: ${tool}`, {
|
||||
hint: `支持的工具: ${Object.keys(validators).join(", ")}`,
|
||||
hint: `支持的工具: ${Object.keys(SUPPORTED_TOOLS).join(", ")}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const tool of tools) {
|
||||
await validators[tool](root);
|
||||
await updaters[tool](root);
|
||||
}
|
||||
console.log(`工具配置已更新:${tools.join(", ")}`);
|
||||
},
|
||||
@@ -252,51 +254,66 @@ cli.command("status [change-name]", "查看变更状态").action(
|
||||
hint: "运行 rune status 查看所有变更",
|
||||
});
|
||||
}
|
||||
console.log(formatChangeStatus(change));
|
||||
console.log(formatChangeStatus(change, config));
|
||||
} else {
|
||||
if (changes.length === 0) {
|
||||
console.log("当前无进行中的变更。");
|
||||
return;
|
||||
}
|
||||
for (const change of changes) {
|
||||
console.log(formatChangeStatus(change));
|
||||
console.log(formatChangeStatus(change, config));
|
||||
console.log("---\n");
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function handleError(e: unknown): never {
|
||||
export function mapError(e: unknown): CliError {
|
||||
if (e instanceof CliError) {
|
||||
printError(e);
|
||||
} else if (e instanceof Error && e.message.includes("Unknown option")) {
|
||||
return e;
|
||||
}
|
||||
if (e instanceof Error) {
|
||||
const err = mapCacError(e);
|
||||
if (err) return err;
|
||||
}
|
||||
return new InternalError();
|
||||
}
|
||||
|
||||
function mapCacError(e: Error): CliError | null {
|
||||
if (e.message.includes("Unknown option")) {
|
||||
const match = e.message.match(/Unknown option `([^`]+)`/);
|
||||
const flag = match ? match[1] : "未知选项";
|
||||
printError(new UsageError(`未知选项: ${flag}`, {
|
||||
return new UsageError(`未知选项: ${flag}`, {
|
||||
hint: "运行 rune help 查看所有命令",
|
||||
}));
|
||||
} else if (e instanceof Error && e.message.includes("Unknown command")) {
|
||||
});
|
||||
}
|
||||
if (e.message.includes("Unknown command")) {
|
||||
const match = e.message.match(/Unknown command `([^`]+)`/);
|
||||
const cmd = match ? match[1] : "未知命令";
|
||||
printError(new UsageError(`未知命令: ${cmd}`, {
|
||||
return new UsageError(`未知命令: ${cmd}`, {
|
||||
hint: "运行 rune help 查看所有命令",
|
||||
}));
|
||||
} else if (e instanceof Error && e.message.includes("Unused args")) {
|
||||
});
|
||||
}
|
||||
if (e.message.includes("Unused args")) {
|
||||
const match = e.message.match(/Unused args: (.+)/);
|
||||
const args = match ? match[1] : "未知参数";
|
||||
printError(new UsageError(`未知命令: ${args.replace(/`/g, "")}`, {
|
||||
return new UsageError(`未知命令: ${args.replace(/`/g, "")}`, {
|
||||
hint: "运行 rune help 查看所有命令",
|
||||
}));
|
||||
} else if (e instanceof Error && e.message.includes("missing required args")) {
|
||||
});
|
||||
}
|
||||
if (e.message.includes("missing required args")) {
|
||||
const match = e.message.match(/command `(\w+)/);
|
||||
const cmd = match ? match[1] : "未知命令";
|
||||
printError(new UsageError(`命令 '${cmd}' 缺少必填参数`, {
|
||||
return new UsageError(`命令 '${cmd}' 缺少必填参数`, {
|
||||
usage: `rune ${cmd} <change-name>`,
|
||||
hint: `运行 rune help ${cmd} 查看用法`,
|
||||
}));
|
||||
} else {
|
||||
printError(new InternalError());
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function handleError(e: unknown): never {
|
||||
printError(mapError(e));
|
||||
}
|
||||
|
||||
process.on("unhandledRejection", (e) => {
|
||||
|
||||
Reference in New Issue
Block a user