From 7d560e4ce18fa669f4a7ef7e2eb95e57dfe35c4a Mon Sep 17 00:00:00 2001 From: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Tue, 29 Sep 2026 18:27:06 +0000 Subject: [PATCH 1/2] fix(server): install the advertised version for pnpm-global provider updates pnpm 11 and later resolve @latest to the newest release older than minimumReleaseAge and exit 0, so the update left the provider outdated. Pin the update command to the version the advisory shows. --- .../providerMaintenanceRunner.test.ts | 36 +++++++++++++++++++ .../src/provider/providerMaintenanceRunner.ts | 3 +- .../src/server/maintenanceResolver.test.ts | 27 ++++++++++++++ .../src/server/maintenanceResolver.ts | 16 ++++++++- 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/apps/server/src/provider/providerMaintenanceRunner.test.ts b/apps/server/src/provider/providerMaintenanceRunner.test.ts index 3b820093542c..1cd1cde2ad6d 100644 --- a/apps/server/src/provider/providerMaintenanceRunner.test.ts +++ b/apps/server/src/provider/providerMaintenanceRunner.test.ts @@ -451,6 +451,42 @@ describe("providerMaintenanceRunner", () => { ); }); + it.effect("installs the advertised version when pnpm owns the global install", () => { + const calls: Array<{ command: string; args: ReadonlyArray }> = []; + return Effect.gen(function* () { + const { registry } = yield* makeRegistry(baseProvider); + const updater = yield* makeTestRunner({ + ...registry, + getProviderMaintenanceCapabilitiesForInstance: (_instanceId, provider) => + Effect.succeed( + makeProviderMaintenanceCapabilities({ + provider, + packageName: "@openai/codex", + updateExecutable: "pnpm", + updateArgs: ["add", "-g", "@openai/codex@latest"], + updateLockKey: "pnpm-global", + }), + ), + }); + + yield* updater.updateProvider(CODEX_DRIVER); + assert.deepStrictEqual(calls, [ + { command: "pnpm", args: ["add", "-g", "@openai/codex@9.9.9"] }, + ]); + }).pipe( + Effect.provide( + Layer.mergeAll( + layerNonWindowsPlatform, + layerLatestVersionHttpClient("9.9.9"), + layerMockSpawner((command, args) => { + calls.push({ command, args }); + return { stdout: "updated" }; + }), + ), + ), + ); + }); + it("splits installer output into clean status lines", () => { assert.deepStrictEqual(ProviderMaintenanceRunner.splitOutputLines("Down", "loading\r\n 5"), { lines: ["Downloading"], diff --git a/apps/server/src/provider/providerMaintenanceRunner.ts b/apps/server/src/provider/providerMaintenanceRunner.ts index 9354e14d760d..d5b0361b1eee 100644 --- a/apps/server/src/provider/providerMaintenanceRunner.ts +++ b/apps/server/src/provider/providerMaintenanceRunner.ts @@ -31,6 +31,7 @@ import { makeProviderMaintenanceCommandCoordinator } from "./providerMaintenance import { enrichProviderSnapshotWithVersionAdvisory, makeTargetedProviderUpdateAction, + resolveLatestProviderUpdateAction, resolveLatestProviderVersion, type ProviderMaintenanceCommandAction, } from "@t3tools/provider-core/server/maintenanceResolver"; @@ -478,7 +479,7 @@ export const make = Effect.fn("ProviderMaintenanceRunner.make")(function* () { const command = targetVersion !== undefined ? makeTargetedProviderUpdateAction(fresh, targetVersion) - : fresh.update; + : resolveLatestProviderUpdateAction(fresh, candidateVersion); const rejected = targetVersion !== undefined ? !command || diff --git a/packages/provider-core/src/server/maintenanceResolver.test.ts b/packages/provider-core/src/server/maintenanceResolver.test.ts index 23f0707a4a51..439144341dda 100644 --- a/packages/provider-core/src/server/maintenanceResolver.test.ts +++ b/packages/provider-core/src/server/maintenanceResolver.test.ts @@ -208,6 +208,33 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { }); }); + it("pins the advertised version in a pnpm-global update command", () => { + const pnpmTool = makeProviderMaintenanceCapabilities({ + provider: driver("packageTool"), + packageName: "@example/package-tool", + updateExecutable: "pnpm", + updateArgs: ["add", "-g", "@example/package-tool@latest"], + updateLockKey: "pnpm-global", + }); + const npmTool = makeProviderMaintenanceCapabilities({ + provider: driver("packageTool"), + packageName: "@example/package-tool", + updateExecutable: "npm", + updateArgs: ["install", "-g", "@example/package-tool@latest"], + updateLockKey: "npm-global:/opt/node", + }); + const advisoryFor = (maintenanceCapabilities: typeof pnpmTool) => + createProviderVersionAdvisory({ + driver: driver("packageTool"), + currentVersion: "1.0.0", + latestVersion: "1.1.0", + maintenanceCapabilities, + }); + + expect(advisoryFor(pnpmTool).updateCommand).toBe("pnpm add -g @example/package-tool@1.1.0"); + expect(advisoryFor(npmTool).updateCommand).toBe("npm install -g @example/package-tool@latest"); + }); + it("keeps the manual update hint when the install is behind but unowned", () => { expect( createProviderVersionAdvisory({ diff --git a/packages/provider-core/src/server/maintenanceResolver.ts b/packages/provider-core/src/server/maintenanceResolver.ts index fb985bd2011a..a37e489b080a 100644 --- a/packages/provider-core/src/server/maintenanceResolver.ts +++ b/packages/provider-core/src/server/maintenanceResolver.ts @@ -196,6 +196,20 @@ export function makeTargetedProviderUpdateAction( return { ...update, args, command }; } +/** + * The action that installs `latestVersion`. pnpm 11 and later resolve `@latest` + * to the newest release older than `minimumReleaseAge` and still exit 0, so a + * pnpm-global update installs the advertised version by exact spec instead. + */ +export function resolveLatestProviderUpdateAction( + capabilities: ProviderMaintenanceCapabilities, + latestVersion: string | null, +): ProviderMaintenanceCommandAction | null { + const update = capabilities.update; + if (update?.lockKey !== "pnpm-global" || latestVersion === null) return update; + return makeTargetedProviderUpdateAction(capabilities, latestVersion) ?? update; +} + export function makeManualOnlyProviderMaintenanceCapabilities(input: { readonly provider: ProviderDriverKind; readonly packageName: string | null; @@ -732,7 +746,7 @@ export function createProviderVersionAdvisory(input: { status: advisory.status, currentVersion: input.currentVersion, latestVersion, - updateCommand: capabilities.update?.command ?? null, + updateCommand: resolveLatestProviderUpdateAction(capabilities, latestVersion)?.command ?? null, canUpdate: capabilities.update !== null, canInstallVersion: makeTargetedProviderUpdateAction(capabilities, "0.0.0") !== null, checkedAt: input.checkedAt ?? null, From 4383ed8c2ebf632b74e065b42b8f05fc1d3f5a1a Mon Sep 17 00:00:00 2001 From: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:10:02 +0000 Subject: [PATCH 2/2] test(server): cover the @latest fallback for prerelease and unknown versions --- .../src/server/maintenanceResolver.test.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/provider-core/src/server/maintenanceResolver.test.ts b/packages/provider-core/src/server/maintenanceResolver.test.ts index 439144341dda..32a7191a5db0 100644 --- a/packages/provider-core/src/server/maintenanceResolver.test.ts +++ b/packages/provider-core/src/server/maintenanceResolver.test.ts @@ -223,16 +223,25 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { updateArgs: ["install", "-g", "@example/package-tool@latest"], updateLockKey: "npm-global:/opt/node", }); - const advisoryFor = (maintenanceCapabilities: typeof pnpmTool) => + const advisoryFor = ( + maintenanceCapabilities: typeof pnpmTool, + latestVersion: string | null = "1.1.0", + ) => createProviderVersionAdvisory({ driver: driver("packageTool"), currentVersion: "1.0.0", - latestVersion: "1.1.0", + latestVersion, maintenanceCapabilities, }); expect(advisoryFor(pnpmTool).updateCommand).toBe("pnpm add -g @example/package-tool@1.1.0"); expect(advisoryFor(npmTool).updateCommand).toBe("npm install -g @example/package-tool@latest"); + expect(advisoryFor(pnpmTool, "1.1.0-beta.1").updateCommand).toBe( + "pnpm add -g @example/package-tool@latest", + ); + expect(advisoryFor(pnpmTool, null).updateCommand).toBe( + "pnpm add -g @example/package-tool@latest", + ); }); it("keeps the manual update hint when the install is behind but unowned", () => {