@xiaozhi-client/cli 1.9.4-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +98 -0
  2. package/package.json +31 -0
  3. package/project.json +75 -0
  4. package/src/Constants.ts +105 -0
  5. package/src/Container.ts +212 -0
  6. package/src/Types.ts +79 -0
  7. package/src/commands/CommandHandlerFactory.ts +98 -0
  8. package/src/commands/ConfigCommandHandler.ts +279 -0
  9. package/src/commands/EndpointCommandHandler.ts +158 -0
  10. package/src/commands/McpCommandHandler.ts +778 -0
  11. package/src/commands/ProjectCommandHandler.ts +254 -0
  12. package/src/commands/ServiceCommandHandler.ts +182 -0
  13. package/src/commands/__tests__/CommandHandlerFactory.test.ts +323 -0
  14. package/src/commands/__tests__/CommandRegistry.test.ts +287 -0
  15. package/src/commands/__tests__/ConfigCommandHandler.test.ts +844 -0
  16. package/src/commands/__tests__/EndpointCommandHandler.test.ts +426 -0
  17. package/src/commands/__tests__/McpCommandHandler.test.ts +753 -0
  18. package/src/commands/__tests__/ProjectCommandHandler.test.ts +230 -0
  19. package/src/commands/__tests__/ServiceCommands.integration.test.ts +408 -0
  20. package/src/commands/index.ts +351 -0
  21. package/src/errors/ErrorHandlers.ts +141 -0
  22. package/src/errors/ErrorMessages.ts +121 -0
  23. package/src/errors/__tests__/index.test.ts +186 -0
  24. package/src/errors/index.ts +163 -0
  25. package/src/global.d.ts +19 -0
  26. package/src/index.ts +53 -0
  27. package/src/interfaces/Command.ts +128 -0
  28. package/src/interfaces/CommandTypes.ts +95 -0
  29. package/src/interfaces/Config.ts +25 -0
  30. package/src/interfaces/Service.ts +99 -0
  31. package/src/services/DaemonManager.ts +318 -0
  32. package/src/services/ProcessManager.ts +235 -0
  33. package/src/services/ServiceManager.ts +319 -0
  34. package/src/services/TemplateManager.ts +382 -0
  35. package/src/services/__tests__/DaemonManager.test.ts +378 -0
  36. package/src/services/__tests__/DaemonMode.integration.test.ts +321 -0
  37. package/src/services/__tests__/ProcessManager.test.ts +296 -0
  38. package/src/services/__tests__/ServiceManager.test.ts +774 -0
  39. package/src/services/__tests__/TemplateManager.test.ts +337 -0
  40. package/src/types/backend.d.ts +48 -0
  41. package/src/utils/FileUtils.ts +320 -0
  42. package/src/utils/FormatUtils.ts +198 -0
  43. package/src/utils/PathUtils.ts +255 -0
  44. package/src/utils/PlatformUtils.ts +217 -0
  45. package/src/utils/Validation.ts +274 -0
  46. package/src/utils/VersionUtils.ts +141 -0
  47. package/src/utils/__tests__/FileUtils.test.ts +728 -0
  48. package/src/utils/__tests__/FormatUtils.test.ts +243 -0
  49. package/src/utils/__tests__/PathUtils.test.ts +1165 -0
  50. package/src/utils/__tests__/PlatformUtils.test.ts +723 -0
  51. package/src/utils/__tests__/Validation.test.ts +560 -0
  52. package/src/utils/__tests__/VersionUtils.test.ts +410 -0
  53. package/tsconfig.json +32 -0
  54. package/tsup.config.ts +100 -0
  55. package/vitest.config.ts +97 -0
