Plugins: avoid false integrity drift prompts on unpinned updates (#37179)

* Plugins: skip drift prompts for unpinned updates

* Plugins: cover unpinned integrity update behavior
This commit is contained in:
Vincent Koc
2026-03-05 23:43:35 -05:00
committed by GitHub
parent 91aed291dd
commit 428d1761b4
2 changed files with 94 additions and 2 deletions

View File

@@ -15,6 +15,76 @@ describe("updateNpmInstalledPlugins", () => {
installPluginFromNpmSpecMock.mockReset();
});
it("skips integrity drift checks for unpinned npm specs during dry-run updates", async () => {
installPluginFromNpmSpecMock.mockResolvedValue({
ok: true,
pluginId: "opik-openclaw",
targetDir: "/tmp/opik-openclaw",
version: "0.2.6",
extensions: ["index.ts"],
});
const { updateNpmInstalledPlugins } = await import("./update.js");
await updateNpmInstalledPlugins({
config: {
plugins: {
installs: {
"opik-openclaw": {
source: "npm",
spec: "@opik/opik-openclaw",
integrity: "sha512-old",
installPath: "/tmp/opik-openclaw",
},
},
},
},
pluginIds: ["opik-openclaw"],
dryRun: true,
});
expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
expect.objectContaining({
spec: "@opik/opik-openclaw",
expectedIntegrity: undefined,
}),
);
});
it("keeps integrity drift checks for exact-version npm specs during dry-run updates", async () => {
installPluginFromNpmSpecMock.mockResolvedValue({
ok: true,
pluginId: "opik-openclaw",
targetDir: "/tmp/opik-openclaw",
version: "0.2.6",
extensions: ["index.ts"],
});
const { updateNpmInstalledPlugins } = await import("./update.js");
await updateNpmInstalledPlugins({
config: {
plugins: {
installs: {
"opik-openclaw": {
source: "npm",
spec: "@opik/opik-openclaw@0.2.5",
integrity: "sha512-old",
installPath: "/tmp/opik-openclaw",
},
},
},
},
pluginIds: ["opik-openclaw"],
dryRun: true,
});
expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
expect.objectContaining({
spec: "@opik/opik-openclaw@0.2.5",
expectedIntegrity: "sha512-old",
}),
);
});
it("formats package-not-found updates with a stable message", async () => {
installPluginFromNpmSpecMock.mockResolvedValue({
ok: false,

View File

@@ -80,6 +80,28 @@ type InstallIntegrityDrift = {
};
};
function expectedIntegrityForUpdate(
spec: string | undefined,
integrity: string | undefined,
): string | undefined {
if (!integrity || !spec) {
return undefined;
}
const value = spec.trim();
if (!value) {
return undefined;
}
const at = value.lastIndexOf("@");
if (at <= 0 || at >= value.length - 1) {
return undefined;
}
const version = value.slice(at + 1).trim();
if (!/^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version)) {
return undefined;
}
return integrity;
}
async function readInstalledPackageVersion(dir: string): Promise<string | undefined> {
const manifestPath = path.join(dir, "package.json");
const opened = openBoundaryFileSync({
@@ -246,7 +268,7 @@ export async function updateNpmInstalledPlugins(params: {
mode: "update",
dryRun: true,
expectedPluginId: pluginId,
expectedIntegrity: record.integrity,
expectedIntegrity: expectedIntegrityForUpdate(record.spec, record.integrity),
onIntegrityDrift: createPluginUpdateIntegrityDriftHandler({
pluginId,
dryRun: true,
@@ -305,7 +327,7 @@ export async function updateNpmInstalledPlugins(params: {
spec: record.spec,
mode: "update",
expectedPluginId: pluginId,
expectedIntegrity: record.integrity,
expectedIntegrity: expectedIntegrityForUpdate(record.spec, record.integrity),
onIntegrityDrift: createPluginUpdateIntegrityDriftHandler({
pluginId,
dryRun: false,