flashts 1.0.7 → 1.0.8

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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to FlashTS will be documented in this file.
4
4
 
5
+ ## [1.0.8] - 2026-01-25
6
+
7
+ ### Fixed
8
+
9
+ - **Production Vite Reliability**: Optimized Vite execution and path resolution for production environments.
10
+ - **ENAMETOOLONG Issue**: Relocated the temporary runtime environment from the package directory to the system temp folder (`os.tmpdir()`). This resolves Windows path length limitations.
11
+ - **CLI Exit Responsiveness**: Optimized SIGINT/SIGTERM handling to ensure the CLI and all subprocesses close instantly.
12
+
5
13
  ## [1.0.7] - 2026-01-25
6
14
 
7
15
  ### Fixed
package/bin/cli.ts CHANGED
@@ -10,7 +10,7 @@ const program = new Command();
10
10
  program
11
11
  .name("flashts")
12
12
  .description("FlashTS: High-performance TypeScript Playground CLI")
13
- .version("1.0.7")
13
+ .version("1.0.8")
14
14
  .option("-p, --port <number>", "Port to run the server on", "3000")
15
15
  .option("-s, --share", "Generate a shareable public link")
16
16
  .option("--no-open", "Do not open the browser automatically")
@@ -45,7 +45,7 @@ program
45
45
 
46
46
  const packageRoot = join(import.meta.dir, "..");
47
47
 
48
- console.log(`\n⚡ ${pc.bold(pc.cyan("FlashTS"))} ${pc.dim("v1.0.7")}`);
48
+ console.log(`\n⚡ ${pc.bold(pc.cyan("FlashTS"))} ${pc.dim("v1.0.8")}`);
49
49
  console.log(`${pc.green("➜")} Local: ${pc.cyan(`http://localhost:${port}`)}`);
50
50
 
51
51
  // Start the server process
@@ -81,8 +81,10 @@ program
81
81
  }
82
82
 
83
83
  const handleExit = () => {
84
- console.log(pc.yellow("\nStopping FlashTS..."));
85
- serverProc.kill();
84
+ // Don't log anything else after this to keep terminal clean
85
+ try {
86
+ serverProc.kill();
87
+ } catch (e) {}
86
88
  process.exit(0);
87
89
  };
88
90
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flashts",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "High-performance TypeScript/JavaScript Playground powered by Bun",
5
5
  "main": "server/index.ts",
6
6
  "bin": {
package/server/index.ts CHANGED
@@ -4,6 +4,7 @@ import { join } from "path";
4
4
  import { mkdir, readdir, readFile, rm } from "fs/promises";
5
5
  import { serveStatic } from "hono/bun";
6
6
  import { streamText } from "hono/streaming";
7
+ import { tmpdir } from "os";
7
8
 
8
9
  const app = new Hono();
9
10
 
@@ -12,8 +13,8 @@ const PACKAGE_ROOT = join(import.meta.dir, "..");
12
13
  // Enable CORS for the frontend
13
14
  app.use("/*", cors());
14
15
 
15
- // Ensure tmp directory exists in the package root or system temp
16
- const TMP_DIR = join(PACKAGE_ROOT, "tmp");
16
+ // Ensure tmp directory exists in the system temp
17
+ const TMP_DIR = join(tmpdir(), "flashts-runtime");
17
18
  await mkdir(TMP_DIR, { recursive: true });
18
19
 
19
20
  // Global state management
@@ -38,19 +39,16 @@ try {
38
39
  private: true,
39
40
  dependencies: {}
40
41
  }, null, 2));
41
- console.log("-> Initialized new isolated sandbox environment");
42
42
  }
43
43
 
44
44
  // Resilient cleanup for a specific session directory
45
45
  const cleanupDir = async (dir: string) => {
46
46
  try {
47
47
  await rm(dir, { recursive: true, force: true });
48
- } catch (e) {
49
- // EBUSY is common on Windows if files are locked, ignore it
50
- }
48
+ } catch (e) {}
51
49
  };
