CLI: support versioned plugin updates (#49998)

Merged via squash.

Prepared head SHA: 545ea60fa2
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
This commit is contained in:
Harold Hunt
2026-03-19 12:51:10 -04:00
committed by GitHub
parent 7fb142d115
commit 401ffb59f5
7 changed files with 345 additions and 12 deletions

View File

@@ -379,6 +379,140 @@ describe("plugins cli", () => {
expect(runtimeLogs.at(-1)).toBe("No tracked plugins to update.");
});
it("maps an explicit unscoped npm dist-tag update to the tracked plugin id", async () => {
const config = {
plugins: {
installs: {
"openclaw-codex-app-server": {
source: "npm",
spec: "openclaw-codex-app-server",
installPath: "/tmp/openclaw-codex-app-server",
resolvedName: "openclaw-codex-app-server",
},
},
},
} as OpenClawConfig;
loadConfig.mockReturnValue(config);
updateNpmInstalledPlugins.mockResolvedValue({
config,
changed: false,
outcomes: [],
});
await runCommand(["plugins", "update", "openclaw-codex-app-server@beta"]);
expect(updateNpmInstalledPlugins).toHaveBeenCalledWith(
expect.objectContaining({
config,
pluginIds: ["openclaw-codex-app-server"],
specOverrides: {
"openclaw-codex-app-server": "openclaw-codex-app-server@beta",
},
}),
);
});
it("maps an explicit scoped npm dist-tag update to the tracked plugin id", async () => {
const config = {
plugins: {
installs: {
"voice-call": {
source: "npm",
spec: "@openclaw/voice-call",
installPath: "/tmp/voice-call",
resolvedName: "@openclaw/voice-call",
},
},
},
} as OpenClawConfig;
loadConfig.mockReturnValue(config);
updateNpmInstalledPlugins.mockResolvedValue({
config,
changed: false,
outcomes: [],
});
await runCommand(["plugins", "update", "@openclaw/voice-call@beta"]);
expect(updateNpmInstalledPlugins).toHaveBeenCalledWith(
expect.objectContaining({
config,
pluginIds: ["voice-call"],
specOverrides: {
"voice-call": "@openclaw/voice-call@beta",
},
}),
);
});
it("maps an explicit npm version update to the tracked plugin id", async () => {
const config = {
plugins: {
installs: {
"openclaw-codex-app-server": {
source: "npm",
spec: "openclaw-codex-app-server",
installPath: "/tmp/openclaw-codex-app-server",
resolvedName: "openclaw-codex-app-server",
},
},
},
} as OpenClawConfig;
loadConfig.mockReturnValue(config);
updateNpmInstalledPlugins.mockResolvedValue({
config,
changed: false,
outcomes: [],
});
await runCommand(["plugins", "update", "openclaw-codex-app-server@0.2.0-beta.4"]);
expect(updateNpmInstalledPlugins).toHaveBeenCalledWith(
expect.objectContaining({
config,
pluginIds: ["openclaw-codex-app-server"],
specOverrides: {
"openclaw-codex-app-server": "openclaw-codex-app-server@0.2.0-beta.4",
},
}),
);
});
it("keeps using the recorded npm tag when update is invoked by plugin id", async () => {
const config = {
plugins: {
installs: {
"openclaw-codex-app-server": {
source: "npm",
spec: "openclaw-codex-app-server@beta",
installPath: "/tmp/openclaw-codex-app-server",
resolvedName: "openclaw-codex-app-server",
},
},
},
} as OpenClawConfig;
loadConfig.mockReturnValue(config);
updateNpmInstalledPlugins.mockResolvedValue({
config,
changed: false,
outcomes: [],
});
await runCommand(["plugins", "update", "openclaw-codex-app-server"]);
expect(updateNpmInstalledPlugins).toHaveBeenCalledWith(
expect.objectContaining({
config,
pluginIds: ["openclaw-codex-app-server"],
}),
);
expect(updateNpmInstalledPlugins).not.toHaveBeenCalledWith(
expect.objectContaining({
specOverrides: expect.anything(),
}),
);
});
it("writes updated config when updater reports changes", async () => {
const cfg = {
plugins: {