fix(plugins): anchor runtime dependency installs · openclaw/openclaw@20223e0
steipete
·
2026-04-26
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -117,6 +117,9 @@ Docs: https://docs.openclaw.ai
|
117 | 117 | - Plugins/startup: remove ownerless bundled runtime-dependency install locks |
118 | 118 | after a short grace window and include lock owner details when startup times |
119 | 119 | out waiting for a plugin runtime-deps lock. |
| 120 | +- Plugins/install: anchor bundled runtime-dependency npm installs with an |
| 121 | + OpenClaw-owned package manifest so Linux updates cannot accidentally write to |
| 122 | + a parent `$HOME/node_modules` tree. Fixes #71730. |
120 | 123 | - Live tests/voice: accept common STT variants for OpenClaw and ElevenLabs |
121 | 124 | brand names so provider smoke tests fail on real regressions rather than |
122 | 125 | equivalent transcripts. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -222,6 +222,44 @@ describe("installBundledRuntimeDeps", () => {
|
222 | 222 | ); |
223 | 223 | }); |
224 | 224 | |
| 225 | +it("anchors non-isolated external install roots with a package manifest", () => { |
| 226 | +const parentRoot = makeTempDir(); |
| 227 | +const installRoot = path.join(parentRoot, ".openclaw", "plugin-runtime-deps", "openclaw-test"); |
| 228 | +fs.mkdirSync(path.join(parentRoot, "node_modules", "@grammyjs"), { recursive: true }); |
| 229 | +spawnSyncMock.mockImplementation((_command, _args, options) => { |
| 230 | +const cwd = String(options?.cwd ?? ""); |
| 231 | +expect(cwd).toBe(installRoot); |
| 232 | +expect(JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"))).toEqual({ |
| 233 | +name: "openclaw-runtime-deps-install", |
| 234 | +private: true, |
| 235 | +}); |
| 236 | +return { |
| 237 | +pid: 123, |
| 238 | +output: [], |
| 239 | +stdout: "", |
| 240 | +stderr: "", |
| 241 | +signal: null, |
| 242 | +status: 0, |
| 243 | +}; |
| 244 | +}); |
| 245 | + |
| 246 | +installBundledRuntimeDeps({ |
| 247 | + installRoot, |
| 248 | +missingSpecs: ["@grammyjs/runner@^2.0.3"], |
| 249 | +env: { |
| 250 | +HOME: parentRoot, |
| 251 | +}, |
| 252 | +}); |
| 253 | + |
| 254 | +expect(spawnSyncMock).toHaveBeenCalledWith( |
| 255 | +expect.any(String), |
| 256 | +expect.any(Array), |
| 257 | +expect.objectContaining({ |
| 258 | +cwd: installRoot, |
| 259 | +}), |
| 260 | +); |
| 261 | +}); |
| 262 | + |
225 | 263 | it("uses an isolated execution root and copies node_modules back when requested", () => { |
226 | 264 | const installRoot = makeTempDir(); |
227 | 265 | const installExecutionRoot = makeTempDir(); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1154,14 +1154,17 @@ function shouldCleanBundledRuntimeDepsInstallExecutionRoot(params: {
|
1154 | 1154 | return installExecutionRoot.startsWith(`${installRoot}${path.sep}`); |
1155 | 1155 | } |
1156 | 1156 | |
1157 | | -function writeBundledRuntimeDepsInstallManifest(installExecutionRoot: string): void { |
| 1157 | +function ensureNpmInstallExecutionManifest(installExecutionRoot: string): void { |
| 1158 | +const manifestPath = path.join(installExecutionRoot, "package.json"); |
| 1159 | +if (fs.existsSync(manifestPath)) { |
| 1160 | +return; |
| 1161 | +} |
1158 | 1162 | fs.writeFileSync( |
1159 | | -path.join(installExecutionRoot, "package.json"), |
| 1163 | +manifestPath, |
1160 | 1164 | `${JSON.stringify({ name: "openclaw-runtime-deps-install", private: true }, null, 2)}\n`, |
1161 | 1165 | "utf8", |
1162 | 1166 | ); |
1163 | 1167 | } |
1164 | | - |
1165 | 1168 | export function installBundledRuntimeDeps(params: { |
1166 | 1169 | installRoot: string; |
1167 | 1170 | installExecutionRoot?: string; |
@@ -1184,7 +1187,7 @@ export function installBundledRuntimeDeps(params: {
|
1184 | 1187 | // doctor repair path installs directly in the external stage dir; without a |
1185 | 1188 | // manifest, npm can honor a user's global prefix config and write under |
1186 | 1189 | // $HOME/node_modules instead of our managed stage. |
1187 | | -writeBundledRuntimeDepsInstallManifest(installExecutionRoot); |
| 1190 | +ensureNpmInstallExecutionManifest(installExecutionRoot); |
1188 | 1191 | const installEnv = createBundledRuntimeDepsInstallEnv(params.env, { |
1189 | 1192 | cacheDir: path.join(installExecutionRoot, ".openclaw-npm-cache"), |
1190 | 1193 | }); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -176,6 +176,7 @@ describe("resolveInstalledPluginProviderContributionIds", () => {
|
176 | 176 | resolveInstalledPluginProviderContributionIds({ |
177 | 177 | candidates: [candidate], |
178 | 178 | env: hermeticEnv(), |
| 179 | +preferPersisted: false, |
179 | 180 | }), |
180 | 181 | ).toEqual(["demo", "demo-alias"]); |
181 | 182 | }); |
@@ -197,6 +198,7 @@ describe("resolveInstalledPluginProviderContributionIds", () => {
|
197 | 198 | }, |
198 | 199 | }, |
199 | 200 | env: hermeticEnv(), |
| 201 | +preferPersisted: false, |
200 | 202 | }; |
201 | 203 | |
202 | 204 | expect(resolveInstalledPluginProviderContributionIds(params)).toEqual([]); |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。