Spaces:
Configuration error
Configuration error
| 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(), `moltbot-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.CLAWDBOT_STATE_DIR; | |
| const prevBundled = process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR; | |
| process.env.CLAWDBOT_STATE_DIR = stateDir; | |
| process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = "/nonexistent/bundled/plugins"; | |
| vi.resetModules(); | |
| try { | |
| return await fn(); | |
| } finally { | |
| if (prev === undefined) { | |
| delete process.env.CLAWDBOT_STATE_DIR; | |
| } else { | |
| process.env.CLAWDBOT_STATE_DIR = prev; | |
| } | |
| if (prevBundled === undefined) { | |
| delete process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR; | |
| } else { | |
| process.env.CLAWDBOT_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("discoverMoltbotPlugins", () => { | |
| 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, ".clawdbot", "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 { discoverMoltbotPlugins } = await import("./discovery.js"); | |
| return discoverMoltbotPlugins({ 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", | |
| moltbot: { 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 { discoverMoltbotPlugins } = await import("./discovery.js"); | |
| return discoverMoltbotPlugins({}); | |
| }); | |
| 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: "@moltbot/voice-call", | |
| moltbot: { 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 { discoverMoltbotPlugins } = await import("./discovery.js"); | |
| return discoverMoltbotPlugins({}); | |
| }); | |
| 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: "@moltbot/demo-plugin-dir", | |
| moltbot: { extensions: ["./index.js"] }, | |
| }), | |
| "utf-8", | |
| ); | |
| fs.writeFileSync(path.join(packDir, "index.js"), "module.exports = {}", "utf-8"); | |
| const { candidates } = await withStateDir(stateDir, async () => { | |
| const { discoverMoltbotPlugins } = await import("./discovery.js"); | |
| return discoverMoltbotPlugins({ extraPaths: [packDir] }); | |
| }); | |
| const ids = candidates.map((c) => c.idHint); | |
| expect(ids).toContain("demo-plugin-dir"); | |
| }); | |
| }); | |