@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,274 @@
1
+ /**
2
+ * 输入验证工具
3
+ */
4
+
5
+ import type { ConfigFormat } from "../Types";
6
+ import { ValidationError } from "../errors/index";
7
+
8
+ /**
9
+ * 验证工具类
10
+ */
11
+ export class Validation {
12
+ /**
13
+ * 验证端口号
14
+ */
15
+ static validatePort(port: number): void {
16
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
17
+ throw ValidationError.invalidPort(port);
18
+ }
19
+ }
20
+
21
+ /**
22
+ * 验证配置文件格式
23
+ */
24
+ static validateConfigFormat(format: string): ConfigFormat {
25
+ const validFormats: ConfigFormat[] = ["json", "json5", "jsonc"];
26
+
27
+ if (!validFormats.includes(format as ConfigFormat)) {
28
+ throw new ValidationError(
29
+ `无效的配置文件格式: ${format},支持的格式: ${validFormats.join(", ")}`,
30
+ "format"
31
+ );
32
+ }
33
+
34
+ return format as ConfigFormat;
35
+ }
36
+
37
+ /**
38
+ * 验证必填字段
39
+ */
40
+ static validateRequired(value: any, fieldName: string): void {
41
+ if (value === undefined || value === null || value === "") {
42
+ throw ValidationError.requiredField(fieldName);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * 验证字符串长度
48
+ */
49
+ static validateStringLength(
50
+ value: string,
51
+ fieldName: string,
52
+ options: { min?: number; max?: number } = {}
53
+ ): void {
54
+ if (options.min !== undefined && value.length < options.min) {
55
+ throw new ValidationError(
56
+ `长度不能少于 ${options.min} 个字符,当前长度: ${value.length}`,
57
+ fieldName
58
+ );
59
+ }
60
+
61
+ if (options.max !== undefined && value.length > options.max) {
62
+ throw new ValidationError(
63
+ `长度不能超过 ${options.max} 个字符,当前长度: ${value.length}`,
64
+ fieldName
65
+ );
66
+ }
67
+ }
68
+
69
+ /**
70
+ * 验证 URL 格式
71
+ */
72
+ static validateUrl(url: string, fieldName = "url"): void {
73
+ try {
74
+ new URL(url);
75
+ } catch {
76
+ throw new ValidationError(`无效的 URL 格式: ${url}`, fieldName);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * 验证 WebSocket URL 格式
82
+ */
83
+ static validateWebSocketUrl(url: string, fieldName = "websocket_url"): void {
84
+ Validation.validateUrl(url, fieldName);
85
+
86
+ const parsedUrl = new URL(url);
87
+ if (!["ws:", "wss:"].includes(parsedUrl.protocol)) {
88
+ throw new ValidationError(
89
+ `WebSocket URL 必须使用 ws:// 或 wss:// 协议,当前协议: ${parsedUrl.protocol}`,
90
+ fieldName
91
+ );
92
+ }
93
+ }
94
+
95
+ /**
96
+ * 验证 HTTP URL 格式
97
+ */
98
+ static validateHttpUrl(url: string, fieldName = "http_url"): void {
99
+ Validation.validateUrl(url, fieldName);
100
+
101
+ const parsedUrl = new URL(url);
102
+ if (!["http:", "https:"].includes(parsedUrl.protocol)) {
103
+ throw new ValidationError(
104
+ `HTTP URL 必须使用 http:// 或 https:// 协议,当前协议: ${parsedUrl.protocol}`,
105
+ fieldName
106
+ );
107
+ }
108
+ }
109
+
110
+ /**
111
+ * 验证项目名称
112
+ */
113
+ static validateProjectName(name: string): void {
114
+ Validation.validateRequired(name, "projectName");
115
+ Validation.validateStringLength(name, "projectName", { min: 1, max: 100 });
116
+
117
+ // 检查是否包含非法字符
118
+ const invalidChars = /[<>:"/\\|?*]/;
119
+ const hasControlChars = name
120
+ .split("")
121
+ .some((char) => char.charCodeAt(0) < 32);
122
+
123
+ if (invalidChars.test(name) || hasControlChars) {
124
+ throw new ValidationError(
125
+ '项目名称不能包含以下字符: < > : " / \\ | ? * 以及控制字符',
126
+ "projectName"
127
+ );
128
+ }
129
+
130
+ // 检查是否以点开头
131
+ if (name.startsWith(".")) {
132
+ throw new ValidationError("项目名称不能以点开头", "projectName");
133
+ }
134
+ }
135
+
136
+ /**
137
+ * 验证模板名称
138
+ */
139
+ static validateTemplateName(name: string): void {
140
+ Validation.validateRequired(name, "templateName");
141
+ Validation.validateStringLength(name, "templateName", { min: 1, max: 50 });
142
+
143
+ // 模板名称只能包含字母、数字、连字符和下划线
144
+ const validPattern = /^[a-zA-Z0-9_-]+$/;
145
+ if (!validPattern.test(name)) {
146
+ throw new ValidationError(
147
+ "模板名称只能包含字母、数字、连字符和下划线",
148
+ "templateName"
149
+ );
150
+ }
151
+ }
152
+
153
+ /**
154
+ * 验证环境变量名称
155
+ */
156
+ static validateEnvVarName(name: string): void {
157
+ Validation.validateRequired(name, "envVarName");
158
+
159
+ // 环境变量名称只能包含大写字母、数字和下划线,且不能以数字开头
160
+ const validPattern = /^[A-Z_][A-Z0-9_]*$/;
161
+ if (!validPattern.test(name)) {
162
+ throw new ValidationError(
163
+ "环境变量名称只能包含大写字母、数字和下划线,且不能以数字开头",
164
+ "envVarName"
165
+ );
166
+ }
167
+ }
168
+
169
+ /**
170
+ * 验证 JSON 格式
171
+ */
172
+ static validateJson(jsonString: string, fieldName = "json"): any {
173
+ try {
174
+ return JSON.parse(jsonString);
175
+ } catch (error) {
176
+ throw new ValidationError(
177
+ `无效的 JSON 格式: ${error instanceof Error ? error.message : String(error)}`,
178
+ fieldName
179
+ );
180
+ }
181
+ }
182
+
183
+ /**
184
+ * 验证数字范围
185
+ */
186
+ static validateNumberRange(
187
+ value: number,
188
+ fieldName: string,
189
+ options: { min?: number; max?: number } = {}
190
+ ): void {
191
+ if (options.min !== undefined && value < options.min) {
192
+ throw new ValidationError(
193
+ `值不能小于 ${options.min},当前值: ${value}`,
194
+ fieldName
195
+ );
196
+ }
197
+
198
+ if (options.max !== undefined && value > options.max) {
199
+ throw new ValidationError(
200
+ `值不能大于 ${options.max},当前值: ${value}`,
201
+ fieldName
202
+ );
203
+ }
204
+ }
205
+
206
+ /**
207
+ * 验证数组长度
208
+ */
209
+ static validateArrayLength(
210
+ array: any[],
211
+ fieldName: string,
212
+ options: { min?: number; max?: number } = {}
213
+ ): void {
214
+ if (options.min !== undefined && array.length < options.min) {
215
+ throw new ValidationError(
216
+ `数组长度不能少于 ${options.min},当前长度: ${array.length}`,
217
+ fieldName
218
+ );
219
+ }
220
+
221
+ if (options.max !== undefined && array.length > options.max) {
222
+ throw new ValidationError(
223
+ `数组长度不能超过 ${options.max},当前长度: ${array.length}`,
224
+ fieldName
225
+ );
226
+ }
227
+ }
228
+
229
+ /**
230
+ * 验证对象属性
231
+ */
232
+ static validateObjectProperties(
233
+ obj: Record<string, any>,
234
+ requiredProps: string[],
235
+ fieldName = "object"
236
+ ): void {
237
+ for (const prop of requiredProps) {
238
+ if (!(prop in obj)) {
239
+ throw new ValidationError(`缺少必需的属性: ${prop}`, fieldName);
240
+ }
241
+ }
242
+ }
243
+
244
+ /**
245
+ * 验证枚举值
246
+ */
247
+ static validateEnum<T extends string>(
248
+ value: string,
249
+ validValues: T[],
250
+ fieldName: string
251
+ ): T {
252
+ if (!validValues.includes(value as T)) {
253
+ throw new ValidationError(
254
+ `无效的值: ${value},有效值: ${validValues.join(", ")}`,
255
+ fieldName
256
+ );
257
+ }
258
+ return value as T;
259
+ }
260
+
261
+ /**
262
+ * 验证正则表达式
263
+ */
264
+ static validateRegex(pattern: string, fieldName = "regex"): RegExp {
265
+ try {
266
+ return new RegExp(pattern);
267
+ } catch (error) {
268
+ throw new ValidationError(
269
+ `无效的正则表达式: ${error instanceof Error ? error.message : String(error)}`,
270
+ fieldName
271
+ );
272
+ }
273
+ }
274
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * 版本管理工具
3
+ */
4
+
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { FileError } from "../errors/index";
9
+
10
+ /**
11
+ * 版本信息接口
12
+ */
13
+ export interface VersionInfo {
14
+ version: string;
15
+ name?: string;
16
+ description?: string;
17
+ author?: string;
18
+ }
19
+
20
+ /**
21
+ * 版本工具类
22
+ */
23
+ export class VersionUtils {
24
+ private static cachedVersion: string | null = null;
25
+
26
+ /**
27
+ * 获取版本号
28
+ */
29
+ static getVersion(): string {
30
+ if (VersionUtils.cachedVersion) {
31
+ return VersionUtils.cachedVersion;
32
+ }
33
+
34
+ try {
35
+ // 在 ES 模块环境中获取当前目录
36
+ const __filename = fileURLToPath(import.meta.url);
37
+ const currentDir = path.dirname(__filename);
38
+
39
+ // 尝试多个可能的 package.json 路径
40
+ const possiblePaths = [
41
+ // 构建后环境:dist/cli.js -> dist/package.json (优先)
42
+ path.join(currentDir, "package.json"),
43
+ // 构建后环境:dist/cli.js -> package.json
44
+ path.join(currentDir, "..", "package.json"),
45
+ // 开发环境:src/cli/utils/VersionUtils.ts -> package.json
46
+ path.join(currentDir, "..", "..", "..", "package.json"),
47
+ // 全局安装环境
48
+ path.join(currentDir, "..", "..", "..", "..", "package.json"),
49
+ ];
50
+
51
+ for (const packagePath of possiblePaths) {
52
+ if (fs.existsSync(packagePath)) {
53
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
54
+ if (packageJson.version) {
55
+ VersionUtils.cachedVersion = packageJson.version;
56
+ return packageJson.version;
57
+ }
58
+ }
59
+ }
60
+
61
+ // 如果都找不到,返回默认版本
62
+ VersionUtils.cachedVersion = "unknown";
63
+ return "unknown";
64
+ } catch (error) {
65
+ console.warn("无法从 package.json 读取版本信息:", error);
66
+ VersionUtils.cachedVersion = "unknown";
67
+ return "unknown";
68
+ }
69
+ }
70
+
71
+ /**
72
+ * 获取完整版本信息
73
+ */
74
+ static getVersionInfo(): VersionInfo {
75
+ try {
76
+ const __filename = fileURLToPath(import.meta.url);
77
+ const currentDir = path.dirname(__filename);
78
+
79
+ const possiblePaths = [
80
+ // 构建后环境:dist/cli.js -> dist/package.json (优先)
81
+ path.join(currentDir, "package.json"),
82
+ // 构建后环境:dist/cli.js -> package.json
83
+ path.join(currentDir, "..", "package.json"),
84
+ // 开发环境:src/cli/utils/VersionUtils.ts -> package.json
85
+ path.join(currentDir, "..", "..", "..", "package.json"),
86
+ // 全局安装环境
87
+ path.join(currentDir, "..", "..", "..", "..", "package.json"),
88
+ ];
89
+
90
+ for (const packagePath of possiblePaths) {
91
+ if (fs.existsSync(packagePath)) {
92
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
93
+ return {
94
+ version: packageJson.version || "unknown",
95
+ name: packageJson.name,
96
+ description: packageJson.description,
97
+ author: packageJson.author,
98
+ };
99
+ }
100
+ }
101
+
102
+ return { version: "unknown" };
103
+ } catch (error) {
104
+ throw new FileError("无法读取版本信息", "package.json");
105
+ }
106
+ }
107
+
108
+ /**
109
+ * 比较版本号
110
+ */
111
+ static compareVersions(version1: string, version2: string): number {
112
+ const v1Parts = version1.split(".").map(Number);
113
+ const v2Parts = version2.split(".").map(Number);
114
+ const maxLength = Math.max(v1Parts.length, v2Parts.length);
115
+
116
+ for (let i = 0; i < maxLength; i++) {
117
+ const v1Part = v1Parts[i] || 0;
118
+ const v2Part = v2Parts[i] || 0;
119
+
120
+ if (v1Part > v2Part) return 1;
121
+ if (v1Part < v2Part) return -1;
122
+ }
123
+
124
+ return 0;
125
+ }
126
+
127
+ /**
128
+ * 检查版本是否有效
129
+ */
130
+ static isValidVersion(version: string): boolean {
131
+ const versionRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/;
132
+ return versionRegex.test(version);
133
+ }
134
+
135
+ /**
136
+ * 清除版本缓存
137
+ */
138
+ static clearCache(): void {
139
+ VersionUtils.cachedVersion = null;
140
+ }
141
+ }