@yumerijs/core 2.0.0 → 2.0.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.
- package/dist/context.d.ts +0 -5
- package/dist/context.js +0 -5
- package/dist/core.d.ts +6 -158
- package/dist/core.js +17 -485
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +12 -5
- package/package.json +1 -2
package/dist/context.d.ts
CHANGED
package/dist/context.js
CHANGED
package/dist/core.d.ts
CHANGED
|
@@ -13,13 +13,6 @@ interface Plugin {
|
|
|
13
13
|
depend: Array<string>;
|
|
14
14
|
provide: Array<string>;
|
|
15
15
|
}
|
|
16
|
-
interface PluginLoader {
|
|
17
|
-
load(pluginName: string): Promise<Plugin>;
|
|
18
|
-
unloadPlugin(pluginName: string): Promise<void>;
|
|
19
|
-
checkPluginDependencies(pluginPath: string): Promise<boolean>;
|
|
20
|
-
installPluginDependencies(pluginName: string): Promise<void>;
|
|
21
|
-
logger: Logger;
|
|
22
|
-
}
|
|
23
16
|
interface CoreOptions {
|
|
24
17
|
port: number;
|
|
25
18
|
host: string;
|
|
@@ -28,22 +21,12 @@ interface CoreOptions {
|
|
|
28
21
|
enableWs: boolean;
|
|
29
22
|
lang: string[];
|
|
30
23
|
}
|
|
31
|
-
/**
|
|
32
|
-
* 定义插件状态枚举
|
|
33
|
-
*/
|
|
34
24
|
export declare const enum PluginStatus {
|
|
35
|
-
ENABLED = "enabled"
|
|
36
|
-
DISABLED = "disabled"
|
|
25
|
+
ENABLED = "enabled",
|
|
26
|
+
DISABLED = "disabled",
|
|
37
27
|
PENDING = "pending"
|
|
38
28
|
}
|
|
39
29
|
export declare class Core {
|
|
40
|
-
plugins: {
|
|
41
|
-
[name: string]: Plugin & {
|
|
42
|
-
depend?: string[];
|
|
43
|
-
provide?: string[];
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
config: any;
|
|
47
30
|
eventListeners: {
|
|
48
31
|
[event: string]: ((...args: any[]) => Promise<void>)[];
|
|
49
32
|
};
|
|
@@ -51,163 +34,28 @@ export declare class Core {
|
|
|
51
34
|
[name: string]: any;
|
|
52
35
|
};
|
|
53
36
|
routes: Record<string, Route>;
|
|
54
|
-
pluginLoader: PluginLoader;
|
|
55
37
|
logger: Logger;
|
|
56
38
|
globalMiddlewares: Record<string, Middleware>;
|
|
57
|
-
pluginStatus: Record<string, PluginStatus>;
|
|
58
39
|
hooks: Record<string, Hook>;
|
|
59
40
|
coreConfig: CoreOptions;
|
|
60
41
|
server: CoreServer;
|
|
61
42
|
i18n: I18n;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
private configPath;
|
|
65
|
-
/**
|
|
66
|
-
* 插件名 -> Context 映射
|
|
67
|
-
*/
|
|
68
|
-
private pluginContexts;
|
|
69
|
-
constructor(pluginLoader?: PluginLoader, coreConfig?: CoreOptions, setCore?: boolean);
|
|
70
|
-
/**
|
|
71
|
-
* 获取或创建插件对应 Context
|
|
72
|
-
*/
|
|
73
|
-
getContext(pluginName: string): Context;
|
|
74
|
-
/**
|
|
75
|
-
* 清理指定插件注册的所有内容
|
|
76
|
-
*/
|
|
77
|
-
unregall(pluginName: string): void;
|
|
78
|
-
/**
|
|
79
|
-
* 加载配置文件
|
|
80
|
-
* @param configPath 配置文件的路径
|
|
81
|
-
*/
|
|
82
|
-
loadConfig(configPath: string): Promise<void>;
|
|
83
|
-
/**
|
|
84
|
-
* 启动应用
|
|
85
|
-
* @returns Promise<void>
|
|
86
|
-
*/
|
|
43
|
+
loader: any;
|
|
44
|
+
constructor(loader?: any, coreConfig?: CoreOptions, setCore?: boolean);
|
|
87
45
|
runCore(): Promise<void>;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
* @param configPath 配置文件的路径
|
|
91
|
-
*/
|
|
92
|
-
private watchConfig;
|
|
93
|
-
/**
|
|
94
|
-
* 获取指定插件的配置
|
|
95
|
-
* @param pluginName 插件名称
|
|
96
|
-
* @returns 插件的配置对象
|
|
97
|
-
*/
|
|
98
|
-
getPluginConfig(pluginName: string): Promise<Config>;
|
|
99
|
-
/**
|
|
100
|
-
* 加载所有在配置文件中启用的插件
|
|
101
|
-
* @returns Promise<void>
|
|
102
|
-
*/
|
|
103
|
-
loadPlugins(): Promise<void>;
|
|
104
|
-
/**
|
|
105
|
-
* 加载单个插件
|
|
106
|
-
* @param pluginName 要加载的插件名
|
|
107
|
-
* @param triggerPendingCheck 是否在加载后触发对其他待定插件的检查,默认为 true
|
|
108
|
-
* @returns Promise<boolean> 是否加载成功
|
|
109
|
-
*/
|
|
110
|
-
loadSinglePlugin(pluginName: string, triggerPendingCheck?: boolean, onlypending?: boolean): Promise<boolean>;
|
|
111
|
-
/**
|
|
112
|
-
* 尝试加载所有处于 PENDING 状态的插件
|
|
113
|
-
* @returns Promise<void>
|
|
114
|
-
*/
|
|
115
|
-
private _loadPendingPlugins;
|
|
116
|
-
/**
|
|
117
|
-
* 卸载插件,并递归卸载依赖于它的其他插件
|
|
118
|
-
* @param pluginNameToUnload 要卸载的插件名
|
|
119
|
-
*/
|
|
120
|
-
unloadPlugin(pluginNameToUnload: string, ispending?: boolean): Promise<void>;
|
|
121
|
-
/**
|
|
122
|
-
* 执行单个插件的卸载逻辑
|
|
123
|
-
* @param pluginName 插件名
|
|
124
|
-
* @param ispending 是否处于 PENDING 状态
|
|
125
|
-
*/
|
|
126
|
-
private _unloadSinglePlugin;
|
|
127
|
-
/**
|
|
128
|
-
* 重新加载插件
|
|
129
|
-
* @param pluginName 插件名称
|
|
130
|
-
*/
|
|
131
|
-
reloadPlugin(pluginName: string): Promise<void>;
|
|
132
|
-
/**
|
|
133
|
-
* 监听插件文件变化
|
|
134
|
-
* @param pluginName 插件名称
|
|
135
|
-
* @param pluginPath 插件路径
|
|
136
|
-
*/
|
|
137
|
-
private watchPlugin;
|
|
138
|
-
/**
|
|
139
|
-
* 注册组件
|
|
140
|
-
* @param name 组件名称
|
|
141
|
-
* @param component 组件实例
|
|
142
|
-
*/
|
|
46
|
+
getShortPluginName(pluginName: string): string;
|
|
47
|
+
plugin(pluginInstance: Plugin, context: Context, config: Config): Promise<void>;
|
|
143
48
|
registerComponent(name: string, component: any): void;
|
|
144
|
-
/**
|
|
145
|
-
* 获取组件
|
|
146
|
-
* @param name 组件名称
|
|
147
|
-
* @returns 组件实例
|
|
148
|
-
*/
|
|
149
49
|
getComponent(name: string): any;
|
|
150
|
-
/**
|
|
151
|
-
* 注销组件
|
|
152
|
-
* @param name 组件名称
|
|
153
|
-
*/
|
|
154
50
|
unregisterComponent(name: string): void;
|
|
155
|
-
/**
|
|
156
|
-
* 注册事件监听器
|
|
157
|
-
* @param event 事件名称
|
|
158
|
-
* @param listener 事件回调函数
|
|
159
|
-
*/
|
|
160
51
|
on(event: string, listener: (...args: any[]) => Promise<void>): void;
|
|
161
|
-
/**
|
|
162
|
-
* 触发事件
|
|
163
|
-
* @param event 事件名称
|
|
164
|
-
* @param args 传递给事件监听器的参数
|
|
165
|
-
*/
|
|
166
52
|
emit(event: string, ...args: any[]): Promise<void>;
|
|
167
|
-
/**
|
|
168
|
-
* 注册全局中间件
|
|
169
|
-
* @param name 中间件名称
|
|
170
|
-
* @param middleware 中间件函数
|
|
171
|
-
* @returns Core 实例
|
|
172
|
-
*/
|
|
173
53
|
use(name: string, middleware: Middleware): Core;
|
|
174
|
-
/**
|
|
175
|
-
* 注册路由
|
|
176
|
-
* @param path 路由路径
|
|
177
|
-
* @returns Route 实例
|
|
178
|
-
*/
|
|
179
54
|
route(path: string): Route;
|
|
180
|
-
/**
|
|
181
|
-
* 注册 Hook 钩子
|
|
182
|
-
* @param name Hook 点名称
|
|
183
|
-
* @param hookname 钩子名称
|
|
184
|
-
* @param callback 钩子函数
|
|
185
|
-
*/
|
|
186
55
|
hook(name: string, hookname: string, callback: HookHandler): any;
|
|
187
|
-
/**
|
|
188
|
-
* 消除 Hook 钩子
|
|
189
|
-
*/
|
|
190
56
|
unhook(name: string, hookname: string): any;
|
|
191
|
-
/**
|
|
192
|
-
* 执行 Hook 钩子
|
|
193
|
-
* @param name Hook 点名称
|
|
194
|
-
* @param args 参数
|
|
195
|
-
* @return Promise<any[]>
|
|
196
|
-
*/
|
|
197
57
|
hookExecute(name: string, ...args: any[]): Promise<any[]>;
|
|
198
|
-
/**
|
|
199
|
-
* 执行路由
|
|
200
|
-
* @param pathname 需要匹配的URL
|
|
201
|
-
* @param session 当前会话
|
|
202
|
-
* @param queryParams URL参数
|
|
203
|
-
* @returns 是否匹配成功
|
|
204
|
-
*/
|
|
205
58
|
executeRoute(pathname: string, session: Session, queryParams: URLSearchParams): Promise<boolean>;
|
|
206
|
-
/**
|
|
207
|
-
* 获取路由
|
|
208
|
-
* @param path 匹配路径
|
|
209
|
-
* @return Route 实例或 false
|
|
210
|
-
*/
|
|
211
59
|
getRoute(path: string): Route | false;
|
|
212
60
|
}
|
|
213
61
|
export {};
|
package/dist/core.js
CHANGED
|
@@ -1,126 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.Core = void 0;
|
|
37
|
-
/**
|
|
38
|
-
* @time: 2025/08/14 09:48
|
|
39
|
-
* @author: FireGuo
|
|
40
|
-
* WindyPear-Team All right reserved
|
|
41
|
-
**/
|
|
42
|
-
const yaml = __importStar(require("js-yaml"));
|
|
43
|
-
const fs = __importStar(require("fs"));
|
|
44
|
-
const path = __importStar(require("path"));
|
|
45
|
-
const config_1 = require("./config");
|
|
46
4
|
const logger_1 = require("./logger");
|
|
47
|
-
const chokidar = __importStar(require("chokidar"));
|
|
48
5
|
const route_1 = require("./route");
|
|
49
|
-
const context_1 = require("./context");
|
|
50
6
|
const hook_1 = require("./hook");
|
|
51
7
|
const server_1 = require("./server");
|
|
52
|
-
const i18n_1 = require("./i18n");
|
|
53
8
|
class Core {
|
|
54
|
-
plugins = {};
|
|
55
|
-
config = null;
|
|
56
9
|
eventListeners = {};
|
|
57
10
|
components = {};
|
|
58
11
|
routes = {};
|
|
59
|
-
pluginLoader;
|
|
60
12
|
logger = new logger_1.Logger('core');
|
|
61
13
|
globalMiddlewares = {};
|
|
62
|
-
pluginStatus = {};
|
|
63
14
|
hooks = {};
|
|
64
15
|
coreConfig;
|
|
65
16
|
server;
|
|
66
17
|
i18n;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
* 插件名 -> Context 映射
|
|
72
|
-
*/
|
|
73
|
-
pluginContexts = {};
|
|
74
|
-
constructor(pluginLoader, coreConfig, setCore = true) {
|
|
75
|
-
this.coreConfig = coreConfig;
|
|
76
|
-
this.pluginLoader = pluginLoader;
|
|
18
|
+
loader;
|
|
19
|
+
constructor(loader, coreConfig, setCore = true) {
|
|
20
|
+
this.coreConfig = coreConfig || {};
|
|
21
|
+
this.loader = loader;
|
|
77
22
|
if (setCore)
|
|
78
23
|
logger_1.Logger.setCore(this);
|
|
79
24
|
}
|
|
80
|
-
/**
|
|
81
|
-
* 获取或创建插件对应 Context
|
|
82
|
-
*/
|
|
83
|
-
getContext(pluginName) {
|
|
84
|
-
if (!this.pluginContexts[pluginName]) {
|
|
85
|
-
this.pluginContexts[pluginName] = new context_1.Context(this, pluginName);
|
|
86
|
-
}
|
|
87
|
-
return this.pluginContexts[pluginName];
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* 清理指定插件注册的所有内容
|
|
91
|
-
*/
|
|
92
|
-
unregall(pluginName) {
|
|
93
|
-
const ctx = this.pluginContexts[pluginName];
|
|
94
|
-
if (ctx) {
|
|
95
|
-
ctx.dispose();
|
|
96
|
-
delete this.pluginContexts[pluginName];
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* 加载配置文件
|
|
101
|
-
* @param configPath 配置文件的路径
|
|
102
|
-
*/
|
|
103
|
-
async loadConfig(configPath) {
|
|
104
|
-
try {
|
|
105
|
-
this.configPath = configPath;
|
|
106
|
-
const doc = yaml.load(fs.readFileSync(configPath, 'utf8'));
|
|
107
|
-
this.config = doc;
|
|
108
|
-
this.logger.info('Config loaded.');
|
|
109
|
-
if (process.env.NODE_ENV === 'development') {
|
|
110
|
-
this.watchConfig(configPath);
|
|
111
|
-
}
|
|
112
|
-
this.coreConfig = this.config.core || {};
|
|
113
|
-
}
|
|
114
|
-
catch (e) {
|
|
115
|
-
this.logger.error('Failed to load config:', e);
|
|
116
|
-
throw e;
|
|
117
|
-
}
|
|
118
|
-
this.i18n = new i18n_1.I18n(this.coreConfig.lang || ['zh', 'en']);
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* 启动应用
|
|
122
|
-
* @returns Promise<void>
|
|
123
|
-
*/
|
|
124
25
|
async runCore() {
|
|
125
26
|
this.server = new server_1.Server(this, {
|
|
126
27
|
port: this.coreConfig.port || 14510,
|
|
@@ -132,369 +33,38 @@ class Core {
|
|
|
132
33
|
await this.server.start();
|
|
133
34
|
this.logger.info(`Yumeri server started at ${this.coreConfig.host}:${this.coreConfig.port}`);
|
|
134
35
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
ignoreInitial: true,
|
|
143
|
-
awaitWriteFinish: {
|
|
144
|
-
stabilityThreshold: 300,
|
|
145
|
-
pollInterval: 100,
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
watcher.on('change', async () => {
|
|
149
|
-
try {
|
|
150
|
-
const doc = yaml.load(fs.readFileSync(configPath, 'utf8'));
|
|
151
|
-
if (doc && doc.plugins) {
|
|
152
|
-
this.config.plugins = doc.plugins;
|
|
153
|
-
}
|
|
154
|
-
await this.emit('config-changed', this.config);
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
this.logger.error('Failed to reload config:', error);
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
this.logger.info(`Watching for config changes at ${configPath}`);
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* 获取指定插件的配置
|
|
164
|
-
* @param pluginName 插件名称
|
|
165
|
-
* @returns 插件的配置对象
|
|
166
|
-
*/
|
|
167
|
-
async getPluginConfig(pluginName) {
|
|
168
|
-
const actualPluginName = pluginName.startsWith('~') ? pluginName.substring(1) : pluginName;
|
|
169
|
-
if (this.configPath && process.env.NODE_ENV === 'development') {
|
|
170
|
-
try {
|
|
171
|
-
const doc = yaml.load(fs.readFileSync(this.configPath, 'utf8'));
|
|
172
|
-
if (doc && doc.plugins) {
|
|
173
|
-
this.config.plugins = doc.plugins;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
this.logger.warn('Failed to refresh config for hot reload:', error);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (!this.config.plugins[actualPluginName]) {
|
|
181
|
-
return new config_1.Config(actualPluginName);
|
|
182
|
-
}
|
|
183
|
-
return new config_1.Config(actualPluginName, this.config.plugins[actualPluginName]);
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* 加载所有在配置文件中启用的插件
|
|
187
|
-
* @returns Promise<void>
|
|
188
|
-
*/
|
|
189
|
-
async loadPlugins() {
|
|
190
|
-
if (!this.config || typeof this.config.plugins !== 'object' || this.config.plugins === null) {
|
|
191
|
-
this.logger.info('No plugins configuration found. No plugins to load.');
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
const allPluginNames = Object.keys(this.config.plugins);
|
|
195
|
-
this.pluginStatus = {}; // 重置状态
|
|
196
|
-
// 初始化所有插件的状态
|
|
197
|
-
for (const name of allPluginNames) {
|
|
198
|
-
if (name.startsWith('~')) {
|
|
199
|
-
const actualName = name.substring(1);
|
|
200
|
-
this.pluginStatus[actualName] = "disabled" /* PluginStatus.DISABLED */;
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
this.pluginStatus[name] = "pending" /* PluginStatus.PENDING */; // 默认为等待加载
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
const enabledPlugins = allPluginNames.filter(name => !name.startsWith('~'));
|
|
207
|
-
// 卸载配置中已禁用或移除的已加载插件
|
|
208
|
-
const currentlyLoaded = Object.keys(this.plugins);
|
|
209
|
-
for (const loadedName of currentlyLoaded) {
|
|
210
|
-
if (!enabledPlugins.includes(loadedName)) {
|
|
211
|
-
await this.unloadPlugin(loadedName);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
if (enabledPlugins.length === 0) {
|
|
215
|
-
this.logger.info('No enabled plugins found in configuration.');
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
// this.logger.info(`Disabled plugins: ${Object.keys(this.pluginStatus).filter(p => this.pluginStatus[p] === PluginStatus.DISABLED).join(', ') || 'None'}`);
|
|
219
|
-
// 循环加载,直到没有插件可以被加载
|
|
220
|
-
let loadedInLastPass = true;
|
|
221
|
-
while (loadedInLastPass) {
|
|
222
|
-
loadedInLastPass = false;
|
|
223
|
-
for (const pluginName of enabledPlugins) {
|
|
224
|
-
// 如果插件已经是启用状态,则跳过
|
|
225
|
-
if (this.pluginStatus[pluginName] === "enabled" /* PluginStatus.ENABLED */) {
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
// 尝试加载单个插件
|
|
229
|
-
const success = await this.loadSinglePlugin(pluginName, false); // 内部加载,不触发后续的pending检查
|
|
230
|
-
if (success) {
|
|
231
|
-
loadedInLastPass = true;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
// 加载完所有能加载的插件后,统一检查并加载待定插件
|
|
236
|
-
await this._loadPendingPlugins();
|
|
237
|
-
const pendingPlugins = Object.keys(this.pluginStatus).filter(p => this.pluginStatus[p] === "pending" /* PluginStatus.PENDING */);
|
|
238
|
-
if (pendingPlugins.length > 0) {
|
|
239
|
-
// this.logger.warn('Some plugins could not be loaded due to unresolved dependencies:', pendingPlugins);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* 加载单个插件
|
|
244
|
-
* @param pluginName 要加载的插件名
|
|
245
|
-
* @param triggerPendingCheck 是否在加载后触发对其他待定插件的检查,默认为 true
|
|
246
|
-
* @returns Promise<boolean> 是否加载成功
|
|
247
|
-
*/
|
|
248
|
-
async loadSinglePlugin(pluginName, triggerPendingCheck = true, onlypending = false) {
|
|
249
|
-
// 如果插件不存在于状态记录中(例如,动态添加),则设为 PENDING
|
|
250
|
-
if (!this.pluginStatus[pluginName]) {
|
|
251
|
-
this.pluginStatus[pluginName] = "pending" /* PluginStatus.PENDING */;
|
|
252
|
-
}
|
|
253
|
-
// 只有 PENDING 状态的插件才能被加载
|
|
254
|
-
if (this.pluginStatus[pluginName] !== "pending" /* PluginStatus.PENDING */ && onlypending) {
|
|
255
|
-
// this.logger.info(`Plugin "${pluginName}" is not in a pending state (current: ${this.pluginStatus[pluginName]}). Skipping load.`);
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
try {
|
|
259
|
-
const pluginInstance = await this.pluginLoader.load(pluginName);
|
|
260
|
-
if (!pluginInstance) {
|
|
261
|
-
throw new Error('Plugin loader returned no instance.');
|
|
262
|
-
}
|
|
263
|
-
const deps = pluginInstance.depend || [];
|
|
264
|
-
const unmetDependencies = deps.filter(dep => !this.components[dep]);
|
|
265
|
-
if (unmetDependencies.length > 0) {
|
|
266
|
-
// this.logger.info(`Plugin "${pluginName}" has unmet dependencies: [${unmetDependencies.join(', ')}]. Will retry later.`);
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
// 依赖满足,开始加载
|
|
270
|
-
this.plugins[pluginName] = pluginInstance;
|
|
271
|
-
if (pluginInstance.apply) {
|
|
272
|
-
this.pluginLoader.logger.info(`Apply plugin "${pluginName}"`);
|
|
273
|
-
await pluginInstance.apply(this.getContext(pluginName), await this.getPluginConfig(pluginName));
|
|
274
|
-
}
|
|
275
|
-
// 注册提供的组件
|
|
276
|
-
// if (pluginInstance.provide) {
|
|
277
|
-
// for (const componentName of pluginInstance.provide) {
|
|
278
|
-
// if (this.components[componentName]) {
|
|
279
|
-
// this.logger.error(`Component "${componentName}" is provided by multiple plugins: "${this.providedComponents[componentName]}" and "${pluginName}".`);
|
|
280
|
-
// } else {
|
|
281
|
-
// this.components[componentName] = pluginName;
|
|
282
|
-
// }
|
|
283
|
-
// }
|
|
284
|
-
// }
|
|
285
|
-
this.pluginStatus[pluginName] = "enabled" /* PluginStatus.ENABLED */; // 更新状态为已启用
|
|
286
|
-
// 加载成功后,检查是否可以加载其他等待中的插件
|
|
287
|
-
if (triggerPendingCheck) {
|
|
288
|
-
await this._loadPendingPlugins();
|
|
289
|
-
}
|
|
290
|
-
// In development mode, watch the plugin for changes.
|
|
291
|
-
if (process.env.NODE_ENV === 'development') {
|
|
292
|
-
let pluginPathToWatch = null;
|
|
293
|
-
try {
|
|
294
|
-
const pkgJsonPath = require.resolve(`${pluginName}/package.json`);
|
|
295
|
-
pluginPathToWatch = path.dirname(pkgJsonPath);
|
|
296
|
-
}
|
|
297
|
-
catch (e) {
|
|
298
|
-
const localPluginPath = path.resolve(process.cwd(), pluginName);
|
|
299
|
-
const localPluginPathInPlugins = path.resolve(process.cwd(), 'plugins', pluginName);
|
|
300
|
-
if (fs.existsSync(localPluginPath)) {
|
|
301
|
-
pluginPathToWatch = localPluginPath;
|
|
302
|
-
}
|
|
303
|
-
else if (fs.existsSync(localPluginPathInPlugins)) {
|
|
304
|
-
pluginPathToWatch = localPluginPathInPlugins;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
if (pluginPathToWatch) {
|
|
308
|
-
this.watchPlugin(pluginName, pluginPathToWatch);
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
this.logger.warn(`Could not resolve path for plugin ${pluginName} to watch for changes.`);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
catch (err) {
|
|
317
|
-
this.pluginLoader.logger.error(`Failed to load plugin "${pluginName}":`, err);
|
|
318
|
-
// 加载失败的插件保持 PENDING 状态,以便后续重试
|
|
319
|
-
return false;
|
|
36
|
+
getShortPluginName(pluginName) {
|
|
37
|
+
const scopeRegex = /^(@[^/]+\/)/;
|
|
38
|
+
const scopeMatch = pluginName.match(scopeRegex);
|
|
39
|
+
const scope = scopeMatch ? scopeMatch[0] : '';
|
|
40
|
+
const nameWithoutScope = pluginName.replace(scopeRegex, '');
|
|
41
|
+
if (nameWithoutScope.startsWith('yumeri-plugin-')) {
|
|
42
|
+
return `${scope}${nameWithoutScope.substring('yumeri-plugin-'.length)}`;
|
|
320
43
|
}
|
|
44
|
+
return pluginName;
|
|
321
45
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const pendingPlugins = Object.keys(this.pluginStatus).filter(p => this.pluginStatus[p] === "pending" /* PluginStatus.PENDING */);
|
|
328
|
-
if (pendingPlugins.length === 0)
|
|
329
|
-
return;
|
|
330
|
-
// this.logger.info('Checking pending plugins...');
|
|
331
|
-
for (const pluginName of pendingPlugins) {
|
|
332
|
-
await this.loadSinglePlugin(pluginName, false); // 递归调用,但不触发顶层的pending检查
|
|
46
|
+
async plugin(pluginInstance, context, config) {
|
|
47
|
+
const shortName = this.getShortPluginName(context.pluginname);
|
|
48
|
+
this.logger.info(`apply plugin ${shortName}`);
|
|
49
|
+
if (pluginInstance.apply) {
|
|
50
|
+
await pluginInstance.apply(context, config);
|
|
333
51
|
}
|
|
334
52
|
}
|
|
335
|
-
/**
|
|
336
|
-
* 卸载插件,并递归卸载依赖于它的其他插件
|
|
337
|
-
* @param pluginNameToUnload 要卸载的插件名
|
|
338
|
-
*/
|
|
339
|
-
async unloadPlugin(pluginNameToUnload, ispending = false) {
|
|
340
|
-
// 1. 找出所有直接或间接依赖于此插件的插件
|
|
341
|
-
const dependents = [];
|
|
342
|
-
const pluginsToCheck = [pluginNameToUnload];
|
|
343
|
-
while (pluginsToCheck.length > 0) {
|
|
344
|
-
const currentPluginName = pluginsToCheck.shift();
|
|
345
|
-
const provided = this.plugins[currentPluginName]?.provide || [];
|
|
346
|
-
if (provided.length === 0)
|
|
347
|
-
continue;
|
|
348
|
-
for (const pluginName in this.plugins) {
|
|
349
|
-
if (dependents.includes(pluginName) || pluginName === pluginNameToUnload)
|
|
350
|
-
continue;
|
|
351
|
-
const deps = this.plugins[pluginName].depend || [];
|
|
352
|
-
if (provided.some(p => deps.includes(p))) {
|
|
353
|
-
if (!dependents.includes(pluginName)) {
|
|
354
|
-
dependents.push(pluginName);
|
|
355
|
-
pluginsToCheck.push(pluginName); // 递归查找
|
|
356
|
-
// this.logger.info(`Plugin "${pluginName}" depends on "${currentPluginName}" and will be unloaded.`);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
// 2. 卸载所有依赖者
|
|
362
|
-
for (const dependentName of dependents) {
|
|
363
|
-
await this.unloadPlugin(dependentName, true);
|
|
364
|
-
}
|
|
365
|
-
// 3. 最后卸载目标插件
|
|
366
|
-
await this._unloadSinglePlugin(pluginNameToUnload, ispending);
|
|
367
|
-
// 4. 卸载完成后,尝试重新加载处于 PENDING 状态的插件
|
|
368
|
-
// await this._loadPendingPlugins();
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* 执行单个插件的卸载逻辑
|
|
372
|
-
* @param pluginName 插件名
|
|
373
|
-
* @param ispending 是否处于 PENDING 状态
|
|
374
|
-
*/
|
|
375
|
-
async _unloadSinglePlugin(pluginName, ispending = false) {
|
|
376
|
-
if (this.pluginStatus[pluginName] !== "enabled" /* PluginStatus.ENABLED */) {
|
|
377
|
-
return; // 只卸载已启用的插件
|
|
378
|
-
}
|
|
379
|
-
this.logger.info(`Unloading plugin "${pluginName}"...`);
|
|
380
|
-
try {
|
|
381
|
-
const plugin = this.plugins[pluginName];
|
|
382
|
-
if (plugin && plugin.disable)
|
|
383
|
-
await plugin.disable(this.getContext(pluginName));
|
|
384
|
-
await this.pluginLoader.unloadPlugin(pluginName);
|
|
385
|
-
this.unregall(pluginName); // 清理该插件注册的所有内容
|
|
386
|
-
delete this.plugins[pluginName];
|
|
387
|
-
delete this.pluginModules[pluginName];
|
|
388
|
-
this.pluginStatus[pluginName] = ispending ? "pending" /* PluginStatus.PENDING */ : "disabled" /* PluginStatus.DISABLED */;
|
|
389
|
-
// Stop watching the plugin directory
|
|
390
|
-
if (this.pluginWatchers[pluginName]) {
|
|
391
|
-
this.pluginWatchers[pluginName].close();
|
|
392
|
-
delete this.pluginWatchers[pluginName];
|
|
393
|
-
}
|
|
394
|
-
this.emit('plugin-unloaded', pluginName);
|
|
395
|
-
}
|
|
396
|
-
catch (error) {
|
|
397
|
-
this.logger.error(`Failed to unload plugin "${pluginName}":`, error);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* 重新加载插件
|
|
402
|
-
* @param pluginName 插件名称
|
|
403
|
-
*/
|
|
404
|
-
async reloadPlugin(pluginName) {
|
|
405
|
-
// this.logger.info(`Reloading plugin "${pluginName}"...`);
|
|
406
|
-
// 1. 递归卸载插件及其依赖者
|
|
407
|
-
await this.unloadPlugin(pluginName);
|
|
408
|
-
// 2. 重新加载该插件
|
|
409
|
-
// unloadPlugin 已经将状态设置为 PENDING,所以 loadSinglePlugin 可以直接工作
|
|
410
|
-
const success = await this.loadSinglePlugin(pluginName);
|
|
411
|
-
if (success) {
|
|
412
|
-
// this.logger.info(`Plugin "${pluginName}" reloaded successfully.`);
|
|
413
|
-
this.emit('plugin-reloaded', pluginName);
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
this.logger.error(`Failed to reload plugin "${pluginName}". It may have unmet dependencies.`);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* 监听插件文件变化
|
|
421
|
-
* @param pluginName 插件名称
|
|
422
|
-
* @param pluginPath 插件路径
|
|
423
|
-
*/
|
|
424
|
-
watchPlugin(pluginName, pluginPath) {
|
|
425
|
-
if (this.pluginWatchers[pluginName]) {
|
|
426
|
-
return; // Already watching
|
|
427
|
-
}
|
|
428
|
-
const logger = new logger_1.Logger('hmr');
|
|
429
|
-
const watcher = chokidar.watch(pluginPath, {
|
|
430
|
-
ignored: /(^|[\/])\../,
|
|
431
|
-
persistent: true,
|
|
432
|
-
ignoreInitial: true,
|
|
433
|
-
awaitWriteFinish: {
|
|
434
|
-
stabilityThreshold: 200,
|
|
435
|
-
pollInterval: 100,
|
|
436
|
-
},
|
|
437
|
-
});
|
|
438
|
-
watcher.on('change', async (changePath) => {
|
|
439
|
-
logger.info(`Plugin file changed: ${changePath}`);
|
|
440
|
-
await this.reloadPlugin(pluginName);
|
|
441
|
-
});
|
|
442
|
-
watcher.on('add', async (changePath) => {
|
|
443
|
-
logger.info(`New file added to plugin ${pluginName}: ${changePath}`);
|
|
444
|
-
await this.reloadPlugin(pluginName);
|
|
445
|
-
});
|
|
446
|
-
watcher.on('unlink', async (changePath) => {
|
|
447
|
-
logger.info(`File removed from plugin ${pluginName}: ${changePath}`);
|
|
448
|
-
await this.reloadPlugin(pluginName);
|
|
449
|
-
});
|
|
450
|
-
this.pluginWatchers[pluginName] = watcher;
|
|
451
|
-
// logger.info(`Watching for changes in plugin: ${pluginName}`);
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* 注册组件
|
|
455
|
-
* @param name 组件名称
|
|
456
|
-
* @param component 组件实例
|
|
457
|
-
*/
|
|
458
53
|
registerComponent(name, component) {
|
|
459
|
-
// if (this.components.hasOwnProperty(name)) {
|
|
460
|
-
// this.logger.warn(`Component "${name}" already registered by plugin "${this.providedComponents[name]}".`);
|
|
461
|
-
// return;
|
|
462
|
-
// }
|
|
463
54
|
this.components[name] = component;
|
|
464
|
-
// this.logger.info(`Component "${name}" registered.`);
|
|
465
55
|
}
|
|
466
|
-
/**
|
|
467
|
-
* 获取组件
|
|
468
|
-
* @param name 组件名称
|
|
469
|
-
* @returns 组件实例
|
|
470
|
-
*/
|
|
471
56
|
getComponent(name) {
|
|
472
57
|
return this.components[name];
|
|
473
58
|
}
|
|
474
|
-
/**
|
|
475
|
-
* 注销组件
|
|
476
|
-
* @param name 组件名称
|
|
477
|
-
*/
|
|
478
59
|
unregisterComponent(name) {
|
|
479
60
|
delete this.components[name];
|
|
480
|
-
// delete this.providedComponents[name];
|
|
481
61
|
}
|
|
482
|
-
/**
|
|
483
|
-
* 注册事件监听器
|
|
484
|
-
* @param event 事件名称
|
|
485
|
-
* @param listener 事件回调函数
|
|
486
|
-
*/
|
|
487
62
|
on(event, listener) {
|
|
488
63
|
if (!this.eventListeners[event]) {
|
|
489
64
|
this.eventListeners[event] = [];
|
|
490
65
|
}
|
|
491
66
|
this.eventListeners[event].push(listener);
|
|
492
67
|
}
|
|
493
|
-
/**
|
|
494
|
-
* 触发事件
|
|
495
|
-
* @param event 事件名称
|
|
496
|
-
* @param args 传递给事件监听器的参数
|
|
497
|
-
*/
|
|
498
68
|
async emit(event, ...args) {
|
|
499
69
|
if (this.eventListeners[event]) {
|
|
500
70
|
for (const listener of this.eventListeners[event]) {
|
|
@@ -507,65 +77,32 @@ class Core {
|
|
|
507
77
|
}
|
|
508
78
|
}
|
|
509
79
|
}
|
|
510
|
-
/**
|
|
511
|
-
* 注册全局中间件
|
|
512
|
-
* @param name 中间件名称
|
|
513
|
-
* @param middleware 中间件函数
|
|
514
|
-
* @returns Core 实例
|
|
515
|
-
*/
|
|
516
80
|
use(name, middleware) {
|
|
517
81
|
this.globalMiddlewares[name] = middleware;
|
|
518
82
|
return this;
|
|
519
83
|
}
|
|
520
|
-
/**
|
|
521
|
-
* 注册路由
|
|
522
|
-
* @param path 路由路径
|
|
523
|
-
* @returns Route 实例
|
|
524
|
-
*/
|
|
525
84
|
route(path) {
|
|
526
85
|
const route = new route_1.Route(path);
|
|
527
86
|
this.routes[path] = route;
|
|
528
87
|
return route;
|
|
529
88
|
}
|
|
530
|
-
/**
|
|
531
|
-
* 注册 Hook 钩子
|
|
532
|
-
* @param name Hook 点名称
|
|
533
|
-
* @param hookname 钩子名称
|
|
534
|
-
* @param callback 钩子函数
|
|
535
|
-
*/
|
|
536
89
|
hook(name, hookname, callback) {
|
|
537
90
|
if (!this.hooks[name]) {
|
|
538
91
|
this.hooks[name] = new hook_1.Hook(name);
|
|
539
92
|
}
|
|
540
93
|
this.hooks[name].add(hookname, callback);
|
|
541
94
|
}
|
|
542
|
-
/**
|
|
543
|
-
* 消除 Hook 钩子
|
|
544
|
-
*/
|
|
545
95
|
unhook(name, hookname) {
|
|
546
96
|
if (this.hooks[name]) {
|
|
547
97
|
this.hooks[name].remove(hookname);
|
|
548
98
|
}
|
|
549
99
|
}
|
|
550
|
-
/**
|
|
551
|
-
* 执行 Hook 钩子
|
|
552
|
-
* @param name Hook 点名称
|
|
553
|
-
* @param args 参数
|
|
554
|
-
* @return Promise<any[]>
|
|
555
|
-
*/
|
|
556
100
|
async hookExecute(name, ...args) {
|
|
557
101
|
if (this.hooks[name]) {
|
|
558
102
|
return await this.hooks[name].trigger(...args);
|
|
559
103
|
}
|
|
560
104
|
return [];
|
|
561
105
|
}
|
|
562
|
-
/**
|
|
563
|
-
* 执行路由
|
|
564
|
-
* @param pathname 需要匹配的URL
|
|
565
|
-
* @param session 当前会话
|
|
566
|
-
* @param queryParams URL参数
|
|
567
|
-
* @returns 是否匹配成功
|
|
568
|
-
*/
|
|
569
106
|
async executeRoute(pathname, session, queryParams) {
|
|
570
107
|
for (const routePath in this.routes) {
|
|
571
108
|
const route = this.routes[routePath];
|
|
@@ -585,11 +122,6 @@ class Core {
|
|
|
585
122
|
}
|
|
586
123
|
return false;
|
|
587
124
|
}
|
|
588
|
-
/**
|
|
589
|
-
* 获取路由
|
|
590
|
-
* @param path 匹配路径
|
|
591
|
-
* @return Route 实例或 false
|
|
592
|
-
*/
|
|
593
125
|
getRoute(path) {
|
|
594
126
|
for (const routePath in this.routes) {
|
|
595
127
|
const route = this.routes[routePath];
|
package/dist/logger.d.ts
CHANGED
package/dist/logger.js
CHANGED
|
@@ -23,15 +23,22 @@ class Logger {
|
|
|
23
23
|
}
|
|
24
24
|
constructor(title) {
|
|
25
25
|
this.title = title;
|
|
26
|
-
this.titleColor = this.
|
|
26
|
+
this.titleColor = this.getColorByName(this.title);
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
getColorByName(name) {
|
|
29
29
|
const availableColors = [
|
|
30
30
|
picocolors_1.default.red, picocolors_1.default.green, picocolors_1.default.yellow, picocolors_1.default.blue,
|
|
31
|
-
picocolors_1.default.magenta, picocolors_1.default.cyan, picocolors_1.default.white,
|
|
31
|
+
picocolors_1.default.magenta, picocolors_1.default.cyan, picocolors_1.default.white,
|
|
32
|
+
picocolors_1.default.redBright, picocolors_1.default.greenBright, picocolors_1.default.yellowBright,
|
|
33
|
+
picocolors_1.default.blueBright, picocolors_1.default.magentaBright, picocolors_1.default.cyanBright
|
|
32
34
|
];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
// Simple hash function to ensure consistent color for the same title
|
|
36
|
+
let hash = 0;
|
|
37
|
+
for (let i = 0; i < name.length; i++) {
|
|
38
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
39
|
+
}
|
|
40
|
+
const index = Math.abs(hash % availableColors.length);
|
|
41
|
+
return availableColors[index];
|
|
35
42
|
}
|
|
36
43
|
getTimestamp() {
|
|
37
44
|
const now = new Date();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yumerijs/core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Core module for yumeri",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
"typescript": "^5.8.2"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@yumerijs/loader": "^2.0.0",
|
|
38
37
|
"chokidar": "^4.0.3",
|
|
39
38
|
"formidable": "^3.5.4",
|
|
40
39
|
"js-yaml": "^4.1.0",
|