mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-28 18:33:37 +00:00
fix: resolve bundled plugins from running CLI
This commit is contained in:
@@ -7,6 +7,7 @@ import { resolveBundledPluginsDir } from "./bundled-dir.js";
|
||||
const tempDirs: string[] = [];
|
||||
const originalBundledDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
|
||||
const originalVitest = process.env.VITEST;
|
||||
const originalArgv1 = process.argv[1];
|
||||
|
||||
function makeRepoRoot(prefix: string): string {
|
||||
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
@@ -26,6 +27,7 @@ afterEach(() => {
|
||||
} else {
|
||||
process.env.VITEST = originalVitest;
|
||||
}
|
||||
process.argv[1] = originalArgv1;
|
||||
for (const dir of tempDirs.splice(0, tempDirs.length)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
@@ -43,6 +45,7 @@ describe("resolveBundledPluginsDir", () => {
|
||||
);
|
||||
|
||||
vi.spyOn(process, "cwd").mockReturnValue(repoRoot);
|
||||
process.argv[1] = "/usr/bin/env";
|
||||
|
||||
expect(fs.realpathSync(resolveBundledPluginsDir() ?? "")).toBe(
|
||||
fs.realpathSync(path.join(repoRoot, "dist-runtime", "extensions")),
|
||||
@@ -59,6 +62,7 @@ describe("resolveBundledPluginsDir", () => {
|
||||
);
|
||||
|
||||
vi.spyOn(process, "cwd").mockReturnValue(repoRoot);
|
||||
process.argv[1] = "/usr/bin/env";
|
||||
|
||||
expect(fs.realpathSync(resolveBundledPluginsDir() ?? "")).toBe(
|
||||
fs.realpathSync(path.join(repoRoot, "dist", "extensions")),
|
||||
@@ -78,6 +82,7 @@ describe("resolveBundledPluginsDir", () => {
|
||||
|
||||
vi.spyOn(process, "cwd").mockReturnValue(repoRoot);
|
||||
process.env.VITEST = "true";
|
||||
process.argv[1] = "/usr/bin/env";
|
||||
|
||||
expect(fs.realpathSync(resolveBundledPluginsDir() ?? "")).toBe(
|
||||
fs.realpathSync(path.join(repoRoot, "extensions")),
|
||||
@@ -99,9 +104,54 @@ describe("resolveBundledPluginsDir", () => {
|
||||
|
||||
vi.spyOn(process, "cwd").mockReturnValue(repoRoot);
|
||||
delete process.env.VITEST;
|
||||
process.argv[1] = "/usr/bin/env";
|
||||
|
||||
expect(fs.realpathSync(resolveBundledPluginsDir() ?? "")).toBe(
|
||||
fs.realpathSync(path.join(repoRoot, "extensions")),
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers the running CLI package root over an unrelated cwd checkout", () => {
|
||||
const installedRoot = makeRepoRoot("openclaw-bundled-dir-installed-");
|
||||
fs.mkdirSync(path.join(installedRoot, "dist", "extensions"), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(installedRoot, "package.json"),
|
||||
`${JSON.stringify({ name: "openclaw" }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const cwdRepoRoot = makeRepoRoot("openclaw-bundled-dir-cwd-");
|
||||
fs.mkdirSync(path.join(cwdRepoRoot, "extensions"), { recursive: true });
|
||||
fs.mkdirSync(path.join(cwdRepoRoot, "src"), { recursive: true });
|
||||
fs.writeFileSync(path.join(cwdRepoRoot, ".git"), "gitdir: /tmp/fake.git\n", "utf8");
|
||||
fs.writeFileSync(
|
||||
path.join(cwdRepoRoot, "package.json"),
|
||||
`${JSON.stringify({ name: "openclaw" }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
|
||||
vi.spyOn(process, "cwd").mockReturnValue(cwdRepoRoot);
|
||||
process.argv[1] = path.join(installedRoot, "openclaw.mjs");
|
||||
|
||||
expect(fs.realpathSync(resolveBundledPluginsDir() ?? "")).toBe(
|
||||
fs.realpathSync(path.join(installedRoot, "dist", "extensions")),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to the running installed package when the override path is stale", () => {
|
||||
const installedRoot = makeRepoRoot("openclaw-bundled-dir-override-");
|
||||
fs.mkdirSync(path.join(installedRoot, "dist", "extensions"), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(installedRoot, "package.json"),
|
||||
`${JSON.stringify({ name: "openclaw" }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
|
||||
process.argv[1] = path.join(installedRoot, "openclaw.mjs");
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = path.join(installedRoot, "missing-extensions");
|
||||
|
||||
expect(fs.realpathSync(resolveBundledPluginsDir() ?? "")).toBe(
|
||||
fs.realpathSync(path.join(installedRoot, "dist", "extensions")),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,39 +12,69 @@ function isSourceCheckoutRoot(packageRoot: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function resolveBundledDirFromPackageRoot(
|
||||
packageRoot: string,
|
||||
preferSourceCheckout: boolean,
|
||||
): string | undefined {
|
||||
const sourceExtensionsDir = path.join(packageRoot, "extensions");
|
||||
const builtExtensionsDir = path.join(packageRoot, "dist", "extensions");
|
||||
if (
|
||||
(preferSourceCheckout || isSourceCheckoutRoot(packageRoot)) &&
|
||||
fs.existsSync(sourceExtensionsDir)
|
||||
) {
|
||||
return sourceExtensionsDir;
|
||||
}
|
||||
// Local source checkouts stage a runtime-complete bundled plugin tree under
|
||||
// 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)) {
|
||||
return runtimeExtensionsDir;
|
||||
}
|
||||
if (fs.existsSync(builtExtensionsDir)) {
|
||||
return builtExtensionsDir;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function resolveBundledPluginsDir(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
||||
const override = env.OPENCLAW_BUNDLED_PLUGINS_DIR?.trim();
|
||||
if (override) {
|
||||
return resolveUserPath(override, env);
|
||||
const resolvedOverride = resolveUserPath(override, env);
|
||||
if (fs.existsSync(resolvedOverride)) {
|
||||
return resolvedOverride;
|
||||
}
|
||||
// Installed CLIs can inherit stale bundled-dir overrides from older shells
|
||||
// or debug sessions. Prefer the package that owns argv[1] over a broken
|
||||
// override so bundled providers keep working in packaged installs.
|
||||
try {
|
||||
const argvPackageRoot = resolveOpenClawPackageRootSync({ argv1: process.argv[1] });
|
||||
if (argvPackageRoot && !isSourceCheckoutRoot(argvPackageRoot)) {
|
||||
const argvFallback = resolveBundledDirFromPackageRoot(argvPackageRoot, false);
|
||||
if (argvFallback) {
|
||||
return argvFallback;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return resolvedOverride;
|
||||
}
|
||||
|
||||
const preferSourceCheckout = Boolean(env.VITEST);
|
||||
|
||||
try {
|
||||
const packageRoots = [
|
||||
resolveOpenClawPackageRootSync({ argv1: process.argv[1] }),
|
||||
resolveOpenClawPackageRootSync({ cwd: process.cwd() }),
|
||||
resolveOpenClawPackageRootSync({ moduleUrl: import.meta.url }),
|
||||
].filter(
|
||||
(entry, index, all): entry is string => Boolean(entry) && all.indexOf(entry) === index,
|
||||
);
|
||||
for (const packageRoot of packageRoots) {
|
||||
const sourceExtensionsDir = path.join(packageRoot, "extensions");
|
||||
const builtExtensionsDir = path.join(packageRoot, "dist", "extensions");
|
||||
if (
|
||||
(preferSourceCheckout || isSourceCheckoutRoot(packageRoot)) &&
|
||||
fs.existsSync(sourceExtensionsDir)
|
||||
) {
|
||||
return sourceExtensionsDir;
|
||||
}
|
||||
// Local source checkouts stage a runtime-complete bundled plugin tree under
|
||||
// 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)) {
|
||||
return runtimeExtensionsDir;
|
||||
}
|
||||
if (fs.existsSync(builtExtensionsDir)) {
|
||||
return builtExtensionsDir;
|
||||
const bundledDir = resolveBundledDirFromPackageRoot(packageRoot, preferSourceCheckout);
|
||||
if (bundledDir) {
|
||||
return bundledDir;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user