@xubill/xx-cli 1.0.6 → 2.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.
package/bin/cli.js CHANGED
@@ -60,12 +60,14 @@
60
60
  // 全局错误处理
61
61
  process.on('uncaughtException', (error) => {
62
62
  console.error(chalk.red('错误:'), error.message);
63
+ console.error(chalk.red('错误堆栈:'), error.stack);
63
64
  console.error(chalk.yellow('提示:'), '请检查命令参数是否正确,或使用 --help 查看帮助信息');
64
65
  process.exit(1);
65
66
  });
66
67
 
67
68
  process.on('unhandledRejection', (error) => {
68
69
  console.error(chalk.red('错误:'), error.message);
70
+ console.error(chalk.red('错误堆栈:'), error.stack);
69
71
  console.error(chalk.yellow('提示:'), '请检查命令参数是否正确,或使用 --help 查看帮助信息');
70
72
  process.exit(1);
71
73
  });
package/lib/core/index.js CHANGED
@@ -8,6 +8,7 @@ const path = require("path");
8
8
  const logger = require("../utils/logger");
9
9
  const pkg = require("../../package.json");
10
10
  const pluginConfig = require("../utils/plugin-config");
11
+ const { createPluginsHelper } = require('../utils/plugins-helper');
11
12
 
12
13
  class Core {
13
14
  constructor() {
@@ -42,7 +43,7 @@ class Core {
42
43
  const userPluginsDir = path.join(
43
44
  process.env.HOME || process.env.USERPROFILE,
44
45
  ".xx-cli",
45
- "plugins"
46
+ "plugins",
46
47
  );
47
48
 
48
49
  // 确保用户插件目录存在
@@ -64,10 +65,10 @@ class Core {
64
65
  // 将用户插件添加到插件文件列表中
65
66
  pluginFiles = [...pluginFiles, ...userPluginFiles];
66
67
  }
67
-
68
68
  // 只收集插件文件信息,不立即加载
69
69
  this.pluginFiles = pluginFiles;
70
70
  }
71
+
71
72
  /**
72
73
  * 加载单个插件
73
74
  * @param {string} pluginFile - 插件文件名
@@ -85,7 +86,7 @@ class Core {
85
86
  const userPluginsDir = path.join(
86
87
  process.env.HOME || process.env.USERPROFILE,
87
88
  ".xx-cli",
88
- "plugins"
89
+ "plugins",
89
90
  );
90
91
 
91
92
  // 优先从内置插件目录加载
@@ -111,14 +112,14 @@ class Core {
111
112
  const coreDir = path.join(__dirname, "../core");
112
113
  pluginContent = pluginContent.replace(
113
114
  /require\('\.\.\/core\//g,
114
- `require('${coreDir}/`
115
+ `require('${coreDir}/`,
115
116
  );
116
117
 
117
118
  // 替换 ../utils 为绝对路径
118
119
  const utilsDir = path.join(__dirname, "../utils");
119
120
  pluginContent = pluginContent.replace(
120
121
  /require\('\.\.\/utils\//g,
121
- `require('${utilsDir}/`
122
+ `require('${utilsDir}/`,
122
123
  );
123
124
 
124
125
  // 创建一个临时模块来加载修改后的插件内容
@@ -132,8 +133,17 @@ class Core {
132
133
 
133
134
  const Plugin = tempModule.exports;
134
135
 
135
- // 检查是否为类
136
- if (typeof Plugin === "function" && Plugin.prototype) {
136
+ // 检查是否为新格式的插件对象
137
+ if (
138
+ Plugin &&
139
+ typeof Plugin === "object" &&
140
+ Plugin.name &&
141
+ (Plugin.action || Plugin.subcommands)
142
+ ) {
143
+ // 新格式插件对象,不需要实例化
144
+ this.pluginCache.set(pluginFile, Plugin);
145
+ return Plugin;
146
+ } else if (typeof Plugin === "function" && Plugin.prototype) {
137
147
  // 实例化类插件
138
148
  const pluginInstance = new Plugin();
139
149
  pluginInstance.pluginName =
@@ -144,16 +154,28 @@ class Core {
144
154
  return pluginInstance;
145
155
  } else {
146
156
  // 保持非类插件的兼容性
147
-
148
- // 缓存插件
149
- this.pluginCache.set(pluginFile, Plugin);
150
- return Plugin;
157
+ // 对于函数式插件,也可能是新格式
158
+ if (
159
+ Plugin &&
160
+ typeof Plugin === "object" &&
161
+ Plugin.name &&
162
+ (Plugin.action || Plugin.subcommands)
163
+ ) {
164
+ // 新格式插件对象
165
+ this.pluginCache.set(pluginFile, Plugin);
166
+ return Plugin;
167
+ } else {
168
+ // 旧格式函数式插件
169
+ this.pluginCache.set(pluginFile, Plugin);
170
+ return Plugin;
171
+ }
151
172
  }
152
173
  } catch (error) {
153
174
  logger.error(`加载插件 ${pluginFile} 失败: ${error.message}`);
154
175
  return null;
155
176
  }
156
177
  }
178
+
157
179
  /**
158
180
  * 获取所有插件信息
159
181
  * @returns {Array} 插件信息数组
@@ -187,15 +209,29 @@ class Core {
187
209
  }
188
210
 
189
211
  const plugin = this.loadPlugin(pluginFile);
190
- if (plugin && plugin.registerCommands) {
212
+ if (plugin) {
191
213
  try {
192
- plugin.registerCommands(program);
214
+ // 检查是否为新格式插件
215
+ if (
216
+ plugin.name &&
217
+ (typeof plugin.action === "function" ||
218
+ (plugin.subcommands && Array.isArray(plugin.subcommands)))
219
+ ) {
220
+ this.registerNewFormatPlugin(program, plugin, pluginName);
221
+ } else if (plugin.registerCommands) {
222
+ // 旧格式插件
223
+ plugin.registerCommands(program);
224
+ }
193
225
  } catch (error) {
194
226
  // 捕获命令别名冲突等错误,确保其他插件能够正常注册
195
- if (error.message && error.message.includes('cannot add alias')) {
196
- console.warn(`警告: 插件 ${pluginName} 的命令别名与其他插件冲突,已跳过此插件的命令注册`);
227
+ if (error.message && error.message.includes("cannot add alias")) {
228
+ console.warn(
229
+ `警告: 插件 ${pluginName} 的命令别名与其他插件冲突,已跳过此插件的命令注册`,
230
+ );
197
231
  } else {
198
- console.error(`错误: 插件 ${pluginName} 注册命令失败: ${error.message}`);
232
+ console.error(
233
+ `错误: 插件 ${pluginName} 注册命令失败: ${error.message}`,
234
+ );
199
235
  }
200
236
  }
201
237
  }
@@ -206,6 +242,117 @@ class Core {
206
242
  this.registerCommandSuggestions(program);
207
243
  }
208
244
 
245
+ /**
246
+ * 注册新格式插件命令
247
+ * @param {Object} program - Commander 实例
248
+ * @param {Object} plugin - 插件对象
249
+ * @param {string} pluginName - 插件名称
250
+ */
251
+ registerNewFormatPlugin(program, plugin, pluginName) {
252
+ const cmd = program.command(plugin.name);
253
+
254
+ // 设置描述
255
+ if (plugin.description) {
256
+ cmd.description(plugin.description);
257
+ }
258
+
259
+ // 设置别名
260
+ if (plugin.alias) {
261
+ cmd.alias(plugin.alias);
262
+ }
263
+
264
+ // 添加选项
265
+ if (plugin.options && Array.isArray(plugin.options)) {
266
+ plugin.options.forEach((option) => {
267
+ if (option.name && option.description) {
268
+ cmd.option(option.name, option.description);
269
+ }
270
+ });
271
+ }
272
+
273
+ // 如果插件定义了子命令,则注册它们
274
+ if (plugin.subcommands && Array.isArray(plugin.subcommands)) {
275
+ plugin.subcommands.forEach((subcmd) => {
276
+ const subCommand = cmd.command(subcmd.name);
277
+
278
+ if (subcmd.description) {
279
+ subCommand.description(subcmd.description);
280
+ }
281
+
282
+ if (subcmd.options && Array.isArray(subcmd.options)) {
283
+ subcmd.options.forEach((option) => {
284
+ if (option.name && option.description) {
285
+ subCommand.option(option.name, option.description);
286
+ }
287
+ });
288
+ }
289
+
290
+ subCommand.action(async (...args) => {
291
+ let commandArgs = [];
292
+ let options = {};
293
+
294
+ if (args.length > 0) {
295
+ const lastArg = args[args.length - 1];
296
+ if (lastArg && typeof lastArg === 'object' && typeof lastArg.opts === 'function') {
297
+ options = { ...lastArg.opts() };
298
+ commandArgs = lastArg.args || [];
299
+ } else {
300
+ commandArgs = [...args];
301
+ }
302
+ }
303
+
304
+ // 创建插件帮助器实例
305
+ const helper = createPluginsHelper(plugin.name);
306
+ // 调用子命令的 action 方法
307
+ await subcmd.action(commandArgs, options, helper);
308
+ });
309
+ });
310
+ }
311
+
312
+ // 设置主命令的 action
313
+ if (plugin.action) {
314
+ cmd.action(async (...args) => {
315
+ // Commander.js 会将参数和选项作为单独的参数传递
316
+ // 最后一个参数通常是 Command 对象,其中包含选项
317
+ let commandArgs = [];
318
+ let options = {};
319
+
320
+ // 如果有参数,需要正确识别哪个是 Command 对象
321
+ if (args.length > 0) {
322
+ // 从后往前检查,找到 Command 对象
323
+ let commandObjIndex = -1;
324
+ for (let i = args.length - 1; i >= 0; i--) {
325
+ const arg = args[i];
326
+ if (
327
+ arg &&
328
+ typeof arg === "object" &&
329
+ typeof arg.opts === "function"
330
+ ) {
331
+ commandObjIndex = i;
332
+ break;
333
+ }
334
+ }
335
+
336
+ if (commandObjIndex !== -1) {
337
+ // 找到了 Command 对象
338
+ options = { ...args[commandObjIndex].opts() };
339
+ // 从 Command 对象的 args 属性获取命令参数
340
+ const commandObj = args[commandObjIndex];
341
+ commandArgs = commandObj.args || [];
342
+ } else {
343
+ // 没有 Command 对象,所有参数都是命令参数
344
+ commandArgs = [...args];
345
+ }
346
+ }
347
+
348
+ // 创建插件帮助器实例
349
+ const helper = createPluginsHelper(plugin.name);
350
+ // 调用插件的 action 方法
351
+ await plugin.action(commandArgs, options, helper);
352
+ });
353
+ }
354
+ }
355
+
209
356
  /**
210
357
  * 注册命令自动建议功能
211
358
  * @param {Object} program - Commander 实例
@@ -322,7 +469,7 @@ class Core {
322
469
  } catch (error) {
323
470
  console.error(
324
471
  `执行插件 ${pluginName} 的钩子 ${event} 时出错:`,
325
- error.message
472
+ error.message,
326
473
  );
327
474
  }
328
475
  }
@@ -21,120 +21,145 @@
21
21
 
22
22
  const fs = require('fs-extra');
23
23
  const path = require('path');
24
- const BasePlugin = require('../core/base-plugin');
25
24
 
26
- class ConfigManagerPlugin extends BasePlugin {
27
- constructor(options = {}) {
28
- super(options);
29
- this.pluginName = 'config-manager';
30
- }
31
-
32
- /**
33
- * 注册命令
34
- * @param {Object} program - Commander.js 实例
35
- */
36
- registerCommands(program) {
37
- // 配置管理命令
38
- const configCommand = program.command('config-manager')
39
- .alias('conf')
40
- .description('管理所有插件的配置文件');
41
-
42
- // 导出配置命令
43
- configCommand.command('export [target]')
44
- .description('导出所有插件配置到指定目录,默认导出到当前目录的 config-backup 文件夹')
45
- .action((target) => this.exportConfig(target));
46
-
47
- // 导入配置命令
48
- configCommand.command('import [source]')
49
- .description('从指定目录导入配置并覆盖,默认从当前目录的 config-backup 文件夹导入')
50
- .action((source) => this.importConfig(source));
51
-
52
- // 列出配置命令
53
- configCommand.command('list')
54
- .description('列出所有插件的配置文件位置和状态')
55
- .action(() => this.listConfig());
25
+ /**
26
+ * 配置管理类
27
+ * 封装所有配置管理相关的方法
28
+ */
29
+ class ConfigManager {
30
+ constructor() {
31
+ // 获取用户主目录,添加 process.cwd() 作为 fallback
32
+ this.homeDir = process.env.HOME || process.env.USERPROFILE || process.cwd();
33
+ this.xxDir = path.join(this.homeDir, '.xx-cli');
56
34
  }
57
35
 
58
36
  /**
59
37
  * 导出配置
60
38
  * @param {string} target - 目标路径
39
+ * @param {Object} helper - 插件帮助器
61
40
  */
62
- exportConfig(target) {
63
- const homeDir = process.env.HOME || process.env.USERPROFILE;
64
- const xxDir = path.join(homeDir, '.xx-cli');
41
+ exportConfig(target, helper) {
65
42
  const exportDir = target || path.join(process.cwd(), 'config-backup');
66
- console.log(`开始导出配置到: ${exportDir}`);
43
+ helper.showInfo(`开始导出配置到: ${exportDir}`);
67
44
 
68
45
  // 确保导出目录存在
69
46
  fs.ensureDirSync(exportDir);
70
47
 
71
48
  // 复制 .xx 目录
72
- if (fs.existsSync(xxDir)) {
49
+ if (fs.existsSync(this.xxDir)) {
73
50
  const exportPath = path.join(exportDir, '.xx-cli');
74
51
  fs.ensureDirSync(path.dirname(exportPath));
75
- fs.copySync(xxDir, exportPath);
76
- console.log(`导出目录: ${xxDir}`);
52
+ fs.copySync(this.xxDir, exportPath);
53
+ helper.showInfo(`导出目录: ${this.xxDir}`);
77
54
  }
78
55
 
79
- console.log('配置导出完成!');
56
+ helper.showSuccess('配置导出完成!');
80
57
  }
81
58
 
82
59
  /**
83
60
  * 导入配置
84
61
  * @param {string} source - 源路径
62
+ * @param {Object} helper - 插件帮助器
85
63
  */
86
- importConfig(source) {
87
- const homeDir = process.env.HOME || process.env.USERPROFILE;
88
- const xxDir = path.join(homeDir, '.xx-cli');
64
+ importConfig(source, helper) {
89
65
  const importDir = source || path.join(process.cwd(), 'config-backup');
90
- console.log(`开始从 ${importDir} 导入配置...`);
66
+ helper.showInfo(`开始从 ${importDir} 导入配置...`);
91
67
 
92
68
  // 检查导入目录是否存在
93
69
  if (!fs.existsSync(importDir)) {
94
- console.log(`导入目录不存在: ${importDir}`);
70
+ helper.showError(`导入目录不存在: ${importDir}`);
95
71
  return;
96
72
  }
97
73
 
98
74
  // 复制配置到 .xx 目录
99
75
  const importPath = path.join(importDir, '.xx-cli');
100
76
  if (fs.existsSync(importPath)) {
101
- fs.ensureDirSync(path.dirname(xxDir));
102
- fs.copySync(importPath, xxDir, { overwrite: true });
103
- console.log(`导入目录: ${importPath}`);
77
+ fs.ensureDirSync(path.dirname(this.xxDir));
78
+ fs.copySync(importPath, this.xxDir, { overwrite: true });
79
+ helper.showInfo(`导入目录: ${importPath}`);
104
80
  }
105
81
 
106
- console.log('配置导入完成!');
82
+ helper.showSuccess('配置导入完成!');
107
83
  }
108
84
 
109
85
  /**
110
86
  * 列出配置
87
+ * @param {Object} helper - 插件帮助器
111
88
  */
112
- listConfig() {
113
- const homeDir = process.env.HOME || process.env.USERPROFILE;
114
- const xxDir = path.join(homeDir, '.xx-cli');
115
- console.log('列出所有插件配置文件...\n');
89
+ listConfig(helper) {
90
+ helper.showInfo('列出所有插件配置文件...\n');
116
91
 
117
- if (fs.existsSync(xxDir)) {
118
- const items = fs.readdirSync(xxDir).filter(item => !item.startsWith('.') && item !== 'plugins');
92
+ if (fs.existsSync(this.xxDir)) {
93
+ const items = fs.readdirSync(this.xxDir).filter(item => !item.startsWith('.') && item !== 'plugins');
119
94
  items.forEach(item => {
120
- const itemPath = path.join(xxDir, item);
95
+ const itemPath = path.join(this.xxDir, item);
121
96
  const stat = fs.statSync(itemPath);
122
97
  if (stat.isDirectory()) {
123
- console.log(`插件配置目录: ${itemPath}`);
98
+ helper.showInfo(`插件配置目录: ${itemPath}`);
124
99
  const configFiles = fs.readdirSync(itemPath).filter(file => !file.startsWith('.'));
125
100
  configFiles.forEach(file => {
126
- console.log(` - ${file}`);
101
+ helper.showInfo(` - ${file}`);
127
102
  });
128
103
  } else if (stat.isFile()) {
129
- console.log(`配置文件: ${itemPath}`);
104
+ helper.showInfo(`配置文件: ${itemPath}`);
130
105
  }
131
106
  });
132
107
  } else {
133
- console.log('配置目录不存在');
108
+ helper.showWarning('配置目录不存在');
134
109
  }
135
110
 
136
- console.log('配置列表完成!');
111
+ helper.showSuccess('配置列表完成!');
137
112
  }
138
113
  }
139
114
 
140
- module.exports = ConfigManagerPlugin;
115
+ // 创建配置管理实例
116
+ const configManager = new ConfigManager();
117
+
118
+ module.exports = {
119
+ name: 'config-manager',
120
+ alias: 'conf',
121
+ description: '管理所有插件的配置文件',
122
+ subcommands: [
123
+ {
124
+ name: 'export',
125
+ description: '导出所有插件配置到指定目录,默认导出到当前目录的 config-backup 文件夹',
126
+ action: (args, options, helper) => {
127
+ configManager.exportConfig(args.target, helper);
128
+ },
129
+ args: [
130
+ {
131
+ name: 'target',
132
+ description: '目标路径',
133
+ optional: true
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ name: 'import',
139
+ description: '从指定目录导入配置并覆盖,默认从当前目录的 config-backup 文件夹导入',
140
+ action: (args, options, helper) => {
141
+ configManager.importConfig(args.source, helper);
142
+ },
143
+ args: [
144
+ {
145
+ name: 'source',
146
+ description: '源路径',
147
+ optional: true
148
+ }
149
+ ]
150
+ },
151
+ {
152
+ name: 'list',
153
+ description: '列出所有插件的配置文件位置和状态',
154
+ action: (args, options, helper) => {
155
+ configManager.listConfig(helper);
156
+ }
157
+ }
158
+ ],
159
+ options: [
160
+ {
161
+ name: '-h, --help',
162
+ description: '显示帮助信息'
163
+ }
164
+ ]
165
+ };
@@ -21,77 +21,88 @@
21
21
  * - 支持命令行参数
22
22
  */
23
23
 
24
- const BasePlugin = require('../core/base-plugin');
25
-
26
- class HistoryPlugin extends BasePlugin {
27
- constructor(options = {}) {
28
- super(options);
29
- this.pluginName = 'history';
30
- }
31
-
32
- /**
33
- * 注册命令
34
- * @param {Object} program - Commander.js 实例
35
- */
36
- registerCommands(program) {
37
- // 命令历史记录
38
- program
39
- .command('history')
40
- .alias('h')
41
- .description('查看命令历史记录')
42
- .option('-l, --limit <number>', '限制显示的历史记录数量', parseInt, 10)
43
- .option('-c, --clear', '清除命令历史记录')
44
- .option('-s, --search <keyword>', '搜索命令历史记录')
45
- .action((options) => this.showHistory(options));
24
+ /**
25
+ * 历史记录管理类
26
+ * 封装所有历史记录相关的方法
27
+ */
28
+ class HistoryManager {
29
+ constructor() {
30
+ // 加载历史记录管理器
31
+ this.historyManager = require('../utils/history-manager');
46
32
  }
47
33
 
48
34
  /**
49
35
  * 显示命令历史记录
50
36
  * @param {Object} options - 选项
37
+ * @param {Object} helper - 插件帮助器
51
38
  */
52
- showHistory(options) {
53
- const historyManager = require('../utils/history-manager');
54
-
39
+ showHistory(options, helper) {
55
40
  if (options.clear) {
56
41
  // 清除命令历史记录
57
- historyManager.clearHistory();
58
- console.log('命令历史记录已清除');
42
+ this.historyManager.clearHistory();
43
+ helper.showSuccess('命令历史记录已清除');
59
44
  return;
60
45
  }
61
46
 
62
47
  if (options.search) {
63
48
  // 搜索命令历史记录
64
- const searchResults = historyManager.searchHistory(options.search);
49
+ const searchResults = this.historyManager.searchHistory(options.search);
65
50
  if (searchResults.length > 0) {
66
- console.log('\n搜索结果:');
67
- console.log('=====================================');
51
+ helper.showInfo('\n搜索结果:');
52
+ helper.showInfo('=====================================');
68
53
  searchResults.forEach((item, index) => {
69
54
  const date = new Date(item.timestamp).toLocaleString();
70
- console.log(`${index + 1}. ${item.command} (${date})`);
55
+ helper.showInfo(`${index + 1}. ${item.command} (${date})`);
71
56
  });
72
- console.log('=====================================');
73
- console.log(`总计: ${searchResults.length} 条记录\n`);
57
+ helper.showInfo('=====================================');
58
+ helper.showInfo(`总计: ${searchResults.length} 条记录\n`);
74
59
  } else {
75
- console.log(`\n未找到匹配 "${options.search}" 的命令历史记录\n`);
60
+ helper.showInfo(`\n未找到匹配 "${options.search}" 的命令历史记录\n`);
76
61
  }
77
62
  return;
78
63
  }
79
64
 
80
65
  // 显示命令历史记录
81
- const history = historyManager.getHistory(options.limit);
66
+ const history = this.historyManager.getHistory(options.limit);
82
67
  if (history.length > 0) {
83
- console.log('\n命令历史记录:');
84
- console.log('=====================================');
68
+ helper.showInfo('\n命令历史记录:');
69
+ helper.showInfo('=====================================');
85
70
  history.forEach((item, index) => {
86
71
  const date = new Date(item.timestamp).toLocaleString();
87
- console.log(`${index + 1}. ${item.command} (${date})`);
72
+ helper.showInfo(`${index + 1}. ${item.command} (${date})`);
88
73
  });
89
- console.log('=====================================');
90
- console.log(`总计: ${history.length} 条记录\n`);
74
+ helper.showInfo('=====================================');
75
+ helper.showInfo(`总计: ${history.length} 条记录\n`);
91
76
  } else {
92
- console.log('\n命令历史记录为空\n');
77
+ helper.showInfo('\n命令历史记录为空\n');
93
78
  }
94
79
  }
95
80
  }
96
81
 
97
- module.exports = HistoryPlugin;
82
+ // 创建历史记录管理实例
83
+ const historyManager = new HistoryManager();
84
+
85
+ module.exports = {
86
+ name: 'history',
87
+ alias: 'h',
88
+ description: '查看命令历史记录',
89
+ options: [
90
+ {
91
+ name: '-l, --limit <number>',
92
+ description: '限制显示的历史记录数量',
93
+ parse: parseInt,
94
+ default: 10
95
+ },
96
+ {
97
+ name: '-c, --clear',
98
+ description: '清除命令历史记录'
99
+ },
100
+ {
101
+ name: '-s, --search <keyword>',
102
+ description: '搜索命令历史记录'
103
+ }
104
+ ],
105
+ action: (args, options, helper) => {
106
+ historyManager.showHistory(options, helper);
107
+ }
108
+ };