mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-22 23:41:07 +00:00
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:
@@ -161,6 +161,129 @@ describe("updateNpmInstalledPlugins", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("reuses a recorded npm dist-tag spec for id-based updates", async () => {
|
||||
installPluginFromNpmSpecMock.mockResolvedValue({
|
||||
ok: true,
|
||||
pluginId: "openclaw-codex-app-server",
|
||||
targetDir: "/tmp/openclaw-codex-app-server",
|
||||
version: "0.2.0-beta.4",
|
||||
extensions: ["index.ts"],
|
||||
});
|
||||
|
||||
const result = await updateNpmInstalledPlugins({
|
||||
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",
|
||||
resolvedSpec: "openclaw-codex-app-server@0.2.0-beta.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pluginIds: ["openclaw-codex-app-server"],
|
||||
});
|
||||
|
||||
expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "openclaw-codex-app-server@beta",
|
||||
expectedPluginId: "openclaw-codex-app-server",
|
||||
}),
|
||||
);
|
||||
expect(result.config.plugins?.installs?.["openclaw-codex-app-server"]).toMatchObject({
|
||||
source: "npm",
|
||||
spec: "openclaw-codex-app-server@beta",
|
||||
installPath: "/tmp/openclaw-codex-app-server",
|
||||
version: "0.2.0-beta.4",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses and persists an explicit npm spec override during updates", async () => {
|
||||
installPluginFromNpmSpecMock.mockResolvedValue({
|
||||
ok: true,
|
||||
pluginId: "openclaw-codex-app-server",
|
||||
targetDir: "/tmp/openclaw-codex-app-server",
|
||||
version: "0.2.0-beta.4",
|
||||
extensions: ["index.ts"],
|
||||
npmResolution: {
|
||||
name: "openclaw-codex-app-server",
|
||||
version: "0.2.0-beta.4",
|
||||
resolvedSpec: "openclaw-codex-app-server@0.2.0-beta.4",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await updateNpmInstalledPlugins({
|
||||
config: {
|
||||
plugins: {
|
||||
installs: {
|
||||
"openclaw-codex-app-server": {
|
||||
source: "npm",
|
||||
spec: "openclaw-codex-app-server",
|
||||
installPath: "/tmp/openclaw-codex-app-server",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pluginIds: ["openclaw-codex-app-server"],
|
||||
specOverrides: {
|
||||
"openclaw-codex-app-server": "openclaw-codex-app-server@beta",
|
||||
},
|
||||
});
|
||||
|
||||
expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "openclaw-codex-app-server@beta",
|
||||
expectedPluginId: "openclaw-codex-app-server",
|
||||
}),
|
||||
);
|
||||
expect(result.config.plugins?.installs?.["openclaw-codex-app-server"]).toMatchObject({
|
||||
source: "npm",
|
||||
spec: "openclaw-codex-app-server@beta",
|
||||
installPath: "/tmp/openclaw-codex-app-server",
|
||||
version: "0.2.0-beta.4",
|
||||
resolvedSpec: "openclaw-codex-app-server@0.2.0-beta.4",
|
||||
});
|
||||
});
|
||||
|
||||
it("skips recorded integrity checks when an explicit npm version override changes the spec", async () => {
|
||||
installPluginFromNpmSpecMock.mockResolvedValue({
|
||||
ok: true,
|
||||
pluginId: "openclaw-codex-app-server",
|
||||
targetDir: "/tmp/openclaw-codex-app-server",
|
||||
version: "0.2.0-beta.4",
|
||||
extensions: ["index.ts"],
|
||||
});
|
||||
|
||||
await updateNpmInstalledPlugins({
|
||||
config: {
|
||||
plugins: {
|
||||
installs: {
|
||||
"openclaw-codex-app-server": {
|
||||
source: "npm",
|
||||
spec: "openclaw-codex-app-server@0.2.0-beta.3",
|
||||
integrity: "sha512-old",
|
||||
installPath: "/tmp/openclaw-codex-app-server",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pluginIds: ["openclaw-codex-app-server"],
|
||||
specOverrides: {
|
||||
"openclaw-codex-app-server": "openclaw-codex-app-server@0.2.0-beta.4",
|
||||
},
|
||||
});
|
||||
|
||||
expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "openclaw-codex-app-server@0.2.0-beta.4",
|
||||
expectedIntegrity: undefined,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("migrates legacy unscoped install keys when a scoped npm package updates", async () => {
|
||||
installPluginFromNpmSpecMock.mockResolvedValue({
|
||||
ok: true,
|
||||
|
||||
@@ -291,6 +291,7 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
pluginIds?: string[];
|
||||
skipIds?: Set<string>;
|
||||
dryRun?: boolean;
|
||||
specOverrides?: Record<string, string>;
|
||||
onIntegrityDrift?: (params: PluginUpdateIntegrityDriftParams) => boolean | Promise<boolean>;
|
||||
}): Promise<PluginUpdateSummary> {
|
||||
const logger = params.logger ?? {};
|
||||
@@ -329,7 +330,14 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (record.source === "npm" && !record.spec) {
|
||||
const effectiveSpec =
|
||||
record.source === "npm" ? (params.specOverrides?.[pluginId] ?? record.spec) : undefined;
|
||||
const expectedIntegrity =
|
||||
record.source === "npm" && effectiveSpec === record.spec
|
||||
? expectedIntegrityForUpdate(record.spec, record.integrity)
|
||||
: undefined;
|
||||
|
||||
if (record.source === "npm" && !effectiveSpec) {
|
||||
outcomes.push({
|
||||
pluginId,
|
||||
status: "skipped",
|
||||
@@ -371,11 +379,11 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
probe =
|
||||
record.source === "npm"
|
||||
? await installPluginFromNpmSpec({
|
||||
spec: record.spec!,
|
||||
spec: effectiveSpec!,
|
||||
mode: "update",
|
||||
dryRun: true,
|
||||
expectedPluginId: pluginId,
|
||||
expectedIntegrity: expectedIntegrityForUpdate(record.spec, record.integrity),
|
||||
expectedIntegrity,
|
||||
onIntegrityDrift: createPluginUpdateIntegrityDriftHandler({
|
||||
pluginId,
|
||||
dryRun: true,
|
||||
@@ -408,7 +416,7 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
record.source === "npm"
|
||||
? formatNpmInstallFailure({
|
||||
pluginId,
|
||||
spec: record.spec!,
|
||||
spec: effectiveSpec!,
|
||||
phase: "check",
|
||||
result: probe,
|
||||
})
|
||||
@@ -452,10 +460,10 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
result =
|
||||
record.source === "npm"
|
||||
? await installPluginFromNpmSpec({
|
||||
spec: record.spec!,
|
||||
spec: effectiveSpec!,
|
||||
mode: "update",
|
||||
expectedPluginId: pluginId,
|
||||
expectedIntegrity: expectedIntegrityForUpdate(record.spec, record.integrity),
|
||||
expectedIntegrity,
|
||||
onIntegrityDrift: createPluginUpdateIntegrityDriftHandler({
|
||||
pluginId,
|
||||
dryRun: false,
|
||||
@@ -487,7 +495,7 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
record.source === "npm"
|
||||
? formatNpmInstallFailure({
|
||||
pluginId,
|
||||
spec: record.spec!,
|
||||
spec: effectiveSpec!,
|
||||
phase: "update",
|
||||
result: result,
|
||||
})
|
||||
@@ -512,7 +520,7 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
next = recordPluginInstall(next, {
|
||||
pluginId: resolvedPluginId,
|
||||
source: "npm",
|
||||
spec: record.spec,
|
||||
spec: effectiveSpec,
|
||||
installPath: result.targetDir,
|
||||
version: nextVersion,
|
||||
...buildNpmResolutionInstallFields(result.npmResolution),
|
||||
|
||||
Reference in New Issue
Block a user