@xubill/xx-cli 2.0.0 → 2.0.1
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/AGENTS.md +177 -0
- package/lib/core/index.js +42 -9
- package/lib/plugins/ai.js +295 -0
- package/package.json +6 -1
- package/plugin-development-new.md +565 -0
- package/plugin-development-old.md +447 -0
- package/readme.md +54 -554
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# 旧格式插件开发教程
|
|
2
|
+
|
|
3
|
+
本教程将详细介绍如何在 xx-cli 中使用旧格式(类结构格式)创建、开发和部署插件。旧格式插件需要继承 `BasePlugin` 类,主要用于向后兼容。
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
- [旧格式插件开发教程](#旧格式插件开发教程)
|
|
8
|
+
- [目录](#目录)
|
|
9
|
+
- [创建插件](#创建插件)
|
|
10
|
+
- [使用 plugin create 命令](#使用-plugin-create-命令)
|
|
11
|
+
- [手动创建插件](#手动创建插件)
|
|
12
|
+
- [插件结构](#插件结构)
|
|
13
|
+
- [命令注册](#命令注册)
|
|
14
|
+
- [基本命令注册](#基本命令注册)
|
|
15
|
+
- [带选项的命令注册](#带选项的命令注册)
|
|
16
|
+
- [命令选项](#命令选项)
|
|
17
|
+
- [常用选项类型](#常用选项类型)
|
|
18
|
+
- [选项示例](#选项示例)
|
|
19
|
+
- [插件配置](#插件配置)
|
|
20
|
+
- [配置文件结构](#配置文件结构)
|
|
21
|
+
- [读取配置](#读取配置)
|
|
22
|
+
- [写入配置](#写入配置)
|
|
23
|
+
- [插件工具](#插件工具)
|
|
24
|
+
- [基础插件类路径](#基础插件类路径)
|
|
25
|
+
- [核心功能](#核心功能)
|
|
26
|
+
- [使用示例](#使用示例)
|
|
27
|
+
- [插件生命周期](#插件生命周期)
|
|
28
|
+
- [测试插件](#测试插件)
|
|
29
|
+
- [本地测试](#本地测试)
|
|
30
|
+
- [命令测试](#命令测试)
|
|
31
|
+
- [部署插件](#部署插件)
|
|
32
|
+
- [发布到插件目录](#发布到插件目录)
|
|
33
|
+
- [分享插件](#分享插件)
|
|
34
|
+
- [从外部添加插件](#从外部添加插件)
|
|
35
|
+
- [最佳实践](#最佳实践)
|
|
36
|
+
- [命名规范](#命名规范)
|
|
37
|
+
- [代码组织](#代码组织)
|
|
38
|
+
- [错误处理](#错误处理)
|
|
39
|
+
- [用户体验](#用户体验)
|
|
40
|
+
|
|
41
|
+
## 创建插件
|
|
42
|
+
|
|
43
|
+
### 使用 plugin create 命令
|
|
44
|
+
|
|
45
|
+
xx-cli 提供了 `plugin create` 命令,可以快速生成旧格式的插件模板:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# 创建旧格式插件
|
|
49
|
+
xx plugin create <plugin-name> --format old
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 手动创建插件
|
|
53
|
+
|
|
54
|
+
如果需要更灵活地控制插件结构,也可以手动创建插件文件:
|
|
55
|
+
|
|
56
|
+
1. 在 `lib/plugins` 目录下创建一个新的 JavaScript 文件,例如 `my-plugin.js`
|
|
57
|
+
2. 实现插件类,继承 `BasePlugin` 类并包含必要的方法
|
|
58
|
+
|
|
59
|
+
## 插件结构
|
|
60
|
+
|
|
61
|
+
旧格式使用类结构定义插件,需要继承 `BasePlugin` 类:
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
/**
|
|
65
|
+
* hello-world 插件
|
|
66
|
+
* 用于实现 hello-world 相关功能
|
|
67
|
+
*
|
|
68
|
+
* 命令说明:
|
|
69
|
+
* - helloWorld [options]:hello-world 相关功能
|
|
70
|
+
* - 示例:helloWorld
|
|
71
|
+
*
|
|
72
|
+
* 功能说明:
|
|
73
|
+
* - 实现 hello-world 相关功能
|
|
74
|
+
*
|
|
75
|
+
* 用户体验:
|
|
76
|
+
* - 使用标准化的用户提示
|
|
77
|
+
* - 提供清晰的错误提示
|
|
78
|
+
* - 支持命令行参数
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
const BasePlugin = require('../core/base-plugin');
|
|
82
|
+
|
|
83
|
+
class HelloWorldPlugin extends BasePlugin {
|
|
84
|
+
constructor(options = {}) {
|
|
85
|
+
super(options);
|
|
86
|
+
this.pluginName = 'hello-world';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 注册命令
|
|
91
|
+
* @param {Object} program - Commander.js 实例
|
|
92
|
+
*/
|
|
93
|
+
registerCommands(program) {
|
|
94
|
+
program.command('helloWorld')
|
|
95
|
+
.description('hello-world 相关功能')
|
|
96
|
+
.option('-v, --verbose', '显示详细信息')
|
|
97
|
+
.option('-h, --help', '显示帮助信息')
|
|
98
|
+
.action((options) => this.helloWorldCommand(options));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* hello-world 命令
|
|
103
|
+
* @param {Object} options - 命令选项
|
|
104
|
+
*/
|
|
105
|
+
helloWorldCommand(options) {
|
|
106
|
+
console.log('hello-world 命令执行成功!');
|
|
107
|
+
if (options && options.verbose) {
|
|
108
|
+
console.log('详细信息:');
|
|
109
|
+
console.log('- 命令名称:', 'helloWorld');
|
|
110
|
+
console.log('- 执行时间:', new Date().toLocaleString());
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = HelloWorldPlugin;
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 命令注册
|
|
119
|
+
|
|
120
|
+
对于旧格式的插件,命令注册通过实现 `registerCommands` 方法实现:
|
|
121
|
+
|
|
122
|
+
### 基本命令注册
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
registerCommands(program) {
|
|
126
|
+
program.command('helloWorld')
|
|
127
|
+
.description('hello-world 相关功能')
|
|
128
|
+
.action(() => this.helloWorldCommand());
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 带选项的命令注册
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
registerCommands(program) {
|
|
136
|
+
program.command('helloWorld [alias]')
|
|
137
|
+
.alias('hw')
|
|
138
|
+
.description('hello-world 相关功能')
|
|
139
|
+
.option('-v, --verbose', '显示详细信息')
|
|
140
|
+
.option('-n, --name <name>', '指定名称')
|
|
141
|
+
.action((alias, options) => this.helloWorldCommand(alias, options));
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 命令选项
|
|
146
|
+
|
|
147
|
+
命令选项可以增强命令的灵活性,允许用户自定义命令行为。
|
|
148
|
+
|
|
149
|
+
### 常用选项类型
|
|
150
|
+
|
|
151
|
+
#### 1. 布尔选项
|
|
152
|
+
|
|
153
|
+
不需要参数的选项,用于开关功能。
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
.option('-v, --verbose', '显示详细信息')
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### 2. 值选项
|
|
160
|
+
|
|
161
|
+
需要参数的选项,用于传递具体值。
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
.option('-n, --name <name>', '指定名称')
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### 3. 数组选项
|
|
168
|
+
|
|
169
|
+
可以接收多个值的选项。
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
.option('-a, --array <items...>', '指定数组参数')
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### 4. 带默认值的选项
|
|
176
|
+
|
|
177
|
+
设置选项的默认值。
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
.option('-p, --port <port>', '指定端口', '3000')
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### 5. 带验证器的选项
|
|
184
|
+
|
|
185
|
+
使用验证器处理选项值。
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
.option('-p, --port <port>', '指定端口', parseInt)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 选项示例
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
registerCommands(program) {
|
|
195
|
+
program.command('example')
|
|
196
|
+
.description('示例命令')
|
|
197
|
+
.option('-v, --verbose', '显示详细信息')
|
|
198
|
+
.option('-n, --name <name>', '指定名称')
|
|
199
|
+
.option('-a, --array <items...>', '指定数组参数')
|
|
200
|
+
.option('-p, --port <port>', '指定端口', '3000')
|
|
201
|
+
.option('-c, --count <count>', '指定数量', parseInt)
|
|
202
|
+
.action((options) => this.exampleCommand(options));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
exampleCommand(options) {
|
|
206
|
+
console.log('示例命令执行成功!');
|
|
207
|
+
if (options.verbose) {
|
|
208
|
+
console.log('详细信息:');
|
|
209
|
+
console.log(`- 名称: ${options.name || '未指定'}`);
|
|
210
|
+
console.log(`- 数组: ${options.array ? options.array.join(', ') : '未指定'}`);
|
|
211
|
+
console.log(`- 端口: ${options.port}`);
|
|
212
|
+
console.log(`- 数量: ${options.count || '未指定'}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## 插件配置
|
|
218
|
+
|
|
219
|
+
插件可以使用配置文件来存储和管理配置信息,例如默认值、用户偏好设置等。
|
|
220
|
+
|
|
221
|
+
### 配置文件结构
|
|
222
|
+
|
|
223
|
+
配置文件通常存储在用户主目录的 `.xx-cli` 文件夹中,例如:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
~/.xx-cli/
|
|
227
|
+
└── hello-world/
|
|
228
|
+
└── config.json
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 读取配置
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
// 使用 BasePlugin 提供的方法
|
|
235
|
+
const config = this.loadConfig('config');
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 写入配置
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
// 使用 BasePlugin 提供的方法
|
|
242
|
+
this.saveConfig(config, 'config');
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## 插件工具
|
|
246
|
+
|
|
247
|
+
对于旧格式的插件,使用 `BasePlugin` 基类:
|
|
248
|
+
|
|
249
|
+
### 基础插件类路径
|
|
250
|
+
|
|
251
|
+
`BasePlugin` 类位于 `/Users/userxx/xx-cli/lib/core/base-plugin.js`,插件可以通过以下方式导入:
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
const BasePlugin = require('../core/base-plugin');
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 核心功能
|
|
258
|
+
|
|
259
|
+
`BasePlugin` 类提供了以下核心功能:
|
|
260
|
+
|
|
261
|
+
#### 1. 命令注册
|
|
262
|
+
|
|
263
|
+
- **`registerCommands(program)`**:注册命令到 Commander.js 实例,子类必须实现此方法
|
|
264
|
+
|
|
265
|
+
#### 2. 插件初始化
|
|
266
|
+
|
|
267
|
+
- **`init()`**:插件初始化逻辑,子类可以覆盖
|
|
268
|
+
- **`getInfo()`**:获取插件信息
|
|
269
|
+
|
|
270
|
+
#### 3. 用户界面
|
|
271
|
+
|
|
272
|
+
- **`startLoading(text)`**:显示加载状态
|
|
273
|
+
- **`stopLoading(text)`**:停止加载状态
|
|
274
|
+
- **`showError(text)`**:显示错误信息
|
|
275
|
+
- **`startProgressBar(text, total)`**:启动进度条
|
|
276
|
+
- **`updateProgressBar(current, text)`**:更新进度条
|
|
277
|
+
- **`stopProgressBar(text)`**:停止进度条
|
|
278
|
+
- **`showSuccess(text)`**:显示成功信息
|
|
279
|
+
- **`showWarning(text)`**:显示警告信息
|
|
280
|
+
- **`showInfo(text)`**:显示信息
|
|
281
|
+
|
|
282
|
+
#### 4. 配置管理
|
|
283
|
+
|
|
284
|
+
- **`loadConfig(configName, cliOptions)`**:加载配置
|
|
285
|
+
- **`saveConfig(config, configName, isGlobal)`**:保存配置
|
|
286
|
+
|
|
287
|
+
#### 5. 错误处理
|
|
288
|
+
|
|
289
|
+
- **`handleError(error, message)`**:处理错误
|
|
290
|
+
|
|
291
|
+
#### 6. 异步操作
|
|
292
|
+
|
|
293
|
+
- **`executeAsync(fn, loadingText, successText, errorText)`**:执行异步操作并显示加载状态
|
|
294
|
+
|
|
295
|
+
#### 7. 钩子系统
|
|
296
|
+
|
|
297
|
+
- **`registerHook(event, callback)`**:注册钩子
|
|
298
|
+
- **`triggerHook(event, data)`**:触发钩子
|
|
299
|
+
- **`hasHook(event)`**:检查是否注册了指定钩子
|
|
300
|
+
|
|
301
|
+
#### 8. 剪贴板操作
|
|
302
|
+
|
|
303
|
+
- **`copyToClipboard(text, successMessage)`**:复制文本到剪贴板
|
|
304
|
+
|
|
305
|
+
### 使用示例
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
const BasePlugin = require('../core/base-plugin');
|
|
309
|
+
|
|
310
|
+
class MyPlugin extends BasePlugin {
|
|
311
|
+
constructor(options = {}) {
|
|
312
|
+
super(options);
|
|
313
|
+
this.pluginName = 'my-plugin';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
registerCommands(program) {
|
|
317
|
+
program.command('mycommand')
|
|
318
|
+
.description('My plugin command')
|
|
319
|
+
.action(() => this.myCommand());
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async myCommand() {
|
|
323
|
+
try {
|
|
324
|
+
await this.executeAsync(
|
|
325
|
+
async () => {
|
|
326
|
+
// 执行异步操作
|
|
327
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
328
|
+
return '操作结果';
|
|
329
|
+
},
|
|
330
|
+
'执行操作...',
|
|
331
|
+
'操作成功',
|
|
332
|
+
'操作失败'
|
|
333
|
+
);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
this.handleError(error, '执行命令失败');
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
module.exports = MyPlugin;
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## 插件生命周期
|
|
344
|
+
|
|
345
|
+
插件的生命周期包括以下阶段:
|
|
346
|
+
|
|
347
|
+
1. **初始化**:插件被加载时,系统会创建插件实例并准备执行环境
|
|
348
|
+
2. **命令注册**:调用 `registerCommands` 方法注册命令
|
|
349
|
+
3. **命令执行**:用户运行插件命令时,系统会调用相应的命令方法
|
|
350
|
+
4. **清理**:命令执行完成后,系统会清理临时资源
|
|
351
|
+
|
|
352
|
+
## 测试插件
|
|
353
|
+
|
|
354
|
+
### 本地测试
|
|
355
|
+
|
|
356
|
+
#### 1. 创建插件
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
xx plugin create <plugin-name> --format old
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### 2. 编辑插件文件,实现具体功能
|
|
363
|
+
|
|
364
|
+
#### 3. 运行插件命令
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
# 直接运行
|
|
368
|
+
node bin/cli.js <plugin-command>
|
|
369
|
+
|
|
370
|
+
# 或者使用 xx 命令(如果已全局安装)
|
|
371
|
+
xx <plugin-command>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### 命令测试
|
|
375
|
+
|
|
376
|
+
测试命令选项和参数:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
# 测试布尔选项
|
|
380
|
+
xx <plugin-command> --verbose
|
|
381
|
+
|
|
382
|
+
# 测试值选项
|
|
383
|
+
xx <plugin-command> --name John
|
|
384
|
+
|
|
385
|
+
# 测试组合选项
|
|
386
|
+
xx <plugin-command> --verbose --name John
|
|
387
|
+
|
|
388
|
+
# 测试数组选项
|
|
389
|
+
xx <plugin-command> --array item1 item2 item3
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## 部署插件
|
|
393
|
+
|
|
394
|
+
### 发布到插件目录
|
|
395
|
+
|
|
396
|
+
1. 确保插件文件位于 `lib/plugins` 或 `lib/myplugins` 目录
|
|
397
|
+
2. 运行 `xx plugin list` 命令,确认插件已被识别
|
|
398
|
+
3. 插件会自动加载并注册到命令系统中
|
|
399
|
+
|
|
400
|
+
### 分享插件
|
|
401
|
+
|
|
402
|
+
如果要与他人分享插件,可以:
|
|
403
|
+
|
|
404
|
+
1. 将插件文件发送给他人
|
|
405
|
+
2. 他人将插件文件放入其 `lib/plugins` 或 `lib/myplugins` 目录
|
|
406
|
+
3. 运行 `xx plugin list` 命令,确认插件已被识别
|
|
407
|
+
|
|
408
|
+
### 从外部添加插件
|
|
409
|
+
|
|
410
|
+
可以使用 `plugin add` 命令从 URL 或本地文件添加插件:
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
# 从 URL 添加插件
|
|
414
|
+
xx plugin add https://example.com/my-plugin.js
|
|
415
|
+
|
|
416
|
+
# 从本地文件添加插件
|
|
417
|
+
xx plugin add /path/to/my-plugin.js
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## 最佳实践
|
|
421
|
+
|
|
422
|
+
### 命名规范
|
|
423
|
+
|
|
424
|
+
1. **插件名称**:使用小写字母和连字符,例如 `hello-world`
|
|
425
|
+
2. **命令名称**:使用驼峰命名法,例如 `helloWorld`
|
|
426
|
+
3. **选项名称**:使用小写字母和连字符,例如 `--verbose`
|
|
427
|
+
4. **变量名称**:使用驼峰命名法,例如 `pluginName`
|
|
428
|
+
|
|
429
|
+
### 代码组织
|
|
430
|
+
|
|
431
|
+
1. **模块化**:将不同功能的代码分离到不同的方法中
|
|
432
|
+
2. **注释**:为关键代码添加注释,解释功能和实现细节
|
|
433
|
+
3. **错误处理**:使用 try-catch 捕获和处理错误
|
|
434
|
+
4. **日志**:使用 `BasePlugin` 提供的方法输出日志信息
|
|
435
|
+
|
|
436
|
+
### 错误处理
|
|
437
|
+
|
|
438
|
+
1. **捕获错误**:使用 try-catch 捕获可能的错误
|
|
439
|
+
2. **错误提示**:使用 `this.showError` 显示友好的错误信息
|
|
440
|
+
3. **错误传递**:对于无法处理的错误,应该向上传递
|
|
441
|
+
|
|
442
|
+
### 用户体验
|
|
443
|
+
|
|
444
|
+
1. **加载状态**:对于耗时操作,使用 `this.startLoading` 显示加载状态
|
|
445
|
+
2. **成功提示**:操作成功后,使用 `this.showSuccess` 显示成功信息
|
|
446
|
+
3. **信息提示**:使用 `this.showInfo` 显示有用的信息
|
|
447
|
+
4. **警告提示**:使用 `this.showWarning` 显示警告信息
|