node-pluginsmanager 3.7.1 → 3.8.0
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/README.md +29 -1
- package/lib/cjs/PluginsManager.d.ts +4 -0
- package/lib/cjs/PluginsManager.js +57 -1
- package/lib/cjs/loadSortedPlugins.d.ts +5 -1
- package/lib/cjs/loadSortedPlugins.js +25 -28
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ $ npm install node-pluginsmanager
|
|
|
25
25
|
|
|
26
26
|
* simply manage plugins (extended from [node-pluginsmanager-plugin](https://github.com/Psychopoulet/node-pluginsmanager-plugin)) to interact with specifics hardwares / API / whatever
|
|
27
27
|
* install plugins manually or via github & initialize them
|
|
28
|
+
* load external plugins from absolute directories (outside the plugins directory)
|
|
28
29
|
* update plugins via github
|
|
29
30
|
* uninstall plugins and release there resources
|
|
30
31
|
* run plugins' middlewares for server, to create specifics rules
|
|
@@ -70,9 +71,36 @@ $ npm install node-pluginsmanager
|
|
|
70
71
|
|
|
71
72
|
* ``` on("uninstalled", (pluginName: string, ...unknown[]) => void) : this ``` fires if a plugin is uninstalled
|
|
72
73
|
|
|
74
|
+
## External plugins
|
|
75
|
+
|
|
76
|
+
External plugins are loaded from absolute directories registered **before** `loadAll()`. They are added to `plugins` and initialized like local plugins, but cannot be updated or uninstalled by the manager.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { join } from "node:path";
|
|
80
|
+
import PluginsManager from "node-pluginsmanager";
|
|
81
|
+
|
|
82
|
+
const manager = new PluginsManager({
|
|
83
|
+
"directory": "/path/to/local/plugins",
|
|
84
|
+
"externalResourcesDirectory": "/path/to/resources"
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await manager.addExternalPluginDirectory("/path/to/external/my-plugin");
|
|
88
|
+
await manager.setOrder([ "my-plugin", "other-plugin" ]); // optional
|
|
89
|
+
await manager.loadAll();
|
|
90
|
+
await manager.initAll();
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Rules:
|
|
94
|
+
|
|
95
|
+
* each path must be an **absolute** directory pointing to a plugin root
|
|
96
|
+
* registration must happen **before** `loadAll()`
|
|
97
|
+
* a plugin name cannot exist both locally and externally
|
|
98
|
+
* external plugins cannot be updated (`updateViaGithub`) or uninstalled (`uninstall`)
|
|
99
|
+
* `releaseAll()` and `destroyAll()` still apply to external plugins
|
|
100
|
+
|
|
73
101
|
## Examples
|
|
74
102
|
|
|
75
|
-
[check the TypeScript compilation tests](https://github.com/Psychopoulet/node-pluginsmanager/blob/master/test/typescript/compilation.
|
|
103
|
+
[check the TypeScript compilation tests](https://github.com/Psychopoulet/node-pluginsmanager/blob/master/test/typescript/compilation.cts)
|
|
76
104
|
|
|
77
105
|
## Tests
|
|
78
106
|
|
|
@@ -30,11 +30,14 @@ export default class PluginsManager extends EventEmitter<{
|
|
|
30
30
|
protected _beforeInitAll: tBeforeAllMethodCallback | null;
|
|
31
31
|
protected _logger: tLogger | null;
|
|
32
32
|
protected _orderedPluginsNames: string[];
|
|
33
|
+
protected _externalPluginDirectories: string[];
|
|
34
|
+
protected _externalPluginNames: Set<string>;
|
|
33
35
|
directory: string;
|
|
34
36
|
externalResourcesDirectory: string;
|
|
35
37
|
plugins: Orchestrator[];
|
|
36
38
|
constructor(options?: iPluginManagerOptions);
|
|
37
39
|
getPluginsNames(): string[];
|
|
40
|
+
addExternalPluginDirectory(directory: string): Promise<void>;
|
|
38
41
|
setOrder(pluginsNames: string[]): Promise<void>;
|
|
39
42
|
getOrder(): string[];
|
|
40
43
|
checkAllModules(): Promise<void>;
|
|
@@ -49,6 +52,7 @@ export default class PluginsManager extends EventEmitter<{
|
|
|
49
52
|
releaseAll(...data: unknown[]): Promise<void>;
|
|
50
53
|
getLatestGithubTag(plugin: Orchestrator): Promise<string>;
|
|
51
54
|
installViaGithub(user: string, repo: string, ...data: unknown[]): Promise<Orchestrator>;
|
|
55
|
+
private _isExternalPlugin;
|
|
52
56
|
private _updateWithoutGit;
|
|
53
57
|
updateViaGithub(plugin: Orchestrator, ...data: unknown[]): Promise<Orchestrator>;
|
|
54
58
|
uninstall(plugin: Orchestrator, ...data: unknown[]): Promise<string>;
|
|
@@ -46,6 +46,8 @@ class PluginsManager extends node_events_1.default {
|
|
|
46
46
|
_beforeInitAll;
|
|
47
47
|
_logger;
|
|
48
48
|
_orderedPluginsNames;
|
|
49
|
+
_externalPluginDirectories;
|
|
50
|
+
_externalPluginNames;
|
|
49
51
|
// public
|
|
50
52
|
directory; // plugins location (must be writable). default : join(homedir(), "node-pluginsmanager-plugins")
|
|
51
53
|
externalResourcesDirectory; // external resources locations (sqlite, files, cache, etc...) (must be writable). default : join(homedir(), "node-pluginsmanager-resources")
|
|
@@ -61,6 +63,8 @@ class PluginsManager extends node_events_1.default {
|
|
|
61
63
|
this._beforeLoadAll = null;
|
|
62
64
|
this._beforeInitAll = null;
|
|
63
65
|
this._orderedPluginsNames = [];
|
|
66
|
+
this._externalPluginDirectories = [];
|
|
67
|
+
this._externalPluginNames = new Set();
|
|
64
68
|
this._logger = "function" === typeof options.logger ? options.logger : null;
|
|
65
69
|
// public
|
|
66
70
|
this.plugins = [];
|
|
@@ -73,6 +77,25 @@ class PluginsManager extends node_events_1.default {
|
|
|
73
77
|
return plugin.name;
|
|
74
78
|
});
|
|
75
79
|
}
|
|
80
|
+
addExternalPluginDirectory(directory) {
|
|
81
|
+
return (0, checkAbsoluteDirectory_1.default)("addExternalPluginDirectory/directory", directory).then(() => {
|
|
82
|
+
if (this._externalPluginDirectories.includes(directory)) {
|
|
83
|
+
throw new Error("\"" + directory + "\" is already registered as external plugin directory");
|
|
84
|
+
}
|
|
85
|
+
const pluginName = (0, node_path_1.basename)(directory);
|
|
86
|
+
if (this._externalPluginNames.has(pluginName)) {
|
|
87
|
+
throw new Error("External plugin \"" + pluginName + "\" is already registered");
|
|
88
|
+
}
|
|
89
|
+
return (0, isDirectory_1.default)((0, node_path_1.join)(this.directory, pluginName));
|
|
90
|
+
}).then((existsAsLocal) => {
|
|
91
|
+
const pluginName = (0, node_path_1.basename)(directory);
|
|
92
|
+
if (existsAsLocal) {
|
|
93
|
+
throw new Error("Plugin \"" + pluginName + "\" already exists as a local plugin and cannot be added as external");
|
|
94
|
+
}
|
|
95
|
+
this._externalPluginDirectories.push(directory);
|
|
96
|
+
this._externalPluginNames.add(pluginName);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
76
99
|
// create a forced order to synchronously initialize plugins. not ordered plugins are asynchronously initialized after.
|
|
77
100
|
setOrder(pluginsNames) {
|
|
78
101
|
return (0, checkNonEmptyArray_1.default)("setOrder/pluginsNames", pluginsNames).then(() => {
|
|
@@ -201,7 +224,28 @@ class PluginsManager extends node_events_1.default {
|
|
|
201
224
|
return (0, promises_1.readdir)(this.directory);
|
|
202
225
|
// load all
|
|
203
226
|
}).then((files) => {
|
|
204
|
-
|
|
227
|
+
const conflictingNames = files.filter((fileName) => {
|
|
228
|
+
return this._externalPluginNames.has(fileName);
|
|
229
|
+
});
|
|
230
|
+
if (conflictingNames.length) {
|
|
231
|
+
throw new Error("Cannot load plugins: name conflict between local and external plugins (\""
|
|
232
|
+
+ conflictingNames.join("\", \"") + "\")");
|
|
233
|
+
}
|
|
234
|
+
const pluginsToLoad = [
|
|
235
|
+
...files.map((pluginName) => {
|
|
236
|
+
return {
|
|
237
|
+
"name": pluginName,
|
|
238
|
+
"directory": (0, node_path_1.join)(this.directory, pluginName)
|
|
239
|
+
};
|
|
240
|
+
}),
|
|
241
|
+
...this._externalPluginDirectories.map((directory) => {
|
|
242
|
+
return {
|
|
243
|
+
"name": (0, node_path_1.basename)(directory),
|
|
244
|
+
"directory": directory
|
|
245
|
+
};
|
|
246
|
+
})
|
|
247
|
+
];
|
|
248
|
+
return (0, loadSortedPlugins_1.default)(pluginsToLoad, this.externalResourcesDirectory, this.plugins, this._orderedPluginsNames, this.emit.bind(this), this._logger, ...data);
|
|
205
249
|
// sort plugins
|
|
206
250
|
}).then(() => {
|
|
207
251
|
this.plugins.sort((a, b) => {
|
|
@@ -291,6 +335,9 @@ class PluginsManager extends node_events_1.default {
|
|
|
291
335
|
}).then(() => {
|
|
292
336
|
return (0, checkNonEmptyString_1.default)("installViaGithub/repo", pluginName);
|
|
293
337
|
}).then(() => {
|
|
338
|
+
if (this._externalPluginNames.has(pluginName)) {
|
|
339
|
+
throw new Error("Plugin \"" + pluginName + "\" is already registered as external and cannot be installed");
|
|
340
|
+
}
|
|
294
341
|
const directory = (0, node_path_1.join)(this.directory, pluginName);
|
|
295
342
|
return new Promise((resolve, reject) => {
|
|
296
343
|
(0, checkAbsoluteDirectory_1.default)("installViaGithub/plugindirectory", directory).then(() => {
|
|
@@ -428,6 +475,9 @@ class PluginsManager extends node_events_1.default {
|
|
|
428
475
|
});
|
|
429
476
|
});
|
|
430
477
|
}
|
|
478
|
+
_isExternalPlugin(plugin) {
|
|
479
|
+
return this._externalPluginNames.has(plugin.name);
|
|
480
|
+
}
|
|
431
481
|
_updateWithoutGit(directory, githubRepository) {
|
|
432
482
|
return (0, promises_1.rm)(directory, {
|
|
433
483
|
"recursive": true,
|
|
@@ -453,6 +503,9 @@ class PluginsManager extends node_events_1.default {
|
|
|
453
503
|
if (!this.getPluginsNames().includes(pluginName)) {
|
|
454
504
|
throw new Error("Plugin \"" + pluginName + "\" is not registered");
|
|
455
505
|
}
|
|
506
|
+
if (this._isExternalPlugin(plugin)) {
|
|
507
|
+
throw new Error("Plugin \"" + pluginName + "\" is external and cannot be updated");
|
|
508
|
+
}
|
|
456
509
|
// check plugin directory
|
|
457
510
|
}).then(() => {
|
|
458
511
|
return (0, checkAbsoluteDirectory_1.default)("updateViaGithub/directory", this.directory).then(() => {
|
|
@@ -551,6 +604,9 @@ class PluginsManager extends node_events_1.default {
|
|
|
551
604
|
if (!this.getPluginsNames().includes(pluginName)) {
|
|
552
605
|
throw new Error("Plugin \"" + pluginName + "\" is not registered");
|
|
553
606
|
}
|
|
607
|
+
if (this._isExternalPlugin(plugin)) {
|
|
608
|
+
throw new Error("Plugin \"" + pluginName + "\" is external and cannot be uninstalled");
|
|
609
|
+
}
|
|
554
610
|
});
|
|
555
611
|
// check plugin directory
|
|
556
612
|
}).then(() => {
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import type { Orchestrator, tLogger } from "node-pluginsmanager-plugin";
|
|
2
|
-
export
|
|
2
|
+
export interface PluginToLoad {
|
|
3
|
+
"name": string;
|
|
4
|
+
"directory": string;
|
|
5
|
+
}
|
|
6
|
+
export default function loadSortedPlugins(pluginsToLoad: PluginToLoad[], externalResourcesDirectory: string, loadedPlugins: Orchestrator[], orderedPluginsNames: string[], emit: (eventName: string, ...subdata: unknown[]) => void, logger: tLogger | null, ...data: unknown[]): Promise<void>;
|
|
@@ -5,22 +5,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.default = loadSortedPlugins;
|
|
8
|
-
// natives
|
|
9
|
-
const node_path_1 = require("node:path");
|
|
10
8
|
// locals
|
|
11
9
|
const createPluginByDirectory_1 = __importDefault(require("./createPluginByDirectory"));
|
|
12
10
|
// private
|
|
13
11
|
// methods
|
|
14
|
-
function _loadPlugin(
|
|
12
|
+
function _loadPlugin(pluginToLoad, externalResourcesDirectory, loadedPlugins, emit, logger, ...data) {
|
|
15
13
|
// is already loaded ?
|
|
16
14
|
const plugin = loadedPlugins.find((p) => {
|
|
17
|
-
return
|
|
15
|
+
return pluginToLoad.name === p.name;
|
|
18
16
|
});
|
|
19
17
|
// is already exists ?
|
|
20
18
|
return plugin ? Promise.resolve() : Promise.resolve().then(() => {
|
|
21
|
-
emit("loading",
|
|
22
|
-
|
|
23
|
-
return (0, createPluginByDirectory_1.default)(directory, externalResourcesDirectory, logger, ...data);
|
|
19
|
+
emit("loading", pluginToLoad.name, ...data);
|
|
20
|
+
return (0, createPluginByDirectory_1.default)(pluginToLoad.directory, externalResourcesDirectory, logger, ...data);
|
|
24
21
|
// emit event
|
|
25
22
|
}).then((createdPlugin) => {
|
|
26
23
|
emit("loaded", createdPlugin, ...data);
|
|
@@ -28,40 +25,40 @@ function _loadPlugin(globalDirectory, externalResourcesDirectory, pluginFileName
|
|
|
28
25
|
return Promise.resolve();
|
|
29
26
|
});
|
|
30
27
|
}
|
|
31
|
-
function _loadPlugins(
|
|
28
|
+
function _loadPlugins(pluginsToLoad, externalResourcesDirectory, loadedPlugins, emit, logger, i, ...data) {
|
|
32
29
|
return i < pluginsToLoad.length ? Promise.resolve().then(() => {
|
|
33
|
-
return _loadPlugin(
|
|
30
|
+
return _loadPlugin(pluginsToLoad[i], externalResourcesDirectory, loadedPlugins, emit, logger, ...data);
|
|
34
31
|
// loop
|
|
35
32
|
}).then(() => {
|
|
36
|
-
return _loadPlugins(
|
|
33
|
+
return _loadPlugins(pluginsToLoad, externalResourcesDirectory, loadedPlugins, emit, logger, i + 1, ...data);
|
|
37
34
|
}) : Promise.resolve();
|
|
38
35
|
}
|
|
39
36
|
// module
|
|
40
|
-
function loadSortedPlugins(
|
|
41
|
-
// if no
|
|
42
|
-
return !
|
|
43
|
-
const
|
|
37
|
+
function loadSortedPlugins(pluginsToLoad, externalResourcesDirectory, loadedPlugins, orderedPluginsNames, emit, logger, ...data) {
|
|
38
|
+
// if no plugins, does not run
|
|
39
|
+
return !pluginsToLoad.length ? Promise.resolve() : Promise.resolve().then(() => {
|
|
40
|
+
const sortedPlugins = [];
|
|
44
41
|
orderedPluginsNames.forEach((pluginName) => {
|
|
45
|
-
const plugin =
|
|
46
|
-
return p === pluginName;
|
|
42
|
+
const plugin = pluginsToLoad.find((p) => {
|
|
43
|
+
return p.name === pluginName;
|
|
47
44
|
});
|
|
48
|
-
if (
|
|
49
|
-
|
|
45
|
+
if (plugin) {
|
|
46
|
+
sortedPlugins.push(plugin);
|
|
50
47
|
}
|
|
51
48
|
});
|
|
52
49
|
// first, sorted plugins
|
|
53
|
-
return
|
|
54
|
-
? _loadPlugins(
|
|
50
|
+
return sortedPlugins.length
|
|
51
|
+
? _loadPlugins(sortedPlugins, externalResourcesDirectory, loadedPlugins, emit, logger, 0, ...data)
|
|
55
52
|
: Promise.resolve();
|
|
56
53
|
}).then(() => {
|
|
57
|
-
const
|
|
58
|
-
...
|
|
59
|
-
return !orderedPluginsNames.includes(
|
|
54
|
+
const unsortedPlugins = [
|
|
55
|
+
...pluginsToLoad.filter((plugin) => {
|
|
56
|
+
return !orderedPluginsNames.includes(plugin.name);
|
|
60
57
|
}).sort((a, b) => {
|
|
61
|
-
if (a < b) {
|
|
58
|
+
if (a.name < b.name) {
|
|
62
59
|
return -1;
|
|
63
60
|
}
|
|
64
|
-
else if (a > b) {
|
|
61
|
+
else if (a.name > b.name) {
|
|
65
62
|
return 1;
|
|
66
63
|
}
|
|
67
64
|
else {
|
|
@@ -69,9 +66,9 @@ function loadSortedPlugins(globalDirectory, externalResourcesDirectory, files, l
|
|
|
69
66
|
}
|
|
70
67
|
})
|
|
71
68
|
];
|
|
72
|
-
// then, all other plugins
|
|
73
|
-
return
|
|
74
|
-
? _loadPlugins(
|
|
69
|
+
// then, all other plugins
|
|
70
|
+
return unsortedPlugins.length
|
|
71
|
+
? _loadPlugins(unsortedPlugins, externalResourcesDirectory, loadedPlugins, emit, logger, 0, ...data)
|
|
75
72
|
: Promise.resolve();
|
|
76
73
|
});
|
|
77
74
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
|
|
3
3
|
"name": "node-pluginsmanager",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.8.0",
|
|
5
5
|
"description": "A plugins manager.",
|
|
6
6
|
|
|
7
7
|
"type": "commonjs",
|
|
@@ -43,32 +43,32 @@
|
|
|
43
43
|
"/lib/cjs"
|
|
44
44
|
],
|
|
45
45
|
"engines": {
|
|
46
|
-
"node": ">=22.23.
|
|
46
|
+
"node": ">=22.23.2"
|
|
47
47
|
},
|
|
48
48
|
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"check-node-engine": "1.
|
|
50
|
+
"check-node-engine": "1.2.0",
|
|
51
51
|
"check-version-modules": "2.5.0",
|
|
52
52
|
"semver": "7.8.5"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/express": "5.0.6",
|
|
56
|
-
"@types/node": "26.
|
|
57
|
-
"@types/semver": "7.
|
|
56
|
+
"@types/node": "26.4.1",
|
|
57
|
+
"@types/semver": "7.8.0",
|
|
58
58
|
"@types/socket.io": "3.0.2",
|
|
59
59
|
"@types/ws": "8.18.1",
|
|
60
60
|
"eslint-plugin-personnallinter": "git+ssh://git@github.com/Psychopoulet/eslint-plugin-personnallinter",
|
|
61
61
|
"express": "5.2.1",
|
|
62
62
|
"husky": "9.1.7",
|
|
63
|
-
"mocha": "
|
|
64
|
-
"node-pluginsmanager-plugin": "7.4.
|
|
63
|
+
"mocha": "12.0.0",
|
|
64
|
+
"node-pluginsmanager-plugin": "7.4.4",
|
|
65
65
|
"nyc": "18.0.0",
|
|
66
66
|
"proxyquire": "2.1.3",
|
|
67
67
|
"rimraf": "6.1.3",
|
|
68
68
|
"socket.io": "4.8.3",
|
|
69
69
|
"typescript": "5.9.3",
|
|
70
70
|
"used-deps-analyzer": "0.4.0",
|
|
71
|
-
"ws": "8.21.
|
|
71
|
+
"ws": "8.21.3"
|
|
72
72
|
},
|
|
73
73
|
|
|
74
74
|
"keywords": [
|