test: harden plugin fixture permissions on macos
This commit is contained in:
@@ -5,13 +5,25 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { clearPluginManifestRegistryCache } from "../plugins/manifest-registry.js";
|
||||
import { validateConfigObjectWithPlugins } from "./config.js";
|
||||
|
||||
async function chmodSafeDir(dir: string) {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
}
|
||||
await fs.chmod(dir, 0o755);
|
||||
}
|
||||
|
||||
async function mkdirSafe(dir: string) {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
await chmodSafeDir(dir);
|
||||
}
|
||||
|
||||
async function writePluginFixture(params: {
|
||||
dir: string;
|
||||
id: string;
|
||||
schema: Record<string, unknown>;
|
||||
channels?: string[];
|
||||
}) {
|
||||
await fs.mkdir(params.dir, { recursive: true });
|
||||
await mkdirSafe(params.dir);
|
||||
await fs.writeFile(
|
||||
path.join(params.dir, "index.js"),
|
||||
`export default { id: "${params.id}", register() {} };`,
|
||||
@@ -32,6 +44,7 @@ async function writePluginFixture(params: {
|
||||
}
|
||||
|
||||
describe("config plugin validation", () => {
|
||||
const previousUmask = process.umask(0o022);
|
||||
let fixtureRoot = "";
|
||||
let suiteHome = "";
|
||||
let badPluginDir = "";
|
||||
@@ -53,8 +66,9 @@ describe("config plugin validation", () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-config-plugin-validation-"));
|
||||
await chmodSafeDir(fixtureRoot);
|
||||
suiteHome = path.join(fixtureRoot, "home");
|
||||
await fs.mkdir(suiteHome, { recursive: true });
|
||||
await mkdirSafe(suiteHome);
|
||||
badPluginDir = path.join(suiteHome, "bad-plugin");
|
||||
enumPluginDir = path.join(suiteHome, "enum-plugin");
|
||||
bluebubblesPluginDir = path.join(suiteHome, "bluebubbles-plugin");
|
||||
@@ -122,6 +136,7 @@ describe("config plugin validation", () => {
|
||||
afterAll(async () => {
|
||||
await fs.rm(fixtureRoot, { recursive: true, force: true });
|
||||
clearPluginManifestRegistryCache();
|
||||
process.umask(previousUmask);
|
||||
});
|
||||
|
||||
it("reports missing plugin refs across load paths, entries, and allowlist surfaces", async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { afterAll, afterEach, describe, expect, it } from "vitest";
|
||||
import { clearPluginDiscoveryCache } from "../plugins/discovery.js";
|
||||
import {
|
||||
clearPluginManifestRegistryCache,
|
||||
@@ -11,15 +11,34 @@ import { validateConfigObject } from "./config.js";
|
||||
import { applyPluginAutoEnable } from "./plugin-auto-enable.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
const previousUmask = process.umask(0o022);
|
||||
|
||||
function chmodSafeDir(dir: string) {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
}
|
||||
fs.chmodSync(dir, 0o755);
|
||||
}
|
||||
|
||||
function mkdtempSafe(prefix: string) {
|
||||
const dir = fs.mkdtempSync(prefix);
|
||||
chmodSafeDir(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
function mkdirSafe(dir: string) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
chmodSafeDir(dir);
|
||||
}
|
||||
|
||||
function makeTempDir() {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-auto-enable-"));
|
||||
const dir = mkdtempSafe(path.join(os.tmpdir(), "openclaw-plugin-auto-enable-"));
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
function writePluginManifestFixture(params: { rootDir: string; id: string; channels: string[] }) {
|
||||
fs.mkdirSync(params.rootDir, { recursive: true });
|
||||
mkdirSafe(params.rootDir);
|
||||
fs.writeFileSync(
|
||||
path.join(params.rootDir, "openclaw.plugin.json"),
|
||||
JSON.stringify(
|
||||
@@ -107,6 +126,10 @@ afterEach(() => {
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.umask(previousUmask);
|
||||
});
|
||||
|
||||
describe("applyPluginAutoEnable", () => {
|
||||
it("auto-enables built-in channels and appends to existing allowlist", () => {
|
||||
const result = applyWithSlackConfig({ plugins: { allow: ["telegram"] } });
|
||||
@@ -228,7 +251,7 @@ describe("applyPluginAutoEnable", () => {
|
||||
it("uses env-scoped catalog metadata for preferOver auto-enable decisions", () => {
|
||||
const stateDir = makeTempDir();
|
||||
const catalogPath = path.join(stateDir, "plugins", "catalog.json");
|
||||
fs.mkdirSync(path.dirname(catalogPath), { recursive: true });
|
||||
mkdirSafe(path.dirname(catalogPath));
|
||||
fs.writeFileSync(
|
||||
catalogPath,
|
||||
JSON.stringify({
|
||||
|
||||
@@ -2,14 +2,27 @@ 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 } from "vitest";
|
||||
import { afterAll, afterEach, describe, expect, it } from "vitest";
|
||||
import { clearPluginDiscoveryCache, discoverOpenClawPlugins } from "./discovery.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
const previousUmask = process.umask(0o022);
|
||||
|
||||
function chmodSafeDir(dir: string) {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
}
|
||||
fs.chmodSync(dir, 0o755);
|
||||
}
|
||||
|
||||
function mkdirSafe(dir: string) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
chmodSafeDir(dir);
|
||||
}
|
||||
|
||||
function makeTempDir() {
|
||||
const dir = path.join(os.tmpdir(), `openclaw-plugins-${randomUUID()}`);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
mkdirSafe(dir);
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
@@ -62,17 +75,21 @@ afterEach(() => {
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.umask(previousUmask);
|
||||
});
|
||||
|
||||
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 });
|
||||
mkdirSafe(globalExt);
|
||||
fs.writeFileSync(path.join(globalExt, "alpha.ts"), "export default function () {}", "utf-8");
|
||||
|
||||
const workspaceExt = path.join(workspaceDir, ".openclaw", "extensions");
|
||||
fs.mkdirSync(workspaceExt, { recursive: true });
|
||||
mkdirSafe(workspaceExt);
|
||||
fs.writeFileSync(path.join(workspaceExt, "beta.ts"), "export default function () {}", "utf-8");
|
||||
|
||||
const { candidates } = await discoverWithStateDir(stateDir, { workspaceDir });
|
||||
@@ -87,7 +104,7 @@ describe("discoverOpenClawPlugins", () => {
|
||||
const homeDir = makeTempDir();
|
||||
const workspaceRoot = path.join(homeDir, "workspace");
|
||||
const workspaceExt = path.join(workspaceRoot, ".openclaw", "extensions");
|
||||
fs.mkdirSync(workspaceExt, { recursive: true });
|
||||
mkdirSafe(workspaceExt);
|
||||
fs.writeFileSync(path.join(workspaceExt, "tilde-workspace.ts"), "export default {}", "utf-8");
|
||||
|
||||
const result = discoverOpenClawPlugins({
|
||||
@@ -106,22 +123,22 @@ describe("discoverOpenClawPlugins", () => {
|
||||
it("ignores backup and disabled plugin directories in scanned roots", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const globalExt = path.join(stateDir, "extensions");
|
||||
fs.mkdirSync(globalExt, { recursive: true });
|
||||
mkdirSafe(globalExt);
|
||||
|
||||
const backupDir = path.join(globalExt, "feishu.backup-20260222");
|
||||
fs.mkdirSync(backupDir, { recursive: true });
|
||||
mkdirSafe(backupDir);
|
||||
fs.writeFileSync(path.join(backupDir, "index.ts"), "export default function () {}", "utf-8");
|
||||
|
||||
const disabledDir = path.join(globalExt, "telegram.disabled.20260222");
|
||||
fs.mkdirSync(disabledDir, { recursive: true });
|
||||
mkdirSafe(disabledDir);
|
||||
fs.writeFileSync(path.join(disabledDir, "index.ts"), "export default function () {}", "utf-8");
|
||||
|
||||
const bakDir = path.join(globalExt, "discord.bak");
|
||||
fs.mkdirSync(bakDir, { recursive: true });
|
||||
mkdirSafe(bakDir);
|
||||
fs.writeFileSync(path.join(bakDir, "index.ts"), "export default function () {}", "utf-8");
|
||||
|
||||
const liveDir = path.join(globalExt, "live");
|
||||
fs.mkdirSync(liveDir, { recursive: true });
|
||||
mkdirSafe(liveDir);
|
||||
fs.writeFileSync(path.join(liveDir, "index.ts"), "export default function () {}", "utf-8");
|
||||
|
||||
const { candidates } = await discoverWithStateDir(stateDir, {});
|
||||
@@ -136,7 +153,7 @@ describe("discoverOpenClawPlugins", () => {
|
||||
it("loads package extension packs", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const globalExt = path.join(stateDir, "extensions", "pack");
|
||||
fs.mkdirSync(path.join(globalExt, "src"), { recursive: true });
|
||||
mkdirSafe(path.join(globalExt, "src"));
|
||||
|
||||
writePluginPackageManifest({
|
||||
packageDir: globalExt,
|
||||
@@ -164,7 +181,7 @@ describe("discoverOpenClawPlugins", () => {
|
||||
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 });
|
||||
mkdirSafe(path.join(globalExt, "src"));
|
||||
|
||||
writePluginPackageManifest({
|
||||
packageDir: globalExt,
|
||||
@@ -186,7 +203,7 @@ describe("discoverOpenClawPlugins", () => {
|
||||
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 });
|
||||
mkdirSafe(packDir);
|
||||
|
||||
writePluginPackageManifest({
|
||||
packageDir: packDir,
|
||||
@@ -204,7 +221,7 @@ describe("discoverOpenClawPlugins", () => {
|
||||
const stateDir = makeTempDir();
|
||||
const globalExt = path.join(stateDir, "extensions", "escape-pack");
|
||||
const outside = path.join(stateDir, "outside.js");
|
||||
fs.mkdirSync(globalExt, { recursive: true });
|
||||
mkdirSafe(globalExt);
|
||||
|
||||
writePluginPackageManifest({
|
||||
packageDir: globalExt,
|
||||
@@ -224,8 +241,8 @@ describe("discoverOpenClawPlugins", () => {
|
||||
const globalExt = path.join(stateDir, "extensions", "pack");
|
||||
const outsideDir = path.join(stateDir, "outside");
|
||||
const linkedDir = path.join(globalExt, "linked");
|
||||
fs.mkdirSync(globalExt, { recursive: true });
|
||||
fs.mkdirSync(outsideDir, { recursive: true });
|
||||
mkdirSafe(globalExt);
|
||||
mkdirSafe(outsideDir);
|
||||
fs.writeFileSync(path.join(outsideDir, "escape.ts"), "export default {}", "utf-8");
|
||||
try {
|
||||
fs.symlinkSync(outsideDir, linkedDir, process.platform === "win32" ? "junction" : "dir");
|
||||
@@ -254,8 +271,8 @@ describe("discoverOpenClawPlugins", () => {
|
||||
const outsideDir = path.join(stateDir, "outside");
|
||||
const outsideFile = path.join(outsideDir, "escape.ts");
|
||||
const linkedFile = path.join(globalExt, "escape.ts");
|
||||
fs.mkdirSync(globalExt, { recursive: true });
|
||||
fs.mkdirSync(outsideDir, { recursive: true });
|
||||
mkdirSafe(globalExt);
|
||||
mkdirSafe(outsideDir);
|
||||
fs.writeFileSync(outsideFile, "export default {}", "utf-8");
|
||||
try {
|
||||
fs.linkSync(outsideFile, linkedFile);
|
||||
@@ -287,8 +304,8 @@ describe("discoverOpenClawPlugins", () => {
|
||||
const outsideDir = path.join(stateDir, "outside");
|
||||
const outsideManifest = path.join(outsideDir, "package.json");
|
||||
const linkedManifest = path.join(globalExt, "package.json");
|
||||
fs.mkdirSync(globalExt, { recursive: true });
|
||||
fs.mkdirSync(outsideDir, { recursive: true });
|
||||
mkdirSafe(globalExt);
|
||||
mkdirSafe(outsideDir);
|
||||
fs.writeFileSync(path.join(globalExt, "entry.ts"), "export default {}", "utf-8");
|
||||
fs.writeFileSync(
|
||||
outsideManifest,
|
||||
@@ -315,7 +332,7 @@ describe("discoverOpenClawPlugins", () => {
|
||||
it.runIf(process.platform !== "win32")("blocks world-writable plugin paths", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const globalExt = path.join(stateDir, "extensions");
|
||||
fs.mkdirSync(globalExt, { recursive: true });
|
||||
mkdirSafe(globalExt);
|
||||
const pluginPath = path.join(globalExt, "world-open.ts");
|
||||
fs.writeFileSync(pluginPath, "export default function () {}", "utf-8");
|
||||
fs.chmodSync(pluginPath, 0o777);
|
||||
@@ -334,7 +351,7 @@ describe("discoverOpenClawPlugins", () => {
|
||||
const stateDir = makeTempDir();
|
||||
const bundledDir = path.join(stateDir, "bundled");
|
||||
const packDir = path.join(bundledDir, "demo-pack");
|
||||
fs.mkdirSync(packDir, { recursive: true });
|
||||
mkdirSafe(packDir);
|
||||
fs.writeFileSync(path.join(packDir, "index.ts"), "export default function () {}", "utf-8");
|
||||
fs.chmodSync(packDir, 0o777);
|
||||
|
||||
@@ -362,7 +379,7 @@ describe("discoverOpenClawPlugins", () => {
|
||||
async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const globalExt = path.join(stateDir, "extensions");
|
||||
fs.mkdirSync(globalExt, { recursive: true });
|
||||
mkdirSafe(globalExt);
|
||||
fs.writeFileSync(
|
||||
path.join(globalExt, "owner-mismatch.ts"),
|
||||
"export default function () {}",
|
||||
@@ -382,7 +399,7 @@ describe("discoverOpenClawPlugins", () => {
|
||||
it("reuses discovery results from cache until cleared", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const globalExt = path.join(stateDir, "extensions");
|
||||
fs.mkdirSync(globalExt, { recursive: true });
|
||||
mkdirSafe(globalExt);
|
||||
const pluginPath = path.join(globalExt, "cached.ts");
|
||||
fs.writeFileSync(pluginPath, "export default function () {}", "utf-8");
|
||||
|
||||
@@ -420,8 +437,8 @@ describe("discoverOpenClawPlugins", () => {
|
||||
const stateDirB = makeTempDir();
|
||||
const globalExtA = path.join(stateDirA, "extensions");
|
||||
const globalExtB = path.join(stateDirB, "extensions");
|
||||
fs.mkdirSync(globalExtA, { recursive: true });
|
||||
fs.mkdirSync(globalExtB, { recursive: true });
|
||||
mkdirSafe(globalExtA);
|
||||
mkdirSafe(globalExtB);
|
||||
fs.writeFileSync(path.join(globalExtA, "alpha.ts"), "export default function () {}", "utf-8");
|
||||
fs.writeFileSync(path.join(globalExtB, "beta.ts"), "export default function () {}", "utf-8");
|
||||
|
||||
@@ -450,8 +467,8 @@ describe("discoverOpenClawPlugins", () => {
|
||||
const homeB = makeTempDir();
|
||||
const pluginA = path.join(homeA, "plugins", "demo.ts");
|
||||
const pluginB = path.join(homeB, "plugins", "demo.ts");
|
||||
fs.mkdirSync(path.dirname(pluginA), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(pluginB), { recursive: true });
|
||||
mkdirSafe(path.dirname(pluginA));
|
||||
mkdirSafe(path.dirname(pluginB));
|
||||
fs.writeFileSync(pluginA, "export default {}", "utf-8");
|
||||
fs.writeFileSync(pluginB, "export default {}", "utf-8");
|
||||
|
||||
@@ -482,7 +499,7 @@ describe("discoverOpenClawPlugins", () => {
|
||||
const stateDir = makeTempDir();
|
||||
const pluginA = path.join(stateDir, "plugins", "alpha.ts");
|
||||
const pluginB = path.join(stateDir, "plugins", "beta.ts");
|
||||
fs.mkdirSync(path.dirname(pluginA), { recursive: true });
|
||||
mkdirSafe(path.dirname(pluginA));
|
||||
fs.writeFileSync(pluginA, "export default {}", "utf-8");
|
||||
fs.writeFileSync(pluginB, "export default {}", "utf-8");
|
||||
|
||||
|
||||
@@ -34,10 +34,29 @@ const {
|
||||
loadOpenClawPlugins,
|
||||
resetGlobalHookRunner,
|
||||
} = await importFreshPluginTestModules();
|
||||
const previousUmask = process.umask(0o022);
|
||||
|
||||
type TempPlugin = { dir: string; file: string; id: string };
|
||||
|
||||
const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-"));
|
||||
function chmodSafeDir(dir: string) {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
}
|
||||
fs.chmodSync(dir, 0o755);
|
||||
}
|
||||
|
||||
function mkdtempSafe(prefix: string) {
|
||||
const dir = fs.mkdtempSync(prefix);
|
||||
chmodSafeDir(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
function mkdirSafe(dir: string) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
chmodSafeDir(dir);
|
||||
}
|
||||
|
||||
const fixtureRoot = mkdtempSafe(path.join(os.tmpdir(), "openclaw-plugin-"));
|
||||
let tempDirIndex = 0;
|
||||
const prevBundledDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
|
||||
const EMPTY_PLUGIN_SCHEMA = { type: "object", additionalProperties: false, properties: {} };
|
||||
@@ -69,7 +88,7 @@ const BUNDLED_TELEGRAM_PLUGIN_BODY = `module.exports = {
|
||||
|
||||
function makeTempDir() {
|
||||
const dir = path.join(fixtureRoot, `case-${tempDirIndex++}`);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
mkdirSafe(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
@@ -81,7 +100,7 @@ function writePlugin(params: {
|
||||
}): TempPlugin {
|
||||
const dir = params.dir ?? makeTempDir();
|
||||
const filename = params.filename ?? `${params.id}.cjs`;
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
mkdirSafe(dir);
|
||||
const file = path.join(dir, filename);
|
||||
fs.writeFileSync(file, params.body, "utf-8");
|
||||
fs.writeFileSync(
|
||||
@@ -126,7 +145,7 @@ function loadBundledMemoryPluginRegistry(options?: {
|
||||
if (options?.packageMeta) {
|
||||
pluginDir = path.join(bundledDir, "memory-core");
|
||||
pluginFilename = options.pluginFilename ?? "index.js";
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
mkdirSafe(pluginDir);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "package.json"),
|
||||
JSON.stringify(
|
||||
@@ -259,8 +278,8 @@ function createPluginSdkAliasFixture(params?: {
|
||||
const root = makeTempDir();
|
||||
const srcFile = path.join(root, "src", "plugin-sdk", params?.srcFile ?? "index.ts");
|
||||
const distFile = path.join(root, "dist", "plugin-sdk", params?.distFile ?? "index.js");
|
||||
fs.mkdirSync(path.dirname(srcFile), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(distFile), { recursive: true });
|
||||
mkdirSafe(path.dirname(srcFile));
|
||||
mkdirSafe(path.dirname(distFile));
|
||||
fs.writeFileSync(srcFile, params?.srcBody ?? "export {};\n", "utf-8");
|
||||
fs.writeFileSync(distFile, params?.distBody ?? "export {};\n", "utf-8");
|
||||
return { root, srcFile, distFile };
|
||||
@@ -281,6 +300,7 @@ afterAll(() => {
|
||||
} catch {
|
||||
// ignore cleanup failures
|
||||
} finally {
|
||||
process.umask(previousUmask);
|
||||
cachedBundledTelegramDir = "";
|
||||
cachedBundledMemoryDir = "";
|
||||
}
|
||||
@@ -571,7 +591,7 @@ describe("loadOpenClawPlugins", () => {
|
||||
const ignoredHome = makeTempDir();
|
||||
const stateDir = makeTempDir();
|
||||
const pluginDir = path.join(openclawHome, "plugins", "tracked-install-cache");
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
mkdirSafe(pluginDir);
|
||||
const plugin = writePlugin({
|
||||
id: "tracked-install-cache",
|
||||
dir: pluginDir,
|
||||
@@ -1271,8 +1291,8 @@ describe("loadOpenClawPlugins", () => {
|
||||
const bundledDir = makeTempDir();
|
||||
const memoryADir = path.join(bundledDir, "memory-a");
|
||||
const memoryBDir = path.join(bundledDir, "memory-b");
|
||||
fs.mkdirSync(memoryADir, { recursive: true });
|
||||
fs.mkdirSync(memoryBDir, { recursive: true });
|
||||
mkdirSafe(memoryADir);
|
||||
mkdirSafe(memoryBDir);
|
||||
writePlugin({
|
||||
id: "memory-a",
|
||||
dir: memoryADir,
|
||||
@@ -1402,7 +1422,7 @@ describe("loadOpenClawPlugins", () => {
|
||||
const stateDir = makeTempDir();
|
||||
withEnv({ OPENCLAW_STATE_DIR: stateDir, CLAWDBOT_STATE_DIR: undefined }, () => {
|
||||
const globalDir = path.join(stateDir, "extensions", "feishu");
|
||||
fs.mkdirSync(globalDir, { recursive: true });
|
||||
mkdirSafe(globalDir);
|
||||
writePlugin({
|
||||
id: "feishu",
|
||||
body: `module.exports = { id: "feishu", register() {} };`,
|
||||
@@ -1456,7 +1476,7 @@ describe("loadOpenClawPlugins", () => {
|
||||
useNoBundledPlugins();
|
||||
const workspaceDir = makeTempDir();
|
||||
const workspaceExtDir = path.join(workspaceDir, ".openclaw", "extensions", "workspace-helper");
|
||||
fs.mkdirSync(workspaceExtDir, { recursive: true });
|
||||
mkdirSafe(workspaceExtDir);
|
||||
writePlugin({
|
||||
id: "workspace-helper",
|
||||
body: `module.exports = { id: "workspace-helper", register() {} };`,
|
||||
@@ -1484,7 +1504,7 @@ describe("loadOpenClawPlugins", () => {
|
||||
useNoBundledPlugins();
|
||||
const workspaceDir = makeTempDir();
|
||||
const workspaceExtDir = path.join(workspaceDir, ".openclaw", "extensions", "workspace-helper");
|
||||
fs.mkdirSync(workspaceExtDir, { recursive: true });
|
||||
mkdirSafe(workspaceExtDir);
|
||||
writePlugin({
|
||||
id: "workspace-helper",
|
||||
body: `module.exports = { id: "workspace-helper", register() {} };`,
|
||||
@@ -1513,7 +1533,7 @@ describe("loadOpenClawPlugins", () => {
|
||||
const stateDir = makeTempDir();
|
||||
withEnv({ OPENCLAW_STATE_DIR: stateDir, CLAWDBOT_STATE_DIR: undefined }, () => {
|
||||
const globalDir = path.join(stateDir, "extensions", "rogue");
|
||||
fs.mkdirSync(globalDir, { recursive: true });
|
||||
mkdirSafe(globalDir);
|
||||
writePlugin({
|
||||
id: "rogue",
|
||||
body: `module.exports = { id: "rogue", register() {} };`,
|
||||
@@ -1549,7 +1569,7 @@ describe("loadOpenClawPlugins", () => {
|
||||
const ignoredHome = makeTempDir();
|
||||
const stateDir = makeTempDir();
|
||||
const pluginDir = path.join(openclawHome, "plugins", "tracked-load-path");
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
mkdirSafe(pluginDir);
|
||||
const plugin = writePlugin({
|
||||
id: "tracked-load-path",
|
||||
dir: pluginDir,
|
||||
@@ -1591,7 +1611,7 @@ describe("loadOpenClawPlugins", () => {
|
||||
const ignoredHome = makeTempDir();
|
||||
const stateDir = makeTempDir();
|
||||
const pluginDir = path.join(openclawHome, "plugins", "tracked-install-path");
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
mkdirSafe(pluginDir);
|
||||
const plugin = writePlugin({
|
||||
id: "tracked-install-path",
|
||||
dir: pluginDir,
|
||||
@@ -1702,7 +1722,7 @@ describe("loadOpenClawPlugins", () => {
|
||||
}
|
||||
const bundledDir = makeTempDir();
|
||||
const pluginDir = path.join(bundledDir, "hardlinked-bundled");
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
mkdirSafe(pluginDir);
|
||||
|
||||
const outsideDir = makeTempDir();
|
||||
const outsideEntry = path.join(outsideDir, "outside.cjs");
|
||||
|
||||
@@ -2,7 +2,7 @@ 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 } from "vitest";
|
||||
import { afterAll, afterEach, describe, expect, it } from "vitest";
|
||||
import type { PluginCandidate } from "./discovery.js";
|
||||
import {
|
||||
clearPluginManifestRegistryCache,
|
||||
@@ -10,10 +10,23 @@ import {
|
||||
} from "./manifest-registry.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
const previousUmask = process.umask(0o022);
|
||||
|
||||
function chmodSafeDir(dir: string) {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
}
|
||||
fs.chmodSync(dir, 0o755);
|
||||
}
|
||||
|
||||
function mkdirSafe(dir: string) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
chmodSafeDir(dir);
|
||||
}
|
||||
|
||||
function makeTempDir() {
|
||||
const dir = path.join(os.tmpdir(), `openclaw-manifest-registry-${randomUUID()}`);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
mkdirSafe(dir);
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
@@ -133,6 +146,10 @@ afterEach(() => {
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.umask(previousUmask);
|
||||
});
|
||||
|
||||
describe("loadPluginManifestRegistry", () => {
|
||||
it("emits duplicate warning for truly distinct plugins with same id", () => {
|
||||
const dirA = makeTempDir();
|
||||
@@ -214,7 +231,7 @@ describe("loadPluginManifestRegistry", () => {
|
||||
|
||||
it("prefers higher-precedence origins for the same physical directory (config > workspace > global > bundled)", () => {
|
||||
const dir = makeTempDir();
|
||||
fs.mkdirSync(path.join(dir, "sub"), { recursive: true });
|
||||
mkdirSafe(path.join(dir, "sub"));
|
||||
const manifest = { id: "precedence-plugin", configSchema: { type: "object" } };
|
||||
writeManifest(dir, manifest);
|
||||
|
||||
@@ -274,8 +291,8 @@ describe("loadPluginManifestRegistry", () => {
|
||||
const bundledB = makeTempDir();
|
||||
const matrixA = path.join(bundledA, "matrix");
|
||||
const matrixB = path.join(bundledB, "matrix");
|
||||
fs.mkdirSync(matrixA, { recursive: true });
|
||||
fs.mkdirSync(matrixB, { recursive: true });
|
||||
mkdirSafe(matrixA);
|
||||
mkdirSafe(matrixB);
|
||||
writeManifest(matrixA, {
|
||||
id: "matrix",
|
||||
name: "Matrix A",
|
||||
@@ -317,8 +334,8 @@ describe("loadPluginManifestRegistry", () => {
|
||||
const homeB = makeTempDir();
|
||||
const demoA = path.join(homeA, "plugins", "demo");
|
||||
const demoB = path.join(homeB, "plugins", "demo");
|
||||
fs.mkdirSync(demoA, { recursive: true });
|
||||
fs.mkdirSync(demoB, { recursive: true });
|
||||
mkdirSafe(demoA);
|
||||
mkdirSafe(demoB);
|
||||
writeManifest(demoA, {
|
||||
id: "demo",
|
||||
name: "Demo A",
|
||||
|
||||
Reference in New Issue
Block a user