@xubill/xx-cli 1.0.1 → 1.0.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.
@@ -0,0 +1,444 @@
1
+ /**
2
+ * 核心命令实现
3
+ * 存放具体的命令实现逻辑,保持 index.js 清晰
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+ const validator = require('../utils/validator');
9
+ const pluginConfig = require('../utils/plugin-config');
10
+
11
+ class CoreCommands {
12
+ /**
13
+ * 创建插件
14
+ * @param {string} name - 插件名称
15
+ * @param {Object} options - 选项
16
+ */
17
+ async createPlugin(name, options) {
18
+ try {
19
+ console.log('创建插件模板...');
20
+
21
+ // 插件目录路径
22
+ const pluginsDir = path.join(__dirname, '../plugins');
23
+ const pluginFileName = `${name}.js`;
24
+ const pluginPath = path.join(pluginsDir, pluginFileName);
25
+
26
+ // 检查插件是否已存在
27
+ if (fs.existsSync(pluginPath)) {
28
+ console.log(`插件 ${name} 已存在,请使用其他名称`);
29
+ return;
30
+ }
31
+
32
+ // 生成插件模板内容
33
+ const pluginContent = this.generatePluginTemplate(name);
34
+
35
+ // 写入插件文件
36
+ await fs.writeFile(pluginPath, pluginContent);
37
+
38
+ console.log(`插件模板已成功创建到:${pluginPath}`);
39
+ console.log('提示:请编辑插件文件,实现具体功能');
40
+ console.log('测试方法:运行 "node bin/cli.js <插件命令>"');
41
+
42
+ } catch (error) {
43
+ console.error('创建插件模板失败:', error.message);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * 生成插件模板内容
49
+ * @param {string} name - 插件名称
50
+ * @returns {string} 插件模板内容
51
+ */
52
+ generatePluginTemplate(name) {
53
+ // 生成插件命令名称(去除后缀,转小写,连字符转驼峰)
54
+ const commandName = name.replace('.js', '').toLowerCase().replace(/-([a-z])/g, (g) => g[1].toUpperCase());
55
+
56
+ // 导入路径
57
+ const basePluginPath = '../core/base-plugin';
58
+
59
+ return `/**
60
+ * ${name} 插件
61
+ * 用于实现 ${name} 相关功能
62
+ *
63
+ * 命令说明:
64
+ * - ${commandName} [options]:${name} 相关功能
65
+ * - 示例:${commandName}
66
+ *
67
+ * 功能说明:
68
+ * - 实现 ${name} 相关功能
69
+ *
70
+ * 用户体验:
71
+ * - 使用标准化的用户提示
72
+ * - 提供清晰的错误提示
73
+ * - 支持命令行参数
74
+ */
75
+
76
+ const BasePlugin = require('${basePluginPath}');
77
+
78
+ class ${this.toCamelCase(name)}Plugin extends BasePlugin {
79
+ constructor(options = {}) {
80
+ super(options);
81
+ this.pluginName = '${name}';
82
+ }
83
+
84
+ /**
85
+ * 注册命令
86
+ * @param {Object} program - Commander.js 实例
87
+ */
88
+ registerCommands(program) {
89
+ program.command('${commandName}')
90
+ .description('${name} 相关功能')
91
+ .option('-v, --verbose', '显示详细信息')
92
+ .option('-h, --help', '显示帮助信息')
93
+ .action((options) => this.${commandName}Command(options));
94
+ }
95
+
96
+ /**
97
+ * ${name} 命令
98
+ * @param {Object} options - 命令选项
99
+ */
100
+ ${commandName}Command(options) {
101
+ console.log('${name} 命令执行成功!');
102
+ if (options && options.verbose) {
103
+ console.log('详细信息:');
104
+ console.log('- 命令名称:', '${commandName}');
105
+ console.log('- 执行时间:', new Date().toLocaleString());
106
+ }
107
+ }
108
+ }
109
+
110
+ module.exports = ${this.toCamelCase(name)}Plugin;
111
+ `;
112
+ }
113
+
114
+ /**
115
+ * 转换为驼峰命名
116
+ * @param {string} str - 字符串
117
+ * @returns {string} 驼峰命名的字符串
118
+ */
119
+ toCamelCase(str) {
120
+ return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase()).replace(/^[a-z]/, (g) => g.toUpperCase());
121
+ }
122
+
123
+ /**
124
+ * 配置管理
125
+ * @param {string} action - 操作类型
126
+ * @param {string} target - 目标路径
127
+ */
128
+ configManager(action, target) {
129
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
130
+ const xxDir = path.join(homeDir, '.xx-cli');
131
+
132
+ if (action === 'export') {
133
+ // 导出配置
134
+ const exportDir = target || path.join(process.cwd(), 'config-backup');
135
+ console.log(`开始导出配置到: ${exportDir}`);
136
+
137
+ // 确保导出目录存在
138
+ fs.ensureDirSync(exportDir);
139
+
140
+ // 复制 .xx 目录
141
+ if (fs.existsSync(xxDir)) {
142
+ const exportPath = path.join(exportDir, '.xx-cli');
143
+ fs.ensureDirSync(path.dirname(exportPath));
144
+ fs.copySync(xxDir, exportPath);
145
+ console.log(`导出目录: ${xxDir}`);
146
+ }
147
+
148
+ console.log('配置导出完成!');
149
+ } else if (action === 'import') {
150
+ // 导入配置
151
+ const importDir = target || path.join(process.cwd(), 'config-backup');
152
+ console.log(`开始从 ${importDir} 导入配置...`);
153
+
154
+ // 检查导入目录是否存在
155
+ if (!fs.existsSync(importDir)) {
156
+ console.log(`导入目录不存在: ${importDir}`);
157
+ return;
158
+ }
159
+
160
+ // 复制配置到 .xx 目录
161
+ const importPath = path.join(importDir, '.xx-cli');
162
+ if (fs.existsSync(importPath)) {
163
+ fs.ensureDirSync(path.dirname(xxDir));
164
+ fs.copySync(importPath, xxDir, { overwrite: true });
165
+ console.log(`导入目录: ${importPath}`);
166
+ }
167
+
168
+ console.log('配置导入完成!');
169
+ } else if (action === 'list') {
170
+ // 列出配置
171
+ console.log('列出所有插件配置文件...\n');
172
+
173
+ if (fs.existsSync(xxDir)) {
174
+ const items = fs.readdirSync(xxDir).filter(item => !item.startsWith('.'));
175
+ items.forEach(item => {
176
+ const itemPath = path.join(xxDir, item);
177
+ const stat = fs.statSync(itemPath);
178
+ if (stat.isDirectory()) {
179
+ console.log(`插件配置目录: ${itemPath}`);
180
+ const configFiles = fs.readdirSync(itemPath).filter(file => !file.startsWith('.'));
181
+ configFiles.forEach(file => {
182
+ console.log(` - ${file}`);
183
+ });
184
+ } else if (stat.isFile()) {
185
+ console.log(`配置文件: ${itemPath}`);
186
+ }
187
+ });
188
+ } else {
189
+ console.log('配置目录不存在');
190
+ }
191
+
192
+ console.log('配置列表完成!');
193
+ }
194
+ }
195
+
196
+ /**
197
+ * 列出所有插件
198
+ */
199
+ listPlugins(core) {
200
+ const pluginsInfo = core.getPluginsInfo();
201
+ console.log('\n可用插件列表:');
202
+ console.log('=====================================');
203
+ pluginsInfo.forEach(plugin => {
204
+ const isDisabled = pluginConfig.isPluginDisabled(plugin.name);
205
+ const status = isDisabled ? '[已禁用]' : '[已启用]';
206
+ console.log(`- ${plugin.name} ${status}`);
207
+ });
208
+ console.log('=====================================');
209
+ console.log(`总计: ${pluginsInfo.length} 个插件\n`);
210
+ }
211
+
212
+ /**
213
+ * 启用插件
214
+ * @param {string} pluginName - 插件名称
215
+ */
216
+ enablePlugin(pluginName) {
217
+ // 验证插件名称
218
+ if (!validator.validateCommandParam(pluginName)) {
219
+ console.error('错误: 插件名称包含无效字符');
220
+ return;
221
+ }
222
+
223
+ // 启用插件
224
+ pluginConfig.enablePlugin(pluginName);
225
+ console.log(`已启用插件: ${pluginName}`);
226
+ console.log('提示: 请重新运行命令以应用更改');
227
+ }
228
+
229
+ /**
230
+ * 禁用插件
231
+ * @param {string} pluginName - 插件名称
232
+ */
233
+ disablePlugin(pluginName) {
234
+ // 验证插件名称
235
+ if (!validator.validateCommandParam(pluginName)) {
236
+ console.error('错误: 插件名称包含无效字符');
237
+ return;
238
+ }
239
+
240
+ // 禁用插件
241
+ pluginConfig.disablePlugin(pluginName);
242
+ console.log(`已禁用插件: ${pluginName}`);
243
+ console.log('提示: 请重新运行命令以应用更改');
244
+ }
245
+
246
+ /**
247
+ * 删除插件
248
+ * @param {string} pluginName - 插件名称
249
+ */
250
+ removePlugin(pluginName) {
251
+ // 验证插件名称
252
+ if (!validator.validateCommandParam(pluginName)) {
253
+ console.error('错误: 插件名称包含无效字符');
254
+ return;
255
+ }
256
+
257
+ const fs = require('fs');
258
+ const path = require('path');
259
+
260
+ // 插件目录路径
261
+ const pluginsDir = path.join(__dirname, '../plugins');
262
+
263
+ // 构建插件文件路径
264
+ const pluginFileName = `${pluginName}.js`;
265
+ const pluginPath = path.join(pluginsDir, pluginFileName);
266
+
267
+ // 检查插件文件是否存在
268
+ if (!fs.existsSync(pluginPath)) {
269
+ console.error(`错误: 插件文件不存在: ${pluginPath}`);
270
+ return;
271
+ }
272
+
273
+ try {
274
+ // 删除插件文件
275
+ fs.unlinkSync(pluginPath);
276
+
277
+ // 从配置中移除插件的禁用状态
278
+ pluginConfig.removePluginFromConfig(pluginName);
279
+
280
+ console.log(`已成功删除插件: ${pluginName}`);
281
+ console.log('提示: 请重新运行命令以应用更改');
282
+ } catch (error) {
283
+ console.error(`删除插件失败: ${error.message}`);
284
+ }
285
+ }
286
+
287
+ /**
288
+ * 管理插件配置
289
+ * @param {string} pluginName - 插件名称
290
+ * @param {string} key - 配置键
291
+ * @param {string} value - 配置值
292
+ */
293
+ managePluginConfig(pluginName, key, value) {
294
+ // 验证插件名称
295
+ if (!validator.validateCommandParam(pluginName)) {
296
+ console.error('错误: 插件名称包含无效字符');
297
+ return;
298
+ }
299
+
300
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
301
+ const xxDir = path.join(homeDir, '.xx-cli');
302
+ const pluginConfigDir = path.join(xxDir, pluginName);
303
+ const configFile = path.join(pluginConfigDir, 'config.json');
304
+
305
+ // 确保插件配置目录存在
306
+ fs.ensureDirSync(pluginConfigDir);
307
+
308
+ // 读取现有配置
309
+ let config = {};
310
+ if (fs.existsSync(configFile)) {
311
+ config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
312
+ }
313
+
314
+ if (!key) {
315
+ // 显示所有配置
316
+ console.log(`插件 ${pluginName} 的配置:`);
317
+ console.log('=====================================');
318
+ if (Object.keys(config).length > 0) {
319
+ Object.entries(config).forEach(([k, v]) => {
320
+ console.log(`${k}: ${v}`);
321
+ });
322
+ } else {
323
+ console.log('无配置项');
324
+ }
325
+ console.log('=====================================');
326
+ } else if (!value) {
327
+ // 显示指定配置
328
+ console.log(`插件 ${pluginName} 的配置 ${key}:`);
329
+ console.log('=====================================');
330
+ if (config.hasOwnProperty(key)) {
331
+ console.log(`${key}: ${config[key]}`);
332
+ } else {
333
+ console.log(`配置项 ${key} 不存在`);
334
+ }
335
+ console.log('=====================================');
336
+ } else {
337
+ // 设置配置
338
+ config[key] = value;
339
+ fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
340
+ console.log(`已设置插件 ${pluginName} 的配置 ${key} = ${value}`);
341
+ }
342
+ }
343
+
344
+ /**
345
+ * 添加外部插件
346
+ * @param {string} url - 插件 URL
347
+ */
348
+ async addExternalPlugin(url) {
349
+ try {
350
+ const fs = require('fs');
351
+ const path = require('path');
352
+ const https = require('https');
353
+ const http = require('http');
354
+
355
+ // 插件目录路径
356
+ const pluginsDir = path.join(__dirname, '../plugins');
357
+
358
+ // 确保插件目录存在
359
+ fs.ensureDirSync(pluginsDir);
360
+
361
+ // 提取插件名称
362
+ const pluginFileName = path.basename(url);
363
+ const pluginPath = path.join(pluginsDir, pluginFileName);
364
+
365
+ // 检查插件是否已存在
366
+ if (fs.existsSync(pluginPath)) {
367
+ console.log(`插件 ${pluginFileName} 已存在,是否覆盖?`);
368
+ // 这里可以添加交互式确认
369
+ console.log('已覆盖现有插件');
370
+ }
371
+
372
+ // 下载或复制插件
373
+ if (url.startsWith('http://') || url.startsWith('https://')) {
374
+ // 是 URL,下载插件
375
+ console.log(`正在从 URL 下载插件: ${url}`);
376
+
377
+ // 选择 HTTP 或 HTTPS
378
+ const protocol = url.startsWith('https://') ? https : http;
379
+
380
+ await new Promise((resolve, reject) => {
381
+ protocol.get(url, (response) => {
382
+ if (response.statusCode === 200) {
383
+ // 获取文件大小
384
+ const fileSize = parseInt(response.headers['content-length'], 10);
385
+ let downloadedSize = 0;
386
+
387
+ // 创建写入流
388
+ const writer = fs.createWriteStream(pluginPath);
389
+
390
+ // 监听数据事件,显示进度
391
+ response.on('data', (chunk) => {
392
+ downloadedSize += chunk.length;
393
+ const percentage = Math.round((downloadedSize / fileSize) * 100);
394
+
395
+ // 清除当前行并显示进度
396
+ process.stdout.clearLine();
397
+ process.stdout.cursorTo(0);
398
+ process.stdout.write(`下载进度: ${percentage}%`);
399
+ });
400
+
401
+ // 管道数据到文件
402
+ response.pipe(writer);
403
+
404
+ // 监听完成事件
405
+ writer.on('finish', () => {
406
+ console.log(''); // 换行
407
+ console.log(`插件已成功下载到: ${pluginPath}`);
408
+ resolve();
409
+ });
410
+
411
+ // 监听错误事件
412
+ writer.on('error', (error) => {
413
+ reject(error);
414
+ });
415
+ } else {
416
+ reject(new Error(`下载失败,状态码: ${response.statusCode}`));
417
+ }
418
+ }).on('error', (error) => {
419
+ reject(error);
420
+ });
421
+ });
422
+ } else if (fs.existsSync(url) && url.endsWith('.js')) {
423
+ // 是本地文件,拷贝插件
424
+ console.log(`正在从本地文件拷贝插件: ${url}`);
425
+
426
+ // 提取文件名
427
+ const pluginFileName = path.basename(url);
428
+ const pluginPath = path.join(pluginsDir, pluginFileName);
429
+
430
+ // 拷贝文件
431
+ await fs.copy(url, pluginPath);
432
+
433
+ console.log(`插件已成功拷贝到: ${pluginPath}`);
434
+ } else {
435
+ console.error('错误: 无效的插件URL或本地文件路径');
436
+ return;
437
+ }
438
+ } catch (error) {
439
+ console.error('添加插件失败:', error.message);
440
+ }
441
+ }
442
+ }
443
+
444
+ module.exports = new CoreCommands();
package/lib/core/index.js CHANGED
@@ -9,6 +9,7 @@ const logger = require('../utils/logger');
9
9
  const pkg = require('../../package.json');
