diff --git a/extensions/matrix/.npmignore b/extensions/matrix/.npmignore new file mode 100644 index 00000000000..9b229d55ade --- /dev/null +++ b/extensions/matrix/.npmignore @@ -0,0 +1,11 @@ +.DS_Store +dist/.boundary-tsc.tsbuildinfo +**/*.map +**/*.test.ts +**/*.test.tsx +**/*.spec.ts +**/*.spec.tsx +**/*.test-helpers.ts +**/*.test-harness.ts +src/test-*.ts +src/test-support/ diff --git a/scripts/stage-bundled-plugin-runtime-deps.mjs b/scripts/stage-bundled-plugin-runtime-deps.mjs index 30817f6896c..b13149202ae 100644 --- a/scripts/stage-bundled-plugin-runtime-deps.mjs +++ b/scripts/stage-bundled-plugin-runtime-deps.mjs @@ -62,12 +62,48 @@ function dependencyVersionSatisfied(spec, installedVersion) { return semverSatisfies(installedVersion, spec, { includePrerelease: false }); } -const stagedRuntimeDepPruneRules = new Map([ +const defaultStagedRuntimeDepGlobalPruneSuffixes = [".map"]; +const defaultStagedRuntimeDepPruneRules = new Map([ // Type declarations only; runtime resolves through lib/es entrypoints. - ["@larksuiteoapi/node-sdk", ["types"]], + ["@larksuiteoapi/node-sdk", { paths: ["types"] }], + [ + "@matrix-org/matrix-sdk-crypto-nodejs", + { + paths: ["index.d.ts", "README.md", "CHANGELOG.md", "RELEASING.md", ".node-version"], + }, + ], + [ + "@matrix-org/matrix-sdk-crypto-wasm", + { + paths: [ + "index.d.ts", + "pkg/matrix_sdk_crypto_wasm.d.ts", + "pkg/matrix_sdk_crypto_wasm_bg.wasm.d.ts", + "README.md", + ], + }, + ], + [ + "matrix-js-sdk", + { + paths: ["src", "CHANGELOG.md", "CONTRIBUTING.rst", "README.md", "release.sh"], + suffixes: [".d.ts"], + }, + ], + ["matrix-widget-api", { paths: ["src"], suffixes: [".d.ts"] }], + ["oidc-client-ts", { paths: ["README.md"], suffixes: [".d.ts"] }], + ["music-metadata", { paths: ["README.md"], suffixes: [".d.ts"] }], ]); const runtimeDepsStagingVersion = 2; +function resolveRuntimeDepPruneConfig(params = {}) { + return { + globalPruneSuffixes: + params.stagedRuntimeDepGlobalPruneSuffixes ?? defaultStagedRuntimeDepGlobalPruneSuffixes, + pruneRules: params.stagedRuntimeDepPruneRules ?? defaultStagedRuntimeDepPruneRules, + }; +} + function collectInstalledRuntimeClosure(rootNodeModulesDir, dependencySpecs) { const packageCache = new Map(); const closure = new Set(); @@ -102,20 +138,73 @@ function collectInstalledRuntimeClosure(rootNodeModulesDir, dependencySpecs) { return [...closure]; } -function pruneStagedInstalledDependencyCargo(nodeModulesDir, depName) { - const prunePaths = stagedRuntimeDepPruneRules.get(depName); - if (!prunePaths) { +function walkFiles(rootDir, visitFile) { + if (!fs.existsSync(rootDir)) { return; } - const depRoot = dependencyNodeModulesPath(nodeModulesDir, depName); - for (const relativePath of prunePaths) { - removePathIfExists(path.join(depRoot, relativePath)); + const queue = [rootDir]; + while (queue.length > 0) { + const currentDir = queue.shift(); + for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) { + const fullPath = path.join(currentDir, entry.name); + if (entry.isDirectory()) { + queue.push(fullPath); + continue; + } + if (entry.isFile()) { + visitFile(fullPath); + } + } } } -function pruneStagedRuntimeDependencyCargo(nodeModulesDir) { - for (const depName of stagedRuntimeDepPruneRules.keys()) { - pruneStagedInstalledDependencyCargo(nodeModulesDir, depName); +function pruneDependencyFilesBySuffixes(depRoot, suffixes) { + if (!suffixes || suffixes.length === 0 || !fs.existsSync(depRoot)) { + return; + } + walkFiles(depRoot, (fullPath) => { + if (suffixes.some((suffix) => fullPath.endsWith(suffix))) { + removePathIfExists(fullPath); + } + }); +} + +function pruneStagedInstalledDependencyCargo(nodeModulesDir, depName, pruneConfig) { + const depRoot = dependencyNodeModulesPath(nodeModulesDir, depName); + const pruneRule = pruneConfig.pruneRules.get(depName); + for (const relativePath of pruneRule?.paths ?? []) { + removePathIfExists(path.join(depRoot, relativePath)); + } + pruneDependencyFilesBySuffixes(depRoot, pruneConfig.globalPruneSuffixes); + pruneDependencyFilesBySuffixes(depRoot, pruneRule?.suffixes ?? []); +} + +function listInstalledDependencyNames(nodeModulesDir) { + if (!fs.existsSync(nodeModulesDir)) { + return []; + } + const names = []; + for (const entry of fs.readdirSync(nodeModulesDir, { withFileTypes: true })) { + if (!entry.isDirectory()) { + continue; + } + if (entry.name.startsWith("@")) { + const scopeDir = path.join(nodeModulesDir, entry.name); + for (const scopedEntry of fs.readdirSync(scopeDir, { withFileTypes: true })) { + if (scopedEntry.isDirectory()) { + names.push(`${entry.name}/${scopedEntry.name}`); + } + } + continue; + } + names.push(entry.name); + } + return names; +} + +function pruneStagedRuntimeDependencyCargo(nodeModulesDir, pruneConfig) { + for (const depName of listInstalledDependencyNames(nodeModulesDir)) { + pruneStagedInstalledDependencyCargo(nodeModulesDir, depName, pruneConfig); } } @@ -174,12 +263,13 @@ function resolveRuntimeDepsStampPath(pluginDir) { return path.join(pluginDir, ".openclaw-runtime-deps-stamp.json"); } -function createRuntimeDepsFingerprint(packageJson) { +function createRuntimeDepsFingerprint(packageJson, pruneConfig) { return createHash("sha256") .update( JSON.stringify({ + globalPruneSuffixes: pruneConfig.globalPruneSuffixes, packageJson, - pruneRules: [...stagedRuntimeDepPruneRules.entries()], + pruneRules: [...pruneConfig.pruneRules.entries()], version: runtimeDepsStagingVersion, }), ) @@ -198,7 +288,7 @@ function readRuntimeDepsStamp(stampPath) { } function stageInstalledRootRuntimeDeps(params) { - const { fingerprint, packageJson, pluginDir, repoRoot } = params; + const { fingerprint, packageJson, pluginDir, pruneConfig, repoRoot } = params; const dependencySpecs = { ...packageJson.dependencies, ...packageJson.optionalDependencies, @@ -230,7 +320,7 @@ function stageInstalledRootRuntimeDeps(params) { fs.mkdirSync(path.dirname(targetPath), { recursive: true }); fs.cpSync(sourcePath, targetPath, { recursive: true, force: true, dereference: true }); } - pruneStagedRuntimeDependencyCargo(stagedNodeModulesDir); + pruneStagedRuntimeDependencyCargo(stagedNodeModulesDir, pruneConfig); replaceDir(nodeModulesDir, stagedNodeModulesDir); writeJson(stampPath, { @@ -244,10 +334,10 @@ function stageInstalledRootRuntimeDeps(params) { } function installPluginRuntimeDeps(params) { - const { fingerprint, packageJson, pluginDir, pluginId, repoRoot } = params; + const { fingerprint, packageJson, pluginDir, pluginId, pruneConfig, repoRoot } = params; if ( repoRoot && - stageInstalledRootRuntimeDeps({ fingerprint, packageJson, pluginDir, repoRoot }) + stageInstalledRootRuntimeDeps({ fingerprint, packageJson, pluginDir, pruneConfig, repoRoot }) ) { return; } @@ -291,7 +381,7 @@ function installPluginRuntimeDeps(params) { ); } - pruneStagedRuntimeDependencyCargo(stagedNodeModulesDir); + pruneStagedRuntimeDependencyCargo(stagedNodeModulesDir, pruneConfig); replaceDir(nodeModulesDir, stagedNodeModulesDir); writeJson(stampPath, { @@ -325,6 +415,7 @@ export function stageBundledPluginRuntimeDeps(params = {}) { const installPluginRuntimeDepsImpl = params.installPluginRuntimeDepsImpl ?? installPluginRuntimeDeps; const installAttempts = params.installAttempts ?? 3; + const pruneConfig = resolveRuntimeDepPruneConfig(params); for (const pluginDir of listBundledPluginRuntimeDirs(repoRoot)) { const pluginId = path.basename(pluginDir); const packageJson = sanitizeBundledManifestForRuntimeInstall(pluginDir); @@ -335,7 +426,7 @@ export function stageBundledPluginRuntimeDeps(params = {}) { removePathIfExists(stampPath); continue; } - const fingerprint = createRuntimeDepsFingerprint(packageJson); + const fingerprint = createRuntimeDepsFingerprint(packageJson, pruneConfig); const stamp = readRuntimeDepsStamp(stampPath); if (fs.existsSync(nodeModulesDir) && stamp?.fingerprint === fingerprint) { continue; @@ -348,6 +439,7 @@ export function stageBundledPluginRuntimeDeps(params = {}) { packageJson, pluginDir, pluginId, + pruneConfig, repoRoot, }, }); diff --git a/test/scripts/stage-bundled-plugin-runtime-deps.test.ts b/test/scripts/stage-bundled-plugin-runtime-deps.test.ts index 3357b5d38ba..9ecf08d4e95 100644 --- a/test/scripts/stage-bundled-plugin-runtime-deps.test.ts +++ b/test/scripts/stage-bundled-plugin-runtime-deps.test.ts @@ -189,6 +189,78 @@ describe("stageBundledPluginRuntimeDeps", () => { ).toBe("module.exports = 'transitive';\n"); }); + it("removes source maps from staged runtime dependencies", () => { + const { pluginDir, repoRoot } = createBundledPluginFixture({ + packageJson: { + name: "@openclaw/fixture-plugin", + version: "1.0.0", + dependencies: { direct: "1.0.0" }, + openclaw: { bundle: { stageRuntimeDependencies: true } }, + }, + }); + const directDir = path.join(repoRoot, "node_modules", "direct"); + fs.mkdirSync(directDir, { recursive: true }); + fs.writeFileSync( + path.join(directDir, "package.json"), + '{ "name": "direct", "version": "1.0.0" }\n', + "utf8", + ); + fs.writeFileSync(path.join(directDir, "index.js"), "module.exports = 1;\n", "utf8"); + fs.writeFileSync(path.join(directDir, "index.js.map"), '{ "version": 3 }\n', "utf8"); + + stageBundledPluginRuntimeDeps({ cwd: repoRoot }); + + expect(fs.existsSync(path.join(pluginDir, "node_modules", "direct", "index.js"))).toBe(true); + expect(fs.existsSync(path.join(pluginDir, "node_modules", "direct", "index.js.map"))).toBe( + false, + ); + }); + + it("applies package-specific cargo prune rules after staging", () => { + const { pluginDir, repoRoot } = createBundledPluginFixture({ + packageJson: { + name: "@openclaw/fixture-plugin", + version: "1.0.0", + dependencies: { "rule-target": "1.0.0" }, + openclaw: { bundle: { stageRuntimeDependencies: true } }, + }, + }); + const depDir = path.join(repoRoot, "node_modules", "rule-target"); + fs.mkdirSync(path.join(depDir, "docs"), { recursive: true }); + fs.mkdirSync(path.join(depDir, "lib"), { recursive: true }); + fs.writeFileSync( + path.join(depDir, "package.json"), + '{ "name": "rule-target", "version": "1.0.0" }\n', + "utf8", + ); + fs.writeFileSync(path.join(depDir, "lib", "index.js"), "export {};\n", "utf8"); + fs.writeFileSync(path.join(depDir, "lib", "index.d.ts"), "export {};\n", "utf8"); + fs.writeFileSync(path.join(depDir, "docs", "guide.md"), "docs\n", "utf8"); + fs.writeFileSync(path.join(depDir, "README.md"), "readme\n", "utf8"); + fs.writeFileSync(path.join(depDir, "LICENSE"), "license\n", "utf8"); + + stageBundledPluginRuntimeDeps({ + cwd: repoRoot, + stagedRuntimeDepPruneRules: new Map([ + ["rule-target", { paths: ["docs", "README.md"], suffixes: [".d.ts"] }], + ]), + }); + + expect( + fs.existsSync(path.join(pluginDir, "node_modules", "rule-target", "lib", "index.js")), + ).toBe(true); + expect( + fs.existsSync(path.join(pluginDir, "node_modules", "rule-target", "lib", "index.d.ts")), + ).toBe(false); + expect(fs.existsSync(path.join(pluginDir, "node_modules", "rule-target", "docs"))).toBe(false); + expect(fs.existsSync(path.join(pluginDir, "node_modules", "rule-target", "README.md"))).toBe( + false, + ); + expect(fs.existsSync(path.join(pluginDir, "node_modules", "rule-target", "LICENSE"))).toBe( + true, + ); + }); + it("falls back to staging installs when the root dependency version is incompatible", () => { const { pluginDir, repoRoot } = createBundledPluginFixture({ packageJson: {