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