import fs from "node:fs" ;
import path from "node:path" ;
import { afterEach, describe, expect, it, vi } from "vitest" ;
import { resolveBundledPluginsDir } from "./bundled-dir.js" ;
import { cleanupTrackedTempDirs, makeTrackedTempDir } from "./test-helpers/fs-fixtures.js" ;
const tempDirs: string[] = [];
const originalBundledDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const originalDisableBundledPlugins = process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS;
const originalVitest = process.env.VITEST;
const originalArgv1 = process.argv[1 ];
const originalExecArgv = [...process.execArgv];
function makeRepoRoot(prefix: string): string {
return makeTrackedTempDir(prefix, tempDirs);
}
function createOpenClawRoot(params: {
prefix: string;
hasExtensions?: boolean ;
hasSrc?: boolean ;
hasDistRuntimeExtensions?: boolean ;
hasDistExtensions?: boolean ;
hasGitCheckout?: boolean ;
}) {
const repoRoot = makeRepoRoot(params.prefix);
if (params.hasExtensions) {
fs.mkdirSync(path.join(repoRoot, "extensions" ), { recursive: true });
}
if (params.hasSrc) {
fs.mkdirSync(path.join(repoRoot, "src" ), { recursive: true });
}
if (params.hasDistRuntimeExtensions) {
fs.mkdirSync(path.join(repoRoot, "dist-runtime" , "extensions" ), { recursive: true });
}
if (params.hasDistExtensions) {
fs.mkdirSync(path.join(repoRoot, "dist" , "extensions" ), { recursive: true });
}
if (params.hasGitCheckout) {
fs.writeFileSync(path.join(repoRoot, ".git" ), "gitdir: /tmp/fake.git\n" , "utf8" );
}
fs.writeFileSync(
path.join(repoRoot, "package.json" ),
`${JSON.stringify({ name: "openclaw" }, null , 2 )}\n`,
"utf8" ,
);
return repoRoot;
}
function seedBundledPluginTree(rootDir: string, relativeDir: string, pluginId = "discord" ) {
const pluginDir = path.join(rootDir, relativeDir, pluginId);
fs.mkdirSync(pluginDir, { recursive: true });
fs.writeFileSync(
path.join(pluginDir, "package.json" ),
`${JSON.stringify({ name: `@openclaw/${pluginId}` }, null , 2 )}\n`,
"utf8" ,
);
fs.writeFileSync(
path.join(pluginDir, "openclaw.plugin.json" ),
`${JSON.stringify({ id: pluginId }, null , 2 )}\n`,
"utf8" ,
);
}
function expectResolvedBundledDir(params: {
cwd: string;
expectedDir: string;
argv1?: string;
bundledDirOverride?: string;
disableBundledPlugins?: string;
vitest?: string;
execArgv?: readonly string[];
}) {
vi.spyOn(process, "cwd" ).mockReturnValue(params.cwd);
process.argv[1 ] = params.argv1 ?? "/usr/bin/env" ;
process.execArgv.length = 0 ;
process.execArgv.push(...(params.execArgv ?? []));
if (params.vitest === undefined) {
delete process.env.VITEST;
} else {
process.env.VITEST = params.vitest;
}
if (params.bundledDirOverride === undefined) {
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
} else {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = params.bundledDirOverride;
}
if (params.disableBundledPlugins === undefined) {
delete process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS;
} else {
process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = params.disableBundledPlugins;
}
expect(fs.realpathSync(resolveBundledPluginsDir() ?? "" )).toBe(
fs.realpathSync(params.expectedDir),
);
}
function expectResolvedBundledDirFromRoot(params: {
repoRoot: string;
expectedRelativeDir: string;
argv1?: string;
bundledDirOverride?: string;
vitest?: string;
cwd?: string;
execArgv?: readonly string[];
}) {
expectResolvedBundledDir({
cwd: params.cwd ?? params.repoRoot,
expectedDir: path.join(params.repoRoot, params.expectedRelativeDir),
...(params.argv1 ? { argv1: params.argv1 } : {}),
...(params.bundledDirOverride ? { bundledDirOverride: params.bundledDirOverride } : {}),
...(params.vitest !== undefined ? { vitest: params.vitest } : {}),
...(params.execArgv ? { execArgv: params.execArgv } : {}),
});
}
function expectInstalledBundledDirScenario(params: {
installedRoot: string;
cwd?: string;
argv1?: string;
bundledDirOverride?: string;
}) {
expectResolvedBundledDirFromRoot({
repoRoot: params.installedRoot,
cwd: params.cwd ?? process.cwd(),
...(params.argv1 ? { argv1: params.argv1 } : {}),
...(params.bundledDirOverride ? { bundledDirOverride: params.bundledDirOverride } : {}),
expectedRelativeDir: path.join("dist" , "extensions" ),
});
}
function expectInstalledBundledDirScenarioCase(
createScenario: () => {
installedRoot: string;
cwd?: string;
argv1?: string;
bundledDirOverride?: string;
},
) {
expectInstalledBundledDirScenario(createScenario());
}
afterEach(() => {
vi.restoreAllMocks();
if (originalBundledDir === undefined) {
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
} else {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = originalBundledDir;
}
if (originalDisableBundledPlugins === undefined) {
delete process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS;
} else {
process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = originalDisableBundledPlugins;
}
if (originalVitest === undefined) {
delete process.env.VITEST;
} else {
process.env.VITEST = originalVitest;
}
process.argv[1 ] = originalArgv1;
process.execArgv.length = 0 ;
process.execArgv.push(...originalExecArgv);
cleanupTrackedTempDirs(tempDirs);
});
describe("resolveBundledPluginsDir" , () => {
it.each([
[
"prefers the staged runtime bundled plugin tree from the package root" ,
{
prefix: "openclaw-bundled-dir-runtime-" ,
hasDistRuntimeExtensions: true ,
hasDistExtensions: true ,
},
{
expectedRelativeDir: path.join("dist-runtime" , "extensions" ),
},
],
[
"falls back to built dist/extensions in installed package roots" ,
{
prefix: "openclaw-bundled-dir-dist-" ,
hasDistExtensions: true ,
},
{
expectedRelativeDir: path.join("dist" , "extensions" ),
},
],
[
"prefers built dist/extensions in a git checkout outside vitest" ,
{
prefix: "openclaw-bundled-dir-git-built-" ,
hasExtensions: true ,
hasSrc: true ,
hasDistRuntimeExtensions: true ,
hasDistExtensions: true ,
hasGitCheckout: true ,
},
{
expectedRelativeDir: path.join("dist-runtime" , "extensions" ),
},
],
[
"prefers source extensions under vitest to avoid stale staged plugins" ,
{
prefix: "openclaw-bundled-dir-vitest-" ,
hasExtensions: true ,
hasDistRuntimeExtensions: true ,
hasDistExtensions: true ,
},
{
expectedRelativeDir: "extensions" ,
vitest: "true" ,
},
],
[
"prefers source extensions during tsx-driven source execution" ,
{
prefix: "openclaw-bundled-dir-tsx-" ,
hasExtensions: true ,
hasSrc: true ,
hasDistRuntimeExtensions: true ,
hasDistExtensions: true ,
hasGitCheckout: true ,
},
{
expectedRelativeDir: "extensions" ,
execArgv: ["--import" , "tsx" ],
},
],
[
"falls back to source extensions in a git checkout when built trees are missing" ,
{
prefix: "openclaw-bundled-dir-git-" ,
hasExtensions: true ,
hasSrc: true ,
hasGitCheckout: true ,
},
{
expectedRelativeDir: "extensions" ,
},
],
] as const )("%s" , (_name, layout, expectation) => {
const repoRoot = createOpenClawRoot(layout);
if (expectation.expectedRelativeDir === path.join("dist-runtime" , "extensions" )) {
seedBundledPluginTree(repoRoot, path.join("dist" , "extensions" ));
seedBundledPluginTree(repoRoot, path.join("dist-runtime" , "extensions" ));
} else if (expectation.expectedRelativeDir === path.join("dist" , "extensions" )) {
seedBundledPluginTree(repoRoot, path.join("dist" , "extensions" ));
}
expectResolvedBundledDirFromRoot({
repoRoot,
expectedRelativeDir: expectation.expectedRelativeDir,
...("vitest" in expectation ? { vitest: expectation.vitest } : {}),
...("execArgv" in expectation ? { execArgv: [...expectation.execArgv] } : {}),
});
});
it("falls back to source extensions when dist trees exist but do not contain real plugin manifests" , () => {
const repoRoot = createOpenClawRoot({
prefix: "openclaw-bundled-dir-incomplete-built-" ,
hasExtensions: true ,
hasSrc: true ,
hasDistRuntimeExtensions: true ,
hasDistExtensions: true ,
hasGitCheckout: true ,
});
fs.mkdirSync(path.join(repoRoot, "dist" , "extensions" , "discord" ), { recursive: true });
fs.mkdirSync(path.join(repoRoot, "dist-runtime" , "extensions" , "discord" ), {
recursive: true ,
});
expectResolvedBundledDirFromRoot({
repoRoot,
expectedRelativeDir: "extensions" ,
});
});
it("returns a stable empty bundled plugin directory when bundled plugins are disabled" , () => {
const repoRoot = createOpenClawRoot({
prefix: "openclaw-bundled-dir-disabled-" ,
hasExtensions: true ,
hasSrc: true ,
hasGitCheckout: true ,
});
vi.spyOn(process, "cwd" ).mockReturnValue(repoRoot);
process.argv[1 ] = "/usr/bin/env" ;
process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = "1" ;
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const bundledDir = resolveBundledPluginsDir();
expect(bundledDir).toBeTruthy();
expect(fs.existsSync(bundledDir ?? "" )).toBe(true );
expect(fs.readdirSync(bundledDir ?? "" )).toEqual([]);
});
it.each([
{
name: "prefers the running CLI package root over an unrelated cwd checkout" ,
createScenario: () => {
const installedRoot = createOpenClawRoot({
prefix: "openclaw-bundled-dir-installed-" ,
hasDistExtensions: true ,
});
seedBundledPluginTree(installedRoot, path.join("dist" , "extensions" ));
const cwdRepoRoot = createOpenClawRoot({
prefix: "openclaw-bundled-dir-cwd-" ,
hasExtensions: true ,
hasSrc: true ,
hasGitCheckout: true ,
});
return {
installedRoot,
cwd: cwdRepoRoot,
argv1: path.join(installedRoot, "openclaw.mjs" ),
};
},
},
{
name: "falls back to the running installed package when the override path is stale" ,
createScenario: () => {
const installedRoot = createOpenClawRoot({
prefix: "openclaw-bundled-dir-override-" ,
hasDistExtensions: true ,
});
seedBundledPluginTree(installedRoot, path.join("dist" , "extensions" ));
return {
installedRoot,
argv1: path.join(installedRoot, "openclaw.mjs" ),
bundledDirOverride: path.join(installedRoot, "missing-extensions" ),
};
},
},
] as const )("$name" , ({ createScenario }) => {
expectInstalledBundledDirScenarioCase(createScenario);
});
});
Messung V0.5 in Prozent C=100 H=100 G=100
¤ Dauer der Verarbeitung: 0.8 Sekunden
(vorverarbeitet am 2026-06-10)
¤
*© Formatika GbR, Deutschland