every-plugin 2.3.0 → 2.4.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/dist/build/rspack/dev-server-middleware.cjs +1 -2
- package/dist/build/rspack/dev-server-middleware.cjs.map +1 -1
- package/dist/build/rspack/dev-server-middleware.mjs +1 -2
- package/dist/build/rspack/dev-server-middleware.mjs.map +1 -1
- package/dist/build/rspack/plugin.cjs +21 -1
- package/dist/build/rspack/plugin.cjs.map +1 -1
- package/dist/build/rspack/plugin.d.cts +5 -0
- package/dist/build/rspack/plugin.d.cts.map +1 -1
- package/dist/build/rspack/plugin.d.mts +5 -0
- package/dist/build/rspack/plugin.d.mts.map +1 -1
- package/dist/build/rspack/plugin.mjs +21 -1
- package/dist/build/rspack/plugin.mjs.map +1 -1
- package/dist/build/shared-deps.cjs +16 -0
- package/dist/build/shared-deps.cjs.map +1 -1
- package/dist/build/shared-deps.d.cts +28 -0
- package/dist/build/shared-deps.d.cts.map +1 -1
- package/dist/build/shared-deps.d.mts +28 -0
- package/dist/build/shared-deps.d.mts.map +1 -1
- package/dist/build/shared-deps.mjs +16 -0
- package/dist/build/shared-deps.mjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/plugin.cjs +2 -1
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +2 -1
- package/dist/plugin.mjs.map +1 -1
- package/dist/runtime/index.cjs +2 -2
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts.map +1 -1
- package/dist/runtime/index.d.mts.map +1 -1
- package/dist/runtime/index.mjs +2 -2
- package/dist/runtime/index.mjs.map +1 -1
- package/dist/runtime/mf-config.cjs +17 -1
- package/dist/runtime/mf-config.cjs.map +1 -1
- package/dist/runtime/mf-config.d.cts +36 -0
- package/dist/runtime/mf-config.d.cts.map +1 -1
- package/dist/runtime/mf-config.d.mts +36 -0
- package/dist/runtime/mf-config.d.mts.map +1 -1
- package/dist/runtime/mf-config.mjs +17 -1
- package/dist/runtime/mf-config.mjs.map +1 -1
- package/dist/runtime/services/module-federation.service.cjs +19 -11
- package/dist/runtime/services/module-federation.service.cjs.map +1 -1
- package/dist/runtime/services/module-federation.service.d.cts.map +1 -1
- package/dist/runtime/services/module-federation.service.d.mts.map +1 -1
- package/dist/runtime/services/module-federation.service.mjs +19 -11
- package/dist/runtime/services/module-federation.service.mjs.map +1 -1
- package/dist/runtime/services/plugin-lifecycle.service.cjs +5 -2
- package/dist/runtime/services/plugin-lifecycle.service.cjs.map +1 -1
- package/dist/runtime/services/plugin-lifecycle.service.mjs +6 -3
- package/dist/runtime/services/plugin-lifecycle.service.mjs.map +1 -1
- package/dist/runtime/services/plugin.service.cjs +1 -1
- package/dist/runtime/services/plugin.service.cjs.map +1 -1
- package/dist/runtime/services/plugin.service.d.cts.map +1 -1
- package/dist/runtime/services/plugin.service.d.mts.map +1 -1
- package/dist/runtime/services/plugin.service.mjs +1 -1
- package/dist/runtime/services/plugin.service.mjs.map +1 -1
- package/package.json +7 -3
- package/skills/plugin-development/SKILL.md +208 -0
- package/skills/plugin-testing/SKILL.md +152 -0
- package/src/build/rspack/dev-server-middleware.ts +1 -6
- package/src/build/rspack/plugin.ts +39 -2
- package/src/build/shared-deps.ts +16 -0
- package/src/index.ts +1 -0
- package/src/plugin.ts +8 -1
- package/src/runtime/index.ts +10 -2
- package/src/runtime/mf-config.ts +16 -0
- package/src/runtime/services/module-federation.service.ts +37 -24
- package/src/runtime/services/plugin-lifecycle.service.ts +18 -23
- package/src/runtime/services/plugin.service.ts +7 -4
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plugin-testing
|
|
3
|
+
description: Test every-plugin modules with vitest and the plugin runtime. Use when writing or modifying plugin tests under plugins/*/src/__tests__/ or plugins/*/tests/.
|
|
4
|
+
metadata:
|
|
5
|
+
sources: "src/testing/index.ts,src/runtime/index.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# every-plugin Testing
|
|
9
|
+
|
|
10
|
+
## Test Structure
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
plugins/your-plugin/
|
|
14
|
+
├── src/__tests__/
|
|
15
|
+
│ ├── integration.test.ts # Full plugin lifecycle via runtime
|
|
16
|
+
│ └── unit.test.ts # Service class in isolation
|
|
17
|
+
└── vitest.config.ts
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Unit Tests (Service Only)
|
|
21
|
+
|
|
22
|
+
Test the service class directly without the plugin runtime:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { describe, expect, it } from "vitest";
|
|
26
|
+
import { Effect } from "effect";
|
|
27
|
+
import { MyService } from "../service";
|
|
28
|
+
|
|
29
|
+
describe("MyService", () => {
|
|
30
|
+
const service = new MyService("https://api.example.com", "test-key");
|
|
31
|
+
|
|
32
|
+
it("ping returns ok", async () => {
|
|
33
|
+
const result = await Effect.runPromise(service.ping());
|
|
34
|
+
expect(result.status).toBe("ok");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("getById returns item", async () => {
|
|
38
|
+
const result = await Effect.runPromise(service.getById("item-1"));
|
|
39
|
+
expect(result.item.id).toBe("item-1");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("getById throws on missing item", async () => {
|
|
43
|
+
await expect(Effect.runPromise(service.getById("missing"))).rejects.toThrow();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Integration Tests (Plugin Runtime)
|
|
49
|
+
|
|
50
|
+
Test the full plugin lifecycle — initialization, router execution, shutdown:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { describe, expect, it, beforeAll, afterAll } from "vitest";
|
|
54
|
+
import { createPluginRuntime } from "every-plugin/runtime";
|
|
55
|
+
import Plugin from "../index";
|
|
56
|
+
|
|
57
|
+
describe("MyPlugin integration", () => {
|
|
58
|
+
let runtime: Awaited<ReturnType<typeof createPluginRuntime>>;
|
|
59
|
+
let client: any;
|
|
60
|
+
|
|
61
|
+
beforeAll(async () => {
|
|
62
|
+
runtime = createPluginRuntime();
|
|
63
|
+
const result = await runtime.usePlugin("my-plugin", {
|
|
64
|
+
variables: { baseUrl: "https://api.example.com" },
|
|
65
|
+
secrets: { apiKey: "test-key" },
|
|
66
|
+
});
|
|
67
|
+
client = result.createClient();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
afterAll(async () => {
|
|
71
|
+
await runtime.dispose();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("ping responds", async () => {
|
|
75
|
+
const result = await client.ping();
|
|
76
|
+
expect(result.status).toBe("ok");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("getById returns item", async () => {
|
|
80
|
+
const result = await client.getById({ id: "item-1" });
|
|
81
|
+
expect(result.item.id).toBe("item-1");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("getById without auth throws UNAUTHORIZED", async () => {
|
|
85
|
+
await expect(client.getById({ id: "item-1" })).rejects.toThrow();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Testing Plugin Composition
|
|
91
|
+
|
|
92
|
+
When testing an API plugin that uses `withPlugins`, mock the `pluginsClient`:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { createPluginRuntime } from "every-plugin/runtime";
|
|
96
|
+
|
|
97
|
+
const mockRegistryClient = {
|
|
98
|
+
getRegistryStatus: vi.fn().mockResolvedValue({ status: "ok" }),
|
|
99
|
+
listRegistryApps: vi.fn().mockResolvedValue({ apps: [] }),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
describe("API with mock registry", () => {
|
|
103
|
+
it("pluginDemo returns composed data", async () => {
|
|
104
|
+
const runtime = createPluginRuntime();
|
|
105
|
+
const result = await runtime.usePlugin(
|
|
106
|
+
"api",
|
|
107
|
+
{ variables: {}, secrets: {} },
|
|
108
|
+
{ registry: () => mockRegistryClient },
|
|
109
|
+
);
|
|
110
|
+
const client = result.createClient();
|
|
111
|
+
const data = await client.pluginDemo();
|
|
112
|
+
expect(data.registryStatus.status).toBe("ok");
|
|
113
|
+
await runtime.dispose();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Testing Streaming (eventIterator)
|
|
119
|
+
|
|
120
|
+
Use `for await` to collect streaming results:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
it("search streams results", async () => {
|
|
124
|
+
const chunks: any[] = [];
|
|
125
|
+
const stream = await client.search({ query: "test", limit: 5 });
|
|
126
|
+
for await (const chunk of stream) {
|
|
127
|
+
chunks.push(chunk);
|
|
128
|
+
}
|
|
129
|
+
expect(chunks.length).toBeGreaterThan(0);
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Vitest Config
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { defineConfig } from "vitest/config";
|
|
137
|
+
import tsconfigPaths from "vite-tsconfig-paths";
|
|
138
|
+
|
|
139
|
+
export default defineConfig({
|
|
140
|
+
plugins: [tsconfigPaths()],
|
|
141
|
+
test: {
|
|
142
|
+
include: ["src/__tests__/**/*.test.ts"],
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Common Mistakes
|
|
148
|
+
|
|
149
|
+
- Not calling `runtime.dispose()` in `afterAll` — leaves processes/resources running
|
|
150
|
+
- Using `vi.fn()` without `.mockResolvedValue()` — unhandled promise rejections
|
|
151
|
+
- Forgetting `vite-tsconfig-paths` plugin — path aliases like `every-plugin/orpc` won't resolve
|
|
152
|
+
- Testing only happy paths — always test error channels (Effect failures, ORPCError throws)
|
|
@@ -63,12 +63,7 @@ export function setupPluginMiddleware(
|
|
|
63
63
|
const loaded = await runtime.usePlugin(pluginId, (devConfig?.config ?? defaultConfig) as any);
|
|
64
64
|
|
|
65
65
|
cleanup = async () => {
|
|
66
|
-
if (
|
|
67
|
-
await (loaded as any).dispose();
|
|
68
|
-
}
|
|
69
|
-
if (runtime && typeof (runtime as any).cleanup === "function") {
|
|
70
|
-
await (runtime as any).cleanup();
|
|
71
|
-
}
|
|
66
|
+
if (runtime) await runtime.shutdown();
|
|
72
67
|
handlers.rpc = null;
|
|
73
68
|
handlers.api = null;
|
|
74
69
|
if (devServer.app.locals.handlers) {
|
|
@@ -14,9 +14,15 @@ export interface EveryPluginOptions {
|
|
|
14
14
|
dts?: boolean;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export interface AdditionalExport {
|
|
18
|
+
srcPath: string;
|
|
19
|
+
exportNames: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
export interface PluginManifestEmitterOptions {
|
|
18
23
|
manifestFileName?: string;
|
|
19
24
|
contractFileName?: string;
|
|
25
|
+
additionalExports?: AdditionalExport[];
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
export class EmitPluginManifest implements RspackPluginInstance {
|
|
@@ -73,7 +79,7 @@ export class EmitPluginManifest implements RspackPluginInstance {
|
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
const contractSha256 = crypto.createHash("sha256").update(contractTypes).digest("hex");
|
|
76
|
-
const manifest = {
|
|
82
|
+
const manifest: Record<string, unknown> = {
|
|
77
83
|
schemaVersion: 1,
|
|
78
84
|
kind: "every-plugin/manifest",
|
|
79
85
|
plugin: {
|
|
@@ -94,6 +100,37 @@ export class EmitPluginManifest implements RspackPluginInstance {
|
|
|
94
100
|
},
|
|
95
101
|
};
|
|
96
102
|
|
|
103
|
+
const additionalExportsEntries: Array<{ path: string; exports: string[]; sha256: string }> =
|
|
104
|
+
[];
|
|
105
|
+
|
|
106
|
+
if (this.options.additionalExports?.length) {
|
|
107
|
+
for (const additional of this.options.additionalExports) {
|
|
108
|
+
const sourcePath = path.join(context, "types", additional.srcPath);
|
|
109
|
+
const content = await tryReadFile(sourcePath);
|
|
110
|
+
if (!content) {
|
|
111
|
+
console.warn(
|
|
112
|
+
`[EmitPluginManifest] Additional export file not found at ${sourcePath}. Skipping.`,
|
|
113
|
+
);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const sha256 = crypto.createHash("sha256").update(content).digest("hex");
|
|
117
|
+
const distPath = `./types/${additional.srcPath}`;
|
|
118
|
+
additionalExportsEntries.push({
|
|
119
|
+
path: distPath,
|
|
120
|
+
exports: additional.exportNames,
|
|
121
|
+
sha256,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (rawSource) {
|
|
125
|
+
compilation.emitAsset(`types/${additional.srcPath}`, new rawSource(content));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (additionalExportsEntries.length > 0) {
|
|
130
|
+
manifest.additionalExports = additionalExportsEntries;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
97
134
|
if (rawSource) {
|
|
98
135
|
compilation.emitAsset(
|
|
99
136
|
manifestFileName,
|
|
@@ -168,7 +205,7 @@ export class EveryPluginDevServer implements RspackPluginInstance {
|
|
|
168
205
|
compiler.options.mode = process.env.NODE_ENV === "development" ? "development" : "production";
|
|
169
206
|
}
|
|
170
207
|
|
|
171
|
-
if (
|
|
208
|
+
if (compiler.options.devtool === undefined) {
|
|
172
209
|
compiler.options.devtool = "source-map";
|
|
173
210
|
}
|
|
174
211
|
|
package/src/build/shared-deps.ts
CHANGED
|
@@ -54,6 +54,22 @@ export const pluginSharedDependencies = {
|
|
|
54
54
|
version: getInstalledPackageVersion("zod", pkg.peerDependencies.zod),
|
|
55
55
|
...DEFAULT_SHARE_CONFIG,
|
|
56
56
|
},
|
|
57
|
+
"better-auth": {
|
|
58
|
+
version: getInstalledPackageVersion("better-auth", "^1.6.9"),
|
|
59
|
+
...DEFAULT_SHARE_CONFIG,
|
|
60
|
+
},
|
|
61
|
+
"drizzle-orm": {
|
|
62
|
+
version: getInstalledPackageVersion("drizzle-orm", "^0.45.1"),
|
|
63
|
+
...DEFAULT_SHARE_CONFIG,
|
|
64
|
+
},
|
|
65
|
+
"@orpc/contract": {
|
|
66
|
+
version: getInstalledPackageVersion("@orpc/contract", pkg.peerDependencies["@orpc/contract"]),
|
|
67
|
+
...DEFAULT_SHARE_CONFIG,
|
|
68
|
+
},
|
|
69
|
+
"@orpc/server": {
|
|
70
|
+
version: getInstalledPackageVersion("@orpc/server", pkg.peerDependencies["@orpc/server"]),
|
|
71
|
+
...DEFAULT_SHARE_CONFIG,
|
|
72
|
+
},
|
|
57
73
|
} satisfies SharedDependencies;
|
|
58
74
|
|
|
59
75
|
export type PluginSharedDependencyName = keyof typeof pluginSharedDependencies;
|
package/src/index.ts
CHANGED
package/src/plugin.ts
CHANGED
|
@@ -152,8 +152,15 @@ export const createPlugin: CreatePluginFn = function createPlugin<
|
|
|
152
152
|
const self = this;
|
|
153
153
|
return Effect.gen(function* () {
|
|
154
154
|
if (config.shutdown && self._deps) {
|
|
155
|
-
yield* config
|
|
155
|
+
yield* config
|
|
156
|
+
.shutdown(self._deps)
|
|
157
|
+
.pipe(
|
|
158
|
+
Effect.catchAll((error) =>
|
|
159
|
+
Effect.logWarning(`Plugin shutdown hook failed for ${self.id}`, error),
|
|
160
|
+
),
|
|
161
|
+
);
|
|
156
162
|
}
|
|
163
|
+
self._deps = null;
|
|
157
164
|
});
|
|
158
165
|
}
|
|
159
166
|
|
package/src/runtime/index.ts
CHANGED
|
@@ -165,10 +165,18 @@ export class PluginRuntime<R = RegisteredPlugins> {
|
|
|
165
165
|
if (pluginResult) {
|
|
166
166
|
yield* pluginService
|
|
167
167
|
.shutdownPlugin(pluginResult)
|
|
168
|
-
.pipe(
|
|
168
|
+
.pipe(
|
|
169
|
+
Effect.catchAll((error) =>
|
|
170
|
+
Effect.logWarning(`Failed to shutdown evicted plugin ${pluginId}`, error),
|
|
171
|
+
),
|
|
172
|
+
);
|
|
169
173
|
}
|
|
170
174
|
}
|
|
171
|
-
}).pipe(
|
|
175
|
+
}).pipe(
|
|
176
|
+
Effect.catchAll((error) =>
|
|
177
|
+
Effect.logWarning(`Plugin eviction failed for ${pluginId}`, error),
|
|
178
|
+
),
|
|
179
|
+
);
|
|
172
180
|
|
|
173
181
|
return this.runPromise(effect);
|
|
174
182
|
}
|
package/src/runtime/mf-config.ts
CHANGED
|
@@ -43,6 +43,22 @@ export const MF_SHARED_DEPS = {
|
|
|
43
43
|
version: getInstalledPackageVersion("zod", "^4.3.6"),
|
|
44
44
|
shareConfig: SHARE_CONFIG,
|
|
45
45
|
},
|
|
46
|
+
"better-auth": {
|
|
47
|
+
version: getInstalledPackageVersion("better-auth", "^1.6.9"),
|
|
48
|
+
shareConfig: SHARE_CONFIG,
|
|
49
|
+
},
|
|
50
|
+
"drizzle-orm": {
|
|
51
|
+
version: getInstalledPackageVersion("drizzle-orm", "^0.45.1"),
|
|
52
|
+
shareConfig: SHARE_CONFIG,
|
|
53
|
+
},
|
|
54
|
+
"@orpc/contract": {
|
|
55
|
+
version: getInstalledPackageVersion("@orpc/contract", "^1.13.4"),
|
|
56
|
+
shareConfig: SHARE_CONFIG,
|
|
57
|
+
},
|
|
58
|
+
"@orpc/server": {
|
|
59
|
+
version: getInstalledPackageVersion("@orpc/server", "^1.13.4"),
|
|
60
|
+
shareConfig: SHARE_CONFIG,
|
|
61
|
+
},
|
|
46
62
|
} as const;
|
|
47
63
|
|
|
48
64
|
export type SharedDepName = keyof typeof MF_SHARED_DEPS;
|
|
@@ -12,38 +12,49 @@ const sharedModuleLoaders = {
|
|
|
12
12
|
"every-plugin": () => import("every-plugin"),
|
|
13
13
|
effect: () => import("effect"),
|
|
14
14
|
zod: () => import("zod"),
|
|
15
|
+
"better-auth": () => import("better-auth"),
|
|
16
|
+
"drizzle-orm": () => import("drizzle-orm"),
|
|
17
|
+
"@orpc/contract": () => import("@orpc/contract"),
|
|
18
|
+
"@orpc/server": () => import("@orpc/server"),
|
|
15
19
|
} satisfies Record<SharedDepName, () => Promise<unknown>>;
|
|
16
20
|
|
|
21
|
+
function buildSharedConfig(): Record<
|
|
22
|
+
string,
|
|
23
|
+
{
|
|
24
|
+
version: string;
|
|
25
|
+
get: () => Promise<() => unknown>;
|
|
26
|
+
shareConfig: (typeof MF_SHARED_DEPS)[SharedDepName]["shareConfig"];
|
|
27
|
+
}
|
|
28
|
+
> {
|
|
29
|
+
return Object.fromEntries(
|
|
30
|
+
(
|
|
31
|
+
Object.entries(MF_SHARED_DEPS) as [SharedDepName, (typeof MF_SHARED_DEPS)[SharedDepName]][]
|
|
32
|
+
).map(([name, config]) => {
|
|
33
|
+
const load = sharedModuleLoaders[name];
|
|
34
|
+
|
|
35
|
+
if (!load) {
|
|
36
|
+
throw new Error(`Missing shared module loader for ${name}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return [
|
|
40
|
+
name,
|
|
41
|
+
{
|
|
42
|
+
version: config.version,
|
|
43
|
+
get: () => load().then((mod) => () => mod),
|
|
44
|
+
shareConfig: config.shareConfig,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
17
51
|
const createModuleFederationInstance = Effect.cached(
|
|
18
52
|
Effect.sync(() => {
|
|
19
53
|
try {
|
|
54
|
+
const shared = buildSharedConfig();
|
|
20
55
|
let instance = getInstance();
|
|
21
56
|
|
|
22
57
|
if (!instance) {
|
|
23
|
-
const shared = Object.fromEntries(
|
|
24
|
-
(
|
|
25
|
-
Object.entries(MF_SHARED_DEPS) as [
|
|
26
|
-
SharedDepName,
|
|
27
|
-
(typeof MF_SHARED_DEPS)[SharedDepName],
|
|
28
|
-
][]
|
|
29
|
-
).map(([name, config]) => {
|
|
30
|
-
const load = sharedModuleLoaders[name];
|
|
31
|
-
|
|
32
|
-
if (!load) {
|
|
33
|
-
throw new Error(`Missing shared module loader for ${name}`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return [
|
|
37
|
-
name,
|
|
38
|
-
{
|
|
39
|
-
version: config.version,
|
|
40
|
-
get: () => load().then((mod) => () => mod),
|
|
41
|
-
shareConfig: config.shareConfig,
|
|
42
|
-
},
|
|
43
|
-
];
|
|
44
|
-
}),
|
|
45
|
-
);
|
|
46
|
-
|
|
47
58
|
instance = createInstance({
|
|
48
59
|
name: "host",
|
|
49
60
|
remotes: [],
|
|
@@ -51,6 +62,8 @@ const createModuleFederationInstance = Effect.cached(
|
|
|
51
62
|
});
|
|
52
63
|
|
|
53
64
|
setGlobalFederationInstance(instance);
|
|
65
|
+
} else {
|
|
66
|
+
instance.registerShared(shared);
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
return instance;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Ref } from "effect";
|
|
1
|
+
import { Effect, Exit, Ref, Scope } from "effect";
|
|
2
2
|
import type { AnyPlugin, InitializedPlugin } from "../../types";
|
|
3
3
|
import { toPluginRuntimeError } from "../errors";
|
|
4
4
|
|
|
@@ -50,36 +50,31 @@ export class PluginLifecycleService extends Effect.Service<PluginLifecycleServic
|
|
|
50
50
|
Effect.gen(function* () {
|
|
51
51
|
const plugins = yield* Ref.get(activePlugins);
|
|
52
52
|
|
|
53
|
-
// Shutdown all active plugins concurrently
|
|
54
53
|
yield* Effect.forEach(
|
|
55
54
|
plugins,
|
|
56
55
|
(plugin) =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
Effect.gen(function* () {
|
|
57
|
+
yield* plugin.plugin
|
|
58
|
+
.shutdown()
|
|
59
|
+
.pipe(
|
|
60
|
+
Effect.catchAll((error) =>
|
|
61
|
+
Effect.logWarning(`Failed to shutdown plugin ${plugin.plugin.id}`, error),
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
yield* Scope.close(plugin.scope, Exit.succeed(undefined)).pipe(
|
|
65
|
+
Effect.catchAll((error) =>
|
|
66
|
+
Effect.logWarning(
|
|
67
|
+
`Failed to close scope for plugin ${plugin.plugin.id}`,
|
|
68
|
+
error,
|
|
69
|
+
),
|
|
65
70
|
),
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
Effect.logError(
|
|
69
|
-
`Failed to shutdown plugin ${plugin.plugin.id} during cleanup`,
|
|
70
|
-
error,
|
|
71
|
-
{ pluginId: plugin.plugin.id },
|
|
72
|
-
),
|
|
73
|
-
),
|
|
74
|
-
),
|
|
71
|
+
);
|
|
72
|
+
}),
|
|
75
73
|
{ concurrency: "unbounded" },
|
|
76
74
|
);
|
|
77
75
|
|
|
78
|
-
// Clear the active plugins set
|
|
79
76
|
yield* Ref.set(activePlugins, new Set());
|
|
80
|
-
}).pipe(
|
|
81
|
-
Effect.catchAll(() => Effect.void), // Convert errors to void for cleanup
|
|
82
|
-
),
|
|
77
|
+
}).pipe(Effect.catchAll((error) => Effect.logWarning("Plugin cleanup failed", error))),
|
|
83
78
|
};
|
|
84
79
|
}),
|
|
85
80
|
},
|
|
@@ -27,13 +27,16 @@ export class PluginService extends Effect.Service<PluginService>()("PluginServic
|
|
|
27
27
|
initializePlugin: loader.initializePlugin,
|
|
28
28
|
shutdownPlugin: (plugin: InitializedPlugin<AnyPlugin>) =>
|
|
29
29
|
Effect.gen(function* () {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
yield* plugin.plugin
|
|
31
|
+
.shutdown()
|
|
32
|
+
.pipe(
|
|
33
|
+
Effect.catchAll((error) =>
|
|
34
|
+
Effect.logWarning(`Failed to shutdown plugin ${plugin.plugin.id}`, error),
|
|
35
|
+
),
|
|
36
|
+
);
|
|
32
37
|
|
|
33
|
-
// Close the plugin scope to interrupt fibers and release resources
|
|
34
38
|
yield* Scope.close(plugin.scope, Exit.succeed(undefined));
|
|
35
39
|
|
|
36
|
-
// Unregister from lifecycle tracking
|
|
37
40
|
yield* lifecycle.unregister(plugin);
|
|
38
41
|
}),
|
|
39
42
|
cleanup: lifecycle.cleanup,
|