From dab63975f56f3e2cefacc2992d33571fcbc50823 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Mon, 8 Jun 2026 22:30:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20CLI=20=E9=94=99=E8=AF=AF=E7=B1=BB?= =?UTF-8?q?=E5=B1=82=E6=AC=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cli/errors.ts | 26 +++++++++++++++++++ tests/cli/errors.test.ts | 55 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/cli/errors.ts create mode 100644 tests/cli/errors.test.ts diff --git a/src/cli/errors.ts b/src/cli/errors.ts new file mode 100644 index 0000000..ea89372 --- /dev/null +++ b/src/cli/errors.ts @@ -0,0 +1,26 @@ +export class CliError extends Error { + readonly hint?: string; + readonly usage?: string; + + constructor( + message: string, + opts?: { hint?: string; usage?: string }, + ) { + super(message); + this.name = this.constructor.name; + this.hint = opts?.hint; + this.usage = opts?.usage; + } +} + +export class UsageError extends CliError {} + +export class ConfigError extends CliError {} + +export class CommandError extends CliError {} + +export class InternalError extends CliError { + constructor() { + super("发生了未预期的错误"); + } +} diff --git a/tests/cli/errors.test.ts b/tests/cli/errors.test.ts new file mode 100644 index 0000000..762a665 --- /dev/null +++ b/tests/cli/errors.test.ts @@ -0,0 +1,55 @@ +import { describe, it, expect } from "bun:test"; +import { + CliError, + UsageError, + ConfigError, + CommandError, + InternalError, +} from "../../src/cli/errors.ts"; + +describe("CliError 类层次", () => { + it("UsageError 携带 message 和可选 hint", () => { + const err = new UsageError("缺少参数", { hint: "请提供参数" }); + expect(err).toBeInstanceOf(CliError); + expect(err).toBeInstanceOf(UsageError); + expect(err.message).toBe("缺少参数"); + expect(err.hint).toBe("请提供参数"); + expect(err.usage).toBeUndefined(); + }); + + it("UsageError 携带 message、hint、usage", () => { + const err = new UsageError("缺少参数", { + hint: "请提供参数", + usage: "rune plan ", + }); + expect(err.usage).toBe("rune plan "); + }); + + it("ConfigError 只带 message", () => { + const err = new ConfigError("未初始化"); + expect(err).toBeInstanceOf(CliError); + expect(err).toBeInstanceOf(ConfigError); + expect(err.message).toBe("未初始化"); + expect(err.hint).toBeUndefined(); + }); + + it("CommandError 带 message 和 hint", () => { + const err = new CommandError("变更不存在", { hint: "请先运行 rune plan" }); + expect(err).toBeInstanceOf(CliError); + expect(err.message).toBe("变更不存在"); + expect(err.hint).toBe("请先运行 rune plan"); + }); + + it("InternalError 用默认 message", () => { + const err = new InternalError(); + expect(err).toBeInstanceOf(CliError); + expect(err.message).toBe("发生了未预期的错误"); + }); + + it("可以用 name 区分错误类型", () => { + expect(new UsageError("a").name).toBe("UsageError"); + expect(new ConfigError("b").name).toBe("ConfigError"); + expect(new CommandError("c").name).toBe("CommandError"); + expect(new InternalError().name).toBe("InternalError"); + }); +});