From 6f1c1040c395aa2801d3ff668e1b18c7eef2dd20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Staltz?= Date: Sat, 6 Jan 2024 16:18:01 +0200 Subject: [PATCH] plugin.needs to assert dependencies (#89) * plugin.needs to assert dependencies * tweak error msg * support async needs check * update documentation on plugins regarding `needs` --- PLUGINS.md | 10 ++++++++++ lib/api.js | 12 ++++++++++++ test/api.js | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/PLUGINS.md b/PLUGINS.md index 2c11c63..3142485 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -12,6 +12,7 @@ Plugins are simply NodeJS modules that export an `object` of form `{ name, versi module.exports = { name: 'bluetooth', + needs: ['conn'], version: '5.0.1', manifest: { localPeers: 'async', @@ -87,6 +88,15 @@ will be available at `node.fooBar`. A `plugin.name` can also be an `'object`. This object will be merged directly with the +### `plugin.needs` (Array) _optional_ + +An array of strings which are the names of other plugins that this plugin +depends on. If those plugins are not present, then secret-stack will throw +an error indicating that the dependency is missing. + +Use this field to declare dependencies on other plugins, and this should +facilitate the correct usage of your plugin. + ### `plugin.version` (String) _optional_ NOTE - not currently used anywhere functionally diff --git a/lib/api.js b/lib/api.js index e7a80fe..42b0c79 100644 --- a/lib/api.js +++ b/lib/api.js @@ -141,6 +141,18 @@ function Api (plugins, defaultConfig) { } const name = plugin.name + + if (plugin.needs) { + queueMicrotask(() => { + for (const needed of plugin.needs) { + const found = create.plugins.some((p) => p.name === needed) + if (!found) { + throw new Error(`secret-stack plugin "${name ?? '?'}" needs plugin "${needed}" but not found`) + } + } + }) + } + if (plugin.manifest) { create.manifest = u.merge.manifest( create.manifest, diff --git a/test/api.js b/test/api.js index 6de3bb1..d71af7e 100644 --- a/test/api.js +++ b/test/api.js @@ -140,6 +140,47 @@ tape('plugin cannot be named global', function (t) { t.end() }) +tape('plugin needs another plugin', function (t) { + // core, not a plugin. + var Create = Api([{ + manifest: {}, + init: function (api) { + return {} + } + }]) + + function uncaughtExceptionListener(err) { + t.equals(err.message, 'secret-stack plugin "x" needs plugin "y" but not found') + + // Wait for potentially other errors + setTimeout(() => { + process.off('uncaughtException', uncaughtExceptionListener) + t.end() + }, 100) + } + + process.on('uncaughtException', uncaughtExceptionListener) + + // Should throw + Create.use({ + name: 'x', + needs: ['y'], + init: function () { } + }) + + // Should NOT throw, even though 'foo' is loaded after 'bar' + Create.use({ + name: 'bar', + needs: ['foo'], + init: function () { } + }) + + Create.use({ + name: 'foo', + init: function () { } + }) +}) + tape('compound (array) plugins', function (t) { // core, not a plugin. var Create = Api([{