import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { stageBundledPluginRuntime } from "./stage-bundled-plugin-runtime.mjs"; const warningFilterKey = Symbol.for("openclaw.warning-filter"); function installProcessWarningFilter() { if (globalThis[warningFilterKey]?.installed) { return; } const originalEmitWarning = process.emitWarning.bind(process); process.emitWarning = (...args) => { const [warningArg, secondArg, thirdArg] = args; const warning = warningArg instanceof Error ? { name: warningArg.name, message: warningArg.message, code: warningArg.code, } : { name: typeof secondArg === "string" ? secondArg : secondArg?.type, message: typeof warningArg === "string" ? warningArg : undefined, code: typeof thirdArg === "string" ? thirdArg : secondArg?.code, }; if (warning.code === "DEP0040" && warning.message?.includes("punycode")) { return; } return Reflect.apply(originalEmitWarning, process, args); }; globalThis[warningFilterKey] = { installed: true }; } installProcessWarningFilter(); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const smokeEntryPath = path.join(repoRoot, "dist", "plugins", "build-smoke-entry.js"); assert.ok(fs.existsSync(smokeEntryPath), `missing build output: ${smokeEntryPath}`); const { clearPluginCommands, getPluginCommandSpecs, loadOpenClawPlugins, matchPluginCommand } = await import(pathToFileURL(smokeEntryPath).href); assert.equal(typeof loadOpenClawPlugins, "function", "built loader export missing"); assert.equal(typeof clearPluginCommands, "function", "clearPluginCommands missing"); assert.equal(typeof getPluginCommandSpecs, "function", "getPluginCommandSpecs missing"); assert.equal(typeof matchPluginCommand, "function", "matchPluginCommand missing"); const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-build-smoke-")); function cleanup() { clearPluginCommands(); fs.rmSync(tempRoot, { recursive: true, force: true }); } process.on("exit", cleanup); process.on("SIGINT", () => { cleanup(); process.exit(130); }); process.on("SIGTERM", () => { cleanup(); process.exit(143); }); const pluginId = "build-smoke-plugin"; const distPluginDir = path.join(tempRoot, "dist", "extensions", pluginId); fs.mkdirSync(distPluginDir, { recursive: true }); fs.writeFileSync(path.join(tempRoot, "package.json"), '{ "type": "module" }\n', "utf8"); fs.writeFileSync( path.join(distPluginDir, "package.json"), JSON.stringify( { name: "@openclaw/build-smoke-plugin", type: "module", openclaw: { extensions: ["./index.js"], }, }, null, 2, ), "utf8", ); fs.writeFileSync( path.join(distPluginDir, "openclaw.plugin.json"), JSON.stringify( { id: pluginId, configSchema: { type: "object", additionalProperties: false, properties: {}, }, }, null, 2, ), "utf8", ); fs.writeFileSync( path.join(distPluginDir, "index.js"), [ "import sdk from 'openclaw/plugin-sdk';", "const { emptyPluginConfigSchema } = sdk;", "", "export default {", ` id: ${JSON.stringify(pluginId)},`, " configSchema: emptyPluginConfigSchema(),", " register(api) {", " api.registerCommand({", " name: 'pair',", " description: 'Pair a device',", " acceptsArgs: true,", " nativeNames: { telegram: 'pair', discord: 'pair' },", " async handler({ args }) {", " return { text: `paired:${args ?? ''}` };", " },", " });", " },", "};", "", ].join("\n"), "utf8", ); stageBundledPluginRuntime({ repoRoot: tempRoot }); const runtimeEntryPath = path.join(tempRoot, "dist-runtime", "extensions", pluginId, "index.js"); assert.ok(fs.existsSync(runtimeEntryPath), "runtime overlay entry missing"); assert.equal( fs.existsSync(path.join(tempRoot, "dist-runtime", "plugins", "commands.js")), false, "dist-runtime must not stage a duplicate commands module", ); clearPluginCommands(); const registry = loadOpenClawPlugins({ cache: false, workspaceDir: tempRoot, env: { ...process.env, OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(tempRoot, "dist-runtime", "extensions"), OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1", }, config: { plugins: { enabled: true, allow: [pluginId], entries: { [pluginId]: { enabled: true }, }, }, }, }); const record = registry.plugins.find((entry) => entry.id === pluginId); assert.ok(record, "smoke plugin missing from registry"); assert.equal(record.status, "loaded", record.error ?? "smoke plugin failed to load"); assert.deepEqual(getPluginCommandSpecs("telegram"), [ { name: "pair", description: "Pair a device", acceptsArgs: true }, ]); const match = matchPluginCommand("/pair now"); assert.ok(match, "canonical built command registry did not receive the command"); assert.equal(match.args, "now"); const result = await match.command.handler({ args: match.args }); assert.deepEqual(result, { text: "paired:now" }); process.stdout.write("[build-smoke] built plugin singleton smoke passed\n");