Skip to content

Commit d6ca2ba

Browse files
committed
fix(cli): keep the update notice on the channel its cache was read from
The update-check cache records the dist-tag it fetched. A cached version from the other channel is not shown, and switching channels starts a fresh check.
1 parent b509254 commit d6ca2ba

2 files changed

Lines changed: 40 additions & 5 deletions

File tree

‎src/__tests__/update-check.test.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,24 @@ test('a nightly build checks and recommends the nightly dist-tag', async () => {
218218
/-> 0\.12\.0-nightly\.20261011\.8\. Run `npm install -g agent-device@nightly`/,
219219
);
220220
});
221+
222+
test('a cached version from the other channel is neither shown nor kept past a channel switch', () => {
223+
vi.mocked(runCmdDetached).mockClear();
224+
const stateDir = makeTempStateDir();
225+
cleanupPaths.push(stateDir);
226+
writeCache(stateDir, {
227+
latestVersion: '0.12.0-nightly.20261011.8',
228+
distTag: 'nightly',
229+
checkedAt: '2026-03-30T10:00:00.000Z',
230+
});
231+
let stderr = '';
232+
vi.spyOn(process.stderr, 'write').mockImplementation(((chunk: unknown) => {
233+
stderr += String(chunk);
234+
return true;
235+
}) as typeof process.stderr.write);
236+
237+
maybeRunUpgradeNotifier({ command: 'devices', currentVersion: '0.11.3', stateDir, flags: {} });
238+
239+
assert.equal(stderr, '');
240+
assert.equal(vi.mocked(runCmdDetached).mock.calls.length, 1, 'the switch starts a fresh check');
241+
});

‎src/cli/update-check.ts‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const UPDATE_CHECK_CACHE_FILE = 'update-check.json';
1212

1313
type UpdateCheckCache = {
1414
checkedAt?: string;
15+
/** The dist-tag `latestVersion` was read from; a cache from before channels means `latest`. */
16+
distTag?: string;
1517
latestVersion?: string;
1618
prompted?: boolean;
1719
};
@@ -50,7 +52,7 @@ export function maybeRunUpgradeNotifier(options: UpgradeNotifierOptions): void {
5052
});
5153
}
5254

53-
if (shouldStartBackgroundCheck(cache, now)) {
55+
if (shouldStartBackgroundCheck(cache, options.currentVersion, now)) {
5456
spawnBackgroundUpdateCheck(cachePath, options.currentVersion);
5557
}
5658
}
@@ -59,16 +61,17 @@ export async function runUpdateCheckWorker(options: UpdateCheckWorkerOptions): P
5961
const now = Date.now();
6062
const cache = readUpdateCheckCache(options.cachePath);
6163

64+
const distTag = distTagFor(options.currentVersion);
6265
try {
63-
const latestVersion =
64-
(await fetchLatestPackageVersion(distTagFor(options.currentVersion))) ?? undefined;
66+
const latestVersion = (await fetchLatestPackageVersion(distTag)) ?? undefined;
6567
if (!latestVersion || compareVersions(latestVersion, options.currentVersion) <= 0) {
66-
writeUpdateCheckCache(options.cachePath, { checkedAt: new Date(now).toISOString() });
68+
writeUpdateCheckCache(options.cachePath, { checkedAt: new Date(now).toISOString(), distTag });
6769
return;
6870
}
6971

7072
writeUpdateCheckCache(options.cachePath, {
7173
checkedAt: new Date(now).toISOString(),
74+
distTag,
7275
latestVersion,
7376
prompted: cache.latestVersion === latestVersion ? cache.prompted === true : false,
7477
});
@@ -92,11 +95,17 @@ function shouldEnableUpgradeNotifier(options: UpgradeNotifierOptions): boolean {
9295

9396
function shouldShowUpgradeNotice(cache: UpdateCheckCache, currentVersion: string): boolean {
9497
if (!cache.latestVersion) return false;
98+
if (cachedDistTag(cache) !== distTagFor(currentVersion)) return false;
9599
if (compareVersions(cache.latestVersion, currentVersion) <= 0) return false;
96100
return cache.prompted !== true;
97101
}
98102

99-
function shouldStartBackgroundCheck(cache: UpdateCheckCache, now: number): boolean {
103+
function shouldStartBackgroundCheck(
104+
cache: UpdateCheckCache,
105+
currentVersion: string,
106+
now: number,
107+
): boolean {
108+
if (cachedDistTag(cache) !== distTagFor(currentVersion)) return true;
100109
const checkedAt = parseTimestamp(cache.checkedAt);
101110
return checkedAt === undefined || now - checkedAt >= UPDATE_CHECK_INTERVAL_MS;
102111
}
@@ -106,6 +115,7 @@ function readUpdateCheckCache(cachePath: string): UpdateCheckCache {
106115
const raw = JSON.parse(fs.readFileSync(cachePath, 'utf8')) as UpdateCheckCache;
107116
return {
108117
checkedAt: typeof raw.checkedAt === 'string' ? raw.checkedAt : undefined,
118+
distTag: typeof raw.distTag === 'string' ? raw.distTag : undefined,
109119
latestVersion: typeof raw.latestVersion === 'string' ? raw.latestVersion : undefined,
110120
prompted: raw.prompted === true,
111121
};
@@ -151,6 +161,10 @@ function spawnBackgroundUpdateCheck(cachePath: string, currentVersion: string):
151161
]);
152162
}
153163

164+
function cachedDistTag(cache: UpdateCheckCache): string {
165+
return cache.distTag ?? 'latest';
166+
}
167+
154168
/** A nightly build follows the `nightly` dist-tag; every other build follows `latest`. */
155169
function distTagFor(version: string): 'latest' | 'nightly' {
156170
return version.includes('-nightly.') ? 'nightly' : 'latest';

0 commit comments

Comments
 (0)