@xubill/xx-cli 1.0.6
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 +87 -0
- package/lib/core/base-plugin.js +292 -0
- package/lib/core/index.js +369 -0
- package/lib/plugins/config-manager.js +140 -0
- package/lib/plugins/history.js +97 -0
- package/lib/plugins/plugin-manager.js +488 -0
- package/lib/utils/config-manager.js +185 -0
- package/lib/utils/history-manager.js +118 -0
- package/lib/utils/logger.js +28 -0
- package/lib/utils/plugin-config.js +158 -0
- package/lib/utils/transformers/getInput.js +76 -0
- package/lib/utils/transformers/index.js +43 -0
- package/lib/utils/transformers/renderLists.js +94 -0
- package/lib/utils/transformers/sfcMaterial.js +113 -0
- package/lib/utils/transformers/transformFilter.js +95 -0
- package/lib/utils/transformers/transformList.js +120 -0
- package/lib/utils/transformers/transformMock.js +43 -0
- package/lib/utils/transformers/transformRoutes.js +36 -0
- package/lib/utils/transformers/transformTable.js +81 -0
- package/lib/utils/transformers/transformTemplate.js +129 -0
- package/lib/utils/validator.js +96 -0
- package/package.json +62 -0
- package/readme.md +474 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 插件管理插件
|
|
3
|
+
* 用于管理所有插件
|
|
4
|
+
*
|
|
5
|
+
* 命令说明:
|
|
6
|
+
* - plugin [options]:管理所有插件
|
|
7
|
+
* - 示例:plugin list
|
|
8
|
+
* - 示例:plugin enable <plugin>
|
|
9
|
+
* - 示例:plugin disable <plugin>
|
|
10
|
+
* - 示例:plugin add <url>
|
|
11
|
+
* - 示例:plugin remove <plugin>
|
|
12
|
+
* - 示例:plugin create <name>
|
|
13
|
+
* - 示例:plugin config <plugin> [key] [value]
|
|
14
|
+
*
|
|
15
|
+
* 功能说明:
|
|
16
|
+
* - 列出所有插件
|
|
17
|
+
* - 启用指定插件
|
|
18
|
+
* - 禁用指定插件
|
|
19
|
+
* - 添加外部插件
|
|
20
|
+
* - 删除指定插件
|
|
21
|
+
* - 创建指定名称的插件模板
|
|
22
|
+
* - 查看和设置插件配置
|
|
23
|
+
*
|
|
24
|
+
* 用户体验:
|
|
25
|
+
* - 使用标准化的用户提示
|
|
26
|
+
* - 提供清晰的错误提示
|
|
27
|
+
* - 支持命令行参数
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const fs = require('fs-extra');
|
|
31
|
+
const path = require('path');
|
|
32
|
+
const https = require('https');
|
|
33
|
+
const http = require('http');
|
|
34
|
+
const BasePlugin = require('../core/base-plugin');
|
|
35
|
+
const validator = require('../utils/validator');
|
|
36
|
+
const pluginConfig = require('../utils/plugin-config');
|
|
37
|
+
|
|
38
|
+
class PluginManagerPlugin extends BasePlugin {
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
super(options);
|
|
41
|
+
this.pluginName = 'plugin-manager';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 注册命令
|
|
46
|
+
* @param {Object} program - Commander.js 实例
|
|
47
|
+
*/
|
|
48
|
+
registerCommands(program) {
|
|
49
|
+
// 插件管理命令
|
|
50
|
+
const pluginCommand = program.command('plugin')
|
|
51
|
+
.alias('p')
|
|
52
|
+
.description('插件管理');
|
|
53
|
+
|
|
54
|
+
// 列出插件
|
|
55
|
+
pluginCommand.command('list')
|
|
56
|
+
.alias('ls')
|
|
57
|
+
.description('列出所有插件')
|
|
58
|
+
.action(() => this.listPlugins());
|
|
59
|
+
|
|
60
|
+
// 启用插件
|
|
61
|
+
pluginCommand.command('enable <plugin>')
|
|
62
|
+
.alias('en')
|
|
63
|
+
.description('启用插件')
|
|
64
|
+
.action((plugin) => this.enablePlugin(plugin));
|
|
65
|
+
|
|
66
|
+
// 禁用插件
|
|
67
|
+
pluginCommand.command('disable <plugin>')
|
|
68
|
+
.alias('dis')
|
|
69
|
+
.description('禁用插件')
|
|
70
|
+
.action((plugin) => this.disablePlugin(plugin));
|
|
71
|
+
|
|
72
|
+
// 添加外部自定义插件
|
|
73
|
+
pluginCommand.command('add <url>')
|
|
74
|
+
.description('添加外部自定义插件')
|
|
75
|
+
.action((url) => this.addExternalPlugin(url));
|
|
76
|
+
|
|
77
|
+
// 删除插件
|
|
78
|
+
pluginCommand.command('remove <plugin>')
|
|
79
|
+
.alias('rm')
|
|
80
|
+
.description('删除指定插件')
|
|
81
|
+
.action((plugin) => this.removePlugin(plugin));
|
|
82
|
+
|
|
83
|
+
// 创建插件
|
|
84
|
+
pluginCommand.command('create <name>')
|
|
85
|
+
.alias('cr')
|
|
86
|
+
.description('创建指定名称的插件模板')
|
|
87
|
+
.action(async (name, options) => this.createPlugin(name, options));
|
|
88
|
+
|
|
89
|
+
// 插件配置管理
|
|
90
|
+
pluginCommand.command('config <plugin> [key] [value]')
|
|
91
|
+
.alias('c')
|
|
92
|
+
.description('查看和设置插件配置')
|
|
93
|
+
.action((plugin, key, value) => this.managePluginConfig(plugin, key, value));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 列出所有插件
|
|
98
|
+
*/
|
|
99
|
+
listPlugins() {
|
|
100
|
+
const core = require('../core');
|
|
101
|
+
const pluginsInfo = core.getPluginsInfo();
|
|
102
|
+
console.log('\n可用插件列表:');
|
|
103
|
+
console.log('=====================================');
|
|
104
|
+
pluginsInfo.forEach(plugin => {
|
|
105
|
+
const isDisabled = pluginConfig.isPluginDisabled(plugin.name);
|
|
106
|
+
const status = isDisabled ? '[已禁用]' : '[已启用]';
|
|
107
|
+
console.log(`- ${plugin.name} ${status}`);
|
|
108
|
+
});
|
|
109
|
+
console.log('=====================================');
|
|
110
|
+
console.log(`总计: ${pluginsInfo.length} 个插件\n`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 启用插件
|
|
115
|
+
* @param {string} pluginName - 插件名称
|
|
116
|
+
*/
|
|
117
|
+
enablePlugin(pluginName) {
|
|
118
|
+
// 验证插件名称
|
|
119
|
+
if (!validator.validateCommandParam(pluginName)) {
|
|
120
|
+
console.error('错误: 插件名称包含无效字符');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 启用插件
|
|
125
|
+
pluginConfig.enablePlugin(pluginName);
|
|
126
|
+
console.log(`已启用插件: ${pluginName}`);
|
|
127
|
+
console.log('提示: 请重新运行命令以应用更改');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 禁用插件
|
|
132
|
+
* @param {string} pluginName - 插件名称
|
|
133
|
+
*/
|
|
134
|
+
disablePlugin(pluginName) {
|
|
135
|
+
// 验证插件名称
|
|
136
|
+
if (!validator.validateCommandParam(pluginName)) {
|
|
137
|
+
console.error('错误: 插件名称包含无效字符');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 禁用插件
|
|
142
|
+
pluginConfig.disablePlugin(pluginName);
|
|
143
|
+
console.log(`已禁用插件: ${pluginName}`);
|
|
144
|
+
console.log('提示: 请重新运行命令以应用更改');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 删除插件
|
|
149
|
+
* @param {string} pluginName - 插件名称
|
|
150
|
+
*/
|
|
151
|
+
removePlugin(pluginName) {
|
|
152
|
+
// 验证插件名称
|
|
153
|
+
if (!validator.validateCommandParam(pluginName)) {
|
|
154
|
+
console.error('错误: 插件名称包含无效字符');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 内置插件目录
|
|
159
|
+
const builtinPluginsDir = path.join(__dirname, '../plugins');
|
|
160
|
+
// 用户插件目录
|
|
161
|
+
const userPluginsDir = path.join(process.env.HOME || process.env.USERPROFILE, '.xx-cli', 'plugins');
|
|
162
|
+
|
|
163
|
+
// 构建插件文件路径
|
|
164
|
+
const pluginFileName = `${pluginName}.js`;
|
|
165
|
+
let pluginPath = path.join(builtinPluginsDir, pluginFileName);
|
|
166
|
+
|
|
167
|
+
// 如果内置插件目录中不存在,则从用户插件目录查找
|
|
168
|
+
if (!fs.existsSync(pluginPath)) {
|
|
169
|
+
pluginPath = path.join(userPluginsDir, pluginFileName);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 检查插件文件是否存在
|
|
173
|
+
if (!fs.existsSync(pluginPath)) {
|
|
174
|
+
console.error(`错误: 插件文件不存在`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
// 删除插件文件
|
|
180
|
+
fs.unlinkSync(pluginPath);
|
|
181
|
+
|
|
182
|
+
// 从配置中移除插件的禁用状态
|
|
183
|
+
pluginConfig.removePluginFromConfig(pluginName);
|
|
184
|
+
|
|
185
|
+
console.log(`已成功删除插件: ${pluginName}`);
|
|
186
|
+
console.log('提示: 请重新运行命令以应用更改');
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error(`删除插件失败: ${error.message}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 创建插件
|
|
194
|
+
* @param {string} name - 插件名称
|
|
195
|
+
* @param {Object} options - 选项
|
|
196
|
+
*/
|
|
197
|
+
async createPlugin(name, options) {
|
|
198
|
+
try {
|
|
199
|
+
console.log('创建插件模板...');
|
|
200
|
+
|
|
201
|
+
// 插件目录路径(用户插件目录,不会被重新安装影响)
|
|
202
|
+
const pluginsDir = path.join(process.env.HOME || process.env.USERPROFILE, '.xx-cli', 'plugins');
|
|
203
|
+
const pluginFileName = `${name}.js`;
|
|
204
|
+
const pluginPath = path.join(pluginsDir, pluginFileName);
|
|
205
|
+
|
|
206
|
+
// 检查插件是否已存在
|
|
207
|
+
if (fs.existsSync(pluginPath)) {
|
|
208
|
+
console.log(`插件 ${name} 已存在,请使用其他名称`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 生成插件模板内容
|
|
213
|
+
const pluginContent = this.generatePluginTemplate(name);
|
|
214
|
+
|
|
215
|
+
// 写入插件文件
|
|
216
|
+
await fs.writeFile(pluginPath, pluginContent);
|
|
217
|
+
|
|
218
|
+
console.log(`插件模板已成功创建到:${pluginPath}`);
|
|
219
|
+
console.log('提示:请编辑插件文件,实现具体功能');
|
|
220
|
+
console.log('测试方法:运行 "node bin/cli.js <插件命令>"');
|
|
221
|
+
|
|
222
|
+
// 使用 vscode 打开创建的插件文件
|
|
223
|
+
try {
|
|
224
|
+
const { exec } = require('child_process');
|
|
225
|
+
const os = require('os');
|
|
226
|
+
let command;
|
|
227
|
+
|
|
228
|
+
// 根据操作系统类型构建不同的命令
|
|
229
|
+
if (os.platform() === 'win32') {
|
|
230
|
+
// Windows 系统
|
|
231
|
+
command = `start code "${pluginPath}"`;
|
|
232
|
+
} else {
|
|
233
|
+
// macOS 和 Linux 系统
|
|
234
|
+
command = `code "${pluginPath}"`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
exec(command, (error) => {
|
|
238
|
+
if (error) {
|
|
239
|
+
console.log('提示:无法自动打开 vscode,请手动打开插件文件进行编辑');
|
|
240
|
+
} else {
|
|
241
|
+
console.log('已自动打开 vscode 编辑插件文件');
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.log('提示:无法自动打开 vscode,请手动打开插件文件进行编辑');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error('创建插件模板失败:', error.message);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 生成插件模板内容
|
|
255
|
+
* @param {string} name - 插件名称
|
|
256
|
+
* @returns {string} 插件模板内容
|
|
257
|
+
*/
|
|
258
|
+
generatePluginTemplate(name) {
|
|
259
|
+
// 生成插件命令名称(去除后缀,转小写,连字符转驼峰)
|
|
260
|
+
const commandName = name.replace('.js', '').toLowerCase().replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
261
|
+
|
|
262
|
+
// 导入路径
|
|
263
|
+
const basePluginPath = '../core/base-plugin';
|
|
264
|
+
|
|
265
|
+
return `/**
|
|
266
|
+
* ${name} 插件
|
|
267
|
+
* 用于实现 ${name} 相关功能
|
|
268
|
+
*
|
|
269
|
+
* 命令说明:
|
|
270
|
+
* - ${commandName} [options]:${name} 相关功能
|
|
271
|
+
* - 示例:${commandName}
|
|
272
|
+
*
|
|
273
|
+
* 功能说明:
|
|
274
|
+
* - 实现 ${name} 相关功能
|
|
275
|
+
*
|
|
276
|
+
* 用户体验:
|
|
277
|
+
* - 使用标准化的用户提示
|
|
278
|
+
* - 提供清晰的错误提示
|
|
279
|
+
* - 支持命令行参数
|
|
280
|
+
*/
|
|
281
|
+
|
|
282
|
+
const BasePlugin = require('${basePluginPath}');
|
|
283
|
+
|
|
284
|
+
class ${this.toCamelCase(name)}Plugin extends BasePlugin {
|
|
285
|
+
constructor(options = {}) {
|
|
286
|
+
super(options);
|
|
287
|
+
this.pluginName = '${name}';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* 注册命令
|
|
292
|
+
* @param {Object} program - Commander.js 实例
|
|
293
|
+
*/
|
|
294
|
+
registerCommands(program) {
|
|
295
|
+
program.command('${commandName}')
|
|
296
|
+
.description('${name} 相关功能')
|
|
297
|
+
.option('-v, --verbose', '显示详细信息')
|
|
298
|
+
.option('-h, --help', '显示帮助信息')
|
|
299
|
+
.action((options) => this.${commandName}Command(options));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* ${name} 命令
|
|
304
|
+
* @param {Object} options - 命令选项
|
|
305
|
+
*/
|
|
306
|
+
${commandName}Command(options) {
|
|
307
|
+
console.log('${name} 命令执行成功!');
|
|
308
|
+
if (options && options.verbose) {
|
|
309
|
+
console.log('详细信息:');
|
|
310
|
+
console.log('- 命令名称:', '${commandName}');
|
|
311
|
+
console.log('- 执行时间:', new Date().toLocaleString());
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
module.exports = ${this.toCamelCase(name)}Plugin;
|
|
317
|
+
`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 转换为驼峰命名
|
|
322
|
+
* @param {string} str - 字符串
|
|
323
|
+
* @returns {string} 驼峰命名的字符串
|
|
324
|
+
*/
|
|
325
|
+
toCamelCase(str) {
|
|
326
|
+
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase()).replace(/^[a-z]/, (g) => g.toUpperCase());
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 管理插件配置
|
|
331
|
+
* @param {string} pluginName - 插件名称
|
|
332
|
+
* @param {string} key - 配置键
|
|
333
|
+
* @param {string} value - 配置值
|
|
334
|
+
*/
|
|
335
|
+
managePluginConfig(pluginName, key, value) {
|
|
336
|
+
// 验证插件名称
|
|
337
|
+
if (!validator.validateCommandParam(pluginName)) {
|
|
338
|
+
console.error('错误: 插件名称包含无效字符');
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
343
|
+
const xxDir = path.join(homeDir, '.xx-cli');
|
|
344
|
+
const pluginConfigDir = path.join(xxDir, pluginName);
|
|
345
|
+
const configFile = path.join(pluginConfigDir, 'config.json');
|
|
346
|
+
|
|
347
|
+
// 确保插件配置目录存在
|
|
348
|
+
fs.ensureDirSync(pluginConfigDir);
|
|
349
|
+
|
|
350
|
+
// 读取现有配置
|
|
351
|
+
let config = {};
|
|
352
|
+
if (fs.existsSync(configFile)) {
|
|
353
|
+
config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!key) {
|
|
357
|
+
// 显示所有配置
|
|
358
|
+
console.log(`插件 ${pluginName} 的配置:`);
|
|
359
|
+
console.log('=====================================');
|
|
360
|
+
if (Object.keys(config).length > 0) {
|
|
361
|
+
Object.entries(config).forEach(([k, v]) => {
|
|
362
|
+
console.log(`${k}: ${v}`);
|
|
363
|
+
});
|
|
364
|
+
} else {
|
|
365
|
+
console.log('无配置项');
|
|
366
|
+
}
|
|
367
|
+
console.log('=====================================');
|
|
368
|
+
} else if (!value) {
|
|
369
|
+
// 显示指定配置
|
|
370
|
+
console.log(`插件 ${pluginName} 的配置 ${key}:`);
|
|
371
|
+
console.log('=====================================');
|
|
372
|
+
if (config.hasOwnProperty(key)) {
|
|
373
|
+
console.log(`${key}: ${config[key]}`);
|
|
374
|
+
} else {
|
|
375
|
+
console.log(`配置项 ${key} 不存在`);
|
|
376
|
+
}
|
|
377
|
+
console.log('=====================================');
|
|
378
|
+
} else {
|
|
379
|
+
// 设置配置
|
|
380
|
+
config[key] = value;
|
|
381
|
+
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
382
|
+
console.log(`已设置插件 ${pluginName} 的配置 ${key} = ${value}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* 添加外部插件
|
|
388
|
+
* @param {string} url - 插件 URL
|
|
389
|
+
*/
|
|
390
|
+
async addExternalPlugin(url) {
|
|
391
|
+
try {
|
|
392
|
+
// 用户插件目录(不会被重新安装影响)
|
|
393
|
+
const userPluginsDir = path.join(process.env.HOME || process.env.USERPROFILE, '.xx-cli', 'plugins');
|
|
394
|
+
|
|
395
|
+
// 确保用户插件目录存在
|
|
396
|
+
fs.ensureDirSync(userPluginsDir);
|
|
397
|
+
|
|
398
|
+
// 提取插件名称
|
|
399
|
+
const pluginFileName = path.basename(url);
|
|
400
|
+
const pluginPath = path.join(userPluginsDir, pluginFileName);
|
|
401
|
+
|
|
402
|
+
// 检查插件是否已存在
|
|
403
|
+
if (fs.existsSync(pluginPath)) {
|
|
404
|
+
console.log(`插件 ${pluginFileName} 已存在,是否覆盖?`);
|
|
405
|
+
// 这里可以添加交互式确认
|
|
406
|
+
console.log('已覆盖现有插件');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// 下载或复制插件
|
|
410
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
411
|
+
// 是 URL,下载插件
|
|
412
|
+
console.log(`正在从 URL 下载插件: ${url}`);
|
|
413
|
+
|
|
414
|
+
// 选择 HTTP 或 HTTPS
|
|
415
|
+
const protocol = url.startsWith('https://') ? https : http;
|
|
416
|
+
|
|
417
|
+
await new Promise((resolve, reject) => {
|
|
418
|
+
protocol.get(url, (response) => {
|
|
419
|
+
if (response.statusCode === 200) {
|
|
420
|
+
// 获取文件大小
|
|
421
|
+
const fileSize = parseInt(response.headers['content-length'], 10);
|
|
422
|
+
let downloadedSize = 0;
|
|
423
|
+
|
|
424
|
+
// 创建写入流
|
|
425
|
+
const writer = fs.createWriteStream(pluginPath);
|
|
426
|
+
|
|
427
|
+
// 监听数据事件,显示进度
|
|
428
|
+
response.on('data', (chunk) => {
|
|
429
|
+
downloadedSize += chunk.length;
|
|
430
|
+
const percentage = Math.round((downloadedSize / fileSize) * 100);
|
|
431
|
+
|
|
432
|
+
// 清除当前行并显示进度
|
|
433
|
+
process.stdout.clearLine();
|
|
434
|
+
process.stdout.cursorTo(0);
|
|
435
|
+
process.stdout.write(`下载进度: ${percentage}%`);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// 管道数据到文件
|
|
439
|
+
response.pipe(writer);
|
|
440
|
+
|
|
441
|
+
// 监听完成事件
|
|
442
|
+
writer.on('finish', () => {
|
|
443
|
+
console.log(''); // 换行
|
|
444
|
+
console.log(`插件已成功下载到: ${pluginPath}`);
|
|
445
|
+
resolve();
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// 监听错误事件
|
|
449
|
+
writer.on('error', (error) => {
|
|
450
|
+
reject(error);
|
|
451
|
+
});
|
|
452
|
+
} else {
|
|
453
|
+
reject(new Error(`下载失败,状态码: ${response.statusCode}`));
|
|
454
|
+
}
|
|
455
|
+
}).on('error', (error) => {
|
|
456
|
+
reject(error);
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
} else if (fs.existsSync(url) && url.endsWith('.js')) {
|
|
460
|
+
// 是本地文件,拷贝插件
|
|
461
|
+
console.log(`正在从本地文件拷贝插件: ${url}`);
|
|
462
|
+
|
|
463
|
+
// 提取文件名
|
|
464
|
+
const pluginFileName = path.basename(url);
|
|
465
|
+
const pluginPath = path.join(userPluginsDir, pluginFileName);
|
|
466
|
+
|
|
467
|
+
// 拷贝文件
|
|
468
|
+
await fs.copy(url, pluginPath);
|
|
469
|
+
|
|
470
|
+
console.log(`插件已成功拷贝到: ${pluginPath}`);
|
|
471
|
+
} else {
|
|
472
|
+
console.error('错误: 无效的插件URL或本地文件路径');
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// 提示用户
|
|
477
|
+
console.log('\n提示:');
|
|
478
|
+
console.log('- 插件已自动注册到命令系统');
|
|
479
|
+
console.log('- 可以使用 "xx plugin list" 命令查看已安装的插件');
|
|
480
|
+
console.log('- 可以使用 "xx <plugin-command>" 命令使用插件功能');
|
|
481
|
+
console.log('- 此插件保存在用户目录中,不会被重新安装影响');
|
|
482
|
+
} catch (error) {
|
|
483
|
+
console.error('添加插件失败:', error.message);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
module.exports = PluginManagerPlugin;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一配置管理模块
|
|
3
|
+
* 提供标准化的配置加载、保存、验证功能
|
|
4
|
+
* 支持配置优先级:命令行参数 > 本地配置 > 全局配置
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const chalk = require('chalk').default;
|
|
10
|
+
|
|
11
|
+
class ConfigManager {
|
|
12
|
+
/**
|
|
13
|
+
* 构造函数
|
|
14
|
+
* @param {string} pluginName - 插件名称
|
|
15
|
+
* @param {Object} options - 配置选项
|
|
16
|
+
*/
|
|
17
|
+
constructor(pluginName, options = {}) {
|
|
18
|
+
this.homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
19
|
+
this.globalConfigDir = path.join(this.homeDir, '.xx-cli', pluginName);
|
|
20
|
+
this.localConfigPath = options.localConfigPath || `.${pluginName}Config`;
|
|
21
|
+
this.ensureConfigDir();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 确保配置目录存在
|
|
26
|
+
*/
|
|
27
|
+
ensureConfigDir() {
|
|
28
|
+
if (!fs.existsSync(this.globalConfigDir)) {
|
|
29
|
+
fs.mkdirSync(this.globalConfigDir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 加载配置
|
|
35
|
+
* @param {string} configName - 配置名称
|
|
36
|
+
* @param {Object} cliOptions - 命令行选项
|
|
37
|
+
* @returns {Object} 合并后的配置对象
|
|
38
|
+
*/
|
|
39
|
+
loadConfig(configName = 'config', cliOptions = {}) {
|
|
40
|
+
// 1. 加载全局配置
|
|
41
|
+
const globalConfig = this.loadGlobalConfig(configName);
|
|
42
|
+
|
|
43
|
+
// 2. 加载本地配置
|
|
44
|
+
const localConfig = this.loadLocalConfig(configName);
|
|
45
|
+
|
|
46
|
+
// 3. 合并配置:本地配置覆盖全局配置,命令行选项覆盖本地配置
|
|
47
|
+
const mergedConfig = {
|
|
48
|
+
...globalConfig,
|
|
49
|
+
...localConfig,
|
|
50
|
+
...cliOptions
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return mergedConfig;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 加载全局配置
|
|
58
|
+
* @param {string} configName - 配置名称
|
|
59
|
+
* @returns {Object} 全局配置对象
|
|
60
|
+
*/
|
|
61
|
+
loadGlobalConfig(configName = 'config') {
|
|
62
|
+
const configPath = path.join(this.globalConfigDir, `${configName}.json`);
|
|
63
|
+
if (fs.existsSync(configPath)) {
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(chalk.red(`解析全局配置文件失败: ${error.message}`));
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 加载本地配置
|
|
76
|
+
* @param {string} configName - 配置名称
|
|
77
|
+
* @returns {Object} 本地配置对象
|
|
78
|
+
*/
|
|
79
|
+
loadLocalConfig(configName = 'config') {
|
|
80
|
+
const configPath = path.resolve(this.localConfigPath);
|
|
81
|
+
if (fs.existsSync(configPath)) {
|
|
82
|
+
try {
|
|
83
|
+
const localConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
84
|
+
// 如果是多环境配置,返回指定环境的配置
|
|
85
|
+
if (localConfig[configName]) {
|
|
86
|
+
return localConfig[configName];
|
|
87
|
+
}
|
|
88
|
+
return localConfig;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(chalk.red(`解析本地配置文件失败: ${error.message}`));
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 保存配置
|
|
99
|
+
* @param {Object} config - 配置对象
|
|
100
|
+
* @param {string} configName - 配置名称
|
|
101
|
+
* @param {boolean} isGlobal - 是否保存到全局配置
|
|
102
|
+
*/
|
|
103
|
+
saveConfig(config, configName = 'config', isGlobal = true) {
|
|
104
|
+
try {
|
|
105
|
+
const configPath = isGlobal
|
|
106
|
+
? path.join(this.globalConfigDir, `${configName}.json`)
|
|
107
|
+
: path.resolve(this.localConfigPath);
|
|
108
|
+
|
|
109
|
+
// 确保目录存在
|
|
110
|
+
if (isGlobal) {
|
|
111
|
+
this.ensureConfigDir();
|
|
112
|
+
} else {
|
|
113
|
+
const configDir = path.dirname(configPath);
|
|
114
|
+
if (!fs.existsSync(configDir)) {
|
|
115
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
120
|
+
console.log(chalk.green(`配置保存成功: ${configPath}`));
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error(chalk.red(`保存配置失败: ${error.message}`));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 验证配置
|
|
128
|
+
* @param {Object} config - 配置对象
|
|
129
|
+
* @param {Object} schema - 配置 schema
|
|
130
|
+
* @returns {Object} 验证结果
|
|
131
|
+
*/
|
|
132
|
+
validateConfig(config, schema) {
|
|
133
|
+
if (!schema) {
|
|
134
|
+
return { valid: true, data: config };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 这里可以集成 Joi 等验证库
|
|
138
|
+
// 暂时返回简单验证
|
|
139
|
+
return { valid: true, data: config };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 获取配置路径
|
|
144
|
+
* @param {string} configName - 配置名称
|
|
145
|
+
* @param {boolean} isGlobal - 是否获取全局配置路径
|
|
146
|
+
* @returns {string} 配置文件路径
|
|
147
|
+
*/
|
|
148
|
+
getConfigPath(configName = 'config', isGlobal = true) {
|
|
149
|
+
return isGlobal
|
|
150
|
+
? path.join(this.globalConfigDir, `${configName}.json`)
|
|
151
|
+
: path.resolve(this.localConfigPath);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 检查配置是否存在
|
|
156
|
+
* @param {string} configName - 配置名称
|
|
157
|
+
* @param {boolean} isGlobal - 是否检查全局配置
|
|
158
|
+
* @returns {boolean} 配置是否存在
|
|
159
|
+
*/
|
|
160
|
+
hasConfig(configName = 'config', isGlobal = true) {
|
|
161
|
+
const configPath = this.getConfigPath(configName, isGlobal);
|
|
162
|
+
return fs.existsSync(configPath);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 删除配置
|
|
167
|
+
* @param {string} configName - 配置名称
|
|
168
|
+
* @param {boolean} isGlobal - 是否删除全局配置
|
|
169
|
+
*/
|
|
170
|
+
deleteConfig(configName = 'config', isGlobal = true) {
|
|
171
|
+
try {
|
|
172
|
+
const configPath = this.getConfigPath(configName, isGlobal);
|
|
173
|
+
if (fs.existsSync(configPath)) {
|
|
174
|
+
fs.unlinkSync(configPath);
|
|
175
|
+
console.log(chalk.green(`配置删除成功: ${configPath}`));
|
|
176
|
+
} else {
|
|
177
|
+
console.log(chalk.yellow(`配置文件不存在: ${configPath}`));
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error(chalk.red(`删除配置失败: ${error.message}`));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = ConfigManager;
|