test: harden plugin fixture permissions on macos

This commit is contained in:
Peter Steinberger
2026-03-13 03:13:16 +00:00
parent fb9984a774
commit ec3c20d96d
5 changed files with 150 additions and 58 deletions

View File

@@ -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 () => {

View File

@@ -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({

View File

@@ -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");

View File

@@ -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");

View File

@@ -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",