@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.

Potentially problematic release.


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

@@ -15,7 +15,7 @@ class ConfigManager {
15
15
  * @param {Object} options - 配置选项
16
16
  */
17
17
  constructor(pluginName, options = {}) {
18
- this.homeDir = process.env.HOME || process.env.USERPROFILE;
18
+ this.homeDir = process.env.HOME || process.env.USERPROFILE || process.cwd();
19
19
  this.globalConfigDir = path.join(this.homeDir, '.xx-cli', pluginName);
20
20
  this.localConfigPath = options.localConfigPath || `.${pluginName}Config`;
21
21
  this.ensureConfigDir();
@@ -8,7 +8,8 @@ const fs = require('fs');
8
8
  const path = require('path');
9
9
 
10
10
  // 历史记录文件路径
11
- const historyPath = path.join(process.env.HOME || process.env.USERPROFILE, '.xx-cli', 'history.json');
11
+ const homeDir = process.env.HOME || process.env.USERPROFILE || process.cwd();
12
+ const historyPath = path.join(homeDir, '.xx-cli', 'history.json');
12
13
 
13
14
  /**
14
15
  * 确保历史记录目录存在
@@ -0,0 +1,276 @@
1
+ /**
2
+ * 插件辅助工具
3
+ * 提供与 BasePlugin 相同的通用功能,供新的插件实现方式使用
4
+ */
5
+
6
+ const ConfigManager = require('./config-manager');
7
+ const chalk = require('chalk').default;
8
+ const ora = require('ora').default;;
9
+ const clipboardy = require('clipboardy').default;
10
+
11
+ /**
12
+ * 插件辅助类
13
+ * @param {string} pluginName - 插件名称
14
+ * @param {Object} options - 选项
15
+ */
16
+ class PluginsHelper {
17
+ constructor(pluginName, options = {}) {
18
+ this.pluginName = pluginName;
19
+ this.homeDir = process.env.HOME || process.env.USERPROFILE || process.cwd();
20
+ this.configManager = new ConfigManager(pluginName, options);
21
+ this.spinner = null;
22
+ this.progressTotal = 0;
23
+ this.progressCurrent = 0;
24
+ this.hooks = {};
25
+ }
26
+
27
+ /**
28
+ * 显示加载状态
29
+ * @param {string} text - 加载文本
30
+ */
31
+ startLoading(text) {
32
+ this.spinner = ora(text).start();
33
+ }
34
+
35
+ /**
36
+ * 停止加载状态
37
+ * @param {string} text - 成功文本
38
+ */
39
+ stopLoading(text) {
40
+ if (this.spinner) {
41
+ this.spinner.succeed(text);
42
+ this.spinner = null;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * 显示错误状态
48
+ * @param {string} text - 错误文本
49
+ */
50
+ showError(text) {
51
+ if (this.spinner) {
52
+ this.spinner.fail(text);
53
+ this.spinner = null;
54
+ } else {
55
+ console.error(chalk.red(`❌ ${text}`));
56
+ }
57
+ }
58
+
59
+ /**
60
+ * 启动进度条
61
+ * @param {string} text - 进度条文本
62
+ * @param {number} total - 总进度值
63
+ */
64
+ startProgressBar(text, total = 100) {
65
+ this.spinner = ora({
66
+ text,
67
+ spinner: 'arc',
68
+ color: 'cyan'
69
+ }).start();
70
+ this.progressTotal = total;
71
+ this.progressCurrent = 0;
72
+ }
73
+
74
+ /**
75
+ * 更新进度条
76
+ * @param {number} current - 当前进度值
77
+ * @param {string} text - 进度条文本
78
+ */
79
+ updateProgressBar(current, text) {
80
+ if (this.spinner && this.progressTotal > 0) {
81
+ this.progressCurrent = current;
82
+ const percentage = Math.min(Math.round((current / this.progressTotal) * 100), 100);
83
+ this.spinner.text = `${text || ''} ${percentage}%`;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * 停止进度条
89
+ * @param {string} text - 完成文本
90
+ */
91
+ stopProgressBar(text) {
92
+ if (this.spinner) {
93
+ this.spinner.succeed(text);
94
+ this.spinner = null;
95
+ this.progressTotal = 0;
96
+ this.progressCurrent = 0;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * 显示成功信息
102
+ * @param {string} text - 成功文本
103
+ */
104
+ showSuccess(text) {
105
+ console.log(chalk.green(`✅ ${text}`));
106
+ }
107
+
108
+ /**
109
+ * 显示警告信息
110
+ * @param {string} text - 警告文本
111
+ */
112
+ showWarning(text) {
113
+ console.log(chalk.yellow(`⚠️ ${text}`));
114
+ }
115
+
116
+ /**
117
+ * 显示信息
118
+ * @param {string} text - 信息文本
119
+ */
120
+ showInfo(text) {
121
+ console.log(chalk.blue(`${text}`));
122
+ }
123
+
124
+ /**
125
+ * 加载配置
126
+ * @param {string} configName - 配置名称
127
+ * @param {Object} cliOptions - 命令行选项
128
+ * @returns {Object} 合并后的配置对象
129
+ */
130
+ loadConfig(configName = 'config', cliOptions = {}) {
131
+ return this.configManager.loadConfig(configName, cliOptions);
132
+ }
133
+
134
+ /**
135
+ * 保存配置
136
+ * @param {Object} config - 配置对象
137
+ * @param {string} configName - 配置名称
138
+ * @param {boolean} isGlobal - 是否保存到全局配置
139
+ */
140
+ saveConfig(config, configName = 'config', isGlobal = true) {
141
+ this.configManager.saveConfig(config, configName, isGlobal);
142
+ }
143
+
144
+ /**
145
+ * 验证配置
146
+ * @param {Object} config - 配置对象
147
+ * @param {Object} schema - 配置 schema
148
+ * @returns {Object} 验证结果
149
+ */
150
+ validateConfig(config, schema) {
151
+ return this.configManager.validateConfig(config, schema);
152
+ }
153
+
154
+ /**
155
+ * 错误处理
156
+ * @param {Error} error - 错误对象
157
+ * @param {string} message - 错误消息
158
+ */
159
+ handleError(error, message = '操作失败') {
160
+ this.showError(`${message}: ${error.message}`);
161
+ // 可以添加错误日志记录等逻辑
162
+ }
163
+
164
+ /**
165
+ * 执行异步操作
166
+ * @param {Function} fn - 异步函数
167
+ * @param {string} loadingText - 加载文本
168
+ * @param {string} successText - 成功文本
169
+ * @param {string} errorText - 错误文本
170
+ * @returns {Promise<any>}
171
+ */
172
+ async executeAsync(fn, loadingText, successText, errorText) {
173
+ try {
174
+ this.startLoading(loadingText);
175
+ const result = await fn();
176
+ this.stopLoading(successText);
177
+ return result;
178
+ } catch (error) {
179
+ this.handleError(error, errorText);
180
+ throw error;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * 注册钩子
186
+ * @param {string} event - 事件名称
187
+ * @param {Function} callback - 回调函数
188
+ */
189
+ registerHook(event, callback) {
190
+ if (!this.hooks) {
191
+ this.hooks = {};
192
+ }
193
+
194
+ if (!this.hooks[event]) {
195
+ this.hooks[event] = [];
196
+ }
197
+
198
+ this.hooks[event].push(callback);
199
+ }
200
+
201
+ /**
202
+ * 触发钩子
203
+ * @param {string} event - 事件名称
204
+ * @param {*} data - 事件数据
205
+ * @returns {Promise<Array>} 钩子执行结果
206
+ */
207
+ async triggerHook(event, data) {
208
+ if (!this.hooks || !this.hooks[event]) {
209
+ return [];
210
+ }
211
+
212
+ const results = [];
213
+ for (const callback of this.hooks[event]) {
214
+ try {
215
+ const result = await callback(data);
216
+ results.push(result);
217
+ } catch (error) {
218
+ this.handleError(error, `执行钩子 ${event} 时出错`);
219
+ }
220
+ }
221
+
222
+ return results;
223
+ }
224
+
225
+ /**
226
+ * 检查是否注册了指定钩子
227
+ * @param {string} event - 事件名称
228
+ * @returns {boolean} 是否注册了钩子
229
+ */
230
+ hasHook(event) {
231
+ return this.hooks && this.hooks[event] && this.hooks[event].length > 0;
232
+ }
233
+
234
+ /**
235
+ * 复制文本到剪贴板
236
+ * @param {string} text - 要复制的文本
237
+ * @param {string} successMessage - 成功消息
238
+ * @returns {Promise<boolean>} 是否复制成功
239
+ */
240
+ async copyToClipboard(text, successMessage) {
241
+ try {
242
+ // 尝试不同的 clipboardy API 调用方式
243
+ if (clipboardy.default && typeof clipboardy.default.write === 'function') {
244
+ await clipboardy.default.write(text);
245
+ } else if (typeof clipboardy.write === 'function') {
246
+ await clipboardy.write(text);
247
+ } else if (typeof clipboardy.writeSync === 'function') {
248
+ clipboardy.writeSync(text);
249
+ } else {
250
+ throw new Error('clipboardy API 不可用');
251
+ }
252
+ if (successMessage) {
253
+ this.showSuccess(successMessage);
254
+ }
255
+ return true;
256
+ } catch (error) {
257
+ this.showWarning(`复制到剪贴板失败: ${error.message}`);
258
+ return false;
259
+ }
260
+ }
261
+ }
262
+
263
+ /**
264
+ * 创建插件辅助实例
265
+ * @param {string} pluginName - 插件名称
266
+ * @param {Object} options - 选项
267
+ * @returns {PluginsHelper} 插件辅助实例
268
+ */
269
+ function createPluginsHelper(pluginName, options = {}) {
270
+ return new PluginsHelper(pluginName, options);
271
+ }
272
+
273
+ module.exports = {
274
+ PluginsHelper,
275
+ createPluginsHelper
276
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xubill/xx-cli",
3
- "version": "1.0.6",
3
+ "version": "2.0.0",
4
4
  "description": "个人工具集",
5
5
  "main": "lib/core/index.js",
6
6
  "bin": {
package/readme.md CHANGED
@@ -24,6 +24,10 @@ xx <command> [options]
24
24
  - 修复原生插件和用户插件区别
25
25
  - 壳功能移动到原生插件目录
26
26
 
27
+ ### v2.0.0
28
+ - 支持新格式命令注册,兼容旧格式插件
29
+ - 更新插件创建模板,采用配置对象格式并包含类结构实现
30
+
27
31
  ## 框架自带命令
28
32
 
29
33
  xx-cli 框架自带以下核心命令(位于 core 模块中):
@@ -109,7 +113,7 @@ xx-cli 提供了 `plugin create` 命令,可以快速生成插件模板。这
109
113
  ##### 基本用法
110
114
 
111
115
  ```bash
112
- # 创建插件到插件目录
116
+ # 创建插件到插件目录(默认使用新格式,包含类结构实现)
113
117
  xx plugin create <plugin-name>
114
118
 
115
119
  # 或者使用别名
@@ -119,6 +123,17 @@ xx p create <plugin-name>
119
123
  xx p cr <plugin-name>
120
124
  ```
121
125
 
126
+ ##### 插件模板格式
127
+
128
+ 从 v2.0.0 版本开始,`plugin create` 命令生成的插件模板采用统一的格式:
129
+
130
+ - **配置对象格式**:使用配置对象定义插件的基本信息和命令
131
+ - **包含类结构实现**:核心功能通过类结构实现,提高代码模块化和可维护性
132
+ - **自动生成命令名称**:根据插件名称自动生成驼峰命名的命令名称
133
+ - **标准的用户提示**:使用标准化的用户提示和错误处理
134
+
135
+ 这种格式结合了配置对象的简洁性和类结构的模块化优势,是推荐的插件开发方式。
136
+
122
137
  ##### 示例
123
138
 
124
139
  ```bash
@@ -142,9 +157,86 @@ xx plugin create hello-world
142
157
 
143
158
  ### 插件结构
144
159
 
145
- #### 基本插件结构
160
+ #### 插件格式
161
+
162
+ xx-cli 支持两种插件格式:
146
163
 
147
- 使用 `plugin create` 命令生成的插件模板包含以下结构:
164
+ 1. **旧格式(类结构格式)**:继承自 `BasePlugin` 类的插件实现
165
+ 2. **新格式(配置对象格式)**:使用配置对象定义的插件实现(推荐)
166
+
167
+ #### 新格式(配置对象格式)插件结构
168
+
169
+ 使用 `plugin create` 命令生成的默认插件模板包含以下结构(v2.0.0 版本更新):
170
+
171
+ ```javascript
172
+ /**
173
+ * hello-world 插件
174
+ * 用于实现 hello-world 相关功能
175
+ *
176
+ * 命令说明:
177
+ * - helloWorld [options]:hello-world 相关功能
178
+ * - 示例:helloWorld
179
+ *
180
+ * 功能说明:
181
+ * - 实现 hello-world 相关功能
182
+ *
183
+ * 用户体验:
184
+ * - 使用标准化的用户提示
185
+ * - 提供清晰的错误提示
186
+ * - 支持命令行参数
187
+ */
188
+
189
+ /**
190
+ * hello-world 插件类
191
+ * 实现 hello-world 相关核心功能
192
+ */
193
+ class HelloWorldPlugin {
194
+ constructor() {
195
+ this.pluginName = 'hello-world';
196
+ }
197
+
198
+ /**
199
+ * 执行 hello-world 命令
200
+ * @param {Array} args - 命令参数
201
+ * @param {Object} options - 命令选项
202
+ * @param {Object} helper - 插件帮助器
203
+ */
204
+ async execute(args, options, helper) {
205
+ helper.showInfo('hello-world 命令执行成功!');
206
+ if (options && options.verbose) {
207
+ helper.showInfo('详细信息:');
208
+ helper.showInfo('- 命令名称: helloWorld');
209
+ helper.showInfo('- 执行时间: ' + new Date().toLocaleString());
210
+ }
211
+ }
212
+ }
213
+
214
+ // 创建插件实例
215
+ const pluginInstance = new HelloWorldPlugin();
216
+
217
+ module.exports = {
218
+ name: "helloWorld",
219
+ alias: "h",
220
+ description: "hello-world 相关功能",
221
+ options: [
222
+ {
223
+ name: "-v, --verbose",
224
+ description: "显示详细信息",
225
+ },
226
+ {
227
+ name: "-h, --help",
228
+ description: "显示帮助信息",
229
+ },
230
+ ],
231
+ action: async (args, options, helper) => {
232
+ await pluginInstance.execute(args, options, helper);
233
+ },
234
+ };
235
+ ```
236
+
237
+ #### 旧格式(类结构格式)插件结构
238
+
239
+ 使用 `plugin create --format old` 命令生成的插件模板包含以下结构:
148
240
 
149
241
  ```javascript
150
242
  /**
@@ -203,19 +295,35 @@ module.exports = HelloWorldPlugin;
203
295
 
204
296
  #### 命令注册
205
297
 
206
- 插件必须实现 `registerCommands` 方法,用于向 Commander.js 注册命令。
298
+ ##### 新格式(配置对象格式)命令注册
207
299
 
208
- ##### 基本命令注册
300
+ 对于新格式的插件,命令注册通过配置对象的 `options` 和 `action` 属性实现:
209
301
 
210
302
  ```javascript
211
- registerCommands(program) {
212
- program.command('helloWorld')
213
- .description('hello-world 相关功能')
214
- .action(() => this.helloWorldCommand());
215
- }
303
+ module.exports = {
304
+ name: 'hello-world',
305
+ alias: 'hw',
306
+ description: 'hello-world 相关功能',
307
+ options: [
308
+ {
309
+ name: '-v, --verbose',
310
+ description: '显示详细信息'
311
+ },
312
+ {
313
+ name: '-n, --name <name>',
314
+ description: '指定名称'
315
+ }
316
+ ],
317
+ action: (args, options, cmd) => {
318
+ const helper = createPluginsHelper('hello-world');
319
+ helloWorldCommand(args.alias, options, helper);
320
+ }
321
+ };
216
322
  ```
217
323
 
218
- ##### 带选项的命令注册
324
+ ##### 旧格式(类结构格式)命令注册
325
+
326
+ 对于旧格式的插件,命令注册通过实现 `registerCommands` 方法实现:
219
327
 
220
328
  ```javascript
221
329
  registerCommands(program) {
@@ -305,11 +413,110 @@ fs.ensureDirSync(pluginConfigDir);
305
413
  fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
306
414
  ```
307
415
 
308
- ### 基础插件类
416
+ ### 插件工具
417
+
418
+ #### 新格式(配置对象格式)插件工具
419
+
420
+ 对于新格式的插件,使用 `plugins-helper` 提供的功能来替代 `BasePlugin` 类的方法:
421
+
422
+ ##### 导入 plugins-helper
423
+
424
+ ```javascript
425
+ const { createPluginsHelper } = require('../utils/plugins-helper');
426
+ ```
427
+
428
+ ##### 核心功能
429
+
430
+ `plugins-helper` 提供了以下核心功能:
431
+
432
+ ##### 1. 用户界面
433
+
434
+ - **`helper.showInfo(text)`**:显示信息
435
+ - **`helper.showSuccess(text)`**:显示成功信息
436
+ - **`helper.showWarning(text)`**:显示警告信息
437
+ - **`helper.showError(text)`**:显示错误信息
438
+ - **`helper.startLoading(text)`**:显示加载状态
439
+ - **`helper.stopLoading(text)`**:停止加载状态
440
+
441
+ ##### 2. 配置管理
442
+
443
+ 可以使用 `fs-extra` 和 `path` 模块来实现配置管理:
444
+
445
+ ```javascript
446
+ const fs = require('fs-extra');
447
+ const path = require('path');
448
+
449
+ // 确保配置目录存在
450
+ function ensureConfigDir(pluginName) {
451
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
452
+ const xxDir = path.join(homeDir, '.xx-cli');
453
+ const pluginConfigDir = path.join(xxDir, pluginName);
454
+ fs.ensureDirSync(pluginConfigDir);
455
+ return pluginConfigDir;
456
+ }
457
+
458
+ // 加载配置
459
+ function loadConfig(pluginName) {
460
+ const configDir = ensureConfigDir(pluginName);
461
+ const configFile = path.join(configDir, 'config.json');
462
+ if (fs.existsSync(configFile)) {
463
+ return JSON.parse(fs.readFileSync(configFile, 'utf8'));
464
+ }
465
+ return {};
466
+ }
467
+
468
+ // 保存配置
469
+ function saveConfig(pluginName, config) {
470
+ const configDir = ensureConfigDir(pluginName);
471
+ const configFile = path.join(configDir, 'config.json');
472
+ fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
473
+ }
474
+ ```
475
+
476
+ ##### 使用示例
477
+
478
+ ```javascript
479
+ const { createPluginsHelper } = require('../utils/plugins-helper');
480
+
481
+ function myCommand(options, helper) {
482
+ try {
483
+ helper.showInfo('执行操作...');
484
+
485
+ // 执行操作
486
+ setTimeout(() => {
487
+ helper.showSuccess('操作成功');
488
+
489
+ if (options && options.verbose) {
490
+ helper.showInfo('详细信息:');
491
+ helper.showInfo('- 执行时间:', new Date().toLocaleString());
492
+ }
493
+ }, 1000);
494
+ } catch (error) {
495
+ helper.showError(`执行命令失败: ${error.message}`);
496
+ }
497
+ }
498
+
499
+ module.exports = {
500
+ name: 'my-plugin',
501
+ description: 'My plugin command',
502
+ options: [
503
+ {
504
+ name: '-v, --verbose',
505
+ description: '显示详细信息'
506
+ }
507
+ ],
508
+ action: (args, options, cmd) => {
509
+ const helper = createPluginsHelper('my-plugin');
510
+ myCommand(options, helper);
511
+ }
512
+ };
513
+ ```
514
+
515
+ #### 旧格式(类结构格式)基础插件类
309
516
 
310
- xx-cli 提供了 `BasePlugin` 基类,所有插件都应该继承此类以确保一致性和获得通用功能支持。
517
+ 对于旧格式的插件,使用 `BasePlugin` 基类:
311
518
 
312
- #### 基础插件类路径
519
+ ##### 基础插件类路径
313
520
 
314
521
  `BasePlugin` 类位于 `/Users/xub/xx-cli/lib/core/base-plugin.js`,插件可以通过以下方式导入:
315
522
 
@@ -317,7 +524,7 @@ xx-cli 提供了 `BasePlugin` 基类,所有插件都应该继承此类以确
317
524
  const BasePlugin = require('../core/base-plugin');
318
525
  ```
319
526
 
320
- #### 核心功能
527
+ ##### 核心功能
321
528
 
322
529
  `BasePlugin` 类提供了以下核心功能:
323
530
 
@@ -365,7 +572,7 @@ const BasePlugin = require('../core/base-plugin');
365
572
 
366
573
  - **`copyToClipboard(text, successMessage)`**:复制文本到剪贴板
367
574
 
368
- #### 使用示例
575
+ ##### 使用示例
369
576
 
370
577
  ```javascript
371
578
  const BasePlugin = require('../core/base-plugin');