@flowscripter/dynamic-plugin-framework 1.2.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.
Files changed (38) hide show
  1. package/.github/workflows/check-bun-dependencies.yml +9 -0
  2. package/.github/workflows/lint-pr-message.yml +11 -0
  3. package/.github/workflows/release-bun-library.yml +8 -0
  4. package/.github/workflows/validate-bun-library-pr.yml +8 -0
  5. package/LICENSE +21 -0
  6. package/README.md +303 -0
  7. package/bun.lock +27 -0
  8. package/index.ts +11 -0
  9. package/package.json +15 -0
  10. package/src/api/plugin/ExtensionDescriptor.ts +22 -0
  11. package/src/api/plugin/ExtensionFactory.ts +13 -0
  12. package/src/api/plugin/Plugin.ts +16 -0
  13. package/src/api/plugin_manager/ExtensionInfo.ts +19 -0
  14. package/src/api/plugin_manager/PluginManager.ts +38 -0
  15. package/src/plugin_manager/DefaultPluginManager.ts +114 -0
  16. package/src/plugin_manager/plugin_repository/ExtensionEntry.ts +33 -0
  17. package/src/plugin_manager/plugin_repository/PluginRepository.ts +30 -0
  18. package/src/plugin_manager/plugin_repository/UrlListPluginRepository.ts +105 -0
  19. package/src/plugin_manager/plugin_repository/UrlPluginSource.ts +34 -0
  20. package/src/plugin_manager/registry/ExtensionPointRegistry.ts +25 -0
  21. package/src/plugin_manager/registry/ExtensionRegistry.ts +38 -0
  22. package/src/plugin_manager/registry/InMemoryExtensionPointRegistry.ts +29 -0
  23. package/src/plugin_manager/registry/InMemoryExtensionRegistry.ts +69 -0
  24. package/src/plugin_manager/util/PluginLoader.ts +93 -0
  25. package/tests/fixtures/Constants.ts +15 -0
  26. package/tests/fixtures/InvalidPlugin1.ts +1 -0
  27. package/tests/fixtures/InvalidPlugin2.ts +5 -0
  28. package/tests/fixtures/InvalidPlugin3.ts +5 -0
  29. package/tests/fixtures/InvalidPlugin4.ts +6 -0
  30. package/tests/fixtures/InvalidPlugin5.ts +8 -0
  31. package/tests/fixtures/ValidPlugin1.ts +34 -0
  32. package/tests/plugin_manager/DefaultPluginManager_test.ts +96 -0
  33. package/tests/plugin_manager/plugin_repository/UrlListPluginRepository_test.ts +61 -0
  34. package/tests/plugin_manager/plugin_repository/UrlPluginSource_test.ts +48 -0
  35. package/tests/plugin_manager/registry/InMemoryExtensionPointRegistry_test.ts +41 -0
  36. package/tests/plugin_manager/registry/InMemoryExtensionRegistry_test.ts +72 -0
  37. package/tests/plugin_manager/util/PluginLoader_test.ts +107 -0
  38. package/tsconfig.json +27 -0
