mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
fix(plugins): prefer usable bundled plugin trees
This commit is contained in:
@@ -46,6 +46,21 @@ function createOpenClawRoot(params: {
|
||||
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;
|
||||
@@ -205,6 +220,12 @@ describe("resolveBundledPluginsDir", () => {
|
||||
],
|
||||
] 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,
|
||||
@@ -212,6 +233,26 @@ describe("resolveBundledPluginsDir", () => {
|
||||
});
|
||||
});
|
||||
|
||||
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-",
|
||||
@@ -239,6 +280,7 @@ describe("resolveBundledPluginsDir", () => {
|
||||
prefix: "openclaw-bundled-dir-installed-",
|
||||
hasDistExtensions: true,
|
||||
});
|
||||
seedBundledPluginTree(installedRoot, path.join("dist", "extensions"));
|
||||
const cwdRepoRoot = createOpenClawRoot({
|
||||
prefix: "openclaw-bundled-dir-cwd-",
|
||||
hasExtensions: true,
|
||||
@@ -259,6 +301,7 @@ describe("resolveBundledPluginsDir", () => {
|
||||
prefix: "openclaw-bundled-dir-override-",
|
||||
hasDistExtensions: true,
|
||||
});
|
||||
seedBundledPluginTree(installedRoot, path.join("dist", "extensions"));
|
||||
return {
|
||||
installedRoot,
|
||||
argv1: path.join(installedRoot, "openclaw.mjs"),
|
||||
|
||||
@@ -25,12 +25,33 @@ function isSourceCheckoutRoot(packageRoot: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function hasUsableBundledPluginTree(pluginsDir: string): boolean {
|
||||
if (!fs.existsSync(pluginsDir)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return fs.readdirSync(pluginsDir, { withFileTypes: true }).some((entry) => {
|
||||
if (!entry.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
const pluginDir = path.join(pluginsDir, entry.name);
|
||||
return (
|
||||
fs.existsSync(path.join(pluginDir, "package.json")) ||
|
||||
fs.existsSync(path.join(pluginDir, "openclaw.plugin.json"))
|
||||
);
|
||||
});
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveBundledDirFromPackageRoot(
|
||||
packageRoot: string,
|
||||
preferSourceCheckout: boolean,
|
||||
): string | undefined {
|
||||
const sourceExtensionsDir = path.join(packageRoot, "extensions");
|
||||
const builtExtensionsDir = path.join(packageRoot, "dist", "extensions");
|
||||
const sourceCheckout = isSourceCheckoutRoot(packageRoot);
|
||||
if (preferSourceCheckout && fs.existsSync(sourceExtensionsDir)) {
|
||||
return sourceExtensionsDir;
|
||||
}
|
||||
@@ -38,13 +59,19 @@ function resolveBundledDirFromPackageRoot(
|
||||
// dist-runtime/. Prefer that over source extensions only when the paired
|
||||
// dist/ tree exists; otherwise wrappers can drift ahead of the last build.
|
||||
const runtimeExtensionsDir = path.join(packageRoot, "dist-runtime", "extensions");
|
||||
if (fs.existsSync(runtimeExtensionsDir) && fs.existsSync(builtExtensionsDir)) {
|
||||
const hasUsableRuntimeTree = sourceCheckout
|
||||
? hasUsableBundledPluginTree(runtimeExtensionsDir)
|
||||
: fs.existsSync(runtimeExtensionsDir);
|
||||
const hasUsableBuiltTree = sourceCheckout
|
||||
? hasUsableBundledPluginTree(builtExtensionsDir)
|
||||
: fs.existsSync(builtExtensionsDir);
|
||||
if (hasUsableRuntimeTree && hasUsableBuiltTree) {
|
||||
return runtimeExtensionsDir;
|
||||
}
|
||||
if (fs.existsSync(builtExtensionsDir)) {
|
||||
if (hasUsableBuiltTree) {
|
||||
return builtExtensionsDir;
|
||||
}
|
||||
if (isSourceCheckoutRoot(packageRoot) && fs.existsSync(sourceExtensionsDir)) {
|
||||
if (sourceCheckout && fs.existsSync(sourceExtensionsDir)) {
|
||||
return sourceExtensionsDir;
|
||||
}
|
||||
return undefined;
|
||||
|
||||
Reference in New Issue
Block a user