10
10
  const validator = require('../utils/validator');
11
11
  const pluginConfig = require('../utils/plugin-config');
12
+ const coreCommands = require('./commands');
12
13
 
13
14
  class Core {
14
15
  constructor() {
@@ -44,7 +45,6 @@ class Core {
44
45
  this.pluginFiles = pluginFiles.filter(file => file.endsWith('.js'));
45
46
  }
46
47
  }
47
-
48
48
  /**
49
49
  * 加载单个插件
50
50
  * @param {string} pluginFile - 插件文件名
@@ -81,7 +81,6 @@ class Core {
81
81
  return null;
82
82
  }
83
83
  }
84
-
85
84
  /**
86
85
  * 获取所有插件信息
87
86
  * @returns {Array} 插件信息数组
@@ -335,6 +334,33 @@ class Core {
335
334
  .option('-s, --search <keyword>', '搜索命令历史记录')
336
335
  .action((options) => this.showHistory(options));
337
336
 
337
+ // 创建插件命令
338
+ program
339
+ .command('create-plugin <name>')
340
+ .alias('crp')
341
+ .description('创建指定名称的插件模板')
342
+ .action(async (name, options) => this.createPlugin(name, options));
343
+
344
+ // 配置管理命令
345
+ const configCommand = program.command('config-manager')
346
+ .alias('conf')
347
+ .description('管理所有插件的配置文件');
348
+
349
+ // 导出配置命令
350
+ configCommand.command('export [target]')
351
+ .description('导出所有插件配置到指定目录,默认导出到当前目录的 config-backup 文件夹')
352
+ .action((target) => this.configManager('export', target));
353
+
354
+ // 导入配置命令
355
+ configCommand.command('import [source]')
356
+ .description('从指定目录导入配置并覆盖,默认从当前目录的 config-backup 文件夹导入')
357
+ .action((source) => this.configManager('import', source));
358
+
359
+ // 列出配置命令
360
+ configCommand.command('list')
361
+ .description('列出所有插件的配置文件位置和状态')
362
+ .action(() => this.configManager('list'));
363
+
338
364
  // 插件管理命令
339
365
  const pluginCommand = program.command('plugin')
340
366
  .alias('p')
@@ -375,20 +401,29 @@ class Core {
375
401
  .action((plugin, key, value) => this.managePluginConfig(plugin, key, value));
376
402
  }
377
403
 
404
+ /**
405
+ * 创建插件
406
+ * @param {string} name - 插件名称
407
+ * @param {Object} options - 选项
408
+ */
409
+ async createPlugin(name, options) {
410
+ await coreCommands.createPlugin(name, options);
411
+ }
412
+
413
+ /**
414
+ * 配置管理
415
+ * @param {string} action - 操作类型
416
+ * @param {string} target - 目标路径
417
+ */
418
+ configManager(action, target) {
419
+ coreCommands.configManager(action, target);
420
+ }
421
+
378
422
  /**
379
423
  * 列出所有插件
380
424
  */
381
425
  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`);
426
+ coreCommands.listPlugins(this);
392
427
  }
393
428
 
394
429
  /**
@@ -396,16 +431,7 @@ class Core {
396
431
  * @param {string} pluginName - 插件名称
397
432
  */
398
433
  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('提示: 请重新运行命令以应用更改');
434
+ coreCommands.enablePlugin(pluginName);
409
435
  }
410
436
 
411
437
  /**
@@ -413,16 +439,7 @@ class Core {
413
439
  * @param {string} pluginName - 插件名称
414
440
  */
415
441
  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('提示: 请重新运行命令以应用更改');
442
+ coreCommands.disablePlugin(pluginName);
426
443
  }
427
444
 
428
445
  /**
@@ -430,40 +447,7 @@ class Core {
430
447
  * @param {string} pluginName - 插件名称
431
448
  */
432
449
  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
- }
450
+ coreCommands.removePlugin(pluginName);
467
451
  }
468
452
 
469
453
  /**
@@ -473,40 +457,7 @@ class Core {
473
457
  * @param {string} value - 配置值
474
458
  */
475
459
  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
- }
460
+ coreCommands.managePluginConfig(pluginName, key, value);
510
461
  }
511
462
 
512
463
  /**
@@ -514,68 +465,7 @@ class Core {
514
465
  * @param {string} url - 插件URL或本地文件路径
515
466
  */
516
467
  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
- }
468
+ await coreCommands.addExternalPlugin(url);
579
469
  }
580
470
  }
581
471
 
@@ -16,7 +16,7 @@ class ConfigManager {
16
16
  */
17
17
  constructor(pluginName, options = {}) {
18
18
  this.homeDir = process.env.HOME || process.env.USERPROFILE;
19
- this.globalConfigDir = path.join(this.homeDir, '.xx', pluginName);
19
+ this.globalConfigDir = path.join(this.homeDir, '.xx-cli', pluginName);
20
20
  this.localConfigPath = options.localConfigPath || `.${pluginName}Config`;
21
21
  this.ensureConfigDir();
22
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xubill/xx-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "个人工具集",
5
5
  "main": "lib/core/index.js",
6
6
  "bin": {
package/readme.md CHANGED
@@ -1,10 +1,436 @@
1
1
  # xx-cli
2
2
 
3
3
  个人开发工具集,支持插件系统,提供常用开发工具功能。
4
- ```
5
- 全局安装 xx-cli
4
+
5
+ ## 安装与使用
6
+
7
+ ```bash
8
+ # 全局安装 xx-cli
6
9
  npm i @xubill/xx-cli
7
10
 
8
- 添加插件
11
+ # 添加插件
9
12
  xx p add xxxx.js
13
+
14
+ # 使用命令
15
+ xx <command> [options]
16
+ ```
17
+
18
+ ## 版本记录
19
+
20
+ ### v1.0.0
21
+ - 初始化项目,搭建基本框架
22
+ - 实现插件系统核心功能
23
+ - 添加基础命令(help, version, history)
24
+
25
+
26
+ ### v1.0.1
27
+ - 文档更新
28
+
29
+ ### v1.0.2
30
+ - 创建插件和配置管理插件移动壳功能
31
+ - 完善文档
32
+
33
+ ## 框架自带命令
34
+
35
+ xx-cli 框架自带以下核心命令(位于 core 模块中):
36
+
37
+ ### 基础命令
38
+
39
+ | 命令 | 别名 | 描述 |
40
+ |------|------|------|
41
+ | `help` | - | 显示帮助信息 |
42
+ | `version` | `v` | 显示版本信息 |
43
+ | `history` | `h` | 查看命令历史记录 |
44
+
45
+ ### 插件管理命令
46
+
47
+ | 命令 | 别名 | 描述 |
48
+ |------|------|------|
49
+ | `create-plugin <name>` | `crp <name>` | 创建指定名称的插件模板 |
50
+ | `plugin list` | `p ls` | 列出所有插件 |
51
+ | `plugin enable <plugin>` | `p en <plugin>` | 启用插件 |
52
+ | `plugin disable <plugin>` | `p dis <plugin>` | 禁用插件 |
53
+ | `plugin add <url>` | `p add <url>` | 添加外部自定义插件 |
54
+ | `plugin remove <plugin>` | `p rm <plugin>` | 删除指定插件 |
55
+ | `plugin config <plugin> [key] [value]` | `p c <plugin> [key] [value]` | 查看和设置插件配置 |
56
+
57
+ ### 配置管理命令
58
+
59
+ | 命令 | 别名 | 描述 |
60
+ |------|------|------|
61
+ | `config-manager export [target]` | `conf export [target]` | 导出所有插件配置到指定目录 |
62
+ | `config-manager import [source]` | `conf import [source]` | 从指定目录导入配置并覆盖 |
63
+ | `config-manager list` | `conf list` | 列出所有插件的配置文件位置和状态 |
64
+
65
+ ## 插件开发
66
+
67
+ 本部分将详细介绍如何在 xx-cli 中创建、开发和部署插件。
68
+
69
+ ### 创建插件
70
+
71
+ #### 使用 create-plugin 命令
72
+
73
+ xx-cli 提供了 `create-plugin` 命令,可以快速生成插件模板。这是推荐的创建插件的方式。
74
+
75
+ ##### 基本用法
76
+
77
+ ```bash
78
+ # 创建插件到插件目录
79
+ xx create-plugin <plugin-name>
80
+
81
+ # 或者使用别名
82
+ xx crp <plugin-name>
83
+ ```
84
+
85
+ ##### 示例
86
+
87
+ ```bash
88
+ # 创建一个名为 `hello-world` 的插件
89
+ xx create-plugin hello-world
90
+
91
+ # 输出:
92
+ # 创建插件模板...
93
+ # 插件模板已成功创建到:/Users/hthj/xx-cli/lib/plugins/hello-world.js
94
+ # 提示:请编辑插件文件,实现具体功能
95
+ # 测试方法:运行 "node bin/cli.js <插件命令>"
96
+ ```
97
+
98
+ #### 手动创建插件
99
+
100
+ 如果需要更灵活地控制插件结构,也可以手动创建插件文件。
101
+
102
+ 1. 在 `lib/plugins` 目录下创建一个新的 JavaScript 文件,例如 `my-plugin.js`
103
+ 2. 实现插件类,包含必要的方法
104
+
105
+ ### 插件结构
106
+
107
+ #### 基本插件结构
108
+
109
+ 使用 `create-plugin` 命令生成的插件模板包含以下结构:
110
+
111
+ ```javascript
112
+ /**
113
+ * hello-world 插件
114
+ * 用于实现 hello-world 相关功能
115
+ *
116
+ * 命令说明:
117
+ * - helloWorld [options]:hello-world 相关功能
118
+ * - 示例:helloWorld
119
+ *
120
+ * 功能说明:
121
+ * - 实现 hello-world 相关功能
122
+ *
123
+ * 用户体验:
124
+ * - 使用标准化的用户提示
125
+ * - 提供清晰的错误提示
126
+ * - 支持命令行参数
127
+ */
128
+
129
+ const BasePlugin = require('../core/base-plugin');
130
+
131
+ class HelloWorldPlugin extends BasePlugin {
132
+ constructor(options = {}) {
133
+ super(options);
134
+ this.pluginName = 'hello-world';
135
+ }
136
+
137
+ /**
138
+ * 注册命令
139
+ * @param {Object} program - Commander.js 实例
140
+ */
141
+ registerCommands(program) {
142
+ program.command('helloWorld')
143
+ .description('hello-world 相关功能')
144
+ .option('-v, --verbose', '显示详细信息')
145
+ .option('-h, --help', '显示帮助信息')
146
+ .action((options) => this.helloWorldCommand(options));
147
+ }
148
+
149
+ /**
150
+ * hello-world 命令
151
+ * @param {Object} options - 命令选项
152
+ */
153
+ helloWorldCommand(options) {
154
+ console.log('hello-world 命令执行成功!');
155
+ if (options && options.verbose) {
156
+ console.log('详细信息:');
157
+ console.log('- 命令名称:', 'helloWorld');
158
+ console.log('- 执行时间:', new Date().toLocaleString());
159
+ }
160
+ }
161
+ }
162
+
163
+ module.exports = HelloWorldPlugin;
164
+ ```
165
+
166
+ #### 命令注册
167
+
168
+ 插件必须实现 `registerCommands` 方法,用于向 Commander.js 注册命令。
169
+
170
+ ##### 基本命令注册
171
+
172
+ ```javascript
173
+ registerCommands(program) {
174
+ program.command('helloWorld')
175
+ .description('hello-world 相关功能')
176
+ .action(() => this.helloWorldCommand());
177
+ }
178
+ ```
179
+
180
+ ##### 带选项的命令注册
181
+
182
+ ```javascript
183
+ registerCommands(program) {
184
+ program.command('helloWorld [alias]')
185
+ .alias('hw')
186
+ .description('hello-world 相关功能')
187
+ .option('-v, --verbose', '显示详细信息')
188
+ .option('-n, --name <name>', '指定名称')
189
+ .action((alias, options) => this.helloWorldCommand(alias, options));
190
+ }
191
+ ```
192
+
193
+ #### 命令选项
194
+
195
+ 可以为命令添加各种选项,以增强命令的灵活性。
196
+
197
+ ##### 常用选项类型
198
+
199
+ - **布尔选项**:不需要参数的选项
200
+ ```javascript
201
+ .option('-v, --verbose', '显示详细信息')
202
+ ```
203
+
204
+ - **值选项**:需要参数的选项
205
+ ```javascript
206
+ .option('-n, --name <name>', '指定名称')
207
+ ```
208
+
209
+ - **数组选项**:可以接收多个值的选项
210
+ ```javascript
211
+ .option('-a, --array <items...>', '指定数组参数')
212
+ ```
213
+
214
+ - **带默认值的选项**:
215
+ ```javascript
216
+ .option('-p, --port <port>', '指定端口', '3000')
217
+ ```
218
+
219
+ - **带验证器的选项**:
220
+ ```javascript
221
+ .option('-p, --port <port>', '指定端口', parseInt)
222
+ ```
223
+
224
+ ### 插件配置
225
+
226
+ 插件可以使用配置文件来存储和管理配置信息。
227
+
228
+ #### 配置文件结构
229
+
230
+ 配置文件通常存储在用户主目录的 `.xx` 文件夹中,例如:
231
+
232
+ ```
233
+ ~/.xx/
234
+ └── hello-world/
235
+ └── config.json
236
+ ```
237
+
238
+ #### 读取配置
239
+
240
+ ```javascript
241
+ const fs = require('fs');
242
+ const path = require('path');
243
+
244
+ // 获取用户主目录
245
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
246
+ const xxDir = path.join(homeDir, '.xx-cli');
247
+ const pluginConfigDir = path.join(xxDir, this.pluginName);
248
+ const configFile = path.join(pluginConfigDir, 'config.json');
249
+
250
+ // 读取配置
251
+ let config = {};
252
+ if (fs.existsSync(configFile)) {
253
+ config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
254
+ }
255
+ ```
256
+
257
+ #### 写入配置
258
+
259
+ ```javascript
260
+ const fs = require('fs');
261
+ const path = require('path');
262
+
263
+ // 确保配置目录存在
264
+ fs.ensureDirSync(pluginConfigDir);
265
+
266
+ // 写入配置
267
+ fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
268
+ ```
269
+
270
+ ### 基础插件类
271
+
272
+ xx-cli 提供了 `BasePlugin` 基类,所有插件都应该继承此类以确保一致性和获得通用功能支持。
273
+
274
+ #### 基础插件类路径
275
+
276
+ `BasePlugin` 类位于 `/Users/hthj/xx-cli/lib/core/base-plugin.js`,插件可以通过以下方式导入:
277
+
278
+ ```javascript
279
+ const BasePlugin = require('../core/base-plugin');
280
+ ```
281
+
282
+ #### 核心功能
283
+
284
+ `BasePlugin` 类提供了以下核心功能:
285
+
286
+ ##### 1. 命令注册
287
+
288
+ - **`registerCommands(program)`**:注册命令到 Commander.js 实例,子类必须实现此方法
289
+
290
+ ##### 2. 插件初始化
291
+
292
+ - **`init()`**:插件初始化逻辑,子类可以覆盖
293
+ - **`getInfo()`**:获取插件信息
294
+
295
+ ##### 3. 用户界面
296
+
297
+ - **`startLoading(text)`**:显示加载状态
298
+ - **`stopLoading(text)`**:停止加载状态
299
+ - **`showError(text)`**:显示错误信息
300
+ - **`startProgressBar(text, total)`**:启动进度条
301
+ - **`updateProgressBar(current, text)`**:更新进度条
302
+ - **`stopProgressBar(text)`**:停止进度条
303
+ - **`showSuccess(text)`**:显示成功信息
304
+ - **`showWarning(text)`**:显示警告信息
305
+ - **`showInfo(text)`**:显示信息
306
+
307
+ ##### 4. 配置管理
308
+
309
+ - **`loadConfig(configName, cliOptions)`**:加载配置
310
+ - **`saveConfig(config, configName, isGlobal)`**:保存配置
311
+
312
+ ##### 5. 错误处理
313
+
314
+ - **`handleError(error, message)`**:处理错误
315
+
316
+ ##### 6. 异步操作
317
+
318
+ - **`executeAsync(fn, loadingText, successText, errorText)`**:执行异步操作并显示加载状态
319
+
320
+ ##### 7. 钩子系统
321
+
322
+ - **`registerHook(event, callback)`**:注册钩子
323
+ - **`triggerHook(event, data)`**:触发钩子
324
+ - **`hasHook(event)`**:检查是否注册了指定钩子
325
+
326
+ ##### 8. 剪贴板操作
327
+
328
+ - **`copyToClipboard(text, successMessage)`**:复制文本到剪贴板
329
+
330
+ #### 使用示例
331
+
332
+ ```javascript
333
+ const BasePlugin = require('../core/base-plugin');
334
+
335
+ class MyPlugin extends BasePlugin {
336
+ constructor(options = {}) {
337
+ super(options);
338
+ this.pluginName = 'my-plugin';
339
+ }
340
+
341
+ registerCommands(program) {
342
+ program.command('mycommand')
343
+ .description('My plugin command')
344
+ .action(() => this.myCommand());
345
+ }
346
+
347
+ async myCommand() {
348
+ try {
349
+ await this.executeAsync(
350
+ async () => {
351
+ // 执行异步操作
352
+ await new Promise(resolve => setTimeout(resolve, 1000));
353
+ return '操作结果';
354
+ },
355
+ '执行操作...',
356
+ '操作成功',
357
+ '操作失败'
358
+ );
359
+ } catch (error) {
360
+ this.handleError(error, '执行命令失败');
361
+ }
362
+ }
363
+ }
364
+
365
+ module.exports = MyPlugin;
366
+ ```
367
+
368
+ ### 插件生命周期
369
+
370
+ 插件的生命周期包括:
371
+
372
+ 1. **初始化**:插件被加载时
373
+ 2. **命令注册**:调用 `registerCommands` 方法
374
+ 3. **命令执行**:用户运行插件命令时
375
+ 4. **清理**:命令执行完成后
376
+
377
+ ### 测试插件
378
+
379
+ #### 本地测试
380
+
381
+ 1. 创建插件:
382
+ ```bash
383
+ xx create-plugin <plugin-name>
384
+ ```
385
+
386
+ 2. 编辑插件文件,实现具体功能
387
+
388
+ 3. 运行插件命令:
389
+ ```bash
390
+ # 直接运行
391
+ node bin/cli.js <plugin-command>
392
+
393
+ # 或者使用 xx 命令(如果已全局安装)
394
+ xx <plugin-command>
395
+ ```
396
+
397
+ #### 测试命令选项
398
+
399
+ ```bash
400
+ # 测试布尔选项
401
+ xx <plugin-command> --verbose
402
+
403
+ # 测试值选项
404
+ xx <plugin-command> --name John
405
+
406
+ # 测试组合选项
407
+ xx <plugin-command> --verbose --name John
408
+ ```
409
+
410
+ ### 部署插件
411
+
412
+ #### 发布到插件目录
413
+
414
+ 1. 确保插件文件位于 `lib/plugins` 目录
415
+ 2. 运行 `xx plugin list` 命令,确认插件已被识别
416
+ 3. 插件会自动加载并注册到命令系统中
417
+
418
+ #### 分享插件
419
+
420
+ 如果要与他人分享插件,可以:
421
+
422
+ 1. 将插件文件发送给他人
423
+ 2. 他人将插件文件放入其 `lib/plugins` 目录
424
+ 3. 运行 `xx plugin list` 命令,确认插件已被识别
425
+
426
+ #### 从外部添加插件
427
+
428
+ 可以使用 `plugin add` 命令从 URL 或本地文件添加插件:
429
+
430
+ ```bash
431
+ # 从 URL 添加插件
432
+ xx plugin add https://example.com/my-plugin.js
433
+
434
+ # 从本地文件添加插件
435
+ xx plugin add /path/to/my-plugin.js
10
436
  ```