fix(cli): the latest version in _meta is never older than the running CLI (#7049)

* fix(cli): the latest version in _meta is never older than the running CLI

* fix(cli): keep the daily recheck cadence when the cached latest is older than the running CLI
This commit is contained in:
Miguel Ángel
2026-11-03 01:13:02 +00:00
committed by GitHub
parent eb3562966f
commit 8a6636efe3
2 changed files with 22 additions and 8 deletions
+14 -1
View File
@@ -403,6 +403,7 @@ describe("updateCheckDue / cachedUpdateCheck — what the parent reads without f
afterEach(() => {
vi.doUnmock("../telemetry/config.js");
vi.doUnmock("./env.js");
vi.doUnmock("../version.js");
vi.resetModules();
vi.unstubAllEnvs();
});
@@ -470,7 +471,19 @@ describe("updateCheckDue / cachedUpdateCheck — what the parent reads without f
},
);
it.each(["0.0.1", "999.0.0", "999.0.0-beta.1+build.5"])(
it("treats a cached latest older than the running CLI as unknown, without re-checking sooner than the daily cadence", async () => {
vi.doMock("../version.js", () => ({ VERSION: "0.9.6", PRODUCER_VERSION: "0.9.6" }));
const mod = await load({ lastUpdateCheck: hoursAgo(1), latestVersion: "0.9.5" });
expect(mod.updateCheckDue()).toBe(false);
expect(mod.getUpdateMeta()).toEqual({
version: "0.9.6",
latestVersion: undefined,
updateAvailable: false,
});
expect(mod.cachedUpdateCheck().latest).toBe("0.9.6");
});
it.each(["999.0.0", "999.0.0-beta.1+build.5"])(
"preserves safe cached version %s and the update-check result in metadata",
async (latestVersion) => {
const mod = await load({ latestVersion });
+8 -7
View File
@@ -96,9 +96,7 @@ export async function checkForUpdate(force?: boolean): Promise<UpdateCheckResult
/** Whether the background check should ask the registry: not opted out, and no fresh safe cache. */
export function updateCheckDue(config = readConfig()): boolean {
if (updateCheckDisabled()) return false;
if (!config.lastUpdateCheck || !config.latestVersion || !isSafeVersion(config.latestVersion)) {
return true;
}
if (!config.lastUpdateCheck || !isSafeCache(config.latestVersion)) return true;
const fresh = Date.now() - new Date(config.lastUpdateCheck).getTime() < CHECK_INTERVAL_MS;
return !fresh;
}
@@ -110,7 +108,7 @@ export function cachedUpdateCheck(): UpdateCheckResult {
function fallbackResult(cachedLatest?: string): UpdateCheckResult {
// Only surface a cached version we can prove is safe — a pre-existing
// poisoned cache must not leak through the fallback path either.
const safeCached = safeCachedVersion(cachedLatest);
const safeCached = cachedVersionForThisInstall(cachedLatest);
return {
current: VERSION,
latest: safeCached ?? VERSION,
@@ -118,8 +116,11 @@ function fallbackResult(cachedLatest?: string): UpdateCheckResult {
};
}
function safeCachedVersion(value: unknown): string | undefined {
return typeof value === "string" && isSafeVersion(value) ? value : undefined;
const isSafeCache = (value: unknown): value is string =>
typeof value === "string" && isSafeVersion(value);
function cachedVersionForThisInstall(value: unknown): string | undefined {
return isSafeCache(value) && !isNewerSemver(VERSION, value) ? value : undefined;
}
/**
@@ -127,7 +128,7 @@ function safeCachedVersion(value: unknown): string | undefined {
* Never fetches. Returns what the last background check found.
*/
export function getUpdateMeta(): UpdateMeta {
const latestVersion = safeCachedVersion(readConfig().latestVersion);
const latestVersion = cachedVersionForThisInstall(readConfig().latestVersion);
return {
version: VERSION,
latestVersion,