@lmcc-dev/mult-fetch-mcp-server 1.1.0 → 1.2.1
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/README.md +8 -8
- package/README.zh.md +16 -8
- package/dist/i18n-test-report.json +8 -0
- package/dist/i18n-unused-keys-report.json +8 -0
- package/dist/src/client.js +70 -37
- package/dist/src/index.js +0 -0
- package/dist/src/lib/BrowserFetcher.js +12 -12
- package/dist/src/lib/NodeFetcher.js +22 -22
- package/dist/src/lib/fetch.js +40 -0
- package/dist/src/lib/fetchers/FetcherFactory.js +43 -0
- package/dist/src/lib/fetchers/browser/BrowserFetcher.js +381 -0
- package/dist/src/lib/fetchers/browser/BrowserInstance.js +180 -0
- package/dist/src/lib/fetchers/browser/PageOperations.js +168 -0
- package/dist/src/lib/fetchers/common/types.js +41 -0
- package/dist/src/lib/fetchers/common/utils.js +141 -0
- package/dist/src/lib/fetchers/index.js +62 -0
- package/dist/src/lib/fetchers/node/HttpClient.js +239 -0
- package/dist/src/lib/fetchers/node/NodeFetcher.js +180 -0
- package/dist/src/lib/i18n/index.js +105 -17
- package/dist/src/lib/i18n/keys/base.js +41 -0
- package/dist/src/lib/i18n/keys/browser.js +91 -0
- package/dist/src/lib/i18n/keys/client.js +68 -0
- package/dist/src/lib/i18n/keys/errors.js +49 -0
- package/dist/src/lib/i18n/keys/factory.js +46 -0
- package/dist/src/lib/i18n/keys/fetcher.js +87 -0
- package/dist/src/lib/i18n/keys/index.js +16 -0
- package/dist/src/lib/i18n/keys/node.js +69 -0
- package/dist/src/lib/i18n/keys/prompts.js +79 -0
- package/dist/src/lib/i18n/keys/resources.js +53 -0
- package/dist/src/lib/i18n/keys/server.js +67 -0
- package/dist/src/lib/i18n/keys/tools.js +39 -0
- package/dist/src/lib/i18n/keys.js +488 -119
- package/dist/src/lib/i18n/locales/en/browser.js +67 -57
- package/dist/src/lib/i18n/locales/en/client.js +57 -33
- package/dist/src/lib/i18n/locales/en/errors.js +36 -12
- package/dist/src/lib/i18n/locales/en/factory.js +38 -0
- package/dist/src/lib/i18n/locales/en/fetcher.js +8 -2
- package/dist/src/lib/i18n/locales/en/index.js +10 -8
- package/dist/src/lib/i18n/locales/en/node.js +57 -35
- package/dist/src/lib/i18n/locales/en/prompts.js +42 -65
- package/dist/src/lib/i18n/locales/en/resources.js +52 -55
- package/dist/src/lib/i18n/locales/en/server.js +49 -24
- package/dist/src/lib/i18n/locales/en/tools.js +27 -19
- package/dist/src/lib/i18n/locales/zh/browser.js +71 -62
- package/dist/src/lib/i18n/locales/zh/client.js +57 -32
- package/dist/src/lib/i18n/locales/zh/errors.js +36 -12
- package/dist/src/lib/i18n/locales/zh/factory.js +38 -0
- package/dist/src/lib/i18n/locales/zh/fetcher.js +36 -30
- package/dist/src/lib/i18n/locales/zh/index.js +10 -8
- package/dist/src/lib/i18n/locales/zh/node.js +61 -36
- package/dist/src/lib/i18n/locales/zh/prompts.js +35 -57
- package/dist/src/lib/i18n/locales/zh/resources.js +52 -55
- package/dist/src/lib/i18n/locales/zh/server.js +51 -25
- package/dist/src/lib/i18n/locales/zh/tools.js +27 -19
- package/dist/src/lib/i18n/logger.js +90 -19
- package/dist/src/lib/logger.js +135 -14
- package/dist/src/lib/server/browser.js +42 -10
- package/dist/src/lib/server/fetcher.js +87 -37
- package/dist/src/lib/server/index.js +2 -2
- package/dist/src/lib/server/prompts.js +1 -1
- package/dist/src/lib/server/resources.js +1 -1
- package/dist/src/lib/server/tools.js +46 -10
- package/dist/src/lib/utils/ErrorHandler.js +125 -0
- package/dist/src/mcp-server.js +0 -0
- package/dist/src/test-i18n.js +139 -0
- package/dist/tests/BrowserFetcher.test.js +0 -0
- package/dist/tests/NodeFetcher.test.js +0 -0
- package/dist/tests/client.test.js +42 -81
- package/dist/tests/fetch.test.js +70 -0
- package/dist/tests/fetchers/Fetcher.test.js +108 -0
- package/dist/tests/fetchers/browser/BrowserFetcher.test.js +195 -0
- package/dist/tests/fetchers/browser/BrowserInstance.test.js +163 -0
- package/dist/tests/fetchers/node/NodeFetcher.test.js +211 -0
- package/dist/tests/i18n-missing-keys.js +364 -0
- package/dist/tests/i18n-remove-unused-keys.js +236 -0
- package/dist/tests/i18n-test-report.json +2004 -0
- package/dist/tests/i18n-unused-keys.js +425 -0
- package/dist/tests/i18n.test.js +6 -7
- package/dist/tests/logger.test.js +14 -13
- package/dist/tests/mcp-server.test.js +7 -7
- package/dist/tests/server/browser.test.js +25 -21
- package/dist/tests/server/fetcher.test.js +58 -101
- package/dist/tests/server/index.test.js +30 -30
- package/dist/tests/server/prompts.test.js +36 -33
- package/dist/tests/server/resources.test.js +120 -84
- package/dist/tests/server/tools.test.js +7 -6
- package/dist/tests/setup.js +36 -0
- package/dist/tests/src/lib/i18n/index.js +108 -0
- package/dist/tests/src/lib/i18n/keys/base.js +47 -0
- package/dist/tests/src/lib/i18n/keys/browser.js +93 -0
- package/dist/tests/src/lib/i18n/keys/client.js +70 -0
- package/dist/tests/src/lib/i18n/keys/errors.js +34 -0
- package/dist/tests/src/lib/i18n/keys/fetcher.js +84 -0
- package/dist/tests/src/lib/i18n/keys/index.js +31 -0
- package/dist/tests/src/lib/i18n/keys/node.js +56 -0
- package/dist/tests/src/lib/i18n/keys/prompts.js +82 -0
- package/dist/tests/src/lib/i18n/keys/resources.js +50 -0
- package/dist/tests/src/lib/i18n/keys/server.js +64 -0
- package/dist/tests/src/lib/i18n/keys/tools.js +34 -0
- package/dist/tests/src/lib/i18n/locales/en/browser.js +88 -0
- package/dist/tests/src/lib/i18n/locales/en/client.js +66 -0
- package/dist/tests/src/lib/i18n/locales/en/errors.js +28 -0
- package/dist/tests/src/lib/i18n/locales/en/fetcher.js +71 -0
- package/dist/tests/src/lib/i18n/locales/en/index.js +29 -0
- package/dist/tests/src/lib/i18n/locales/en/node.js +51 -0
- package/dist/tests/src/lib/i18n/locales/en/prompts.js +52 -0
- package/dist/tests/src/lib/i18n/locales/en/resources.js +50 -0
- package/dist/tests/src/lib/i18n/locales/en/server.js +56 -0
- package/dist/tests/src/lib/i18n/locales/en/tools.js +28 -0
- package/dist/tests/src/lib/i18n/locales/zh/browser.js +87 -0
- package/dist/tests/src/lib/i18n/locales/zh/client.js +66 -0
- package/dist/tests/src/lib/i18n/locales/zh/errors.js +28 -0
- package/dist/tests/src/lib/i18n/locales/zh/fetcher.js +71 -0
- package/dist/tests/src/lib/i18n/locales/zh/index.js +29 -0
- package/dist/tests/src/lib/i18n/locales/zh/node.js +51 -0
- package/dist/tests/src/lib/i18n/locales/zh/prompts.js +53 -0
- package/dist/tests/src/lib/i18n/locales/zh/resources.js +50 -0
- package/dist/tests/src/lib/i18n/locales/zh/server.js +57 -0
- package/dist/tests/src/lib/i18n/locales/zh/tools.js +28 -0
- package/dist/tests/src/lib/i18n/logger.js +114 -0
- package/dist/tests/src/lib/logger.js +181 -0
- package/dist/tests/test-direct-client.js +0 -0
- package/dist/tests/test-i18n.js +530 -0
- package/dist/tests/test-mcp-methods.js +1 -1
- package/dist/tests/test-mcp.js +0 -0
- package/dist/tests/test-mini4k.js +0 -0
- package/dist/tests/tests/test-i18n.js +588 -0
- package/dist/tests/types.test.js +1 -1
- package/dist/tests/utils/ErrorHandler.test.js +144 -0
- package/dist/vitest.config.js +29 -0
- package/package.json +31 -27
- package/dist/src/cli-language.js +0 -78
- package/dist/src/client.d.ts +0 -6
- package/dist/src/client.js.map +0 -1
- package/dist/src/index.d.ts +0 -15
- package/dist/src/index.js.map +0 -1
- package/dist/src/lib/BrowserFetcher.d.ts +0 -175
- package/dist/src/lib/BrowserFetcher.js.map +0 -1
- package/dist/src/lib/NodeFetcher.d.ts +0 -92
- package/dist/src/lib/NodeFetcher.js.map +0 -1
- package/dist/src/lib/example.js +0 -93
- package/dist/src/lib/i18n/index.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/browser.d.ts +0 -61
- package/dist/src/lib/i18n/locales/en/browser.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/client.d.ts +0 -39
- package/dist/src/lib/i18n/locales/en/client.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/errors.d.ts +0 -17
- package/dist/src/lib/i18n/locales/en/errors.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/fetcher.d.ts +0 -66
- package/dist/src/lib/i18n/locales/en/fetcher.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/index.d.ts +0 -344
- package/dist/src/lib/i18n/locales/en/index.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/node.d.ts +0 -41
- package/dist/src/lib/i18n/locales/en/node.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/prompts.d.ts +0 -65
- package/dist/src/lib/i18n/locales/en/prompts.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/resources.d.ts +0 -41
- package/dist/src/lib/i18n/locales/en/resources.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/server.d.ts +0 -30
- package/dist/src/lib/i18n/locales/en/server.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/tools.d.ts +0 -24
- package/dist/src/lib/i18n/locales/en/tools.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/browser.d.ts +0 -61
- package/dist/src/lib/i18n/locales/zh/browser.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/client.d.ts +0 -38
- package/dist/src/lib/i18n/locales/zh/client.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/errors.d.ts +0 -17
- package/dist/src/lib/i18n/locales/zh/errors.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/fetcher.d.ts +0 -66
- package/dist/src/lib/i18n/locales/zh/fetcher.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/index.d.ts +0 -343
- package/dist/src/lib/i18n/locales/zh/index.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/node.d.ts +0 -41
- package/dist/src/lib/i18n/locales/zh/node.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/prompts.d.ts +0 -65
- package/dist/src/lib/i18n/locales/zh/prompts.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/resources.d.ts +0 -41
- package/dist/src/lib/i18n/locales/zh/resources.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/server.d.ts +0 -30
- package/dist/src/lib/i18n/locales/zh/server.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/tools.d.ts +0 -24
- package/dist/src/lib/i18n/locales/zh/tools.js.map +0 -1
- package/dist/src/lib/i18n/logger.d.ts +0 -38
- package/dist/src/lib/i18n/logger.js.map +0 -1
- package/dist/src/lib/logger.d.ts +0 -32
- package/dist/src/lib/logger.js.map +0 -1
- package/dist/src/lib/server/browser.d.ts +0 -34
- package/dist/src/lib/server/browser.js.map +0 -1
- package/dist/src/lib/server/fetcher.d.ts +0 -20
- package/dist/src/lib/server/fetcher.js.map +0 -1
- package/dist/src/lib/server/index.d.ts +0 -10
- package/dist/src/lib/server/index.js.map +0 -1
- package/dist/src/lib/server/logger.js +0 -23
- package/dist/src/lib/server/prompts.d.ts +0 -11
- package/dist/src/lib/server/prompts.js.map +0 -1
- package/dist/src/lib/server/resources.d.ts +0 -11
- package/dist/src/lib/server/resources.js.map +0 -1
- package/dist/src/lib/server/tools.d.ts +0 -12
- package/dist/src/lib/server/tools.js.map +0 -1
- package/dist/src/lib/server/types.d.ts +0 -71
- package/dist/src/lib/server/types.js.map +0 -1
- package/dist/src/lib/types.d.ts +0 -119
- package/dist/src/lib/types.js.map +0 -1
- package/dist/src/mcp-server.d.ts +0 -6
- package/dist/src/mcp-server.js.map +0 -1
- package/dist/tests/jest.setup.d.ts +0 -6
- package/dist/tests/jest.setup.js +0 -30
- package/dist/tests/jest.setup.js.map +0 -1
- package/dist/tests/test-direct-client.d.ts +0 -6
- package/dist/tests/test-direct-client.js.map +0 -1
- package/dist/tests/test-mcp-methods.d.ts +0 -6
- package/dist/tests/test-mcp-methods.js.map +0 -1
- package/dist/tests/test-mcp.d.ts +0 -6
- package/dist/tests/test-mcp.js.map +0 -1
- package/dist/tests/test-mini4k.d.ts +0 -6
- package/dist/tests/test-mini4k.js.map +0 -1
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author: Martin <lmccc.dev@gmail.com>
|
|
3
|
+
* Co-Author: AI Assistant (Claude)
|
|
4
|
+
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
5
|
+
*/
|
|
6
|
+
// 检测未使用的国际化键 (Detect unused internationalization keys)
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { enTranslation } from '../src/lib/i18n/locales/en/index.js';
|
|
11
|
+
import { zhTranslation } from '../src/lib/i18n/locales/zh/index.js';
|
|
12
|
+
import * as allKeys from '../src/lib/i18n/keys/index.js';
|
|
13
|
+
// 获取当前文件的目录路径 (Get the directory path of the current file)
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
// 默认测试配置 (Default test configuration)
|
|
17
|
+
const defaultConfig = {
|
|
18
|
+
generateReport: true,
|
|
19
|
+
reportPath: path.join(__dirname, '../i18n-unused-keys-report.json'),
|
|
20
|
+
verbose: false,
|
|
21
|
+
srcDir: path.join(__dirname, '../src'),
|
|
22
|
+
excludeDirs: ['node_modules', 'dist', 'build', 'coverage'],
|
|
23
|
+
fileExtensions: ['.ts', '.tsx', '.js', '.jsx'],
|
|
24
|
+
preserveErrorKeys: true, // 默认保留错误相关的键 (Preserve error-related keys by default)
|
|
25
|
+
reportOnly: false, // 默认不只生成报告 (Don't only generate report by default)
|
|
26
|
+
strictMode: true // 默认使用严格模式 (Use strict mode by default)
|
|
27
|
+
};
|
|
28
|
+
// 解析命令行参数 (Parse command line arguments)
|
|
29
|
+
function parseCommandLineArgs() {
|
|
30
|
+
const config = { ...defaultConfig };
|
|
31
|
+
const args = process.argv.slice(2);
|
|
32
|
+
for (let i = 0; i < args.length; i++) {
|
|
33
|
+
const arg = args[i];
|
|
34
|
+
switch (arg) {
|
|
35
|
+
case '--no-report':
|
|
36
|
+
config.generateReport = false;
|
|
37
|
+
break;
|
|
38
|
+
case '--report-path':
|
|
39
|
+
if (i + 1 < args.length) {
|
|
40
|
+
config.reportPath = args[++i];
|
|
41
|
+
}
|
|
42
|
+
break;
|
|
43
|
+
case '--verbose':
|
|
44
|
+
config.verbose = true;
|
|
45
|
+
break;
|
|
46
|
+
case '--src-dir':
|
|
47
|
+
if (i + 1 < args.length) {
|
|
48
|
+
config.srcDir = args[++i];
|
|
49
|
+
}
|
|
50
|
+
break;
|
|
51
|
+
case '--exclude-dirs':
|
|
52
|
+
if (i + 1 < args.length) {
|
|
53
|
+
config.excludeDirs = args[++i].split(',');
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
case '--file-extensions':
|
|
57
|
+
if (i + 1 < args.length) {
|
|
58
|
+
config.fileExtensions = args[++i].split(',');
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
case '--no-preserve-error-keys':
|
|
62
|
+
config.preserveErrorKeys = false;
|
|
63
|
+
break;
|
|
64
|
+
case '--report-only':
|
|
65
|
+
config.reportOnly = true;
|
|
66
|
+
break;
|
|
67
|
+
case '--no-strict-mode':
|
|
68
|
+
config.strictMode = false;
|
|
69
|
+
break;
|
|
70
|
+
case '--help':
|
|
71
|
+
printHelp();
|
|
72
|
+
process.exit(0);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return config;
|
|
77
|
+
}
|
|
78
|
+
// 打印帮助信息 (Print help information)
|
|
79
|
+
function printHelp() {
|
|
80
|
+
console.log(`
|
|
81
|
+
未使用国际化键检测工具 (Unused Internationalization Keys Detection Tool)
|
|
82
|
+
|
|
83
|
+
用法: node --loader ts-node/esm tests/i18n-unused-keys.ts [选项]
|
|
84
|
+
|
|
85
|
+
选项:
|
|
86
|
+
--no-report 不生成报告文件 (Don't generate report file)
|
|
87
|
+
--report-path <path> 指定报告文件路径 (Specify report file path)
|
|
88
|
+
--verbose 显示详细信息 (Show verbose information)
|
|
89
|
+
--src-dir <path> 指定源代码目录 (Specify source code directory)
|
|
90
|
+
--exclude-dirs <dirs> 指定排除的目录,用逗号分隔 (Specify excluded directories, separated by commas)
|
|
91
|
+
--file-extensions <exts> 指定文件扩展名,用逗号分隔 (Specify file extensions, separated by commas)
|
|
92
|
+
--no-preserve-error-keys 不保留错误相关的键 (Don't preserve error-related keys)
|
|
93
|
+
--report-only 只生成报告,不执行删除 (Only generate report, don't delete)
|
|
94
|
+
--no-strict-mode 不使用严格模式,可能会误报 (Don't use strict mode, may have false positives)
|
|
95
|
+
--help 显示帮助信息 (Show help information)
|
|
96
|
+
`);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 递归获取对象中的所有键 (Recursively get all keys in an object)
|
|
100
|
+
* @param obj 要获取键的对象 (Object to get keys from)
|
|
101
|
+
* @param prefix 键前缀 (Key prefix)
|
|
102
|
+
* @returns 键数组 (Array of keys)
|
|
103
|
+
*/
|
|
104
|
+
function getAllKeysFromObject(obj, prefix = '') {
|
|
105
|
+
let keys = [];
|
|
106
|
+
for (const key in obj) {
|
|
107
|
+
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
108
|
+
// 递归获取嵌套对象的键 (Recursively get keys of nested objects)
|
|
109
|
+
keys = [...keys, ...getAllKeysFromObject(obj[key], prefix ? `${prefix}.${key}` : key)];
|
|
110
|
+
}
|
|
111
|
+
else if (typeof obj[key] === 'string') {
|
|
112
|
+
// 直接使用键值作为完整的键路径 (Use key value as the complete key path)
|
|
113
|
+
keys.push(obj[key]);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return keys;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 从键模块中获取所有键 (Get all keys from key modules)
|
|
120
|
+
* @returns 键数组 (Array of keys)
|
|
121
|
+
*/
|
|
122
|
+
function getAllKeysFromModules() {
|
|
123
|
+
const allKeysSet = new Set();
|
|
124
|
+
// 遍历所有键模块 (Iterate through all key modules)
|
|
125
|
+
for (const moduleKey in allKeys) {
|
|
126
|
+
const keyModule = allKeys[moduleKey];
|
|
127
|
+
if (typeof keyModule === 'object' && keyModule !== null) {
|
|
128
|
+
// 获取模块中的所有键 (Get all keys in the module)
|
|
129
|
+
const keysInModule = getAllKeysFromObject(keyModule);
|
|
130
|
+
keysInModule.forEach(key => allKeysSet.add(key));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return Array.from(allKeysSet);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 获取翻译值 (Get translation value)
|
|
137
|
+
* @param key 键 (Key)
|
|
138
|
+
* @param translations 翻译资源 (Translation resources)
|
|
139
|
+
* @returns 翻译值 (Translation value)
|
|
140
|
+
*/
|
|
141
|
+
function getTranslationValue(key, translations) {
|
|
142
|
+
// 检查是否是命名空间格式的键 (Check if the key is in namespace format)
|
|
143
|
+
const parts = key.split('.');
|
|
144
|
+
if (parts.length > 1) {
|
|
145
|
+
const namespace = parts[0];
|
|
146
|
+
const subKey = parts.slice(1).join('.');
|
|
147
|
+
// 检查命名空间是否存在 (Check if the namespace exists)
|
|
148
|
+
if (translations[namespace] && translations[namespace][subKey] !== undefined) {
|
|
149
|
+
return translations[namespace][subKey];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// 直接在翻译资源中查找键 (Look up the key directly in translation resources)
|
|
153
|
+
for (const moduleName in translations) {
|
|
154
|
+
const module = translations[moduleName];
|
|
155
|
+
if (module && module[key] !== undefined) {
|
|
156
|
+
return module[key];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 获取项目中的所有源代码文件 (Get all source code files in the project)
|
|
163
|
+
* @param config 测试配置 (Test configuration)
|
|
164
|
+
* @returns 文件路径数组 (Array of file paths)
|
|
165
|
+
*/
|
|
166
|
+
async function getAllSourceFiles(config) {
|
|
167
|
+
const { srcDir, excludeDirs, fileExtensions } = config;
|
|
168
|
+
console.log(`查找源代码文件,目录: ${srcDir} (Finding source code files, directory: ${srcDir})`);
|
|
169
|
+
try {
|
|
170
|
+
// 使用更简单的方法查找文件 (Use a simpler method to find files)
|
|
171
|
+
const allFiles = [];
|
|
172
|
+
// 递归查找文件 (Recursively find files)
|
|
173
|
+
function findFilesRecursively(dir) {
|
|
174
|
+
const files = fs.readdirSync(dir);
|
|
175
|
+
for (const file of files) {
|
|
176
|
+
const filePath = path.join(dir, file);
|
|
177
|
+
const stat = fs.statSync(filePath);
|
|
178
|
+
// 检查是否是目录 (Check if it's a directory)
|
|
179
|
+
if (stat.isDirectory()) {
|
|
180
|
+
// 检查是否应该排除该目录 (Check if the directory should be excluded)
|
|
181
|
+
if (!excludeDirs.some(excludeDir => file === excludeDir)) {
|
|
182
|
+
findFilesRecursively(filePath);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else if (stat.isFile()) {
|
|
186
|
+
// 检查文件扩展名 (Check file extension)
|
|
187
|
+
const ext = path.extname(file);
|
|
188
|
+
if (fileExtensions.includes(ext)) {
|
|
189
|
+
allFiles.push(filePath);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// 开始递归查找 (Start recursive search)
|
|
195
|
+
findFilesRecursively(srcDir);
|
|
196
|
+
// 也包括测试目录 (Also include test directory)
|
|
197
|
+
const testDir = path.join(__dirname, '..');
|
|
198
|
+
if (testDir !== srcDir) {
|
|
199
|
+
findFilesRecursively(testDir);
|
|
200
|
+
}
|
|
201
|
+
console.log(`找到 ${allFiles.length} 个源代码文件 (Found ${allFiles.length} source code files)`);
|
|
202
|
+
return allFiles;
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
console.error(`获取源代码文件时出错 (Error getting source code files):`, error);
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* 检查文件中是否使用了指定的键 (Check if a key is used in a file)
|
|
211
|
+
* @param filePath 文件路径 (File path)
|
|
212
|
+
* @param key 键 (Key)
|
|
213
|
+
* @returns 是否使用了键 (Whether the key is used)
|
|
214
|
+
*/
|
|
215
|
+
function isKeyUsedInFile(filePath, key) {
|
|
216
|
+
try {
|
|
217
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
218
|
+
// 检查直接使用键的情况 (Check direct key usage)
|
|
219
|
+
if (content.includes(`'${key}'`) || content.includes(`"${key}"`)) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
// 检查使用 t 函数的情况 (Check usage with t function)
|
|
223
|
+
if (content.includes(`t('${key}'`) || content.includes(`t("${key}"`)) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
// 检查使用 log 函数的情况 (Check usage with log function)
|
|
227
|
+
if (content.includes(`log('${key}'`) || content.includes(`log("${key}"`)) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
// 检查动态生成的键名 (Check dynamically generated key names)
|
|
231
|
+
const parts = key.split('.');
|
|
232
|
+
if (parts.length > 1) {
|
|
233
|
+
const namespace = parts[0];
|
|
234
|
+
const subKey = parts.slice(1).join('.');
|
|
235
|
+
// 检查使用模板字符串的情况 (Check usage with template strings)
|
|
236
|
+
if (content.includes(`\`${namespace}.`) ||
|
|
237
|
+
(content.includes(`\`\${`) && content.includes(`}.${subKey}\``))) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
// 检查使用字符串拼接的情况 (Check usage with string concatenation)
|
|
241
|
+
if (content.includes(`'${namespace}.' +`) || content.includes(`"${namespace}." +`) ||
|
|
242
|
+
content.includes(`+ '.${subKey}'`) || content.includes(`+ ".${subKey}"`)) {
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
// 检查使用变量拼接的情况 (Check usage with variable concatenation)
|
|
246
|
+
if (content.includes(`${namespace}.` + subKey) ||
|
|
247
|
+
content.includes(`${namespace}["${subKey}"]`) ||
|
|
248
|
+
content.includes(`${namespace}['${subKey}']`)) {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
console.error(`读取文件 ${filePath} 时出错 (Error reading file ${filePath}):`, error);
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* 检查键是否在任何文件中使用 (Check if a key is used in any file)
|
|
261
|
+
* @param key 键 (Key)
|
|
262
|
+
* @param files 文件路径数组 (Array of file paths)
|
|
263
|
+
* @param config 测试配置 (Test configuration)
|
|
264
|
+
* @returns 是否使用了键 (Whether the key is used)
|
|
265
|
+
*/
|
|
266
|
+
function isKeyUsed(key, files, config) {
|
|
267
|
+
// 如果配置为保留错误相关的键,并且键以 errors. 开头,则认为它是使用的
|
|
268
|
+
if (config.preserveErrorKeys && key.startsWith('errors.')) {
|
|
269
|
+
if (config.verbose) {
|
|
270
|
+
console.log(`保留错误相关的键: ${key} (Preserving error-related key: ${key})`);
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
// 在严格模式下,保留所有可能动态生成的键
|
|
275
|
+
if (config.strictMode) {
|
|
276
|
+
// 保留所有可能通过动态方式使用的键
|
|
277
|
+
const parts = key.split('.');
|
|
278
|
+
if (parts.length > 1) {
|
|
279
|
+
const namespace = parts[0];
|
|
280
|
+
// 检查是否有文件包含这个命名空间的动态键生成
|
|
281
|
+
for (const file of files) {
|
|
282
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
283
|
+
// 检查各种动态键生成模式
|
|
284
|
+
if (content.includes(`${namespace}.`) &&
|
|
285
|
+
(content.includes('variable') ||
|
|
286
|
+
content.includes('dynamic') ||
|
|
287
|
+
content.includes('concat') ||
|
|
288
|
+
content.includes('join') ||
|
|
289
|
+
content.includes('template'))) {
|
|
290
|
+
if (config.verbose) {
|
|
291
|
+
console.log(`可能动态使用的键: ${key} 在文件 ${file} 中 (Potentially dynamically used key: ${key} in file ${file})`);
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// 检查每个文件中是否使用了键
|
|
299
|
+
for (const file of files) {
|
|
300
|
+
if (isKeyUsedInFile(file, key)) {
|
|
301
|
+
if (config.verbose) {
|
|
302
|
+
console.log(`键 ${key} 在文件 ${file} 中使用 (Key ${key} is used in file ${file})`);
|
|
303
|
+
}
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* 生成报告 (Generate report)
|
|
311
|
+
* @param report 报告数据 (Report data)
|
|
312
|
+
* @param reportPath 报告文件路径 (Report file path)
|
|
313
|
+
*/
|
|
314
|
+
function generateReport(report, reportPath) {
|
|
315
|
+
try {
|
|
316
|
+
// 创建目录 (Create directory)
|
|
317
|
+
const dir = path.dirname(reportPath);
|
|
318
|
+
if (!fs.existsSync(dir)) {
|
|
319
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
320
|
+
}
|
|
321
|
+
// 写入报告文件 (Write report file)
|
|
322
|
+
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf-8');
|
|
323
|
+
console.log(`报告已生成: ${reportPath} (Report generated: ${reportPath})`);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
console.error(`生成报告时出错 (Error generating report):`, error);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* 检查未使用的键 (Check unused keys)
|
|
331
|
+
* @param config 测试配置 (Test configuration)
|
|
332
|
+
*/
|
|
333
|
+
async function checkUnusedKeys(config) {
|
|
334
|
+
console.log('\n=== 检查未使用的国际化键 (Check unused internationalization keys) ===');
|
|
335
|
+
try {
|
|
336
|
+
// 获取所有键 (Get all keys)
|
|
337
|
+
const allKeys = getAllKeysFromModules();
|
|
338
|
+
console.log(`总计找到 ${allKeys.length} 个键 (Total keys found: ${allKeys.length})`);
|
|
339
|
+
// 获取所有源代码文件 (Get all source code files)
|
|
340
|
+
const files = await getAllSourceFiles(config);
|
|
341
|
+
console.log(`总计找到 ${files.length} 个源代码文件 (Total source code files found: ${files.length})`);
|
|
342
|
+
// 检查每个键是否被使用 (Check if each key is used)
|
|
343
|
+
const unusedKeys = [];
|
|
344
|
+
const usedKeys = [];
|
|
345
|
+
for (const key of allKeys) {
|
|
346
|
+
if (isKeyUsed(key, files, config)) {
|
|
347
|
+
usedKeys.push(key);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
unusedKeys.push(key);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// 输出结果 (Output results)
|
|
354
|
+
if (unusedKeys.length > 0) {
|
|
355
|
+
console.log(`\n❌ 发现 ${unusedKeys.length} 个未使用的键 (Found ${unusedKeys.length} unused keys)`);
|
|
356
|
+
console.log(`✅ 发现 ${usedKeys.length} 个使用的键 (Found ${usedKeys.length} used keys)`);
|
|
357
|
+
if (config.verbose) {
|
|
358
|
+
for (const key of unusedKeys) {
|
|
359
|
+
const enValue = getTranslationValue(key, enTranslation);
|
|
360
|
+
const zhValue = getTranslationValue(key, zhTranslation);
|
|
361
|
+
console.log(`键 (Key): ${key}`);
|
|
362
|
+
console.log(`英文值 (English value): ${enValue}`);
|
|
363
|
+
console.log(`中文值 (Chinese value): ${zhValue}`);
|
|
364
|
+
console.log('---');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
console.log('\n✅ 所有键都被使用 (All keys are used)');
|
|
370
|
+
}
|
|
371
|
+
// 生成报告 (Generate report)
|
|
372
|
+
if (config.generateReport) {
|
|
373
|
+
const report = {
|
|
374
|
+
timestamp: new Date().toISOString(),
|
|
375
|
+
totalKeys: allKeys.length,
|
|
376
|
+
totalUsedKeys: usedKeys.length,
|
|
377
|
+
totalFiles: files.length,
|
|
378
|
+
unusedKeys: unusedKeys.map(key => ({
|
|
379
|
+
key,
|
|
380
|
+
enValue: getTranslationValue(key, enTranslation),
|
|
381
|
+
zhValue: getTranslationValue(key, zhTranslation)
|
|
382
|
+
})),
|
|
383
|
+
hasUnusedKeys: unusedKeys.length > 0
|
|
384
|
+
};
|
|
385
|
+
generateReport(report, config.reportPath);
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
unusedKeys,
|
|
389
|
+
hasUnusedKeys: unusedKeys.length > 0
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
console.error('\n❌ 检查未使用的键时出错 (Error checking unused keys):');
|
|
394
|
+
console.error(error);
|
|
395
|
+
return {
|
|
396
|
+
unusedKeys: [],
|
|
397
|
+
hasUnusedKeys: true
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* 主函数 (Main function)
|
|
403
|
+
*/
|
|
404
|
+
async function main() {
|
|
405
|
+
console.log('=== 检测未使用的国际化键 (Detecting unused internationalization keys) ===');
|
|
406
|
+
try {
|
|
407
|
+
// 解析命令行参数 (Parse command line arguments)
|
|
408
|
+
const config = parseCommandLineArgs();
|
|
409
|
+
// 检查是否只生成报告 (Check if only generating report)
|
|
410
|
+
if (config.reportOnly) {
|
|
411
|
+
console.log('只生成报告,不执行删除 (Only generating report, not deleting)');
|
|
412
|
+
}
|
|
413
|
+
// 检查未使用的键 (Check unused keys)
|
|
414
|
+
const result = await checkUnusedKeys(config);
|
|
415
|
+
// 设置退出码 (Set exit code)
|
|
416
|
+
process.exit(result.hasUnusedKeys ? 1 : 0);
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
console.error('❌ 程序执行出错 (Error executing program):');
|
|
420
|
+
console.error(error);
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// 执行主函数 (Execute main function)
|
|
425
|
+
main();
|
package/dist/tests/i18n.test.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import i18next, { t, changeLanguage, getCurrentLanguage } from '../src/lib/i18n/index.js';
|
|
7
7
|
import { createLogger } from '../src/lib/i18n/logger.js';
|
|
8
|
+
import { vi, describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
8
9
|
describe('i18n模块测试 (i18n Module Tests)', () => {
|
|
9
10
|
// 保存原始环境变量
|
|
10
11
|
const originalEnv = process.env.MCP_LANG;
|
|
@@ -81,8 +82,8 @@ describe('i18n Logger模块测试 (i18n Logger Module Tests)', () => {
|
|
|
81
82
|
const originalConsoleError = console.error;
|
|
82
83
|
// 在每个测试前设置mock
|
|
83
84
|
beforeEach(() => {
|
|
84
|
-
console.log =
|
|
85
|
-
console.error =
|
|
85
|
+
console.log = vi.fn();
|
|
86
|
+
console.error = vi.fn();
|
|
86
87
|
process.env.DEBUG = 'false';
|
|
87
88
|
});
|
|
88
89
|
// 在每个测试后恢复原始方法
|
|
@@ -100,13 +101,11 @@ describe('i18n Logger模块测试 (i18n Logger Module Tests)', () => {
|
|
|
100
101
|
expect(logger).toHaveProperty('warn');
|
|
101
102
|
expect(logger).toHaveProperty('debug');
|
|
102
103
|
});
|
|
103
|
-
test('info方法在
|
|
104
|
-
// 设置DEBUG环境变量为true
|
|
105
|
-
process.env.DEBUG = 'true';
|
|
104
|
+
test('info方法在forceLog=true时输出日志 (info method outputs logs when forceLog=true)', () => {
|
|
106
105
|
// 创建日志记录器
|
|
107
106
|
const logger = createLogger('TEST');
|
|
108
|
-
// 调用info
|
|
109
|
-
logger.info('test.info');
|
|
107
|
+
// 调用info方法,设置forceLog为true
|
|
108
|
+
logger.info('test.info', {}, true);
|
|
110
109
|
// 验证console.error被调用(i18n logger使用console.error输出所有日志)
|
|
111
110
|
expect(console.error).toHaveBeenCalled();
|
|
112
111
|
// 验证调用参数包含前缀和消息
|
|
@@ -4,15 +4,16 @@
|
|
|
4
4
|
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
5
5
|
*/
|
|
6
6
|
import { log, COMPONENTS } from '../src/lib/logger.js';
|
|
7
|
+
import { vi, describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
7
8
|
// 模拟i18n模块
|
|
8
|
-
|
|
9
|
+
vi.mock('../src/lib/i18n/index.js', () => ({
|
|
9
10
|
t: (key, options) => {
|
|
10
11
|
// 简单地返回原始key,用于测试
|
|
11
12
|
return options ? `${key} ${JSON.stringify(options)}` : key;
|
|
12
13
|
}
|
|
13
14
|
}));
|
|
14
15
|
// 模拟i18n/logger模块
|
|
15
|
-
|
|
16
|
+
vi.mock('../src/lib/i18n/logger.js', () => ({
|
|
16
17
|
createLogger: (prefix) => ({
|
|
17
18
|
debug: (key, params, forceDebug) => {
|
|
18
19
|
if (forceDebug === true) {
|
|
@@ -37,8 +38,8 @@ describe('Logger模块测试 (Logger Module Tests)', () => {
|
|
|
37
38
|
// 在每个测试前设置mock
|
|
38
39
|
beforeEach(() => {
|
|
39
40
|
// Mock console.log和console.error
|
|
40
|
-
console.log =
|
|
41
|
-
console.error =
|
|
41
|
+
console.log = vi.fn();
|
|
42
|
+
console.error = vi.fn();
|
|
42
43
|
});
|
|
43
44
|
// 在每个测试后恢复原始函数
|
|
44
45
|
afterEach(() => {
|
|
@@ -48,10 +49,10 @@ describe('Logger模块测试 (Logger Module Tests)', () => {
|
|
|
48
49
|
test('正常日志输出 (Normal log output)', () => {
|
|
49
50
|
// 调用log函数,debug设为true
|
|
50
51
|
log('test.message', true, {}, COMPONENTS.SERVER);
|
|
51
|
-
// 验证console.
|
|
52
|
-
expect(console.
|
|
52
|
+
// 验证console.error被调用
|
|
53
|
+
expect(console.error).toHaveBeenCalled();
|
|
53
54
|
// 验证调用参数包含正确的组件和消息
|
|
54
|
-
const callArg = console.
|
|
55
|
+
const callArg = console.error.mock.calls[0][0];
|
|
55
56
|
expect(callArg).toContain(COMPONENTS.SERVER);
|
|
56
57
|
expect(callArg).toContain('test.message');
|
|
57
58
|
});
|
|
@@ -69,10 +70,10 @@ describe('Logger模块测试 (Logger Module Tests)', () => {
|
|
|
69
70
|
const extraData = { key: 'value', number: 123 };
|
|
70
71
|
// 调用log函数,传入额外数据,debug设为true
|
|
71
72
|
log('test.extra', true, extraData, COMPONENTS.NODE_FETCH);
|
|
72
|
-
// 验证console.
|
|
73
|
-
expect(console.
|
|
73
|
+
// 验证console.error被调用
|
|
74
|
+
expect(console.error).toHaveBeenCalled();
|
|
74
75
|
// 验证调用参数包含正确的组件、消息和额外数据
|
|
75
|
-
const callArg = console.
|
|
76
|
+
const callArg = console.error.mock.calls[0][0];
|
|
76
77
|
expect(callArg).toContain(COMPONENTS.NODE_FETCH);
|
|
77
78
|
expect(callArg).toContain('test.extra');
|
|
78
79
|
expect(callArg).toContain(JSON.stringify(extraData));
|
|
@@ -82,10 +83,10 @@ describe('Logger模块测试 (Logger Module Tests)', () => {
|
|
|
82
83
|
Object.values(COMPONENTS).forEach(component => {
|
|
83
84
|
// 调用log函数,debug设为true
|
|
84
85
|
log('test.component', true, {}, component);
|
|
85
|
-
// 验证console.
|
|
86
|
-
expect(console.
|
|
86
|
+
// 验证console.error被调用
|
|
87
|
+
expect(console.error).toHaveBeenCalled();
|
|
87
88
|
// 验证调用参数包含正确的组件
|
|
88
|
-
const callArg = console.
|
|
89
|
+
const callArg = console.error.mock.calls.pop()[0];
|
|
89
90
|
expect(callArg).toContain(component);
|
|
90
91
|
});
|
|
91
92
|
});
|
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
* Co-Author: AI Assistant (Claude)
|
|
5
5
|
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { vi, describe, test, expect, beforeEach, afterAll } from 'vitest';
|
|
8
8
|
// 模拟依赖模块
|
|
9
|
-
const mockStartServer =
|
|
10
|
-
|
|
9
|
+
const mockStartServer = vi.fn();
|
|
10
|
+
vi.mock('../src/lib/server/index.js', () => ({
|
|
11
11
|
startServer: mockStartServer
|
|
12
12
|
}));
|
|
13
13
|
// 模拟 console.error
|
|
14
14
|
const originalConsoleError = console.error;
|
|
15
|
-
console.error =
|
|
15
|
+
console.error = vi.fn();
|
|
16
16
|
// 模拟 process.exit
|
|
17
17
|
const originalProcessExit = process.exit;
|
|
18
|
-
const mockExit =
|
|
18
|
+
const mockExit = vi.fn();
|
|
19
19
|
Object.defineProperty(process, 'exit', { value: mockExit });
|
|
20
20
|
// 创建一个模拟的 main 函数
|
|
21
21
|
const main = async () => {
|
|
@@ -39,7 +39,7 @@ const main = async () => {
|
|
|
39
39
|
describe('mcp-server.ts 测试', () => {
|
|
40
40
|
// 在每个测试前重置所有模拟
|
|
41
41
|
beforeEach(() => {
|
|
42
|
-
|
|
42
|
+
vi.clearAllMocks();
|
|
43
43
|
});
|
|
44
44
|
// 在所有测试后恢复原始值
|
|
45
45
|
afterAll(() => {
|
|
@@ -84,7 +84,7 @@ describe('mcp-server.ts 测试', () => {
|
|
|
84
84
|
const timeoutError = new Error('Server startup timed out after 30000ms');
|
|
85
85
|
// 模拟 setTimeout 立即执行
|
|
86
86
|
const originalSetTimeout = global.setTimeout;
|
|
87
|
-
global.setTimeout =
|
|
87
|
+
global.setTimeout = vi.fn().mockImplementation((callback) => {
|
|
88
88
|
// 不立即执行回调,而是手动触发错误
|
|
89
89
|
return 123;
|
|
90
90
|
});
|