fix(package-manager): use semver.gt for version comparison (#8239)

This commit is contained in:
Cristina Poncela Cubeiro 2026-08-17 12:44:38 +02:00 committed by GitHub
parent d3e3bbc011
commit 080932e53c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 35 additions and 3 deletions

View file

@ -4,6 +4,7 @@
### Fixed
- Fixed npm package update checks treating older registry versions as available updates, preventing `pi update` from downgrading already-newer installed packages ([#8226](https://github.com/earendil-works/pi/issues/8226)).
- Fixed built-in llama.cpp models disappearing from `/model` when `/llama` refreshed a configured server under `PI_OFFLINE`, and included idle-slept `sleeping` router models in the selectable catalog ([#8167](https://github.com/earendil-works/pi/issues/8167)).
- Fixed `pi.registerFlag()` accepting default values that do not match the declared flag type ([#8064](https://github.com/earendil-works/pi/issues/8064)).
- Fixed Z.AI Coding Plan defaults referencing the removed GLM-5.1 model ([#8096](https://github.com/earendil-works/pi/issues/8096)).

View file

@ -27,7 +27,7 @@ import type { Readable } from "node:stream";
import { globSync } from "glob";
import ignore from "ignore";
import { minimatch } from "minimatch";
import { maxSatisfying, rcompare, satisfies, valid, validRange } from "semver";
import { gt, maxSatisfying, rcompare, satisfies, valid, validRange } from "semver";
import { CONFIG_DIR_NAME } from "../config.ts";
import { spawnProcess, spawnProcessSync } from "../utils/child-process.ts";
import { type GitSource, parseGitUrl } from "../utils/git.ts";
@ -1129,7 +1129,7 @@ export class DefaultPackageManager implements PackageManager {
try {
const targetVersion = await this.getLatestNpmVersion(source.version ? source.spec : source.name, source.range);
return targetVersion !== installedVersion;
return gt(targetVersion, installedVersion);
} catch {
// Preserve existing update behavior when version lookup fails.
return true;
@ -1463,7 +1463,7 @@ export class DefaultPackageManager implements PackageManager {
try {
const targetVersion = await this.getLatestNpmVersion(source.version ? source.spec : source.name, source.range);
return targetVersion !== installedVersion;
return gt(targetVersion, installedVersion);
} catch {
return false;
}

View file

@ -2244,6 +2244,25 @@ export default function(api) { api.registerTool({ name: "test", description: "te
expect(runCommandSpy).not.toHaveBeenCalled();
});
it("should skip npm updates when the installed version is newer than the registry version", async () => {
const installedPath = join(tempDir, ".pi", "npm", "node_modules", "example");
mkdirSync(installedPath, { recursive: true });
writeFileSync(join(installedPath, "package.json"), JSON.stringify({ name: "example", version: "2.0.0" }));
settingsManager.setProjectPackages(["npm:example"]);
const runCommandCaptureSpy = vi.spyOn(packageManager as any, "runCommandCapture").mockResolvedValue('"1.9.0"');
const runCommandSpy = vi.spyOn(packageManager as any, "runCommand").mockResolvedValue(undefined);
await packageManager.update("npm:example");
expect(runCommandCaptureSpy).toHaveBeenCalledWith(
"npm",
["view", "example", "version", "--json"],
expect.objectContaining({ cwd: tempDir, timeoutMs: expect.any(Number) }),
);
expect(runCommandSpy).not.toHaveBeenCalled();
});
it("should migrate legacy user npm installs into the managed npm root during update", async () => {
const legacyRoot = join(tempDir, "legacy-global", "node_modules");
const legacyPath = join(legacyRoot, "legacy-pkg");
@ -2504,6 +2523,18 @@ export default function(api) { api.registerTool({ name: "test", description: "te
]);
});
it("should not report npm updates when the installed version is newer than the registry version", async () => {
const installedPath = join(tempDir, ".pi", "npm", "node_modules", "example");
mkdirSync(installedPath, { recursive: true });
writeFileSync(join(installedPath, "package.json"), JSON.stringify({ name: "example", version: "2.0.0" }));
settingsManager.setProjectPackages(["npm:example"]);
vi.spyOn(packageManager as any, "runCommandCapture").mockResolvedValue('"1.9.0"');
const updates = await packageManager.checkForAvailableUpdates();
expect(updates).toEqual([]);
});
it("should skip pinned packages when checking for updates", async () => {
const installedNpmPath = join(tempDir, ".pi", "npm", "node_modules", "example");
mkdirSync(installedNpmPath, { recursive: true });