@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.
@@ -0,0 +1,369 @@
1
+ /**
2
+ * xx 核心模块
3
+ * 负责初始化系统、注册命令、管理插件等
4
+ */
5
+
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const logger = require("../utils/logger");
9
+ const pkg = require("../../package.json");
10
+ const pluginConfig = require("../utils/plugin-config");
11
+
12
+ class Core {
13
+ constructor() {
14
+ this.plugins = [];
15
+ this.commands = [];
16
+ this.version = pkg.version;
17
+ this.pluginFiles = [];
18
+ this.pluginCache = new Map(); // 插件缓存
19
+ this.hooks = {}; // 全局钩子
20
+ }
21
+
22
+ /**
23
+ * 初始化核心功能
24
+ */
25
+ async init() {
26
+ //logger.info('正在初始化 xx 核心...');
27
+ this.loadPlugins();
28
+
29
+ // 触发初始化完成钩子
30
+ await this.triggerHook("init", { core: this });
31
+
32
+ //logger.info('xx 核心初始化完成');
33
+ }
34
+
35
+ /**
36
+ * 加载插件
37
+ */
38
+ loadPlugins() {
39
+ // 内置插件目录(会被重新安装覆盖)
40
+ const builtinPluginsDir = path.join(__dirname, "../plugins");
41
+ // 用户插件目录(不会被重新安装影响)
42
+ const userPluginsDir = path.join(
43
+ process.env.HOME || process.env.USERPROFILE,
44
+ ".xx-cli",
45
+ "plugins"
46
+ );
47
+
48
+ // 确保用户插件目录存在
49
+ if (!fs.existsSync(userPluginsDir)) {
50
+ fs.mkdirSync(userPluginsDir, { recursive: true });
51
+ }
52
+
53
+ // 收集内置插件
54
+ let pluginFiles = [];
55
+ if (fs.existsSync(builtinPluginsDir)) {
56
+ const builtinFiles = fs.readdirSync(builtinPluginsDir);
57
+ pluginFiles = builtinFiles.filter((file) => file.endsWith(".js"));
58
+ }
59
+
60
+ // 收集用户插件
61
+ if (fs.existsSync(userPluginsDir)) {
62
+ const userFiles = fs.readdirSync(userPluginsDir);
63
+ const userPluginFiles = userFiles.filter((file) => file.endsWith(".js"));
64
+ // 将用户插件添加到插件文件列表中
65
+ pluginFiles = [...pluginFiles, ...userPluginFiles];
66
+ }
67
+
68
+ // 只收集插件文件信息,不立即加载
69
+ this.pluginFiles = pluginFiles;
70
+ }
71
+ /**
72
+ * 加载单个插件
73
+ * @param {string} pluginFile - 插件文件名
74
+ * @returns {Object|null} 插件实例或null
75
+ */
76
+ loadPlugin(pluginFile) {
77
+ // 检查缓存中是否已存在插件实例
78
+ if (this.pluginCache.has(pluginFile)) {
79
+ return this.pluginCache.get(pluginFile);
80
+ }
81
+
82
+ // 内置插件目录
83
+ const builtinPluginsDir = path.join(__dirname, "../plugins");
84
+ // 用户插件目录
85
+ const userPluginsDir = path.join(
86
+ process.env.HOME || process.env.USERPROFILE,
87
+ ".xx-cli",
88
+ "plugins"
89
+ );
90
+
91
+ // 优先从内置插件目录加载
92
+ let pluginPath = path.join(builtinPluginsDir, pluginFile);
93
+
94
+ // 如果内置插件目录中不存在,则从用户插件目录加载
95
+ if (!fs.existsSync(pluginPath)) {
96
+ pluginPath = path.join(userPluginsDir, pluginFile);
97
+ }
98
+
99
+ // 如果两个目录中都不存在,则返回null
100
+ if (!fs.existsSync(pluginPath)) {
101
+ logger.error(`插件文件 ${pluginFile} 不存在`);
102
+ return null;
103
+ }
104
+
105
+ try {
106
+ // 读取插件文件内容
107
+ let pluginContent = fs.readFileSync(pluginPath, "utf8");
108
+
109
+ // 替换插件文件中的相对路径导入为绝对路径导入
110
+ // 替换 ../core 为绝对路径
111
+ const coreDir = path.join(__dirname, "../core");
112
+ pluginContent = pluginContent.replace(
113
+ /require\('\.\.\/core\//g,
114
+ `require('${coreDir}/`
115
+ );
116
+
117
+ // 替换 ../utils 为绝对路径
118
+ const utilsDir = path.join(__dirname, "../utils");
119
+ pluginContent = pluginContent.replace(
120
+ /require\('\.\.\/utils\//g,
121
+ `require('${utilsDir}/`
122
+ );
123
+
124
+ // 创建一个临时模块来加载修改后的插件内容
125
+ const Module = require("module");
126
+ const tempModule = new Module(pluginPath);
127
+ tempModule.filename = pluginPath;
128
+ tempModule.paths = module.paths;
129
+
130
+ // 编译并执行修改后的插件内容
131
+ tempModule._compile(pluginContent, pluginPath);
132
+
133
+ const Plugin = tempModule.exports;
134
+
135
+ // 检查是否为类
136
+ if (typeof Plugin === "function" && Plugin.prototype) {
137
+ // 实例化类插件
138
+ const pluginInstance = new Plugin();
139
+ pluginInstance.pluginName =
140
+ pluginInstance.pluginName || pluginFile.replace(".js", "");
141
+
142
+ // 缓存插件实例
143
+ this.pluginCache.set(pluginFile, pluginInstance);
144
+ return pluginInstance;
145
+ } else {
146
+ // 保持非类插件的兼容性
147
+
148
+ // 缓存插件
149
+ this.pluginCache.set(pluginFile, Plugin);
150
+ return Plugin;
151
+ }
152
+ } catch (error) {
153
+ logger.error(`加载插件 ${pluginFile} 失败: ${error.message}`);
154
+ return null;
155
+ }
156
+ }
157
+ /**
158
+ * 获取所有插件信息
159
+ * @returns {Array} 插件信息数组
160
+ */
161
+ getPluginsInfo() {
162
+ return this.pluginFiles.map((pluginFile) => {
163
+ const pluginName = pluginFile.replace(".js", "");
164
+ return {
165
+ name: pluginName,
166
+ file: pluginFile,
167
+ };
168
+ });
169
+ }
170
+
171
+ /**
172
+ * 注册命令到 Commander
173
+ * @param {Object} program - Commander 实例
174
+ */
175
+ registerCommands(program) {
176
+ // 注册核心命令
177
+ this.registerCoreCommands(program);
178
+
179
+ // 注册插件命令
180
+ if (this.pluginFiles) {
181
+ this.pluginFiles.forEach((pluginFile) => {
182
+ const pluginName = pluginFile.replace(".js", "");
183
+
184
+ // 检查插件是否已禁用
185
+ if (pluginConfig.isPluginDisabled(pluginName)) {
186
+ return;
187
+ }
188
+
189
+ const plugin = this.loadPlugin(pluginFile);
190
+ if (plugin && plugin.registerCommands) {
191
+ try {
192
+ plugin.registerCommands(program);
193
+ } catch (error) {
194
+ // 捕获命令别名冲突等错误,确保其他插件能够正常注册
195
+ if (error.message && error.message.includes('cannot add alias')) {
196
+ console.warn(`警告: 插件 ${pluginName} 的命令别名与其他插件冲突,已跳过此插件的命令注册`);
197
+ } else {
198
+ console.error(`错误: 插件 ${pluginName} 注册命令失败: ${error.message}`);
199
+ }
200
+ }
201
+ }
202
+ });
203
+ }
204
+
205
+ // 注册命令自动建议功能
206
+ this.registerCommandSuggestions(program);
207
+ }
208
+
209
+ /**
210
+ * 注册命令自动建议功能
211
+ * @param {Object} program - Commander 实例
212
+ */
213
+ registerCommandSuggestions(program) {
214
+ // 收集所有命令信息
215
+ const allCommands = this.collectAllCommands(program);
216
+
217
+ // 添加命令自动建议
218
+ program.on("command:*", (args) => {
219
+ const inputCommand = args[0];
220
+ const suggestions = this.getCommandSuggestions(inputCommand, allCommands);
221
+
222
+ if (suggestions.length > 0) {
223
+ console.log(`\n未找到命令 "${inputCommand}",您可能想要:`);
224
+ suggestions.forEach((suggestion) => {
225
+ console.log(` - ${suggestion}`);
226
+ });
227
+ console.log("");
228
+ process.exit(1);
229
+ }
230
+ });
231
+ }
232
+
233
+ /**
234
+ * 收集所有命令信息
235
+ * @param {Object} program - Commander 实例
236
+ * @returns {Array} 命令数组
237
+ */
238
+ collectAllCommands(program) {
239
+ const commands = [];
240
+
241
+ // 收集核心命令
242
+ if (program.commands) {
243
+ program.commands.forEach((command) => {
244
+ // 添加命令名称
245
+ if (command._name && command._name !== "*") {
246
+ commands.push(command._name);
247
+ }
248
+
249
+ // 添加命令别名
250
+ if (command._alias) {
251
+ commands.push(command._alias);
252
+ }
253
+ });
254
+ }
255
+
256
+ return commands;
257
+ }
258
+
259
+ /**
260
+ * 获取命令建议
261
+ * @param {string} input - 用户输入
262
+ * @param {Array} commands - 命令数组
263
+ * @returns {Array} 建议的命令数组
264
+ */
265
+ getCommandSuggestions(input, commands) {
266
+ return commands
267
+ .filter((command) => {
268
+ return command.startsWith(input.toLowerCase());
269
+ })
270
+ .slice(0, 5); // 最多返回5个建议
271
+ }
272
+
273
+ /**
274
+ * 注册全局钩子
275
+ * @param {string} event - 事件名称
276
+ * @param {Function} callback - 回调函数
277
+ */
278
+ registerHook(event, callback) {
279
+ if (!this.hooks[event]) {
280
+ this.hooks[event] = [];
281
+ }
282
+
283
+ this.hooks[event].push(callback);
284
+ }
285
+
286
+ /**
287
+ * 触发全局钩子
288
+ * @param {string} event - 事件名称
289
+ * @param {*} data - 事件数据
290
+ * @returns {Promise<Array>} 钩子执行结果
291
+ */
292
+ async triggerHook(event, data) {
293
+ const results = [];
294
+
295
+ // 触发全局钩子
296
+ if (this.hooks[event]) {
297
+ for (const callback of this.hooks[event]) {
298
+ try {
299
+ const result = await callback(data);
300
+ results.push(result);
301
+ } catch (error) {
302
+ console.error(`执行全局钩子 ${event} 时出错:`, error.message);
303
+ }
304
+ }
305
+ }
306
+
307
+ // 触发所有插件的钩子
308
+ if (this.pluginFiles) {
309
+ for (const pluginFile of this.pluginFiles) {
310
+ const pluginName = pluginFile.replace(".js", "");
311
+
312
+ // 检查插件是否已禁用
313
+ if (pluginConfig.isPluginDisabled(pluginName)) {
314
+ continue;
315
+ }
316
+
317
+ const plugin = this.loadPlugin(pluginFile);
318
+ if (plugin && plugin.triggerHook) {
319
+ try {
320
+ const pluginResults = await plugin.triggerHook(event, data);
321
+ results.push(...pluginResults);
322
+ } catch (error) {
323
+ console.error(
324
+ `执行插件 ${pluginName} 的钩子 ${event} 时出错:`,
325
+ error.message
326
+ );
327
+ }
328
+ }
329
+ }
330
+ }
331
+
332
+ return results;
333
+ }
334
+
335
+ /**
336
+ * 检查是否注册了指定全局钩子
337
+ * @param {string} event - 事件名称
338
+ * @returns {boolean} 是否注册了钩子
339
+ */
340
+ hasHook(event) {
341
+ return this.hooks[event] && this.hooks[event].length > 0;
342
+ }
343
+
344
+ /**
345
+ * 注册核心命令
346
+ * @param {Object} program - Commander 实例
347
+ */
348
+ registerCoreCommands(program) {
349
+ // 帮助命令
350
+ program
351
+ .command("help")
352
+ .description("显示帮助信息")
353
+ .action(() => {
354
+ program.outputHelp();
355
+ });
356
+
357
+ // 版本命令
358
+ program
359
+ .command("version")
360
+ .alias("v")
361
+ .description("显示版本信息")
362
+ .action(() => {
363
+ console.log(this.version);
364
+ });
365
+ }
366
+ }
367
+
368
+ // 导出单例实例
369
+ module.exports = new Core();
@@ -0,0 +1,140 @@
1
+ /**
2
+ * 配置管理插件
3
+ * 用于管理所有插件的配置文件
4
+ *
5
+ * 命令说明:
6
+ * - config-manager [options]:管理所有插件的配置文件
7
+ * - 示例:config-manager export
8
+ * - 示例:config-manager import
9
+ * - 示例:config-manager list
10
+ *
11
+ * 功能说明:
12
+ * - 导出所有插件配置到指定目录
13
+ * - 从指定目录导入配置并覆盖
14
+ * - 列出所有插件的配置文件位置和状态
15
+ *
16
+ * 用户体验:
17
+ * - 使用标准化的用户提示
18
+ * - 提供清晰的错误提示
19
+ * - 支持命令行参数
20
+ */
21
+
22
+ const fs = require('fs-extra');
23
+ const path = require('path');
24
+ const BasePlugin = require('../core/base-plugin');
25
+
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());
56
+ }
57
+
58
+ /**
59
+ * 导出配置
60
+ * @param {string} target - 目标路径
61
+ */
62
+ exportConfig(target) {
63
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
64
+ const xxDir = path.join(homeDir, '.xx-cli');
65
+ const exportDir = target || path.join(process.cwd(), 'config-backup');
66
+ console.log(`开始导出配置到: ${exportDir}`);
67
+
68
+ // 确保导出目录存在
69
+ fs.ensureDirSync(exportDir);
70
+
71
+ // 复制 .xx 目录
72
+ if (fs.existsSync(xxDir)) {
73
+ const exportPath = path.join(exportDir, '.xx-cli');
74
+ fs.ensureDirSync(path.dirname(exportPath));
75
+ fs.copySync(xxDir, exportPath);
76
+ console.log(`导出目录: ${xxDir}`);
77
+ }
78
+
79
+ console.log('配置导出完成!');
80
+ }
81
+
82
+ /**
83
+ * 导入配置
84
+ * @param {string} source - 源路径
85
+ */
86
+ importConfig(source) {
87
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
88
+ const xxDir = path.join(homeDir, '.xx-cli');
89
+ const importDir = source || path.join(process.cwd(), 'config-backup');
90
+ console.log(`开始从 ${importDir} 导入配置...`);
91
+
92
+ // 检查导入目录是否存在
93
+ if (!fs.existsSync(importDir)) {
94
+ console.log(`导入目录不存在: ${importDir}`);
95
+ return;
96
+ }
97
+
98
+ // 复制配置到 .xx 目录
99
+ const importPath = path.join(importDir, '.xx-cli');
100
+ if (fs.existsSync(importPath)) {
101
+ fs.ensureDirSync(path.dirname(xxDir));
102
+ fs.copySync(importPath, xxDir, { overwrite: true });
103
+ console.log(`导入目录: ${importPath}`);
104
+ }
105
+
106
+ console.log('配置导入完成!');
107
+ }
108
+
109
+ /**
110
+ * 列出配置
111
+ */
112
+ listConfig() {
113
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
114
+ const xxDir = path.join(homeDir, '.xx-cli');
115
+ console.log('列出所有插件配置文件...\n');
116
+
117
+ if (fs.existsSync(xxDir)) {
118
+ const items = fs.readdirSync(xxDir).filter(item => !item.startsWith('.') && item !== 'plugins');
119
+ items.forEach(item => {
120
+ const itemPath = path.join(xxDir, item);
121
+ const stat = fs.statSync(itemPath);
122
+ if (stat.isDirectory()) {
123
+ console.log(`插件配置目录: ${itemPath}`);
124
+ const configFiles = fs.readdirSync(itemPath).filter(file => !file.startsWith('.'));
125
+ configFiles.forEach(file => {
126
+ console.log(` - ${file}`);
127
+ });
128
+ } else if (stat.isFile()) {
129
+ console.log(`配置文件: ${itemPath}`);
130
+ }
131
+ });
132
+ } else {
133
+ console.log('配置目录不存在');
134
+ }
135
+
136
+ console.log('配置列表完成!');
137
+ }
138
+ }
139
+
140
+ module.exports = ConfigManagerPlugin;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * 历史记录插件
3
+ * 用于查看命令历史记录
4
+ *
5
+ * 命令说明:
6
+ * - history [options]:查看命令历史记录
7
+ * - 示例:history
8
+ * - 示例:history --limit 20
9
+ * - 示例:history --clear
10
+ * - 示例:history --search <keyword>
11
+ *
12
+ * 功能说明:
13
+ * - 查看命令历史记录
14
+ * - 限制显示的历史记录数量
15
+ * - 清除命令历史记录
16
+ * - 搜索命令历史记录
17
+ *
18
+ * 用户体验:
19
+ * - 使用标准化的用户提示
20
+ * - 提供清晰的错误提示
21
+ * - 支持命令行参数
22
+ */
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));
46
+ }
47
+
48
+ /**
49
+ * 显示命令历史记录
50
+ * @param {Object} options - 选项
51
+ */
52
+ showHistory(options) {
53
+ const historyManager = require('../utils/history-manager');
54
+
55
+ if (options.clear) {
56
+ // 清除命令历史记录
57
+ historyManager.clearHistory();
58
+ console.log('命令历史记录已清除');
59
+ return;
60
+ }
61
+
62
+ if (options.search) {
63
+ // 搜索命令历史记录
64
+ const searchResults = historyManager.searchHistory(options.search);
65
+ if (searchResults.length > 0) {
66
+ console.log('\n搜索结果:');
67
+ console.log('=====================================');
68
+ searchResults.forEach((item, index) => {
69
+ const date = new Date(item.timestamp).toLocaleString();
70
+ console.log(`${index + 1}. ${item.command} (${date})`);
71
+ });
72
+ console.log('=====================================');
73
+ console.log(`总计: ${searchResults.length} 条记录\n`);
74
+ } else {
75
+ console.log(`\n未找到匹配 "${options.search}" 的命令历史记录\n`);
76
+ }
77
+ return;
78
+ }
79
+
80
+ // 显示命令历史记录
81
+ const history = historyManager.getHistory(options.limit);
82
+ if (history.length > 0) {
83
+ console.log('\n命令历史记录:');
84
+ console.log('=====================================');
85
+ history.forEach((item, index) => {
86
+ const date = new Date(item.timestamp).toLocaleString();
87
+ console.log(`${index + 1}. ${item.command} (${date})`);
88
+ });
89
+ console.log('=====================================');
90
+ console.log(`总计: ${history.length} 条记录\n`);
91
+ } else {
92
+ console.log('\n命令历史记录为空\n');
93
+ }
94
+ }
95
+ }
96
+
97
+ module.exports = HistoryPlugin;