@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.
- package/dist/core.d.ts +61 -44
- package/dist/core.js +296 -294
- 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
|
|
77
|
-
* @param
|
|
79
|
+
* 导出的函数:加载单个插件
|
|
80
|
+
* @param pluginName 要加载的插件名
|
|
81
|
+
* @param triggerPendingCheck 是否在加载后触发对其他待定插件的检查,默认为 true
|
|
82
|
+
* @returns Promise<boolean> 是否加载成功
|
|
78
83
|
*/
|
|
79
|
-
|
|
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
|
|
103
|
+
reloadPlugin(pluginName: string): Promise<void>;
|
|
86
104
|
/**
|
|
87
|
-
*
|
|
88
|
-
* @param
|
|
89
|
-
* @param
|
|
105
|
+
* 监听插件文件变化,实现热更新
|
|
106
|
+
* @param core Core 实例
|
|
107
|
+
* @param pluginsDir 插件目录,默认为 'plugins'
|
|
90
108
|
*/
|
|
91
|
-
|
|
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
|
|
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/
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
149
|
+
this.logger.info('No plugins configuration found. No plugins to load.');
|
|
160
150
|
return;
|
|
161
151
|
}
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
|
168
|
-
if (
|
|
169
|
-
this.logger.info(
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
let
|
|
174
|
-
while (
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
if (
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
this.logger.
|
|
321
|
+
catch (error) {
|
|
322
|
+
this.logger.error(`Failed to unload plugin "${pluginName}":`, error);
|
|
227
323
|
}
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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[
|
|
396
|
+
this.logger.warn(`Component "${name}" already registered by plugin "${this.providedComponents[name]}".`);
|
|
366
397
|
return;
|
|
367
398
|
}
|
|
368
|
-
this.components[
|
|
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[
|
|
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[
|
|
385
|
-
delete this.providedComponents[
|
|
386
|
-
delete this.comtoplu[
|
|
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[
|
|
395
|
-
this.eventListeners[
|
|
425
|
+
if (!this.eventListeners[event]) {
|
|
426
|
+
this.eventListeners[event] = [];
|
|
396
427
|
}
|
|
397
|
-
this.eventListeners[
|
|
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[
|
|
407
|
-
|
|
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);
|
|
438
|
-
this.commands[
|
|
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
|
|
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[
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|