From ee3efc0152dd36aa0cff2d90bf6a3b498ca1231d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 29 May 2026 03:53:50 +0100 Subject: [PATCH] test(plugins): alias gateway workspace packages in plugin loader --- src/plugins/sdk-alias.test.ts | 103 +++++++++++++++++++++++++- src/plugins/sdk-alias.ts | 110 ++++++++++++++++++++++++++-- test/vitest/vitest.shared.config.ts | 12 --- 3 files changed, 205 insertions(+), 20 deletions(-) diff --git a/src/plugins/sdk-alias.test.ts b/src/plugins/sdk-alias.test.ts index 1323e8ddc78..99e76820b60 100644 --- a/src/plugins/sdk-alias.test.ts +++ b/src/plugins/sdk-alias.test.ts @@ -154,6 +154,21 @@ function createExtensionApiAliasFixture(params?: { return { root, srcFile, distFile }; } +function writeWorkspacePackageEntry(params: { + root: string; + packageDir: string; + srcFile: string; + distFile: string; +}) { + const srcFile = path.join(params.root, "packages", params.packageDir, "src", params.srcFile); + const distFile = path.join(params.root, "packages", params.packageDir, "dist", params.distFile); + mkdirSafeDir(path.dirname(srcFile)); + mkdirSafeDir(path.dirname(distFile)); + fs.writeFileSync(srcFile, "export {};\n", "utf-8"); + fs.writeFileSync(distFile, "export {};\n", "utf-8"); + return { srcFile, distFile }; +} + function createPluginRuntimeAliasFixture(params?: { srcBody?: string; distBody?: string }) { const root = makeTempDir(); const srcFile = path.join(root, "src", "plugins", "runtime", "index.ts"); @@ -1331,6 +1346,90 @@ describe("plugin sdk alias helpers", () => { }); }); + it("aliases gateway workspace packages to source when dist artifacts are missing", () => { + const fixture = createPluginSdkAliasFixture(); + const gatewayClient = writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "gateway-client", + srcFile: "index.ts", + distFile: "index.mjs", + }); + const gatewayClientTimeouts = writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "gateway-client", + srcFile: "timeouts.ts", + distFile: "timeouts.mjs", + }); + const gatewayProtocol = writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "gateway-protocol", + srcFile: "index.ts", + distFile: "index.mjs", + }); + const gatewayProtocolSchema = writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "gateway-protocol", + srcFile: "schema.ts", + distFile: "schema.mjs", + }); + fs.rmSync(gatewayClient.distFile); + fs.rmSync(gatewayClientTimeouts.distFile); + fs.rmSync(gatewayProtocol.distFile); + fs.rmSync(gatewayProtocolSchema.distFile); + const sourcePluginEntry = writePluginEntry( + fixture.root, + bundledPluginFile("demo", "src/index.ts"), + ); + + const aliases = withEnv({ NODE_ENV: undefined }, () => + buildPluginLoaderAliasMap(sourcePluginEntry, undefined, undefined, "dist"), + ); + + expect(fs.realpathSync(aliases["@openclaw/gateway-client"] ?? "")).toBe( + fs.realpathSync(gatewayClient.srcFile), + ); + expect(fs.realpathSync(aliases["@openclaw/gateway-client/timeouts"] ?? "")).toBe( + fs.realpathSync(gatewayClientTimeouts.srcFile), + ); + expect(fs.realpathSync(aliases["@openclaw/gateway-protocol"] ?? "")).toBe( + fs.realpathSync(gatewayProtocol.srcFile), + ); + expect(fs.realpathSync(aliases["@openclaw/gateway-protocol/schema"] ?? "")).toBe( + fs.realpathSync(gatewayProtocolSchema.srcFile), + ); + }); + + it("aliases gateway workspace package subpaths to dist when available", () => { + const fixture = createPluginSdkAliasFixture(); + const gatewayClient = writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "gateway-client", + srcFile: "readiness.ts", + distFile: "readiness.mjs", + }); + const gatewayProtocol = writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "gateway-protocol", + srcFile: "connect-error-details.ts", + distFile: "connect-error-details.mjs", + }); + const sourcePluginEntry = writePluginEntry( + fixture.root, + bundledPluginFile("demo", "src/index.ts"), + ); + + const aliases = withEnv({ NODE_ENV: undefined }, () => + buildPluginLoaderAliasMap(sourcePluginEntry, undefined, undefined, "dist"), + ); + + expect(fs.realpathSync(aliases["@openclaw/gateway-client/readiness"] ?? "")).toBe( + fs.realpathSync(gatewayClient.distFile), + ); + expect(fs.realpathSync(aliases["@openclaw/gateway-protocol/connect-error-details"] ?? "")).toBe( + fs.realpathSync(gatewayProtocol.distFile), + ); + }); + it("aliases bundled plugin package public surfaces for source plugin transforms", () => { const { fixture, sourceApiPath, sourceRuntimeApiPath } = createBundledPluginPackagePublicSurfaceAliasFixture(); @@ -2146,9 +2245,7 @@ describe("buildPluginLoaderJitiOptions", () => { ), ); - expect(guardedFsCache).toContain( - path.join("jiti", "openclaw", "1.2.3-beta.4") + path.sep, - ); + expect(guardedFsCache).toContain(path.join("jiti", "openclaw", "1.2.3-beta.4") + path.sep); expect(guardedFsCache.startsWith(path.join(root, "jiti") + path.sep)).toBe(false); expect(respectedFsCache).toContain( path.join(root, "jiti", "openclaw", "1.2.3-beta.4") + path.sep, diff --git a/src/plugins/sdk-alias.ts b/src/plugins/sdk-alias.ts index 0c9cc2d0383..d377ad75daa 100644 --- a/src/plugins/sdk-alias.ts +++ b/src/plugins/sdk-alias.ts @@ -42,11 +42,7 @@ function sanitizeJitiCachePathSegment(value: string): string { function resolveJitiFsCacheTmpDir(): string { let tmpDir = os.tmpdir(); - if ( - process.env.TMPDIR && - tmpDir === process.cwd() && - !process.env.JITI_RESPECT_TMPDIR_ENV - ) { + if (process.env.TMPDIR && tmpDir === process.cwd() && !process.env.JITI_RESPECT_TMPDIR_ENV) { const originalTmpDir = process.env.TMPDIR; delete process.env.TMPDIR; try { @@ -497,6 +493,71 @@ const PLUGIN_SDK_SOURCE_CANDIDATE_EXTENSIONS = [ const BUNDLED_PLUGIN_PUBLIC_SURFACE_SOURCE_PATTERN = /^(?:api|runtime-api|test-api|.+-api)$/u; const JS_STATIC_RELATIVE_DEPENDENCY_PATTERN = /(?:\bfrom\s*["']|\bimport\s*\(\s*["']|\brequire\s*\(\s*["'])(\.{1,2}\/[^"']+)["']/g; +const WORKSPACE_PACKAGE_ALIAS_ENTRIES = [ + { + packageName: "@openclaw/gateway-client", + packageDir: "gateway-client", + subpath: "", + srcFile: "index.ts", + distFile: "index.mjs", + }, + { + packageName: "@openclaw/gateway-client", + packageDir: "gateway-client", + subpath: "readiness", + srcFile: "readiness.ts", + distFile: "readiness.mjs", + }, + { + packageName: "@openclaw/gateway-client", + packageDir: "gateway-client", + subpath: "timeouts", + srcFile: "timeouts.ts", + distFile: "timeouts.mjs", + }, + { + packageName: "@openclaw/gateway-protocol", + packageDir: "gateway-protocol", + subpath: "", + srcFile: "index.ts", + distFile: "index.mjs", + }, + { + packageName: "@openclaw/gateway-protocol", + packageDir: "gateway-protocol", + subpath: "client-info", + srcFile: "client-info.ts", + distFile: "client-info.mjs", + }, + { + packageName: "@openclaw/gateway-protocol", + packageDir: "gateway-protocol", + subpath: "connect-error-details", + srcFile: "connect-error-details.ts", + distFile: "connect-error-details.mjs", + }, + { + packageName: "@openclaw/gateway-protocol", + packageDir: "gateway-protocol", + subpath: "schema", + srcFile: "schema.ts", + distFile: "schema.mjs", + }, + { + packageName: "@openclaw/gateway-protocol", + packageDir: "gateway-protocol", + subpath: "startup-unavailable", + srcFile: "startup-unavailable.ts", + distFile: "startup-unavailable.mjs", + }, + { + packageName: "@openclaw/gateway-protocol", + packageDir: "gateway-protocol", + subpath: "version", + srcFile: "version.ts", + distFile: "version.mjs", + }, +] as const; function isUsableDistPluginSdkArtifact(candidate: string): boolean { if (!fs.existsSync(candidate)) { @@ -683,6 +744,39 @@ function resolveBundledPluginPackagePublicSurfaceAliasMap(params: { return aliasMap; } +function resolveWorkspacePackageAliasMap(params: { + modulePath: string; + argv1?: string; + moduleUrl?: string; + pluginSdkResolution: PluginSdkResolutionPreference; +}): Record { + const packageRoot = resolveLoaderPluginSdkPackageRoot(params); + if (!packageRoot) { + return {}; + } + const orderedKinds = resolvePluginSdkAliasCandidateOrder({ + modulePath: params.modulePath, + isProduction: process.env.NODE_ENV === "production", + pluginSdkResolution: params.pluginSdkResolution, + }); + const aliasMap: Record = {}; + for (const entry of WORKSPACE_PACKAGE_ALIAS_ENTRIES) { + const alias = entry.subpath ? `${entry.packageName}/${entry.subpath}` : entry.packageName; + for (const kind of orderedKinds) { + const candidate = + kind === "dist" + ? path.join(packageRoot, "packages", entry.packageDir, "dist", entry.distFile) + : path.join(packageRoot, "packages", entry.packageDir, "src", entry.srcFile); + if (!fs.existsSync(candidate)) { + continue; + } + aliasMap[alias] = normalizeJitiAliasTargetPath(candidate); + break; + } + } + return aliasMap; +} + function shouldIncludePrivateLocalOnlyPluginSdkSubpaths() { return process.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI === "1"; } @@ -1162,6 +1256,12 @@ export function buildPluginLoaderAliasMap( moduleUrl, pluginSdkResolution, }), + ...resolveWorkspacePackageAliasMap({ + modulePath, + argv1, + moduleUrl, + pluginSdkResolution, + }), ...(pluginSdkAlias ? Object.fromEntries( PLUGIN_SDK_PACKAGE_NAMES.map((packageName) => [ diff --git a/test/vitest/vitest.shared.config.ts b/test/vitest/vitest.shared.config.ts index 4048e83d802..8f14b7b7eb2 100644 --- a/test/vitest/vitest.shared.config.ts +++ b/test/vitest/vitest.shared.config.ts @@ -173,18 +173,6 @@ export const sharedVitestConfig = { find: "@openclaw/whatsapp/api.js", replacement: path.join(repoRoot, "extensions", "whatsapp", "api.ts"), }, - { - find: "@openclaw/gateway-client/readiness", - replacement: path.join(repoRoot, "packages", "gateway-client", "src", "readiness.ts"), - }, - { - find: "@openclaw/gateway-client/timeouts", - replacement: path.join(repoRoot, "packages", "gateway-client", "src", "timeouts.ts"), - }, - { - find: "@openclaw/gateway-client", - replacement: path.join(repoRoot, "packages", "gateway-client", "src", "index.ts"), - }, { find: "@openclaw/gateway-protocol/client-info", replacement: path.join(repoRoot, "packages", "gateway-protocol", "src", "client-info.ts"),