diff --git a/scripts/stage-bundled-plugin-runtime-deps.mjs b/scripts/stage-bundled-plugin-runtime-deps.mjs index 5f790c980ac..f253c122a2f 100644 --- a/scripts/stage-bundled-plugin-runtime-deps.mjs +++ b/scripts/stage-bundled-plugin-runtime-deps.mjs @@ -62,6 +62,12 @@ function dependencyVersionSatisfied(spec, installedVersion) { return semverSatisfies(installedVersion, spec, { includePrerelease: false }); } +const stagedRuntimeDepPruneRules = new Map([ + // Type declarations only; runtime resolves through lib/es entrypoints. + ["@larksuiteoapi/node-sdk", ["types"]], +]); +const runtimeDepsStagingVersion = 2; + function collectInstalledRuntimeClosure(rootNodeModulesDir, dependencySpecs) { const packageCache = new Map(); const closure = new Set(); @@ -96,6 +102,23 @@ function collectInstalledRuntimeClosure(rootNodeModulesDir, dependencySpecs) { return [...closure]; } +function pruneStagedInstalledDependencyCargo(nodeModulesDir, depName) { + const prunePaths = stagedRuntimeDepPruneRules.get(depName); + if (!prunePaths) { + return; + } + const depRoot = dependencyNodeModulesPath(nodeModulesDir, depName); + for (const relativePath of prunePaths) { + removePathIfExists(path.join(depRoot, relativePath)); + } +} + +function pruneStagedRuntimeDependencyCargo(nodeModulesDir) { + for (const depName of stagedRuntimeDepPruneRules.keys()) { + pruneStagedInstalledDependencyCargo(nodeModulesDir, depName); + } +} + function listBundledPluginRuntimeDirs(repoRoot) { const extensionsRoot = path.join(repoRoot, "dist", "extensions"); if (!fs.existsSync(extensionsRoot)) { @@ -170,7 +193,15 @@ function resolveRuntimeDepsStampPath(pluginDir) { } function createRuntimeDepsFingerprint(packageJson) { - return createHash("sha256").update(JSON.stringify(packageJson)).digest("hex"); + return createHash("sha256") + .update( + JSON.stringify({ + packageJson, + pruneRules: [...stagedRuntimeDepPruneRules.entries()], + version: runtimeDepsStagingVersion, + }), + ) + .digest("hex"); } function readRuntimeDepsStamp(stampPath) { @@ -217,6 +248,7 @@ function stageInstalledRootRuntimeDeps(params) { fs.mkdirSync(path.dirname(targetPath), { recursive: true }); fs.cpSync(sourcePath, targetPath, { recursive: true, force: true, dereference: true }); } + pruneStagedRuntimeDependencyCargo(stagedNodeModulesDir); replaceDir(nodeModulesDir, stagedNodeModulesDir); writeJson(stampPath, { @@ -277,6 +309,8 @@ function installPluginRuntimeDeps(params) { ); } + pruneStagedRuntimeDependencyCargo(stagedNodeModulesDir); + replaceDir(nodeModulesDir, stagedNodeModulesDir); writeJson(stampPath, { fingerprint, diff --git a/src/plugins/stage-bundled-plugin-runtime-deps.test.ts b/src/plugins/stage-bundled-plugin-runtime-deps.test.ts new file mode 100644 index 00000000000..3889faf987f --- /dev/null +++ b/src/plugins/stage-bundled-plugin-runtime-deps.test.ts @@ -0,0 +1,109 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; + +type StageBundledPluginRuntimeDeps = (params?: { cwd?: string; repoRoot?: string }) => void; + +async function loadStageBundledPluginRuntimeDeps(): Promise { + const moduleUrl = new URL("../../scripts/stage-bundled-plugin-runtime-deps.mjs", import.meta.url); + const loaded = (await import(moduleUrl.href)) as { + stageBundledPluginRuntimeDeps: StageBundledPluginRuntimeDeps; + }; + return loaded.stageBundledPluginRuntimeDeps; +} + +const tempDirs: string[] = []; + +function makeRepoRoot(prefix: string): string { + const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); + tempDirs.push(repoRoot); + return repoRoot; +} + +function writeRepoFile(repoRoot: string, relativePath: string, value: string) { + const fullPath = path.join(repoRoot, relativePath); + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); + fs.writeFileSync(fullPath, value, "utf8"); +} + +afterEach(() => { + for (const dir of tempDirs.splice(0, tempDirs.length)) { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +describe("stageBundledPluginRuntimeDeps", () => { + it("drops Lark SDK type cargo while keeping runtime entrypoints", () => { + const repoRoot = makeRepoRoot("openclaw-stage-bundled-runtime-deps-"); + + writeRepoFile( + repoRoot, + "dist/extensions/feishu/package.json", + JSON.stringify( + { + name: "@openclaw/feishu", + version: "2026.4.5", + dependencies: { + "@larksuiteoapi/node-sdk": "^1.60.0", + }, + openclaw: { + bundle: { + stageRuntimeDependencies: true, + }, + }, + }, + null, + 2, + ), + ); + + writeRepoFile( + repoRoot, + "node_modules/@larksuiteoapi/node-sdk/package.json", + JSON.stringify( + { + name: "@larksuiteoapi/node-sdk", + version: "1.60.0", + main: "./lib/index.js", + module: "./es/index.js", + types: "./types", + }, + null, + 2, + ), + ); + writeRepoFile( + repoRoot, + "node_modules/@larksuiteoapi/node-sdk/lib/index.js", + "export const runtime = true;\n", + ); + writeRepoFile( + repoRoot, + "node_modules/@larksuiteoapi/node-sdk/es/index.js", + "export const moduleRuntime = true;\n", + ); + writeRepoFile( + repoRoot, + "node_modules/@larksuiteoapi/node-sdk/types/index.d.ts", + "export interface HugeTypeSurface {}\n", + ); + + return loadStageBundledPluginRuntimeDeps().then((stageBundledPluginRuntimeDeps) => { + stageBundledPluginRuntimeDeps({ repoRoot }); + + const stagedRoot = path.join( + repoRoot, + "dist", + "extensions", + "feishu", + "node_modules", + "@larksuiteoapi", + "node-sdk", + ); + expect(fs.existsSync(path.join(stagedRoot, "lib", "index.js"))).toBe(true); + expect(fs.existsSync(path.join(stagedRoot, "es", "index.js"))).toBe(true); + expect(fs.existsSync(path.join(stagedRoot, "types"))).toBe(false); + }); + }); +});