perf(plugins): list from installed metadata (#117900)

This commit is contained in:
Peter Steinberger
2026-08-02 01:18:40 -07:00
committed by GitHub
parent f5c985d92c
commit 76ec39556e
4 changed files with 132 additions and 24 deletions
+2 -1
View File
@@ -44,7 +44,8 @@ export async function runPluginsListCommand(
runtime: RuntimeEnv = defaultRuntime,
): Promise<void> {
const { buildPluginRegistrySnapshotReport } = await import("../plugins/status-snapshot.js");
const cfg = getRuntimeConfig();
// The inventory projector owns plugin metadata validation from the installed index.
const cfg = getRuntimeConfig({ skipPluginValidation: true });
const report = buildPluginRegistrySnapshotReport({
config: cfg,
...(opts.json ? { logger: quietPluginJsonLogger } : {}),
@@ -0,0 +1,96 @@
// The default plugin inventory route must render manifest facts without executing plugin code.
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterAll, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import {
createColdPluginFixture,
isColdPluginRuntimeLoaded,
} from "../plugins/test-helpers/cold-plugin-fixtures.js";
const testState = vi.hoisted(() => ({
config: {} as OpenClawConfig,
loadedModules: new Set<string>(),
logs: [] as string[],
}));
vi.mock("./command-execution-startup.js", () => ({
applyCliExecutionStartupPresentation: vi.fn(async () => {}),
ensureCliExecutionBootstrap: vi.fn(async () => {}),
resolveCliExecutionStartupContext: vi.fn(() => ({
startupPolicy: { loadPlugins: false, suppressDoctorStdout: true },
})),
}));
vi.mock("../config/config.js", () => ({
getRuntimeConfig: () => testState.config,
}));
vi.mock("../runtime.js", () => ({
defaultRuntime: {
error: vi.fn(),
exit: vi.fn(),
log: (...args: unknown[]) => testState.logs.push(args.map(String).join(" ")),
writeJson: vi.fn(),
writeStdout: vi.fn(),
},
writeRuntimeJson: vi.fn(),
}));
vi.mock("./plugins-cli.js", () => {
testState.loadedModules.add("plugins-cli");
return { registerPluginsCli: vi.fn() };
});
vi.mock("../plugins/loader-module-runtime.js", () => {
testState.loadedModules.add("plugin-module-runtime");
return {};
});
import { tryRouteCli } from "./route.js";
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugins-list-route-"));
afterAll(() => {
fs.rmSync(tempRoot, { recursive: true, force: true });
vi.unstubAllEnvs();
});
it("renders the default list from metadata without loading plugin modules", async () => {
const pluginRoot = path.join(tempRoot, "route-demo");
fs.mkdirSync(pluginRoot, { recursive: true });
const fixture = createColdPluginFixture({
rootDir: pluginRoot,
pluginId: "route-demo",
packageName: "@example/route-demo",
packageVersion: "4.2.0",
manifest: {
name: "Route Demo",
description: "Prepared inventory metadata",
},
});
const bundledRoot = path.join(tempRoot, "bundled");
fs.mkdirSync(bundledRoot, { recursive: true });
vi.stubEnv("OPENCLAW_BUNDLED_PLUGINS_DIR", bundledRoot);
vi.stubEnv("OPENCLAW_DISABLE_BUNDLED_PLUGINS", "1");
vi.stubEnv("OPENCLAW_HOME", path.join(tempRoot, "home"));
vi.stubEnv("OPENCLAW_STATE_DIR", path.join(tempRoot, "state"));
testState.config = {
plugins: {
load: { paths: [pluginRoot] },
entries: { "route-demo": { enabled: true } },
},
};
await expect(tryRouteCli(["node", "openclaw", "plugins", "list"])).resolves.toBe(true);
const output = testState.logs.join("\n");
expect(output).toContain("Route Demo");
expect(output).toContain("route-demo");
expect(output).toContain("Prepared inventory metadata");
expect(output).toContain("4.2.0");
expect(isColdPluginRuntimeLoaded(fixture)).toBe(false);
expect(testState.loadedModules).not.toContain("plugin-module-runtime");
expect(testState.loadedModules).not.toContain("plugins-cli");
});
+2 -5
View File
@@ -449,11 +449,8 @@ export function parseChannelsStatusRouteArgs(argv: string[]) {
};
}
/** Parse JSON-only `openclaw plugins list` flags for plugin inventory output. */
/** Parse `openclaw plugins list` flags for the metadata-only inventory path. */
export function parsePluginsListRouteArgs(argv: string[]) {
if (!hasFlag(argv, "--json")) {
return null;
}
const positionals = getRoutedCommandPositionals(argv, {
commandPath: ["plugins", "list"],
booleanFlags: ["--json", "--enabled", "--verbose"],
@@ -462,7 +459,7 @@ export function parsePluginsListRouteArgs(argv: string[]) {
return null;
}
return {
json: true as const,
json: hasFlag(argv, "--json"),
enabled: hasFlag(argv, "--enabled"),
verbose: hasFlag(argv, "--verbose"),
};
+32 -18
View File
@@ -169,27 +169,41 @@ describe("program routes", () => {
);
});
it("routes plugins list JSON without importing the full plugins CLI", async () => {
const route = expectRoute(["plugins", "list"]);
expect(route.loadPlugins).toBeUndefined();
expect(route.canRun?.(["node", "openclaw", "plugins", "list"])).toBe(false);
it.each([
{ label: "default", flags: [], options: { json: false, enabled: false, verbose: false } },
{
label: "enabled",
flags: ["--enabled"],
options: { json: false, enabled: true, verbose: false },
},
{
label: "verbose",
flags: ["--verbose"],
options: { json: false, enabled: false, verbose: true },
},
{
label: "JSON",
flags: ["--json", "--enabled", "--verbose"],
options: { json: true, enabled: true, verbose: true },
},
])(
"routes plugins list $label without importing the full plugins CLI",
async ({ flags, options }) => {
const route = expectRoute(["plugins", "list"]);
expect(route.loadPlugins).toBeUndefined();
expect(route.canRun?.(["node", "openclaw", "plugins", "list", ...flags])).toBe(true);
await expect(
route.run(["node", "openclaw", "plugins", "list", "--json", "--enabled", "--verbose"]),
).resolves.toBe(true);
await expect(route.run(["node", "openclaw", "plugins", "list", ...flags])).resolves.toBe(
true,
);
expect(runPluginsListCommandMock).toHaveBeenCalledWith(
{ json: true, enabled: true, verbose: true },
defaultRuntime,
);
expect(pluginsCliLoadedMock).not.toHaveBeenCalled();
});
expect(runPluginsListCommandMock).toHaveBeenCalledWith(options, defaultRuntime);
expect(pluginsCliLoadedMock).not.toHaveBeenCalled();
},
);
it("returns false for plugins list JSON route with unsupported arguments", async () => {
await expectRunFalse(
["plugins", "list"],
["node", "openclaw", "plugins", "list", "--json", "--wat"],
);
it("returns false for plugins list route with unsupported arguments", async () => {
await expectRunFalse(["plugins", "list"], ["node", "openclaw", "plugins", "list", "--wat"]);
});
it("matches gateway status route without plugin preload", () => {