fix: normalize plugin SDK aliases on Windows

This commit is contained in:
Peter Steinberger
2026-04-05 15:57:13 +01:00
parent 65f18d6e24
commit 3a4b96bfbf
4 changed files with 83 additions and 10 deletions

View File

@@ -125,14 +125,16 @@ function buildPluginSdkAliasMap(useDist) {
const packageRoot = getPackageRoot();
const pluginSdkDir = path.join(packageRoot, useDist ? "dist" : "src", "plugin-sdk");
const ext = useDist ? ".js" : ".ts";
const normalizeTarget = (target) =>
process.platform === "win32" ? target.replace(/\\/g, "/") : target;
const aliasMap = {
"openclaw/plugin-sdk": __filename,
"openclaw/plugin-sdk": normalizeTarget(__filename),
};
for (const subpath of listPluginSdkExportedSubpaths()) {
const candidate = path.join(pluginSdkDir, `${subpath}${ext}`);
if (fs.existsSync(candidate)) {
aliasMap[`openclaw/plugin-sdk/${subpath}`] = candidate;
aliasMap[`openclaw/plugin-sdk/${subpath}`] = normalizeTarget(candidate);
}
}
@@ -140,20 +142,22 @@ function buildPluginSdkAliasMap(useDist) {
}
function getJiti(tryNative) {
if (jitiLoaders.has(tryNative)) {
return jitiLoaders.get(tryNative);
const effectiveTryNative = process.platform === "win32" ? false : tryNative;
if (jitiLoaders.has(effectiveTryNative)) {
return jitiLoaders.get(effectiveTryNative);
}
const { createJiti } = require("jiti");
const jitiLoader = createJiti(__filename, {
alias: buildPluginSdkAliasMap(tryNative),
alias: buildPluginSdkAliasMap(effectiveTryNative),
interopDefault: true,
// Prefer Node's native sync ESM loader for built dist/plugin-sdk/*.js files
// so local plugins do not create a second transpiled OpenClaw core graph.
tryNative,
tryNative: effectiveTryNative,
extensions: [".ts", ".tsx", ".mts", ".cts", ".mtsx", ".ctsx", ".js", ".mjs", ".cjs", ".json"],
});
jitiLoaders.set(tryNative, jitiLoader);
jitiLoaders.set(effectiveTryNative, jitiLoader);
return jitiLoader;
}

View File

@@ -26,6 +26,7 @@ function loadRootAliasWithStubs(options?: {
env?: Record<string, string | undefined>;
monolithicExports?: Record<string | symbol, unknown>;
aliasPath?: string;
platform?: string;
}) {
let createJitiCalls = 0;
let jitiLoadCalls = 0;
@@ -39,6 +40,7 @@ function loadRootAliasWithStubs(options?: {
{
process: {
env: options?.env ?? {},
platform: options?.platform ?? "darwin",
},
},
{ filename: rootAliasPath },
@@ -206,6 +208,18 @@ describe("plugin-sdk root alias", () => {
},
expectedTryNative: false,
},
{
name: "prefers source loading on Windows even when compat resolves to dist",
options: {
distExists: true,
env: { NODE_ENV: "production" },
platform: "win32",
monolithicExports: {
slowHelper: (): string => "loaded",
},
},
expectedTryNative: false,
},
])("$name", ({ options, expectedTryNative }) => {
const lazyModule = loadRootAliasWithStubs(options);

View File

@@ -13,6 +13,7 @@ import {
buildPluginLoaderJitiOptions,
listPluginSdkAliasCandidates,
listPluginSdkExportedSubpaths,
normalizeJitiAliasTargetPath,
resolveExtensionApiAlias,
resolvePluginRuntimeModulePath,
resolvePluginSdkAliasFile,
@@ -661,6 +662,45 @@ describe("plugin sdk alias helpers", () => {
}
});
it("disables native Jiti loads on Windows even for built JavaScript entries", () => {
const originalPlatform = process.platform;
Object.defineProperty(process, "platform", {
configurable: true,
value: "win32",
});
try {
expect(shouldPreferNativeJiti("/repo/dist/plugins/runtime/index.js")).toBe(false);
expect(shouldPreferNativeJiti(`/repo/${bundledDistPluginFile("browser", "index.js")}`)).toBe(
false,
);
} finally {
Object.defineProperty(process, "platform", {
configurable: true,
value: originalPlatform,
});
}
});
it("normalizes Windows alias targets before handing them to Jiti", () => {
const originalPlatform = process.platform;
Object.defineProperty(process, "platform", {
configurable: true,
value: "win32",
});
try {
expect(normalizeJitiAliasTargetPath(String.raw`C:\repo\dist\plugin-sdk\root-alias.cjs`)).toBe(
"C:/repo/dist/plugin-sdk/root-alias.cjs",
);
} finally {
Object.defineProperty(process, "platform", {
configurable: true,
value: originalPlatform,
});
}
});
it("loads source runtime shims through the non-native Jiti boundary", async () => {
const copiedExtensionRoot = path.join(makeTempDir(), bundledPluginRoot("discord"));
const copiedSourceDir = path.join(copiedExtensionRoot, "src");

View File

@@ -21,6 +21,10 @@ type PluginSdkPackageJson = {
const STARTUP_ARGV1 = process.argv[1];
export function normalizeJitiAliasTargetPath(targetPath: string): string {
return process.platform === "win32" ? targetPath.replace(/\\/g, "/") : targetPath;
}
function resolveLoaderModulePath(params: LoaderModuleResolveParams = {}): string {
return params.modulePath ?? fileURLToPath(params.moduleUrl ?? import.meta.url);
}
@@ -368,9 +372,17 @@ export function buildPluginLoaderAliasMap(
});
const extensionApiAlias = resolveExtensionApiAlias({ modulePath, pluginSdkResolution });
return {
...(extensionApiAlias ? { "openclaw/extension-api": extensionApiAlias } : {}),
...(pluginSdkAlias ? { "openclaw/plugin-sdk": pluginSdkAlias } : {}),
...resolvePluginSdkScopedAliasMap({ modulePath, argv1, moduleUrl, pluginSdkResolution }),
...(extensionApiAlias
? { "openclaw/extension-api": normalizeJitiAliasTargetPath(extensionApiAlias) }
: {}),
...(pluginSdkAlias
? { "openclaw/plugin-sdk": normalizeJitiAliasTargetPath(pluginSdkAlias) }
: {}),
...Object.fromEntries(
Object.entries(
resolvePluginSdkScopedAliasMap({ modulePath, argv1, moduleUrl, pluginSdkResolution }),
).map(([key, value]) => [key, normalizeJitiAliasTargetPath(value)]),
),
};
}
@@ -426,6 +438,9 @@ export function shouldPreferNativeJiti(modulePath: string): boolean {
if (typeof versions.bun === "string") {
return false;
}
if (process.platform === "win32") {
return false;
}
switch (path.extname(modulePath).toLowerCase()) {
case ".js":
case ".mjs":