@yumerijs/core 1.1.1 → 1.1.3

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.
Files changed (3) hide show
  1. package/dist/core.d.ts +61 -44
  2. package/dist/core.js +296 -294
  3. package/package.json +1 -1
package/dist/core.d.ts CHANGED
@@ -18,6 +18,14 @@ interface PluginLoader {
18
18
  installPluginDependencies(pluginName: string): Promise<void>;
19
19
  logger: Logger;
20
20
  }
21
+ /**
22
+ * 定义插件状态枚举
23
+ */
24
+ export declare const enum PluginStatus {
25
+ ENABLED = "enabled",// 正常启用
26
+ DISABLED = "disabled",// 已禁用
27
+ PENDING = "pending"
28
+ }
21
29
  export declare class Core {
22
30
  plugins: {
23
31
  [name: string]: Plugin & {
@@ -45,116 +53,125 @@ export declare class Core {
45
53
  evttoplu: Record<string, Record<string, ((...args: any[]) => Promise<void>)[]>>;
46
54
  mdwtoplu: Record<string, string>;
47
55
  plftoplu: Record<string, string>;
48
- /**
49
- * 创建Core实例
50
- * @param pluginLoader 插件加载器
51
- */
56
+ pluginStatus: Record<string, PluginStatus>;
52
57
  constructor(pluginLoader: PluginLoader);
53
58
  /**
54
59
  * 加载配置文件
55
- * @param configPath 配置文件路径
60
+ * @param configPath 配置文件的路径
56
61
  */
57
62
  loadConfig(configPath: string): Promise<void>;
58
63
  /**
59
- * 监听配置文件变化
60
- * @param configPath 配置文件路径
64
+ * 监听配置文件变化,实现热重载
65
+ * @param configPath 配置文件的路径
61
66
  */
62
67
  private watchConfig;
63
68
  /**
64
- * 获取插件配置
69
+ * 获取指定插件的配置
65
70
  * @param pluginName 插件名称
66
- * @returns 配置
71
+ * @returns 插件的配置对象
67
72
  */
68
73
  getPluginConfig(pluginName: string): Promise<Config>;
69
74
  /**
70
- * 加载插件
71
- * @returns Promise<void>
75
+ * 加载所有在配置文件中启用的插件
72
76
  */
73
77
  loadPlugins(): Promise<void>;
74
78
  /**
75
- * 监听插件目录,实现热重载
76
- * @param core Core实例
77
- * @param pluginsDir 插件目录
79
+ * 导出的函数:加载单个插件
80
+ * @param pluginName 要加载的插件名
81
+ * @param triggerPendingCheck 是否在加载后触发对其他待定插件的检查,默认为 true
82
+ * @returns Promise<boolean> 是否加载成功
78
83
  */
79
- watchPlugins(core: Core, pluginsDir?: string): void;
84
+ loadSinglePlugin(pluginName: string, triggerPendingCheck?: boolean, onlypending?: boolean): Promise<boolean>;
85
+ /**
86
+ * 内部函数:尝试加载所有处于 PENDING 状态的插件
87
+ */
88
+ private _loadPendingPlugins;
89
+ /**
90
+ * 卸载插件,并递归卸载依赖于它的其他插件
91
+ * @param pluginNameToUnload 要卸载的插件名
92
+ */
93
+ unloadPlugin(pluginNameToUnload: string, ispending?: boolean): Promise<void>;
94
+ /**
95
+ * 内部函数:执行单个插件的卸载逻辑
96
+ * @param pluginName 插件名
97
+ */
98
+ private _unloadSinglePlugin;
80
99
  /**
81
100
  * 重新加载插件
82
101
  * @param pluginName 插件名称
83
- * @param core Core实例
84
102
  */
85
- reloadPlugin(pluginName: string, core: Core): Promise<void>;
103
+ reloadPlugin(pluginName: string): Promise<void>;
86
104
  /**
87
- * 卸载插件并触发事件
88
- * @param pluginName 插件名称
89
- * @param core Core实例
105
+ * 监听插件文件变化,实现热更新
106
+ * @param core Core 实例
107
+ * @param pluginsDir 插件目录,默认为 'plugins'
90
108
  */
91
- unloadPluginAndEmit(pluginName: string, core: Core): Promise<void>;
109
+ watchPlugins(core: Core, pluginsDir?: string): void;
92
110
  /**
93
111
  * 注册组件
94
112
  * @param name 组件名称
95
- * @param component 组件
96
- * @returns void
113
+ * @param component 组件实例
97
114
  */
98
115
  registerComponent(name: string, component: any): void;
99
116
  /**
100
117
  * 获取组件
101
118
  * @param name 组件名称
102
- * @returns 组件
119
+ * @returns 组件实例
103
120
  */
104
121
  getComponent(name: string): any;
105
122
  /**
106
- * 取消注册组件
123
+ * 注销组件
107
124
  * @param name 组件名称
108
125
  */
109
126
  unregisterComponent(name: string): void;
110
127
  /**
111
- * 监听事件
128
+ * 注册事件监听器
112
129
  * @param event 事件名称
113
- * @param listener 回调函数
130
+ * @param listener 事件回调函数
114
131
  */
115
132
  on(event: string, listener: (...args: any[]) => Promise<void>): void;
116
133
  /**
117
134
  * 触发事件
118
135
  * @param event 事件名称
119
- * @param args 参数
136
+ * @param args 传递给事件监听器的参数
120
137
  */
121
138
  emit(event: string, ...args: any[]): Promise<void>;
122
139
  /**
123
140
  * 注册全局中间件
124
141
  * @param name 中间件名称
125
- * @param middleware 中间件
126
- * @returns Core对象
142
+ * @param middleware 中间件函数
143
+ * @returns Core 实例
127
144
  */
128
145
  use(name: string, middleware: Middleware): Core;
129
146
  /**
130
- * 定义指令
131
- * @param name 指令名称
132
- * @returns 指令对象
147
+ * 注册命令
148
+ * @param name 命令名称
149
+ * @returns Command 实例
133
150
  */
134
151
  command(name: string): Command;
135
152
  /**
136
- * 执行指令
137
- * @param name 指令名称
138
- * @param session 会话
139
- * @param args 参数
140
- * @returns 回话/null
153
+ * 执行命令
154
+ * @param name 命令名称
155
+ * @param session 会话对象
156
+ * @param args 传递给命令处理函数的参数
157
+ * @returns 会话对象或 null
141
158
  */
142
159
  executeCommand(name: string, session: any, ...args: any[]): Promise<Session | null>;
143
160
  /**
144
161
  * 注册平台
145
- * @param platform 平台名称
146
- * @returns 启动结果
162
+ * @param platform 平台实例
163
+ * @returns 平台启动结果
147
164
  */
148
165
  registerPlatform(platform: Platform): any;
149
166
  /**
150
- * 取消注册插件
167
+ * 清理指定插件注册的所有内容
151
168
  * @param pluginname 插件名称
152
169
  */
153
170
  unregall(pluginname: string): void;
154
171
  /**
155
- * 获取指令对象
156
- * @param name 指令名称
157
- * @returns null/指令对象
172
+ * 获取命令
173
+ * @param name 命令名称
174
+ * @returns Command 实例或 null
158
175
  */
159
176
  getCommand(name: string): Command | null;
160
177
  }
package/dist/core.js CHANGED
@@ -38,8 +38,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.Core = void 0;
40
40
  /**
41
- * @time: 2025/04/20 11:45
42
- * @author: FireGuo
41
+ * @time: 2025/07/12 18:14
42
+ * @author: FireGuo & Manus
43
43
  * WindyPear-Team All right reserved
44
44
  **/
45
45
  const yaml = __importStar(require("js-yaml"));
@@ -61,43 +61,39 @@ class Core {
61
61
  logger = new logger_1.Logger('core');
62
62
  providedComponents = {};
63
63
  pluginModules = {};
64
- configPath = ''; // 存储配置文件路径
65
- globalMiddlewares = {}; // 全局中间件数组
66
- cmdtoplu = {}; // 存储命令与插件名的映射关系
67
- comtoplu = {}; // 存储组件与插件名的映射关系
68
- evttoplu = {}; // 存储事件与插件名的映射关系
69
- mdwtoplu = {}; // 存储中间件与插件名的映射关系
70
- plftoplu = {}; // 存储平台与插件名的映射关系
71
- /**
72
- * 创建Core实例
73
- * @param pluginLoader 插件加载器
74
- */
64
+ configPath = '';
65
+ globalMiddlewares = {};
66
+ cmdtoplu = {};
67
+ comtoplu = {};
68
+ evttoplu = {};
69
+ mdwtoplu = {};
70
+ plftoplu = {};
71
+ pluginStatus = {};
75
72
  constructor(pluginLoader) {
76
73
  this.pluginLoader = pluginLoader;
77
74
  }
78
75
  /**
79
76
  * 加载配置文件
80
- * @param configPath 配置文件路径
77
+ * @param configPath 配置文件的路径
81
78
  */
82
79
  async loadConfig(configPath) {
83
80
  try {
84
- this.configPath = configPath; // 保存配置文件路径
81
+ this.configPath = configPath;
85
82
  const doc = yaml.load(fs.readFileSync(configPath, 'utf8'));
86
83
  this.config = doc;
87
84
  this.logger.info('Config loaded.');
88
- // 开发环境下监听配置文件变化
89
85
  if (process.env.NODE_ENV === 'development') {
90
86
  this.watchConfig(configPath);
91
87
  }
92
88
  }
93
89
  catch (e) {
94
90
  this.logger.error('Failed to load config:', e);
95
- throw e; // 抛出异常,让上层处理
91
+ throw e;
96
92
  }
97
93
  }
98
94
  /**
99
- * 监听配置文件变化
100
- * @param configPath 配置文件路径
95
+ * 监听配置文件变化,实现热重载
96
+ * @param configPath 配置文件的路径
101
97
  */
102
98
  watchConfig(configPath) {
103
99
  const watcher = chokidar_1.default.watch(configPath, {
@@ -109,30 +105,26 @@ class Core {
109
105
  },
110
106
  });
111
107
  watcher.on('change', async () => {
112
- //this.logger.info('Config file changed, reloading...');
113
108
  try {
114
- // 重新加载配置文件
115
109
  const doc = yaml.load(fs.readFileSync(configPath, 'utf8'));
116
- this.config = doc;
117
- // this.logger.info('Config reloaded successfully.');
118
- // 触发配置变更事件
110
+ if (doc && doc.plugins) {
111
+ this.config.plugins = doc.plugins;
112
+ }
119
113
  await this.emit('config-changed', this.config);
120
114
  }
121
115
  catch (error) {
122
- // this.logger.error('Failed to reload config:', error);
116
+ this.logger.error('Failed to reload config:', error);
123
117
  }
124
118
  });
125
119
  this.logger.info(`Watching for config changes at ${configPath}`);
126
120
  }
127
121
  /**
128
- * 获取插件配置
122
+ * 获取指定插件的配置
129
123
  * @param pluginName 插件名称
130
- * @returns 配置
124
+ * @returns 插件的配置对象
131
125
  */
132
126
  async getPluginConfig(pluginName) {
133
- // 如果插件名以~开头,则去掉~前缀获取配置
134
127
  const actualPluginName = pluginName.startsWith('~') ? pluginName.substring(1) : pluginName;
135
- // 如果配置文件路径存在且处于开发模式,每次都重新读取配置文件
136
128
  if (this.configPath && process.env.NODE_ENV === 'development') {
137
129
  try {
138
130
  const doc = yaml.load(fs.readFileSync(this.configPath, 'utf8'));
@@ -147,97 +139,217 @@ class Core {
147
139
  if (!this.config.plugins[actualPluginName]) {
148
140
  return new config_1.Config(actualPluginName);
149
141
  }
150
- const config = new config_1.Config(actualPluginName, this.config.plugins[actualPluginName]);
151
- return config;
142
+ return new config_1.Config(actualPluginName, this.config.plugins[actualPluginName]);
152
143
  }
153
144
  /**
154
- * 加载插件
155
- * @returns Promise<void>
145
+ * 加载所有在配置文件中启用的插件
156
146
  */
157
147
  async loadPlugins() {
158
148
  if (!this.config || typeof this.config.plugins !== 'object' || this.config.plugins === null) {
159
- this.logger.info('No plugins configuration found or it is not an object. No plugins to load.');
149
+ this.logger.info('No plugins configuration found. No plugins to load.');
160
150
  return;
161
151
  }
162
- const pluginNamesToLoad = Object.keys(this.config.plugins).filter(name => !name.startsWith('~'));
163
- if (pluginNamesToLoad.length === 0) {
164
- this.logger.info('No enabled plugins found in configuration. All plugins might be disabled or configuration is empty.');
165
- return;
152
+ const allPluginNames = Object.keys(this.config.plugins);
153
+ this.pluginStatus = {}; // 重置状态
154
+ // 初始化所有插件的状态
155
+ for (const name of allPluginNames) {
156
+ if (name.startsWith('~')) {
157
+ const actualName = name.substring(1);
158
+ this.pluginStatus[actualName] = "disabled" /* PluginStatus.DISABLED */;
159
+ }
160
+ else {
161
+ this.pluginStatus[name] = "pending" /* PluginStatus.PENDING */; // 默认为等待加载
162
+ }
166
163
  }
167
- const disabledPlugins = Object.keys(this.config.plugins).filter(name => name.startsWith('~'));
168
- if (disabledPlugins.length > 0) {
169
- this.logger.info(`Skipping disabled plugins: ${disabledPlugins.join(', ')}`);
164
+ const enabledPlugins = allPluginNames.filter(name => !name.startsWith('~'));
165
+ if (enabledPlugins.length === 0) {
166
+ this.logger.info('No enabled plugins found in configuration.');
167
+ return;
170
168
  }
171
- const loadedPluginNames = [];
172
- let loadAttempted = {};
173
- let remainingPluginNames = [...pluginNamesToLoad];
174
- while (remainingPluginNames.length > 0) {
175
- let loadedInThisPass = false;
176
- const nextRemainingPluginNames = [];
177
- for (const pluginName of remainingPluginNames) {
178
- if (loadedPluginNames.includes(pluginName)) {
169
+ // this.logger.info(`Disabled plugins: ${Object.keys(this.pluginStatus).filter(p => this.pluginStatus[p] === PluginStatus.DISABLED).join(', ') || 'None'}`);
170
+ // 循环加载,直到没有插件可以被加载
171
+ let loadedInLastPass = true;
172
+ while (loadedInLastPass) {
173
+ loadedInLastPass = false;
174
+ for (const pluginName of enabledPlugins) {
175
+ // 如果插件已经是启用状态,则跳过
176
+ if (this.pluginStatus[pluginName] === "enabled" /* PluginStatus.ENABLED */) {
179
177
  continue;
180
178
  }
181
- try {
182
- // this.logger.info(`Attempting to load plugin: ${pluginName}`);
183
- const pluginInstance = await this.pluginLoader.load(pluginName);
184
- if (pluginInstance) {
185
- const depend = pluginInstance.depend;
186
- const provide = pluginInstance.provide;
187
- // 用 providedComponents 作为依赖判断来源(比 components 更准确)
188
- const unmetDependencies = depend?.filter(dep => !this.providedComponents.hasOwnProperty(dep)) || [];
189
- if (unmetDependencies.length === 0) {
190
- this.plugins[pluginName] = Object.assign(pluginInstance, { depend, provide });
191
- loadedPluginNames.push(pluginName);
192
- loadedInThisPass = true;
193
- if (pluginInstance.apply) {
194
- await pluginInstance.apply(new context_1.Context(this, pluginName), await this.getPluginConfig(pluginName));
195
- this.pluginLoader.logger.info(`apply plugin ${pluginName}`);
196
- }
197
- if (provide) {
198
- for (const componentName of provide) {
199
- if (this.providedComponents.hasOwnProperty(componentName)) {
200
- this.logger.error(`Multiple plugins provide the component "${componentName}". Provided by "${this.providedComponents[componentName]}" and "${pluginName}". Only the first loaded will be active.`);
201
- }
202
- else {
203
- this.providedComponents[componentName] = pluginName;
204
- }
205
- }
206
- }
207
- }
208
- else {
209
- nextRemainingPluginNames.push(pluginName);
210
- // 不加入 loadAttempted,让下一轮重试
211
- }
179
+ // 尝试加载单个插件
180
+ const success = await this.loadSinglePlugin(pluginName, false); // 内部加载,不触发后续的pending检查
181
+ if (success) {
182
+ loadedInLastPass = true;
183
+ }
184
+ }
185
+ }
186
+ // 加载完所有能加载的插件后,统一检查并加载待定插件
187
+ await this._loadPendingPlugins();
188
+ const pendingPlugins = Object.keys(this.pluginStatus).filter(p => this.pluginStatus[p] === "pending" /* PluginStatus.PENDING */);
189
+ if (pendingPlugins.length > 0) {
190
+ // this.logger.warn('Some plugins could not be loaded due to unresolved dependencies:', pendingPlugins);
191
+ }
192
+ if (process.env.NODE_ENV === 'development') {
193
+ this.watchPlugins(this);
194
+ }
195
+ }
196
+ /**
197
+ * 导出的函数:加载单个插件
198
+ * @param pluginName 要加载的插件名
199
+ * @param triggerPendingCheck 是否在加载后触发对其他待定插件的检查,默认为 true
200
+ * @returns Promise<boolean> 是否加载成功
201
+ */
202
+ async loadSinglePlugin(pluginName, triggerPendingCheck = true, onlypending = false) {
203
+ // 如果插件不存在于状态记录中(例如,动态添加),则设为 PENDING
204
+ if (!this.pluginStatus[pluginName]) {
205
+ this.pluginStatus[pluginName] = "pending" /* PluginStatus.PENDING */;
206
+ }
207
+ // 只有 PENDING 状态的插件才能被加载
208
+ if (this.pluginStatus[pluginName] !== "pending" /* PluginStatus.PENDING */ && onlypending) {
209
+ // this.logger.info(`Plugin "${pluginName}" is not in a pending state (current: ${this.pluginStatus[pluginName]}). Skipping load.`);
210
+ return false;
211
+ }
212
+ try {
213
+ const pluginInstance = await this.pluginLoader.load(pluginName);
214
+ if (!pluginInstance) {
215
+ throw new Error('Plugin loader returned no instance.');
216
+ }
217
+ const deps = pluginInstance.depend || [];
218
+ const unmetDependencies = deps.filter(dep => !this.providedComponents[dep]);
219
+ if (unmetDependencies.length > 0) {
220
+ // this.logger.info(`Plugin "${pluginName}" has unmet dependencies: [${unmetDependencies.join(', ')}]. Will retry later.`);
221
+ return false;
222
+ }
223
+ // 依赖满足,开始加载
224
+ this.plugins[pluginName] = pluginInstance;
225
+ if (pluginInstance.apply) {
226
+ this.pluginLoader.logger.info(`Apply plugin "${pluginName}"`);
227
+ await pluginInstance.apply(new context_1.Context(this, pluginName), await this.getPluginConfig(pluginName));
228
+ }
229
+ // 注册提供的组件
230
+ if (pluginInstance.provide) {
231
+ for (const componentName of pluginInstance.provide) {
232
+ if (this.providedComponents[componentName]) {
233
+ this.logger.error(`Component "${componentName}" is provided by multiple plugins: "${this.providedComponents[componentName]}" and "${pluginName}".`);
234
+ }
235
+ else {
236
+ this.providedComponents[componentName] = pluginName;
212
237
  }
213
238
  }
214
- catch (err) {
215
- this.pluginLoader.logger.error(`Failed to load or process plugin ${pluginName}:`, err);
216
- loadAttempted[pluginName] = true; // 真正失败才标记
239
+ }
240
+ this.pluginStatus[pluginName] = "enabled" /* PluginStatus.ENABLED */; // 更新状态为已启用
241
+ // 加载成功后,检查是否可以加载其他等待中的插件
242
+ if (triggerPendingCheck) {
243
+ await this._loadPendingPlugins();
244
+ }
245
+ return true;
246
+ }
247
+ catch (err) {
248
+ this.pluginLoader.logger.error(`Failed to load plugin "${pluginName}":`, err);
249
+ // 加载失败的插件保持 PENDING 状态,以便后续重试
250
+ return false;
251
+ }
252
+ }
253
+ /**
254
+ * 内部函数:尝试加载所有处于 PENDING 状态的插件
255
+ */
256
+ async _loadPendingPlugins() {
257
+ const pendingPlugins = Object.keys(this.pluginStatus).filter(p => this.pluginStatus[p] === "pending" /* PluginStatus.PENDING */);
258
+ if (pendingPlugins.length === 0)
259
+ return;
260
+ // this.logger.info('Checking pending plugins...');
261
+ for (const pluginName of pendingPlugins) {
262
+ await this.loadSinglePlugin(pluginName, false); // 递归调用,但不触发顶层的pending检查
263
+ }
264
+ }
265
+ /**
266
+ * 卸载插件,并递归卸载依赖于它的其他插件
267
+ * @param pluginNameToUnload 要卸载的插件名
268
+ */
269
+ async unloadPlugin(pluginNameToUnload, ispending = false) {
270
+ // 1. 找出所有直接或间接依赖于此插件的插件
271
+ const dependents = [];
272
+ const pluginsToCheck = [pluginNameToUnload];
273
+ while (pluginsToCheck.length > 0) {
274
+ const currentPluginName = pluginsToCheck.shift();
275
+ const provided = this.plugins[currentPluginName]?.provide || [];
276
+ if (provided.length === 0)
277
+ continue;
278
+ for (const pluginName in this.plugins) {
279
+ if (dependents.includes(pluginName) || pluginName === pluginNameToUnload)
280
+ continue;
281
+ const deps = this.plugins[pluginName].depend || [];
282
+ if (provided.some(p => deps.includes(p))) {
283
+ if (!dependents.includes(pluginName)) {
284
+ dependents.push(pluginName);
285
+ pluginsToCheck.push(pluginName); // 递归查找
286
+ // this.logger.info(`Plugin "${pluginName}" depends on "${currentPluginName}" and will be unloaded.`);
287
+ }
217
288
  }
218
289
  }
219
- if (!loadedInThisPass && nextRemainingPluginNames.length === remainingPluginNames.length && remainingPluginNames.length > 0) {
220
- this.logger.error('Detected circular or unresolvable plugin dependencies. Remaining plugins:', nextRemainingPluginNames);
221
- break;
290
+ }
291
+ // 2. 卸载所有依赖者
292
+ for (const dependentName of dependents) {
293
+ await this.unloadPlugin(dependentName, true);
294
+ }
295
+ // 3. 最后卸载目标插件
296
+ await this._unloadSinglePlugin(pluginNameToUnload, ispending);
297
+ // 4. 卸载完成后,尝试重新加载处于 PENDING 状态的插件
298
+ // await this._loadPendingPlugins();
299
+ }
300
+ /**
301
+ * 内部函数:执行单个插件的卸载逻辑
302
+ * @param pluginName 插件名
303
+ */
304
+ async _unloadSinglePlugin(pluginName, ispending = false) {
305
+ if (this.pluginStatus[pluginName] !== "enabled" /* PluginStatus.ENABLED */) {
306
+ return; // 只卸载已启用的插件
307
+ }
308
+ this.logger.info(`Unloading plugin "${pluginName}"...`);
309
+ try {
310
+ const plugin = this.plugins[pluginName];
311
+ if (plugin && plugin.disable) {
312
+ await plugin.disable(new context_1.Context(this, pluginName));
222
313
  }
223
- remainingPluginNames = nextRemainingPluginNames;
314
+ await this.pluginLoader.unloadPlugin(pluginName);
315
+ this.unregall(pluginName); // 清理该插件注册的所有内容
316
+ delete this.plugins[pluginName];
317
+ delete this.pluginModules[pluginName];
318
+ this.pluginStatus[pluginName] = ispending ? "pending" /* PluginStatus.PENDING */ : "disabled" /* PluginStatus.DISABLED */;
319
+ this.emit('plugin-unloaded', pluginName);
224
320
  }
225
- if (remainingPluginNames.length > 0) {
226
- this.logger.warn('Some plugins could not be fully loaded due to unresolved dependencies or errors:', remainingPluginNames);
321
+ catch (error) {
322
+ this.logger.error(`Failed to unload plugin "${pluginName}":`, error);
227
323
  }
228
- if (process.env.NODE_ENV === 'development') {
229
- this.watchPlugins(this);
324
+ }
325
+ /**
326
+ * 重新加载插件
327
+ * @param pluginName 插件名称
328
+ */
329
+ async reloadPlugin(pluginName) {
330
+ // this.logger.info(`Reloading plugin "${pluginName}"...`);
331
+ // 1. 递归卸载插件及其依赖者
332
+ await this.unloadPlugin(pluginName);
333
+ // 2. 重新加载该插件
334
+ // unloadPlugin 已经将状态设置为 PENDING,所以 loadSinglePlugin 可以直接工作
335
+ const success = await this.loadSinglePlugin(pluginName);
336
+ if (success) {
337
+ // this.logger.info(`Plugin "${pluginName}" reloaded successfully.`);
338
+ this.emit('plugin-reloaded', pluginName);
339
+ }
340
+ else {
341
+ this.logger.error(`Failed to reload plugin "${pluginName}". It may have unmet dependencies.`);
230
342
  }
231
343
  }
232
344
  /**
233
- * 监听插件目录,实现热重载
234
- * @param core Core实例
235
- * @param pluginsDir 插件目录
345
+ * 监听插件文件变化,实现热更新
346
+ * @param core Core 实例
347
+ * @param pluginsDir 插件目录,默认为 'plugins'
236
348
  */
237
349
  watchPlugins(core, pluginsDir = 'plugins') {
238
350
  const logger = new logger_1.Logger('hmr');
239
351
  const watcher = chokidar_1.default.watch(pluginsDir, {
240
- ignored: /(^|[/\\])\../, // 忽略点文件
352
+ ignored: /(^|[/\\])\../,
241
353
  persistent: true,
242
354
  ignoreInitial: true,
243
355
  awaitWriteFinish: {
@@ -245,167 +357,84 @@ class Core {
245
357
  pollInterval: 100,
246
358
  },
247
359
  });
248
- watcher.on('change', async (changePath) => {
360
+ const getPluginNameFromPath = (changePath) => {
249
361
  if (!changePath.endsWith('.ts') && !changePath.endsWith('.js'))
250
- return;
362
+ return null;
251
363
  const parts = changePath.split(path.sep);
252
- if (parts.length < 2)
253
- return;
254
- const pluginName = parts[1];
255
- this.logger.info(`Plugin file changed: ${changePath}`);
256
- this.reloadPlugin(pluginName, core);
364
+ return parts.length > 1 ? parts[1] : null;
365
+ };
366
+ watcher.on('change', async (changePath) => {
367
+ const pluginName = getPluginNameFromPath(changePath);
368
+ if (pluginName) {
369
+ logger.info(`Plugin file changed: ${changePath}`);
370
+ await core.reloadPlugin(pluginName);
371
+ }
257
372
  });
258
373
  watcher.on('add', async (changePath) => {
259
- if (!changePath.endsWith('.ts') && !changePath.endsWith('.js'))
260
- return;
261
- const parts = changePath.split(path.sep);
262
- if (parts.length < 2)
263
- return;
264
- const pluginName = parts[1];
265
- this.logger.info(`New plugin file added: ${changePath}`);
266
- this.reloadPlugin(pluginName, core);
374
+ const pluginName = getPluginNameFromPath(changePath);
375
+ if (pluginName) {
376
+ logger.info(`New plugin file added: ${changePath}`);
377
+ await core.reloadPlugin(pluginName);
378
+ }
267
379
  });
268
380
  watcher.on('unlink', async (changePath) => {
269
- if (!changePath.endsWith('.ts') && !changePath.endsWith('.js'))
270
- return;
271
- const parts = changePath.split(path.sep);
272
- if (parts.length < 2)
273
- return;
274
- const pluginName = parts[1];
275
- this.logger.info(`Plugin file removed: ${changePath}`);
276
- await this.unloadPluginAndEmit(pluginName, core);
381
+ const pluginName = getPluginNameFromPath(changePath);
382
+ if (pluginName) {
383
+ logger.info(`Plugin file removed: ${changePath}`);
384
+ await core.unloadPlugin(pluginName);
385
+ }
277
386
  });
278
387
  logger.info(`Watching for plugin changes in ${pluginsDir}`);
279
388
  }
280
- /**
281
- * 重新加载插件
282
- * @param pluginName 插件名称
283
- * @param core Core实例
284
- */
285
- async reloadPlugin(pluginName, core) {
286
- try {
287
- // 强制重新读取配置文件,确保获取最新配置
288
- if (this.configPath && process.env.NODE_ENV === 'development') {
289
- try {
290
- const doc = yaml.load(fs.readFileSync(this.configPath, 'utf8'));
291
- if (doc && doc.plugins) {
292
- this.config.plugins = doc.plugins;
293
- }
294
- }
295
- catch (error) {
296
- this.logger.warn('Failed to refresh config for plugin reload:', error);
297
- }
298
- }
299
- // 获取最新的插件配置
300
- const config = await core.getPluginConfig(pluginName);
301
- // 加载插件实例
302
- const plugin = await core.pluginLoader.load(pluginName);
303
- // 如果插件存在,先禁用旧实例
304
- if (plugin && plugin.disable) {
305
- await plugin.disable(new context_1.Context(this, pluginName));
306
- }
307
- // 清理插件提供的组件
308
- // if (plugin.provide) {
309
- // for (let providedmodules of plugin.provide) {
310
- // if (this.components[`${providedmodules}`]) {
311
- // this.unregisterComponent(providedmodules);
312
- // }
313
- // }
314
- // }
315
- // 卸载插件
316
- await core.unloadPluginAndEmit(pluginName, core);
317
- // 应用新的插件实例
318
- if (plugin && plugin.apply) {
319
- await plugin.apply(new context_1.Context(this, pluginName), config);
320
- }
321
- this.pluginLoader.logger.info(`apply plugin ${pluginName}`);
322
- // 触发插件重载事件
323
- core.emit('plugin-reloaded', pluginName);
324
- }
325
- catch (error) {
326
- this.logger.error(`Failed to reload plugin ${pluginName}:`, error);
327
- }
328
- }
329
- /**
330
- * 卸载插件并触发事件
331
- * @param pluginName 插件名称
332
- * @param core Core实例
333
- */
334
- async unloadPluginAndEmit(pluginName, core) {
335
- try {
336
- const plugin = core.plugins[`${pluginName}`];
337
- if (plugin && plugin.disable) {
338
- await plugin.disable(new context_1.Context(this, pluginName));
339
- }
340
- await core.pluginLoader.unloadPlugin(pluginName);
341
- delete core.plugins[`${pluginName}`];
342
- delete this.pluginModules[`${pluginName}`];
343
- // Remove provided components from this plugin
344
- // for (const componentName in this.providedComponents) {
345
- // if (this.providedComponents[`${componentName}`] === pluginName) {
346
- // delete this.components[`${componentName}`];
347
- // delete this.providedComponents[`${componentName}`];
348
- // }
349
- // }
350
- this.unregall(pluginName);
351
- core.emit('plugin-unloaded', pluginName);
352
- }
353
- catch (error) {
354
- this.logger.error(`Failed to unload plugin ${pluginName}:`, error);
355
- }
356
- }
357
389
  /**
358
390
  * 注册组件
359
391
  * @param name 组件名称
360
- * @param component 组件
361
- * @returns void
392
+ * @param component 组件实例
362
393
  */
363
394
  registerComponent(name, component) {
364
395
  if (this.components.hasOwnProperty(name)) {
365
- this.logger.warn(`Component "${name}" already registered by plugin "${this.providedComponents[`${name}`]}".`);
396
+ this.logger.warn(`Component "${name}" already registered by plugin "${this.providedComponents[name]}".`);
366
397
  return;
367
398
  }
368
- this.components[`${name}`] = component;
369
- this.logger.info(`Component "${name}" registered.`);
399
+ this.components[name] = component;
400
+ // this.logger.info(`Component "${name}" registered.`);
370
401
  }
371
402
  /**
372
403
  * 获取组件
373
404
  * @param name 组件名称
374
- * @returns 组件
405
+ * @returns 组件实例
375
406
  */
376
407
  getComponent(name) {
377
- return this.components[`${name}`];
408
+ return this.components[name];
378
409
  }
379
410
  /**
380
- * 取消注册组件
411
+ * 注销组件
381
412
  * @param name 组件名称
382
413
  */
383
414
  unregisterComponent(name) {
384
- delete this.components[`${name}`];
385
- delete this.providedComponents[`${name}`];
386
- delete this.comtoplu[`${name}`];
415
+ delete this.components[name];
416
+ delete this.providedComponents[name];
417
+ delete this.comtoplu[name];
387
418
  }
388
419
  /**
389
- * 监听事件
420
+ * 注册事件监听器
390
421
  * @param event 事件名称
391
- * @param listener 回调函数
422
+ * @param listener 事件回调函数
392
423
  */
393
424
  on(event, listener) {
394
- if (!this.eventListeners[`${event}`]) {
395
- this.eventListeners[`${event}`] = [];
425
+ if (!this.eventListeners[event]) {
426
+ this.eventListeners[event] = [];
396
427
  }
397
- this.eventListeners[`${event}`].push(listener);
398
- //this.logger.info(`Listener added for event "${event}".`);
428
+ this.eventListeners[event].push(listener);
399
429
  }
400
430
  /**
401
431
  * 触发事件
402
432
  * @param event 事件名称
403
- * @param args 参数
433
+ * @param args 传递给事件监听器的参数
404
434
  */
405
435
  async emit(event, ...args) {
406
- if (this.eventListeners[`${event}`]) {
407
- // this.logger.info(`Emitting event "${event}" with args:`, args);
408
- for (const listener of this.eventListeners[`${event}`]) {
436
+ if (this.eventListeners[event]) {
437
+ for (const listener of this.eventListeners[event]) {
409
438
  try {
410
439
  await listener(...args);
411
440
  }
@@ -414,136 +443,109 @@ class Core {
414
443
  }
415
444
  }
416
445
  }
417
- else {
418
- //this.logger.info(`No listeners for event "${event}".`);
419
- }
420
446
  }
421
447
  /**
422
448
  * 注册全局中间件
423
449
  * @param name 中间件名称
424
- * @param middleware 中间件
425
- * @returns Core对象
450
+ * @param middleware 中间件函数
451
+ * @returns Core 实例
426
452
  */
427
453
  use(name, middleware) {
428
454
  this.globalMiddlewares[name] = middleware;
429
455
  return this;
430
456
  }
431
457
  /**
432
- * 定义指令
433
- * @param name 指令名称
434
- * @returns 指令对象
458
+ * 注册命令
459
+ * @param name 命令名称
460
+ * @returns Command 实例
435
461
  */
436
462
  command(name) {
437
- const command = new command_1.Command(this, name); // 传递 Core 实例
438
- this.commands[`${name}`] = command;
463
+ const command = new command_1.Command(this, name);
464
+ this.commands[name] = command;
439
465
  return command;
440
466
  }
441
467
  /**
442
- * 执行指令
443
- * @param name 指令名称
444
- * @param session 会话
445
- * @param args 参数
446
- * @returns 回话/null
468
+ * 执行命令
469
+ * @param name 命令名称
470
+ * @param session 会话对象
471
+ * @param args 传递给命令处理函数的参数
472
+ * @returns 会话对象或 null
447
473
  */
448
474
  async executeCommand(name, session, ...args) {
449
- const command = this.commands[`${name}`];
475
+ const command = this.commands[name];
450
476
  if (command) {
451
- // 将 globalMiddlewares 从对象转为数组
452
477
  const globalMiddlewareList = Object.values(this.globalMiddlewares || {});
453
478
  const commandMiddlewareList = command.middlewares || [];
454
- // 如果有中间件,执行中间件链
455
- if (globalMiddlewareList.length > 0 || commandMiddlewareList.length > 0) {
456
- const middlewares = [...globalMiddlewareList, ...commandMiddlewareList];
457
- let index = 0;
458
- const runner = async () => {
459
- if (index >= middlewares.length) {
460
- await command.executeHandler(session, ...args);
461
- return;
462
- }
479
+ const middlewares = [...globalMiddlewareList, ...commandMiddlewareList];
480
+ let index = 0;
481
+ const runner = async () => {
482
+ if (index < middlewares.length) {
463
483
  const middleware = middlewares[index++];
464
484
  await middleware(session, runner);
465
- };
466
- await runner();
467
- return session;
468
- }
469
- else {
470
- return await command.execute(session, ...args);
471
- }
485
+ }
486
+ else {
487
+ await command.executeHandler(session, ...args);
488
+ }
489
+ };
490
+ await runner();
491
+ return session;
472
492
  }
473
493
  return null;
474
494
  }
475
495
  /**
476
496
  * 注册平台
477
- * @param platform 平台名称
478
- * @returns 启动结果
497
+ * @param platform 平台实例
498
+ * @returns 平台启动结果
479
499
  */
480
500
  registerPlatform(platform) {
481
501
  this.platforms.push(platform);
482
502
  return platform.startPlatform(this);
483
503
  }
484
504
  /**
485
- * 取消注册插件
505
+ * 清理指定插件注册的所有内容
486
506
  * @param pluginname 插件名称
487
507
  */
488
508
  unregall(pluginname) {
489
- let cmdtodel = [];
490
- for (const cmd in this.cmdtoplu) {
491
- if (this.cmdtoplu[cmd] === pluginname) {
492
- cmdtodel.push(cmd);
493
- }
494
- }
495
- for (const cmd of cmdtodel) {
509
+ // 清理 commands
510
+ Object.keys(this.cmdtoplu)
511
+ .filter(cmd => this.cmdtoplu[cmd] === pluginname)
512
+ .forEach(cmd => {
496
513
  delete this.cmdtoplu[cmd];
497
514
  delete this.commands[cmd];
498
- }
499
- let comptodel = [];
500
- for (const comp in this.comtoplu) {
501
- if (this.comtoplu[comp] === pluginname) {
502
- comptodel.push(comp);
503
- }
504
- }
505
- for (const comp of comptodel) {
515
+ });
516
+ // 清理 components 和 providedComponents
517
+ Object.keys(this.comtoplu)
518
+ .filter(comp => this.comtoplu[comp] === pluginname)
519
+ .forEach(comp => {
506
520
  delete this.comtoplu[comp];
507
521
  delete this.components[comp];
508
522
  delete this.providedComponents[comp];
509
- }
510
- let evttodel = [];
523
+ });
524
+ // 清理 event listeners
511
525
  for (const evt in this.evttoplu) {
512
526
  if (this.evttoplu[evt][pluginname]) {
513
- // 清除插件在 evttoplu 中的记录
514
- delete this.evttoplu[evt][pluginname];
515
- // 如果该事件的插件已经全部清除了,也可以选择删掉整个事件
516
- if (Object.keys(this.evttoplu[evt]).length === 0) {
517
- evttodel.push(evt);
518
- }
519
- // 同时在 this.eventListeners 中删掉对应 listener
520
527
  const pluginListeners = this.evttoplu[evt][pluginname] || [];
521
- this.eventListeners[evt] = this.eventListeners[evt]?.filter(l => !pluginListeners.includes(l)) || [];
522
- }
523
- }
524
- let mdwtodel = [];
525
- for (const mdw in this.mdwtoplu) {
526
- if (this.mdwtoplu[mdw] === pluginname) {
527
- mdwtodel.push(mdw);
528
+ if (this.eventListeners[evt]) {
529
+ this.eventListeners[evt] = this.eventListeners[evt].filter(l => !pluginListeners.includes(l));
530
+ }
531
+ delete this.evttoplu[evt][pluginname];
528
532
  }
529
533
  }
530
- for (const mdw of mdwtodel) {
534
+ // 清理 middlewares
535
+ Object.keys(this.mdwtoplu)
536
+ .filter(mdw => this.mdwtoplu[mdw] === pluginname)
537
+ .forEach(mdw => {
531
538
  delete this.mdwtoplu[mdw];
532
539
  delete this.globalMiddlewares[mdw];
533
- }
540
+ });
534
541
  }
535
542
  /**
536
- * 获取指令对象
537
- * @param name 指令名称
538
- * @returns null/指令对象
543
+ * 获取命令
544
+ * @param name 命令名称
545
+ * @returns Command 实例或 null
539
546
  */
540
547
  getCommand(name) {
541
- if (Object.hasOwn(this.commands, name)) {
542
- return this.commands[name];
543
- }
544
- else {
545
- return null;
546
- }
548
+ return this.commands[name] || null;
547
549
  }
548
550
  }
549
551
  exports.Core = Core;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yumerijs/core",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Core module for yumeri",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",