@yumerijs/core 1.1.0 → 1.1.2

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 +298 -324
  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): Promise<boolean>;
85
+ /**
86
+ * 内部函数:尝试加载所有处于 PENDING 状态的插件
87
+ */
88
+ private _loadPendingPlugins;
89
+ /**
90
+ * 卸载插件,并递归卸载依赖于它的其他插件
91
+ * @param pluginNameToUnload 要卸载的插件名
92
+ */
93
+ unloadPlugin(pluginNameToUnload: string): 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,129 +139,221 @@ 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
- // 检查 plugins 配置是否存在且是对象类型
159
148
  if (!this.config || typeof this.config.plugins !== 'object' || this.config.plugins === null) {
160
- 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.');
161
150
  return;
162
151
  }
163
- // 获取所有需要加载的插件名,过滤掉以~开头的插件(禁用的插件)
164
- const pluginNamesToLoad = Object.keys(this.config.plugins).filter(name => !name.startsWith('~'));
165
- if (pluginNamesToLoad.length === 0) {
166
- this.logger.info('No enabled plugins found in configuration. All plugins might be disabled or configuration is empty.');
167
- 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
+ }
168
163
  }
169
- // 记录被禁用的插件
170
- const disabledPlugins = Object.keys(this.config.plugins).filter(name => name.startsWith('~'));
171
- if (disabledPlugins.length > 0) {
172
- 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;
173
168
  }
