claude-dev-server 1.2.1 → 1.2.3
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/assets/dev.html +91 -14
- package/dist/chunk-EEXRIRSF.js +667 -0
- package/dist/chunk-EEXRIRSF.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-PAE5WTS2.js +0 -1869
- package/dist/chunk-PAE5WTS2.js.map +0 -1
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
|
+
import { dirname, join, relative, resolve } from 'path';
|
|
4
|
+
import http from 'http';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { createConnection } from 'net';
|
|
7
|
+
import { SourceMapConsumer } from 'source-map';
|
|
8
|
+
import { WebSocketServer } from 'ws';
|
|
9
|
+
import { readFile } from 'fs/promises';
|
|
10
|
+
|
|
11
|
+
// src/universal/index.ts
|
|
12
|
+
function spawnClaudeCode(options) {
|
|
13
|
+
const port = 5e4 + Math.floor(Math.random() * 1e4);
|
|
14
|
+
const ttydProc = spawn("ttyd", [
|
|
15
|
+
"--port",
|
|
16
|
+
String(port),
|
|
17
|
+
"--interface",
|
|
18
|
+
"127.0.0.1",
|
|
19
|
+
"--writable",
|
|
20
|
+
options.claudePath,
|
|
21
|
+
...options.args
|
|
22
|
+
], {
|
|
23
|
+
cwd: options.cwd,
|
|
24
|
+
env: {
|
|
25
|
+
...process.env,
|
|
26
|
+
...options.env,
|
|
27
|
+
TERM: "xterm-256color",
|
|
28
|
+
FORCE_COLOR: "1"
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
ttydProc.on("exit", (code) => {
|
|
32
|
+
console.log(`[claude-dev-server] ttyd exited - code: ${code}`);
|
|
33
|
+
});
|
|
34
|
+
ttydProc.on("error", (err) => {
|
|
35
|
+
console.error(`[claude-dev-server] ttyd error: ${err.message}`);
|
|
36
|
+
});
|
|
37
|
+
return new Promise((resolve2, reject) => {
|
|
38
|
+
ttydProc.stdout?.on("data", (data) => {
|
|
39
|
+
const msg = data.toString();
|
|
40
|
+
console.log(`[ttyd] ${msg}`);
|
|
41
|
+
});
|
|
42
|
+
ttydProc.stderr?.on("data", (data) => {
|
|
43
|
+
const msg = data.toString();
|
|
44
|
+
console.error(`[ttyd stderr] ${msg}`);
|
|
45
|
+
});
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
resolve2({
|
|
48
|
+
wsUrl: `ws://127.0.0.1:${port}`,
|
|
49
|
+
process: ttydProc,
|
|
50
|
+
port
|
|
51
|
+
});
|
|
52
|
+
}, 500);
|
|
53
|
+
ttydProc.on("error", reject);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
var sourceMapCache = /* @__PURE__ */ new Map();
|
|
57
|
+
async function parseSourceMap(sourceMapUrl, content) {
|
|
58
|
+
try {
|
|
59
|
+
const consumer = await new SourceMapConsumer(content);
|
|
60
|
+
sourceMapCache.set(sourceMapUrl, consumer);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error("Failed to parse source map:", err);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function findOriginalPosition(generatedFile, line, column) {
|
|
66
|
+
const consumer = sourceMapCache.get(generatedFile);
|
|
67
|
+
if (!consumer) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const position = consumer.originalPositionFor({ line, column });
|
|
72
|
+
if (position.source) {
|
|
73
|
+
return {
|
|
74
|
+
source: position.source,
|
|
75
|
+
line: position.line || 1,
|
|
76
|
+
column: position.column || 0,
|
|
77
|
+
name: position.name
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error("Failed to find original position:", err);
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
function formatCodeContext(location) {
|
|
86
|
+
return `
|
|
87
|
+
\u{1F4CD} Code Location:
|
|
88
|
+
File: ${location.file}
|
|
89
|
+
Line: ${location.line}
|
|
90
|
+
Column: ${location.column}
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
function createWebSocketServer(options) {
|
|
94
|
+
let wss = null;
|
|
95
|
+
let port = 0;
|
|
96
|
+
let ttydInfo = null;
|
|
97
|
+
const start = async () => {
|
|
98
|
+
console.log("[claude-dev-server] Starting ttyd...");
|
|
99
|
+
ttydInfo = await spawnClaudeCode({
|
|
100
|
+
cwd: options.projectRoot,
|
|
101
|
+
claudePath: options.claudePath,
|
|
102
|
+
args: options.claudeArgs
|
|
103
|
+
});
|
|
104
|
+
console.log(`[claude-dev-server] ttyd running at ${ttydInfo.wsUrl}`);
|
|
105
|
+
return new Promise((resolve2, reject) => {
|
|
106
|
+
wss = new WebSocketServer({ port, host: "127.0.0.1" });
|
|
107
|
+
wss.on("listening", () => {
|
|
108
|
+
const address = wss.address();
|
|
109
|
+
if (address && typeof address === "object") {
|
|
110
|
+
port = address.port;
|
|
111
|
+
resolve2({ wsPort: port, ttydPort: ttydInfo.port });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
wss.on("error", (err) => {
|
|
115
|
+
reject(err);
|
|
116
|
+
});
|
|
117
|
+
wss.on("connection", (ws) => {
|
|
118
|
+
ws.on("message", async (message) => {
|
|
119
|
+
try {
|
|
120
|
+
const msg = JSON.parse(message.toString());
|
|
121
|
+
if (msg.type === "inspect") {
|
|
122
|
+
await handleInspect(msg, ws, options.projectRoot);
|
|
123
|
+
} else if (msg.type === "loadSourceMap") {
|
|
124
|
+
await handleLoadSourceMap(msg, ws, options.projectRoot);
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
ws.send(JSON.stringify({
|
|
130
|
+
type: "ready",
|
|
131
|
+
ttydUrl: ttydInfo.wsUrl
|
|
132
|
+
}));
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
const stop = () => {
|
|
137
|
+
ttydInfo?.process.kill();
|
|
138
|
+
wss?.close();
|
|
139
|
+
};
|
|
140
|
+
return { start, stop };
|
|
141
|
+
}
|
|
142
|
+
async function handleLoadSourceMap(msg, ws, projectRoot) {
|
|
143
|
+
const { sourceMapUrl } = msg;
|
|
144
|
+
try {
|
|
145
|
+
const mapPath = resolve(projectRoot, sourceMapUrl.replace(/^\//, ""));
|
|
146
|
+
const content = await readFile(mapPath, "utf-8");
|
|
147
|
+
await parseSourceMap(sourceMapUrl, content);
|
|
148
|
+
ws.send(JSON.stringify({
|
|
149
|
+
type: "sourceMapLoaded",
|
|
150
|
+
sourceMapUrl,
|
|
151
|
+
success: true
|
|
152
|
+
}));
|
|
153
|
+
} catch (err) {
|
|
154
|
+
ws.send(JSON.stringify({
|
|
155
|
+
type: "sourceMapLoaded",
|
|
156
|
+
sourceMapUrl,
|
|
157
|
+
success: false,
|
|
158
|
+
error: err.message
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function handleInspect(msg, ws, projectRoot) {
|
|
163
|
+
const { url, line, column, sourceMapUrl } = msg;
|
|
164
|
+
let location = null;
|
|
165
|
+
if (sourceMapUrl) {
|
|
166
|
+
const original = await findOriginalPosition(sourceMapUrl, line, column);
|
|
167
|
+
if (original) {
|
|
168
|
+
location = {
|
|
169
|
+
file: resolve(projectRoot, original.source),
|
|
170
|
+
line: original.line,
|
|
171
|
+
column: original.column
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (!location) {
|
|
176
|
+
const match = url.match(/\/@fs\/(.+?)(?:\?|$)|\/@vite\/(.+?)(?:\?|$)/);
|
|
177
|
+
if (match) {
|
|
178
|
+
location = {
|
|
179
|
+
file: decodeURIComponent(match[1] || match[2]),
|
|
180
|
+
line,
|
|
181
|
+
column
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
ws.send(JSON.stringify({
|
|
186
|
+
type: "inspectResult",
|
|
187
|
+
location: location ? {
|
|
188
|
+
file: location.file,
|
|
189
|
+
line: location.line,
|
|
190
|
+
column: location.column,
|
|
191
|
+
context: formatCodeContext(location)
|
|
192
|
+
} : null
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/universal/index.ts
|
|
197
|
+
var __filename$1 = fileURLToPath(import.meta.url);
|
|
198
|
+
dirname(__filename$1);
|
|
199
|
+
async function startUniversalServer(options = {}) {
|
|
200
|
+
const cwd = options.cwd || process.cwd();
|
|
201
|
+
const port = options.port || 3e3;
|
|
202
|
+
const projectType = options.server?.type || detectProjectType(cwd);
|
|
203
|
+
const targetCommand = options.server?.command || getDefaultCommand(projectType);
|
|
204
|
+
console.log(`[Claude Dev Server] Detected project type: ${projectType}`);
|
|
205
|
+
console.log(`[Claude Dev Server] Starting target server...`);
|
|
206
|
+
const targetServer = spawnTargetServer(targetCommand, cwd);
|
|
207
|
+
console.log(`[Claude Dev Server] Waiting for target server to start (PID: ${targetServer.pid})...`);
|
|
208
|
+
const targetPort = await detectServerPort(targetServer, 3e4);
|
|
209
|
+
if (!targetPort) {
|
|
210
|
+
throw new Error("Failed to detect target server port. Please check if the dev server started successfully.");
|
|
211
|
+
}
|
|
212
|
+
console.log(`[Claude Dev Server] Target server is running on port ${targetPort}`);
|
|
213
|
+
const wsServer = createWebSocketServer({
|
|
214
|
+
port: 0,
|
|
215
|
+
// 自动分配端口
|
|
216
|
+
projectRoot: cwd,
|
|
217
|
+
claudePath: options.claudePath || "claude",
|
|
218
|
+
claudeArgs: options.claudeArgs || []
|
|
219
|
+
});
|
|
220
|
+
const { wsPort, ttydPort } = await wsServer.start();
|
|
221
|
+
console.log(`[Claude Dev Server] Control server running on ws://localhost:${wsPort}`);
|
|
222
|
+
console.log(`[Claude Dev Server] ttyd running on ws://localhost:${ttydPort}`);
|
|
223
|
+
const proxyServer = createProxyServer(targetPort, wsPort, cwd);
|
|
224
|
+
proxyServer.listen(port);
|
|
225
|
+
console.log(`[Claude Dev Server] Proxy server running on http://localhost:${port}`);
|
|
226
|
+
console.log(`
|
|
227
|
+
\u{1F680} Ready! Open http://localhost:${port} in your browser`);
|
|
228
|
+
const cleanup = () => {
|
|
229
|
+
console.log("[Claude Dev Server] Shutting down...");
|
|
230
|
+
targetServer.kill();
|
|
231
|
+
wsServer.stop();
|
|
232
|
+
proxyServer.close();
|
|
233
|
+
process.exit(0);
|
|
234
|
+
};
|
|
235
|
+
process.on("SIGINT", cleanup);
|
|
236
|
+
process.on("SIGTERM", cleanup);
|
|
237
|
+
return { proxyServer, targetServer, wsServer };
|
|
238
|
+
}
|
|
239
|
+
function detectProjectType(cwd) {
|
|
240
|
+
if (existsSync(join(cwd, "vite.config.ts")) || existsSync(join(cwd, "vite.config.js"))) {
|
|
241
|
+
return "vite";
|
|
242
|
+
}
|
|
243
|
+
if (existsSync(join(cwd, "next.config.js")) || existsSync(join(cwd, "next.config.mjs"))) {
|
|
244
|
+
return "next";
|
|
245
|
+
}
|
|
246
|
+
if (existsSync(join(cwd, "webpack.config.js")) || existsSync(join(cwd, "webpack.config.ts"))) {
|
|
247
|
+
return "webpack";
|
|
248
|
+
}
|
|
249
|
+
return "custom";
|
|
250
|
+
}
|
|
251
|
+
function getDefaultCommand(projectType) {
|
|
252
|
+
switch (projectType) {
|
|
253
|
+
case "vite":
|
|
254
|
+
return "npm run dev";
|
|
255
|
+
case "next":
|
|
256
|
+
return "npm run dev";
|
|
257
|
+
case "webpack":
|
|
258
|
+
return "npm run dev";
|
|
259
|
+
default:
|
|
260
|
+
return "npm run dev";
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function spawnTargetServer(command, cwd) {
|
|
264
|
+
const [cmd, ...args] = command.split(" ");
|
|
265
|
+
const child = spawn(cmd, args, {
|
|
266
|
+
cwd,
|
|
267
|
+
stdio: "pipe",
|
|
268
|
+
env: process.env
|
|
269
|
+
});
|
|
270
|
+
child.stdout?.pipe(process.stdout);
|
|
271
|
+
child.stderr?.pipe(process.stderr);
|
|
272
|
+
return child;
|
|
273
|
+
}
|
|
274
|
+
async function detectServerPort(childProcess, timeout) {
|
|
275
|
+
return new Promise((resolve2) => {
|
|
276
|
+
const timeoutId = setTimeout(() => {
|
|
277
|
+
cleanup();
|
|
278
|
+
resolve2(null);
|
|
279
|
+
}, timeout);
|
|
280
|
+
let stdout = "";
|
|
281
|
+
const onData = (chunk) => {
|
|
282
|
+
stdout += chunk.toString();
|
|
283
|
+
const patterns = [
|
|
284
|
+
/localhost:(\d{4,5})/,
|
|
285
|
+
/127\.0\.0\.1:(\d{4,5})/,
|
|
286
|
+
/0\.0\.0\.0:(\d{4,5})/
|
|
287
|
+
];
|
|
288
|
+
for (const pattern of patterns) {
|
|
289
|
+
const match = stdout.match(pattern);
|
|
290
|
+
if (match) {
|
|
291
|
+
const port = parseInt(match[1], 10);
|
|
292
|
+
console.log(`[Claude Dev Server] Detected port from stdout: ${port}`);
|
|
293
|
+
cleanup();
|
|
294
|
+
resolve2(port);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
const cleanup = () => {
|
|
300
|
+
clearTimeout(timeoutId);
|
|
301
|
+
childProcess.stdout?.off("data", onData);
|
|
302
|
+
};
|
|
303
|
+
childProcess.stdout?.on("data", onData);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
async function handleSourceMapRequest(projectRoot, filePath, line, column, targetPort) {
|
|
307
|
+
try {
|
|
308
|
+
let content;
|
|
309
|
+
let fullPath = filePath;
|
|
310
|
+
if (filePath.startsWith("/") && targetPort) {
|
|
311
|
+
try {
|
|
312
|
+
const response = await fetch(`http://localhost:${targetPort}${filePath}`);
|
|
313
|
+
if (!response.ok) {
|
|
314
|
+
console.log("[Claude Dev Server] Failed to fetch from Dev Server:", response.status);
|
|
315
|
+
return { error: "Failed to fetch from Dev Server" };
|
|
316
|
+
}
|
|
317
|
+
content = await response.text();
|
|
318
|
+
console.log("[Claude Dev Server] Fetched", content.length, "chars from Dev Server");
|
|
319
|
+
} catch (e) {
|
|
320
|
+
console.log("[Claude Dev Server] Fetch error:", e);
|
|
321
|
+
return { error: "Fetch error: " + e.message };
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
if (!filePath.startsWith("/")) {
|
|
325
|
+
fullPath = join(projectRoot, filePath);
|
|
326
|
+
}
|
|
327
|
+
if (!existsSync(fullPath)) {
|
|
328
|
+
console.log("[Claude Dev Server] File not found:", fullPath);
|
|
329
|
+
return { error: "File not found" };
|
|
330
|
+
}
|
|
331
|
+
content = readFileSync(fullPath, "utf-8");
|
|
332
|
+
console.log("[Claude Dev Server] Resolving source map for:", fullPath, "at line:", line);
|
|
333
|
+
}
|
|
334
|
+
let sourceMapUrl = null;
|
|
335
|
+
const patterns = [
|
|
336
|
+
/\/\/[@#]\s*sourceMappingURL=([^\s]+)/,
|
|
337
|
+
/\/\*[@#]\s*sourceMappingURL=([^\s]+)\s*\*\//
|
|
338
|
+
];
|
|
339
|
+
for (const pattern of patterns) {
|
|
340
|
+
const match = content.match(pattern);
|
|
341
|
+
if (match) {
|
|
342
|
+
sourceMapUrl = match[1];
|
|
343
|
+
console.log("[Claude Dev Server] Found sourceMappingURL:", sourceMapUrl.substring(0, 100) + "...");
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (!sourceMapUrl) {
|
|
348
|
+
console.log("[Claude Dev Server] No source map found in:", fullPath);
|
|
349
|
+
return { file: relative(projectRoot, fullPath), line, column };
|
|
350
|
+
}
|
|
351
|
+
let sourceMapContent;
|
|
352
|
+
let sourceMap;
|
|
353
|
+
if (sourceMapUrl.startsWith("data:application/json;base64,") || sourceMapUrl.startsWith("data:application/json;charset=utf-8;base64,")) {
|
|
354
|
+
console.log("[Claude Dev Server] Found inline source map");
|
|
355
|
+
const base64Data = sourceMapUrl.split(",", 2)[1];
|
|
356
|
+
sourceMapContent = Buffer.from(base64Data, "base64").toString("utf-8");
|
|
357
|
+
try {
|
|
358
|
+
sourceMap = JSON.parse(sourceMapContent);
|
|
359
|
+
} catch (e) {
|
|
360
|
+
console.log("[Claude Dev Server] Failed to parse inline source map:", e);
|
|
361
|
+
return { file: relative(projectRoot, fullPath), line, column };
|
|
362
|
+
}
|
|
363
|
+
} else if (sourceMapUrl.startsWith("http://") || sourceMapUrl.startsWith("https://")) {
|
|
364
|
+
console.log("[Claude Dev Server] Remote source map not supported:", sourceMapUrl);
|
|
365
|
+
return { file: relative(projectRoot, fullPath), line, column };
|
|
366
|
+
} else {
|
|
367
|
+
let sourceMapPath;
|
|
368
|
+
if (sourceMapUrl.startsWith("/")) {
|
|
369
|
+
sourceMapPath = sourceMapUrl;
|
|
370
|
+
} else {
|
|
371
|
+
sourceMapPath = join(dirname(fullPath), sourceMapUrl);
|
|
372
|
+
}
|
|
373
|
+
console.log("[Claude Dev Server] Reading external source map:", sourceMapPath);
|
|
374
|
+
if (!existsSync(sourceMapPath)) {
|
|
375
|
+
console.log("[Claude Dev Server] Source map file not found:", sourceMapPath);
|
|
376
|
+
return { file: relative(projectRoot, fullPath), line, column };
|
|
377
|
+
}
|
|
378
|
+
sourceMapContent = readFileSync(sourceMapPath, "utf-8");
|
|
379
|
+
sourceMap = JSON.parse(sourceMapContent);
|
|
380
|
+
}
|
|
381
|
+
const consumer = await new SourceMapConsumer(sourceMap);
|
|
382
|
+
const original = consumer.originalPositionFor({
|
|
383
|
+
line,
|
|
384
|
+
column
|
|
385
|
+
});
|
|
386
|
+
consumer.destroy();
|
|
387
|
+
if (original.source && original.line !== null) {
|
|
388
|
+
let originalFile = original.source;
|
|
389
|
+
if (originalFile.startsWith("webpack://")) {
|
|
390
|
+
originalFile = originalFile.replace(/^webpack:\/\/[\/\\]?/, "");
|
|
391
|
+
}
|
|
392
|
+
if (!originalFile.startsWith("/")) {
|
|
393
|
+
const possiblePath = join(projectRoot, originalFile);
|
|
394
|
+
if (existsSync(possiblePath)) {
|
|
395
|
+
} else {
|
|
396
|
+
const fileName = originalFile.split("/").pop() || originalFile.split("\\").pop() || originalFile;
|
|
397
|
+
if (fileName === originalFile) {
|
|
398
|
+
if (filePath.startsWith("/")) {
|
|
399
|
+
originalFile = filePath.substring(1);
|
|
400
|
+
console.log("[Claude Dev Server] Source is just a filename, using filePath:", originalFile);
|
|
401
|
+
} else {
|
|
402
|
+
originalFile = relative(projectRoot, fullPath);
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
originalFile = relative(projectRoot, possiblePath);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
file: originalFile,
|
|
411
|
+
line: original.line,
|
|
412
|
+
column: original.column || 1,
|
|
413
|
+
original: {
|
|
414
|
+
source: original.source,
|
|
415
|
+
line: original.line,
|
|
416
|
+
column: original.column,
|
|
417
|
+
name: original.name
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
return { file: relative(projectRoot, fullPath), line, column };
|
|
422
|
+
} catch (err) {
|
|
423
|
+
console.error("[Claude Dev Server] Source map resolution error:", err);
|
|
424
|
+
return { error: String(err) };
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function handleTurbopackLookup(projectRoot, pagePath, targetPort) {
|
|
428
|
+
try {
|
|
429
|
+
console.log("[Claude Dev Server] Turbopack lookup for page:", pagePath);
|
|
430
|
+
const pageRes = await fetch(`http://localhost:${targetPort}${pagePath}`);
|
|
431
|
+
if (!pageRes.ok) {
|
|
432
|
+
return { error: "Failed to fetch page" };
|
|
433
|
+
}
|
|
434
|
+
const html = await pageRes.text();
|
|
435
|
+
const chunkUrls = [];
|
|
436
|
+
const scriptMatches = html.matchAll(/<script[^>]*src="([^"]*\/_next\/static\/chunks\/[^"]*)"/g);
|
|
437
|
+
for (const match of scriptMatches) {
|
|
438
|
+
if (match[1]) {
|
|
439
|
+
chunkUrls.push(match[1]);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
console.log("[Claude Dev Server] Found", chunkUrls.length, "chunk URLs");
|
|
443
|
+
for (const chunkUrl of chunkUrls) {
|
|
444
|
+
try {
|
|
445
|
+
const fullUrl = chunkUrl.startsWith("http") ? chunkUrl : `http://localhost:${targetPort}${chunkUrl}`;
|
|
446
|
+
const chunkRes = await fetch(fullUrl);
|
|
447
|
+
if (!chunkRes.ok) continue;
|
|
448
|
+
const chunkContent = await chunkRes.text();
|
|
449
|
+
const modulePathRegex = /\[project\]([^\s"]+\.(tsx?|jsx?))/g;
|
|
450
|
+
const matches = [...chunkContent.matchAll(modulePathRegex)];
|
|
451
|
+
for (const match of matches) {
|
|
452
|
+
if (match[1]) {
|
|
453
|
+
const sourcePath = match[1];
|
|
454
|
+
let relativePath = sourcePath.replace(/^\[project\]/, "");
|
|
455
|
+
const normalizedPagePath = pagePath.replace(/^\/[^/]+/, "");
|
|
456
|
+
if (relativePath.toLowerCase().includes(normalizedPagePath.toLowerCase()) || relativePath.toLowerCase().includes("login")) {
|
|
457
|
+
console.log("[Claude Dev Server] Found source file:", relativePath);
|
|
458
|
+
return {
|
|
459
|
+
file: relativePath,
|
|
460
|
+
line: void 0
|
|
461
|
+
// Turbopack doesn't provide line numbers
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
} catch (e) {
|
|
467
|
+
console.log("[Claude Dev Server] Error fetching chunk:", chunkUrl, e);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return { error: "Source file not found for page: " + pagePath };
|
|
471
|
+
} catch (err) {
|
|
472
|
+
console.error("[Claude Dev Server] Turbopack lookup error:", err);
|
|
473
|
+
return { error: String(err) };
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function isHtmlPageRequest(req) {
|
|
477
|
+
const accept = req.headers.accept || "";
|
|
478
|
+
const url = req.url || "";
|
|
479
|
+
if (!accept.includes("text/html")) {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
if (url.startsWith("/@") || url.startsWith("/_next/") || url.startsWith("/ttyd") || url.startsWith("/dev.html")) {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
function createProxyServer(targetPort, wsPort, projectRoot) {
|
|
488
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
489
|
+
const assetsPath = join(moduleDir, "assets");
|
|
490
|
+
let ttydHtml;
|
|
491
|
+
let ttydBridgeJs;
|
|
492
|
+
let devHtml;
|
|
493
|
+
try {
|
|
494
|
+
ttydHtml = readFileSync(join(assetsPath, "ttyd-terminal.html"), "utf-8");
|
|
495
|
+
ttydBridgeJs = readFileSync(join(assetsPath, "ttyd-bridge.js"), "utf-8");
|
|
496
|
+
devHtml = readFileSync(join(assetsPath, "dev.html"), "utf-8");
|
|
497
|
+
} catch (e) {
|
|
498
|
+
console.error("[Claude Dev Server] Failed to read assets from", assetsPath);
|
|
499
|
+
console.error("[Claude Dev Server] moduleDir:", moduleDir);
|
|
500
|
+
console.error("[Claude Dev Server] Error:", e.message);
|
|
501
|
+
throw new Error("Assets not found. Please run `npm run build` first.");
|
|
502
|
+
}
|
|
503
|
+
const server = http.createServer((req, res) => {
|
|
504
|
+
const referer = req.headers.referer || "";
|
|
505
|
+
const isFromDevPage = referer.includes("dev.html");
|
|
506
|
+
if (req.url?.startsWith("/dev.html")) {
|
|
507
|
+
const urlParams = new URL(req.url || "", `http://${req.headers.host}`);
|
|
508
|
+
const originalPath = urlParams.searchParams.get("path") || "/";
|
|
509
|
+
const host = req.headers.host || "localhost:3000";
|
|
510
|
+
const origin = `http://${host}`;
|
|
511
|
+
const modifiedDevHtml = devHtml.replace(
|
|
512
|
+
/__CLAUDE_IFRAME_SRC__/g,
|
|
513
|
+
`${origin}${originalPath}`
|
|
514
|
+
).replace(
|
|
515
|
+
/__CLAUDE_ORIGINAL_PATH__/g,
|
|
516
|
+
originalPath
|
|
517
|
+
);
|
|
518
|
+
res.setHeader("Content-Type", "text/html");
|
|
519
|
+
res.end(modifiedDevHtml);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (!isFromDevPage && isHtmlPageRequest(req)) {
|
|
523
|
+
const currentPath = req.url || "/";
|
|
524
|
+
const devPageUrl = `/dev.html?path=${encodeURIComponent(currentPath)}`;
|
|
525
|
+
res.writeHead(302, { "Location": devPageUrl });
|
|
526
|
+
res.end();
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (req.url === "/@claude-port") {
|
|
530
|
+
res.setHeader("Content-Type", "application/json");
|
|
531
|
+
res.end(JSON.stringify({ port: wsPort }));
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
if (req.url?.startsWith("/@sourcemap?")) {
|
|
535
|
+
const url = new URL(req.url, `http://localhost:${wsPort}`);
|
|
536
|
+
const file = url.searchParams.get("file");
|
|
537
|
+
const line = url.searchParams.get("line");
|
|
538
|
+
const column = url.searchParams.get("col");
|
|
539
|
+
if (file && line && column) {
|
|
540
|
+
handleSourceMapRequest(projectRoot, file, parseInt(line), parseInt(column || "1"), targetPort).then((result) => {
|
|
541
|
+
res.setHeader("Content-Type", "application/json");
|
|
542
|
+
res.end(JSON.stringify(result));
|
|
543
|
+
}).catch((err) => {
|
|
544
|
+
console.error("[Claude Dev Server] Source map error:", err);
|
|
545
|
+
res.setHeader("Content-Type", "application/json");
|
|
546
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
547
|
+
});
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (req.url?.startsWith("/@sourcemap-lookup?")) {
|
|
552
|
+
const url = new URL(req.url, `http://localhost:${wsPort}`);
|
|
553
|
+
const page = url.searchParams.get("page");
|
|
554
|
+
const framework = url.searchParams.get("framework");
|
|
555
|
+
if (page && framework === "nextjs") {
|
|
556
|
+
handleTurbopackLookup(projectRoot, page, targetPort).then((result) => {
|
|
557
|
+
res.setHeader("Content-Type", "application/json");
|
|
558
|
+
res.end(JSON.stringify(result));
|
|
559
|
+
}).catch((err) => {
|
|
560
|
+
console.error("[Claude Dev Server] Turbopack lookup error:", err);
|
|
561
|
+
res.setHeader("Content-Type", "application/json");
|
|
562
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
563
|
+
});
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (req.url?.startsWith("/ttyd/")) {
|
|
568
|
+
const urlPath = req.url.split("?")[0];
|
|
569
|
+
if (urlPath === "/ttyd/index.html" || urlPath === "/ttyd/") {
|
|
570
|
+
res.setHeader("Content-Type", "text/html");
|
|
571
|
+
res.end(ttydHtml);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (urlPath === "/ttyd/ttyd-bridge.js") {
|
|
575
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
576
|
+
res.end(ttydBridgeJs);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (urlPath === "/ttyd/token" || urlPath === "/ttyd/index.html/token") {
|
|
580
|
+
res.setHeader("Content-Type", "application/json");
|
|
581
|
+
res.end(JSON.stringify({ token: "" }));
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
res.statusCode = 404;
|
|
585
|
+
res.end("Not found");
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const proxyHeaders = { ...req.headers };
|
|
589
|
+
delete proxyHeaders["accept-encoding"];
|
|
590
|
+
const options = {
|
|
591
|
+
hostname: "localhost",
|
|
592
|
+
port: targetPort,
|
|
593
|
+
path: req.url,
|
|
594
|
+
method: req.method,
|
|
595
|
+
headers: proxyHeaders
|
|
596
|
+
};
|
|
597
|
+
const proxyReq = http.request(options, (proxyRes) => {
|
|
598
|
+
const statusCode = proxyRes.statusCode || 200;
|
|
599
|
+
res.writeHead(statusCode, proxyRes.headers);
|
|
600
|
+
proxyRes.pipe(res);
|
|
601
|
+
});
|
|
602
|
+
proxyReq.on("error", (err) => {
|
|
603
|
+
console.error("[Claude Dev Server] Proxy error:", err);
|
|
604
|
+
res.statusCode = 502;
|
|
605
|
+
res.end("Bad Gateway");
|
|
606
|
+
});
|
|
607
|
+
req.pipe(proxyReq);
|
|
608
|
+
});
|
|
609
|
+
server.on("upgrade", (req, socket, head) => {
|
|
610
|
+
if (req.headers["upgrade"]?.toLowerCase() !== "websocket") {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
console.log("[Claude Dev Server] WebSocket upgrade request:", req.url);
|
|
614
|
+
const targetSocket = createConnection(targetPort, "localhost", () => {
|
|
615
|
+
console.log("[Claude Dev Server] Connected to target WebSocket server");
|
|
616
|
+
const upgradeRequest = [
|
|
617
|
+
`${req.method} ${req.url} HTTP/1.1`,
|
|
618
|
+
`Host: localhost:${targetPort}`,
|
|
619
|
+
"Upgrade: websocket",
|
|
620
|
+
"Connection: Upgrade",
|
|
621
|
+
`Sec-WebSocket-Key: ${req.headers["sec-websocket-key"]}`,
|
|
622
|
+
`Sec-WebSocket-Version: ${req.headers["sec-websocket-version"] || "13"}`
|
|
623
|
+
];
|
|
624
|
+
if (req.headers["sec-websocket-protocol"]) {
|
|
625
|
+
upgradeRequest.push(`Sec-WebSocket-Protocol: ${req.headers["sec-websocket-protocol"]}`);
|
|
626
|
+
}
|
|
627
|
+
if (req.headers["sec-websocket-extensions"]) {
|
|
628
|
+
upgradeRequest.push(`Sec-WebSocket-Extensions: ${req.headers["sec-websocket-extensions"]}`);
|
|
629
|
+
}
|
|
630
|
+
targetSocket.write(upgradeRequest.join("\r\n") + "\r\n\r\n");
|
|
631
|
+
if (head && head.length > 0) {
|
|
632
|
+
targetSocket.write(head);
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
targetSocket.on("data", (data) => {
|
|
636
|
+
if (socket.writable) {
|
|
637
|
+
socket.write(data);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
socket.on("data", (data) => {
|
|
641
|
+
if (targetSocket.writable) {
|
|
642
|
+
targetSocket.write(data);
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
socket.on("close", () => {
|
|
646
|
+
console.log("[Claude Dev Server] Client WebSocket closed");
|
|
647
|
+
targetSocket.destroy();
|
|
648
|
+
});
|
|
649
|
+
targetSocket.on("close", () => {
|
|
650
|
+
console.log("[Claude Dev Server] Target WebSocket closed");
|
|
651
|
+
socket.end();
|
|
652
|
+
});
|
|
653
|
+
socket.on("error", (err) => {
|
|
654
|
+
console.error("[Claude Dev Server] Client socket error:", err.message);
|
|
655
|
+
targetSocket.destroy();
|
|
656
|
+
});
|
|
657
|
+
targetSocket.on("error", (err) => {
|
|
658
|
+
console.error("[Claude Dev Server] Target socket error:", err.message);
|
|
659
|
+
socket.end();
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
return server;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export { startUniversalServer };
|
|
666
|
+
//# sourceMappingURL=chunk-EEXRIRSF.js.map
|
|
667
|
+
//# sourceMappingURL=chunk-EEXRIRSF.js.map
|