@xubill/xx-cli 1.0.0

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.

Potentially problematic release.


This version of @xubill/xx-cli might be problematic. Click here for more details.

@@ -0,0 +1,583 @@
1
+ /**
2
+ * xx 核心模块
3
+ * 负责初始化系统、注册命令、管理插件等
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const logger = require('../utils/logger');
9
+ const pkg = require('../../package.json');
10
+ const validator = require('../utils/validator');
11
+ const pluginConfig = require('../utils/plugin-config');
12
+
13
+ class Core {
14
+ constructor() {
15
+ this.plugins = [];
16
+ this.commands = [];
17
+ this.version = pkg.version;
18
+ this.pluginFiles = [];
19
+ this.pluginCache = new Map(); // 插件缓存
20
+ this.hooks = {}; // 全局钩子
21
+ }
22
+
23
+ /**
24
+ * 初始化核心功能
25
+ */
26
+ async init() {
27
+ //logger.info('正在初始化 xx 核心...');
28
+ this.loadPlugins();
29
+
30
+ // 触发初始化完成钩子
31
+ await this.triggerHook('init', { core: this });
32
+
33
+ //logger.info('xx 核心初始化完成');
34
+ }
35
+
36
+ /**
37
+ * 加载插件
38
+ */
39
+ loadPlugins() {
40
+ const pluginsDir = path.join(__dirname, '../plugins');
41
+ if (fs.existsSync(pluginsDir)) {
42
+ const pluginFiles = fs.readdirSync(pluginsDir);
43
+ // 只收集插件文件信息,不立即加载
44
+ this.pluginFiles = pluginFiles.filter(file => file.endsWith('.js'));
45
+ }
46
+ }
47
+
48
+ /**
49
+ * 加载单个插件
50
+ * @param {string} pluginFile - 插件文件名
51
+ * @returns {Object|null} 插件实例或null
52
+ */
53
+ loadPlugin(pluginFile) {
54
+ // 检查缓存中是否已存在插件实例
55
+ if (this.pluginCache.has(pluginFile)) {
56
+ return this.pluginCache.get(pluginFile);
57
+ }
58
+
59
+ const pluginPath = path.join(__dirname, '../plugins', pluginFile);
60
+ try {
61
+ const Plugin = require(pluginPath);
62
+
63
+ // 检查是否为类
64
+ if (typeof Plugin === 'function' && Plugin.prototype) {
65
+ // 实例化类插件
66
+ const pluginInstance = new Plugin();
67
+ pluginInstance.pluginName = pluginInstance.pluginName || pluginFile.replace('.js', '');
68
+
69
+ // 缓存插件实例
70
+ this.pluginCache.set(pluginFile, pluginInstance);
71
+ return pluginInstance;
72
+ } else {
73
+ // 保持非类插件的兼容性
74
+
75
+ // 缓存插件
76
+ this.pluginCache.set(pluginFile, Plugin);
77
+ return Plugin;
78
+ }
79
+ } catch (error) {
80
+ logger.error(`加载插件 ${pluginFile} 失败: ${error.message}`);
81
+ return null;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * 获取所有插件信息
87
+ * @returns {Array} 插件信息数组
88
+ */
89
+ getPluginsInfo() {
90
+ return this.pluginFiles.map(pluginFile => {
91
+ const pluginName = pluginFile.replace('.js', '');
92
+ return {
93
+ name: pluginName,
94
+ file: pluginFile
95
+ };
96
+ });
97
+ }
98
+
99
+ /**
100
+ * 注册命令到 Commander
101
+ * @param {Object} program - Commander 实例
102
+ */
103
+ registerCommands(program) {
104
+ // 注册核心命令
105
+ this.registerCoreCommands(program);
106
+
107
+ // 注册插件命令
108
+ if (this.pluginFiles) {
109
+ this.pluginFiles.forEach(pluginFile => {
110
+ const pluginName = pluginFile.replace('.js', '');
111
+
112
+ // 检查插件是否已禁用
113
+ if (pluginConfig.isPluginDisabled(pluginName)) {
114
+ return;
115
+ }
116
+
117
+ const plugin = this.loadPlugin(pluginFile);
118
+ if (plugin && plugin.registerCommands) {
119
+ plugin.registerCommands(program);
120
+ }
121
+ });
122
+ }
123
+
124
+ // 注册命令自动建议功能
125
+ this.registerCommandSuggestions(program);
126
+ }
127
+
128
+ /**
129
+ * 注册命令自动建议功能
130
+ * @param {Object} program - Commander 实例
131
+ */
132
+ registerCommandSuggestions(program) {
133
+ // 收集所有命令信息
134
+ const allCommands = this.collectAllCommands(program);
135
+
136
+ // 添加命令自动建议
137
+ program.on('command:*', (args) => {
138
+ const inputCommand = args[0];
139
+ const suggestions = this.getCommandSuggestions(inputCommand, allCommands);
140
+
141
+ if (suggestions.length > 0) {
142
+ console.log(`\n未找到命令 "${inputCommand}",您可能想要:`);
143
+ suggestions.forEach(suggestion => {
144
+ console.log(` - ${suggestion}`);
145
+ });
146
+ console.log('');
147
+ process.exit(1);
148
+ }
149
+ });
150
+ }
151
+
152
+ /**
153
+ * 收集所有命令信息
154
+ * @param {Object} program - Commander 实例
155
+ * @returns {Array} 命令数组
156
+ */
157
+ collectAllCommands(program) {
158
+ const commands = [];
159
+
160
+ // 收集核心命令
161
+ if (program.commands) {
162
+ program.commands.forEach(command => {
163
+ // 添加命令名称
164
+ if (command._name && command._name !== '*') {
165
+ commands.push(command._name);
166
+ }
167
+
168
+ // 添加命令别名
169
+ if (command._alias) {
170
+ commands.push(command._alias);
171
+ }
172
+ });
173
+ }
174
+
175
+ return commands;
176
+ }
177
+
178
+ /**
179
+ * 获取命令建议
180
+ * @param {string} input - 用户输入
181
+ * @param {Array} commands - 命令数组
182
+ * @returns {Array} 建议的命令数组
183
+ */
184
+ getCommandSuggestions(input, commands) {
185
+ return commands.filter(command => {
186
+ return command.startsWith(input.toLowerCase());
187
+ }).slice(0, 5); // 最多返回5个建议
188
+ }
189
+
190
+ /**
191
+ * 注册全局钩子
192
+ * @param {string} event - 事件名称
193
+ * @param {Function} callback - 回调函数
194
+ */
195
+ registerHook(event, callback) {
196
+ if (!this.hooks[event]) {
197
+ this.hooks[event] = [];
198
+ }
199
+
200
+ this.hooks[event].push(callback);
201
+ }
202
+
203
+ /**
204
+ * 触发全局钩子
205
+ * @param {string} event - 事件名称
206
+ * @param {*} data - 事件数据
207
+ * @returns {Promise<Array>} 钩子执行结果
208
+ */
209
+ async triggerHook(event, data) {
210
+ const results = [];
211
+
212
+ // 触发全局钩子
213
+ if (this.hooks[event]) {
214
+ for (const callback of this.hooks[event]) {
215
+ try {
216
+ const result = await callback(data);
217
+ results.push(result);
218
+ } catch (error) {
219
+ console.error(`执行全局钩子 ${event} 时出错:`, error.message);
220
+ }
221
+ }
222
+ }
223
+
224
+ // 触发所有插件的钩子
225
+ if (this.pluginFiles) {
226
+ for (const pluginFile of this.pluginFiles) {
227
+ const pluginName = pluginFile.replace('.js', '');
228
+
229
+ // 检查插件是否已禁用
230
+ if (pluginConfig.isPluginDisabled(pluginName)) {
231
+ continue;
232
+ }
233
+
234
+ const plugin = this.loadPlugin(pluginFile);
235
+ if (plugin && plugin.triggerHook) {
236
+ try {
237
+ const pluginResults = await plugin.triggerHook(event, data);
238
+ results.push(...pluginResults);
239
+ } catch (error) {
240
+ console.error(`执行插件 ${pluginName} 的钩子 ${event} 时出错:`, error.message);
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ return results;
247
+ }
248
+
249
+ /**
250
+ * 检查是否注册了指定全局钩子
251
+ * @param {string} event - 事件名称
252
+ * @returns {boolean} 是否注册了钩子
253
+ */
254
+ hasHook(event) {
255
+ return this.hooks[event] && this.hooks[event].length > 0;
256
+ }
257
+
258
+ /**
259
+ * 显示命令历史记录
260
+ * @param {Object} options - 选项
261
+ */
262
+ showHistory(options) {
263
+ const historyManager = require('../utils/history-manager');
264
+
265
+ if (options.clear) {
266
+ // 清除命令历史记录
267
+ historyManager.clearHistory();
268
+ console.log('命令历史记录已清除');
269
+ return;
270
+ }
271
+
272
+ if (options.search) {
273
+ // 搜索命令历史记录
274
+ const searchResults = historyManager.searchHistory(options.search);
275
+ if (searchResults.length > 0) {
276
+ console.log('\n搜索结果:');
277
+ console.log('=====================================');
278
+ searchResults.forEach((item, index) => {
279
+ const date = new Date(item.timestamp).toLocaleString();
280
+ console.log(`${index + 1}. ${item.command} (${date})`);
281
+ });
282
+ console.log('=====================================');
283
+ console.log(`总计: ${searchResults.length} 条记录\n`);
284
+ } else {
285
+ console.log(`\n未找到匹配 "${options.search}" 的命令历史记录\n`);
286
+ }
287
+ return;
288
+ }
289
+
290
+ // 显示命令历史记录
291
+ const history = historyManager.getHistory(options.limit);
292
+ if (history.length > 0) {
293
+ console.log('\n命令历史记录:');
294
+ console.log('=====================================');
295
+ history.forEach((item, index) => {
296
+ const date = new Date(item.timestamp).toLocaleString();
297
+ console.log(`${index + 1}. ${item.command} (${date})`);
298
+ });
299
+ console.log('=====================================');
300
+ console.log(`总计: ${history.length} 条记录\n`);
301
+ } else {
302
+ console.log('\n命令历史记录为空\n');
303
+ }
304
+ }
305
+
306
+ /**
307
+ * 注册核心命令
308
+ * @param {Object} program - Commander 实例
309
+ */
310
+ registerCoreCommands(program) {
311
+ // 帮助命令
312
+ program
313
+ .command('help')
314
+ .description('显示帮助信息')
315
+ .action(() => {
316
+ program.outputHelp();
317
+ });
318
+
319
+ // 版本命令
320
+ program
321
+ .command('version')
322
+ .alias('v')
323
+ .description('显示版本信息')
324
+ .action(() => {
325
+ console.log(this.version);
326
+ });
327
+
328
+ // 命令历史记录
329
+ program
330
+ .command('history')
331
+ .alias('h')
332
+ .description('查看命令历史记录')
333
+ .option('-l, --limit <number>', '限制显示的历史记录数量', parseInt, 10)
334
+ .option('-c, --clear', '清除命令历史记录')
335
+ .option('-s, --search <keyword>', '搜索命令历史记录')
336
+ .action((options) => this.showHistory(options));
337
+
338
+ // 插件管理命令
339
+ const pluginCommand = program.command('plugin')
340
+ .alias('p')
341
+ .description('插件管理');
342
+
343
+ // 列出插件
344
+ pluginCommand.command('list')
345
+ .alias('ls')
346
+ .description('列出所有插件')
347
+ .action(() => this.listPlugins());
348
+
349
+ // 启用插件
350
+ pluginCommand.command('enable <plugin>')
351
+ .alias('en')
352
+ .description('启用插件')
353
+ .action((plugin) => this.enablePlugin(plugin));
354
+
355
+ // 禁用插件
356
+ pluginCommand.command('disable <plugin>')
357
+ .alias('dis')
358
+ .description('禁用插件')
359
+ .action((plugin) => this.disablePlugin(plugin));
360
+
361
+ // 添加外部自定义插件
362
+ pluginCommand.command('add <url>')
363
+ .description('添加外部自定义插件')
364
+ .action((url) => this.addExternalPlugin(url));
365
+
366
+ pluginCommand.command('remove <plugin>')
367
+ .alias('rm')
368
+ .description('删除指定插件')
369
+ .action((plugin) => this.removePlugin(plugin));
370
+
371
+ // 插件配置管理
372
+ pluginCommand.command('config <plugin> [key] [value]')
373
+ .alias('c')
374
+ .description('查看和设置插件配置')
375
+ .action((plugin, key, value) => this.managePluginConfig(plugin, key, value));
376
+ }
377
+
378
+ /**
379
+ * 列出所有插件
380
+ */
381
+ listPlugins() {
382
+ const pluginsInfo = this.getPluginsInfo();
383
+ console.log('\n可用插件列表:');
384
+ console.log('=====================================');
385
+ pluginsInfo.forEach(plugin => {
386
+ const isDisabled = pluginConfig.isPluginDisabled(plugin.name);
387
+ const status = isDisabled ? '[已禁用]' : '[已启用]';
388
+ console.log(`- ${plugin.name} ${status}`);
389
+ });
390
+ console.log('=====================================');
391
+ console.log(`总计: ${pluginsInfo.length} 个插件\n`);
392
+ }
393
+
394
+ /**
395
+ * 启用插件
396
+ * @param {string} pluginName - 插件名称
397
+ */
398
+ enablePlugin(pluginName) {
399
+ // 验证插件名称
400
+ if (!validator.validateCommandParam(pluginName)) {
401
+ console.error('错误: 插件名称包含无效字符');
402
+ return;
403
+ }
404
+
405
+ // 启用插件
406
+ pluginConfig.enablePlugin(pluginName);
407
+ console.log(`已启用插件: ${pluginName}`);
408
+ console.log('提示: 请重新运行命令以应用更改');
409
+ }
410
+
411
+ /**
412
+ * 禁用插件
413
+ * @param {string} pluginName - 插件名称
414
+ */
415
+ disablePlugin(pluginName) {
416
+ // 验证插件名称
417
+ if (!validator.validateCommandParam(pluginName)) {
418
+ console.error('错误: 插件名称包含无效字符');
419
+ return;
420
+ }
421
+
422
+ // 禁用插件
423
+ pluginConfig.disablePlugin(pluginName);
424
+ console.log(`已禁用插件: ${pluginName}`);
425
+ console.log('提示: 请重新运行命令以应用更改');
426
+ }
427
+
428
+ /**
429
+ * 删除插件
430
+ * @param {string} pluginName - 插件名称
431
+ */
432
+ removePlugin(pluginName) {
433
+ // 验证插件名称
434
+ if (!validator.validateCommandParam(pluginName)) {
435
+ console.error('错误: 插件名称包含无效字符');
436
+ return;
437
+ }
438
+
439
+ const fs = require('fs');
440
+ const path = require('path');
441
+
442
+ // 插件目录路径
443
+ const pluginsDir = path.join(__dirname, '../plugins');
444
+
445
+ // 构建插件文件路径
446
+ const pluginFileName = `${pluginName}.js`;
447
+ const pluginPath = path.join(pluginsDir, pluginFileName);
448
+
449
+ // 检查插件文件是否存在
450
+ if (!fs.existsSync(pluginPath)) {
451
+ console.error(`错误: 插件文件不存在: ${pluginPath}`);
452
+ return;
453
+ }
454
+
455
+ try {
456
+ // 删除插件文件
457
+ fs.unlinkSync(pluginPath);
458
+
459
+ // 从配置中移除插件的禁用状态
460
+ pluginConfig.removePluginFromConfig(pluginName);
461
+
462
+ console.log(`已成功删除插件: ${pluginName}`);
463
+ console.log('提示: 请重新运行命令以应用更改');
464
+ } catch (error) {
465
+ console.error(`删除插件失败: ${error.message}`);
466
+ }
467
+ }
468
+
469
+ /**
470
+ * 管理插件配置
471
+ * @param {string} pluginName - 插件名称
472
+ * @param {string} key - 配置键
473
+ * @param {string} value - 配置值
474
+ */
475
+ managePluginConfig(pluginName, key, value) {
476
+ // 验证插件名称
477
+ if (!validator.validateCommandParam(pluginName)) {
478
+ console.error('错误: 插件名称包含无效字符');
479
+ return;
480
+ }
481
+
482
+ const config = require('../utils/plugin-config');
483
+
484
+ if (key && value) {
485
+ // 设置插件配置
486
+ config.setPluginConfig(pluginName, key, value);
487
+ console.log(`已设置插件 ${pluginName} 的配置: ${key} = ${value}`);
488
+ } else if (key) {
489
+ // 查看插件特定配置
490
+ const pluginConfigValue = config.getPluginConfig(pluginName, key);
491
+ if (pluginConfigValue !== undefined) {
492
+ console.log(`插件 ${pluginName} 的配置 ${key}: ${pluginConfigValue}`);
493
+ } else {
494
+ console.log(`插件 ${pluginName} 未设置配置 ${key}`);
495
+ }
496
+ } else {
497
+ // 查看插件所有配置
498
+ const pluginConfigs = config.getPluginConfigs(pluginName);
499
+ if (Object.keys(pluginConfigs).length > 0) {
500
+ console.log(`插件 ${pluginName} 的配置:`);
501
+ console.log('=====================================');
502
+ Object.entries(pluginConfigs).forEach(([k, v]) => {
503
+ console.log(`${k}: ${v}`);
504
+ });
505
+ console.log('=====================================');
506
+ } else {
507
+ console.log(`插件 ${pluginName} 暂无配置`);
508
+ }
509
+ }
510
+ }
511
+
512
+ /**
513
+ * 添加外部自定义插件
514
+ * @param {string} url - 插件URL或本地文件路径
515
+ */
516
+ async addExternalPlugin(url) {
517
+ const fs = require('fs-extra');
518
+ const path = require('path');
519
+ const axios = require('axios');
520
+
521
+ // 插件目录路径
522
+ const pluginsDir = path.join(__dirname, '../plugins');
523
+
524
+ try {
525
+ // 检查是URL还是本地文件
526
+ if (validator.validateUrl(url)) {
527
+ // 是URL,下载插件
528
+ console.log(`正在从URL下载插件: ${url}`);
529
+
530
+ // 从URL中提取插件文件名
531
+ const urlParts = url.split('/');
532
+ const pluginFileName = urlParts[urlParts.length - 1];
533
+ const pluginPath = path.join(pluginsDir, pluginFileName);
534
+
535
+ // 下载文件
536
+ const response = await axios.get(url, {
537
+ responseType: 'stream',
538
+ onDownloadProgress: (progressEvent) => {
539
+ const total = progressEvent.total;
540
+ const current = progressEvent.loaded;
541
+ if (total) {
542
+ const percentage = Math.round((current / total) * 100);
543
+ process.stdout.clearLine();
544
+ process.stdout.cursorTo(0);
545
+ process.stdout.write(`下载进度: ${percentage}%`);
546
+ }
547
+ }
548
+ });
549
+ const writer = fs.createWriteStream(pluginPath);
550
+
551
+ response.data.pipe(writer);
552
+
553
+ await new Promise((resolve, reject) => {
554
+ writer.on('finish', resolve);
555
+ writer.on('error', reject);
556
+ });
557
+
558
+ console.log(''); // 换行
559
+ console.log(`插件已成功下载到: ${pluginPath}`);
560
+ } else if (fs.existsSync(url) && url.endsWith('.js')) {
561
+ // 是本地文件,拷贝插件
562
+ console.log(`正在从本地文件拷贝插件: ${url}`);
563
+
564
+ // 提取文件名
565
+ const pluginFileName = path.basename(url);
566
+ const pluginPath = path.join(pluginsDir, pluginFileName);
567
+
568
+ // 拷贝文件
569
+ await fs.copy(url, pluginPath);
570
+
571
+ console.log(`插件已成功拷贝到: ${pluginPath}`);
572
+ } else {
573
+ console.error('错误: 无效的插件URL或本地文件路径');
574
+ return;
575
+ }
576
+ } catch (error) {
577
+ console.error('添加插件失败:', error.message);
578
+ }
579
+ }
580
+ }
581
+
582
+ // 导出单例实例
583
+ module.exports = new Core();