gemini-cli-devtools 0.1.5 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{dist/client → client}/index.html +1 -1
- package/dist/client/main.js +88 -0
- package/dist/src/index.d.ts +46 -0
- package/dist/src/index.js +80 -166
- package/dist/src/types.d.ts +36 -0
- package/dist/src/types.js +6 -0
- package/package.json +15 -15
- package/dist/client/assets/index-CAfB090D.js +0 -55
- package/dist/src/bin.js +0 -22
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { EventEmitter } from 'node:events';
|
|
7
|
+
import { type WebSocket } from 'ws';
|
|
8
|
+
import type { NetworkLog, ConsoleLogPayload } from './types.js';
|
|
9
|
+
export type { NetworkLog, ConsoleLogPayload, InspectorConsoleLog, } from './types.js';
|
|
10
|
+
interface IncomingNetworkPayload extends Partial<NetworkLog> {
|
|
11
|
+
chunk?: {
|
|
12
|
+
index: number;
|
|
13
|
+
data: string;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface SessionInfo {
|
|
18
|
+
sessionId: string;
|
|
19
|
+
ws: WebSocket;
|
|
20
|
+
lastPing: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* DevTools Viewer
|
|
24
|
+
*
|
|
25
|
+
* Receives logs via WebSocket from CLI sessions.
|
|
26
|
+
*/
|
|
27
|
+
export declare class DevTools extends EventEmitter {
|
|
28
|
+
private static instance;
|
|
29
|
+
private logs;
|
|
30
|
+
private consoleLogs;
|
|
31
|
+
private server;
|
|
32
|
+
private wss;
|
|
33
|
+
private sessions;
|
|
34
|
+
private heartbeatTimer;
|
|
35
|
+
private port;
|
|
36
|
+
private constructor();
|
|
37
|
+
static getInstance(): DevTools;
|
|
38
|
+
addInternalConsoleLog(payload: ConsoleLogPayload, sessionId?: string, timestamp?: number): void;
|
|
39
|
+
addInternalNetworkLog(payload: IncomingNetworkPayload, sessionId?: string, timestamp?: number): void;
|
|
40
|
+
getUrl(): string;
|
|
41
|
+
getPort(): number;
|
|
42
|
+
stop(): Promise<void>;
|
|
43
|
+
start(): Promise<string>;
|
|
44
|
+
private setupWebSocketServer;
|
|
45
|
+
private handleWebSocketMessage;
|
|
46
|
+
}
|
package/dist/src/index.js
CHANGED
|
@@ -4,21 +4,15 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import http from 'node:http';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import fs from 'node:fs';
|
|
10
|
-
import path from 'node:path';
|
|
11
|
-
import os from 'node:os';
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
12
9
|
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { dirname, join } from 'node:path';
|
|
11
|
+
import { EventEmitter } from 'node:events';
|
|
13
12
|
import { WebSocketServer } from 'ws';
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const srcDir = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
// In dev: src/index.ts -> srcDir is "src"
|
|
19
|
-
// In prod: dist/src/index.js -> srcDir is "dist/src"
|
|
20
|
-
return srcDir.endsWith('/src') && !srcDir.includes('/dist/');
|
|
21
|
-
})();
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const indexHtml = readFileSync(join(__dirname, '../../client/index.html'), 'utf-8');
|
|
15
|
+
const mainJs = readFileSync(join(__dirname, '../client/main.js'), 'utf-8');
|
|
22
16
|
/**
|
|
23
17
|
* DevTools Viewer
|
|
24
18
|
*
|
|
@@ -31,6 +25,7 @@ export class DevTools extends EventEmitter {
|
|
|
31
25
|
server = null;
|
|
32
26
|
wss = null;
|
|
33
27
|
sessions = new Map();
|
|
28
|
+
heartbeatTimer = null;
|
|
34
29
|
port = 25417;
|
|
35
30
|
constructor() {
|
|
36
31
|
super();
|
|
@@ -41,22 +36,17 @@ export class DevTools extends EventEmitter {
|
|
|
41
36
|
}
|
|
42
37
|
return DevTools.instance;
|
|
43
38
|
}
|
|
44
|
-
getNetworkLogs() {
|
|
45
|
-
return this.logs;
|
|
46
|
-
}
|
|
47
|
-
getConsoleLogs() {
|
|
48
|
-
return this.consoleLogs;
|
|
49
|
-
}
|
|
50
39
|
addInternalConsoleLog(payload, sessionId, timestamp) {
|
|
51
|
-
|
|
40
|
+
const entry = {
|
|
52
41
|
...payload,
|
|
53
|
-
id:
|
|
42
|
+
id: randomUUID(),
|
|
54
43
|
sessionId,
|
|
55
44
|
timestamp: timestamp || Date.now(),
|
|
56
|
-
}
|
|
45
|
+
};
|
|
46
|
+
this.consoleLogs.push(entry);
|
|
57
47
|
if (this.consoleLogs.length > 5000)
|
|
58
48
|
this.consoleLogs.shift();
|
|
59
|
-
this.emit('console-update');
|
|
49
|
+
this.emit('console-update', entry);
|
|
60
50
|
}
|
|
61
51
|
addInternalNetworkLog(payload, sessionId, timestamp) {
|
|
62
52
|
if (!payload.id)
|
|
@@ -68,7 +58,6 @@ export class DevTools extends EventEmitter {
|
|
|
68
58
|
if (payload.chunk) {
|
|
69
59
|
const chunks = existing.chunks || [];
|
|
70
60
|
chunks.push(payload.chunk);
|
|
71
|
-
console.log(`[DevTools] Received chunk ${payload.chunk.index} for request ${payload.id}, total chunks: ${chunks.length}`);
|
|
72
61
|
this.logs[existingIndex] = {
|
|
73
62
|
...existing,
|
|
74
63
|
chunks,
|
|
@@ -86,31 +75,47 @@ export class DevTools extends EventEmitter {
|
|
|
86
75
|
: existing.response,
|
|
87
76
|
};
|
|
88
77
|
}
|
|
78
|
+
this.emit('update', this.logs[existingIndex]);
|
|
89
79
|
}
|
|
90
80
|
else if (payload.url) {
|
|
91
|
-
|
|
81
|
+
const entry = {
|
|
92
82
|
...payload,
|
|
93
83
|
sessionId,
|
|
94
84
|
timestamp: timestamp || Date.now(),
|
|
95
85
|
chunks: payload.chunk ? [payload.chunk] : undefined,
|
|
96
|
-
}
|
|
86
|
+
};
|
|
87
|
+
this.logs.push(entry);
|
|
97
88
|
if (this.logs.length > 2000)
|
|
98
89
|
this.logs.shift();
|
|
90
|
+
this.emit('update', entry);
|
|
99
91
|
}
|
|
100
|
-
this.emit('update');
|
|
101
92
|
}
|
|
102
93
|
getUrl() {
|
|
103
94
|
return `http://127.0.0.1:${this.port}`;
|
|
104
95
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
96
|
+
getPort() {
|
|
97
|
+
return this.port;
|
|
98
|
+
}
|
|
99
|
+
stop() {
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
if (this.heartbeatTimer) {
|
|
102
|
+
clearInterval(this.heartbeatTimer);
|
|
103
|
+
this.heartbeatTimer = null;
|
|
104
|
+
}
|
|
105
|
+
if (this.wss) {
|
|
106
|
+
this.wss.close();
|
|
107
|
+
this.wss = null;
|
|
108
|
+
}
|
|
109
|
+
if (this.server) {
|
|
110
|
+
this.server.close(() => resolve());
|
|
111
|
+
this.server = null;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
resolve();
|
|
115
|
+
}
|
|
116
|
+
// Reset singleton so a fresh start() is possible
|
|
117
|
+
DevTools.instance = undefined;
|
|
118
|
+
});
|
|
114
119
|
}
|
|
115
120
|
start() {
|
|
116
121
|
return new Promise((resolve) => {
|
|
@@ -118,100 +123,53 @@ export class DevTools extends EventEmitter {
|
|
|
118
123
|
resolve(this.getUrl());
|
|
119
124
|
return;
|
|
120
125
|
}
|
|
121
|
-
const clientPath = this.getClientPath();
|
|
122
126
|
this.server = http.createServer((req, res) => {
|
|
123
127
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
124
128
|
// API routes
|
|
125
|
-
if (req.url === '/
|
|
126
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
127
|
-
res.end(JSON.stringify(this.logs));
|
|
128
|
-
}
|
|
129
|
-
else if (req.url === '/console-logs') {
|
|
130
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
131
|
-
res.end(JSON.stringify(this.consoleLogs));
|
|
132
|
-
}
|
|
133
|
-
else if (req.url === '/sessions') {
|
|
134
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
135
|
-
const sessionIds = Array.from(this.sessions.keys());
|
|
136
|
-
res.end(JSON.stringify(sessionIds));
|
|
137
|
-
}
|
|
138
|
-
else if (req.url === '/events') {
|
|
129
|
+
if (req.url === '/events') {
|
|
139
130
|
res.writeHead(200, {
|
|
140
131
|
'Content-Type': 'text/event-stream',
|
|
141
132
|
'Cache-Control': 'no-cache',
|
|
142
133
|
Connection: 'keep-alive',
|
|
143
134
|
});
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
135
|
+
// Send full snapshot on connect
|
|
136
|
+
const snapshot = JSON.stringify({
|
|
137
|
+
networkLogs: this.logs,
|
|
138
|
+
consoleLogs: this.consoleLogs,
|
|
139
|
+
sessions: Array.from(this.sessions.keys()),
|
|
140
|
+
});
|
|
141
|
+
res.write(`event: snapshot\ndata: ${snapshot}\n\n`);
|
|
142
|
+
// Incremental updates
|
|
143
|
+
const onNetwork = (log) => {
|
|
144
|
+
res.write(`event: network\ndata: ${JSON.stringify(log)}\n\n`);
|
|
145
|
+
};
|
|
146
|
+
const onConsole = (log) => {
|
|
147
|
+
res.write(`event: console\ndata: ${JSON.stringify(log)}\n\n`);
|
|
148
|
+
};
|
|
149
|
+
const onSession = () => {
|
|
150
|
+
const sessions = Array.from(this.sessions.keys());
|
|
151
|
+
res.write(`event: session\ndata: ${JSON.stringify(sessions)}\n\n`);
|
|
152
|
+
};
|
|
153
|
+
this.on('update', onNetwork);
|
|
154
|
+
this.on('console-update', onConsole);
|
|
155
|
+
this.on('session-update', onSession);
|
|
150
156
|
req.on('close', () => {
|
|
151
|
-
this.off('update',
|
|
152
|
-
this.off('console-update',
|
|
153
|
-
this.off('session-update',
|
|
157
|
+
this.off('update', onNetwork);
|
|
158
|
+
this.off('console-update', onConsole);
|
|
159
|
+
this.off('session-update', onSession);
|
|
154
160
|
});
|
|
155
161
|
}
|
|
156
|
-
else if (req.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
req.on('end', () => {
|
|
160
|
-
try {
|
|
161
|
-
const entry = JSON.parse(body);
|
|
162
|
-
if (entry.type === 'console') {
|
|
163
|
-
this.addInternalConsoleLog(entry.payload, entry.sessionId, entry.timestamp);
|
|
164
|
-
}
|
|
165
|
-
else if (entry.type === 'network') {
|
|
166
|
-
this.addInternalNetworkLog(entry.payload, entry.sessionId, entry.timestamp);
|
|
167
|
-
}
|
|
168
|
-
res.writeHead(200);
|
|
169
|
-
res.end('OK');
|
|
170
|
-
}
|
|
171
|
-
catch {
|
|
172
|
-
res.writeHead(400);
|
|
173
|
-
res.end('Invalid JSON');
|
|
174
|
-
}
|
|
175
|
-
});
|
|
162
|
+
else if (req.url === '/' || req.url === '/index.html') {
|
|
163
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
164
|
+
res.end(indexHtml);
|
|
176
165
|
}
|
|
177
|
-
else if (req.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
req.on('end', () => {
|
|
181
|
-
const tmpIn = path.join(os.tmpdir(), `req-${Date.now()}.json`);
|
|
182
|
-
fs.writeFileSync(tmpIn, body);
|
|
183
|
-
const script = `
|
|
184
|
-
import sys
|
|
185
|
-
sys.path.insert(0, '/tmp/proto_gen')
|
|
186
|
-
from google.cloud.aiplatform.v1 import prediction_service_pb2
|
|
187
|
-
from google.protobuf.json_format import Parse
|
|
188
|
-
from google.protobuf import text_format
|
|
189
|
-
import json
|
|
190
|
-
|
|
191
|
-
with open(sys.argv[1]) as f:
|
|
192
|
-
msg = Parse(f.read(), prediction_service_pb2.GenerateContentRequest(), ignore_unknown_fields=True)
|
|
193
|
-
print(text_format.MessageToString(msg, as_utf8=True), end='')
|
|
194
|
-
`;
|
|
195
|
-
execFile('/tmp/proto_venv/bin/python3', ['-c', script, tmpIn], { maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
196
|
-
fs.unlinkSync(tmpIn);
|
|
197
|
-
if (err) {
|
|
198
|
-
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
199
|
-
res.end(stderr || err.message);
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
203
|
-
res.end(stdout);
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
else if (isDev) {
|
|
209
|
-
// Dev mode: proxy to Vite
|
|
210
|
-
this.proxyToVite(req, res);
|
|
166
|
+
else if (req.url === '/assets/main.js') {
|
|
167
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript' });
|
|
168
|
+
res.end(mainJs);
|
|
211
169
|
}
|
|
212
170
|
else {
|
|
213
|
-
|
|
214
|
-
|
|
171
|
+
res.writeHead(404);
|
|
172
|
+
res.end('Not Found');
|
|
215
173
|
}
|
|
216
174
|
});
|
|
217
175
|
this.server.on('error', (e) => {
|
|
@@ -246,7 +204,6 @@ print(text_format.MessageToString(msg, as_utf8=True), end='')
|
|
|
246
204
|
ws,
|
|
247
205
|
lastPing: Date.now(),
|
|
248
206
|
});
|
|
249
|
-
console.log(`📡 WebSocket registered: ${sessionId}`);
|
|
250
207
|
// Notify session update
|
|
251
208
|
this.emit('session-update');
|
|
252
209
|
// Send registration acknowledgement
|
|
@@ -260,28 +217,25 @@ print(text_format.MessageToString(msg, as_utf8=True), end='')
|
|
|
260
217
|
this.handleWebSocketMessage(sessionId, message);
|
|
261
218
|
}
|
|
262
219
|
}
|
|
263
|
-
catch
|
|
264
|
-
|
|
220
|
+
catch {
|
|
221
|
+
// Invalid WebSocket message
|
|
265
222
|
}
|
|
266
223
|
});
|
|
267
224
|
ws.on('close', () => {
|
|
268
225
|
if (sessionId) {
|
|
269
|
-
console.log(`📡 WebSocket disconnected: ${sessionId}`);
|
|
270
226
|
this.sessions.delete(sessionId);
|
|
271
|
-
this.emit('session-disconnected', sessionId);
|
|
272
227
|
this.emit('session-update');
|
|
273
228
|
}
|
|
274
229
|
});
|
|
275
|
-
ws.on('error', (
|
|
276
|
-
|
|
230
|
+
ws.on('error', () => {
|
|
231
|
+
// WebSocket error — no action needed
|
|
277
232
|
});
|
|
278
233
|
});
|
|
279
234
|
// Heartbeat mechanism
|
|
280
|
-
setInterval(() => {
|
|
235
|
+
this.heartbeatTimer = setInterval(() => {
|
|
281
236
|
const now = Date.now();
|
|
282
237
|
this.sessions.forEach((session, sessionId) => {
|
|
283
238
|
if (now - session.lastPing > 30000) {
|
|
284
|
-
console.log(`⚠️ Session ${sessionId} timeout, closing...`);
|
|
285
239
|
session.ws.close();
|
|
286
240
|
this.sessions.delete(sessionId);
|
|
287
241
|
}
|
|
@@ -307,47 +261,7 @@ print(text_format.MessageToString(msg, as_utf8=True), end='')
|
|
|
307
261
|
this.addInternalNetworkLog(message.payload, sessionId, message.timestamp);
|
|
308
262
|
break;
|
|
309
263
|
default:
|
|
310
|
-
|
|
264
|
+
break;
|
|
311
265
|
}
|
|
312
266
|
}
|
|
313
|
-
proxyToVite(req, res) {
|
|
314
|
-
const proxyReq = http.request({
|
|
315
|
-
hostname: '127.0.0.1',
|
|
316
|
-
port: VITE_DEV_PORT,
|
|
317
|
-
path: req.url,
|
|
318
|
-
method: req.method,
|
|
319
|
-
headers: req.headers,
|
|
320
|
-
}, (proxyRes) => {
|
|
321
|
-
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
|
322
|
-
proxyRes.pipe(res);
|
|
323
|
-
});
|
|
324
|
-
proxyReq.on('error', () => {
|
|
325
|
-
res.writeHead(502);
|
|
326
|
-
res.end('Vite dev server not ready');
|
|
327
|
-
});
|
|
328
|
-
req.pipe(proxyReq);
|
|
329
|
-
}
|
|
330
|
-
serveStatic(req, res, clientPath) {
|
|
331
|
-
const url = req.url === '/' ? '/index.html' : req.url || '/index.html';
|
|
332
|
-
const filePath = path.join(clientPath, url);
|
|
333
|
-
fs.readFile(filePath, (err, data) => {
|
|
334
|
-
if (err) {
|
|
335
|
-
res.writeHead(404);
|
|
336
|
-
res.end('Not Found');
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
const ext = path.extname(filePath);
|
|
340
|
-
const contentTypes = {
|
|
341
|
-
'.html': 'text/html',
|
|
342
|
-
'.js': 'application/javascript',
|
|
343
|
-
'.css': 'text/css',
|
|
344
|
-
'.svg': 'image/svg+xml',
|
|
345
|
-
'.json': 'application/json',
|
|
346
|
-
};
|
|
347
|
-
res.writeHead(200, {
|
|
348
|
-
'Content-Type': contentTypes[ext] || 'text/plain',
|
|
349
|
-
});
|
|
350
|
-
res.end(data);
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
267
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
export interface NetworkLog {
|
|
7
|
+
id: string;
|
|
8
|
+
sessionId?: string;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
method: string;
|
|
11
|
+
url: string;
|
|
12
|
+
headers: Record<string, string | string[] | undefined>;
|
|
13
|
+
body?: string;
|
|
14
|
+
pending?: boolean;
|
|
15
|
+
chunks?: Array<{
|
|
16
|
+
index: number;
|
|
17
|
+
data: string;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
}>;
|
|
20
|
+
response?: {
|
|
21
|
+
status: number;
|
|
22
|
+
headers: Record<string, string | string[] | undefined>;
|
|
23
|
+
body?: string;
|
|
24
|
+
durationMs: number;
|
|
25
|
+
};
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ConsoleLogPayload {
|
|
29
|
+
type: 'log' | 'warn' | 'error' | 'debug' | 'info';
|
|
30
|
+
content: string;
|
|
31
|
+
}
|
|
32
|
+
export interface InspectorConsoleLog extends ConsoleLogPayload {
|
|
33
|
+
id: string;
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
timestamp: number;
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gemini-cli-devtools",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
7
|
-
"
|
|
8
|
-
"
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/src/index.d.ts",
|
|
10
|
+
"default": "./dist/src/index.js"
|
|
11
|
+
}
|
|
9
12
|
},
|
|
10
13
|
"scripts": {
|
|
11
|
-
"
|
|
12
|
-
"build": "
|
|
13
|
-
"build:client": "vite build"
|
|
14
|
+
"build": "npm run build:client && tsc --outDir dist/src --declaration --module ESNext --target ESNext --moduleResolution bundler src/index.ts",
|
|
15
|
+
"build:client": "node esbuild.client.js"
|
|
14
16
|
},
|
|
15
17
|
"files": [
|
|
16
|
-
"dist"
|
|
18
|
+
"dist",
|
|
19
|
+
"client/index.html"
|
|
17
20
|
],
|
|
18
21
|
"engines": {
|
|
19
22
|
"node": ">=20"
|
|
20
23
|
},
|
|
21
24
|
"dependencies": {
|
|
22
|
-
"react": "^18.2.0",
|
|
23
|
-
"react-dom": "^18.2.0",
|
|
24
25
|
"ws": "^8.16.0"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
28
|
+
"@types/node": "^20.11.24",
|
|
27
29
|
"@types/react": "^18.2.0",
|
|
28
30
|
"@types/react-dom": "^18.2.0",
|
|
29
31
|
"@types/ws": "^8.5.10",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"concurrently": "^8.2.2",
|
|
35
|
-
"tsx": "^4.7.0"
|
|
32
|
+
"esbuild": "^0.24.0",
|
|
33
|
+
"react": "^18.2.0",
|
|
34
|
+
"react-dom": "^18.2.0",
|
|
35
|
+
"typescript": "^5.3.3"
|
|
36
36
|
}
|
|
37
37
|
}
|