@lmcc-dev/mult-fetch-mcp-server 1.3.1 → 1.3.2
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 +68 -15
- package/README.zh.md +31 -0
- package/dist/src/client.js +118 -91
- package/dist/src/lib/fetchers/browser/BrowserFetcher.js +25 -10
- package/dist/src/lib/fetchers/browser/BrowserInstance.js +67 -64
- package/dist/src/lib/fetchers/common/BaseFetcher.js +106 -10
- package/dist/src/lib/fetchers/common/types.js +10 -1
- package/dist/src/lib/fetchers/common/utils.js +1 -1
- package/dist/src/lib/fetchers/node/HttpClient.js +41 -21
- package/dist/src/lib/fetchers/node/NodeFetcher.js +24 -8
- package/dist/src/lib/i18n/index.js +2 -2
- package/dist/src/lib/i18n/keys/client.js +1 -0
- package/dist/src/lib/i18n/keys/extractor.js +26 -0
- package/dist/src/lib/i18n/keys/fetcher.js +12 -1
- package/dist/src/lib/i18n/keys/index.js +1 -0
- package/dist/src/lib/i18n/keys/node.js +2 -0
- package/dist/src/lib/i18n/locales/en/client.js +1 -0
- package/dist/src/lib/i18n/locales/en/extractor.js +24 -0
- package/dist/src/lib/i18n/locales/en/fetcher.js +3 -1
- package/dist/src/lib/i18n/locales/en/index.js +2 -0
- package/dist/src/lib/i18n/locales/en/node.js +2 -0
- package/dist/src/lib/i18n/locales/zh/client.js +1 -0
- package/dist/src/lib/i18n/locales/zh/extractor.js +22 -0
- package/dist/src/lib/i18n/locales/zh/fetcher.js +13 -3
- package/dist/src/lib/i18n/locales/zh/index.js +2 -0
- package/dist/src/lib/i18n/locales/zh/node.js +2 -0
- package/dist/src/lib/i18n/logger.js +2 -2
- package/dist/src/lib/logger.js +38 -17
- package/dist/src/lib/server/browser.js +2 -2
- package/dist/src/lib/server/fetcher.js +0 -3
- package/dist/src/lib/server/index.js +2 -2
- package/dist/src/lib/server/prompts.js +4 -4
- package/dist/src/lib/server/tools.js +127 -354
- package/dist/src/lib/utils/ChunkManager.js +2 -2
- package/dist/src/lib/utils/ContentExtractor.js +141 -0
- package/dist/src/lib/utils/ContentProcessor.js +5 -11
- package/dist/src/lib/utils/ContentSizeManager.js +2 -2
- package/dist/src/lib/utils/ErrorHandler.js +1 -0
- package/dist/src/lib/utils/TemplateUtils.js +6 -2
- package/dist/tests/client.test.js +1 -1
- package/dist/tests/test-extract-single.js +389 -0
- package/dist/tests/utils/ContentExtractor.test.js +173 -0
- package/dist/tests/utils/ContentProcessor.test.js +136 -0
- package/dist/tests/utils/TemplateUtils.test.js +118 -0
- package/package.json +13 -3
- package/dist/i18n-test-report.json +0 -8
- package/dist/i18n-unused-keys-report.json +0 -8
- package/dist/src/lib/BrowserFetcher.js +0 -787
- package/dist/src/lib/NodeFetcher.js +0 -492
- package/dist/src/lib/i18n/keys.js +0 -529
- package/dist/src/test-i18n.js +0 -139
- package/dist/tests/BrowserFetcher.test.js +0 -951
- package/dist/tests/NodeFetcher.test.js +0 -263
- package/dist/tests/i18n-remove-unused-keys.js +0 -236
- package/dist/tests/i18n-test-report.json +0 -2004
- package/dist/tests/src/lib/i18n/index.js +0 -108
- package/dist/tests/src/lib/i18n/keys/base.js +0 -47
- package/dist/tests/src/lib/i18n/keys/browser.js +0 -93
- package/dist/tests/src/lib/i18n/keys/client.js +0 -70
- package/dist/tests/src/lib/i18n/keys/errors.js +0 -34
- package/dist/tests/src/lib/i18n/keys/fetcher.js +0 -84
- package/dist/tests/src/lib/i18n/keys/index.js +0 -31
- package/dist/tests/src/lib/i18n/keys/node.js +0 -56
- package/dist/tests/src/lib/i18n/keys/prompts.js +0 -82
- package/dist/tests/src/lib/i18n/keys/resources.js +0 -50
- package/dist/tests/src/lib/i18n/keys/server.js +0 -64
- package/dist/tests/src/lib/i18n/keys/tools.js +0 -34
- package/dist/tests/src/lib/i18n/locales/en/browser.js +0 -88
- package/dist/tests/src/lib/i18n/locales/en/client.js +0 -66
- package/dist/tests/src/lib/i18n/locales/en/errors.js +0 -28
- package/dist/tests/src/lib/i18n/locales/en/fetcher.js +0 -71
- package/dist/tests/src/lib/i18n/locales/en/index.js +0 -29
- package/dist/tests/src/lib/i18n/locales/en/node.js +0 -51
- package/dist/tests/src/lib/i18n/locales/en/prompts.js +0 -52
- package/dist/tests/src/lib/i18n/locales/en/resources.js +0 -50
- package/dist/tests/src/lib/i18n/locales/en/server.js +0 -56
- package/dist/tests/src/lib/i18n/locales/en/tools.js +0 -28
- package/dist/tests/src/lib/i18n/locales/zh/browser.js +0 -87
- package/dist/tests/src/lib/i18n/locales/zh/client.js +0 -66
- package/dist/tests/src/lib/i18n/locales/zh/errors.js +0 -28
- package/dist/tests/src/lib/i18n/locales/zh/fetcher.js +0 -71
- package/dist/tests/src/lib/i18n/locales/zh/index.js +0 -29
- package/dist/tests/src/lib/i18n/locales/zh/node.js +0 -51
- package/dist/tests/src/lib/i18n/locales/zh/prompts.js +0 -53
- package/dist/tests/src/lib/i18n/locales/zh/resources.js +0 -50
- package/dist/tests/src/lib/i18n/locales/zh/server.js +0 -57
- package/dist/tests/src/lib/i18n/locales/zh/tools.js +0 -28
- package/dist/tests/src/lib/i18n/logger.js +0 -114
- package/dist/tests/src/lib/logger.js +0 -181
- package/dist/tests/tests/test-i18n.js +0 -588
- package/dist/vitest.config.js +0 -29
|
@@ -1,263 +0,0 @@
|
|
|
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
|
-
import { NodeFetcher } from '../src/lib/NodeFetcher.js';
|
|
7
|
-
// 模拟node-fetch
|
|
8
|
-
jest.mock('node-fetch', () => {
|
|
9
|
-
const mockFetch = jest.fn();
|
|
10
|
-
// 创建模拟Response
|
|
11
|
-
const createMockResponse = (url) => {
|
|
12
|
-
// 根据URL返回不同的内容
|
|
13
|
-
let textContent = '<html><body><h1>测试页面</h1></body></html>';
|
|
14
|
-
let jsonContent = { test: 'data' };
|
|
15
|
-
let contentType = 'text/html; charset=utf-8';
|
|
16
|
-
if (url.includes('/api')) {
|
|
17
|
-
textContent = JSON.stringify(jsonContent);
|
|
18
|
-
contentType = 'application/json; charset=utf-8';
|
|
19
|
-
}
|
|
20
|
-
else if (url.includes('/text')) {
|
|
21
|
-
textContent = '这是纯文本内容';
|
|
22
|
-
contentType = 'text/plain; charset=utf-8';
|
|
23
|
-
}
|
|
24
|
-
else if (url.includes('/md')) {
|
|
25
|
-
textContent = '# 标题\n\n这是Markdown内容';
|
|
26
|
-
contentType = 'text/markdown; charset=utf-8';
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
ok: true,
|
|
30
|
-
status: 200,
|
|
31
|
-
statusText: 'OK',
|
|
32
|
-
url: url,
|
|
33
|
-
headers: {
|
|
34
|
-
get: jest.fn((name) => {
|
|
35
|
-
if (name.toLowerCase() === 'content-type') {
|
|
36
|
-
return contentType;
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
}),
|
|
40
|
-
raw: () => ({}),
|
|
41
|
-
entries: () => [['content-type', contentType]]
|
|
42
|
-
},
|
|
43
|
-
text: jest.fn().mockResolvedValue(textContent),
|
|
44
|
-
json: jest.fn().mockResolvedValue(jsonContent),
|
|
45
|
-
buffer: jest.fn().mockResolvedValue(Buffer.from(textContent)),
|
|
46
|
-
clone: jest.fn(function () { return this; })
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
// 设置默认返回值
|
|
50
|
-
mockFetch.mockImplementation((url) => {
|
|
51
|
-
return Promise.resolve(createMockResponse(url));
|
|
52
|
-
});
|
|
53
|
-
return mockFetch;
|
|
54
|
-
});
|
|
55
|
-
// 模拟http-proxy-agent和https-proxy-agent
|
|
56
|
-
jest.mock('http-proxy-agent', () => ({
|
|
57
|
-
HttpProxyAgent: jest.fn().mockImplementation(() => ({}))
|
|
58
|
-
}));
|
|
59
|
-
jest.mock('https-proxy-agent', () => ({
|
|
60
|
-
HttpsProxyAgent: jest.fn().mockImplementation(() => ({}))
|
|
61
|
-
}));
|
|
62
|
-
// 模拟child_process
|
|
63
|
-
jest.mock('child_process', () => ({
|
|
64
|
-
execSync: jest.fn().mockReturnValue(Buffer.from(''))
|
|
65
|
-
}));
|
|
66
|
-
// 模拟i18n
|
|
67
|
-
jest.mock('../src/lib/i18n/index.js', () => ({
|
|
68
|
-
t: (key, options) => {
|
|
69
|
-
return options ? `${key} ${JSON.stringify(options)}` : key;
|
|
70
|
-
}
|
|
71
|
-
}));
|
|
72
|
-
// 模拟logger
|
|
73
|
-
jest.mock('../src/lib/logger.js', () => ({
|
|
74
|
-
log: jest.fn(),
|
|
75
|
-
COMPONENTS: {
|
|
76
|
-
NODE_FETCH: 'NODE-FETCH'
|
|
77
|
-
}
|
|
78
|
-
}));
|
|
79
|
-
describe('NodeFetcher类测试 (NodeFetcher Class Tests)', () => {
|
|
80
|
-
let fetch;
|
|
81
|
-
beforeEach(() => {
|
|
82
|
-
// 重置所有模拟
|
|
83
|
-
jest.clearAllMocks();
|
|
84
|
-
// 获取模拟的fetch函数
|
|
85
|
-
fetch = require('node-fetch');
|
|
86
|
-
});
|
|
87
|
-
describe('基本功能测试 (Basic Functionality Tests)', () => {
|
|
88
|
-
test('应该有html方法 (Should have html method)', () => {
|
|
89
|
-
expect(typeof NodeFetcher.html).toBe('function');
|
|
90
|
-
});
|
|
91
|
-
test('应该有json方法 (Should have json method)', () => {
|
|
92
|
-
expect(typeof NodeFetcher.json).toBe('function');
|
|
93
|
-
});
|
|
94
|
-
test('应该有txt方法 (Should have txt method)', () => {
|
|
95
|
-
expect(typeof NodeFetcher.txt).toBe('function');
|
|
96
|
-
});
|
|
97
|
-
test('应该有markdown方法 (Should have markdown method)', () => {
|
|
98
|
-
expect(typeof NodeFetcher.markdown).toBe('function');
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
describe('html方法测试 (html Method Tests)', () => {
|
|
102
|
-
test('应该能够获取HTML内容 (Should fetch HTML content)', async () => {
|
|
103
|
-
// 准备测试数据
|
|
104
|
-
const payload = {
|
|
105
|
-
url: 'https://example.com',
|
|
106
|
-
debug: true
|
|
107
|
-
};
|
|
108
|
-
// 调用方法
|
|
109
|
-
const result = await NodeFetcher.html(payload);
|
|
110
|
-
// 验证结果
|
|
111
|
-
expect(result).toBeDefined();
|
|
112
|
-
expect(result.html).toBeDefined();
|
|
113
|
-
expect(result.url).toBe('https://example.com');
|
|
114
|
-
expect(result.status).toBe(200);
|
|
115
|
-
expect(result.headers).toBeDefined();
|
|
116
|
-
expect(result.html).toContain('<html>');
|
|
117
|
-
// 验证fetch被调用
|
|
118
|
-
expect(fetch).toHaveBeenCalledWith(payload.url, expect.objectContaining({
|
|
119
|
-
headers: expect.any(Object),
|
|
120
|
-
timeout: expect.any(Number)
|
|
121
|
-
}));
|
|
122
|
-
});
|
|
123
|
-
test('应该处理获取HTML时的错误 (Should handle errors when fetching HTML)', async () => {
|
|
124
|
-
// 模拟fetch失败
|
|
125
|
-
fetch.mockRejectedValueOnce(new Error('Network error'));
|
|
126
|
-
// 准备测试数据
|
|
127
|
-
const payload = {
|
|
128
|
-
url: 'https://example.com',
|
|
129
|
-
debug: true
|
|
130
|
-
};
|
|
131
|
-
// 调用方法并验证它抛出错误
|
|
132
|
-
await expect(NodeFetcher.html(payload)).rejects.toThrow('Network error');
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
describe('json方法测试 (json Method Tests)', () => {
|
|
136
|
-
test('应该能够获取JSON内容 (Should fetch JSON content)', async () => {
|
|
137
|
-
// 准备测试数据
|
|
138
|
-
const payload = {
|
|
139
|
-
url: 'https://example.com/api',
|
|
140
|
-
debug: true
|
|
141
|
-
};
|
|
142
|
-
// 调用方法
|
|
143
|
-
const result = await NodeFetcher.json(payload);
|
|
144
|
-
// 验证结果
|
|
145
|
-
expect(result).toBeDefined();
|
|
146
|
-
expect(result.json).toBeDefined();
|
|
147
|
-
expect(result.json).toEqual({ test: 'data' });
|
|
148
|
-
expect(result.url).toBe('https://example.com/api');
|
|
149
|
-
expect(result.status).toBe(200);
|
|
150
|
-
expect(result.headers).toBeDefined();
|
|
151
|
-
// 验证fetch被调用
|
|
152
|
-
expect(fetch).toHaveBeenCalledWith(payload.url, expect.objectContaining({
|
|
153
|
-
headers: expect.any(Object),
|
|
154
|
-
timeout: expect.any(Number)
|
|
155
|
-
}));
|
|
156
|
-
});
|
|
157
|
-
test('应该处理获取JSON时的错误 (Should handle errors when fetching JSON)', async () => {
|
|
158
|
-
// 模拟fetch失败
|
|
159
|
-
fetch.mockRejectedValueOnce(new Error('Network error'));
|
|
160
|
-
// 准备测试数据
|
|
161
|
-
const payload = {
|
|
162
|
-
url: 'https://example.com/api',
|
|
163
|
-
debug: true
|
|
164
|
-
};
|
|
165
|
-
// 调用方法并验证它抛出错误
|
|
166
|
-
await expect(NodeFetcher.json(payload)).rejects.toThrow('Network error');
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
describe('txt方法测试 (txt Method Tests)', () => {
|
|
170
|
-
test('应该能够获取文本内容 (Should fetch text content)', async () => {
|
|
171
|
-
// 准备测试数据
|
|
172
|
-
const payload = {
|
|
173
|
-
url: 'https://example.com/text',
|
|
174
|
-
debug: true
|
|
175
|
-
};
|
|
176
|
-
// 调用方法
|
|
177
|
-
const result = await NodeFetcher.txt(payload);
|
|
178
|
-
// 验证结果
|
|
179
|
-
expect(result).toBeDefined();
|
|
180
|
-
expect(result.text).toBeDefined();
|
|
181
|
-
expect(result.url).toBe('https://example.com/text');
|
|
182
|
-
expect(result.status).toBe(200);
|
|
183
|
-
expect(result.headers).toBeDefined();
|
|
184
|
-
// 验证fetch被调用
|
|
185
|
-
expect(fetch).toHaveBeenCalledWith(payload.url, expect.objectContaining({
|
|
186
|
-
headers: expect.any(Object),
|
|
187
|
-
timeout: expect.any(Number)
|
|
188
|
-
}));
|
|
189
|
-
});
|
|
190
|
-
test('应该处理获取文本时的错误 (Should handle errors when fetching text)', async () => {
|
|
191
|
-
// 模拟fetch失败
|
|
192
|
-
fetch.mockRejectedValueOnce(new Error('Network error'));
|
|
193
|
-
// 准备测试数据
|
|
194
|
-
const payload = {
|
|
195
|
-
url: 'https://example.com/text',
|
|
196
|
-
debug: true
|
|
197
|
-
};
|
|
198
|
-
// 调用方法并验证它抛出错误
|
|
199
|
-
await expect(NodeFetcher.txt(payload)).rejects.toThrow('Network error');
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
describe('markdown方法测试 (markdown Method Tests)', () => {
|
|
203
|
-
test('应该能够获取Markdown内容 (Should fetch Markdown content)', async () => {
|
|
204
|
-
// 准备测试数据
|
|
205
|
-
const payload = {
|
|
206
|
-
url: 'https://example.com/md',
|
|
207
|
-
debug: true
|
|
208
|
-
};
|
|
209
|
-
// 调用方法
|
|
210
|
-
const result = await NodeFetcher.markdown(payload);
|
|
211
|
-
// 验证结果
|
|
212
|
-
expect(result).toBeDefined();
|
|
213
|
-
expect(result.markdown).toBeDefined();
|
|
214
|
-
expect(result.url).toBe('https://example.com/md');
|
|
215
|
-
expect(result.status).toBe(200);
|
|
216
|
-
expect(result.headers).toBeDefined();
|
|
217
|
-
// 验证fetch被调用
|
|
218
|
-
expect(fetch).toHaveBeenCalledWith(payload.url, expect.objectContaining({
|
|
219
|
-
headers: expect.any(Object),
|
|
220
|
-
timeout: expect.any(Number)
|
|
221
|
-
}));
|
|
222
|
-
});
|
|
223
|
-
test('应该处理获取Markdown时的错误 (Should handle errors when fetching Markdown)', async () => {
|
|
224
|
-
// 模拟fetch失败
|
|
225
|
-
fetch.mockRejectedValueOnce(new Error('Network error'));
|
|
226
|
-
// 准备测试数据
|
|
227
|
-
const payload = {
|
|
228
|
-
url: 'https://example.com/md',
|
|
229
|
-
debug: true
|
|
230
|
-
};
|
|
231
|
-
// 调用方法并验证它抛出错误
|
|
232
|
-
await expect(NodeFetcher.markdown(payload)).rejects.toThrow('Network error');
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
describe('代理功能测试 (Proxy Functionality Tests)', () => {
|
|
236
|
-
test('应该正确处理HTTP代理 (Should handle HTTP proxy correctly)', async () => {
|
|
237
|
-
// 准备测试数据
|
|
238
|
-
const payload = {
|
|
239
|
-
url: 'http://example.com',
|
|
240
|
-
proxy: 'http://proxy.example.com:8080',
|
|
241
|
-
debug: true
|
|
242
|
-
};
|
|
243
|
-
// 调用方法
|
|
244
|
-
await NodeFetcher.html(payload);
|
|
245
|
-
// 验证HttpProxyAgent被调用
|
|
246
|
-
const { HttpProxyAgent } = require('http-proxy-agent');
|
|
247
|
-
expect(HttpProxyAgent).toHaveBeenCalledWith(payload.proxy);
|
|
248
|
-
});
|
|
249
|
-
test('应该正确处理HTTPS代理 (Should handle HTTPS proxy correctly)', async () => {
|
|
250
|
-
// 准备测试数据
|
|
251
|
-
const payload = {
|
|
252
|
-
url: 'https://example.com',
|
|
253
|
-
proxy: 'http://proxy.example.com:8080',
|
|
254
|
-
debug: true
|
|
255
|
-
};
|
|
256
|
-
// 调用方法
|
|
257
|
-
await NodeFetcher.html(payload);
|
|
258
|
-
// 验证HttpsProxyAgent被调用
|
|
259
|
-
const { HttpsProxyAgent } = require('https-proxy-agent');
|
|
260
|
-
expect(HttpsProxyAgent).toHaveBeenCalledWith(payload.proxy);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
});
|
|
@@ -1,236 +0,0 @@
|
|
|
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
|
-
// 删除未使用的国际化键 (Remove unused internationalization keys)
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
import * as path from 'path';
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
10
|
-
// 获取当前文件的目录路径 (Get the directory path of the current file)
|
|
11
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
-
const __dirname = path.dirname(__filename);
|
|
13
|
-
// 默认配置 (Default configuration)
|
|
14
|
-
const defaultConfig = {
|
|
15
|
-
reportPath: path.join(__dirname, '../i18n-unused-keys-report.json'),
|
|
16
|
-
verbose: false,
|
|
17
|
-
preserveErrorKeys: true,
|
|
18
|
-
dryRun: false
|
|
19
|
-
};
|
|
20
|
-
// 解析命令行参数 (Parse command line arguments)
|
|
21
|
-
function parseCommandLineArgs() {
|
|
22
|
-
const config = { ...defaultConfig };
|
|
23
|
-
const args = process.argv.slice(2);
|
|
24
|
-
for (let i = 0; i < args.length; i++) {
|
|
25
|
-
const arg = args[i];
|
|
26
|
-
switch (arg) {
|
|
27
|
-
case '--report-path':
|
|
28
|
-
if (i + 1 < args.length) {
|
|
29
|
-
config.reportPath = args[++i];
|
|
30
|
-
}
|
|
31
|
-
break;
|
|
32
|
-
case '--verbose':
|
|
33
|
-
config.verbose = true;
|
|
34
|
-
break;
|
|
35
|
-
case '--no-preserve-error-keys':
|
|
36
|
-
config.preserveErrorKeys = false;
|
|
37
|
-
break;
|
|
38
|
-
case '--dry-run':
|
|
39
|
-
config.dryRun = true;
|
|
40
|
-
break;
|
|
41
|
-
case '--help':
|
|
42
|
-
printHelp();
|
|
43
|
-
process.exit(0);
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return config;
|
|
48
|
-
}
|
|
49
|
-
// 打印帮助信息 (Print help information)
|
|
50
|
-
function printHelp() {
|
|
51
|
-
console.log(`
|
|
52
|
-
删除未使用的国际化键工具 (Remove Unused Internationalization Keys Tool)
|
|
53
|
-
|
|
54
|
-
用法: node --loader ts-node/esm tests/i18n-remove-unused-keys.ts [选项]
|
|
55
|
-
|
|
56
|
-
选项:
|
|
57
|
-
--report-path <path> 指定报告文件路径 (Specify report file path)
|
|
58
|
-
--verbose 显示详细信息 (Show verbose information)
|
|
59
|
-
--no-preserve-error-keys 不保留错误相关的键 (Don't preserve error-related keys)
|
|
60
|
-
--dry-run 仅显示将要删除的键,不实际删除 (Only show keys to be deleted, don't actually delete)
|
|
61
|
-
--help 显示帮助信息 (Show help information)
|
|
62
|
-
`);
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* 读取报告文件 (Read report file)
|
|
66
|
-
* @param reportPath 报告文件路径 (Report file path)
|
|
67
|
-
* @returns 报告数据 (Report data)
|
|
68
|
-
*/
|
|
69
|
-
function readReport(reportPath) {
|
|
70
|
-
try {
|
|
71
|
-
const reportContent = fs.readFileSync(reportPath, 'utf-8');
|
|
72
|
-
return JSON.parse(reportContent);
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
console.error(`读取报告文件时出错 (Error reading report file):`, error);
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* 获取要删除的键 (Get keys to delete)
|
|
81
|
-
* @param report 报告数据 (Report data)
|
|
82
|
-
* @param config 配置 (Configuration)
|
|
83
|
-
* @returns 要删除的键 (Keys to delete)
|
|
84
|
-
*/
|
|
85
|
-
function getKeysToDelete(report, config) {
|
|
86
|
-
const { unusedKeys } = report;
|
|
87
|
-
if (!unusedKeys || !Array.isArray(unusedKeys)) {
|
|
88
|
-
console.error('报告中没有未使用的键 (No unused keys in report)');
|
|
89
|
-
return [];
|
|
90
|
-
}
|
|
91
|
-
// 过滤要删除的键 (Filter keys to delete)
|
|
92
|
-
return unusedKeys
|
|
93
|
-
.map(item => item.key)
|
|
94
|
-
.filter(key => {
|
|
95
|
-
// 如果配置为保留错误相关的键,则跳过以 errors. 开头的键
|
|
96
|
-
// (If configured to preserve error-related keys, skip keys starting with errors.)
|
|
97
|
-
if (config.preserveErrorKeys && key.startsWith('errors.')) {
|
|
98
|
-
if (config.verbose) {
|
|
99
|
-
console.log(`保留错误相关的键: ${key} (Preserving error-related key: ${key})`);
|
|
100
|
-
}
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
return true;
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* 获取键所在的文件 (Get file containing the key)
|
|
108
|
-
* @param key 键 (Key)
|
|
109
|
-
* @returns 文件路径 (File path)
|
|
110
|
-
*/
|
|
111
|
-
function getKeyFile(key) {
|
|
112
|
-
const parts = key.split('.');
|
|
113
|
-
if (parts.length < 1)
|
|
114
|
-
return null;
|
|
115
|
-
const namespace = parts[0];
|
|
116
|
-
// 根据命名空间确定文件路径 (Determine file path based on namespace)
|
|
117
|
-
const enPath = path.join(__dirname, `../src/lib/i18n/locales/en/${namespace}.ts`);
|
|
118
|
-
const zhPath = path.join(__dirname, `../src/lib/i18n/locales/zh/${namespace}.ts`);
|
|
119
|
-
// 检查文件是否存在 (Check if files exist)
|
|
120
|
-
if (fs.existsSync(enPath) && fs.existsSync(zhPath)) {
|
|
121
|
-
return namespace;
|
|
122
|
-
}
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* 从文件中删除键 (Remove key from file)
|
|
127
|
-
* @param namespace 命名空间 (Namespace)
|
|
128
|
-
* @param key 键 (Key)
|
|
129
|
-
* @param config 配置 (Configuration)
|
|
130
|
-
*/
|
|
131
|
-
function removeKeyFromFiles(namespace, key, config) {
|
|
132
|
-
const enPath = path.join(__dirname, `../src/lib/i18n/locales/en/${namespace}.ts`);
|
|
133
|
-
const zhPath = path.join(__dirname, `../src/lib/i18n/locales/zh/${namespace}.ts`);
|
|
134
|
-
// 从英文文件中删除键 (Remove key from English file)
|
|
135
|
-
removeKeyFromFile(enPath, key, config);
|
|
136
|
-
// 从中文文件中删除键 (Remove key from Chinese file)
|
|
137
|
-
removeKeyFromFile(zhPath, key, config);
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* 从文件中删除键 (Remove key from file)
|
|
141
|
-
* @param filePath 文件路径 (File path)
|
|
142
|
-
* @param key 键 (Key)
|
|
143
|
-
* @param config 配置 (Configuration)
|
|
144
|
-
*/
|
|
145
|
-
function removeKeyFromFile(filePath, key, config) {
|
|
146
|
-
try {
|
|
147
|
-
// 读取文件内容 (Read file content)
|
|
148
|
-
let content = fs.readFileSync(filePath, 'utf-8');
|
|
149
|
-
// 获取键的完整名称 (Get full key name)
|
|
150
|
-
const parts = key.split('.');
|
|
151
|
-
const keyName = parts.slice(1).join('.');
|
|
152
|
-
// 构建正则表达式 (Build regular expression)
|
|
153
|
-
const regex = new RegExp(`\\s*\\[.*\\.${keyName}\\]:\\s*['"].*['"],?\\n?`);
|
|
154
|
-
// 替换内容 (Replace content)
|
|
155
|
-
const newContent = content.replace(regex, '');
|
|
156
|
-
// 如果内容没有变化,说明没有找到键 (If content hasn't changed, key wasn't found)
|
|
157
|
-
if (newContent === content) {
|
|
158
|
-
if (config.verbose) {
|
|
159
|
-
console.log(`在文件 ${filePath} 中未找到键 ${key} (Key ${key} not found in file ${filePath})`);
|
|
160
|
-
}
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
// 如果是演习模式,只显示要删除的内容 (If dry run, only show content to be deleted)
|
|
164
|
-
if (config.dryRun) {
|
|
165
|
-
if (config.verbose) {
|
|
166
|
-
console.log(`将从文件 ${filePath} 中删除键 ${key} (Will remove key ${key} from file ${filePath})`);
|
|
167
|
-
}
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
// 写入文件 (Write file)
|
|
171
|
-
fs.writeFileSync(filePath, newContent, 'utf-8');
|
|
172
|
-
if (config.verbose) {
|
|
173
|
-
console.log(`已从文件 ${filePath} 中删除键 ${key} (Removed key ${key} from file ${filePath})`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
console.error(`从文件 ${filePath} 中删除键 ${key} 时出错 (Error removing key ${key} from file ${filePath}):`, error);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* 主函数 (Main function)
|
|
182
|
-
*/
|
|
183
|
-
async function main() {
|
|
184
|
-
console.log('=== 删除未使用的国际化键 (Removing unused internationalization keys) ===');
|
|
185
|
-
try {
|
|
186
|
-
// 解析命令行参数 (Parse command line arguments)
|
|
187
|
-
const config = parseCommandLineArgs();
|
|
188
|
-
// 如果是演习模式,显示提示 (If dry run, show notice)
|
|
189
|
-
if (config.dryRun) {
|
|
190
|
-
console.log('演习模式:不会实际删除键 (Dry run: keys will not be actually deleted)');
|
|
191
|
-
}
|
|
192
|
-
// 读取报告 (Read report)
|
|
193
|
-
const report = readReport(config.reportPath);
|
|
194
|
-
// 获取要删除的键 (Get keys to delete)
|
|
195
|
-
const keysToDelete = getKeysToDelete(report, config);
|
|
196
|
-
console.log(`找到 ${keysToDelete.length} 个要删除的键 (Found ${keysToDelete.length} keys to delete)`);
|
|
197
|
-
// 按命名空间分组键 (Group keys by namespace)
|
|
198
|
-
const keysByNamespace = {};
|
|
199
|
-
for (const key of keysToDelete) {
|
|
200
|
-
const namespace = getKeyFile(key);
|
|
201
|
-
if (namespace) {
|
|
202
|
-
if (!keysByNamespace[namespace]) {
|
|
203
|
-
keysByNamespace[namespace] = [];
|
|
204
|
-
}
|
|
205
|
-
keysByNamespace[namespace].push(key);
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
console.warn(`无法确定键 ${key} 所在的文件 (Cannot determine file for key ${key})`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// 删除键 (Delete keys)
|
|
212
|
-
let totalDeleted = 0;
|
|
213
|
-
for (const namespace in keysByNamespace) {
|
|
214
|
-
const keys = keysByNamespace[namespace];
|
|
215
|
-
console.log(`从命名空间 ${namespace} 中删除 ${keys.length} 个键 (Deleting ${keys.length} keys from namespace ${namespace})`);
|
|
216
|
-
for (const key of keys) {
|
|
217
|
-
removeKeyFromFiles(namespace, key, config);
|
|
218
|
-
totalDeleted++;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
// 输出结果 (Output results)
|
|
222
|
-
if (config.dryRun) {
|
|
223
|
-
console.log(`演习模式:将删除 ${totalDeleted} 个键 (Dry run: would delete ${totalDeleted} keys)`);
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
console.log(`已删除 ${totalDeleted} 个键 (Deleted ${totalDeleted} keys)`);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
catch (error) {
|
|
230
|
-
console.error('❌ 程序执行出错 (Error executing program):');
|
|
231
|
-
console.error(error);
|
|
232
|
-
process.exit(1);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
// 执行主函数 (Execute main function)
|
|
236
|
-
main();
|