174
- const loadedPluginNames = [];
175
- // 使用对象来跟踪尝试加载的插件名
176
- let loadAttempted = {};
177
- // 使用插件名数组来管理剩余待加载的插件
178
- let remainingPluginNames = [...pluginNamesToLoad];
179
- // 不加载名称以~开头的插件
180
- remainingPluginNames = remainingPluginNames.filter(name => !name.startsWith('~'));
181
- // 多次尝试加载插件,直到所有插件加载完毕或检测到循环依赖
182
- while (remainingPluginNames.length > 0) {
183
- let loadedInThisPass = false;
184
- // 用于存储下一轮尝试加载的插件名
185
- const nextRemainingPluginNames = [];
186
- // 遍历当前剩余待加载的插件名
187
- for (const pluginName of remainingPluginNames) {
188
- // 如果插件已经加载或已尝试加载,则跳过
189
- if (loadedPluginNames.includes(pluginName) || loadAttempted[`${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 */) {
190
177
  continue;
191
178
  }
192
- // 标记该插件已尝试加载
193
- loadAttempted[`${pluginName}`] = true;
194
- try {
195
- this.logger.info(`Attempting to load plugin: ${pluginName}`);
196
- // 使用插件名加载插件实例和模块
197
- const pluginInstance = await this.pluginLoader.load(pluginName);
198
- //const pluginModule = await this.importPluginModule(pluginName);
199
- if (pluginInstance) {
200
- const depend = pluginInstance.depend;
201
- const provide = pluginInstance.provide;
202
- // 检查依赖是否满足 (此处仍然检查 this.components,请确保 this.components 在此阶段已包含必要的基准组件)
203
- const unmetDependencies = depend?.filter(dep => !this.components.hasOwnProperty(dep)) || [];
204
- if (unmetDependencies.length === 0) {
205
- // 将加载成功并满足依赖的插件存储起来
206
- this.plugins[`${pluginName}`] = Object.assign(pluginInstance, { depend, provide });
207
- //this.pluginModules[`${pluginName}`] = pluginModule;
208
- //this.logger.info(`Plugin ${pluginName} loaded.`);
209
- loadedPluginNames.push(pluginName);
210
- loadedInThisPass = true; // 标记本轮有插件加载成功
211
- if (loadedInThisPass && pluginInstance && pluginInstance.apply) {
212
- await pluginInstance.apply(new context_1.Context(this, pluginName), await this.getPluginConfig(pluginName));
213
- // 使用 pluginName
214
- this.pluginLoader.logger.info(`apply plugin ${pluginName}`);
215
- }
216
- // 记录该插件提供了哪些组件
217
- if (provide) {
218
- for (const componentName of provide) {
219
- if (this.providedComponents.hasOwnProperty(componentName)) {
220
- this.logger.error(`Multiple plugins provide the component "${componentName}". Provided by "${this.providedComponents[`${componentName}`]}" and "${pluginName}". Only the first loaded will be active.`);
221
- }
222
- else {
223
- this.providedComponents[`${componentName}`] = pluginName;
224
- }
225
- }
226
- }
227
- }
228
- else {
229
- // 如果存在未满足的依赖,将插件名放回下一轮尝试加载列表
230
- nextRemainingPluginNames.push(pluginName);
231
- /*this.logger.warn(
232
- `Plugin ${pluginName} has unmet dependencies: ${unmetDependencies.join(', ')}. Will try again later.`
233
- );*/
234
- }
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) {
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 */) {
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
+ await pluginInstance.apply(new context_1.Context(this, pluginName), await this.getPluginConfig(pluginName));
227
+ this.pluginLoader.logger.info(`Applied plugin "${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}".`);
235
234
  }
236
235
  else {
237
- //this.logger.warn(`Plugin "${pluginName}" could not be loaded or its module could not be imported.`);
236
+ this.providedComponents[componentName] = pluginName;
238
237
  }
239
238
  }
240
- catch (err) {
241
- this.pluginLoader.logger.error(`Failed to load or process plugin ${pluginName}:`, err);
242
- // 如果加载或处理失败,不将其加入下一轮尝试列表,但也不标记为已加载成功
239
+ }
240
+ this.pluginStatus[pluginName] = "enabled" /* PluginStatus.ENABLED */; // 更新状态为已启用
241
+ // this.logger.info(`Plugin "${pluginName}" loaded successfully.`);
242
+ // 加载成功后,检查是否可以加载其他等待中的插件
243
+ if (triggerPendingCheck) {
244
+ await this._loadPendingPlugins();
245
+ }
246
+ return true;
247
+ }
248
+ catch (err) {
249
+ this.pluginLoader.logger.error(`Failed to load plugin "${pluginName}":`, err);
250
+ // 加载失败的插件保持 PENDING 状态,以便后续重试
251
+ return false;
252
+ }
253
+ }
254
+ /**
255
+ * 内部函数:尝试加载所有处于 PENDING 状态的插件
256
+ */
257
+ async _loadPendingPlugins() {
258
+ const pendingPlugins = Object.keys(this.pluginStatus).filter(p => this.pluginStatus[p] === "pending" /* PluginStatus.PENDING */);
259
+ if (pendingPlugins.length === 0)
260
+ return;
261
+ // this.logger.info('Checking pending plugins...');
262
+ for (const pluginName of pendingPlugins) {
263
+ await this.loadSinglePlugin(pluginName, false); // 递归调用,但不触发顶层的pending检查
264
+ }
265
+ }
266
+ /**
267
+ * 卸载插件,并递归卸载依赖于它的其他插件
268
+ * @param pluginNameToUnload 要卸载的插件名
269
+ */
270
+ async unloadPlugin(pluginNameToUnload) {
271
+ // 1. 找出所有直接或间接依赖于此插件的插件
272
+ const dependents = [];
273
+ const pluginsToCheck = [pluginNameToUnload];
274
+ while (pluginsToCheck.length > 0) {
275
+ const currentPluginName = pluginsToCheck.shift();
276
+ const provided = this.plugins[currentPluginName]?.provide || [];
277
+ if (provided.length === 0)
278
+ continue;
279
+ for (const pluginName in this.plugins) {
280
+ if (dependents.includes(pluginName) || pluginName === pluginNameToUnload)
281
+ continue;
282
+ const deps = this.plugins[pluginName].depend || [];
283
+ if (provided.some(p => deps.includes(p))) {
284
+ if (!dependents.includes(pluginName)) {
285
+ dependents.push(pluginName);
286
+ pluginsToCheck.push(pluginName); // 递归查找
287
+ // this.logger.info(`Plugin "${pluginName}" depends on "${currentPluginName}" and will be unloaded.`);
288
+ }
243
289
  }
244
290
  }
245
- // 如果本轮没有插件加载成功,并且剩余插件列表没有变化,则检测到循环或无法解决的依赖
246
- if (!loadedInThisPass && nextRemainingPluginNames.length === remainingPluginNames.length && remainingPluginNames.length > 0) {
247
- this.logger.error('Detected circular or unresolvable plugin dependencies. Remaining plugins:', nextRemainingPluginNames.map(name => name) // 映射回插件名
248
- );
249
- break; // 防止无限循环
291
+ }
292
+ // 2. 卸载所有依赖者
293
+ for (const dependentName of dependents) {
294
+ await this._unloadSinglePlugin(dependentName);
295
+ }
296
+ // 3. 最后卸载目标插件
297
+ await this._unloadSinglePlugin(pluginNameToUnload);
298
+ // 4. 卸载完成后,尝试重新加载处于 PENDING 状态的插件
299
+ // await this._loadPendingPlugins();
300
+ }
301
+ /**
302
+ * 内部函数:执行单个插件的卸载逻辑
303
+ * @param pluginName 插件名
304
+ */
305
+ async _unloadSinglePlugin(pluginName) {
306
+ if (this.pluginStatus[pluginName] !== "enabled" /* PluginStatus.ENABLED */) {
307
+ return; // 只卸载已启用的插件
308
+ }
309
+ this.logger.info(`Unloading plugin "${pluginName}"...`);
310
+ try {
311
+ const plugin = this.plugins[pluginName];
312
+ if (plugin && plugin.disable) {
313
+ await plugin.disable(new context_1.Context(this, pluginName));
250
314
  }
251
- // 更新剩余待加载插件列表为下一轮的列表
252
- remainingPluginNames = nextRemainingPluginNames;
315
+ await this.pluginLoader.unloadPlugin(pluginName);
316
+ this.unregall(pluginName); // 清理该插件注册的所有内容
317
+ delete this.plugins[pluginName];
318
+ delete this.pluginModules[pluginName];
319
+ // 更新状态为 PENDING,因为它的配置仍然是启用的
320
+ // 如果它被其他插件依赖,当那个插件被卸载时,它也会被重新评估
321
+ this.pluginStatus[pluginName] = "pending" /* PluginStatus.PENDING */;
322
+ this.emit('plugin-unloaded', pluginName);
323
+ // this.logger.info(`Plugin "${pluginName}" unloaded.`);
253
324
  }
254
- // 警告未加载成功的插件
255
- if (remainingPluginNames.length > 0) {
256
- this.logger.warn('Some plugins could not be fully loaded due to unresolved dependencies or errors:', remainingPluginNames.map(name => name) // 映射回插件名
257
- );
325
+ catch (error) {
326
+ this.logger.error(`Failed to unload plugin "${pluginName}":`, error);
258
327
  }
259
- // 开发环境下监听插件变化
260
- if (process.env.NODE_ENV === 'development') {
261
- this.watchPlugins(this);
328
+ }
329
+ /**
330
+ * 重新加载插件
331
+ * @param pluginName 插件名称
332
+ */
333
+ async reloadPlugin(pluginName) {
334
+ // this.logger.info(`Reloading plugin "${pluginName}"...`);
335
+ // 1. 递归卸载插件及其依赖者
336
+ await this.unloadPlugin(pluginName);
337
+ // 2. 重新加载该插件
338
+ // unloadPlugin 已经将状态设置为 PENDING,所以 loadSinglePlugin 可以直接工作
339
+ const success = await this.loadSinglePlugin(pluginName);
340
+ if (success) {
341
+ // this.logger.info(`Plugin "${pluginName}" reloaded successfully.`);
342
+ this.emit('plugin-reloaded', pluginName);
343
+ }
344
+ else {
345
+ this.logger.error(`Failed to reload plugin "${pluginName}". It may have unmet dependencies.`);
262
346
  }
263
347
  }
264
348
  /**
265
- * 监听插件目录,实现热重载
266
- * @param core Core实例
267
- * @param pluginsDir 插件目录
349
+ * 监听插件文件变化,实现热更新
350
+ * @param core Core 实例
351
+ * @param pluginsDir 插件目录,默认为 'plugins'
268
352
  */
269
353
  watchPlugins(core, pluginsDir = 'plugins') {
270
354
  const logger = new logger_1.Logger('hmr');
271
355
  const watcher = chokidar_1.default.watch(pluginsDir, {
272
- ignored: /(^|[/\\])\../, // 忽略点文件
356
+ ignored: /(^|[/\\])\../,
273
357
  persistent: true,
274
358
  ignoreInitial: true,
275
359
  awaitWriteFinish: {
@@ -277,167 +361,84 @@ class Core {
277
361
  pollInterval: 100,
278
362
  },
279
363
  });
280
- watcher.on('change', async (changePath) => {
364
+ const getPluginNameFromPath = (changePath) => {
281
365
  if (!changePath.endsWith('.ts') && !changePath.endsWith('.js'))
282
- return;
366
+ return null;
283
367
  const parts = changePath.split(path.sep);
284
- if (parts.length < 2)
285
- return;
286
- const pluginName = parts[1];
287
- this.logger.info(`Plugin file changed: ${changePath}`);
288
- this.reloadPlugin(pluginName, core);
368
+ return parts.length > 1 ? parts[1] : null;
369
+ };
370
+ watcher.on('change', async (changePath) => {
371
+ const pluginName = getPluginNameFromPath(changePath);
372
+ if (pluginName) {
373
+ logger.info(`Plugin file changed: ${changePath}`);
374
+ await core.reloadPlugin(pluginName);
375
+ }
289
376
  });
290
377
  watcher.on('add', async (changePath) => {
291
- if (!changePath.endsWith('.ts') && !changePath.endsWith('.js'))
292
- return;
293
- const parts = changePath.split(path.sep);
294
- if (parts.length < 2)
295
- return;
296
- const pluginName = parts[1];
297
- this.logger.info(`New plugin file added: ${changePath}`);
298
- this.reloadPlugin(pluginName, core);
378
+ const pluginName = getPluginNameFromPath(changePath);
379
+ if (pluginName) {
380
+ logger.info(`New plugin file added: ${changePath}`);
381
+ await core.reloadPlugin(pluginName);
382
+ }
299
383
  });
300
384
  watcher.on('unlink', async (changePath) => {
301
- if (!changePath.endsWith('.ts') && !changePath.endsWith('.js'))
302
- return;
303
- const parts = changePath.split(path.sep);
304
- if (parts.length < 2)
305
- return;
306
- const pluginName = parts[1];
307
- this.logger.info(`Plugin file removed: ${changePath}`);
308
- await this.unloadPluginAndEmit(pluginName, core);
385
+ const pluginName = getPluginNameFromPath(changePath);
386
+ if (pluginName) {
387
+ logger.info(`Plugin file removed: ${changePath}`);
388
+ await core.unloadPlugin(pluginName);
389
+ }
309
390
  });
310
391
  logger.info(`Watching for plugin changes in ${pluginsDir}`);
311
392
  }
312
- /**
313
- * 重新加载插件
314
- * @param pluginName 插件名称
315
- * @param core Core实例
316
- */
317
- async reloadPlugin(pluginName, core) {
318
- try {
319
- // 强制重新读取配置文件,确保获取最新配置
320
- if (this.configPath && process.env.NODE_ENV === 'development') {
321
- try {
322
- const doc = yaml.load(fs.readFileSync(this.configPath, 'utf8'));
323
- if (doc && doc.plugins) {
324
- this.config.plugins = doc.plugins;
325
- }
326
- }
327
- catch (error) {
328
- this.logger.warn('Failed to refresh config for plugin reload:', error);
329
- }
330
- }
331
- // 获取最新的插件配置
332
- const config = await core.getPluginConfig(pluginName);
333
- // 加载插件实例
334
- const plugin = await core.pluginLoader.load(pluginName);
335
- // 如果插件存在,先禁用旧实例
336
- if (plugin && plugin.disable) {
337
- await plugin.disable(new context_1.Context(this, pluginName));
338
- }
339
- // 清理插件提供的组件
340
- // if (plugin.provide) {
341
- // for (let providedmodules of plugin.provide) {
342
- // if (this.components[`${providedmodules}`]) {
343
- // this.unregisterComponent(providedmodules);
344
- // }
345
- // }
346
- // }
347
- // 卸载插件
348
- await core.unloadPluginAndEmit(pluginName, core);
349
- // 应用新的插件实例
350
- if (plugin && plugin.apply) {
351
- await plugin.apply(new context_1.Context(this, pluginName), config);
352
- }
353
- this.pluginLoader.logger.info(`apply plugin ${pluginName}`);
354
- // 触发插件重载事件
355
- core.emit('plugin-reloaded', pluginName);
356
- }
357
- catch (error) {
358
- this.logger.error(`Failed to reload plugin ${pluginName}:`, error);
359
- }
360
- }
361
- /**
362
- * 卸载插件并触发事件
363
- * @param pluginName 插件名称
364
- * @param core Core实例
365
- */
366
- async unloadPluginAndEmit(pluginName, core) {
367
- try {
368
- const plugin = core.plugins[`${pluginName}`];
369
- if (plugin && plugin.disable) {
370
- await plugin.disable(new context_1.Context(this, pluginName));
371
- }
372
- await core.pluginLoader.unloadPlugin(pluginName);
373
- delete core.plugins[`${pluginName}`];
374
- delete this.pluginModules[`${pluginName}`];
375
- // Remove provided components from this plugin
376
- // for (const componentName in this.providedComponents) {
377
- // if (this.providedComponents[`${componentName}`] === pluginName) {
378
- // delete this.components[`${componentName}`];
379
- // delete this.providedComponents[`${componentName}`];
380
- // }
381
- // }
382
- this.unregall(pluginName);
383
- core.emit('plugin-unloaded', pluginName);
384
- }
385
- catch (error) {
386
- this.logger.error(`Failed to unload plugin ${pluginName}:`, error);
387
- }
388
- }
389
393
  /**
390
394
  * 注册组件
391
395
  * @param name 组件名称
392
- * @param component 组件
393
- * @returns void
396
+ * @param component 组件实例
394
397
  */
395
398
  registerComponent(name, component) {
396
399
  if (this.components.hasOwnProperty(name)) {
397
- this.logger.warn(`Component "${name}" already registered by plugin "${this.providedComponents[`${name}`]}".`);
400
+ this.logger.warn(`Component "${name}" already registered by plugin "${this.providedComponents[name]}".`);
398
401
  return;
399
402
  }
400
- this.components[`${name}`] = component;
401
- this.logger.info(`Component "${name}" registered.`);
403
+ this.components[name] = component;
404
+ // this.logger.info(`Component "${name}" registered.`);
402
405
  }
403
406
  /**
404
407
  * 获取组件
405
408
  * @param name 组件名称
406
- * @returns 组件
409
+ * @returns 组件实例
407
410
  */
408
411
  getComponent(name) {
409
- return this.components[`${name}`];
412
+ return this.components[name];
410
413
  }
411
414
  /**
412
- * 取消注册组件
415
+ * 注销组件
413
416
  * @param name 组件名称
414
417
  */
415
418
  unregisterComponent(name) {
416
- delete this.components[`${name}`];
417
- delete this.providedComponents[`${name}`];
418
- delete this.comtoplu[`${name}`];
419
+ delete this.components[name];
420
+ delete this.providedComponents[name];
421
+ delete this.comtoplu[name];
419
422
  }
420
423
  /**
421
- * 监听事件
424
+ * 注册事件监听器
422
425
  * @param event 事件名称
423
- * @param listener 回调函数
426
+ * @param listener 事件回调函数
424
427
  */
425
428
  on(event, listener) {
426
- if (!this.eventListeners[`${event}`]) {
427
- this.eventListeners[`${event}`] = [];
429
+ if (!this.eventListeners[event]) {
430
+ this.eventListeners[event] = [];
428
431
  }
429
- this.eventListeners[`${event}`].push(listener);
430
- //this.logger.info(`Listener added for event "${event}".`);
432
+ this.eventListeners[event].push(listener);
431
433
  }
432
434
  /**
433
435
  * 触发事件
434
436
  * @param event 事件名称
435
- * @param args 参数
437
+ * @param args 传递给事件监听器的参数
436
438
  */
437
439
  async emit(event, ...args) {
438
- if (this.eventListeners[`${event}`]) {
439
- // this.logger.info(`Emitting event "${event}" with args:`, args);
440
- for (const listener of this.eventListeners[`${event}`]) {
440
+ if (this.eventListeners[event]) {
441
+ for (const listener of this.eventListeners[event]) {
441
442
  try {
442
443
  await listener(...args);
443
444
  }
@@ -446,136 +447,109 @@ class Core {
446
447
  }
447
448
  }
448
449
  }
449
- else {
450
- //this.logger.info(`No listeners for event "${event}".`);
451
- }
452
450
  }
453
451
  /**
454
452
  * 注册全局中间件
455
453
  * @param name 中间件名称
456
- * @param middleware 中间件
457
- * @returns Core对象
454
+ * @param middleware 中间件函数
455
+ * @returns Core 实例
458
456
  */
459
457
  use(name, middleware) {
460
458
  this.globalMiddlewares[name] = middleware;
461
459
  return this;
462
460
  }
463
461
  /**
464
- * 定义指令
465
- * @param name 指令名称
466
- * @returns 指令对象
462
+ * 注册命令
463
+ * @param name 命令名称
464
+ * @returns Command 实例
467
465
  */
468
466
  command(name) {
469
- const command = new command_1.Command(this, name); // 传递 Core 实例
470
- this.commands[`${name}`] = command;
467
+ const command = new command_1.Command(this, name);
468
+ this.commands[name] = command;
471
469
  return command;
472
470
  }
473
471
  /**
474
- * 执行指令
475
- * @param name 指令名称
476
- * @param session 会话
477
- * @param args 参数
478
- * @returns 回话/null
472
+ * 执行命令
473
+ * @param name 命令名称
474
+ * @param session 会话对象
475
+ * @param args 传递给命令处理函数的参数
476
+ * @returns 会话对象或 null
479
477
  */
480
478
  async executeCommand(name, session, ...args) {
481
- const command = this.commands[`${name}`];
479
+ const command = this.commands[name];
482
480
  if (command) {
483
- // 将 globalMiddlewares 从对象转为数组
484
481
  const globalMiddlewareList = Object.values(this.globalMiddlewares || {});
485
482
  const commandMiddlewareList = command.middlewares || [];
486
- // 如果有中间件,执行中间件链
487
- if (globalMiddlewareList.length > 0 || commandMiddlewareList.length > 0) {
488
- const middlewares = [...globalMiddlewareList, ...commandMiddlewareList];
489
- let index = 0;
490
- const runner = async () => {
491
- if (index >= middlewares.length) {
492
- await command.executeHandler(session, ...args);
493
- return;
494
- }
483
+ const middlewares = [...globalMiddlewareList, ...commandMiddlewareList];
484
+ let index = 0;
485
+ const runner = async () => {
486
+ if (index < middlewares.length) {
495
487
  const middleware = middlewares[index++];
496
488
  await middleware(session, runner);
497
- };
498
- await runner();
499
- return session;
500
- }
501
- else {
502
- return await command.execute(session, ...args);
503
- }
489
+ }
490
+ else {
491
+ await command.executeHandler(session, ...args);
492
+ }
493
+ };
494
+ await runner();
495
+ return session;
504
496
  }
505
497
  return null;
506
498
  }
507
499
  /**
508
500
  * 注册平台
509
- * @param platform 平台名称
510
- * @returns 启动结果
501
+ * @param platform 平台实例
502
+ * @returns 平台启动结果
511
503
  */
512
504
  registerPlatform(platform) {
513
505
  this.platforms.push(platform);
514
506
  return platform.startPlatform(this);
515
507
  }
516
508
  /**
517
- * 取消注册插件
509
+ * 清理指定插件注册的所有内容
518
510
  * @param pluginname 插件名称
519
511
  */
520
512
  unregall(pluginname) {
521
- let cmdtodel = [];
522
- for (const cmd in this.cmdtoplu) {
523
- if (this.cmdtoplu[cmd] === pluginname) {
524
- cmdtodel.push(cmd);
525
- }
526
- }
527
- for (const cmd of cmdtodel) {
513
+ // 清理 commands
514
+ Object.keys(this.cmdtoplu)
515
+ .filter(cmd => this.cmdtoplu[cmd] === pluginname)
516
+ .forEach(cmd => {
528
517
  delete this.cmdtoplu[cmd];
529
518
  delete this.commands[cmd];
530
- }
531
- let comptodel = [];
532
- for (const comp in this.comtoplu) {
533
- if (this.comtoplu[comp] === pluginname) {
534
- comptodel.push(comp);
535
- }
536
- }
537
- for (const comp of comptodel) {
519
+ });
520
+ // 清理 components 和 providedComponents
521
+ Object.keys(this.comtoplu)
522
+ .filter(comp => this.comtoplu[comp] === pluginname)
523
+ .forEach(comp => {
538
524
  delete this.comtoplu[comp];
539
525
  delete this.components[comp];
540
526
  delete this.providedComponents[comp];
541
- }
542
- let evttodel = [];
527
+ });
528
+ // 清理 event listeners
543
529
  for (const evt in this.evttoplu) {
544
530
  if (this.evttoplu[evt][pluginname]) {
545
- // 清除插件在 evttoplu 中的记录
546
- delete this.evttoplu[evt][pluginname];
547
- // 如果该事件的插件已经全部清除了,也可以选择删掉整个事件
548
- if (Object.keys(this.evttoplu[evt]).length === 0) {
549
- evttodel.push(evt);
550
- }
551
- // 同时在 this.eventListeners 中删掉对应 listener
552
531
  const pluginListeners = this.evttoplu[evt][pluginname] || [];
553
- this.eventListeners[evt] = this.eventListeners[evt]?.filter(l => !pluginListeners.includes(l)) || [];
554
- }
555
- }
556
- let mdwtodel = [];
557
- for (const mdw in this.mdwtoplu) {
558
- if (this.mdwtoplu[mdw] === pluginname) {
559
- mdwtodel.push(mdw);
532
+ if (this.eventListeners[evt]) {
533
+ this.eventListeners[evt] = this.eventListeners[evt].filter(l => !pluginListeners.includes(l));
534
+ }
535
+ delete this.evttoplu[evt][pluginname];
560
536
  }
561
537
  }
562
- for (const mdw of mdwtodel) {
538
+ // 清理 middlewares
539
+ Object.keys(this.mdwtoplu)
540
+ .filter(mdw => this.mdwtoplu[mdw] === pluginname)
541
+ .forEach(mdw => {
563
542
  delete this.mdwtoplu[mdw];
564
543
  delete this.globalMiddlewares[mdw];
565
- }
544
+ });
566
545
  }
567
546
  /**
568
- * 获取指令对象
569
- * @param name 指令名称
570
- * @returns null/指令对象
547
+ * 获取命令
548
+ * @param name 命令名称
549
+ * @returns Command 实例或 null
571
550
  */
572
551
  getCommand(name) {
573
- if (Object.hasOwn(this.commands, name)) {
574
- return this.commands[name];
575
- }
576
- else {
577
- return null;
578
- }
552
+ return this.commands[name] || null;
579
553
  }
580
554
  }
581
555
  exports.Core = Core;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yumerijs/core",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Core module for yumeri",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",