@@ -0,0 +1,323 @@
1
+ /**
2
+ * CommandHandlerFactory 单元测试
3
+ */
4
+
5
+ import { beforeEach, describe, expect, it, vi } from "vitest";
6
+ import type { IDIContainer } from "../../interfaces/Config.js";
7
+ import { CommandHandlerFactory } from "../CommandHandlerFactory.js";
8
+
9
+ describe("CommandHandlerFactory", () => {
10
+ let mockContainer: IDIContainer;
11
+ let factory: CommandHandlerFactory;
12
+
13
+ beforeEach(() => {
14
+ mockContainer = {
15
+ register: vi.fn(),
16
+ get: vi.fn(),
17
+ has: vi.fn(),
18
+ };
19
+
20
+ factory = new CommandHandlerFactory(mockContainer);
21
+ vi.clearAllMocks();
22
+ });
23
+
24
+ describe("构造函数", () => {
25
+ it("应该正确初始化工厂实例", () => {
26
+ expect(factory).toBeInstanceOf(CommandHandlerFactory);
27
+ expect(factory).toBeDefined();
28
+ });
29
+
30
+ it("应该接受依赖注入容器", () => {
31
+ // 验证工厂可以被创建,说明容器参数被正确接受
32
+ expect(factory).toBeDefined();
33
+ });
34
+ });
35
+
36
+ describe("createHandler - 错误处理", () => {
37
+ it("对于未知类型应该抛出错误", () => {
38
+ expect(() => {
39
+ factory.createHandler("unknown");
40
+ }).toThrow("未知的命令处理器类型: unknown");
41
+ });
42
+
43
+ it("应该抛出包含具体类型信息的错误", () => {
44
+ try {
45
+ factory.createHandler("invalid_type");
46
+ expect.fail("应该抛出错误");
47
+ } catch (error) {
48
+ const err = error as Error;
49
+ expect(err).toBeInstanceOf(Error);
50
+ expect(err.message).toContain("invalid_type");
51
+ expect(err.message).toContain("未知的命令处理器类型");
52
+ }
53
+ });
54
+
55
+ it("应该处理空字符串类型", () => {
56
+ expect(() => {
57
+ factory.createHandler("");
58
+ }).toThrow("未知的命令处理器类型: ");
59
+ });
60
+
61
+ it("应该处理特殊字符类型", () => {
62
+ expect(() => {
63
+ factory.createHandler("@#$%");
64
+ }).toThrow("未知的命令处理器类型: @#$%");
65
+ });
66
+
67
+ it("应该处理数字类型字符串", () => {
68
+ expect(() => {
69
+ factory.createHandler("123");
70
+ }).toThrow("未知的命令处理器类型: 123");
71
+ });
72
+
73
+ it("应该处理混合大小写类型", () => {
74
+ expect(() => {
75
+ factory.createHandler("Service");
76
+ }).toThrow("未知的命令处理器类型: Service");
77
+ });
78
+
79
+ it("应该处理 null 和 undefined 类型", () => {
80
+ expect(() => {
81
+ factory.createHandler(null as any);
82
+ }).toThrow("未知的命令处理器类型: null");
83
+
84
+ expect(() => {
85
+ factory.createHandler(undefined as any);
86
+ }).toThrow("未知的命令处理器类型: undefined");
87
+ });
88
+
89
+ it("应该处理对象类型的参数", () => {
90
+ expect(() => {
91
+ factory.createHandler({} as any);
92
+ }).toThrow("未知的命令处理器类型: [object Object]");
93
+ });
94
+
95
+ it("应该处理数组类型的参数", () => {
96
+ expect(() => {
97
+ factory.createHandler([] as any);
98
+ }).toThrow("未知的命令处理器类型: ");
99
+ });
100
+
101
+ it("应该处理函数类型的参数", () => {
102
+ expect(() => {
103
+ factory.createHandler((() => {}) as any);
104
+ }).toThrow(/未知的命令处理器类型: /);
105
+ });
106
+ });
107
+
108
+ describe("错误处理的完整性", () => {
109
+ it("应该提供清晰的错误信息", () => {
110
+ const invalidTypes = [
111
+ "",
112
+ "nonexistent",
113
+ "123",
114
+ "Service",
115
+ null,
116
+ undefined,
117
+ ];
118
+
119
+ for (const type of invalidTypes) {
120
+ try {
121
+ factory.createHandler(type as any);
122
+ expect.fail(`应该为类型 ${type} 抛出错误`);
123
+ } catch (error) {
124
+ const err = error as Error;
125
+ expect(err).toBeInstanceOf(Error);
126
+ expect(err.message).toContain("未知的命令处理器类型");
127
+ expect(err.message).toContain(String(type));
128
+ }
129
+ }
130
+ });
131
+
132
+ it("应该保证错误信息的一致性", () => {
133
+ try {
134
+ factory.createHandler("test");
135
+ } catch (error1) {
136
+ try {
137
+ factory.createHandler("another");
138
+ } catch (error2) {
139
+ const err1 = error1 as Error;
140
+ const err2 = error2 as Error;
141
+ expect(err1.message).toContain("未知的命令处理器类型");
142
+ expect(err2.message).toContain("未知的命令处理器类型");
143
+ expect(err1.message.length).toBeGreaterThan(0);
144
+ expect(err2.message.length).toBeGreaterThan(0);
145
+ }
146
+ }
147
+ });
148
+ });
149
+
150
+ describe("依赖注入容器的使用", () => {
151
+ it("应该能够在不同容器实例间独立工作", () => {
152
+ const container1: IDIContainer = {
153
+ register: vi.fn(),
154
+ get: vi.fn(),
155
+ has: vi.fn(),
156
+ };
157
+
158
+ const container2: IDIContainer = {
159
+ register: vi.fn(),
160
+ get: vi.fn(),
161
+ has: vi.fn(),
162
+ };
163
+
164
+ const factory1 = new CommandHandlerFactory(container1);
165
+ const factory2 = new CommandHandlerFactory(container2);
166
+
167
+ expect(factory1).toBeDefined();
168
+ expect(factory2).toBeDefined();
169
+ expect(container1).not.toBe(container2);
170
+ });
171
+
172
+ it("应该保存容器的引用", () => {
173
+ // 验证工厂实例被正确创建,说明容器被保存
174
+ expect(factory).toBeDefined();
175
+ });
176
+ });
177
+
178
+ describe("createHandler 方法的参数验证", () => {
179
+ it("应该正确处理各种无效输入", () => {
180
+ const invalidInputs = [
181
+ null,
182
+ undefined,
183
+ "",
184
+ 123,
185
+ {},
186
+ [],
187
+ () => {},
188
+ true,
189
+ false,
190
+ 0,
191
+ -1,
192
+ Number.POSITIVE_INFINITY,
193
+ Number.NaN,
194
+ ];
195
+
196
+ for (const input of invalidInputs) {
197
+ expect(() => {
198
+ factory.createHandler(input as any);
199
+ }).toThrow();
200
+ }
201
+ });
202
+
203
+ it("应该为所有支持的处理器类型抛出模块不存在的错误", () => {
204
+ const supportedTypes = [
205
+ "service",
206
+ "config",
207
+ "project",
208
+ "mcp",
209
+ "endpoint",
210
+ ];
211
+
212
+ for (const type of supportedTypes) {
213
+ expect(() => {
214
+ factory.createHandler(type);
215
+ }).toThrow();
216
+ }
217
+ });
218
+ });
219
+
220
+ describe("工厂模式的核心逻辑", () => {
221
+ it("应该有正确的类型判断逻辑", () => {
222
+ // 测试 switch 语句的逻辑
223
+ const types = ["service", "config", "project", "mcp", "endpoint"];
224
+
225
+ for (const type of types) {
226
+ // 这些应该尝试加载模块(会失败,但不会在 switch 语句中抛出错误)
227
+ expect(() => {
228
+ try {
229
+ factory.createHandler(type);
230
+ } catch (error) {
231
+ // 错误应该来自模块加载,而不是类型判断
232
+ const err = error as Error;
233
+ expect(err.message).not.toContain("switch");
234
+ throw error;
235
+ }
236
+ }).toThrow();
237
+ }
238
+ });
239
+
240
+ it("应该为所有已知类型提供错误处理", () => {
241
+ const knownTypes = [
242
+ { type: "service", module: "ServiceCommandHandler.js" },
243
+ { type: "config", module: "ConfigCommandHandler.js" },
244
+ { type: "project", module: "ProjectCommandHandler.js" },
245
+ { type: "mcp", module: "McpCommandHandler.js" },
246
+ { type: "endpoint", module: "EndpointCommandHandler.js" },
247
+ ];
248
+
249
+ for (const { type, module } of knownTypes) {
250
+ try {
251
+ factory.createHandler(type);
252
+ } catch (error) {
253
+ // 错误应该是模块加载错误,而不是类型错误
254
+ const err = error as Error;
255
+ expect(err.message).toContain("Cannot find module");
256
+ expect(err.message).toContain(module);
257
+ }
258
+ }
259
+ });
260
+ });
261
+
262
+ describe("接口实现验证", () => {
263
+ it("应该实现 ICommandHandlerFactory 接口", () => {
264
+ expect(factory.createHandlers).toBeDefined();
265
+ expect(factory.createHandler).toBeDefined();
266
+ expect(typeof factory.createHandlers).toBe("function");
267
+ expect(typeof factory.createHandler).toBe("function");
268
+ });
269
+
270
+ it("应该有正确的方法签名", () => {
271
+ // createHandlers 不需要参数
272
+ expect(() => factory.createHandlers()).toThrow();
273
+
274
+ // createHandler 需要一个字符串参数
275
+ expect(() => factory.createHandler("test")).toThrow();
276
+ expect(() => factory.createHandler(undefined as any)).toThrow();
277
+ });
278
+ });
279
+
280
+ describe("错误边界", () => {
281
+ it("应该处理极端的输入情况", () => {
282
+ const extremeInputs = [
283
+ -0,
284
+ +0,
285
+ "",
286
+ " ",
287
+ "\t",
288
+ "\n",
289
+ "\r",
290
+ "\u0000",
291
+ "\uFFFF",
292
+ {},
293
+ { toString: () => "test" },
294
+ [],
295
+ [1, 2, 3],
296
+ new Date(0),
297
+ /regex/,
298
+ new Error("test"),
299
+ ];
300
+
301
+ for (const input of extremeInputs) {
302
+ expect(() => {
303
+ factory.createHandler(input as any);
304
+ }).toThrow();
305
+ }
306
+ });
307
+
308
+ it("应该为所有错误提供一致的错误格式", () => {
309
+ const testCases = ["", "test", "123", null, undefined];
310
+
311
+ for (const input of testCases) {
312
+ try {
313
+ factory.createHandler(input as any);
314
+ } catch (error) {
315
+ const err = error as Error;
316
+ expect(err).toBeInstanceOf(Error);
317
+ expect(err.message).toContain("未知的命令处理器类型");
318
+ expect(err.message.length).toBeGreaterThan(0);
319
+ }
320
+ }
321
+ });
322
+ });
323
+ });
@@ -0,0 +1,287 @@
1
+ import { Command } from "commander";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import type { IDIContainer } from "../../interfaces/Config";
4
+ import { CommandRegistry } from "../index";
5
+
6
+ // Mock ServiceCommandHandler
7
+ const mockServiceCommandHandler = {
8
+ name: "service",
9
+ description: "服务管理命令",
10
+ execute: vi.fn(),
11
+ subcommands: [
12
+ {
13
+ name: "start",
14
+ description: "启动服务",
15
+ options: [
16
+ { flags: "-d, --daemon", description: "后台运行模式" },
17
+ { flags: "-p, --port <port>", description: "指定端口" },
18
+ ],
19
+ execute: vi.fn(),
20
+ },
21
+ {
22
+ name: "stop",
23
+ description: "停止服务",
24
+ execute: vi.fn(),
25
+ },
26
+ {
27
+ name: "status",
28
+ description: "检查服务状态",
29
+ execute: vi.fn(),
30
+ },
31
+ ],
32
+ };
33
+
34
+ // Mock other command handlers
35
+ const mockConfigCommandHandler = {
36
+ name: "config",
37
+ description: "配置管理命令",
38
+ execute: vi.fn(),
39
+ subcommands: [],
40
+ };
41
+
42
+ const mockProjectCommandHandler = {
43
+ name: "project",
44
+ description: "项目管理命令",
45
+ execute: vi.fn(),
46
+ subcommands: [],
47
+ };
48
+
49
+ const mockMcpCommandHandler = {
50
+ name: "mcp",
51
+ description: "MCP 服务和工具管理",
52
+ execute: vi.fn(),
53
+ subcommands: [],
54
+ };
55
+
56
+ const mockEndpointCommandHandler = {
57
+ name: "endpoint",
58
+ description: "端点管理命令",
59
+ execute: vi.fn(),
60
+ subcommands: [],
61
+ };
62
+
63
+ // Mock CommandHandlerFactory
64
+ vi.mock("./CommandHandlerFactory.js", () => ({
65
+ CommandHandlerFactory: vi.fn().mockImplementation(() => ({
66
+ createHandlers: vi
67
+ .fn()
68
+ .mockReturnValue([
69
+ mockServiceCommandHandler,
70
+ mockConfigCommandHandler,
71
+ mockProjectCommandHandler,
72
+ mockMcpCommandHandler,
73
+ mockEndpointCommandHandler,
74
+ ]),
75
+ })),
76
+ }));
77
+
78
+ // Mock ErrorHandler
79
+ vi.mock("../errors/ErrorHandlers.js", () => ({
80
+ ErrorHandler: {
81
+ handle: vi.fn(),
82
+ },
83
+ }));
84
+
85
+ // Create mock DI container
86
+ const mockContainer: IDIContainer = {
87
+ get: vi.fn().mockImplementation(<T>(serviceName: string): T => {
88
+ switch (serviceName) {
89
+ case "versionUtils":
90
+ return {
91
+ getVersion: vi.fn().mockReturnValue("1.0.0"),
92
+ } as T;
93
+ default:
94
+ return {} as T;
95
+ }
96
+ }),
97
+ register: vi.fn(),
98
+ has: vi.fn(),
99
+ };
100
+
101
+ describe("CommandRegistry - 传统服务命令", () => {
102
+ let commandRegistry: CommandRegistry;
103
+ let program: Command;
104
+
105
+ beforeEach(() => {
106
+ commandRegistry = new CommandRegistry(mockContainer);
107
+ program = new Command();
108
+ vi.clearAllMocks();
109
+ });
110
+
111
+ afterEach(() => {
112
+ vi.restoreAllMocks();
113
+ });
114
+
115
+ describe("registerLegacyServiceCommands 注册传统服务命令", () => {
116
+ it("应注册传统服务命令并正确解析选项", async () => {
117
+ // 创建 handlers 数组
118
+ const handlers = [mockServiceCommandHandler];
119
+
120
+ // 注册命令
121
+ (commandRegistry as any).registerLegacyServiceCommands(program, handlers);
122
+
123
+ // 验证命令是否正确注册
124
+ const startCommand = program.commands.find(
125
+ (cmd) => cmd.name() === "start"
126
+ );
127
+ expect(startCommand).toBeDefined();
128
+ expect(startCommand!.description()).toBe("启动服务");
129
+
130
+ // 验证选项是否正确注册
131
+ const options = startCommand!.options;
132
+ expect(options.some((opt) => opt.flags === "-d, --daemon")).toBe(true);
133
+ expect(options.some((opt) => opt.flags === "-p, --port <port>")).toBe(
134
+ true
135
+ );
136
+ });
137
+
138
+ it("应注册所有服务子命令", async () => {
139
+ const handlers = [mockServiceCommandHandler];
140
+ (commandRegistry as any).registerLegacyServiceCommands(program, handlers);
141
+
142
+ // 验证所有子命令都被注册为顶级命令
143
+ const startCommand = program.commands.find(
144
+ (cmd) => cmd.name() === "start"
145
+ );
146
+ const stopCommand = program.commands.find((cmd) => cmd.name() === "stop");
147
+ const statusCommand = program.commands.find(
148
+ (cmd) => cmd.name() === "status"
149
+ );
150
+
151
+ expect(startCommand).toBeDefined();
152
+ expect(stopCommand).toBeDefined();
153
+ expect(statusCommand).toBeDefined();
154
+
155
+ expect(startCommand!.description()).toBe("启动服务");
156
+ expect(stopCommand!.description()).toBe("停止服务");
157
+ expect(statusCommand!.description()).toBe("检查服务状态");
158
+ });
159
+
160
+ it("应处理没有服务处理器的情况", async () => {
161
+ const handlers = [mockConfigCommandHandler]; // 没有服务处理器
162
+ (commandRegistry as any).registerLegacyServiceCommands(program, handlers);
163
+
164
+ // 验证没有注册任何服务命令
165
+ const startCommand = program.commands.find(
166
+ (cmd) => cmd.name() === "start"
167
+ );
168
+ expect(startCommand).toBeUndefined();
169
+ });
170
+
171
+ it("应处理服务处理器没有子命令的情况", async () => {
172
+ const handlerWithoutSubcommands = {
173
+ ...mockServiceCommandHandler,
174
+ subcommands: undefined,
175
+ };
176
+ const handlers = [handlerWithoutSubcommands];
177
+
178
+ (commandRegistry as any).registerLegacyServiceCommands(program, handlers);
179
+
180
+ // 验证没有注册任何服务命令
181
+ const startCommand = program.commands.find(
182
+ (cmd) => cmd.name() === "start"
183
+ );
184
+ expect(startCommand).toBeUndefined();
185
+ });
186
+ });
187
+
188
+ describe("服务命令业务逻辑测试", () => {
189
+ it("应正确执行 start 命令并传递选项", async () => {
190
+ const startSubcommand = mockServiceCommandHandler.subcommands[0];
191
+
192
+ await startSubcommand.execute([], {
193
+ daemon: true,
194
+ port: "3000",
195
+ });
196
+
197
+ expect(
198
+ mockServiceCommandHandler.subcommands[0].execute
199
+ ).toHaveBeenCalledWith([], {
200
+ daemon: true,
201
+ port: "3000",
202
+ });
203
+ });
204
+
205
+ it("应正确执行 start 命令并传递位置参数", async () => {
206
+ const startSubcommand = mockServiceCommandHandler.subcommands[0];
207
+
208
+ await startSubcommand.execute(["arg1", "arg2"], {
209
+ daemon: true,
210
+ });
211
+
212
+ expect(
213
+ mockServiceCommandHandler.subcommands[0].execute
214
+ ).toHaveBeenCalledWith(["arg1", "arg2"], {
215
+ daemon: true,
216
+ });
217
+ });
218
+
219
+ it("应正确执行 stop 命令", async () => {
220
+ const stopSubcommand = mockServiceCommandHandler.subcommands[1];
221
+
222
+ await stopSubcommand.execute([], {});
223
+
224
+ expect(
225
+ mockServiceCommandHandler.subcommands[1].execute
226
+ ).toHaveBeenCalledWith([], {});
227
+ });
228
+
229
+ it("应正确执行 status 命令", async () => {
230
+ const statusSubcommand = mockServiceCommandHandler.subcommands[2];
231
+
232
+ await statusSubcommand.execute([], {});
233
+
234
+ expect(
235
+ mockServiceCommandHandler.subcommands[2].execute
236
+ ).toHaveBeenCalledWith([], {});
237
+ });
238
+ });
239
+
240
+ describe("选项解析边界情况", () => {
241
+ it("应正确处理布尔标志", async () => {
242
+ const startSubcommand = mockServiceCommandHandler.subcommands[0];
243
+
244
+ await startSubcommand.execute([], {
245
+ daemon: true,
246
+ verbose: false,
247
+ help: undefined,
248
+ });
249
+
250
+ expect(
251
+ mockServiceCommandHandler.subcommands[0].execute
252
+ ).toHaveBeenCalledWith([], {
253
+ daemon: true,
254
+ verbose: false,
255
+ help: undefined,
256
+ });
257
+ });
258
+
259
+ it("应正确处理字符串选项", async () => {
260
+ const startSubcommand = mockServiceCommandHandler.subcommands[0];
261
+
262
+ await startSubcommand.execute([], {
263
+ port: "8080",
264
+ config: "/path/to/config",
265
+ mode: "production",
266
+ });
267
+
268
+ expect(
269
+ mockServiceCommandHandler.subcommands[0].execute
270
+ ).toHaveBeenCalledWith([], {
271
+ port: "8080",
272
+ config: "/path/to/config",
273
+ mode: "production",
274
+ });
275
+ });
276
+
277
+ it("应正确处理空选项对象", async () => {
278
+ const startSubcommand = mockServiceCommandHandler.subcommands[0];
279
+
280
+ await startSubcommand.execute([], {});
281
+
282
+ expect(
283
+ mockServiceCommandHandler.subcommands[0].execute
284
+ ).toHaveBeenCalledWith([], {});
285
+ });
286
+ });
287
+ });