Skip to content

Commit 428d176

Browse files
authored
Plugins: avoid false integrity drift prompts on unpinned updates (#37179)
* Plugins: skip drift prompts for unpinned updates * Plugins: cover unpinned integrity update behavior
1 parent 91aed29 commit 428d176

2 files changed

Lines changed: 94 additions & 2 deletions

File tree

src/plugins/update.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,76 @@ describe("updateNpmInstalledPlugins", () => {
1515
installPluginFromNpmSpecMock.mockReset();
1616
});
1717

18+
it("skips integrity drift checks for unpinned npm specs during dry-run updates", async () => {
19+
installPluginFromNpmSpecMock.mockResolvedValue({
20+
ok: true,
21+
pluginId: "opik-openclaw",
22+
targetDir: "/tmp/opik-openclaw",
23+
version: "0.2.6",
24+
extensions: ["index.ts"],
25+
});
26+
27+
const { updateNpmInstalledPlugins } = await import("./update.js");
28+
await updateNpmInstalledPlugins({
29+
config: {
30+
plugins: {
31+
installs: {
32+
"opik-openclaw": {
33+
source: "npm",
34+
spec: "@opik/opik-openclaw",
35+
integrity: "sha512-old",
36+
installPath: "/tmp/opik-openclaw",
37+
},
38+
},
39+
},
40+
},
41+
pluginIds: ["opik-openclaw"],
42+
dryRun: true,
43+
});
44+
45+
expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
46+
expect.objectContaining({
47+
spec: "@opik/opik-openclaw",
48+
expectedIntegrity: undefined,
49+
}),
50+
);
51+
});
52+
53+
it("keeps integrity drift checks for exact-version npm specs during dry-run updates", async () => {
54+
installPluginFromNpmSpecMock.mockResolvedValue({
55+
ok: true,
56+
pluginId: "opik-openclaw",
57+
targetDir: "/tmp/opik-openclaw",
58+
version: "0.2.6",
59+
extensions: ["index.ts"],
60+
});
61+
62+
const { updateNpmInstalledPlugins } = await import("./update.js");
63+
await updateNpmInstalledPlugins({
64+
config: {
65+
plugins: {
66+
installs: {
67+
"opik-openclaw": {
68+
source: "npm",
69+
spec: "@opik/[email protected]",
70+
integrity: "sha512-old",
71+
installPath: "/tmp/opik-openclaw",
72+
},
73+
},
74+
},
75+
},
76+
pluginIds: ["opik-openclaw"],
77+
dryRun: true,
78+
});
79+
80+
expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
81+
expect.objectContaining({
82+
spec: "@opik/[email protected]",
83+
expectedIntegrity: "sha512-old",
84+
}),
85+
);
86+
});
87+
1888
it("formats package-not-found updates with a stable message", async () => {
1989
installPluginFromNpmSpecMock.mockResolvedValue({
2090
ok: false,

src/plugins/update.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,28 @@ type InstallIntegrityDrift = {
8080
};
8181
};
8282

83+
function expectedIntegrityForUpdate(
84+
spec: string | undefined,
85+
integrity: string | undefined,
86+
): string | undefined {
87+
if (!integrity || !spec) {
88+
return undefined;
89+
}
90+
const value = spec.trim();
91+
if (!value) {
92+
return undefined;
93+
}
94+
const at = value.lastIndexOf("@");
95+
if (at <= 0 || at >= value.length - 1) {
96+
return undefined;
97+
}
98+
const version = value.slice(at + 1).trim();
99+
if (!/^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version)) {
100+
return undefined;
101+
}
102+
return integrity;
103+
}
104+
83105
async function readInstalledPackageVersion(dir: string): Promise<string | undefined> {
84106
const manifestPath = path.join(dir, "package.json");
85107
const opened = openBoundaryFileSync({
@@ -246,7 +268,7 @@ export async function updateNpmInstalledPlugins(params: {
246268
mode: "update",
247269
dryRun: true,
248270
expectedPluginId: pluginId,
249-
expectedIntegrity: record.integrity,
271+
expectedIntegrity: expectedIntegrityForUpdate(record.spec, record.integrity),
250272
onIntegrityDrift: createPluginUpdateIntegrityDriftHandler({
251273
pluginId,
252274
dryRun: true,
@@ -305,7 +327,7 @@ export async function updateNpmInstalledPlugins(params: {
305327
spec: record.spec,
306328
mode: "update",
307329
expectedPluginId: pluginId,
308-
expectedIntegrity: record.integrity,
330+
expectedIntegrity: expectedIntegrityForUpdate(record.spec, record.integrity),
309331
onIntegrityDrift: createPluginUpdateIntegrityDriftHandler({
310332
pluginId,
311333
dryRun: false,

0 commit comments

Comments
 (0)