@@ -0,0 +1,48 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import path from "node:path";
3
+ import UrlPluginSource from "../../../src/plugin_manager/plugin_repository/UrlPluginSource.ts";
4
+
5
+ const PLUGIN_1_URL = "file://" +
6
+ path.join(
7
+ path.dirname(Bun.fileURLToPath(import.meta.url)),
8
+ "../../fixtures/ValidPlugin1.ts",
9
+ );
10
+
11
+ const INVALID_PLUGIN_URL = "file://" +
12
+ path.join(
13
+ path.dirname(Bun.fileURLToPath(import.meta.url)),
14
+ "../../fixtures/InvalidPlugin1.ts",
15
+ );
16
+
17
+ describe("UrlPluginSource Tests", () => {
18
+ test("Returns undefined on invalid plugin", async () => {
19
+ const urlPluginSource = new UrlPluginSource();
20
+
21
+ expect(
22
+ await urlPluginSource.loadPlugin(
23
+ new URL(INVALID_PLUGIN_URL),
24
+ ),
25
+ ).toBeUndefined();
26
+ });
27
+
28
+ test("Loads a plugin", async () => {
29
+ const urlPluginSource = new UrlPluginSource();
30
+
31
+ const plugin = await urlPluginSource.loadPlugin(new URL(PLUGIN_1_URL));
32
+
33
+ expect(plugin).not.toEqual(undefined);
34
+ expect(plugin?.extensionDescriptors.length).toEqual(1);
35
+ });
36
+
37
+ test("Can load a plugin twice", async () => {
38
+ const urlPluginSource = new UrlPluginSource();
39
+
40
+ let plugin = await urlPluginSource.loadPlugin(new URL(PLUGIN_1_URL));
41
+
42
+ expect(plugin).not.toEqual(undefined);
43
+
44
+ plugin = await urlPluginSource.loadPlugin(new URL(PLUGIN_1_URL));
45
+
46
+ expect(plugin).not.toEqual(undefined);
47
+ });
48
+ });
@@ -0,0 +1,41 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import InMemoryExtensionPointRegistry from "../../../src/plugin_manager/registry/InMemoryExtensionPointRegistry.ts";
3
+ import {
4
+ EXTENSION_POINT_1,
5
+ EXTENSION_POINT_2,
6
+ } from "../../fixtures/Constants.ts";
7
+
8
+ describe("InMemoryExtensionPointRegistry Tests", () => {
9
+ test("Register extension point", async () => {
10
+ const extensionPointRegistry = new InMemoryExtensionPointRegistry();
11
+
12
+ expect(await extensionPointRegistry.isRegistered(EXTENSION_POINT_1))
13
+ .toBeFalse();
14
+
15
+ await extensionPointRegistry.register(EXTENSION_POINT_1);
16
+
17
+ expect(await extensionPointRegistry.isRegistered(EXTENSION_POINT_1))
18
+ .toBeTrue();
19
+ });
20
+
21
+ test("Cannot register extension point twice", async () => {
22
+ const extensionPointRegistry = new InMemoryExtensionPointRegistry();
23
+
24
+ await extensionPointRegistry.register(EXTENSION_POINT_1);
25
+
26
+ expect(extensionPointRegistry.register(EXTENSION_POINT_1))
27
+ .rejects.toThrow();
28
+ });
29
+
30
+ test("Get registered extension points", async () => {
31
+ const extensionPointRegistry = new InMemoryExtensionPointRegistry();
32
+
33
+ await extensionPointRegistry.register(EXTENSION_POINT_1);
34
+ await extensionPointRegistry.register(EXTENSION_POINT_2);
35
+
36
+ const extensionPoints = await extensionPointRegistry.getAll();
37
+
38
+ expect(extensionPoints.has(EXTENSION_POINT_1)).toBeTrue();
39
+ expect(extensionPoints.has(EXTENSION_POINT_2)).toBeTrue();
40
+ });
41
+ });
@@ -0,0 +1,72 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import InMemoryExtensionRegistry from "../../../src/plugin_manager/registry/InMemoryExtensionRegistry.ts";
3
+ import type ExtensionEntry from "../../../src/plugin_manager/plugin_repository/ExtensionEntry.ts";
4
+
5
+ import {
6
+ EXTENSION_1_HANDLE,
7
+ EXTENSION_1_ID,
8
+ EXTENSION_2_HANDLE,
9
+ EXTENSION_2_ID,
10
+ EXTENSION_POINT_1,
11
+ EXTENSION_POINT_2,
12
+ PLUGIN_1_ID,
13
+ PLUGIN_2_ID,
14
+ } from "../../fixtures/Constants.ts";
15
+
16
+ const extensionEntry1: ExtensionEntry = {
17
+ pluginId: PLUGIN_1_ID,
18
+ extensionId: EXTENSION_1_ID,
19
+ extensionPoint: EXTENSION_POINT_1,
20
+ };
21
+ const extensionEntry2: ExtensionEntry = {
22
+ pluginId: PLUGIN_2_ID,
23
+ extensionId: EXTENSION_2_ID,
24
+ extensionPoint: EXTENSION_POINT_2,
25
+ };
26
+
27
+ describe("InMemoryExtensionRegistry Tests", () => {
28
+ test("Register extension", async () => {
29
+ const extensionRegistry = new InMemoryExtensionRegistry();
30
+
31
+ expect(extensionRegistry.get(EXTENSION_1_HANDLE)).rejects
32
+ .toThrow();
33
+
34
+ await extensionRegistry.register(EXTENSION_1_HANDLE, extensionEntry1);
35
+
36
+ await extensionRegistry.get(EXTENSION_1_HANDLE);
37
+ });
38
+
39
+ test("Cannot register extension twice", async () => {
40
+ const extensionRegistry = new InMemoryExtensionRegistry();
41
+
42
+ await extensionRegistry.register(EXTENSION_1_HANDLE, extensionEntry1);
43
+
44
+ expect(extensionRegistry.register(EXTENSION_1_HANDLE, extensionEntry1))
45
+ .rejects.toThrow();
46
+ });
47
+
48
+ test("Get registered extensions", async () => {
49
+ const extensionRegistry = new InMemoryExtensionRegistry();
50
+
51
+ await extensionRegistry.register(EXTENSION_1_HANDLE, extensionEntry1);
52
+ await extensionRegistry.register(EXTENSION_2_HANDLE, extensionEntry2);
53
+
54
+ const extensionEntries1 = await extensionRegistry.getExtensions(
55
+ EXTENSION_POINT_1,
56
+ );
57
+
58
+ expect(extensionEntries1.size).toEqual(1);
59
+ expect(
60
+ extensionEntries1.get(EXTENSION_1_HANDLE)?.extensionId,
61
+ ).toEqual(EXTENSION_1_ID);
62
+
63
+ const extensionEntries2 = await extensionRegistry.getExtensions(
64
+ EXTENSION_POINT_2,
65
+ );
66
+
67
+ expect(extensionEntries2.size).toEqual(1);
68
+ expect(
69
+ extensionEntries2.get(EXTENSION_2_HANDLE)?.extensionId,
70
+ ).toEqual(EXTENSION_2_ID);
71
+ });
72
+ });
@@ -0,0 +1,107 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import loadPlugin from "../../../src/plugin_manager/util/PluginLoader.ts";
3
+
4
+ describe("PluginLoader Tests", () => {
5
+ test("Rejects invalid URL", async () => {
6
+ const pluginLoadResult = await loadPlugin("foo");
7
+
8
+ expect(pluginLoadResult.isValidPlugin).toBeFalse();
9
+ expect(pluginLoadResult.error?.message).toStartWith("Cannot find package");
10
+ });
11
+
12
+ test("Rejects invalid modules", async () => {
13
+ const pluginLoadResult = await loadPlugin(
14
+ "../plugin_repository/UrlListPluginRepository.ts",
15
+ );
16
+
17
+ expect(pluginLoadResult.isValidPlugin).toBeFalse();
18
+ expect(
19
+ pluginLoadResult.error?.message,
20
+ ).toEqual(
21
+ "Plugin from ../plugin_repository/UrlListPluginRepository.ts does not provide an extensionDescriptors array",
22
+ );
23
+ });
24
+
25
+ test("Rejects invalid plugins", async () => {
26
+ let pluginLoadResult = await loadPlugin(
27
+ "../../../tests/fixtures/InvalidPlugin1.ts",
28
+ );
29
+
30
+ expect(pluginLoadResult.isValidPlugin).toBeFalse();
31
+ expect(pluginLoadResult.error?.name).toEqual("Error");
32
+ expect(
33
+ pluginLoadResult.error?.message,
34
+ ).toEqual(
35
+ "Plugin from ../../../tests/fixtures/InvalidPlugin1.ts does not provide an extensionDescriptors array",
36
+ );
37
+
38
+ pluginLoadResult = await loadPlugin(
39
+ "../../../tests/fixtures/InvalidPlugin2.ts",
40
+ );
41
+
42
+ expect(pluginLoadResult.isValidPlugin).toBeFalse();
43
+ expect(pluginLoadResult.error?.name).toEqual("Error");
44
+ expect(
45
+ pluginLoadResult.error?.message,
46
+ ).toEqual(
47
+ "Plugin from ../../../tests/fixtures/InvalidPlugin2.ts does not provide an extensionPoint string in one of the extensionDescriptors",
48
+ );
49
+
50
+ pluginLoadResult = await loadPlugin(
51
+ "../../../tests/fixtures/InvalidPlugin3.ts",
52
+ );
53
+
54
+ expect(pluginLoadResult.isValidPlugin).toBeFalse();
55
+ expect(pluginLoadResult.error?.name).toEqual("Error");
56
+ expect(
57
+ pluginLoadResult.error?.message,
58
+ ).toEqual(
59
+ "Plugin from ../../../tests/fixtures/InvalidPlugin3.ts does not provide a factory with a create function in one of the extensionDescriptors",
60
+ );
61
+
62
+ pluginLoadResult = await loadPlugin(
63
+ "../../../tests/fixtures/InvalidPlugin4.ts",
64
+ );
65
+
66
+ expect(pluginLoadResult.isValidPlugin).toBeFalse();
67
+ expect(pluginLoadResult.error?.name).toEqual("Error");
68
+ expect(
69
+ pluginLoadResult.error?.message,
70
+ ).toEqual(
71
+ "Plugin from ../../../tests/fixtures/InvalidPlugin4.ts does not provide a factory with a create function in one of the extensionDescriptors",
72
+ );
73
+
74
+ pluginLoadResult = await loadPlugin(
75
+ "../../../tests/fixtures/InvalidPlugin5.ts",
76
+ );
77
+
78
+ expect(pluginLoadResult.isValidPlugin).toBeFalse();
79
+ expect(pluginLoadResult.error?.name).toEqual("Error");
80
+ expect(
81
+ pluginLoadResult.error?.message,
82
+ ).toEqual(
83
+ "Plugin from ../../../tests/fixtures/InvalidPlugin5.ts does not provide a factory with a create function in one of the extensionDescriptors",
84
+ );
85
+ });
86
+
87
+ test("Returns valid plugin", async () => {
88
+ const pluginLoadResult = await loadPlugin(
89
+ "../../../tests/fixtures/ValidPlugin1.ts",
90
+ );
91
+
92
+ expect(pluginLoadResult.isValidPlugin).toBeTrue();
93
+ expect(pluginLoadResult.error).toBeUndefined();
94
+
95
+ const plugin = pluginLoadResult.plugin;
96
+
97
+ expect(plugin?.pluginData?.get("foo")).toEqual("bar");
98
+
99
+ expect(plugin?.extensionDescriptors.length).toEqual(1);
100
+
101
+ const extensionDescriptor = plugin?.extensionDescriptors[0];
102
+
103
+ expect(extensionDescriptor?.extensionData?.get("foo")).toEqual("bar");
104
+
105
+ await extensionDescriptor?.factory.create();
106
+ });
107
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Enable latest features
4
+ "lib": ["ESNext", "DOM"],
5
+ "target": "ESNext",
6
+ "module": "ESNext",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+
22
+ // Some stricter flags (disabled by default)
23
+ "noUnusedLocals": false,
24
+ "noUnusedParameters": false,
25
+ "noPropertyAccessFromIndexSignature": false
26
+ }
27
+ }