bun_plugins 1.2.6 → 1.2.7
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/LICENSE +9 -0
- package/README.md +127 -3
- package/dist/examples/action-registry-declarative.d.ts +14 -0
- package/dist/examples/action-registry-declarative.d.ts.map +1 -0
- package/dist/examples/action-registry-declarative.js +130 -0
- package/dist/examples/action-registry-declarative.js.map +1 -0
- package/dist/examples/action-registry-example.d.ts +31 -0
- package/dist/examples/action-registry-example.d.ts.map +1 -0
- package/dist/examples/action-registry-example.js +84 -0
- package/dist/examples/action-registry-example.js.map +1 -0
- package/dist/examples/index-declarative.d.ts +32 -0
- package/dist/examples/index-declarative.d.ts.map +1 -0
- package/dist/examples/index-declarative.js +206 -0
- package/dist/examples/index-declarative.js.map +1 -0
- package/dist/examples/logger-declarative.d.ts +109 -0
- package/dist/examples/logger-declarative.d.ts.map +1 -0
- package/dist/examples/logger-declarative.js +330 -0
- package/dist/examples/logger-declarative.js.map +1 -0
- package/dist/examples/logger-simple.d.ts +8 -0
- package/dist/examples/logger-simple.d.ts.map +1 -0
- package/dist/examples/logger-simple.js +165 -0
- package/dist/examples/logger-simple.js.map +1 -0
- package/dist/examples/server-declarative.d.ts +46 -0
- package/dist/examples/server-declarative.d.ts.map +1 -0
- package/dist/examples/server-declarative.js +205 -0
- package/dist/examples/server-declarative.js.map +1 -0
- package/dist/examples/shared/action-registry.d.ts +45 -0
- package/dist/examples/shared/action-registry.d.ts.map +1 -0
- package/dist/examples/shared/action-registry.js +65 -0
- package/dist/examples/shared/action-registry.js.map +1 -0
- package/dist/examples/shared/plugin-builder.d.ts +41 -0
- package/dist/examples/shared/plugin-builder.d.ts.map +1 -0
- package/dist/examples/shared/plugin-builder.js +85 -0
- package/dist/examples/shared/plugin-builder.js.map +1 -0
- package/dist/examples/storage-declarative.d.ts +13 -0
- package/dist/examples/storage-declarative.d.ts.map +1 -0
- package/dist/examples/storage-declarative.js +203 -0
- package/dist/examples/storage-declarative.js.map +1 -0
- package/dist/plugins/ActionRegistryPlugin.d.ts +14 -0
- package/dist/plugins/ActionRegistryPlugin.d.ts.map +1 -0
- package/dist/plugins/ActionRegistryPlugin.js +52 -0
- package/dist/plugins/ActionRegistryPlugin.js.map +1 -0
- package/dist/plugins/DynamicJSActionsPlugin.d.ts +21 -0
- package/dist/plugins/DynamicJSActionsPlugin.d.ts.map +1 -0
- package/dist/plugins/DynamicJSActionsPlugin.js +57 -0
- package/dist/plugins/DynamicJSActionsPlugin.js.map +1 -0
- package/dist/plugins/DynamicMathActionsPlugin.d.ts +22 -0
- package/dist/plugins/DynamicMathActionsPlugin.d.ts.map +1 -0
- package/dist/plugins/DynamicMathActionsPlugin.js +64 -0
- package/dist/plugins/DynamicMathActionsPlugin.js.map +1 -0
- package/dist/plugins/DynamicTextActionsPlugin.d.ts +22 -0
- package/dist/plugins/DynamicTextActionsPlugin.d.ts.map +1 -0
- package/dist/plugins/DynamicTextActionsPlugin.js +58 -0
- package/dist/plugins/DynamicTextActionsPlugin.js.map +1 -0
- package/dist/plugins/DynamicUtilityActionsPlugin.d.ts +22 -0
- package/dist/plugins/DynamicUtilityActionsPlugin.d.ts.map +1 -0
- package/dist/plugins/DynamicUtilityActionsPlugin.js +75 -0
- package/dist/plugins/DynamicUtilityActionsPlugin.js.map +1 -0
- package/dist/plugins/ExamplePlugin.d.ts +3 -0
- package/dist/plugins/ExamplePlugin.d.ts.map +1 -0
- package/dist/plugins/ExamplePlugin.js +24 -0
- package/dist/plugins/ExamplePlugin.js.map +1 -0
- package/dist/plugins/LifecycleDemoPlugin.d.ts +20 -0
- package/dist/plugins/LifecycleDemoPlugin.d.ts.map +1 -0
- package/dist/plugins/LifecycleDemoPlugin.js +34 -0
- package/dist/plugins/LifecycleDemoPlugin.js.map +1 -0
- package/dist/plugins/MathPlugin.d.ts +16 -0
- package/dist/plugins/MathPlugin.d.ts.map +1 -0
- package/dist/plugins/MathPlugin.js +30 -0
- package/dist/plugins/MathPlugin.js.map +1 -0
- package/dist/plugins/MyJSPlugin.d.ts +7 -0
- package/dist/plugins/MyJSPlugin.d.ts.map +1 -0
- package/dist/plugins/MyJSPlugin.js +12 -0
- package/dist/plugins/MyJSPlugin.js.map +1 -0
- package/dist/plugins/TypedExamplePlugin.d.ts +10 -0
- package/dist/plugins/TypedExamplePlugin.d.ts.map +1 -0
- package/dist/plugins/TypedExamplePlugin.js +61 -0
- package/dist/plugins/TypedExamplePlugin.js.map +1 -0
- package/dist/plugins/arktype/index.d.ts +8 -0
- package/dist/plugins/arktype/index.d.ts.map +1 -0
- package/dist/plugins/arktype/index.js +25 -0
- package/dist/plugins/arktype/index.js.map +1 -0
- package/dist/src/Plugin.d.ts +28 -0
- package/dist/src/Plugin.d.ts.map +1 -0
- package/dist/src/Plugin.js +36 -0
- package/dist/src/Plugin.js.map +1 -0
- package/dist/{PluginManager.d.ts → src/PluginManager.d.ts} +33 -10
- package/dist/src/PluginManager.d.ts.map +1 -0
- package/dist/{PluginManager.js → src/PluginManager.js} +245 -193
- package/dist/src/PluginManager.js.map +1 -0
- package/dist/{index.d.ts → src/index.d.ts} +2 -1
- package/dist/src/index.d.ts.map +1 -0
- package/dist/{index.js → src/index.js} +2 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/logger/LoggerAdapter.d.ts +77 -0
- package/dist/src/logger/LoggerAdapter.d.ts.map +1 -0
- package/dist/src/logger/LoggerAdapter.js +242 -0
- package/dist/src/logger/LoggerAdapter.js.map +1 -0
- package/dist/src/logger/LoggerFactory.d.ts +73 -0
- package/dist/src/logger/LoggerFactory.d.ts.map +1 -0
- package/dist/src/logger/LoggerFactory.js +99 -0
- package/dist/src/logger/LoggerFactory.js.map +1 -0
- package/dist/src/logger/index.d.ts +3 -0
- package/dist/src/logger/index.d.ts.map +1 -0
- package/dist/src/logger/index.js +3 -0
- package/dist/src/logger/index.js.map +1 -0
- package/dist/{managers → src/managers}/ContextFactory.d.ts +1 -1
- package/dist/src/managers/ContextFactory.d.ts.map +1 -0
- package/dist/{managers → src/managers}/ContextFactory.js +51 -26
- package/dist/src/managers/ContextFactory.js.map +1 -0
- package/dist/src/managers/DependencyManager.d.ts.map +1 -0
- package/dist/{managers → src/managers}/DependencyManager.js +7 -5
- package/dist/src/managers/DependencyManager.js.map +1 -0
- package/dist/{managers → src/managers}/HooksManager.d.ts +4 -1
- package/dist/src/managers/HooksManager.d.ts.map +1 -0
- package/dist/{managers → src/managers}/HooksManager.js +36 -8
- package/dist/src/managers/HooksManager.js.map +1 -0
- package/dist/src/managers/ResourceManager.d.ts.map +1 -0
- package/dist/{managers → src/managers}/ResourceManager.js +3 -3
- package/dist/src/managers/ResourceManager.js.map +1 -0
- package/dist/src/storage/JsonPluginStorage.d.ts +75 -0
- package/dist/src/storage/JsonPluginStorage.d.ts.map +1 -0
- package/dist/src/storage/JsonPluginStorage.js +240 -0
- package/dist/src/storage/JsonPluginStorage.js.map +1 -0
- package/dist/src/types/plugin-registry-base.d.ts +80 -0
- package/dist/src/types/plugin-registry-base.d.ts.map +1 -0
- package/dist/src/types/plugin-registry-base.js +6 -0
- package/dist/src/types/plugin-registry-base.js.map +1 -0
- package/dist/{types.d.ts → src/types.d.ts} +60 -19
- package/dist/src/types.d.ts.map +1 -0
- package/dist/{types.js → src/types.js} +6 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/errorParser.d.ts.map +1 -0
- package/dist/{utils → src/utils}/errorParser.js +2 -5
- package/dist/src/utils/errorParser.js.map +1 -0
- package/dist/src/utils/pluginValidator.d.ts +17 -0
- package/dist/src/utils/pluginValidator.d.ts.map +1 -0
- package/dist/{utils → src/utils}/pluginValidator.js +11 -14
- package/dist/src/utils/pluginValidator.js.map +1 -0
- package/dist/src/utils/security.d.ts.map +1 -0
- package/dist/src/utils/security.js.map +1 -0
- package/dist/src/worker/WorkerRunner.d.ts.map +1 -0
- package/dist/{worker → src/worker}/WorkerRunner.js +38 -13
- package/dist/src/worker/WorkerRunner.js.map +1 -0
- package/package.json +9 -6
- package/dist/PluginManager.d.ts.map +0 -1
- package/dist/PluginManager.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/managers/ContextFactory.d.ts.map +0 -1
- package/dist/managers/ContextFactory.js.map +0 -1
- package/dist/managers/DependencyManager.d.ts.map +0 -1
- package/dist/managers/DependencyManager.js.map +0 -1
- package/dist/managers/HooksManager.d.ts.map +0 -1
- package/dist/managers/HooksManager.js.map +0 -1
- package/dist/managers/ResourceManager.d.ts.map +0 -1
- package/dist/managers/ResourceManager.js.map +0 -1
- package/dist/storage/JsonPluginStorage.d.ts +0 -14
- package/dist/storage/JsonPluginStorage.d.ts.map +0 -1
- package/dist/storage/JsonPluginStorage.js +0 -63
- package/dist/storage/JsonPluginStorage.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils/errorParser.d.ts.map +0 -1
- package/dist/utils/errorParser.js.map +0 -1
- package/dist/utils/pluginValidator.d.ts +0 -93
- package/dist/utils/pluginValidator.d.ts.map +0 -1
- package/dist/utils/pluginValidator.js.map +0 -1
- package/dist/utils/security.d.ts.map +0 -1
- package/dist/utils/security.js.map +0 -1
- package/dist/worker/WorkerRunner.d.ts.map +0 -1
- package/dist/worker/WorkerRunner.js.map +0 -1
- /package/dist/{managers → src/managers}/DependencyManager.d.ts +0 -0
- /package/dist/{managers → src/managers}/ResourceManager.d.ts +0 -0
- /package/dist/{utils → src/utils}/errorParser.d.ts +0 -0
- /package/dist/{utils → src/utils}/security.d.ts +0 -0
- /package/dist/{utils → src/utils}/security.js +0 -0
- /package/dist/{worker → src/worker}/WorkerRunner.d.ts +0 -0
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { readdir, mkdir } from "node:fs/promises";
|
|
3
|
-
import { join
|
|
3
|
+
import { join } from "node:path";
|
|
4
4
|
import { watch, existsSync } from "node:fs";
|
|
5
5
|
import { WorkerMessageType, PluginPermission, RPCMethod, HookType } from "./types";
|
|
6
6
|
import { validatePlugin } from "./utils/pluginValidator";
|
|
7
7
|
import { JsonPluginStorage } from "./storage/JsonPluginStorage";
|
|
8
|
-
import semver from "semver";
|
|
8
|
+
import * as semver from "semver";
|
|
9
9
|
import { ResourceManager } from "./managers/ResourceManager";
|
|
10
10
|
import { DependencyManager } from "./managers/DependencyManager";
|
|
11
11
|
import { HooksManager } from "./managers/HooksManager";
|
|
12
12
|
import { createPluginContext } from "./managers/ContextFactory";
|
|
13
13
|
import { errorParser } from "./utils/errorParser";
|
|
14
14
|
import { checkNetworkPermission, checkPermission as checkGeneralPermission } from "./utils/security";
|
|
15
|
+
import { logger } from "./logger";
|
|
15
16
|
export class PluginManager extends EventEmitter {
|
|
16
17
|
plugins = new Map();
|
|
17
18
|
availablePlugins = new Map();
|
|
18
|
-
|
|
19
|
+
configs = new Map();
|
|
20
|
+
pluginApis = new Map();
|
|
21
|
+
pluginFilePaths = new Map();
|
|
19
22
|
// Modules
|
|
20
23
|
resources;
|
|
21
24
|
dependencyManager;
|
|
@@ -25,8 +28,6 @@ export class PluginManager extends EventEmitter {
|
|
|
25
28
|
pluginLoadTimeout;
|
|
26
29
|
workerFactory;
|
|
27
30
|
workerRunnerPath;
|
|
28
|
-
hotReloadWatcher = null;
|
|
29
|
-
hotReloadTimer = null;
|
|
30
31
|
constructor(storageRoot = join(process.cwd(), "storage"), options) {
|
|
31
32
|
super();
|
|
32
33
|
this.storageRoot = storageRoot;
|
|
@@ -62,9 +63,9 @@ export class PluginManager extends EventEmitter {
|
|
|
62
63
|
throw new Error(`Plugin ${plugin.name} requires host version ${plugin.engines.host}, but found ${this.hostVersion}`);
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
// 1. Prepare Storage
|
|
67
|
-
const storage =
|
|
66
|
+
logger.getLogger("PluginManager").info(`Loading plugin: ${plugin.name} v${plugin.version}`);
|
|
67
|
+
// 1. Prepare Storage (singleton)
|
|
68
|
+
const storage = JsonPluginStorage.getInstance(this.storageRoot, plugin.name);
|
|
68
69
|
// 2. Load & Validate Configuration
|
|
69
70
|
let config = plugin.defaultConfig || {};
|
|
70
71
|
if (plugin.configSchema) {
|
|
@@ -73,8 +74,7 @@ export class PluginManager extends EventEmitter {
|
|
|
73
74
|
}
|
|
74
75
|
catch (e) {
|
|
75
76
|
const error = errorParser(e, `Error in plugin ${plugin.name}`);
|
|
76
|
-
|
|
77
|
-
config = {}; // Reset to empty config on validation failure to prevent downstream errors
|
|
77
|
+
logger.getLogger("PluginManager").warn(`[SafeMode] Using default config, validation failed. Error: ${error.message}`);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
// 3. Initialize Resources
|
|
@@ -84,37 +84,57 @@ export class PluginManager extends EventEmitter {
|
|
|
84
84
|
throw new Error(`Invalid plugin name: ${plugin.name}. Name cannot contain path traversal characters.`);
|
|
85
85
|
}
|
|
86
86
|
// 4. Context Creation
|
|
87
|
-
|
|
87
|
+
this.configs.set(plugin.name, config);
|
|
88
|
+
const context = createPluginContext(this, plugin, this.resources, storage);
|
|
88
89
|
// 5. Setup Hooks with Performance Monitoring (Delegated to HooksManager via Builder)
|
|
89
90
|
if (plugin.setup) {
|
|
90
91
|
try {
|
|
91
|
-
await plugin.setup(this.hooksManager.getBuilder(plugin.name));
|
|
92
|
+
await plugin.setup(this.hooksManager.getBuilder(plugin.name, config));
|
|
92
93
|
}
|
|
93
94
|
catch (e) {
|
|
94
|
-
|
|
95
|
+
logger.getLogger("PluginManager").error(`Error during setup for ${plugin.name}:`, e);
|
|
95
96
|
throw errorParser(e, `Error during setup for ${plugin.name}`);
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
// 6. Lifecycle onLoad
|
|
99
100
|
try {
|
|
100
|
-
|
|
101
|
+
let completed = false;
|
|
102
|
+
let timeoutError = null;
|
|
103
|
+
const timeoutId = setTimeout(() => {
|
|
104
|
+
if (!completed) {
|
|
105
|
+
completed = true;
|
|
106
|
+
timeoutError = new Error(`Plugin ${plugin.name} timed out (${this.pluginLoadTimeout}ms)`);
|
|
107
|
+
}
|
|
108
|
+
}, this.pluginLoadTimeout);
|
|
101
109
|
const start = performance.now();
|
|
102
|
-
|
|
110
|
+
try {
|
|
111
|
+
await plugin.onLoad(context);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
completed = true;
|
|
115
|
+
clearTimeout(timeoutId);
|
|
116
|
+
}
|
|
117
|
+
// Check if timeout triggered
|
|
118
|
+
if (timeoutError) {
|
|
119
|
+
throw timeoutError;
|
|
120
|
+
}
|
|
103
121
|
const duration = performance.now() - start;
|
|
104
122
|
this.plugins.set(plugin.name, plugin);
|
|
105
|
-
|
|
123
|
+
logger.getLogger("PluginManager").info(`Plugin ${plugin.name} loaded successfully in ${duration.toFixed(2)}ms.`);
|
|
106
124
|
// Trigger onStarted after registration if provided
|
|
107
125
|
if (plugin.onStarted) {
|
|
108
126
|
try {
|
|
109
127
|
await plugin.onStarted();
|
|
110
128
|
}
|
|
111
129
|
catch (e) {
|
|
112
|
-
|
|
130
|
+
logger.getLogger("PluginManager").error(`Error in onStarted for ${plugin.name}:`, e);
|
|
113
131
|
}
|
|
114
132
|
}
|
|
133
|
+
// 7. Trigger onStart hooks registered via setup()
|
|
134
|
+
await this.hooksManager.runOnStart();
|
|
115
135
|
}
|
|
116
136
|
catch (error) {
|
|
117
|
-
|
|
137
|
+
logger.getLogger("PluginManager").error(`Failed to load plugin ${plugin.name}:`, error);
|
|
118
138
|
// Cleanup resources directly since plugin is not in this.plugins yet
|
|
119
139
|
this.resources.cleanup(plugin.name, this);
|
|
120
140
|
this.hooksManager.cleanup(plugin.name);
|
|
@@ -124,7 +144,7 @@ export class PluginManager extends EventEmitter {
|
|
|
124
144
|
async registerIsolated(pluginPath, pluginName) {
|
|
125
145
|
return new Promise((resolve, reject) => {
|
|
126
146
|
const workerScript = this.workerRunnerPath;
|
|
127
|
-
|
|
147
|
+
logger.getLogger("PluginManager").info("Worker Path:", workerScript);
|
|
128
148
|
const timeoutId = setTimeout(() => {
|
|
129
149
|
worker.terminate();
|
|
130
150
|
reject(new Error(`Isolated plugin ${pluginName} timed out during loading (${this.pluginLoadTimeout}ms)`));
|
|
@@ -138,7 +158,7 @@ export class PluginManager extends EventEmitter {
|
|
|
138
158
|
worker.terminate();
|
|
139
159
|
return reject(new Error(`Invalid plugin name: ${pluginName}. Name cannot contain path traversal characters.`));
|
|
140
160
|
}
|
|
141
|
-
const storage =
|
|
161
|
+
const storage = JsonPluginStorage.getInstance(this.storageRoot, pluginName);
|
|
142
162
|
const pendingHooks = new Map();
|
|
143
163
|
// Initialize resources
|
|
144
164
|
const res = this.resources.init(pluginName);
|
|
@@ -205,16 +225,28 @@ export class PluginManager extends EventEmitter {
|
|
|
205
225
|
else if (type === HookType.ON_LOAD) {
|
|
206
226
|
this.hooksManager.registerOnLoad(filterRegExp, proxyCallback, pluginName, options?.order);
|
|
207
227
|
}
|
|
228
|
+
else if (type === HookType.ON_START) {
|
|
229
|
+
this.hooksManager.registerOnStart(() => proxyCallback({}), pluginName);
|
|
230
|
+
}
|
|
208
231
|
result = true;
|
|
209
232
|
}
|
|
210
233
|
else if (method === RPCMethod.ManagerGetPlugin) {
|
|
211
234
|
const targetName = args[0];
|
|
212
|
-
|
|
213
|
-
|
|
235
|
+
// Get the registered API
|
|
236
|
+
const api = this.getPluginApi(targetName);
|
|
237
|
+
if (api) {
|
|
238
|
+
result = api;
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
// Fallback to the plugin itself
|
|
242
|
+
const p = this.getPlugin(targetName);
|
|
243
|
+
result = api !== undefined ? api : p || undefined;
|
|
244
|
+
}
|
|
214
245
|
}
|
|
215
246
|
else if (method === RPCMethod.Log) {
|
|
216
247
|
const level = args[0];
|
|
217
|
-
|
|
248
|
+
const [msg, ...restArgs] = args.slice(1);
|
|
249
|
+
logger.getLogger(pluginName)[level](msg, ...restArgs);
|
|
218
250
|
result = true;
|
|
219
251
|
}
|
|
220
252
|
else if (method === RPCMethod.PermissionCheck) {
|
|
@@ -230,13 +262,27 @@ export class PluginManager extends EventEmitter {
|
|
|
230
262
|
const urlStr = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
|
|
231
263
|
checkPermission(PluginPermission.Network, urlStr);
|
|
232
264
|
const response = await fetch(input, init);
|
|
265
|
+
const responseHeaders = {};
|
|
266
|
+
response.headers.forEach((v, k) => { responseHeaders[k] = v; });
|
|
233
267
|
result = {
|
|
234
268
|
status: response.status,
|
|
235
269
|
statusText: response.statusText,
|
|
236
|
-
headers:
|
|
270
|
+
headers: responseHeaders,
|
|
237
271
|
body: await response.text()
|
|
238
272
|
};
|
|
239
273
|
}
|
|
274
|
+
else if (method === RPCMethod.ConfigGet) {
|
|
275
|
+
result = this.getPluginConfig(pluginName);
|
|
276
|
+
}
|
|
277
|
+
else if (method === RPCMethod.StorageReload) {
|
|
278
|
+
await storage.reload();
|
|
279
|
+
result = true;
|
|
280
|
+
}
|
|
281
|
+
else if (method === RPCMethod.ManagerRegisterApi) {
|
|
282
|
+
const [api] = args;
|
|
283
|
+
this.registerApi(pluginName, api);
|
|
284
|
+
result = true;
|
|
285
|
+
}
|
|
240
286
|
worker.postMessage({ id, result });
|
|
241
287
|
}
|
|
242
288
|
catch (e) {
|
|
@@ -263,7 +309,7 @@ export class PluginManager extends EventEmitter {
|
|
|
263
309
|
else if (msg.type === WorkerMessageType.MANIFEST) {
|
|
264
310
|
const { metadata } = msg;
|
|
265
311
|
pluginMetadata = metadata;
|
|
266
|
-
|
|
312
|
+
logger.getLogger("PluginManager").info(`Metadata received for isolated plugin: ${metadata.name}`);
|
|
267
313
|
}
|
|
268
314
|
else if (msg.type === WorkerMessageType.LOAD_SUCCESS) {
|
|
269
315
|
clearTimeout(timeoutId); // Success, clear global timeout
|
|
@@ -286,20 +332,26 @@ export class PluginManager extends EventEmitter {
|
|
|
286
332
|
await new Promise(r => setTimeout(r, 200));
|
|
287
333
|
worker.terminate();
|
|
288
334
|
// Clear pending hooks on unload
|
|
289
|
-
for (const pending of pendingHooks.values()) {
|
|
335
|
+
for (const pending of Array.from(pendingHooks.values())) {
|
|
290
336
|
pending.reject(new Error("Plugin unloaded"));
|
|
291
337
|
}
|
|
292
338
|
pendingHooks.clear();
|
|
293
339
|
},
|
|
294
340
|
};
|
|
295
341
|
this.plugins.set(metadata.name, proxyPlugin);
|
|
296
|
-
|
|
342
|
+
// Registrar el path del archivo del plugin aislado
|
|
343
|
+
this.pluginFilePaths.set(metadata.name, pluginPath);
|
|
344
|
+
// Register API if provided in metadata
|
|
345
|
+
if (metadata.api) {
|
|
346
|
+
this.registerApi(metadata.name, metadata.api);
|
|
347
|
+
}
|
|
348
|
+
logger.getLogger("PluginManager").info(`Isolated Plugin ${metadata.name} loaded in worker.`);
|
|
297
349
|
resolve();
|
|
298
350
|
}
|
|
299
351
|
else if (msg.type === WorkerMessageType.LOAD_ERROR) {
|
|
300
352
|
clearTimeout(timeoutId);
|
|
301
353
|
// Cleanup pending hooks on load error
|
|
302
|
-
for (const pending of pendingHooks.values()) {
|
|
354
|
+
for (const pending of Array.from(pendingHooks.values())) {
|
|
303
355
|
pending.reject(new Error(`Load error: ${msg.error}`));
|
|
304
356
|
}
|
|
305
357
|
pendingHooks.clear();
|
|
@@ -310,9 +362,9 @@ export class PluginManager extends EventEmitter {
|
|
|
310
362
|
worker.addEventListener("message", (event) => rpcHandler(event.data));
|
|
311
363
|
worker.addEventListener("error", (err) => {
|
|
312
364
|
clearTimeout(timeoutId);
|
|
313
|
-
|
|
365
|
+
logger.getLogger("PluginManager").error(`[Isolated:${pluginName}] Worker Error:`, err);
|
|
314
366
|
// Reject all pending hooks on crash
|
|
315
|
-
for (const pending of pendingHooks.values()) {
|
|
367
|
+
for (const pending of Array.from(pendingHooks.values())) {
|
|
316
368
|
pending.reject(new Error("Worker terminated unexpectedly"));
|
|
317
369
|
}
|
|
318
370
|
pendingHooks.clear();
|
|
@@ -320,40 +372,87 @@ export class PluginManager extends EventEmitter {
|
|
|
320
372
|
});
|
|
321
373
|
});
|
|
322
374
|
}
|
|
323
|
-
async unregister(pluginName) {
|
|
375
|
+
async unregister(pluginName, clean = false) {
|
|
324
376
|
const plugin = this.plugins.get(pluginName);
|
|
325
377
|
if (!plugin)
|
|
326
378
|
return;
|
|
327
|
-
for (const [name, p] of this.plugins.entries()) {
|
|
379
|
+
for (const [name, p] of Array.from(this.plugins.entries())) {
|
|
328
380
|
if (p.dependencies && p.dependencies[pluginName]) {
|
|
329
|
-
|
|
381
|
+
logger.getLogger("PluginManager").warn(`Warning: Plugin ${name} depends on ${pluginName} which is being unloaded.`);
|
|
330
382
|
}
|
|
331
383
|
}
|
|
332
384
|
try {
|
|
333
385
|
await plugin.onUnload();
|
|
334
386
|
}
|
|
335
387
|
catch (error) {
|
|
336
|
-
|
|
388
|
+
logger.getLogger("PluginManager").error(`Error unloading plugin ${pluginName}:`, error);
|
|
337
389
|
}
|
|
338
390
|
finally {
|
|
339
391
|
this.plugins.delete(pluginName);
|
|
340
392
|
this.resources.cleanup(pluginName, this);
|
|
341
393
|
this.hooksManager.cleanup(pluginName);
|
|
342
|
-
|
|
394
|
+
this.unregisterApi(pluginName);
|
|
395
|
+
if (clean) {
|
|
396
|
+
this.pluginFilePaths.delete(pluginName);
|
|
397
|
+
}
|
|
398
|
+
logger.getLogger("PluginManager").info(`Plugin ${pluginName} unloaded.`);
|
|
343
399
|
}
|
|
344
400
|
}
|
|
345
401
|
getPlugin(name) {
|
|
346
402
|
return this.plugins.get(name);
|
|
347
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* @param pluginName - Plugin name
|
|
406
|
+
* @returns The absolute path of the plugin file, or undefined if not found
|
|
407
|
+
*/
|
|
408
|
+
getPluginPath(pluginName) {
|
|
409
|
+
return this.pluginFilePaths.get(pluginName);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Get all plugin paths
|
|
413
|
+
* @returns Map with plugin names as keys and their paths as values
|
|
414
|
+
*/
|
|
415
|
+
getAllPluginPaths() {
|
|
416
|
+
return new Map(this.pluginFilePaths);
|
|
417
|
+
}
|
|
418
|
+
getApi(name) {
|
|
419
|
+
// Get the registered API
|
|
420
|
+
return this.getPluginApi(name);
|
|
421
|
+
}
|
|
422
|
+
getPluginConfig(name) {
|
|
423
|
+
return this.configs.get(name) || {};
|
|
424
|
+
}
|
|
425
|
+
setPluginConfig(name, config) {
|
|
426
|
+
this.configs.set(name, config);
|
|
427
|
+
return this.configs.get(name) || {};
|
|
428
|
+
}
|
|
348
429
|
listPlugins() {
|
|
349
430
|
return Array.from(this.plugins.keys());
|
|
350
431
|
}
|
|
432
|
+
// register plugin API
|
|
433
|
+
registerApi(pluginName, api) {
|
|
434
|
+
this.pluginApis.set(pluginName, api);
|
|
435
|
+
}
|
|
436
|
+
// get plugin API
|
|
437
|
+
getPluginApi(pluginName) {
|
|
438
|
+
return this.pluginApis.get(pluginName);
|
|
439
|
+
}
|
|
440
|
+
// unregister plugin API
|
|
441
|
+
unregisterApi(pluginName) {
|
|
442
|
+
this.pluginApis.delete(pluginName);
|
|
443
|
+
}
|
|
351
444
|
emit(eventName, ...args) {
|
|
352
445
|
return super.emit(eventName, ...args);
|
|
353
446
|
}
|
|
354
447
|
on(eventName, listener) {
|
|
355
448
|
return super.on(eventName, listener);
|
|
356
449
|
}
|
|
450
|
+
once(eventName, listener) {
|
|
451
|
+
return super.once(eventName, listener);
|
|
452
|
+
}
|
|
453
|
+
off(eventName, listener) {
|
|
454
|
+
return super.off(eventName, listener);
|
|
455
|
+
}
|
|
357
456
|
async loadPluginsFromDirectory(directoryPath = join(process.cwd(), "plugins")) {
|
|
358
457
|
try {
|
|
359
458
|
const globalConfigPath = join(this.storageRoot, "plugins.json");
|
|
@@ -366,38 +465,64 @@ export class PluginManager extends EventEmitter {
|
|
|
366
465
|
}
|
|
367
466
|
}
|
|
368
467
|
catch (e) {
|
|
369
|
-
|
|
468
|
+
logger.getLogger("PluginManager").warn("Failed to load global plugin config", e);
|
|
370
469
|
}
|
|
371
|
-
const
|
|
470
|
+
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
372
471
|
this.availablePlugins.clear();
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
472
|
+
// Clear the file paths when reloading the directory
|
|
473
|
+
this.pluginFilePaths.clear();
|
|
474
|
+
for (const entry of entries) {
|
|
475
|
+
let fullPath = null;
|
|
476
|
+
if (entry.isDirectory()) {
|
|
477
|
+
const pluginDir = join(directoryPath, entry.name);
|
|
478
|
+
const pkgPath = join(pluginDir, "package.json");
|
|
479
|
+
if (existsSync(pkgPath)) {
|
|
480
|
+
// Auto-install dependencies if node_modules missing
|
|
481
|
+
await this.ensureDependenciesInstalled(pluginDir);
|
|
482
|
+
fullPath = pluginDir;
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
const distPath = join(pluginDir, "dist");
|
|
486
|
+
if (existsSync(distPath)) {
|
|
487
|
+
fullPath = distPath;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
else if ((entry.name.endsWith(".ts") || entry.name.endsWith(".js")) && !entry.name.endsWith(".d.ts")) {
|
|
492
|
+
fullPath = join(directoryPath, entry.name);
|
|
493
|
+
}
|
|
494
|
+
if (fullPath) {
|
|
376
495
|
try {
|
|
377
|
-
|
|
496
|
+
// Use cache-busting for hot reload
|
|
497
|
+
const query = `?update=${Date.now()}`;
|
|
498
|
+
const module = await import(fullPath + query);
|
|
378
499
|
for (const key in module) {
|
|
379
500
|
const ExportedItem = module[key];
|
|
380
501
|
const validation = validatePlugin(ExportedItem);
|
|
381
502
|
if (validation.valid) {
|
|
382
503
|
this.availablePlugins.set(validation.plugin.name, validation.plugin);
|
|
504
|
+
// Registrar la ubicación del archivo del plugin
|
|
505
|
+
this.pluginFilePaths.set(validation.plugin.name, fullPath);
|
|
506
|
+
logger.getLogger("PluginManager").info(`[PluginManager] Plugin file path registered: ${validation.plugin.name} -> ${fullPath}`);
|
|
383
507
|
}
|
|
384
508
|
else {
|
|
385
|
-
|
|
509
|
+
const errorMsg = validation.error;
|
|
510
|
+
logger.getLogger("PluginManager").warn(`Skipping invalid plugin item in ${entry.name}: ${errorMsg}`);
|
|
386
511
|
}
|
|
387
512
|
}
|
|
388
513
|
}
|
|
389
514
|
catch (err) {
|
|
390
|
-
|
|
515
|
+
logger.getLogger("PluginManager").error(`Error importing ${fullPath}:`, err);
|
|
391
516
|
}
|
|
392
517
|
}
|
|
393
518
|
}
|
|
394
519
|
const pluginsToLoad = [];
|
|
395
|
-
for (const plugin of this.availablePlugins.values()) {
|
|
520
|
+
for (const plugin of Array.from(this.availablePlugins.values())) {
|
|
396
521
|
if (!disabledPlugins.includes(plugin.name)) {
|
|
397
522
|
pluginsToLoad.push(plugin);
|
|
398
523
|
}
|
|
399
524
|
else {
|
|
400
|
-
|
|
525
|
+
logger.getLogger("PluginManager").info(`Plugin ${plugin.name} is disabled.`);
|
|
401
526
|
}
|
|
402
527
|
}
|
|
403
528
|
try {
|
|
@@ -408,7 +533,7 @@ export class PluginManager extends EventEmitter {
|
|
|
408
533
|
if (plugin.dependencies) {
|
|
409
534
|
for (const dep of Object.keys(plugin.dependencies)) {
|
|
410
535
|
if (!this.plugins.has(dep)) {
|
|
411
|
-
|
|
536
|
+
logger.getLogger("PluginManager").warn(`Skipping ${plugin.name}: Dependency ${dep} failed to load or is missing.`);
|
|
412
537
|
dependenciesOk = false;
|
|
413
538
|
break;
|
|
414
539
|
}
|
|
@@ -417,13 +542,18 @@ export class PluginManager extends EventEmitter {
|
|
|
417
542
|
if (!dependenciesOk)
|
|
418
543
|
continue;
|
|
419
544
|
try {
|
|
420
|
-
if (
|
|
545
|
+
if (this.plugins.has(plugin.name)) {
|
|
546
|
+
logger.getLogger("PluginManager").info(`Updating plugin: ${plugin.name}`);
|
|
547
|
+
await this.reloadPlugin(plugin.name);
|
|
548
|
+
loadedPlugins.push(plugin);
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
421
551
|
await this.register(plugin);
|
|
422
552
|
loadedPlugins.push(plugin);
|
|
423
553
|
}
|
|
424
554
|
}
|
|
425
555
|
catch (e) {
|
|
426
|
-
|
|
556
|
+
logger.getLogger("PluginManager").error(`Failed to load/update ${plugin.name}:`, e);
|
|
427
557
|
}
|
|
428
558
|
}
|
|
429
559
|
for (const plugin of loadedPlugins) {
|
|
@@ -432,23 +562,23 @@ export class PluginManager extends EventEmitter {
|
|
|
432
562
|
await plugin.onStarted();
|
|
433
563
|
}
|
|
434
564
|
catch (e) {
|
|
435
|
-
|
|
565
|
+
logger.getLogger("PluginManager").error(`Error in onStarted for ${plugin.name}:`, e);
|
|
436
566
|
}
|
|
437
567
|
}
|
|
438
568
|
}
|
|
439
569
|
}
|
|
440
570
|
catch (e) {
|
|
441
|
-
|
|
571
|
+
logger.getLogger("PluginManager").error("Failed to resolve plugin dependencies or load plugins:", e);
|
|
442
572
|
}
|
|
443
573
|
}
|
|
444
574
|
catch (error) {
|
|
445
|
-
|
|
575
|
+
logger.getLogger("PluginManager").error(`Failed to load plugins from ${directoryPath}`, error);
|
|
446
576
|
}
|
|
447
577
|
}
|
|
448
578
|
async disablePlugin(name) {
|
|
449
579
|
await this.unregister(name);
|
|
450
580
|
await this.updatePluginState(name, true);
|
|
451
|
-
|
|
581
|
+
logger.getLogger("PluginManager").info(`Plugin ${name} disabled.`);
|
|
452
582
|
}
|
|
453
583
|
async enablePlugin(name) {
|
|
454
584
|
await this.updatePluginState(name, false);
|
|
@@ -458,7 +588,7 @@ export class PluginManager extends EventEmitter {
|
|
|
458
588
|
if (plugin.dependencies) {
|
|
459
589
|
for (const dep of Object.keys(plugin.dependencies)) {
|
|
460
590
|
if (!this.plugins.has(dep)) {
|
|
461
|
-
|
|
591
|
+
logger.getLogger("PluginManager").warn(`Cannot enable ${name}: Dependency ${dep} is not loaded.`);
|
|
462
592
|
return;
|
|
463
593
|
}
|
|
464
594
|
}
|
|
@@ -466,19 +596,33 @@ export class PluginManager extends EventEmitter {
|
|
|
466
596
|
await this.register(plugin);
|
|
467
597
|
}
|
|
468
598
|
else {
|
|
469
|
-
|
|
599
|
+
logger.getLogger("PluginManager").warn(`Plugin ${name} enabled but not found in available plugins.`);
|
|
470
600
|
}
|
|
471
601
|
}
|
|
472
602
|
}
|
|
473
603
|
async reloadPlugin(name) {
|
|
474
|
-
const
|
|
475
|
-
if (
|
|
476
|
-
|
|
604
|
+
const oldPlugin = this.plugins.get(name);
|
|
605
|
+
if (oldPlugin) {
|
|
606
|
+
logger.getLogger("PluginManager").info(`Reloading plugin ${name}...`);
|
|
477
607
|
await this.unregister(name);
|
|
478
608
|
}
|
|
479
609
|
const definition = this.availablePlugins.get(name);
|
|
480
610
|
if (definition) {
|
|
481
611
|
await this.register(definition);
|
|
612
|
+
const newPlugin = this.plugins.get(name);
|
|
613
|
+
// If the new version has onReload, call it
|
|
614
|
+
if (newPlugin && newPlugin.onReload) {
|
|
615
|
+
// We need a context for onReload as well
|
|
616
|
+
const storage = JsonPluginStorage.getInstance(this.storageRoot, name);
|
|
617
|
+
const context = createPluginContext(this, newPlugin, this.resources, storage);
|
|
618
|
+
try {
|
|
619
|
+
await newPlugin.onReload(context);
|
|
620
|
+
}
|
|
621
|
+
catch (e) {
|
|
622
|
+
logger.getLogger("PluginManager").error(`Error in onReload for ${name}:`, e);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
this.emit("plugin:updated", { name: definition.name, version: definition.version });
|
|
482
626
|
}
|
|
483
627
|
else {
|
|
484
628
|
throw new Error(`Plugin ${name} not found in available plugins.`);
|
|
@@ -504,7 +648,7 @@ export class PluginManager extends EventEmitter {
|
|
|
504
648
|
await Bun.write(globalConfigPath, JSON.stringify(config, null, 2));
|
|
505
649
|
}
|
|
506
650
|
catch (e) {
|
|
507
|
-
|
|
651
|
+
logger.getLogger("PluginManager").error("Failed to update plugin state", e);
|
|
508
652
|
}
|
|
509
653
|
}
|
|
510
654
|
async runOnResolve(args) {
|
|
@@ -513,158 +657,34 @@ export class PluginManager extends EventEmitter {
|
|
|
513
657
|
async runOnLoad(args) {
|
|
514
658
|
return this.hooksManager.runOnLoad(args);
|
|
515
659
|
}
|
|
660
|
+
// Bun-like Aliases
|
|
661
|
+
async use(plugin) { return this.register(plugin); }
|
|
662
|
+
async plugin(plugin) { return this.register(plugin); }
|
|
663
|
+
async remove(pluginName) { return this.unregister(pluginName); }
|
|
516
664
|
toBunPlugin() {
|
|
517
665
|
return this.hooksManager.toBunPlugin();
|
|
518
666
|
}
|
|
667
|
+
hotReloadTimer = null;
|
|
519
668
|
enableHotReload(pluginDir) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
this.hotReloadWatcher.close();
|
|
523
|
-
}
|
|
524
|
-
// Initial scan to build file map
|
|
525
|
-
this.scanAndTrackPlugins(pluginDir);
|
|
526
|
-
console.log(`[HotReload] Watching ${pluginDir} for changes...`);
|
|
527
|
-
this.hotReloadWatcher = watch(pluginDir, { recursive: true }, async (event, filename) => {
|
|
669
|
+
logger.getLogger("PluginManager").info(`[HotReload] Watching ${pluginDir} for changes...`);
|
|
670
|
+
watch(pluginDir, { recursive: true }, async (event, filename) => {
|
|
528
671
|
if (!filename || (!filename.endsWith(".ts") && !filename.endsWith(".js")))
|
|
529
672
|
return;
|
|
530
|
-
if (filename.endsWith(".d.ts"))
|
|
531
|
-
return; // Skip type definitions
|
|
532
|
-
const fullPath = join(pluginDir, filename);
|
|
533
673
|
if (this.hotReloadTimer)
|
|
534
674
|
clearTimeout(this.hotReloadTimer);
|
|
535
675
|
this.hotReloadTimer = setTimeout(async () => {
|
|
536
|
-
|
|
676
|
+
logger.getLogger("PluginManager").info(`[HotReload] Change detected in ${filename}. Re-scanning plugins...`);
|
|
677
|
+
await this.loadPluginsFromDirectory(pluginDir);
|
|
537
678
|
}, 300);
|
|
538
679
|
});
|
|
539
680
|
}
|
|
540
|
-
async scanAndTrackPlugins(pluginDir) {
|
|
541
|
-
this.pluginFiles.clear();
|
|
542
|
-
try {
|
|
543
|
-
const files = await readdir(pluginDir);
|
|
544
|
-
for (const file of files) {
|
|
545
|
-
if ((file.endsWith(".ts") || file.endsWith(".js")) && !file.endsWith(".d.ts")) {
|
|
546
|
-
const fullPath = join(pluginDir, file);
|
|
547
|
-
try {
|
|
548
|
-
const module = await import(fullPath);
|
|
549
|
-
for (const key in module) {
|
|
550
|
-
const ExportedItem = module[key];
|
|
551
|
-
const validation = validatePlugin(ExportedItem);
|
|
552
|
-
if (validation.valid) {
|
|
553
|
-
this.pluginFiles.set(fullPath, validation.plugin.name);
|
|
554
|
-
this.availablePlugins.set(validation.plugin.name, validation.plugin);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
catch (err) {
|
|
559
|
-
console.warn(`[HotReload] Failed to scan ${file}:`, err);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
catch (err) {
|
|
565
|
-
console.error(`[HotReload] Failed to scan plugin directory:`, err);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
async handleFileChange(event, fullPath, filename, pluginDir) {
|
|
569
|
-
const exists = existsSync(fullPath);
|
|
570
|
-
const previousName = this.pluginFiles.get(fullPath);
|
|
571
|
-
if (!exists && previousName) {
|
|
572
|
-
// File was removed - uninstall plugin
|
|
573
|
-
console.log(`[HotReload] Plugin file removed: ${filename}. Unloading plugin ${previousName}...`);
|
|
574
|
-
this.pluginFiles.delete(fullPath);
|
|
575
|
-
if (this.plugins.has(previousName)) {
|
|
576
|
-
await this.unregister(previousName);
|
|
577
|
-
console.log(`[HotReload] Plugin ${previousName} unloaded.`);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
else if (exists && !previousName) {
|
|
581
|
-
// New file added - install plugin
|
|
582
|
-
console.log(event, `[HotReload] New plugin file detected: ${filename}. Loading plugin...`);
|
|
583
|
-
try {
|
|
584
|
-
const module = await import(fullPath);
|
|
585
|
-
for (const key in module) {
|
|
586
|
-
const ExportedItem = module[key];
|
|
587
|
-
const validation = validatePlugin(ExportedItem);
|
|
588
|
-
if (validation.valid) {
|
|
589
|
-
const plugin = validation.plugin;
|
|
590
|
-
this.pluginFiles.set(fullPath, plugin.name);
|
|
591
|
-
this.availablePlugins.set(plugin.name, plugin);
|
|
592
|
-
// Check if not disabled and load it
|
|
593
|
-
const globalConfigPath = join(this.storageRoot, "plugins.json");
|
|
594
|
-
let disabledPlugins = [];
|
|
595
|
-
try {
|
|
596
|
-
const file = Bun.file(globalConfigPath);
|
|
597
|
-
if (await file.exists()) {
|
|
598
|
-
const data = await file.json();
|
|
599
|
-
disabledPlugins = data.disabled || [];
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
catch (e) { /* ignore */ }
|
|
603
|
-
if (!disabledPlugins.includes(plugin.name) && !this.plugins.has(plugin.name)) {
|
|
604
|
-
await this.register(plugin);
|
|
605
|
-
console.log(`[HotReload] Plugin ${plugin.name} loaded.`);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
catch (err) {
|
|
611
|
-
console.error(`[HotReload] Failed to load new plugin ${filename}:`, pluginDir, err);
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
else if (exists && previousName) {
|
|
615
|
-
// File was modified - reload plugin
|
|
616
|
-
console.log(`[HotReload] Plugin file modified: ${filename}. Reloading plugin ${previousName}...`);
|
|
617
|
-
try {
|
|
618
|
-
// Remove from module cache to force reimport
|
|
619
|
-
delete require.cache[fullPath];
|
|
620
|
-
// Unload existing plugin
|
|
621
|
-
if (this.plugins.has(previousName)) {
|
|
622
|
-
await this.unregister(previousName);
|
|
623
|
-
}
|
|
624
|
-
// Reload the plugin
|
|
625
|
-
const module = await import(fullPath);
|
|
626
|
-
for (const key in module) {
|
|
627
|
-
const ExportedItem = module[key];
|
|
628
|
-
const validation = validatePlugin(ExportedItem);
|
|
629
|
-
if (validation.valid) {
|
|
630
|
-
const plugin = validation.plugin;
|
|
631
|
-
this.availablePlugins.set(plugin.name, plugin);
|
|
632
|
-
// Check if not disabled and load it
|
|
633
|
-
const globalConfigPath = join(this.storageRoot, "plugins.json");
|
|
634
|
-
let disabledPlugins = [];
|
|
635
|
-
try {
|
|
636
|
-
const file = Bun.file(globalConfigPath);
|
|
637
|
-
if (await file.exists()) {
|
|
638
|
-
const data = await file.json();
|
|
639
|
-
disabledPlugins = data.disabled || [];
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
catch (e) { /* ignore */ }
|
|
643
|
-
if (!disabledPlugins.includes(plugin.name)) {
|
|
644
|
-
await this.register(plugin);
|
|
645
|
-
console.log(`[HotReload] Plugin ${plugin.name} reloaded.`);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
catch (err) {
|
|
651
|
-
console.error(`[HotReload] Failed to reload plugin ${filename}:`, err);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
disableHotReload() {
|
|
656
|
-
if (this.hotReloadWatcher) {
|
|
657
|
-
this.hotReloadWatcher.close();
|
|
658
|
-
this.hotReloadWatcher = null;
|
|
659
|
-
console.log(`[HotReload] File watcher stopped.`);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
681
|
getMetrics() {
|
|
663
682
|
return {
|
|
664
683
|
totalPlugins: this.plugins.size,
|
|
665
684
|
activePlugins: Array.from(this.plugins.keys()),
|
|
666
685
|
resources: this.resources.getUsageSummary(),
|
|
667
686
|
hooks: {
|
|
687
|
+
onStart: this.hooksManager.getHookCount(HookType.ON_START),
|
|
668
688
|
onResolve: this.hooksManager.getHookCount(HookType.ON_RESOLVE),
|
|
669
689
|
onLoad: this.hooksManager.getHookCount(HookType.ON_LOAD)
|
|
670
690
|
}
|
|
@@ -672,7 +692,7 @@ export class PluginManager extends EventEmitter {
|
|
|
672
692
|
}
|
|
673
693
|
getPluginStatus() {
|
|
674
694
|
const status = {};
|
|
675
|
-
for (const [name, plugin] of this.plugins) {
|
|
695
|
+
for (const [name, plugin] of Array.from(this.plugins)) {
|
|
676
696
|
const resources = this.resources.get(name);
|
|
677
697
|
status[name] = {
|
|
678
698
|
version: plugin.version,
|
|
@@ -686,5 +706,37 @@ export class PluginManager extends EventEmitter {
|
|
|
686
706
|
}
|
|
687
707
|
return status;
|
|
688
708
|
}
|
|
709
|
+
async ensureDependenciesInstalled(pluginPath) {
|
|
710
|
+
const nodeModulesPath = join(pluginPath, "node_modules");
|
|
711
|
+
if (!existsSync(nodeModulesPath)) {
|
|
712
|
+
// 1. Check if we are in production
|
|
713
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
714
|
+
if (isProduction) {
|
|
715
|
+
logger.getLogger("PluginManager").warn(`[PluginManager] Warning: Plugin at ${pluginPath} is missing node_modules. Auto-install is disabled in production.`);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
// 2. Check if 'bun' CLI is even available in the system
|
|
719
|
+
const bunPath = Bun.which("bun");
|
|
720
|
+
if (!bunPath) {
|
|
721
|
+
logger.getLogger("PluginManager").warn(`[PluginManager] Warning: node_modules missing in ${pluginPath}, but 'bun' CLI was not found. Cannot auto-install.`);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
logger.getLogger("PluginManager").info(`[PluginManager] Development mode detected. node_modules missing in ${pluginPath}. Installing deps...`);
|
|
725
|
+
try {
|
|
726
|
+
const proc = Bun.spawn([bunPath, "install"], {
|
|
727
|
+
cwd: pluginPath,
|
|
728
|
+
stdout: "inherit",
|
|
729
|
+
stderr: "inherit",
|
|
730
|
+
});
|
|
731
|
+
const exitCode = await proc.exited;
|
|
732
|
+
if (exitCode !== 0) {
|
|
733
|
+
logger.getLogger("PluginManager").error(`[PluginManager] Failed to install dependencies for ${pluginPath}. Exit code: ${exitCode}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
catch (e) {
|
|
737
|
+
logger.getLogger("PluginManager").error(`[PluginManager] Error running bun install for ${pluginPath}:`, e);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
689
741
|
}
|
|
690
742
|
//# sourceMappingURL=PluginManager.js.map
|