52
50
 
53
- // Aggressive GC: attempt to delete all session directories that aren't node_modules
51
+ // Aggressive GC
54
52
  const performGC = async () => {
55
53
  try {
56
54
  const entries = await readdir(ISOLATED_ROOT, { withFileTypes: true });
@@ -63,20 +61,16 @@ const performGC = async () => {
63
61
  };
64
62
 
65
63
  const killActiveProcesses = () => {
66
- // Kill all vite processes
67
64
  for (const [id, proc] of vites) {
68
65
  try { proc.kill(); } catch (e) {}
69
66
  }
70
67
  vites.clear();
71
-
72
- // Kill active execution
73
68
  if (currentExecuteProc) {
74
69
  try { currentExecuteProc.kill(); } catch (e) {}
75
70
  currentExecuteProc = null;
76
71
  }
77
72
  };
78
73
 
79
- // GUARANTEED Cleanup on CLI exit
80
74
  const wipeTempSessions = async () => {
81
75
  try {
82
76
  killActiveProcesses();
@@ -84,7 +78,6 @@ const wipeTempSessions = async () => {
84
78
  } catch (e) {}
85
79
  };
86
80
 
87
- // Start with a clean slate
88
81
  await wipeTempSessions();
89
82
 
90
83
  process.on("SIGINT", () => {
@@ -96,7 +89,6 @@ process.on("SIGTERM", () => {
96
89
  process.exit(0);
97
90
  });
98
91
 
99
- // Serve static files from the client/dist directory relative to package root
100
92
  app.use("/*", serveStatic({ root: join(PACKAGE_ROOT, "client/dist") }));
101
93
 
102
94
  app.get("/api/health", (c) => c.text("FlashTS API Active"));
@@ -104,29 +96,22 @@ app.get("/api/health", (c) => c.text("FlashTS API Active"));
104
96
  app.post("/execute", async (c) => {
105
97
  const body = await c.req.json();
106
98
  const { files, entryPoint } = body;
99
+ if (!files || !entryPoint) return c.json({ error: "Invalid project state" }, 400);
107
100
 
108
- if (!files || !entryPoint) {
109
- return c.json({ error: "Invalid project state" }, 400);
110
- }
111
-
112
- // Kill any existing execution process
113
101
  if (currentExecuteProc) {
114
102
  try { currentExecuteProc.kill(); } catch (e) {}
115
103
  currentExecuteProc = null;
116
104
  }
117
105
 
118
- // Use a single static folder for all executions - efficient reuse
119
106
  const sessionDir = join(ISOLATED_ROOT, "execute-session");
120
107
  await mkdir(sessionDir, { recursive: true });
121
108
 
122
109
  for (const [name, content] of Object.entries(files)) {
123
- if (name.endsWith('/')) continue; // Skip directory markers
124
-
110
+ if (name.endsWith('/')) continue;
125
111
  const filePath = join(sessionDir, name as string);
126
112
  const parts = (name as string).split('/');
127
113
  if (parts.length > 1) {
128
- const dir = join(sessionDir, join(...parts.slice(0, -1)));
129
- await mkdir(dir, { recursive: true });
114
+ await mkdir(join(sessionDir, join(...parts.slice(0, -1))), { recursive: true });
130
115
  }
131
116
  await Bun.write(filePath, content as string);
132
117
  }
@@ -139,13 +124,9 @@ app.post("/execute", async (c) => {
139
124
  currentExecuteProc = proc;
140
125
 
141
126
  return streamText(c, async (stream) => {
142
- stream.onAbort(() => {
143
- proc.kill();
144
- });
145
-
127
+ stream.onAbort(() => { proc.kill(); });
146
128
  const stdoutReader = proc.stdout.getReader();
147
129
  const stderrReader = proc.stderr.getReader();
148
-
149
130
  const decoder = new TextDecoder();
150
131
 
151
132
  const read = async (reader: any, type: 'stdout' | 'stderr') => {
@@ -162,26 +143,16 @@ app.post("/execute", async (c) => {
162
143
  }
163
144
  };
164
145
 
165
- // Run both readers concurrently
166
- await Promise.all([
167
- read(stdoutReader, 'stdout'),
168
- read(stderrReader, 'stderr')
169
- ]);
170
-
146
+ await Promise.all([read(stdoutReader, 'stdout'), read(stderrReader, 'stderr')]);
171
147
  const exitCode = await proc.exited;
172
148
  if (currentExecuteProc === proc) currentExecuteProc = null;
173
-
174
- try {
175
- await stream.writeln(JSON.stringify({ type: 'exit', data: exitCode }));
176
- } catch (e) {}
149
+ try { await stream.writeln(JSON.stringify({ type: 'exit', data: exitCode })); } catch (e) {}
177
150
  });
178
151
  });
179
152
 
180
153
  app.post("/install", async (c) => {
181
154
  const { package: pkgName } = await c.req.json();
182
- if (!pkgName || typeof pkgName !== 'string') {
183
- return c.json({ error: "Invalid package name" }, 400);
184
- }
155
+ if (!pkgName || typeof pkgName !== 'string') return c.json({ error: "Invalid package name" }, 400);
185
156
 
186
157
  try {
187
158
  const proc = Bun.spawn([process.execPath, "add", pkgName], {
@@ -189,17 +160,12 @@ app.post("/install", async (c) => {
189
160
  stdout: "pipe",
190
161
  stderr: "pipe",
191
162
  });
192
-
193
163
  const rawOutput = await new Response(proc.stdout).text();
194
164
  const rawError = await new Response(proc.stderr).text();
195
165
  const output = rawOutput.replace(/\u001b\[[0-9;]*[a-zA-Z]/g, '');
196
166
  const error = rawError.replace(/\u001b\[[0-9;]*[a-zA-Z]/g, '');
197
167
  const exitCode = await proc.exited;
198
-
199
- if (exitCode !== 0) {
200
- return c.json({ success: false, output: error || output }, 500);
201
- }
202
-
168
+ if (exitCode !== 0) return c.json({ success: false, output: error || output }, 500);
203
169
  return c.json({ success: true, output });
204
170
  } catch (err: any) {
205
171
  return c.json({ success: false, output: err.message }, 500);
@@ -209,38 +175,25 @@ app.post("/install", async (c) => {
209
175
  app.get("/dependencies", async (c) => {
210
176
  try {
211
177
  const pkgData = JSON.parse(await readFile(ISOLATED_PKG_PATH, "utf-8"));
212
- return c.json({
213
- ...pkgData.dependencies,
214
- ...pkgData.devDependencies
215
- });
216
- } catch (e) {
217
- return c.json({});
218
- }
178
+ return c.json({ ...pkgData.dependencies, ...pkgData.devDependencies });
179
+ } catch (e) { return c.json({}); }
219
180
  });
220
181
 
221
182
  app.post("/uninstall", async (c) => {
222
183
  const { package: pkgName } = await c.req.json();
223
- if (!pkgName || typeof pkgName !== 'string') {
224
- return c.json({ error: "Invalid package name" }, 400);
225
- }
226
-
184
+ if (!pkgName || typeof pkgName !== 'string') return c.json({ error: "Invalid package name" }, 400);
227
185
  try {
228
186
  const proc = Bun.spawn([process.execPath, "remove", pkgName], {
229
187
  cwd: ISOLATED_ROOT,
230
188
  stdout: "pipe",
231
189
  stderr: "pipe",
232
190
  });
233
-
234
191
  const rawOutput = await new Response(proc.stdout).text();
235
192
  const rawError = await new Response(proc.stderr).text();
236
193
  const output = rawOutput.replace(/\u001b\[[0-9;]*[a-zA-Z]/g, '');
237
194
  const error = rawError.replace(/\u001b\[[0-9;]*[a-zA-Z]/g, '');
238
195
  const exitCode = await proc.exited;
239
-
240
- if (exitCode !== 0) {
241
- return c.json({ success: false, output: error || output }, 500);
242
- }
243
-
196
+ if (exitCode !== 0) return c.json({ success: false, output: error || output }, 500);
244
197
  return c.json({ success: true, output });
245
198
  } catch (err: any) {
246
199
  return c.json({ success: false, output: err.message }, 500);
@@ -249,89 +202,61 @@ app.post("/uninstall", async (c) => {
249
202
 
250
203
  async function getPackageTypeTree(pkgName: string) {
251
204
  let files: Record<string, string> = {};
252
-
253
205
  async function scan(dir: string, relativePath: string = "") {
254
206
  try {
255
207
  const entries = await readdir(dir, { withFileTypes: true });
256
208
  for (const entry of entries) {
257
209
  const res = join(dir, entry.name);
258
210
  const rel = relativePath ? `${relativePath}/${entry.name}` : entry.name;
259
-
260
211
  if (entry.isDirectory()) {
261
212
  if (entry.name === 'node_modules') continue;
262
213
  await scan(res, rel);
263
214
  } else if (entry.name.endsWith(".d.ts") || entry.name === "package.json") {
264
- try {
265
- files[rel] = await readFile(res, "utf-8");
266
- } catch (e) {}
215
+ try { files[rel] = await readFile(res, "utf-8"); } catch (e) {}
267
216
  }
268
217
  }
269
218
  } catch (e) {}
270
219
  }
271
-
272
- // 1. Try to find the package in ISOLATED_ROOT
273
220
  const isoPkgDir = join(ISOLATED_ROOT, "node_modules", pkgName);
274
221
  await scan(isoPkgDir);
275
-
276
- // 2. Try to find @types in ISOLATED_ROOT
277
- const typesPkgName = pkgName.startsWith('@')
278
- ? pkgName.slice(1).replace('/', '__')
279
- : pkgName;
222
+ const typesPkgName = pkgName.startsWith('@') ? pkgName.slice(1).replace('/', '__') : pkgName;
280
223
  const isoAtTypesDir = join(ISOLATED_ROOT, "node_modules", "@types", typesPkgName);
281
224
  await scan(isoAtTypesDir);
282
-
283
- // 3. Fallback to PACKAGE_ROOT (core types)
284
225
  if (Object.keys(files).length === 0) {
285
226
  const corePkgDir = join(PACKAGE_ROOT, "node_modules", pkgName);
286
227
  await scan(corePkgDir);
287
228
  const coreAtTypesDir = join(PACKAGE_ROOT, "node_modules", "@types", typesPkgName);
288
229
  await scan(coreAtTypesDir);
289
230
  }
290
-
291
231
  return Object.keys(files).length > 0 ? files : null;
292
232
  }
293
233
 
294
234
  app.get("/package-types/:pkg{.+$}", async (c) => {
295
235
  const pkgName = c.req.param("pkg");
296
236
  const tree = await getPackageTypeTree(pkgName);
297
-
298
- if (!tree) {
299
- return c.json({ error: "Types not found" }, 404);
300
- }
301
-
237
+ if (!tree) return c.json({ error: "Types not found" }, 404);
302
238
  return c.json({ tree, pkgName });
303
239
  });
304
240
 
305
241
  app.post("/preview/start", async (c) => {
306
242
  const body = await c.req.json();
307
243
  const { files, sessionId: incomingId } = body;
244
+ if (!files) return c.json({ error: "Invalid project state" }, 400);
308
245
 
309
- if (!files) {
310
- return c.json({ error: "Invalid project state" }, 400);
311
- }
312
-
313
- // Kill ALL existing vite processes to be safe
314
- for (const [id, proc] of vites) {
315
- try { proc.kill(); } catch (e) {}
316
- }
246
+ for (const [id, proc] of vites) { try { proc.kill(); } catch (e) {} }
317
247
  vites.clear();
318
248
 
319
- // GC old session folders (non-blocking)
320
249
  while (previewSessionQueue.length >= 5) {
321
250
  const oldId = previewSessionQueue.shift();
322
- if (oldId) {
323
- await cleanupDir(join(ISOLATED_ROOT, "preview-" + oldId));
324
- }
251
+ if (oldId) await cleanupDir(join(ISOLATED_ROOT, "preview-" + oldId));
325
252
  }
326
253
 
327
254
  const sessionId = incomingId || crypto.randomUUID();
328
255
  previewSessionQueue.push(sessionId);
329
-
330
256
  const sessionDir = join(ISOLATED_ROOT, "preview-" + sessionId);
331
- await cleanupDir(sessionDir); // Ensure clean slate
257
+ await cleanupDir(sessionDir);
332
258
  await mkdir(sessionDir, { recursive: true });
333
259
 
334
- // Write files
335
260
  for (const [name, content] of Object.entries(files)) {
336
261
  if (name.endsWith('/')) continue;
337
262
  const filePath = join(sessionDir, name as string);
@@ -342,7 +267,6 @@ app.post("/preview/start", async (c) => {
342
267
  await Bun.write(filePath, content as string);
343
268
  }
344
269
 
345
- // Ensure index.html exists, if not create a default one for JSX/TSX entry
346
270
  const hasIndex = Object.keys(files).some(f => f.toLowerCase() === 'index.html');
347
271
  if (!hasIndex) {
348
272
  const entryPoint = Object.keys(files).find(f => f.endsWith('.tsx') || f.endsWith('.jsx') || f.endsWith('.ts') || f.endsWith('.js')) || 'src/main.ts';
@@ -362,13 +286,12 @@ app.post("/preview/start", async (c) => {
362
286
  `.trim());
363
287
  }
364
288
 
365
- // Find an available port for Vite and HMR
366
289
  const tempServer = Bun.serve({ port: 0, fetch: () => new Response() });
367
290
  const vitePort = tempServer.port;
368
291
  tempServer.stop();
369
292
 
370
- // Create vite config to handle dependencies from root node_modules
371
293
  const normalizedPackageRoot = join(PACKAGE_ROOT).replace(/\\/g, '/');
294
+ const nodeModulesPath = join(PACKAGE_ROOT, "node_modules").replace(/\\/g, '/');
372
295
 
373
296
  const viteConfigActual = `
374
297
  import { defineConfig } from 'vite';
@@ -405,174 +328,106 @@ export default defineConfig({
405
328
  }
406
329
  }
407
330
  ],
331
+ resolve: {
332
+ alias: {
333
+ 'react': '${nodeModulesPath}/react',
334
+ 'react-dom': '${nodeModulesPath}/react-dom',
335
+ '@vitejs/plugin-react': '${nodeModulesPath}/@vitejs/plugin-react'
336
+ }
337
+ },
408
338
  root: '.',
409
339
  base: './',
410
340
  server: {
411
341
  port: ${vitePort},
412
342
  strictPort: true,
413
- host: true,
414
- hmr: {
415
- host: 'localhost',
416
- port: ${vitePort},
417
- },
418
- fs: {
419
- allow: ['${normalizedPackageRoot}']
420
- }
343
+ host: 'localhost',
344
+ hmr: { host: 'localhost', port: ${vitePort} },
345
+ fs: { allow: ['${normalizedPackageRoot}', '${TMP_DIR.replace(/\\/g, '/')}'] }
421
346
  }
422
347
  });
423
348
  `;
424
349
  await Bun.write(join(sessionDir, "vite.config.ts"), viteConfigActual);
425
350
 
426
- // Install @vitejs/plugin-react in the temp dir might be needed or we use global
427
- // For simplicity, we assume vite-related plugins are available in PACKAGE_ROOT/node_modules
428
-
429
351
  const viteBin = join(PACKAGE_ROOT, "node_modules", "vite", "bin", "vite.js");
430
- const proc = Bun.spawn([process.execPath, viteBin, "--port", String(vitePort), "--strictPort", "--host"], {
352
+ const proc = Bun.spawn([process.execPath, viteBin, "--port", String(vitePort), "--strictPort", "--host", "localhost"], {
431
353
  cwd: sessionDir,
432
354
  stdout: "pipe",
433
355
  stderr: "pipe",
434
356
  env: {
435
357
  ...process.env,
436
- NODE_PATH: join(PACKAGE_ROOT, "node_modules")
358
+ NODE_PATH: nodeModulesPath
437
359
  }
438
360
  });
439
361
 
440
362
  vites.set(sessionId, proc);
441
363
 
442
- // Wait for port
443
364
  return new Promise<any>((resolve) => {
444
365
  let output = "";
445
- const timeout = 25000; // Increase to 25s
446
366
  const timer = setTimeout(() => {
447
- console.error("Vite preview timeout. Current output:", output);
448
- resolve(c.json({ error: "Vite timed out starting. Please try again." }, 500));
449
- }, timeout);
450
-
451
- const read = async () => {
452
- const reader = proc.stdout.getReader();
453
- const decoder = new TextDecoder();
454
-
367
+ console.error("Vite preview timeout. Current output/errors:\n", output);
368
+ resolve(c.json({ error: "Vite timed out. Check terminal for errors." }, 500));
369
+ }, 25000);
370
+
371
+ const read = async (reader: any) => {
372
+ const dr = new TextDecoder();
455
373
  while (true) {
456
374
  const { done, value } = await reader.read();
457
375
  if (done) break;
458
- const chunk = decoder.decode(value, { stream: true });
459
- const strippedChunk = chunk.replace(/\u001b\[[0-9;]*[a-zA-Z]/g, '');
460
-
461
- // Push to session-specific log listeners
462
- const listeners = previewLogs.get(sessionId);
463
- if (listeners) {
464
- listeners.forEach(l => l(strippedChunk));
465
- }
466
-
467
- output += strippedChunk;
468
-
469
- // Match various ways Vite might report the port
470
- const match = output.match(/Local:\s+http:\/\/localhost:(\d+)/i) ||
471
- output.match(/Local:\s+http:\/\/127\.0\.0\.1:(\d+)/i) ||
472
- output.match(/Local:\s+http:\/\/.*:(\d+)/i) ||
473
- output.match(/network:\s+http:\/\/.*:(\d+)/i);
474
-
376
+ const chunk = dr.decode(value, { stream: true });
377
+ const stripped = chunk.replace(/\u001b\[[0-9;]*[a-zA-Z]/g, '');
378
+ output += stripped;
379
+ const match = output.match(/Local:.*:(\d+)/i);
475
380
  if (match) {
476
381
  clearTimeout(timer);
477
- const port = match[1];
478
- const hostname = c.req.header('host')?.split(':')[0] || 'localhost';
479
- resolve(c.json({ sessionId, port, url: `http://${hostname}:${port}` }));
480
- break;
382
+ resolve(c.json({ sessionId, port: match[1], url: `http://localhost:${match[1]}` }));
481
383
  }
482
- }
483
- };
484
-
485
- const readErr = async () => {
486
- const reader = proc.stderr.getReader();
487
- const decoder = new TextDecoder();
488
- while (true) {
489
- const { done, value } = await reader.read();
490
- if (done) break;
491
- const errorText = decoder.decode(value, { stream: true });
492
- const strippedError = errorText.replace(/\u001b\[[0-9;]*[a-zA-Z]/g, '');
493
-
494
- // Push to session-specific log listeners
495
384
  const listeners = previewLogs.get(sessionId);
496
- if (listeners) {
497
- listeners.forEach(l => l(strippedError));
498
- }
499
-
500
- // Stop logging to GLOBAL server console entirely as requested
385
+ if (listeners) listeners.forEach(l => l(stripped));
501
386
  }
502
387
  };
503
-
504
- read();
505
- readErr();
388
+ read(proc.stdout.getReader());
389
+ read(proc.stderr.getReader());
506
390
  });
507
391
  });
508
392
 
509
393
  app.get("/preview/logs/:sessionId", async (c) => {
510
394
  const sessionId = c.req.param("sessionId");
511
-
512
395
  return streamText(c, async (stream) => {
513
- const listener = (log: string) => {
514
- stream.writeln(log);
515
- };
516
-
517
- if (!previewLogs.has(sessionId)) {
518
- previewLogs.set(sessionId, []);
519
- }
396
+ const listener = (log: string) => { stream.writeln(log); };
397
+ if (!previewLogs.has(sessionId)) previewLogs.set(sessionId, []);
520
398
  previewLogs.get(sessionId)!.push(listener);
521
-
522
399
  stream.onAbort(() => {
523
400
  const listeners = previewLogs.get(sessionId);
524
- if (listeners) {
525
- previewLogs.set(sessionId, listeners.filter(l => l !== listener));
526
- }
401
+ if (listeners) previewLogs.set(sessionId, listeners.filter(l => l !== listener));
527
402
  });
528
-
529
- // Keep connection alive
530
- while (true) {
531
- await new Promise(r => setTimeout(r, 1000));
532
- if (stream.closed) break;
533
- }
403
+ while (!stream.closed) { await new Promise(r => setTimeout(r, 1000)); }
534
404
  });
535
405
  });
536
406
 
537
407
  app.post("/preview/sync", async (c) => {
538
408
  const { files, sessionId } = await c.req.json();
539
409
  if (!files || !sessionId) return c.json({ error: "Missing data" }, 400);
540
-
541
410
  const sessionDir = join(ISOLATED_ROOT, "preview-" + sessionId);
542
-
543
411
  try {
544
412
  for (const [name, content] of Object.entries(files)) {
545
413
  if (name.endsWith('/')) continue;
546
414
  const filePath = join(sessionDir, name as string);
547
415
  const parts = (name as string).split('/');
548
- if (parts.length > 1) {
549
- await mkdir(join(sessionDir, join(...parts.slice(0, -1))), { recursive: true });
550
- }
416
+ if (parts.length > 1) await mkdir(join(sessionDir, join(...parts.slice(0, -1))), { recursive: true });
551
417
  await Bun.write(filePath, content as string);
552
418
  }
553
419
  return c.json({ success: true });
554
- } catch (e: any) {
555
- return c.json({ error: e.message }, 500);
556
- }
420
+ } catch (e: any) { return c.json({ error: e.message }, 500); }
557
421
  });
558
422
 
559
423
  app.get("/search", async (c) => {
560
- const query = c.req.query("q");
561
- if (!query) return c.json({ objects: [] });
562
-
424
+ const q = c.req.query("q");
425
+ if (!q) return c.json({ objects: [] });
563
426
  try {
564
- const res = await fetch(`https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=20`);
565
- const data = await res.json();
566
- return c.json(data);
567
- } catch (err) {
568
- return c.json({ objects: [] });
569
- }
427
+ const res = await fetch(`https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(q)}&size=20`);
428
+ return c.json(await res.json());
429
+ } catch (err) { return c.json({ objects: [] }); }
570
430
  });
571
431
 
572
432
  const port = parseInt(process.env.PORT || "3000");
573
-
574
- Bun.serve({
575
- port,
576
- fetch: app.fetch,
577
- idleTimeout: 60,
578
- });
433
+ Bun.serve({ port, fetch: app.fetch, idleTimeout: 60 });