From eae9a4dd4972aacf686a49968b498ad9e4e6aa80 Mon Sep 17 00:00:00 2001 From: Andre Staltz Date: Fri, 5 Jan 2024 14:48:03 +0200 Subject: [PATCH 1/4] plugin.needs to assert dependencies --- lib/api.js | 10 ++++++++++ test/api.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/lib/api.js b/lib/api.js index e7a80fe..a646ca9 100644 --- a/lib/api.js +++ b/lib/api.js @@ -141,6 +141,16 @@ function Api (plugins, defaultConfig) { } const name = plugin.name + + if (plugin.needs) { + for (const needed of plugin.needs) { + const found = create.plugins.some((p) => p.name === needed) + if (!found) { + throw new Error(`plugin "${name ?? '?'}" needs "${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..904bcb5 100644 --- a/test/api.js +++ b/test/api.js @@ -140,6 +140,39 @@ 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 {} + } + }]) + + t.throws(() => { + Create.use({ + name: 'x', + needs: ['y'], + init: function () { } + }) + }, 'throws on missing plugin') + + Create.use({ + name: 'foo', + init: function () { } + }) + + t.doesNotThrow(() => { + Create.use({ + name: 'bar', + needs: ['foo'], + init: function () { } + }) + }, 'does not throw on existing plugin') + + t.end() +}) + tape('compound (array) plugins', function (t) { // core, not a plugin. var Create = Api([{ From ccbc10b277d363b80d3b43b41c1fe0fd64053ef8 Mon Sep 17 00:00:00 2001 From: Andre Staltz Date: Fri, 5 Jan 2024 15:14:41 +0200 Subject: [PATCH 2/4] tweak error msg --- lib/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api.js b/lib/api.js index a646ca9..f7d10c4 100644 --- a/lib/api.js +++ b/lib/api.js @@ -146,7 +146,7 @@ function Api (plugins, defaultConfig) { for (const needed of plugin.needs) { const found = create.plugins.some((p) => p.name === needed) if (!found) { - throw new Error(`plugin "${name ?? '?'}" needs "${needed}" but not found`) + throw new Error(`plugin "${name ?? '?'}" needs plugin "${needed}" but not found`) } } } From d72f20b9e30bc61d26d9b63a5b0ef3c9ac5c134d Mon Sep 17 00:00:00 2001 From: Andre Staltz Date: Fri, 5 Jan 2024 16:38:56 +0200 Subject: [PATCH 3/4] support async needs check --- lib/api.js | 12 +++++++----- test/api.js | 40 ++++++++++++++++++++++++---------------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/lib/api.js b/lib/api.js index f7d10c4..42b0c79 100644 --- a/lib/api.js +++ b/lib/api.js @@ -143,12 +143,14 @@ function Api (plugins, defaultConfig) { const name = plugin.name if (plugin.needs) { - for (const needed of plugin.needs) { - const found = create.plugins.some((p) => p.name === needed) - if (!found) { - throw new Error(`plugin "${name ?? '?'}" needs plugin "${needed}" but not found`) + 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) { diff --git a/test/api.js b/test/api.js index 904bcb5..d71af7e 100644 --- a/test/api.js +++ b/test/api.js @@ -149,28 +149,36 @@ tape('plugin needs another plugin', function (t) { } }]) - t.throws(() => { - Create.use({ - name: 'x', - needs: ['y'], - init: function () { } - }) - }, 'throws on missing plugin') + 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: 'foo', + name: 'x', + needs: ['y'], init: function () { } }) - t.doesNotThrow(() => { - Create.use({ - name: 'bar', - needs: ['foo'], - init: function () { } - }) - }, 'does not throw on existing plugin') + // Should NOT throw, even though 'foo' is loaded after 'bar' + Create.use({ + name: 'bar', + needs: ['foo'], + init: function () { } + }) - t.end() + Create.use({ + name: 'foo', + init: function () { } + }) }) tape('compound (array) plugins', function (t) { From e2c4e802384f17c59eedf4f83ecbc5be9b67b691 Mon Sep 17 00:00:00 2001 From: Andre Staltz Date: Fri, 5 Jan 2024 16:41:12 +0200 Subject: [PATCH 4/4] update documentation on plugins regarding `needs` --- PLUGINS.md | 10 ++++++++++ 1 file changed, 10 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