Spaces:
Sleeping
Sleeping
File size: 5,028 Bytes
fb4d8fe | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | import { randomUUID } from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
const tempDirs: string[] = [];
function makeTempDir() {
const dir = path.join(os.tmpdir(), `openclaw-plugins-${randomUUID()}`);
fs.mkdirSync(dir, { recursive: true });
tempDirs.push(dir);
return dir;
}
async function withStateDir<T>(stateDir: string, fn: () => Promise<T>) {
const prev = process.env.OPENCLAW_STATE_DIR;
const prevBundled = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
process.env.OPENCLAW_STATE_DIR = stateDir;
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins";
vi.resetModules();
try {
return await fn();
} finally {
if (prev === undefined) {
delete process.env.OPENCLAW_STATE_DIR;
} else {
process.env.OPENCLAW_STATE_DIR = prev;
}
if (prevBundled === undefined) {
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
} else {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = prevBundled;
}
vi.resetModules();
}
}
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
try {
fs.rmSync(dir, { recursive: true, force: true });
} catch {
// ignore cleanup failures
}
}
});
describe("discoverOpenClawPlugins", () => {
it("discovers global and workspace extensions", async () => {
const stateDir = makeTempDir();
const workspaceDir = path.join(stateDir, "workspace");
const globalExt = path.join(stateDir, "extensions");
fs.mkdirSync(globalExt, { recursive: true });
fs.writeFileSync(path.join(globalExt, "alpha.ts"), "export default function () {}", "utf-8");
const workspaceExt = path.join(workspaceDir, ".openclaw", "extensions");
fs.mkdirSync(workspaceExt, { recursive: true });
fs.writeFileSync(path.join(workspaceExt, "beta.ts"), "export default function () {}", "utf-8");
const { candidates } = await withStateDir(stateDir, async () => {
const { discoverOpenClawPlugins } = await import("./discovery.js");
return discoverOpenClawPlugins({ workspaceDir });
});
const ids = candidates.map((c) => c.idHint);
expect(ids).toContain("alpha");
expect(ids).toContain("beta");
});
it("loads package extension packs", async () => {
const stateDir = makeTempDir();
const globalExt = path.join(stateDir, "extensions", "pack");
fs.mkdirSync(path.join(globalExt, "src"), { recursive: true });
fs.writeFileSync(
path.join(globalExt, "package.json"),
JSON.stringify({
name: "pack",
openclaw: { extensions: ["./src/one.ts", "./src/two.ts"] },
}),
"utf-8",
);
fs.writeFileSync(
path.join(globalExt, "src", "one.ts"),
"export default function () {}",
"utf-8",
);
fs.writeFileSync(
path.join(globalExt, "src", "two.ts"),
"export default function () {}",
"utf-8",
);
const { candidates } = await withStateDir(stateDir, async () => {
const { discoverOpenClawPlugins } = await import("./discovery.js");
return discoverOpenClawPlugins({});
});
const ids = candidates.map((c) => c.idHint);
expect(ids).toContain("pack/one");
expect(ids).toContain("pack/two");
});
it("derives unscoped ids for scoped packages", async () => {
const stateDir = makeTempDir();
const globalExt = path.join(stateDir, "extensions", "voice-call-pack");
fs.mkdirSync(path.join(globalExt, "src"), { recursive: true });
fs.writeFileSync(
path.join(globalExt, "package.json"),
JSON.stringify({
name: "@openclaw/voice-call",
openclaw: { extensions: ["./src/index.ts"] },
}),
"utf-8",
);
fs.writeFileSync(
path.join(globalExt, "src", "index.ts"),
"export default function () {}",
"utf-8",
);
const { candidates } = await withStateDir(stateDir, async () => {
const { discoverOpenClawPlugins } = await import("./discovery.js");
return discoverOpenClawPlugins({});
});
const ids = candidates.map((c) => c.idHint);
expect(ids).toContain("voice-call");
});
it("treats configured directory paths as plugin packages", async () => {
const stateDir = makeTempDir();
const packDir = path.join(stateDir, "packs", "demo-plugin-dir");
fs.mkdirSync(packDir, { recursive: true });
fs.writeFileSync(
path.join(packDir, "package.json"),
JSON.stringify({
name: "@openclaw/demo-plugin-dir",
openclaw: { extensions: ["./index.js"] },
}),
"utf-8",
);
fs.writeFileSync(path.join(packDir, "index.js"), "module.exports = {}", "utf-8");
const { candidates } = await withStateDir(stateDir, async () => {
const { discoverOpenClawPlugins } = await import("./discovery.js");
return discoverOpenClawPlugins({ extraPaths: [packDir] });
});
const ids = candidates.map((c) => c.idHint);
expect(ids).toContain("demo-plugin-dir");
});
});
|