node-pluginsmanager 3.7.2 → 3.8.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/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.ts)
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
- return (0, loadSortedPlugins_1.default)(this.directory, this.externalResourcesDirectory, files, this.plugins, this._orderedPluginsNames, this.emit.bind(this), this._logger, ...data);
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 default function loadSortedPlugins(globalDirectory: string, externalResourcesDirectory: string, files: string[], loadedPlugins: Orchestrator[], orderedPluginsNames: string[], emit: (eventName: string, ...subdata: unknown[]) => void, logger: tLogger | null, ...data: unknown[]): Promise<void>;
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(globalDirectory, externalResourcesDirectory, pluginFileName, loadedPlugins, emit, logger, ...data) {
12
+ function _loadPlugin(pluginToLoad, externalResourcesDirectory, loadedPlugins, emit, logger, ...data) {
15
13
  // is already loaded ?
16
14
  const plugin = loadedPlugins.find((p) => {
17
- return pluginFileName === p.name;
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", pluginFileName, ...data);
22
- const directory = (0, node_path_1.join)(globalDirectory, pluginFileName);
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(globalDirectory, externalResourcesDirectory, pluginsToLoad, loadedPlugins, emit, logger, i, ...data) {
28
+ function _loadPlugins(pluginsToLoad, externalResourcesDirectory, loadedPlugins, emit, logger, i, ...data) {
32
29
  return i < pluginsToLoad.length ? Promise.resolve().then(() => {
33
- return _loadPlugin(globalDirectory, externalResourcesDirectory, pluginsToLoad[i], loadedPlugins, emit, logger, ...data);
30
+ return _loadPlugin(pluginsToLoad[i], externalResourcesDirectory, loadedPlugins, emit, logger, ...data);
34
31
  // loop
35
32
  }).then(() => {
36
- return _loadPlugins(globalDirectory, externalResourcesDirectory, pluginsToLoad, loadedPlugins, emit, logger, i + 1, ...data);
33
+ return _loadPlugins(pluginsToLoad, externalResourcesDirectory, loadedPlugins, emit, logger, i + 1, ...data);
37
34
  }) : Promise.resolve();
38
35
  }
39
36
  // module
40
- function loadSortedPlugins(globalDirectory, externalResourcesDirectory, files, loadedPlugins, orderedPluginsNames, emit, logger, ...data) {
41
- // if no files, does not run
42
- return !files.length ? Promise.resolve() : Promise.resolve().then(() => {
43
- const sortedPluginsNames = [];
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 = files.find((p) => {
46
- return p === pluginName;
42
+ const plugin = pluginsToLoad.find((p) => {
43
+ return p.name === pluginName;
47
44
  });
48
- if ("string" === typeof plugin) {
49
- sortedPluginsNames.push(pluginName);
45
+ if (plugin) {
46
+ sortedPlugins.push(plugin);
50
47
  }
51
48
  });
52
49
  // first, sorted plugins
53
- return sortedPluginsNames.length
54
- ? _loadPlugins(globalDirectory, externalResourcesDirectory, sortedPluginsNames, loadedPlugins, emit, logger, 0, ...data)
50
+ return sortedPlugins.length
51
+ ? _loadPlugins(sortedPlugins, externalResourcesDirectory, loadedPlugins, emit, logger, 0, ...data)
55
52
  : Promise.resolve();
56
53
  }).then(() => {
57
- const unsortedPluginsNames = [
58
- ...files.filter((pluginName) => {
59
- return !orderedPluginsNames.includes(pluginName);
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, asynchronously
73
- return unsortedPluginsNames.length
74
- ? _loadPlugins(globalDirectory, externalResourcesDirectory, unsortedPluginsNames, loadedPlugins, emit, logger, 0, ...data)
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.7.2",
4
+ "version": "3.8.1",
5
5
  "description": "A plugins manager.",
6
6
 
7
7
  "type": "commonjs",
@@ -32,10 +32,10 @@
32
32
  "check-node-engine": "npx check-node-engine",
33
33
  "check-requires": "npx used-deps-analyzer ./package.json ./lib/src --no-dev",
34
34
  "check-updates": "npx check-version-modules --no-fail-at-major --fail-at-minor --fail-at-patch",
35
- "unit-tests": "npx mocha",
36
- "unit-tests-local": "npx nyc --reporter=html --reporter=text mocha",
35
+ "unit-tests": "npx c8 --reporter=text-summary --all --include \"lib/src/**/*.ts\" --include \"lib/src/**/*.cts\" --extension .ts --extension .cts --check-coverage --statements 95 --branches 95 --functions 95 --lines 95 mocha --require ./test/register.js",
36
+ "unit-tests-local": "npx c8 --reporter=html --reporter=text --all --include \"lib/src/**/*.ts\" --include \"lib/src/**/*.cts\" --extension .ts --extension .cts mocha --require ./test/register.js",
37
37
 
38
- "tests": "npm run lint && npm run check-node-engine && npm run check-requires && npm run check-updates && npm run build && npm run unit-tests-local"
38
+ "tests": "npm run lint && npm run check-node-engine && npm run check-requires && npm run check-updates && npm run build && npm run unit-tests"
39
39
 
40
40
  },
41
41
 
@@ -43,32 +43,32 @@
43
43
  "/lib/cjs"
44
44
  ],
45
45
  "engines": {
46
- "node": ">=22.23.2"
46
+ "node": ">=22.23.4"
47
47
  },
48
48
 
49
49
  "dependencies": {
50
- "check-node-engine": "1.2.0",
51
- "check-version-modules": "2.5.0",
50
+ "check-node-engine": "1.2.1",
51
+ "check-version-modules": "2.5.2",
52
52
  "semver": "7.8.5"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/express": "5.0.6",
56
- "@types/node": "26.2.0",
56
+ "@types/node": "26.6.5",
57
57
  "@types/semver": "7.8.0",
58
58
  "@types/socket.io": "3.0.2",
59
- "@types/ws": "8.18.1",
59
+ "@types/ws": "8.18.2",
60
+ "c8": "12.0.0",
60
61
  "eslint-plugin-personnallinter": "git+ssh://git@github.com/Psychopoulet/eslint-plugin-personnallinter",
61
- "express": "5.2.1",
62
+ "express": "5.3.0",
62
63
  "husky": "9.1.7",
63
- "mocha": "11.8.0",
64
- "node-pluginsmanager-plugin": "7.4.4",
65
- "nyc": "18.0.0",
64
+ "mocha": "12.0.3",
65
+ "node-pluginsmanager-plugin": "7.4.5",
66
66
  "proxyquire": "2.1.3",
67
67
  "rimraf": "6.1.3",
68
- "socket.io": "4.8.3",
69
- "typescript": "5.9.3",
70
- "used-deps-analyzer": "0.4.0",
71
- "ws": "8.21.3"
68
+ "socket.io": "4.8.4",
69
+ "typescript": "6.0.3",
70
+ "used-deps-analyzer": "0.4.1",
71
+ "ws": "8.22.0"
72
72
  },
73
73
 
74
74
  "keywords": [