mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
fix: load staged dist-runtime plugins in docker
This commit is contained in:
@@ -636,7 +636,8 @@ function applyBundledPluginRuntimeHotfixes(params = {}) {
|
||||
export function isSourceCheckoutRoot(params) {
|
||||
const pathExists = params.existsSync ?? existsSync;
|
||||
return (
|
||||
pathExists(join(params.packageRoot, ".git")) &&
|
||||
(pathExists(join(params.packageRoot, ".git")) ||
|
||||
pathExists(join(params.packageRoot, "pnpm-workspace.yaml"))) &&
|
||||
pathExists(join(params.packageRoot, "src")) &&
|
||||
pathExists(join(params.packageRoot, "extensions"))
|
||||
);
|
||||
|
||||
@@ -140,15 +140,35 @@ function shouldCopyRuntimeFile(sourcePath) {
|
||||
);
|
||||
}
|
||||
|
||||
function hasDefaultExport(sourcePath) {
|
||||
const text = fs.readFileSync(sourcePath, "utf8");
|
||||
return /\bexport\s+default\b/u.test(text) || /\bas\s+default\b/u.test(text);
|
||||
}
|
||||
|
||||
function writeRuntimeModuleWrapper(sourcePath, targetPath) {
|
||||
const specifier = relativeSymlinkTarget(sourcePath, targetPath).replace(/\\/g, "/");
|
||||
const normalizedSpecifier = specifier.startsWith(".") ? specifier : `./${specifier}`;
|
||||
const defaultForwarder = hasDefaultExport(sourcePath)
|
||||
? [
|
||||
`import defaultModule from ${JSON.stringify(normalizedSpecifier)};`,
|
||||
`let defaultExport = defaultModule;`,
|
||||
`for (let index = 0; index < 4 && defaultExport && typeof defaultExport === "object" && "default" in defaultExport; index += 1) {`,
|
||||
` defaultExport = defaultExport.default;`,
|
||||
`}`,
|
||||
]
|
||||
: [
|
||||
`import * as module from ${JSON.stringify(normalizedSpecifier)};`,
|
||||
`let defaultExport = "default" in module ? module.default : module;`,
|
||||
`for (let index = 0; index < 4 && defaultExport && typeof defaultExport === "object" && "default" in defaultExport; index += 1) {`,
|
||||
` defaultExport = defaultExport.default;`,
|
||||
`}`,
|
||||
];
|
||||
fs.writeFileSync(
|
||||
targetPath,
|
||||
[
|
||||
`export * from ${JSON.stringify(normalizedSpecifier)};`,
|
||||
`import * as module from ${JSON.stringify(normalizedSpecifier)};`,
|
||||
"export default module.default;",
|
||||
...defaultForwarder,
|
||||
"export { defaultExport as default };",
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
|
||||
@@ -16,12 +16,22 @@ type AcpRuntimeRegistryGlobalState = {
|
||||
const ACP_RUNTIME_REGISTRY_STATE_KEY = Symbol.for("openclaw.acpRuntimeRegistryState");
|
||||
|
||||
function resolveAcpRuntimeRegistryGlobalState(): AcpRuntimeRegistryGlobalState {
|
||||
return resolveGlobalSingleton<AcpRuntimeRegistryGlobalState>(
|
||||
const processStore = process as NodeJS.Process & Record<PropertyKey, unknown>;
|
||||
const existing = processStore[ACP_RUNTIME_REGISTRY_STATE_KEY];
|
||||
if (existing) {
|
||||
return existing as AcpRuntimeRegistryGlobalState;
|
||||
}
|
||||
const created = resolveGlobalSingleton<AcpRuntimeRegistryGlobalState>(
|
||||
ACP_RUNTIME_REGISTRY_STATE_KEY,
|
||||
() => ({
|
||||
backendsById: new Map<string, AcpRuntimeBackend>(),
|
||||
}),
|
||||
);
|
||||
// ACP runtime backends are registered from bundled plugin code and read from
|
||||
// core/test code. In Vitest and Jiti, those can run in different globalThis
|
||||
// contexts while still sharing one Node process.
|
||||
processStore[ACP_RUNTIME_REGISTRY_STATE_KEY] = created;
|
||||
return created;
|
||||
}
|
||||
|
||||
const ACP_BACKENDS_BY_ID = resolveAcpRuntimeRegistryGlobalState().backendsById;
|
||||
|
||||
@@ -17,6 +17,7 @@ export async function writeFakeClaudeCli(filePath: string): Promise<void> {
|
||||
`#!/usr/bin/env node
|
||||
import fs from "node:fs/promises";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import readline from "node:readline/promises";
|
||||
import { Client } from ${JSON.stringify(SDK_CLIENT_INDEX_PATH)};
|
||||
import { StdioClientTransport } from ${JSON.stringify(SDK_CLIENT_STDIO_PATH)};
|
||||
|
||||
@@ -39,6 +40,17 @@ if (!mcpConfigPath) {
|
||||
throw new Error("missing --mcp-config");
|
||||
}
|
||||
|
||||
const input = readline.createInterface({ input: process.stdin });
|
||||
try {
|
||||
for await (const line of input) {
|
||||
if (line.trim()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
input.close();
|
||||
}
|
||||
|
||||
const raw = JSON.parse(await fs.readFile(mcpConfigPath, "utf-8"));
|
||||
const servers = raw?.mcpServers ?? raw?.servers ?? {};
|
||||
const server = servers.bundleProbe ?? Object.values(servers)[0];
|
||||
@@ -75,8 +87,9 @@ const text = Array.isArray(result.content)
|
||||
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
type: "result",
|
||||
session_id: readArg("--session-id") ?? randomUUID(),
|
||||
message: "BUNDLE MCP OK " + text,
|
||||
result: "BUNDLE MCP OK " + text,
|
||||
}) + "\\n",
|
||||
);
|
||||
`,
|
||||
|
||||
@@ -1126,7 +1126,7 @@ export async function runEmbeddedAttempt(
|
||||
|
||||
({ session } = await createEmbeddedAgentSessionWithResourceLoader({
|
||||
createAgentSession: async (options) =>
|
||||
await createAgentSession(options as Parameters<typeof createAgentSession>[0]),
|
||||
await createAgentSession(options as unknown as Parameters<typeof createAgentSession>[0]),
|
||||
options: {
|
||||
cwd: resolvedWorkspace,
|
||||
agentDir,
|
||||
|
||||
@@ -8,9 +8,11 @@ import { getAcpRuntimeBackend } from "../acp/runtime/registry.js";
|
||||
import { isLiveTestEnabled } from "../agents/live-test-helpers.js";
|
||||
import { clearConfigCache, clearRuntimeConfigSnapshot, loadConfig } from "../config/config.js";
|
||||
import { isTruthyEnvValue } from "../infra/env.js";
|
||||
import { clearPluginLoaderCache } from "../plugins/loader.js";
|
||||
import {
|
||||
pinActivePluginChannelRegistry,
|
||||
releasePinnedPluginChannelRegistry,
|
||||
resetPluginRuntimeStateForTest,
|
||||
} from "../plugins/runtime.js";
|
||||
import { extractFirstTextBlock } from "../shared/chat-message-content.js";
|
||||
import { createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
@@ -503,6 +505,8 @@ describeLive("gateway live (ACP bind)", () => {
|
||||
process.env.OPENCLAW_CONFIG_PATH = tempConfigPath;
|
||||
clearConfigCache();
|
||||
clearRuntimeConfigSnapshot();
|
||||
clearPluginLoaderCache();
|
||||
resetPluginRuntimeStateForTest();
|
||||
|
||||
logLiveStep(`starting gateway on port ${String(port)}`);
|
||||
const server = await startGatewayServer(port, {
|
||||
|
||||
@@ -42,6 +42,21 @@ describe("gateway codex harness live helpers", () => {
|
||||
expect(isExpectedCodexModelsCommandText(text)).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts missing codex CLI fallback output", () => {
|
||||
const text = [
|
||||
"`codex` is not installed on the shell PATH in this environment.",
|
||||
"",
|
||||
"Command result:",
|
||||
"```text",
|
||||
"/bin/bash: line 1: codex: command not found",
|
||||
"```",
|
||||
].join("\n");
|
||||
|
||||
expect(
|
||||
EXPECTED_CODEX_MODELS_COMMAND_TEXT.some((expectedText) => text.includes(expectedText)),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts sandbox escalation rejection for codex models", () => {
|
||||
const texts = [
|
||||
"I couldn’t list them because `codex models` requires running outside the sandbox here, and that approval was rejected.",
|
||||
|
||||
@@ -13,6 +13,7 @@ export const EXPECTED_CODEX_MODELS_COMMAND_TEXT = [
|
||||
"`codex models` failed in this sandbox",
|
||||
"`codex models` could not be run in this sandbox.",
|
||||
"`codex models` is not runnable in this sandboxed session.",
|
||||
"`codex` is not installed on the shell PATH in this environment.",
|
||||
"`codex models` didn’t return a plain list in this environment",
|
||||
"I couldn’t get a direct `codex models` CLI listing because the local sandbox blocked that command.",
|
||||
"I couldn’t list all installed/available Codex models from the local CLI because the sandboxed `codex` command failed to start in this environment.",
|
||||
|
||||
@@ -603,6 +603,50 @@ describe("ensureBundledPluginRuntimeDeps", () => {
|
||||
expect(resolveBundledRuntimeDependencyInstallRoot(pluginRoot, { env: {} })).toBe(pluginRoot);
|
||||
});
|
||||
|
||||
it("treats Docker build source trees without .git as source checkouts", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
fs.mkdirSync(path.join(packageRoot, "src"), { recursive: true });
|
||||
fs.writeFileSync(path.join(packageRoot, "pnpm-workspace.yaml"), "packages:\n - .\n");
|
||||
const pluginRoot = path.join(packageRoot, "extensions", "acpx");
|
||||
fs.mkdirSync(pluginRoot, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "package.json"),
|
||||
JSON.stringify({
|
||||
dependencies: {
|
||||
acpx: "0.5.3",
|
||||
},
|
||||
devDependencies: {
|
||||
"@openclaw/plugin-sdk": "workspace:*",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const calls: BundledRuntimeDepsInstallParams[] = [];
|
||||
const result = ensureBundledPluginRuntimeDeps({
|
||||
env: {},
|
||||
installDeps: (params) => {
|
||||
calls.push(params);
|
||||
},
|
||||
pluginId: "acpx",
|
||||
pluginRoot,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
installedSpecs: ["acpx@0.5.3"],
|
||||
retainSpecs: ["acpx@0.5.3"],
|
||||
});
|
||||
expect(calls).toEqual([
|
||||
{
|
||||
installRoot: pluginRoot,
|
||||
installExecutionRoot: expect.stringContaining(
|
||||
path.join(".local", "bundled-plugin-runtime-deps"),
|
||||
),
|
||||
missingSpecs: ["acpx@0.5.3"],
|
||||
installSpecs: ["acpx@0.5.3"],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not trust package-root runtime deps for source-checkout bundled plugins", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
const stageDir = makeTempDir();
|
||||
|
||||
@@ -172,7 +172,8 @@ function collectRuntimeDeps(packageJson: JsonObject): Record<string, unknown> {
|
||||
|
||||
function isSourceCheckoutRoot(packageRoot: string): boolean {
|
||||
return (
|
||||
fs.existsSync(path.join(packageRoot, ".git")) &&
|
||||
(fs.existsSync(path.join(packageRoot, ".git")) ||
|
||||
fs.existsSync(path.join(packageRoot, "pnpm-workspace.yaml"))) &&
|
||||
fs.existsSync(path.join(packageRoot, "src")) &&
|
||||
fs.existsSync(path.join(packageRoot, "extensions"))
|
||||
);
|
||||
|
||||
@@ -1262,6 +1262,102 @@ module.exports = {
|
||||
expect(registry.plugins.find((entry) => entry.id === "alpha")?.status).toBe("loaded");
|
||||
});
|
||||
|
||||
it("loads dist-runtime wrappers from an external stage dir", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
const stageDir = makeTempDir();
|
||||
const bundledDir = path.join(packageRoot, "dist-runtime", "extensions");
|
||||
const pluginRoot = path.join(bundledDir, "acpx");
|
||||
const canonicalPluginRoot = path.join(packageRoot, "dist", "extensions", "acpx");
|
||||
fs.mkdirSync(pluginRoot, { recursive: true });
|
||||
fs.mkdirSync(canonicalPluginRoot, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "index.js"),
|
||||
[
|
||||
`export * from "../../../dist/extensions/acpx/index.js";`,
|
||||
`import defaultModule from "../../../dist/extensions/acpx/index.js";`,
|
||||
`export default defaultModule;`,
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(canonicalPluginRoot, "index.js"),
|
||||
[
|
||||
`import runtimeDep from "external-runtime";`,
|
||||
`export default {`,
|
||||
` id: "acpx",`,
|
||||
` register(api) {`,
|
||||
` api.registerCommand({ name: "external-runtime", handler: () => runtimeDep.marker });`,
|
||||
` },`,
|
||||
`};`,
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf-8",
|
||||
);
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledDir;
|
||||
process.env.OPENCLAW_PLUGIN_STAGE_DIR = stageDir;
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "@openclaw/acpx",
|
||||
version: "1.0.0",
|
||||
type: "module",
|
||||
dependencies: {
|
||||
"external-runtime": "1.0.0",
|
||||
},
|
||||
openclaw: { extensions: ["./index.js"] },
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginRoot, "openclaw.plugin.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
id: "acpx",
|
||||
enabledByDefault: true,
|
||||
configSchema: EMPTY_PLUGIN_SCHEMA,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
config: {
|
||||
plugins: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
bundledRuntimeDepsInstaller: ({ installRoot }) => {
|
||||
const depRoot = path.join(installRoot, "node_modules", "external-runtime");
|
||||
fs.mkdirSync(depRoot, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(depRoot, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "external-runtime",
|
||||
version: "1.0.0",
|
||||
type: "module",
|
||||
exports: "./index.js",
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(depRoot, "index.js"),
|
||||
"export default { marker: 'dist-runtime-ok' };\n",
|
||||
"utf-8",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
expect(registry.plugins.find((entry) => entry.id === "acpx")?.status).toBe("loaded");
|
||||
});
|
||||
|
||||
it("loads source-checkout bundled runtime deps without mirroring the repo tree", () => {
|
||||
const packageRoot = makeTempDir();
|
||||
fs.mkdirSync(path.join(packageRoot, ".git"), { recursive: true });
|
||||
|
||||
@@ -437,7 +437,8 @@ function toSafeImportPath(specifier: string): string {
|
||||
function createPluginJitiLoader(options: Pick<PluginLoadOptions, "pluginSdkResolution">) {
|
||||
const jitiLoaders: PluginJitiLoaderCache = new Map();
|
||||
return (modulePath: string) => {
|
||||
const tryNative = shouldPreferNativeJiti(modulePath);
|
||||
const tryNative =
|
||||
shouldPreferNativeJiti(modulePath) && !isBundledRuntimeDependencyMirrorPath(modulePath);
|
||||
return getCachedPluginJitiLoader({
|
||||
cache: jitiLoaders,
|
||||
modulePath,
|
||||
@@ -453,8 +454,32 @@ function createPluginJitiLoader(options: Pick<PluginLoadOptions, "pluginSdkResol
|
||||
};
|
||||
}
|
||||
|
||||
function resolveCanonicalDistRuntimeSource(source: string): string {
|
||||
const marker = `${path.sep}dist-runtime${path.sep}extensions${path.sep}`;
|
||||
const index = source.indexOf(marker);
|
||||
if (index === -1) {
|
||||
return source;
|
||||
}
|
||||
const candidate = `${source.slice(0, index)}${path.sep}dist${path.sep}extensions${path.sep}${source.slice(index + marker.length)}`;
|
||||
return fs.existsSync(candidate) ? candidate : source;
|
||||
}
|
||||
|
||||
const registeredBundledRuntimeDepNodePaths = new Set<string>();
|
||||
|
||||
function isBundledRuntimeDependencyMirrorPath(modulePath: string): boolean {
|
||||
const resolvedModulePath = path.resolve(modulePath);
|
||||
for (const nodeModulesDir of registeredBundledRuntimeDepNodePaths) {
|
||||
const installRoot = path.dirname(nodeModulesDir);
|
||||
if (
|
||||
resolvedModulePath === installRoot ||
|
||||
resolvedModulePath.startsWith(`${installRoot}${path.sep}`)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function registerBundledRuntimeDependencyNodePath(installRoot: string): void {
|
||||
const nodeModulesDir = path.join(installRoot, "node_modules");
|
||||
if (registeredBundledRuntimeDepNodePaths.has(nodeModulesDir) || !fs.existsSync(nodeModulesDir)) {
|
||||
@@ -529,7 +554,8 @@ function prepareBundledPluginRuntimeDistMirror(params: {
|
||||
}): string {
|
||||
const sourceExtensionsRoot = path.dirname(params.pluginRoot);
|
||||
const sourceDistRoot = path.dirname(sourceExtensionsRoot);
|
||||
const mirrorDistRoot = path.join(params.installRoot, "dist");
|
||||
const sourceDistRootName = path.basename(sourceDistRoot);
|
||||
const mirrorDistRoot = path.join(params.installRoot, sourceDistRootName);
|
||||
const mirrorExtensionsRoot = path.join(mirrorDistRoot, "extensions");
|
||||
fs.mkdirSync(mirrorExtensionsRoot, { recursive: true, mode: 0o755 });
|
||||
for (const entry of fs.readdirSync(sourceDistRoot, { withFileTypes: true })) {
|
||||
@@ -551,6 +577,24 @@ function prepareBundledPluginRuntimeDistMirror(params: {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sourceDistRootName === "dist-runtime") {
|
||||
const sourceCanonicalDistRoot = path.join(path.dirname(sourceDistRoot), "dist");
|
||||
const targetCanonicalDistRoot = path.join(params.installRoot, "dist");
|
||||
if (fs.existsSync(sourceCanonicalDistRoot)) {
|
||||
const targetMatchesSource =
|
||||
fs.existsSync(targetCanonicalDistRoot) &&
|
||||
safeRealpathOrResolve(targetCanonicalDistRoot) ===
|
||||
safeRealpathOrResolve(sourceCanonicalDistRoot);
|
||||
if (!targetMatchesSource) {
|
||||
fs.rmSync(targetCanonicalDistRoot, { recursive: true, force: true });
|
||||
try {
|
||||
fs.symlinkSync(sourceCanonicalDistRoot, targetCanonicalDistRoot, "junction");
|
||||
} catch {
|
||||
copyBundledPluginRuntimeRoot(sourceCanonicalDistRoot, targetCanonicalDistRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mirrorExtensionsRoot;
|
||||
}
|
||||
|
||||
@@ -938,7 +982,33 @@ function resolvePluginModuleExport(moduleExport: unknown): {
|
||||
definition?: OpenClawPluginDefinition;
|
||||
register?: OpenClawPluginDefinition["register"];
|
||||
} {
|
||||
const resolved = unwrapDefaultModuleExport(moduleExport);
|
||||
const seen = new Set<unknown>();
|
||||
const candidates: unknown[] = [unwrapDefaultModuleExport(moduleExport), moduleExport];
|
||||
for (let index = 0; index < candidates.length && index < 12; index += 1) {
|
||||
const resolved = candidates[index];
|
||||
if (seen.has(resolved)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(resolved);
|
||||
if (typeof resolved === "function") {
|
||||
return {
|
||||
register: resolved as OpenClawPluginDefinition["register"],
|
||||
};
|
||||
}
|
||||
if (resolved && typeof resolved === "object") {
|
||||
const def = resolved as OpenClawPluginDefinition;
|
||||
const register = def.register ?? def.activate;
|
||||
if (typeof register === "function") {
|
||||
return { definition: def, register };
|
||||
}
|
||||
for (const key of ["default", "module"]) {
|
||||
if (key in def) {
|
||||
candidates.push((def as Record<string, unknown>)[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const resolved = candidates[0];
|
||||
if (typeof resolved === "function") {
|
||||
return {
|
||||
register: resolved as OpenClawPluginDefinition["register"],
|
||||
@@ -2132,9 +2202,11 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
runtimeSetupSource
|
||||
? runtimeSetupSource
|
||||
: runtimeCandidateSource;
|
||||
const moduleLoadSource = resolveCanonicalDistRuntimeSource(loadSource);
|
||||
const moduleRoot = resolveCanonicalDistRuntimeSource(runtimePluginRoot);
|
||||
const opened = openBoundaryFileSync({
|
||||
absolutePath: loadSource,
|
||||
rootPath: runtimePluginRoot,
|
||||
absolutePath: moduleLoadSource,
|
||||
rootPath: moduleRoot,
|
||||
boundaryLabel: "plugin root",
|
||||
rejectHardlinks: candidate.origin !== "bundled",
|
||||
skipLexicalRootCheck: true,
|
||||
|
||||
@@ -53,6 +53,23 @@ function expectRuntimePluginWrapperContains(params: {
|
||||
expect(fs.readFileSync(runtimePath, "utf8")).toContain(params.expectedImport);
|
||||
}
|
||||
|
||||
function expectRuntimePluginWrapperForwardsDefault(params: {
|
||||
repoRoot: string;
|
||||
pluginId: string;
|
||||
expectedImport: string;
|
||||
}) {
|
||||
const runtimePath = path.join(
|
||||
params.repoRoot,
|
||||
"dist-runtime",
|
||||
"extensions",
|
||||
params.pluginId,
|
||||
"index.js",
|
||||
);
|
||||
expect(fs.readFileSync(runtimePath, "utf8")).toContain(
|
||||
`import defaultModule from "${params.expectedImport}";`,
|
||||
);
|
||||
}
|
||||
|
||||
function expectRuntimeArtifactText(params: {
|
||||
repoRoot: string;
|
||||
pluginId: string;
|
||||
@@ -102,6 +119,11 @@ describe("stageBundledPluginRuntime", () => {
|
||||
pluginId: "diffs",
|
||||
expectedImport: distRuntimeImportPath("diffs"),
|
||||
});
|
||||
expectRuntimePluginWrapperForwardsDefault({
|
||||
repoRoot,
|
||||
pluginId: "diffs",
|
||||
expectedImport: distRuntimeImportPath("diffs"),
|
||||
});
|
||||
expect(fs.lstatSync(path.join(runtimePluginDir, "node_modules")).isSymbolicLink()).toBe(true);
|
||||
expect(fs.realpathSync(path.join(runtimePluginDir, "node_modules"))).toBe(
|
||||
fs.realpathSync(path.join(distPluginDir, "node_modules")),
|
||||
|
||||
Reference in New Issue
Block a user