@xiaozhi-client/version 1.9.7-beta.21

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 shenjingnan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,72 @@
1
+ /**
2
+ * 版本号常量(构建时注入)
3
+ *
4
+ * 如果构建时能读取到 package.json,则为真实版本号
5
+ * 否则为占位符,运行时从 package.json 读取
6
+ */
7
+ declare const VERSION: string;
8
+ declare const APP_NAME: string;
9
+
10
+ /**
11
+ * 版本管理工具
12
+ *
13
+ * 提供版本号获取、比较、验证等功能
14
+ */
15
+ /**
16
+ * 版本信息接口
17
+ */
18
+ interface VersionInfo {
19
+ version: string;
20
+ name?: string;
21
+ description?: string;
22
+ author?: string;
23
+ }
24
+ /**
25
+ * 版本工具类
26
+ */
27
+ declare class VersionUtils {
28
+ private static cachedVersion;
29
+ private static cachedVersionInfo;
30
+ /**
31
+ * 获取版本号
32
+ *
33
+ * 优先使用构建时注入的版本号常量
34
+ * 如果是占位符,则运行时从 package.json 读取
35
+ */
36
+ static getVersion(): string;
37
+ /**
38
+ * 获取完整版本信息
39
+ */
40
+ static getVersionInfo(): VersionInfo;
41
+ /**
42
+ * 比较版本号
43
+ *
44
+ * @param version1 第一个版本号
45
+ * @param version2 第二个版本号
46
+ * @returns 返回值:1 表示 version1 > version2,-1 表示 version1 < version2,0 表示相等
47
+ */
48
+ static compareVersions(version1: string, version2: string): number;
49
+ /**
50
+ * 检查版本是否有效
51
+ *
52
+ * @param version 版本号字符串
53
+ * @returns 是否为有效的语义化版本号
54
+ */
55
+ static isValidVersion(version: string): boolean;
56
+ /**
57
+ * 运行时从 package.json 读取版本号
58
+ */
59
+ private static getRuntimeVersion;
60
+ /**
61
+ * 运行时从 package.json 读取完整版本信息
62
+ */
63
+ private static getRuntimeVersionInfo;
64
+ /**
65
+ * 清除版本缓存
66
+ *
67
+ * 主要用于测试场景
68
+ */
69
+ static clearCache(): void;
70
+ }
71
+
72
+ export { APP_NAME, VERSION, type VersionInfo, VersionUtils };
package/dist/index.js ADDED
@@ -0,0 +1,158 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/version-constants.ts
5
+ var VERSION = "1.9.7-beta.21";
6
+ var APP_NAME = "xiaozhi-client";
7
+
8
+ // src/VersionUtils.ts
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import { fileURLToPath } from "url";
12
+ var VersionUtils = class _VersionUtils {
13
+ static {
14
+ __name(this, "VersionUtils");
15
+ }
16
+ static cachedVersion = null;
17
+ static cachedVersionInfo = null;
18
+ /**
19
+ * 获取版本号
20
+ *
21
+ * 优先使用构建时注入的版本号常量
22
+ * 如果是占位符,则运行时从 package.json 读取
23
+ */
24
+ static getVersion() {
25
+ if (VERSION === "__VERSION__") {
26
+ return _VersionUtils.getRuntimeVersion();
27
+ }
28
+ return VERSION;
29
+ }
30
+ /**
31
+ * 获取完整版本信息
32
+ */
33
+ static getVersionInfo() {
34
+ if (_VersionUtils.cachedVersionInfo) {
35
+ return _VersionUtils.cachedVersionInfo;
36
+ }
37
+ if (VERSION === "__VERSION__") {
38
+ _VersionUtils.cachedVersionInfo = _VersionUtils.getRuntimeVersionInfo();
39
+ return _VersionUtils.cachedVersionInfo;
40
+ }
41
+ _VersionUtils.cachedVersionInfo = {
42
+ version: VERSION,
43
+ name: APP_NAME === "__APP_NAME__" ? void 0 : APP_NAME
44
+ };
45
+ return _VersionUtils.cachedVersionInfo;
46
+ }
47
+ /**
48
+ * 比较版本号
49
+ *
50
+ * @param version1 第一个版本号
51
+ * @param version2 第二个版本号
52
+ * @returns 返回值:1 表示 version1 > version2,-1 表示 version1 < version2,0 表示相等
53
+ */
54
+ static compareVersions(version1, version2) {
55
+ const v1Parts = version1.split(".").map(Number);
56
+ const v2Parts = version2.split(".").map(Number);
57
+ const maxLength = Math.max(v1Parts.length, v2Parts.length);
58
+ for (let i = 0; i < maxLength; i++) {
59
+ const v1Part = v1Parts[i] || 0;
60
+ const v2Part = v2Parts[i] || 0;
61
+ if (v1Part > v2Part) return 1;
62
+ if (v1Part < v2Part) return -1;
63
+ }
64
+ return 0;
65
+ }
66
+ /**
67
+ * 检查版本是否有效
68
+ *
69
+ * @param version 版本号字符串
70
+ * @returns 是否为有效的语义化版本号
71
+ */
72
+ static isValidVersion(version) {
73
+ const versionRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/;
74
+ return versionRegex.test(version);
75
+ }
76
+ /**
77
+ * 运行时从 package.json 读取版本号
78
+ */
79
+ static getRuntimeVersion() {
80
+ if (_VersionUtils.cachedVersion) {
81
+ return _VersionUtils.cachedVersion;
82
+ }
83
+ try {
84
+ const __filename = fileURLToPath(import.meta.url);
85
+ const currentDir = path.dirname(__filename);
86
+ const possiblePaths = [
87
+ // 从 packages/version/dist/version/index.js 到项目根目录的 package.json
88
+ path.join(currentDir, "..", "..", "..", "package.json"),
89
+ // 从 dist/version/index.js 到项目根目录的 package.json
90
+ path.join(currentDir, "..", "..", "package.json"),
91
+ // 全局安装环境
92
+ path.join(currentDir, "..", "..", "..", "..", "package.json")
93
+ ];
94
+ for (const packagePath of possiblePaths) {
95
+ if (fs.existsSync(packagePath)) {
96
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
97
+ if (packageJson.version) {
98
+ _VersionUtils.cachedVersion = packageJson.version;
99
+ return packageJson.version;
100
+ }
101
+ }
102
+ }
103
+ _VersionUtils.cachedVersion = "unknown";
104
+ return "unknown";
105
+ } catch (error) {
106
+ console.warn("\u65E0\u6CD5\u4ECE package.json \u8BFB\u53D6\u7248\u672C\u4FE1\u606F:", error);
107
+ _VersionUtils.cachedVersion = "unknown";
108
+ return "unknown";
109
+ }
110
+ }
111
+ /**
112
+ * 运行时从 package.json 读取完整版本信息
113
+ */
114
+ static getRuntimeVersionInfo() {
115
+ try {
116
+ const __filename = fileURLToPath(import.meta.url);
117
+ const currentDir = path.dirname(__filename);
118
+ const possiblePaths = [
119
+ // 从 packages/version/dist/version/index.js 到项目根目录的 package.json
120
+ path.join(currentDir, "..", "..", "..", "package.json"),
121
+ // 从 dist/version/index.js 到项目根目录的 package.json
122
+ path.join(currentDir, "..", "..", "package.json"),
123
+ // 全局安装环境
124
+ path.join(currentDir, "..", "..", "..", "..", "package.json")
125
+ ];
126
+ for (const packagePath of possiblePaths) {
127
+ if (fs.existsSync(packagePath)) {
128
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
129
+ return {
130
+ version: packageJson.version || "unknown",
131
+ name: packageJson.name,
132
+ description: packageJson.description,
133
+ author: packageJson.author
134
+ };
135
+ }
136
+ }
137
+ return { version: "unknown" };
138
+ } catch (error) {
139
+ console.warn("\u65E0\u6CD5\u8BFB\u53D6\u7248\u672C\u4FE1\u606F:", error);
140
+ return { version: "unknown" };
141
+ }
142
+ }
143
+ /**
144
+ * 清除版本缓存
145
+ *
146
+ * 主要用于测试场景
147
+ */
148
+ static clearCache() {
149
+ _VersionUtils.cachedVersion = null;
150
+ _VersionUtils.cachedVersionInfo = null;
151
+ }
152
+ };
153
+ export {
154
+ APP_NAME,
155
+ VERSION,
156
+ VersionUtils
157
+ };
158
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/version-constants.ts","../src/VersionUtils.ts"],"sourcesContent":["/**\n * 版本号常量(构建时注入)\n *\n * 如果构建时能读取到 package.json,则为真实版本号\n * 否则为占位符,运行时从 package.json 读取\n */\nexport const VERSION = __VERSION__;\nexport const APP_NAME = __APP_NAME__;\n","/**\n * 版本管理工具\n *\n * 提供版本号获取、比较、验证等功能\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { VERSION, APP_NAME } from \"./version-constants.js\";\n\n/**\n * 版本信息接口\n */\nexport interface VersionInfo {\n version: string;\n name?: string;\n description?: string;\n author?: string;\n}\n\n/**\n * 版本工具类\n */\nexport class VersionUtils {\n private static cachedVersion: string | null = null;\n private static cachedVersionInfo: VersionInfo | null = null;\n\n /**\n * 获取版本号\n *\n * 优先使用构建时注入的版本号常量\n * 如果是占位符,则运行时从 package.json 读取\n */\n static getVersion(): string {\n // 如果版本号是占位符,则运行时读取\n if (VERSION === \"__VERSION__\") {\n return VersionUtils.getRuntimeVersion();\n }\n\n // 使用构建时注入的版本号\n return VERSION;\n }\n\n /**\n * 获取完整版本信息\n */\n static getVersionInfo(): VersionInfo {\n // 如果有缓存,直接返回\n if (VersionUtils.cachedVersionInfo) {\n return VersionUtils.cachedVersionInfo;\n }\n\n // 如果版本号是占位符,则运行时读取\n if (VERSION === \"__VERSION__\") {\n VersionUtils.cachedVersionInfo = VersionUtils.getRuntimeVersionInfo();\n return VersionUtils.cachedVersionInfo;\n }\n\n // 使用构建时注入的版本号\n VersionUtils.cachedVersionInfo = {\n version: VERSION,\n name: APP_NAME === \"__APP_NAME__\" ? undefined : APP_NAME,\n };\n\n return VersionUtils.cachedVersionInfo;\n }\n\n /**\n * 比较版本号\n *\n * @param version1 第一个版本号\n * @param version2 第二个版本号\n * @returns 返回值:1 表示 version1 > version2,-1 表示 version1 < version2,0 表示相等\n */\n static compareVersions(version1: string, version2: string): number {\n const v1Parts = version1.split(\".\").map(Number);\n const v2Parts = version2.split(\".\").map(Number);\n const maxLength = Math.max(v1Parts.length, v2Parts.length);\n\n for (let i = 0; i < maxLength; i++) {\n const v1Part = v1Parts[i] || 0;\n const v2Part = v2Parts[i] || 0;\n\n if (v1Part > v2Part) return 1;\n if (v1Part < v2Part) return -1;\n }\n\n return 0;\n }\n\n /**\n * 检查版本是否有效\n *\n * @param version 版本号字符串\n * @returns 是否为有效的语义化版本号\n */\n static isValidVersion(version: string): boolean {\n // 支持语义化版本号:1.2.3 或 1.2.3-alpha.1 或 1.2.3-beta.1+build.123\n const versionRegex = /^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?(\\+[a-zA-Z0-9.-]+)?$/;\n return versionRegex.test(version);\n }\n\n /**\n * 运行时从 package.json 读取版本号\n */\n private static getRuntimeVersion(): string {\n // 如果有缓存,直接返回\n if (VersionUtils.cachedVersion) {\n return VersionUtils.cachedVersion;\n }\n\n try {\n // 在 ES 模块环境中获取当前目录\n const __filename = fileURLToPath(import.meta.url);\n const currentDir = path.dirname(__filename);\n\n // 尝试多个可能的 package.json 路径\n const possiblePaths = [\n // 从 packages/version/dist/version/index.js 到项目根目录的 package.json\n path.join(currentDir, \"..\", \"..\", \"..\", \"package.json\"),\n // 从 dist/version/index.js 到项目根目录的 package.json\n path.join(currentDir, \"..\", \"..\", \"package.json\"),\n // 全局安装环境\n path.join(currentDir, \"..\", \"..\", \"..\", \"..\", \"package.json\"),\n ];\n\n for (const packagePath of possiblePaths) {\n if (fs.existsSync(packagePath)) {\n const packageJson = JSON.parse(fs.readFileSync(packagePath, \"utf8\"));\n if (packageJson.version) {\n VersionUtils.cachedVersion = packageJson.version;\n return packageJson.version;\n }\n }\n }\n\n // 如果都找不到,返回默认版本\n VersionUtils.cachedVersion = \"unknown\";\n return \"unknown\";\n } catch (error) {\n console.warn(\"无法从 package.json 读取版本信息:\", error);\n VersionUtils.cachedVersion = \"unknown\";\n return \"unknown\";\n }\n }\n\n /**\n * 运行时从 package.json 读取完整版本信息\n */\n private static getRuntimeVersionInfo(): VersionInfo {\n try {\n const __filename = fileURLToPath(import.meta.url);\n const currentDir = path.dirname(__filename);\n\n const possiblePaths = [\n // 从 packages/version/dist/version/index.js 到项目根目录的 package.json\n path.join(currentDir, \"..\", \"..\", \"..\", \"package.json\"),\n // 从 dist/version/index.js 到项目根目录的 package.json\n path.join(currentDir, \"..\", \"..\", \"package.json\"),\n // 全局安装环境\n path.join(currentDir, \"..\", \"..\", \"..\", \"..\", \"package.json\"),\n ];\n\n for (const packagePath of possiblePaths) {\n if (fs.existsSync(packagePath)) {\n const packageJson = JSON.parse(fs.readFileSync(packagePath, \"utf8\"));\n return {\n version: packageJson.version || \"unknown\",\n name: packageJson.name,\n description: packageJson.description,\n author: packageJson.author,\n };\n }\n }\n\n return { version: \"unknown\" };\n } catch (error) {\n console.warn(\"无法读取版本信息:\", error);\n return { version: \"unknown\" };\n }\n }\n\n /**\n * 清除版本缓存\n *\n * 主要用于测试场景\n */\n static clearCache(): void {\n VersionUtils.cachedVersion = null;\n VersionUtils.cachedVersionInfo = null;\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@xiaozhi-client/version",
3
+ "version": "1.9.7-beta.21",
4
+ "private": false,
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/shenjingnan/xiaozhi-client.git",
8
+ "directory": "packages/version"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "dependencies": {},
23
+ "devDependencies": {
24
+ "@types/node": "^24.3.0",
25
+ "typescript": "^5.9.2",
26
+ "vitest": "^3.0.5"
27
+ },
28
+ "nx": {
29
+ "targets": {
30
+ "build": {
31
+ "dependsOn": [
32
+ "^build"
33
+ ],
34
+ "outputs": [
35
+ "{projectRoot}/dist"
36
+ ]
37
+ },
38
+ "test": {
39
+ "outputs": [
40
+ "{workspaceRoot}/coverage/version"
41
+ ]
42
+ }
43
+ }
44
+ },
45
+ "scripts": {
46
+ "build": "tsup",
47
+ "test": "vitest",
48
+ "type-check": "tsc --noEmit"
49
+ }
50
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * 版本管理工具
3
+ *
4
+ * 提供版本号获取、比较、验证等功能
5
+ */
6
+
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { VERSION, APP_NAME } from "./version-constants.js";
11
+
12
+ /**
13
+ * 版本信息接口
14
+ */
15
+ export interface VersionInfo {
16
+ version: string;
17
+ name?: string;
18
+ description?: string;
19
+ author?: string;
20
+ }
21
+
22
+ /**
23
+ * 版本工具类
24
+ */
25
+ export class VersionUtils {
26
+ private static cachedVersion: string | null = null;
27
+ private static cachedVersionInfo: VersionInfo | null = null;
28
+
29
+ /**
30
+ * 获取版本号
31
+ *
32
+ * 优先使用构建时注入的版本号常量
33
+ * 如果是占位符,则运行时从 package.json 读取
34
+ */
35
+ static getVersion(): string {
36
+ // 如果版本号是占位符,则运行时读取
37
+ if (VERSION === "__VERSION__") {
38
+ return VersionUtils.getRuntimeVersion();
39
+ }
40
+
41
+ // 使用构建时注入的版本号
42
+ return VERSION;
43
+ }
44
+
45
+ /**
46
+ * 获取完整版本信息
47
+ */
48
+ static getVersionInfo(): VersionInfo {
49
+ // 如果有缓存,直接返回
50
+ if (VersionUtils.cachedVersionInfo) {
51
+ return VersionUtils.cachedVersionInfo;
52
+ }
53
+
54
+ // 如果版本号是占位符,则运行时读取
55
+ if (VERSION === "__VERSION__") {
56
+ VersionUtils.cachedVersionInfo = VersionUtils.getRuntimeVersionInfo();
57
+ return VersionUtils.cachedVersionInfo;
58
+ }
59
+
60
+ // 使用构建时注入的版本号
61
+ VersionUtils.cachedVersionInfo = {
62
+ version: VERSION,
63
+ name: APP_NAME === "__APP_NAME__" ? undefined : APP_NAME,
64
+ };
65
+
66
+ return VersionUtils.cachedVersionInfo;
67
+ }
68
+
69
+ /**
70
+ * 比较版本号
71
+ *
72
+ * @param version1 第一个版本号
73
+ * @param version2 第二个版本号
74
+ * @returns 返回值:1 表示 version1 > version2,-1 表示 version1 < version2,0 表示相等
75
+ */
76
+ static compareVersions(version1: string, version2: string): number {
77
+ const v1Parts = version1.split(".").map(Number);
78
+ const v2Parts = version2.split(".").map(Number);
79
+ const maxLength = Math.max(v1Parts.length, v2Parts.length);
80
+
81
+ for (let i = 0; i < maxLength; i++) {
82
+ const v1Part = v1Parts[i] || 0;
83
+ const v2Part = v2Parts[i] || 0;
84
+
85
+ if (v1Part > v2Part) return 1;
86
+ if (v1Part < v2Part) return -1;
87
+ }
88
+
89
+ return 0;
90
+ }
91
+
92
+ /**
93
+ * 检查版本是否有效
94
+ *
95
+ * @param version 版本号字符串
96
+ * @returns 是否为有效的语义化版本号
97
+ */
98
+ static isValidVersion(version: string): boolean {
99
+ // 支持语义化版本号:1.2.3 或 1.2.3-alpha.1 或 1.2.3-beta.1+build.123
100
+ const versionRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/;
101
+ return versionRegex.test(version);
102
+ }
103
+
104
+ /**
105
+ * 运行时从 package.json 读取版本号
106
+ */
107
+ private static getRuntimeVersion(): string {
108
+ // 如果有缓存,直接返回
109
+ if (VersionUtils.cachedVersion) {
110
+ return VersionUtils.cachedVersion;
111
+ }
112
+
113
+ try {
114
+ // 在 ES 模块环境中获取当前目录
115
+ const __filename = fileURLToPath(import.meta.url);
116
+ const currentDir = path.dirname(__filename);
117
+
118
+ // 尝试多个可能的 package.json 路径
119
+ const possiblePaths = [
120
+ // 从 packages/version/dist/version/index.js 到项目根目录的 package.json
121
+ path.join(currentDir, "..", "..", "..", "package.json"),
122
+ // 从 dist/version/index.js 到项目根目录的 package.json
123
+ path.join(currentDir, "..", "..", "package.json"),
124
+ // 全局安装环境
125
+ path.join(currentDir, "..", "..", "..", "..", "package.json"),
126
+ ];
127
+
128
+ for (const packagePath of possiblePaths) {
129
+ if (fs.existsSync(packagePath)) {
130
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
131
+ if (packageJson.version) {
132
+ VersionUtils.cachedVersion = packageJson.version;
133
+ return packageJson.version;
134
+ }
135
+ }
136
+ }
137
+
138
+ // 如果都找不到,返回默认版本
139
+ VersionUtils.cachedVersion = "unknown";
140
+ return "unknown";
141
+ } catch (error) {
142
+ console.warn("无法从 package.json 读取版本信息:", error);
143
+ VersionUtils.cachedVersion = "unknown";
144
+ return "unknown";
145
+ }
146
+ }
147
+
148
+ /**
149
+ * 运行时从 package.json 读取完整版本信息
150
+ */
151
+ private static getRuntimeVersionInfo(): VersionInfo {
152
+ try {
153
+ const __filename = fileURLToPath(import.meta.url);
154
+ const currentDir = path.dirname(__filename);
155
+
156
+ const possiblePaths = [
157
+ // 从 packages/version/dist/version/index.js 到项目根目录的 package.json
158
+ path.join(currentDir, "..", "..", "..", "package.json"),
159
+ // 从 dist/version/index.js 到项目根目录的 package.json
160
+ path.join(currentDir, "..", "..", "package.json"),
161
+ // 全局安装环境
162
+ path.join(currentDir, "..", "..", "..", "..", "package.json"),
163
+ ];
164
+
165
+ for (const packagePath of possiblePaths) {
166
+ if (fs.existsSync(packagePath)) {
167
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
168
+ return {
169
+ version: packageJson.version || "unknown",
170
+ name: packageJson.name,
171
+ description: packageJson.description,
172
+ author: packageJson.author,
173
+ };
174
+ }
175
+ }
176
+
177
+ return { version: "unknown" };
178
+ } catch (error) {
179
+ console.warn("无法读取版本信息:", error);
180
+ return { version: "unknown" };
181
+ }
182
+ }
183
+
184
+ /**
185
+ * 清除版本缓存
186
+ *
187
+ * 主要用于测试场景
188
+ */
189
+ static clearCache(): void {
190
+ VersionUtils.cachedVersion = null;
191
+ VersionUtils.cachedVersionInfo = null;
192
+ }
193
+ }
@@ -0,0 +1,225 @@
1
+ /**
2
+ * VersionUtils 单元测试
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, vi } from "vitest";
6
+ import { VersionUtils } from "../VersionUtils.js";
7
+
8
+ describe("VersionUtils", () => {
9
+ beforeEach(() => {
10
+ // 清除缓存以确保每个测试独立运行
11
+ VersionUtils.clearCache();
12
+ });
13
+
14
+ describe("getVersion", () => {
15
+ it("应该返回版本号字符串", () => {
16
+ const version = VersionUtils.getVersion();
17
+ expect(typeof version).toBe("string");
18
+ expect(version.length).toBeGreaterThan(0);
19
+ });
20
+
21
+ it("应该返回有效的版本号格式", () => {
22
+ const version = VersionUtils.getVersion();
23
+ // 版本号应该是数字点分隔格式,或者 "unknown"
24
+ const isValid =
25
+ version === "unknown" || /^\d+\.\d+\.\d+/.test(version);
26
+ expect(isValid).toBe(true);
27
+ });
28
+
29
+ it("多次调用应该返回相同结果(缓存生效)", () => {
30
+ const version1 = VersionUtils.getVersion();
31
+ const version2 = VersionUtils.getVersion();
32
+ expect(version1).toBe(version2);
33
+ });
34
+
35
+ it("清除缓存后应该重新读取版本号", () => {
36
+ const version1 = VersionUtils.getVersion();
37
+ VersionUtils.clearCache();
38
+ const version2 = VersionUtils.getVersion();
39
+ expect(version1).toBe(version2);
40
+ });
41
+ });
42
+
43
+ describe("getVersionInfo", () => {
44
+ it("应该返回包含版本号的对象", () => {
45
+ const info = VersionUtils.getVersionInfo();
46
+ expect(info).toHaveProperty("version");
47
+ expect(typeof info.version).toBe("string");
48
+ });
49
+
50
+ it("应该可能包含应用名称", () => {
51
+ const info = VersionUtils.getVersionInfo();
52
+ // name 可能存在也可能不存在,取决于构建配置
53
+ if (info.name) {
54
+ expect(typeof info.name).toBe("string");
55
+ }
56
+ });
57
+
58
+ it("应该可能包含描述信息", () => {
59
+ const info = VersionUtils.getVersionInfo();
60
+ // description 可能存在也可能不存在
61
+ if (info.description) {
62
+ expect(typeof info.description).toBe("string");
63
+ }
64
+ });
65
+
66
+ it("应该可能包含作者信息", () => {
67
+ const info = VersionUtils.getVersionInfo();
68
+ // author 可能存在也可能不存在
69
+ if (info.author) {
70
+ expect(typeof info.author).toBe("string" || typeof info.author === "object");
71
+ }
72
+ });
73
+
74
+ it("多次调用应该返回相同结果(缓存生效)", () => {
75
+ const info1 = VersionUtils.getVersionInfo();
76
+ const info2 = VersionUtils.getVersionInfo();
77
+ expect(info1).toEqual(info2);
78
+ });
79
+ });
80
+
81
+ describe("compareVersions", () => {
82
+ it("应该正确比较相等的版本号", () => {
83
+ const result = VersionUtils.compareVersions("1.0.0", "1.0.0");
84
+ expect(result).toBe(0);
85
+ });
86
+
87
+ it("应该正确比较主版本号差异", () => {
88
+ const result = VersionUtils.compareVersions("2.0.0", "1.0.0");
89
+ expect(result).toBe(1);
90
+ });
91
+
92
+ it("应该正确比较次版本号差异", () => {
93
+ const result = VersionUtils.compareVersions("1.2.0", "1.1.0");
94
+ expect(result).toBe(1);
95
+ });
96
+
97
+ it("应该正确比较补丁版本号差异", () => {
98
+ const result = VersionUtils.compareVersions("1.0.1", "1.0.0");
99
+ expect(result).toBe(1);
100
+ });
101
+
102
+ it("应该正确处理小于的情况", () => {
103
+ const result = VersionUtils.compareVersions("1.0.0", "2.0.0");
104
+ expect(result).toBe(-1);
105
+ });
106
+
107
+ it("应该正确处理不同长度的版本号", () => {
108
+ const result = VersionUtils.compareVersions("1.0", "1.0.0");
109
+ expect(result).toBe(0);
110
+ });
111
+
112
+ it("应该正确处理较短的版本号", () => {
113
+ const result = VersionUtils.compareVersions("1.0.0", "1.0");
114
+ expect(result).toBe(0);
115
+ });
116
+
117
+ it("应该正确处理带预发布标签的版本号", () => {
118
+ // 注意:当前实现不处理预发布标签,仅比较数字部分
119
+ const result = VersionUtils.compareVersions("1.0.0-alpha", "1.0.0");
120
+ // 预发布标签会被 split(".") 处理,可能产生非数字部分
121
+ // 这里测试数字部分的比较
122
+ expect(typeof result).toBe("number");
123
+ });
124
+
125
+ it("应该正确处理复杂版本号比较", () => {
126
+ expect(VersionUtils.compareVersions("1.2.3", "1.2.4")).toBe(-1);
127
+ expect(VersionUtils.compareVersions("1.2.4", "1.2.3")).toBe(1);
128
+ expect(VersionUtils.compareVersions("2.0.0", "1.9.9")).toBe(1);
129
+ expect(VersionUtils.compareVersions("0.1.0", "0.0.9")).toBe(1);
130
+ });
131
+ });
132
+
133
+ describe("isValidVersion", () => {
134
+ it("应该接受有效的标准版本号", () => {
135
+ expect(VersionUtils.isValidVersion("1.0.0")).toBe(true);
136
+ expect(VersionUtils.isValidVersion("0.0.1")).toBe(true);
137
+ expect(VersionUtils.isValidVersion("10.20.30")).toBe(true);
138
+ });
139
+
140
+ it("应该接受带预发布标签的版本号", () => {
141
+ expect(VersionUtils.isValidVersion("1.0.0-alpha")).toBe(true);
142
+ expect(VersionUtils.isValidVersion("1.0.0-beta.1")).toBe(true);
143
+ expect(VersionUtils.isValidVersion("1.0.0-rc.1")).toBe(true);
144
+ expect(VersionUtils.isValidVersion("2.0.0-alpha.1.beta.2")).toBe(true);
145
+ });
146
+
147
+ it("应该接受带构建元数据的版本号", () => {
148
+ expect(VersionUtils.isValidVersion("1.0.0+build.1")).toBe(true);
149
+ expect(VersionUtils.isValidVersion("1.0.0+20130313144700")).toBe(true);
150
+ });
151
+
152
+ it("应该接受带预发布和构建元数据的版本号", () => {
153
+ expect(VersionUtils.isValidVersion("1.0.0-alpha+001")).toBe(true);
154
+ expect(VersionUtils.isValidVersion("1.0.0-beta+exp.sha.5114f85")).toBe(true);
155
+ });
156
+
157
+ it("应该拒绝无效的版本号", () => {
158
+ expect(VersionUtils.isValidVersion("1")).toBe(false);
159
+ expect(VersionUtils.isValidVersion("1.0")).toBe(false);
160
+ expect(VersionUtils.isValidVersion("v1.0.0")).toBe(false);
161
+ expect(VersionUtils.isValidVersion("")).toBe(false);
162
+ expect(VersionUtils.isValidVersion("invalid")).toBe(false);
163
+ expect(VersionUtils.isValidVersion("1.0.0-")).toBe(false); // 只有连字符
164
+ });
165
+
166
+ it("应该拒绝边界情况", () => {
167
+ expect(VersionUtils.isValidVersion(".1.0.0")).toBe(false);
168
+ expect(VersionUtils.isValidVersion("1.0.0.")).toBe(false);
169
+ expect(VersionUtils.isValidVersion("..")).toBe(false);
170
+ });
171
+ });
172
+
173
+ describe("clearCache", () => {
174
+ it("应该清除版本缓存", () => {
175
+ // 获取版本号(建立缓存)
176
+ const version1 = VersionUtils.getVersion();
177
+
178
+ // 清除缓存
179
+ VersionUtils.clearCache();
180
+
181
+ // 再次获取版本号
182
+ const version2 = VersionUtils.getVersion();
183
+
184
+ // 应该返回相同值,但是重新读取的
185
+ expect(version1).toBe(version2);
186
+ });
187
+
188
+ it("应该清除版本信息缓存", () => {
189
+ // 获取版本信息(建立缓存)
190
+ const info1 = VersionUtils.getVersionInfo();
191
+
192
+ // 清除缓存
193
+ VersionUtils.clearCache();
194
+
195
+ // 再次获取版本信息
196
+ const info2 = VersionUtils.getVersionInfo();
197
+
198
+ // 应该返回相同值,但是重新读取的
199
+ expect(info1).toEqual(info2);
200
+ });
201
+ });
202
+
203
+ describe("边界情况测试", () => {
204
+ it("应该处理 getRuntimeVersion 的错误情况", () => {
205
+ // 清除缓存
206
+ VersionUtils.clearCache();
207
+
208
+ // 获取版本号,即使在某些环境中无法读取 package.json
209
+ // 也应该返回 "unknown" 或有效的版本号
210
+ const version = VersionUtils.getVersion();
211
+ expect(["unknown", "1.9.7"].includes(version) || /^\d+\.\d+\.\d+/.test(version)).toBe(true);
212
+ });
213
+
214
+ it("应该处理 getRuntimeVersionInfo 的错误情况", () => {
215
+ // 清除缓存
216
+ VersionUtils.clearCache();
217
+
218
+ // 获取版本信息,即使在某些环境中无法读取 package.json
219
+ // 也应该返回包含至少 version 字段的对象
220
+ const info = VersionUtils.getVersionInfo();
221
+ expect(info).toHaveProperty("version");
222
+ expect(typeof info.version).toBe("string");
223
+ });
224
+ });
225
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 全局常量类型声明
3
+ *
4
+ * 这些常量由构建工具在构建时注入
5
+ */
6
+ declare const __VERSION__: string;
7
+ declare const __APP_NAME__: string;
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @xiaozhi-client/version
3
+ *
4
+ * 小智客户端版本管理工具
5
+ *
6
+ * 提供版本号获取、比较、验证等功能
7
+ */
8
+
9
+ export { VERSION, APP_NAME } from "./version-constants.js";
10
+ export { VersionUtils } from "./VersionUtils.js";
11
+ export type { VersionInfo } from "./VersionUtils.js";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 版本号常量(构建时注入)
3
+ *
4
+ * 如果构建时能读取到 package.json,则为真实版本号
5
+ * 否则为占位符,运行时从 package.json 读取
6
+ */
7
+ export const VERSION = __VERSION__;
8
+ export const APP_NAME = __APP_NAME__;
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "allowSyntheticDefaultImports": true,
7
+ "esModuleInterop": true,
8
+ "allowJs": false,
9
+ "outDir": "../../dist/version",
10
+ "strict": true,
11
+ "noImplicitAny": true,
12
+ "strictNullChecks": true,
13
+ "strictFunctionTypes": true,
14
+ "noImplicitReturns": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "noImplicitOverride": true,
17
+ "useUnknownInCatchVariables": true,
18
+ "forceConsistentCasingInFileNames": true,
19
+ "declaration": true,
20
+ "declarationMap": true,
21
+ "sourceMap": true,
22
+ "skipLibCheck": true,
23
+ "resolveJsonModule": true
24
+ },
25
+ "include": ["src/**/*"],
26
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
27
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,48 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { defineConfig } from "tsup";
5
+
6
+ // 获取当前文件所在目录
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+
9
+ // 读取根目录 package.json 获取版本号
10
+ const rootPkgPath = resolve(__dirname, "../../package.json");
11
+ const pkg = JSON.parse(readFileSync(rootPkgPath, "utf-8"));
12
+
13
+ export default defineConfig({
14
+ entry: {
15
+ index: resolve(__dirname, "src/index.ts"),
16
+ },
17
+ format: ["esm"],
18
+ target: "node18",
19
+ outDir: "dist",
20
+ clean: true,
21
+ sourcemap: true,
22
+ dts: true,
23
+ minify: false,
24
+ splitting: false,
25
+ bundle: true,
26
+ keepNames: true,
27
+ platform: "node",
28
+ // 使用版本包自己的 tsconfig.json
29
+ tsconfig: resolve(__dirname, "tsconfig.json"),
30
+ esbuildOptions: (options) => {
31
+ options.resolveExtensions = [".ts", ".js", ".json"];
32
+
33
+ // 构建时注入版本号常量
34
+ // 如果构建时能读取到版本号,则注入真实值
35
+ // 否则注入占位符,运行时从 package.json 读取
36
+ const versionValue = pkg.version || "__VERSION__";
37
+ const appNameValue = pkg.name || "__APP_NAME__";
38
+
39
+ options.define = {
40
+ ...options.define,
41
+ __VERSION__: JSON.stringify(versionValue),
42
+ __APP_NAME__: JSON.stringify(appNameValue),
43
+ };
44
+ },
45
+ outExtension: () => ({
46
+ js: ".js",
47
+ }),
48
+ });
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ },
7
+ esbuild: {
8
+ define: {
9
+ __VERSION__: JSON.stringify("1.9.7"),
10
+ __APP_NAME__: JSON.stringify("xiaozhi-client"),
11
+ },
12
+ },
13
+ });