























@@ -421,6 +421,51 @@ function mockNpmViewMetadata(params: { name: string; version?: string }) {
421421});
422422}
423423424+function mockSuccessfulManagedNpmInstall(params: { packageName: string; version?: string }) {
425+vi.mocked(runCommandWithTimeout).mockImplementation(async (args, options) => {
426+if (args[0] !== "npm" || args[1] !== "install") {
427+throw new Error(`unexpected command: ${args.join(" ")}`);
428+}
429+if (!args.includes("--package-lock-only")) {
430+const npmRoot = options.cwd;
431+if (!npmRoot) {
432+throw new Error("expected npm install cwd");
433+}
434+const packageDir = path.join(npmRoot, "node_modules", ...params.packageName.split("/"));
435+fs.mkdirSync(packageDir, { recursive: true });
436+fs.writeFileSync(
437+path.join(packageDir, "package.json"),
438+JSON.stringify({
439+name: params.packageName,
440+version: params.version ?? "1.0.0",
441+openclaw: { extensions: ["index.js"] },
442+}),
443+);
444+fs.writeFileSync(path.join(packageDir, "index.js"), "export {};\n");
445+fs.writeFileSync(
446+path.join(npmRoot, "package-lock.json"),
447+JSON.stringify({
448+packages: {
449+[`node_modules/${params.packageName}`]: {
450+version: params.version ?? "1.0.0",
451+integrity: "sha512-test",
452+resolved: `https://registry.npmjs.org/${params.packageName}/-/${params.packageName.split("/").at(-1)}-${params.version ?? "1.0.0"}.tgz`,
453+},
454+},
455+}),
456+);
457+}
458+return {
459+code: 0,
460+killed: false,
461+signal: null,
462+stderr: "",
463+termination: "exit",
464+stdout: "",
465+};
466+});
467+}
468+424469async function installFromArchiveWithWarnings(params: {
425470archivePath: string;
426471extensionsDir: string;
@@ -2412,6 +2457,40 @@ describe("installPluginFromArchive", () => {
24122457});
2413245824142459describe("installPluginFromNpmSpec", () => {
2460+it("emits one npm security event after installing from npm", async () => {
2461+const root = suiteTempRootTracker.makeTempDir();
2462+const npmDir = path.join(root, "npm");
2463+const extensionsDir = path.join(root, "extensions");
2464+const packageName = "@acme/security-event-plugin";
2465+mockNpmViewMetadata({ name: packageName, version: "1.2.3" });
2466+mockSuccessfulManagedNpmInstall({ packageName, version: "1.2.3" });
2467+const captured = captureSecurityEvents();
2468+2469+let result: Awaited<ReturnType<typeof installPluginFromNpmSpec>>;
2470+try {
2471+result = await installPluginFromNpmSpec({
2472+spec: `${packageName}@1.2.3`,
2473+ extensionsDir,
2474+ npmDir,
2475+});
2476+} finally {
2477+captured.stop();
2478+}
2479+2480+expect(result!.ok).toBe(true);
2481+expect(captured.events).toHaveLength(1);
2482+expect(captured.events[0]).toMatchObject({
2483+category: "plugin",
2484+action: "plugin.installed",
2485+outcome: "success",
2486+target: { kind: "plugin", name: packageName },
2487+attributes: {
2488+source_family: "npm",
2489+mode: "install",
2490+},
2491+});
2492+});
2493+24152494it("runs operator policy before npm install mutates the managed root", async () => {
24162495const root = suiteTempRootTracker.makeTempDir();
24172496const npmDir = path.join(root, "npm");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。