mirror of
https://github.com/Kaelio/ktx.git
synced 2026-07-07 11:02:11 +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
80
packages/cli/test/update-check/registry.test.ts
Normal file
80
packages/cli/test/update-check/registry.test.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { EventEmitter } from 'node:events';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const requestMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock('node:https', () => ({
|
||||
request: requestMock,
|
||||
}));
|
||||
|
||||
type MockResponse = EventEmitter & { statusCode?: number };
|
||||
type MockRequest = EventEmitter & {
|
||||
destroy: ReturnType<typeof vi.fn>;
|
||||
end: () => void;
|
||||
setTimeout: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
|
||||
function mockHttpsResponse(statusCode: number, body: string): { socket: { unref: ReturnType<typeof vi.fn> } } {
|
||||
const socket = { unref: vi.fn() };
|
||||
requestMock.mockImplementation((_url: unknown, _options: unknown, callback: (response: MockResponse) => void) => {
|
||||
const request = new EventEmitter() as MockRequest;
|
||||
request.destroy = vi.fn();
|
||||
request.setTimeout = vi.fn();
|
||||
request.end = () => {
|
||||
request.emit('socket', socket);
|
||||
const response = new EventEmitter() as MockResponse;
|
||||
response.statusCode = statusCode;
|
||||
callback(response);
|
||||
response.emit('data', Buffer.from(body));
|
||||
response.emit('end');
|
||||
};
|
||||
return request;
|
||||
});
|
||||
return { socket };
|
||||
}
|
||||
|
||||
describe('fetchDistTags', () => {
|
||||
beforeEach(() => {
|
||||
requestMock.mockReset();
|
||||
});
|
||||
|
||||
it('fetches @kaelio/ktx npm dist-tags and unrefs the socket', async () => {
|
||||
const { socket } = mockHttpsResponse(200, JSON.stringify({ latest: '0.10.0', next: '0.11.0-rc.1' }));
|
||||
const { fetchDistTags } = await import('../../src/update-check/registry.js');
|
||||
|
||||
await expect(fetchDistTags()).resolves.toEqual({ latest: '0.10.0', next: '0.11.0-rc.1' });
|
||||
|
||||
expect(requestMock).toHaveBeenCalledWith(
|
||||
expect.any(URL),
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
headers: expect.objectContaining({ accept: 'application/json' }),
|
||||
}),
|
||||
expect.any(Function),
|
||||
);
|
||||
const [url] = requestMock.mock.calls[0] as [URL];
|
||||
expect(url.toString()).toBe('https://registry.npmjs.org/-/package/@kaelio/ktx/dist-tags');
|
||||
expect(socket.unref).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('rejects non-2xx responses', async () => {
|
||||
mockHttpsResponse(503, 'registry unavailable');
|
||||
const { fetchDistTags } = await import('../../src/update-check/registry.js');
|
||||
|
||||
await expect(fetchDistTags()).rejects.toThrow('npm dist-tags request failed with 503');
|
||||
});
|
||||
|
||||
it('rejects invalid JSON payloads', async () => {
|
||||
mockHttpsResponse(200, '{bad json');
|
||||
const { fetchDistTags } = await import('../../src/update-check/registry.js');
|
||||
|
||||
await expect(fetchDistTags()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('rejects payloads that are not string dist-tag maps', async () => {
|
||||
mockHttpsResponse(200, JSON.stringify({ latest: 123 }));
|
||||
const { fetchDistTags } = await import('../../src/update-check/registry.js');
|
||||
|
||||
await expect(fetchDistTags()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue