mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-22 08:38:08 +02:00
feat(cli): add channel-aware update notifier (#265)
* feat(cli): show cached update notices after commands * docs(cli): describe update notices * fix(cli): type update check environment * fix(cli): decouple update notice display from refresh and harden suppression Display a cached "update available" notice based solely on the lastNoticeAt 24h throttle, independent of checkedAt refresh freshness, matching the design's independent display/refresh decisions. Suppress the check unconditionally under --json, CI, and non-TTY before consulting output-mode preferences, so a KTX_OUTPUT=pretty override can no longer make CI/non-TTY contexts phone npm.
This commit is contained in:
parent
377f21acd7
commit
698efdcef8
14 changed files with 1153 additions and 4 deletions
95
packages/cli/test/update-check/cache.test.ts
Normal file
95
packages/cli/test/update-check/cache.test.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
readUpdateCheckCache,
|
||||
updateCheckCachePath,
|
||||
writeUpdateCheckCache,
|
||||
} from '../../src/update-check/cache.js';
|
||||
|
||||
describe('update-check cache', () => {
|
||||
let homeDir: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
homeDir = await mkdtemp(join(tmpdir(), 'ktx-update-check-cache-'));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(homeDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('uses ~/.ktx/update-check.json', () => {
|
||||
expect(updateCheckCachePath(homeDir)).toBe(join(homeDir, '.ktx', 'update-check.json'));
|
||||
});
|
||||
|
||||
it('round-trips strict cache data', async () => {
|
||||
await writeUpdateCheckCache(
|
||||
{
|
||||
checkedAt: '2026-06-06T10:00:00.000Z',
|
||||
channel: 'latest',
|
||||
installedVersion: '0.9.0',
|
||||
latestForChannel: '0.10.0',
|
||||
lastNoticeAt: '2026-06-06T11:00:00.000Z',
|
||||
},
|
||||
{ homeDir },
|
||||
);
|
||||
|
||||
await expect(readUpdateCheckCache({ homeDir })).resolves.toEqual({
|
||||
checkedAt: '2026-06-06T10:00:00.000Z',
|
||||
channel: 'latest',
|
||||
installedVersion: '0.9.0',
|
||||
latestForChannel: '0.10.0',
|
||||
lastNoticeAt: '2026-06-06T11:00:00.000Z',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns null when the cache file is missing', async () => {
|
||||
await expect(readUpdateCheckCache({ homeDir })).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when the cache file is corrupt JSON', async () => {
|
||||
await mkdir(join(homeDir, '.ktx'), { recursive: true });
|
||||
await writeFile(updateCheckCachePath(homeDir), '{bad json', 'utf-8');
|
||||
|
||||
await expect(readUpdateCheckCache({ homeDir })).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when the cache has unknown fields', async () => {
|
||||
await mkdir(join(homeDir, '.ktx'), { recursive: true });
|
||||
await writeFile(
|
||||
updateCheckCachePath(homeDir),
|
||||
JSON.stringify(
|
||||
{
|
||||
checkedAt: '2026-06-06T10:00:00.000Z',
|
||||
channel: 'latest',
|
||||
installedVersion: '0.9.0',
|
||||
latestForChannel: '0.10.0',
|
||||
unexpected: true,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
await expect(readUpdateCheckCache({ homeDir })).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it('writes formatted JSON with a trailing newline', async () => {
|
||||
await writeUpdateCheckCache(
|
||||
{
|
||||
checkedAt: '2026-06-06T10:00:00.000Z',
|
||||
channel: 'next',
|
||||
installedVersion: '0.10.0-rc.1',
|
||||
latestForChannel: '0.10.0-rc.2',
|
||||
},
|
||||
{ homeDir },
|
||||
);
|
||||
|
||||
const raw = await readFile(updateCheckCachePath(homeDir), 'utf-8');
|
||||
expect(raw).toContain('"channel": "next"');
|
||||
expect(raw.endsWith('\n')).toBe(true);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue