mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Merge PR #2926: fix Turbopack Docker build — plugin loader fork→spawn (+ IPC test)
URGENT: Fix Docker release build failure for v3.8.7
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
/**
|
||||
* Plugin loader — loads plugins in isolated child processes.
|
||||
*
|
||||
* Uses child_process.fork() for process-level isolation. Each plugin
|
||||
* Uses a child Node.js process with IPC for process-level isolation. Each plugin
|
||||
* runs in a separate Node.js process with restricted environment.
|
||||
* Complies with Rule 3 (no eval/new Function/implied eval).
|
||||
*
|
||||
* @module plugins/loader
|
||||
*/
|
||||
|
||||
import { fork } from "child_process";
|
||||
import { spawn } from "child_process";
|
||||
import { writeFile, rm } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import { tmpdir } from "os";
|
||||
@@ -29,7 +29,7 @@ export interface LoadedPlugin {
|
||||
cleanup: () => void;
|
||||
}
|
||||
|
||||
// ── Plugin host script (runs in child process via fork) ──
|
||||
// ── Plugin host script (runs in child process over IPC) ──
|
||||
// Uses process.send()/process.on("message") — NOT worker_threads.
|
||||
// Written as .mjs to force ESM execution regardless of package.json.
|
||||
|
||||
@@ -83,10 +83,9 @@ export async function loadPlugin(
|
||||
PLUGIN_NAME: manifest.name,
|
||||
};
|
||||
|
||||
const child = fork(hostScriptPath, [entryPoint], {
|
||||
const child = spawn(process.execPath, ["--no-warnings", hostScriptPath, entryPoint], {
|
||||
env,
|
||||
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
||||
execArgv: ["--no-warnings"],
|
||||
stdio: ["ignore", "ignore", "ignore", "ipc"],
|
||||
});
|
||||
|
||||
// Track pending calls with timeout support
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
// Loader uses child_process.fork() — we test the module exports and types,
|
||||
// not actual fork behavior (that requires integration tests with real plugins).
|
||||
|
||||
import type { LoadedPlugin } from "../../src/lib/plugins/loader.ts";
|
||||
import { loadPlugin, type LoadedPlugin } from "../../src/lib/plugins/loader.ts";
|
||||
import type { Plugin, PluginContext, PluginResult } from "../../src/lib/plugins/index.ts";
|
||||
|
||||
// ── Type checks ──
|
||||
@@ -39,7 +39,7 @@ test("Plugin interface supports lifecycle hooks", () => {
|
||||
onRequest: async (_ctx: PluginContext): Promise<PluginResult | void> => {
|
||||
return { blocked: false };
|
||||
},
|
||||
onResponse: async (_ctx: PluginContext, response: any) => response,
|
||||
onResponse: async (_ctx: PluginContext, response: unknown) => response,
|
||||
onError: async (_ctx: PluginContext, _error: Error) => null,
|
||||
};
|
||||
assert.equal(typeof plugin.onRequest, "function");
|
||||
@@ -76,3 +76,57 @@ test("PluginResult supports body modification", () => {
|
||||
assert.equal(modified.body.model, "gpt-4-turbo");
|
||||
assert.equal(modified.metadata?.plugin, "model-switcher");
|
||||
});
|
||||
|
||||
test(
|
||||
"loadPlugin runs hooks in an isolated child process over IPC",
|
||||
{ timeout: 5_000 },
|
||||
async (t) => {
|
||||
const pluginDir = await mkdtemp(join(tmpdir(), "omniroute-plugin-loader-"));
|
||||
const entryPoint = join(pluginDir, "index.mjs");
|
||||
let loaded: LoadedPlugin | undefined;
|
||||
|
||||
t.after(async () => {
|
||||
loaded?.cleanup();
|
||||
await rm(pluginDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
await writeFile(
|
||||
entryPoint,
|
||||
`
|
||||
export async function onRequest(ctx) {
|
||||
return {
|
||||
body: { ...ctx.body, touchedByPlugin: true },
|
||||
metadata: { pluginHook: "onRequest" },
|
||||
};
|
||||
}
|
||||
`,
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
loaded = await loadPlugin(entryPoint, {
|
||||
name: "ipc-test",
|
||||
version: "1.0.0",
|
||||
license: "MIT",
|
||||
main: "index.mjs",
|
||||
source: "local",
|
||||
tags: [],
|
||||
requires: { permissions: [] },
|
||||
hooks: { onRequest: true, onResponse: false, onError: false },
|
||||
skills: [],
|
||||
enabledByDefault: false,
|
||||
configSchema: {},
|
||||
});
|
||||
|
||||
const result = await loaded.plugin.onRequest?.({
|
||||
requestId: "test-request",
|
||||
body: { model: "gpt-4" },
|
||||
model: "gpt-4",
|
||||
metadata: {},
|
||||
});
|
||||
|
||||
assert.deepEqual(result, {
|
||||
body: { model: "gpt-4", touchedByPlugin: true },
|
||||
metadata: { pluginHook: "onRequest" },
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user