From c59a6407f76f16872e8528035bd6dc56bfc11f7a Mon Sep 17 00:00:00 2001 From: David Gil Date: Mon, 30 May 2022 21:30:48 +0200 Subject: [PATCH 1/2] test: create a failing case for a wrongly resolved dependency chain of multiple links --- src/examples/happy/depend-on-dependencies-hub-bis.js | 7 +++++++ src/examples/happy/depend-on-dependencies-hub.js | 7 +++++++ src/pluginus.test.js | 10 +++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 src/examples/happy/depend-on-dependencies-hub-bis.js create mode 100644 src/examples/happy/depend-on-dependencies-hub.js diff --git a/src/examples/happy/depend-on-dependencies-hub-bis.js b/src/examples/happy/depend-on-dependencies-hub-bis.js new file mode 100644 index 0000000..e711b72 --- /dev/null +++ b/src/examples/happy/depend-on-dependencies-hub-bis.js @@ -0,0 +1,7 @@ +export default { + depend: ["DependOnDependenciesHub"], + + create: () => ({ + dependOn: "hub", + }), +} diff --git a/src/examples/happy/depend-on-dependencies-hub.js b/src/examples/happy/depend-on-dependencies-hub.js new file mode 100644 index 0000000..d1fb9d6 --- /dev/null +++ b/src/examples/happy/depend-on-dependencies-hub.js @@ -0,0 +1,7 @@ +export default { + depend: ["DependOnPlain"], + + create: () => ({ + dependOn: "hub", + }), +} diff --git a/src/pluginus.test.js b/src/pluginus.test.js index 395a701..6625db7 100644 --- a/src/pluginus.test.js +++ b/src/pluginus.test.js @@ -23,7 +23,15 @@ test("Happy", async t => { t.deepEquals( Object.keys(plugins).sort(), - ["DependOnPlain", "ExplicitName", "Object", "Plain", "PromisePlugin"], + [ + "DependOnDependenciesHub", + "DependOnDependenciesHubBis", + "DependOnPlain", + "ExplicitName", + "Object", + "Plain", + "PromisePlugin", + ], 'given [pluginus called with invalid values in "source" param] should [sanitize and load only valid]' ) From a97db4ec447dad9e5d0238bb0c7e1498bc00b497 Mon Sep 17 00:00:00 2001 From: David Gil Date: Mon, 30 May 2022 23:03:56 +0200 Subject: [PATCH 2/2] feat: use topological sort to deal with slightly more complex dependency trees --- src/pluginus.js | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/pluginus.js b/src/pluginus.js index 841180b..98a66dd 100644 --- a/src/pluginus.js +++ b/src/pluginus.js @@ -3,8 +3,10 @@ import fs from "fs" import path from "path" import { + all, pipe, - sortBy, + pick, + findWith, reduce, distinct, remove, @@ -18,7 +20,7 @@ import { toLower, is, isEmpty, - has, + any, } from "@asd14/m" const capitalizeFirstLetter = string => @@ -168,6 +170,30 @@ const resolve = unresolvedPlugins => { )(unresolvedPlugins) } +// Sort based on dependency topology, using TSort +// https://en.wikipedia.org/wiki/Topological_sorting +const tSort = (names, input, start = [], depth = 0) => { + const processed = reduce((accum, item) => { + const plugin = findWith({ name: item }, {})(input) + + const allDependenciesIncluded = pipe( + read("depend", []), + all(dependency => any(dependency, pick("name")(accum))) + )(plugin) + + if (allDependenciesIncluded) { + accum.push(plugin) + } + + return accum + }, start)(names) + + const nextNames = names.filter(n => !processed.includes(n)), + goAgain = nextNames.length !== 0 && depth <= names.length + + return goAgain ? tSort(nextNames, input, processed, depth + 1) : processed +} + export const pluginus = ({ source, nameFn = defaultNameFn }) => pipeP( // Sanitize @@ -179,17 +205,7 @@ export const pluginus = ({ source, nameFn = defaultNameFn }) => plugins => Promise.all(plugins), prepare(nameFn), - // Sort based on dependency. Plugins without dependencies first - sortBy((a, b) => { - const aHasB = has(b.name, a.depend) - const bHasA = has(a.name, b.depend) - - if (!aHasB && !bHasA) { - return a.depend.length > b.depend.length ? 1 : -1 - } - - return bHasA ? -1 : 1 - }), + plugins => tSort(pick("name", plugins), plugins), // resolve