mirror of
https://github.com/SrIzan10/hctv.git
synced 2026-06-06 00:56:56 +00:00
feat: preliminary chat api
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
"author": "Izan Gil <npm@srizan.dev>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
||||
"@typescript-eslint/parser": "^8.50.1",
|
||||
"eslint": "^9.39.2",
|
||||
|
||||
94
packages/sdk/src/chat.ts
Normal file
94
packages/sdk/src/chat.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
// most code here has been written by claude opus 4.5
|
||||
import type { ChatMessage, MessageHandler, SystemMessage, SystemMessageHandler } from './types';
|
||||
|
||||
export class ChatClient {
|
||||
private botToken: string;
|
||||
private ws: WebSocket | null = null;
|
||||
private messageHandlers: Set<MessageHandler> = new Set();
|
||||
private systemMessageHandlers: Set<SystemMessageHandler> = new Set();
|
||||
private channelName: string | null = null;
|
||||
|
||||
constructor(botToken: string) {
|
||||
this.botToken = botToken;
|
||||
}
|
||||
|
||||
connect(channelName: string): Promise<void> {
|
||||
if (this.isConnected) {
|
||||
return Promise.reject(new Error('Already connected. Disconnect first.'));
|
||||
}
|
||||
|
||||
this.channelName = channelName;
|
||||
const wsUrl = `${process.env.CHAT_WS_URL || 'ws://localhost:3001'}/ws`;
|
||||
this.ws = new WebSocket(wsUrl);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.ws!.onopen = () => {
|
||||
this.ws!.send(JSON.stringify({ type: 'auth', token: this.botToken, channelName }));
|
||||
this.emit('system', { type: 'connected', channelName, message: `Connected to ${channelName}`, timestamp: Date.now() });
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.ws!.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'message' && typeof data.message === 'string') {
|
||||
this.emit('message', {
|
||||
id: data.id || `${Date.now()}-${Math.random()}`,
|
||||
channelName: data.channelName || channelName,
|
||||
username: data.username || data.user?.username || 'Unknown',
|
||||
message: data.message,
|
||||
timestamp: data.timestamp || Date.now(),
|
||||
type: data.messageType || 'message',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.ws!.onerror = () => {
|
||||
this.emit('system', { type: 'error', channelName, message: 'WebSocket error', timestamp: Date.now() });
|
||||
reject(new Error('WebSocket error'));
|
||||
};
|
||||
|
||||
this.ws!.onclose = () => {
|
||||
this.emit('system', { type: 'disconnected', channelName, message: 'Disconnected', timestamp: Date.now() });
|
||||
this.ws = null;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this.ws?.close();
|
||||
this.ws = null;
|
||||
this.channelName = null;
|
||||
}
|
||||
|
||||
sendMessage(message: string): void {
|
||||
if (!this.isConnected) {
|
||||
throw new Error('Not connected to a channel');
|
||||
}
|
||||
this.ws!.send(JSON.stringify({ type: 'message', message, channelName: this.channelName }));
|
||||
}
|
||||
|
||||
onMessage(handler: MessageHandler): () => void {
|
||||
this.messageHandlers.add(handler);
|
||||
return () => this.messageHandlers.delete(handler);
|
||||
}
|
||||
|
||||
onSystemMessage(handler: SystemMessageHandler): () => void {
|
||||
this.systemMessageHandlers.add(handler);
|
||||
return () => this.systemMessageHandlers.delete(handler);
|
||||
}
|
||||
|
||||
private emit(type: 'message', data: ChatMessage): void;
|
||||
private emit(type: 'system', data: SystemMessage): void;
|
||||
private emit(type: 'message' | 'system', data: ChatMessage | SystemMessage): void {
|
||||
const handlers = type === 'message' ? this.messageHandlers : this.systemMessageHandlers;
|
||||
handlers.forEach((handler) => handler(data as any));
|
||||
}
|
||||
|
||||
get isConnected(): boolean {
|
||||
return this.ws?.readyState === WebSocket.OPEN;
|
||||
}
|
||||
|
||||
get currentChannel(): string | null {
|
||||
return this.channelName;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,18 @@
|
||||
import { ChatClient } from './chat.js';
|
||||
import type { ChatMessage, MessageHandler } from './types.js';
|
||||
|
||||
export class HctvSdk {
|
||||
private botToken: string
|
||||
private botToken: string;
|
||||
public chat: ChatClient;
|
||||
|
||||
constructor(args: ConstructorArgs) {
|
||||
this.botToken = args.botToken
|
||||
this.botToken = args.botToken;
|
||||
this.chat = new ChatClient(args.botToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
const client = new HctvSdk({ botToken: 'hctvb_asddfasdfasdfasdfasdf' });
|
||||
await client.chat.connect('channelName');
|
||||
client.chat.onMessage((message) => {
|
||||
// message would include data like the channelname etc
|
||||
console.log('New message:', message);
|
||||
});
|
||||
*/
|
||||
|
||||
interface ConstructorArgs {
|
||||
botToken: string;
|
||||
}
|
||||
export { ChatClient } from './chat.js';
|
||||
export type { ChatMessage, MessageHandler } from './types.js';
|
||||
|
||||
18
packages/sdk/src/types.ts
Normal file
18
packages/sdk/src/types.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export interface ChatMessage {
|
||||
id: string;
|
||||
channelName: string;
|
||||
username: string;
|
||||
message: string;
|
||||
timestamp: number;
|
||||
type: 'message' | 'systemMsg';
|
||||
}
|
||||
|
||||
export interface SystemMessage {
|
||||
type: 'connected' | 'disconnected' | 'error';
|
||||
channelName: string;
|
||||
message: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export type MessageHandler = (message: ChatMessage) => void;
|
||||
export type SystemMessageHandler = (message: SystemMessage) => void;
|
||||
793
packages/sdk/tests/chat.test.ts
Normal file
793
packages/sdk/tests/chat.test.ts
Normal file
@@ -0,0 +1,793 @@
|
||||
// testing completely controlled by claude opus 4.5 because i'm lazy as heck
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { HctvSdk, ChatClient } from '../src/index.js';
|
||||
import type { ChatMessage, SystemMessage } from '../src/types.js';
|
||||
|
||||
class MockWebSocket {
|
||||
static CONNECTING = 0;
|
||||
static OPEN = 1;
|
||||
static CLOSING = 2;
|
||||
static CLOSED = 3;
|
||||
|
||||
readyState = MockWebSocket.CONNECTING;
|
||||
onopen: (() => void) | null = null;
|
||||
onmessage: ((event: { data: string }) => void) | null = null;
|
||||
onerror: ((error: any) => void) | null = null;
|
||||
onclose: (() => void) | null = null;
|
||||
|
||||
sentMessages: string[] = [];
|
||||
url: string;
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
setTimeout(() => {
|
||||
this.readyState = MockWebSocket.OPEN;
|
||||
this.onopen?.();
|
||||
}, 10);
|
||||
}
|
||||
|
||||
send(data: string) {
|
||||
this.sentMessages.push(data);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.readyState = MockWebSocket.CLOSED;
|
||||
this.onclose?.();
|
||||
}
|
||||
|
||||
simulateMessage(data: any) {
|
||||
this.onmessage?.({ data: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
simulateError(error: any) {
|
||||
this.onerror?.(error);
|
||||
}
|
||||
}
|
||||
|
||||
let mockWebSocketInstance: MockWebSocket | null = null;
|
||||
|
||||
vi.stubGlobal('WebSocket', class extends MockWebSocket {
|
||||
constructor(url: string) {
|
||||
super(url);
|
||||
mockWebSocketInstance = this;
|
||||
}
|
||||
});
|
||||
|
||||
describe('HctvSdk', () => {
|
||||
beforeEach(() => {
|
||||
mockWebSocketInstance = null;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with bot token', () => {
|
||||
const sdk = new HctvSdk({ botToken: 'test-token' });
|
||||
expect(sdk).toBeDefined();
|
||||
expect(sdk.chat).toBeInstanceOf(ChatClient);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChatClient', () => {
|
||||
let client: ChatClient;
|
||||
|
||||
beforeEach(() => {
|
||||
mockWebSocketInstance = null;
|
||||
client = new ChatClient('test-bot-token');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
client.disconnect();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with bot token', () => {
|
||||
expect(client).toBeDefined();
|
||||
expect(client.isConnected).toBe(false);
|
||||
expect(client.currentChannel).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('connect', () => {
|
||||
it('should connect to a channel', async () => {
|
||||
const connectPromise = client.connect('testchannel');
|
||||
|
||||
await connectPromise;
|
||||
|
||||
expect(client.isConnected).toBe(true);
|
||||
expect(mockWebSocketInstance).not.toBeNull();
|
||||
expect(mockWebSocketInstance?.url).toContain('/ws');
|
||||
});
|
||||
|
||||
it('should send auth message on connect', async () => {
|
||||
await client.connect('testchannel');
|
||||
|
||||
expect(mockWebSocketInstance?.sentMessages.length).toBeGreaterThan(0);
|
||||
const authMsg = JSON.parse(mockWebSocketInstance!.sentMessages[0]);
|
||||
expect(authMsg.type).toBe('auth');
|
||||
expect(authMsg.token).toBe('test-bot-token');
|
||||
expect(authMsg.channelName).toBe('testchannel');
|
||||
});
|
||||
|
||||
it('should throw error when already connected', async () => {
|
||||
await client.connect('testchannel');
|
||||
|
||||
await expect(client.connect('anotherchannel')).rejects.toThrow(
|
||||
'Already connected to a channel'
|
||||
);
|
||||
});
|
||||
|
||||
it('should emit connected system message', async () => {
|
||||
const systemHandler = vi.fn();
|
||||
client.onSystemMessage(systemHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
expect(systemHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'connected',
|
||||
channelName: 'testchannel',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('disconnect', () => {
|
||||
it('should disconnect from channel', async () => {
|
||||
await client.connect('testchannel');
|
||||
expect(client.isConnected).toBe(true);
|
||||
|
||||
client.disconnect();
|
||||
|
||||
expect(client.isConnected).toBe(false);
|
||||
expect(client.currentChannel).toBeNull();
|
||||
});
|
||||
|
||||
it('should emit disconnected system message', async () => {
|
||||
const systemHandler = vi.fn();
|
||||
client.onSystemMessage(systemHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
client.disconnect();
|
||||
|
||||
expect(systemHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'disconnected',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendMessage', () => {
|
||||
it('should send a message when connected', async () => {
|
||||
await client.connect('testchannel');
|
||||
|
||||
client.sendMessage('Hello, world!');
|
||||
|
||||
const messages = mockWebSocketInstance!.sentMessages;
|
||||
const lastMsg = JSON.parse(messages[messages.length - 1]);
|
||||
expect(lastMsg.type).toBe('message');
|
||||
expect(lastMsg.message).toBe('Hello, world!');
|
||||
});
|
||||
|
||||
it('should throw error when not connected', () => {
|
||||
expect(() => client.sendMessage('test')).toThrow('Not connected to a channel');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onMessage', () => {
|
||||
it('should call handler when message received', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
message: 'Hello from server',
|
||||
user: {
|
||||
id: 'user-123',
|
||||
username: 'testuser',
|
||||
pfpUrl: 'https://example.com/pfp.jpg',
|
||||
},
|
||||
});
|
||||
|
||||
expect(messageHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Hello from server',
|
||||
username: 'testuser',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should return unsubscribe function', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
const unsubscribe = client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
unsubscribe();
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
message: 'Should not receive',
|
||||
user: { username: 'testuser' },
|
||||
});
|
||||
|
||||
expect(messageHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle multiple message handlers', async () => {
|
||||
const handler1 = vi.fn();
|
||||
const handler2 = vi.fn();
|
||||
|
||||
client.onMessage(handler1);
|
||||
client.onMessage(handler2);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
message: 'test message',
|
||||
user: { username: 'testuser' },
|
||||
});
|
||||
|
||||
expect(handler1).toHaveBeenCalled();
|
||||
expect(handler2).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSystemMessage', () => {
|
||||
it('should call handler for system events', async () => {
|
||||
const systemHandler = vi.fn();
|
||||
client.onSystemMessage(systemHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
expect(systemHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'connected',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should return unsubscribe function', async () => {
|
||||
const systemHandler = vi.fn();
|
||||
const unsubscribe = client.onSystemMessage(systemHandler);
|
||||
|
||||
unsubscribe();
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
expect(systemHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('message parsing', () => {
|
||||
it('should handle message with full user object', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
message: 'Hello',
|
||||
user: {
|
||||
id: 'user-123',
|
||||
username: 'johndoe',
|
||||
pfpUrl: 'https://example.com/pfp.jpg',
|
||||
displayName: 'John Doe',
|
||||
isBot: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(messageHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
username: 'johndoe',
|
||||
message: 'Hello',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle message with username directly', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
message: 'Direct username',
|
||||
username: 'directuser',
|
||||
});
|
||||
|
||||
expect(messageHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
username: 'directuser',
|
||||
message: 'Direct username',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should ignore invalid messages', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
});
|
||||
|
||||
expect(messageHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ignore non-message types', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'pong',
|
||||
});
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'history',
|
||||
messages: [],
|
||||
});
|
||||
|
||||
expect(messageHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('currentChannel', () => {
|
||||
it('should return null when not connected', () => {
|
||||
expect(client.currentChannel).toBeNull();
|
||||
});
|
||||
|
||||
it('should return channel name when connected', async () => {
|
||||
await client.connect('mychannel');
|
||||
expect(client.currentChannel).toBe('mychannel');
|
||||
});
|
||||
|
||||
it('should return null after disconnect', async () => {
|
||||
await client.connect('mychannel');
|
||||
client.disconnect();
|
||||
expect(client.currentChannel).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isConnected', () => {
|
||||
it('should return false initially', () => {
|
||||
expect(client.isConnected).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when connected', async () => {
|
||||
await client.connect('testchannel');
|
||||
expect(client.isConnected).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false after disconnect', async () => {
|
||||
await client.connect('testchannel');
|
||||
client.disconnect();
|
||||
expect(client.isConnected).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('should emit error system message on WebSocket error', async () => {
|
||||
const systemHandler = vi.fn();
|
||||
client.onSystemMessage(systemHandler);
|
||||
|
||||
const connectPromise = client.connect('testchannel');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 5));
|
||||
|
||||
mockWebSocketInstance?.simulateError(new Error('Connection failed'));
|
||||
|
||||
await expect(connectPromise).rejects.toBeDefined();
|
||||
|
||||
expect(systemHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'error',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle malformed message data gracefully', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
client.onMessage(messageHandler);
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.onmessage?.({ data: 'not valid json{' });
|
||||
|
||||
expect(messageHandler).not.toHaveBeenCalled();
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChatMessage type', () => {
|
||||
it('should have correct structure', () => {
|
||||
const message: ChatMessage = {
|
||||
id: 'msg-123',
|
||||
channelName: 'testchannel',
|
||||
username: 'testuser',
|
||||
message: 'Hello, world!',
|
||||
timestamp: Date.now(),
|
||||
type: 'message',
|
||||
};
|
||||
|
||||
expect(message.id).toBe('msg-123');
|
||||
expect(message.channelName).toBe('testchannel');
|
||||
expect(message.username).toBe('testuser');
|
||||
expect(message.message).toBe('Hello, world!');
|
||||
expect(typeof message.timestamp).toBe('number');
|
||||
expect(message.type).toBe('message');
|
||||
});
|
||||
|
||||
it('should support systemMsg type', () => {
|
||||
const message: ChatMessage = {
|
||||
id: 'sys-123',
|
||||
channelName: 'testchannel',
|
||||
username: 'system',
|
||||
message: 'User joined',
|
||||
timestamp: Date.now(),
|
||||
type: 'systemMsg',
|
||||
};
|
||||
|
||||
expect(message.type).toBe('systemMsg');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SystemMessage type', () => {
|
||||
it('should support connected type', () => {
|
||||
const message: SystemMessage = {
|
||||
type: 'connected',
|
||||
channelName: 'testchannel',
|
||||
message: 'Connected to testchannel',
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
expect(message.type).toBe('connected');
|
||||
});
|
||||
|
||||
it('should support disconnected type', () => {
|
||||
const message: SystemMessage = {
|
||||
type: 'disconnected',
|
||||
channelName: 'testchannel',
|
||||
message: 'Disconnected from testchannel',
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
expect(message.type).toBe('disconnected');
|
||||
});
|
||||
|
||||
it('should support error type', () => {
|
||||
const message: SystemMessage = {
|
||||
type: 'error',
|
||||
channelName: 'testchannel',
|
||||
message: 'An error occurred',
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
expect(message.type).toBe('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Integration with Chat Server Protocol', () => {
|
||||
let client: ChatClient;
|
||||
|
||||
beforeEach(() => {
|
||||
mockWebSocketInstance = null;
|
||||
client = new ChatClient('test-bot-token');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
client.disconnect();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('history messages', () => {
|
||||
it('should handle history message from server', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'history',
|
||||
messages: [
|
||||
{
|
||||
user: { id: 'u1', username: 'user1', pfpUrl: '' },
|
||||
message: 'First message',
|
||||
type: 'message',
|
||||
},
|
||||
{
|
||||
user: { id: 'u2', username: 'user2', pfpUrl: '' },
|
||||
message: 'Second message',
|
||||
type: 'message',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(messageHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ping/pong', () => {
|
||||
it('should handle pong response from server', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'pong',
|
||||
});
|
||||
|
||||
expect(messageHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('emoji responses', () => {
|
||||
it('should handle emojiMsgResponse from server', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'emojiMsgResponse',
|
||||
emojis: {
|
||||
smile: 'https://example.com/emoji/smile.png',
|
||||
laugh: 'https://example.com/emoji/laugh.png',
|
||||
},
|
||||
});
|
||||
|
||||
expect(messageHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle emojiSearchResponse from server', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'emojiSearchResponse',
|
||||
results: ['smile', 'smirk', 'smiley'],
|
||||
});
|
||||
|
||||
expect(messageHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('bot user messages', () => {
|
||||
it('should handle messages from bot users', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
message: 'Hello from bot!',
|
||||
user: {
|
||||
id: 'bot-123',
|
||||
username: 'mybot',
|
||||
pfpUrl: 'https://example.com/bot-avatar.png',
|
||||
displayName: 'My Bot',
|
||||
isBot: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(messageHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
username: 'mybot',
|
||||
message: 'Hello from bot!',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('message structure from server', () => {
|
||||
it('should handle message structure matching chat server format', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
user: {
|
||||
id: 'user-abc',
|
||||
username: 'streamuser',
|
||||
pfpUrl: 'https://example.com/pfp.jpg',
|
||||
displayName: 'Stream User',
|
||||
isBot: false,
|
||||
},
|
||||
message: 'Great stream!',
|
||||
});
|
||||
|
||||
expect(messageHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle message with explicit type', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
user: {
|
||||
id: 'user-abc',
|
||||
username: 'streamuser',
|
||||
pfpUrl: 'https://example.com/pfp.jpg',
|
||||
},
|
||||
message: 'Great stream!',
|
||||
});
|
||||
|
||||
expect(messageHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
username: 'streamuser',
|
||||
message: 'Great stream!',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
let client: ChatClient;
|
||||
|
||||
beforeEach(() => {
|
||||
mockWebSocketInstance = null;
|
||||
client = new ChatClient('test-bot-token');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
client.disconnect();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should handle empty message', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
message: '',
|
||||
user: { username: 'testuser' },
|
||||
});
|
||||
|
||||
expect(messageHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: '',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle message with special characters', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
const specialMessage = '🎉 Hello <script>alert("xss")</script> & "quotes" \'apostrophe\'';
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
message: specialMessage,
|
||||
user: { username: 'testuser' },
|
||||
});
|
||||
|
||||
expect(messageHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: specialMessage,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle very long messages', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
const longMessage = 'a'.repeat(10000);
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
message: longMessage,
|
||||
user: { username: 'testuser' },
|
||||
});
|
||||
|
||||
expect(messageHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: longMessage,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle unicode usernames', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
message: 'Hello',
|
||||
user: { username: '日本語ユーザー' },
|
||||
});
|
||||
|
||||
expect(messageHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
username: '日本語ユーザー',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle rapid successive messages', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
message: `Message ${i}`,
|
||||
user: { username: 'testuser' },
|
||||
});
|
||||
}
|
||||
|
||||
expect(messageHandler).toHaveBeenCalledTimes(100);
|
||||
});
|
||||
|
||||
it('should handle errors in message handlers gracefully', async () => {
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
const errorHandler = vi.fn(() => {
|
||||
throw new Error('Handler error');
|
||||
});
|
||||
const goodHandler = vi.fn();
|
||||
|
||||
client.onMessage(errorHandler);
|
||||
client.onMessage(goodHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
mockWebSocketInstance?.simulateMessage({
|
||||
type: 'message',
|
||||
message: 'Test',
|
||||
user: { username: 'testuser' },
|
||||
});
|
||||
|
||||
expect(goodHandler).toHaveBeenCalled();
|
||||
expect(consoleSpy).toHaveBeenCalled();
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle disconnect while message is being processed', async () => {
|
||||
const messageHandler = vi.fn();
|
||||
client.onMessage(messageHandler);
|
||||
|
||||
await client.connect('testchannel');
|
||||
|
||||
client.disconnect();
|
||||
|
||||
expect(() => client.sendMessage('test')).toThrow('Not connected');
|
||||
});
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
|
||||
describe('index', () => {
|
||||
it('should pass', () => {
|
||||
expect(1 + 1).toBe(2)
|
||||
})
|
||||
})
|
||||
@@ -49,13 +49,12 @@
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
"declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
@@ -105,5 +104,12 @@
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
110
pnpm-lock.yaml
generated
110
pnpm-lock.yaml
generated
@@ -56,16 +56,16 @@ importers:
|
||||
dependencies:
|
||||
'@astrojs/starlight':
|
||||
specifier: ^0.35.2
|
||||
version: 0.35.3(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))
|
||||
version: 0.35.3(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))
|
||||
'@catppuccin/starlight':
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2(@astrojs/starlight@0.35.3(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)))(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))
|
||||
version: 1.0.2(@astrojs/starlight@0.35.3(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)))(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))
|
||||
astro:
|
||||
specifier: ^5.6.1
|
||||
version: 5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)
|
||||
version: 5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)
|
||||
astro-mermaid:
|
||||
specifier: ^1.0.4
|
||||
version: 1.2.0(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))(mermaid@11.12.2)
|
||||
version: 1.2.0(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))(mermaid@11.12.2)
|
||||
mermaid:
|
||||
specifier: ^11.10.1
|
||||
version: 11.12.2
|
||||
@@ -364,6 +364,9 @@ importers:
|
||||
|
||||
packages/sdk:
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^25.1.0
|
||||
version: 25.1.0
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^8.50.1
|
||||
version: 8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
@@ -381,7 +384,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^4.0.16
|
||||
version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(jiti@2.6.1)(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)
|
||||
version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.1.0)(jiti@2.6.1)(msw@2.12.7(@types/node@25.1.0)(typescript@5.9.3))(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -3174,6 +3177,9 @@ packages:
|
||||
'@types/node@24.10.4':
|
||||
resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==}
|
||||
|
||||
'@types/node@25.1.0':
|
||||
resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==}
|
||||
|
||||
'@types/pg-pool@2.0.6':
|
||||
resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==}
|
||||
|
||||
@@ -8125,12 +8131,12 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@astrojs/mdx@4.3.13(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))':
|
||||
'@astrojs/mdx@4.3.13(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@astrojs/markdown-remark': 6.3.10
|
||||
'@mdx-js/mdx': 3.1.1
|
||||
acorn: 8.15.0
|
||||
astro: 5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)
|
||||
astro: 5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)
|
||||
es-module-lexer: 1.7.0
|
||||
estree-util-visit: 2.0.0
|
||||
hast-util-to-html: 9.0.5
|
||||
@@ -8154,17 +8160,17 @@ snapshots:
|
||||
stream-replace-string: 2.0.0
|
||||
zod: 3.25.76
|
||||
|
||||
'@astrojs/starlight@0.35.3(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))':
|
||||
'@astrojs/starlight@0.35.3(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@astrojs/markdown-remark': 6.3.10
|
||||
'@astrojs/mdx': 4.3.13(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))
|
||||
'@astrojs/mdx': 4.3.13(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))
|
||||
'@astrojs/sitemap': 3.6.0
|
||||
'@pagefind/default-ui': 1.4.0
|
||||
'@types/hast': 3.0.4
|
||||
'@types/js-yaml': 4.0.9
|
||||
'@types/mdast': 4.0.4
|
||||
astro: 5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)
|
||||
astro-expressive-code: 0.41.5(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))
|
||||
astro: 5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)
|
||||
astro-expressive-code: 0.41.5(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))
|
||||
bcp-47: 2.1.0
|
||||
hast-util-from-html: 2.0.3
|
||||
hast-util-select: 6.0.4
|
||||
@@ -8369,10 +8375,10 @@ snapshots:
|
||||
dependencies:
|
||||
fontkit: 2.0.4
|
||||
|
||||
'@catppuccin/starlight@1.0.2(@astrojs/starlight@0.35.3(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)))(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))':
|
||||
'@catppuccin/starlight@1.0.2(@astrojs/starlight@0.35.3(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)))(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@astrojs/starlight': 0.35.3(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))
|
||||
astro: 5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)
|
||||
'@astrojs/starlight': 0.35.3(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))
|
||||
astro: 5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)
|
||||
|
||||
'@chevrotain/cst-dts-gen@11.0.3':
|
||||
dependencies:
|
||||
@@ -9026,12 +9032,12 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.19.27
|
||||
|
||||
'@inquirer/confirm@5.1.21(@types/node@24.10.4)':
|
||||
'@inquirer/confirm@5.1.21(@types/node@25.1.0)':
|
||||
dependencies:
|
||||
'@inquirer/core': 10.3.2(@types/node@24.10.4)
|
||||
'@inquirer/type': 3.0.10(@types/node@24.10.4)
|
||||
'@inquirer/core': 10.3.2(@types/node@25.1.0)
|
||||
'@inquirer/type': 3.0.10(@types/node@25.1.0)
|
||||
optionalDependencies:
|
||||
'@types/node': 24.10.4
|
||||
'@types/node': 25.1.0
|
||||
optional: true
|
||||
|
||||
'@inquirer/core@10.3.2(@types/node@20.19.27)':
|
||||
@@ -9047,18 +9053,18 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.19.27
|
||||
|
||||
'@inquirer/core@10.3.2(@types/node@24.10.4)':
|
||||
'@inquirer/core@10.3.2(@types/node@25.1.0)':
|
||||
dependencies:
|
||||
'@inquirer/ansi': 1.0.2
|
||||
'@inquirer/figures': 1.0.15
|
||||
'@inquirer/type': 3.0.10(@types/node@24.10.4)
|
||||
'@inquirer/type': 3.0.10(@types/node@25.1.0)
|
||||
cli-width: 4.1.0
|
||||
mute-stream: 2.0.0
|
||||
signal-exit: 4.1.0
|
||||
wrap-ansi: 6.2.0
|
||||
yoctocolors-cjs: 2.1.3
|
||||
optionalDependencies:
|
||||
'@types/node': 24.10.4
|
||||
'@types/node': 25.1.0
|
||||
optional: true
|
||||
|
||||
'@inquirer/figures@1.0.15': {}
|
||||
@@ -9067,9 +9073,9 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 20.19.27
|
||||
|
||||
'@inquirer/type@3.0.10(@types/node@24.10.4)':
|
||||
'@inquirer/type@3.0.10(@types/node@25.1.0)':
|
||||
optionalDependencies:
|
||||
'@types/node': 24.10.4
|
||||
'@types/node': 25.1.0
|
||||
optional: true
|
||||
|
||||
'@internationalized/date@3.10.1':
|
||||
@@ -11273,6 +11279,10 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/node@25.1.0':
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/pg-pool@2.0.6':
|
||||
dependencies:
|
||||
'@types/pg': 8.15.6
|
||||
@@ -11578,14 +11588,14 @@ snapshots:
|
||||
chai: 6.2.2
|
||||
tinyrainbow: 3.0.3
|
||||
|
||||
'@vitest/mocker@4.0.16(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))':
|
||||
'@vitest/mocker@4.0.16(msw@2.12.7(@types/node@25.1.0)(typescript@5.9.3))(vite@7.3.0(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.0.16
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
msw: 2.12.7(@types/node@24.10.4)(typescript@5.9.3)
|
||||
vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)
|
||||
msw: 2.12.7(@types/node@25.1.0)(typescript@5.9.3)
|
||||
vite: 7.3.0(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)
|
||||
|
||||
'@vitest/pretty-format@4.0.16':
|
||||
dependencies:
|
||||
@@ -11961,21 +11971,21 @@ snapshots:
|
||||
|
||||
astring@1.9.0: {}
|
||||
|
||||
astro-expressive-code@0.41.5(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)):
|
||||
astro-expressive-code@0.41.5(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)):
|
||||
dependencies:
|
||||
astro: 5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)
|
||||
astro: 5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)
|
||||
rehype-expressive-code: 0.41.5
|
||||
|
||||
astro-mermaid@1.2.0(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))(mermaid@11.12.2):
|
||||
astro-mermaid@1.2.0(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))(mermaid@11.12.2):
|
||||
dependencies:
|
||||
'@anthropic-ai/claude-code': 1.0.128
|
||||
astro: 5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)
|
||||
astro: 5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)
|
||||
import-meta-resolve: 4.2.0
|
||||
mdast-util-to-string: 4.0.0
|
||||
mermaid: 11.12.2
|
||||
unist-util-visit: 5.0.0
|
||||
|
||||
astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0):
|
||||
astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.0
|
||||
'@astrojs/internal-helpers': 0.7.5
|
||||
@@ -12032,8 +12042,8 @@ snapshots:
|
||||
unist-util-visit: 5.0.0
|
||||
unstorage: 1.17.3(ioredis@5.8.2)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))
|
||||
vfile: 6.0.3
|
||||
vite: 6.4.1(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)
|
||||
vitefu: 1.1.1(vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))
|
||||
vite: 6.4.1(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)
|
||||
vitefu: 1.1.1(vite@6.4.1(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))
|
||||
xxhash-wasm: 1.1.0
|
||||
yargs-parser: 21.1.1
|
||||
yocto-spinner: 0.2.3
|
||||
@@ -13074,7 +13084,7 @@ snapshots:
|
||||
'@typescript-eslint/parser': 8.51.0(eslint@8.57.1)(typescript@5.9.3)
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
|
||||
eslint-plugin-react: 7.37.5(eslint@8.57.1)
|
||||
@@ -13094,7 +13104,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1):
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.3
|
||||
@@ -13109,14 +13119,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.51.0(eslint@8.57.1)(typescript@5.9.3)
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -13131,7 +13141,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@@ -15060,9 +15070,9 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
|
||||
msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3):
|
||||
msw@2.12.7(@types/node@25.1.0)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@inquirer/confirm': 5.1.21(@types/node@24.10.4)
|
||||
'@inquirer/confirm': 5.1.21(@types/node@25.1.0)
|
||||
'@mswjs/interceptors': 0.40.0
|
||||
'@open-draft/deferred-promise': 2.2.0
|
||||
'@types/statuses': 2.0.6
|
||||
@@ -16973,7 +16983,7 @@ snapshots:
|
||||
'@types/unist': 3.0.3
|
||||
vfile-message: 4.0.3
|
||||
|
||||
vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0):
|
||||
vite@6.4.1(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
@@ -16982,14 +16992,14 @@ snapshots:
|
||||
rollup: 4.54.0
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 24.10.4
|
||||
'@types/node': 25.1.0
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
terser: 5.44.1
|
||||
tsx: 4.21.0
|
||||
yaml: 2.8.0
|
||||
|
||||
vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0):
|
||||
vite@7.3.0(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0):
|
||||
dependencies:
|
||||
esbuild: 0.27.2
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
@@ -16998,21 +17008,21 @@ snapshots:
|
||||
rollup: 4.54.0
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 24.10.4
|
||||
'@types/node': 25.1.0
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
terser: 5.44.1
|
||||
tsx: 4.21.0
|
||||
yaml: 2.8.0
|
||||
|
||||
vitefu@1.1.1(vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)):
|
||||
vitefu@1.1.1(vite@6.4.1(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.1(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)
|
||||
vite: 6.4.1(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)
|
||||
|
||||
vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(jiti@2.6.1)(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0):
|
||||
vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.1.0)(jiti@2.6.1)(msw@2.12.7(@types/node@25.1.0)(typescript@5.9.3))(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.0.16
|
||||
'@vitest/mocker': 4.0.16(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))
|
||||
'@vitest/mocker': 4.0.16(msw@2.12.7(@types/node@25.1.0)(typescript@5.9.3))(vite@7.3.0(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))
|
||||
'@vitest/pretty-format': 4.0.16
|
||||
'@vitest/runner': 4.0.16
|
||||
'@vitest/snapshot': 4.0.16
|
||||
@@ -17029,11 +17039,11 @@ snapshots:
|
||||
tinyexec: 1.0.2
|
||||
tinyglobby: 0.2.15
|
||||
tinyrainbow: 3.0.3
|
||||
vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)
|
||||
vite: 7.3.0(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@types/node': 24.10.4
|
||||
'@types/node': 25.1.0
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- less
|
||||
|
||||
Reference in New Issue
Block a user