mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-06 23:01:08 +00:00
Plugins: add install --force overwrite flag (#60544)
Merged via squash.
Prepared head SHA: 28ae50b615
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
committed by
GitHub
parent
3fd29e549d
commit
9004ef65df
@@ -86,6 +86,21 @@ describe("plugins cli install", () => {
|
||||
resetPluginsCliTestState();
|
||||
});
|
||||
|
||||
it("shows the force overwrite option in install help", async () => {
|
||||
const { Command } = await import("commander");
|
||||
const { registerPluginsCli } = await import("./plugins-cli.js");
|
||||
const program = new Command();
|
||||
registerPluginsCli(program);
|
||||
|
||||
const pluginsCommand = program.commands.find((command) => command.name() === "plugins");
|
||||
const installCommand = pluginsCommand?.commands.find((command) => command.name() === "install");
|
||||
const helpText = installCommand?.helpInformation() ?? "";
|
||||
|
||||
expect(helpText).toContain("--force");
|
||||
expect(helpText).toContain("Overwrite an existing installed plugin or");
|
||||
expect(helpText).toContain("hook pack");
|
||||
});
|
||||
|
||||
it("exits when --marketplace is combined with --link", async () => {
|
||||
await expect(
|
||||
runPluginsCommand(["plugins", "install", "alpha", "--marketplace", "local/repo", "--link"]),
|
||||
@@ -95,6 +110,16 @@ describe("plugins cli install", () => {
|
||||
expect(installPluginFromMarketplace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("exits when --force is combined with --link", async () => {
|
||||
await expect(
|
||||
runPluginsCommand(["plugins", "install", "./plugin", "--link", "--force"]),
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(runtimeErrors.at(-1)).toContain("`--force` is not supported with `--link`.");
|
||||
expect(installPluginFromMarketplace).not.toHaveBeenCalled();
|
||||
expect(installPluginFromNpmSpec).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("exits when marketplace install fails", async () => {
|
||||
await expect(
|
||||
runPluginsCommand(["plugins", "install", "alpha", "--marketplace", "local/repo"]),
|
||||
@@ -197,6 +222,20 @@ describe("plugins cli install", () => {
|
||||
expect(runtimeLogs.some((line) => line.includes("Installed plugin: alpha"))).toBe(true);
|
||||
});
|
||||
|
||||
it("passes force through as overwrite mode for marketplace installs", async () => {
|
||||
await expect(
|
||||
runPluginsCommand(["plugins", "install", "alpha", "--marketplace", "local/repo", "--force"]),
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(installPluginFromMarketplace).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
marketplace: "local/repo",
|
||||
plugin: "alpha",
|
||||
mode: "update",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("installs ClawHub plugins and persists source metadata", async () => {
|
||||
const cfg = {
|
||||
plugins: {
|
||||
@@ -256,6 +295,41 @@ describe("plugins cli install", () => {
|
||||
expect(installPluginFromNpmSpec).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes force through as overwrite mode for ClawHub installs", async () => {
|
||||
const cfg = {
|
||||
plugins: {
|
||||
entries: {},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const enabledCfg = createEnabledPluginConfig("demo");
|
||||
|
||||
loadConfig.mockReturnValue(cfg);
|
||||
parseClawHubPluginSpec.mockReturnValue({ name: "demo" });
|
||||
installPluginFromClawHub.mockResolvedValue(
|
||||
createClawHubInstallResult({
|
||||
pluginId: "demo",
|
||||
packageName: "demo",
|
||||
version: "1.2.3",
|
||||
channel: "official",
|
||||
}),
|
||||
);
|
||||
enablePluginInConfig.mockReturnValue({ config: enabledCfg });
|
||||
recordPluginInstall.mockReturnValue(enabledCfg);
|
||||
applyExclusiveSlotSelection.mockReturnValue({
|
||||
config: enabledCfg,
|
||||
warnings: [],
|
||||
});
|
||||
|
||||
await runPluginsCommand(["plugins", "install", "clawhub:demo", "--force"]);
|
||||
|
||||
expect(installPluginFromClawHub).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "clawhub:demo",
|
||||
mode: "update",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers ClawHub before npm for bare plugin specs", async () => {
|
||||
const cfg = {
|
||||
plugins: {
|
||||
@@ -417,6 +491,48 @@ describe("plugins cli install", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("passes force through as overwrite mode for npm installs", async () => {
|
||||
const cfg = {
|
||||
plugins: {
|
||||
entries: {},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const enabledCfg = createEnabledPluginConfig("demo");
|
||||
|
||||
loadConfig.mockReturnValue(cfg);
|
||||
installPluginFromClawHub.mockResolvedValue({
|
||||
ok: false,
|
||||
error: "ClawHub /api/v1/packages/demo failed (404): Package not found",
|
||||
code: "package_not_found",
|
||||
});
|
||||
installPluginFromNpmSpec.mockResolvedValue({
|
||||
ok: true,
|
||||
pluginId: "demo",
|
||||
targetDir: cliInstallPath("demo"),
|
||||
version: "1.2.3",
|
||||
npmResolution: {
|
||||
packageName: "demo",
|
||||
resolvedVersion: "1.2.3",
|
||||
tarballUrl: "https://registry.npmjs.org/demo/-/demo-1.2.3.tgz",
|
||||
},
|
||||
});
|
||||
enablePluginInConfig.mockReturnValue({ config: enabledCfg });
|
||||
recordPluginInstall.mockReturnValue(enabledCfg);
|
||||
applyExclusiveSlotSelection.mockReturnValue({
|
||||
config: enabledCfg,
|
||||
warnings: [],
|
||||
});
|
||||
|
||||
await runPluginsCommand(["plugins", "install", "demo", "--force"]);
|
||||
|
||||
expect(installPluginFromNpmSpec).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "demo",
|
||||
mode: "update",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not fall back to npm when ClawHub rejects a real package", async () => {
|
||||
installPluginFromClawHub.mockResolvedValue({
|
||||
ok: false,
|
||||
@@ -486,4 +602,53 @@ describe("plugins cli install", () => {
|
||||
expect(writeConfigFile).toHaveBeenCalledWith(installedCfg);
|
||||
expect(runtimeLogs.some((line) => line.includes("Installed hook pack: demo-hooks"))).toBe(true);
|
||||
});
|
||||
|
||||
it("passes force through as overwrite mode for hook-pack npm fallback installs", async () => {
|
||||
const cfg = {} as OpenClawConfig;
|
||||
const installedCfg = {
|
||||
hooks: {
|
||||
internal: {
|
||||
installs: {
|
||||
"demo-hooks": {
|
||||
source: "npm",
|
||||
spec: "@acme/demo-hooks@1.2.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
loadConfig.mockReturnValue(cfg);
|
||||
installPluginFromClawHub.mockResolvedValue({
|
||||
ok: false,
|
||||
error: "ClawHub /api/v1/packages/@acme/demo-hooks failed (404): Package not found",
|
||||
code: "package_not_found",
|
||||
});
|
||||
installPluginFromNpmSpec.mockResolvedValue({
|
||||
ok: false,
|
||||
error: "package.json missing openclaw.plugin.json",
|
||||
});
|
||||
installHooksFromNpmSpec.mockResolvedValue({
|
||||
ok: true,
|
||||
hookPackId: "demo-hooks",
|
||||
hooks: ["command-audit"],
|
||||
targetDir: "/tmp/hooks/demo-hooks",
|
||||
version: "1.2.3",
|
||||
npmResolution: {
|
||||
name: "@acme/demo-hooks",
|
||||
spec: "@acme/demo-hooks@1.2.3",
|
||||
integrity: "sha256-demo",
|
||||
},
|
||||
});
|
||||
recordHookInstall.mockReturnValue(installedCfg);
|
||||
|
||||
await runPluginsCommand(["plugins", "install", "@acme/demo-hooks", "--force"]);
|
||||
|
||||
expect(installHooksFromNpmSpec).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "@acme/demo-hooks",
|
||||
mode: "update",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user