// src/index.ts import { Server } from "socket.io"; import { spawn } from "child_process"; import fs from "fs/promises"; import path from "path"; var WebContainerServer = class { constructor(config = {}) { this.workspaces = /* @__PURE__ */ new Map(); this.processes = /* @__PURE__ */ new Map(); this.config = { port: config.port || 3e3, host: config.host || "localhost", workspaceRoot: config.workspaceRoot || path.join(process.cwd(), "workspaces") }; this.io = new Server({ cors: { origin: "*", methods: ["GET", "POST"] } }); this.setupSocketHandlers(); } start() { fs.mkdir(this.config.workspaceRoot, { recursive: true }).then(() => { this.io.listen(this.config.port); console.log(`WebContainer Server is running on http://${this.config.host}:${this.config.port}`); console.log(`Workspace root: ${this.config.workspaceRoot}`); }).catch((error) => { console.error("Failed to create workspace directory:", error); process.exit(1); }); return this; } setupSocketHandlers() { this.io.on("connection", (socket) => { const workspaceId = Math.random().toString(36).substr(2, 9); const workspacePath = path.join(this.config.workspaceRoot, workspaceId); this.workspaces.set(socket.id, workspacePath); this.processes.set(socket.id, []); console.log(`Client connected. Workspace created at: ${workspacePath}`); socket.on("mount", async (files, callback) => { try { await this.handleMount(workspacePath, files); callback({ success: true }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; callback({ error: errorMessage }); } }); socket.on("spawn", async (data, callback) => { try { const result = await this.handleSpawn(socket.id, workspacePath, data.command, data.args); callback({ success: true, result }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; callback({ error: errorMessage }); } }); socket.on("fs:list", async (path2, callback) => { try { const result = await this.handleList(workspacePath, path2); callback({ success: true, result }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; callback({ error: errorMessage }); } }); socket.on("fs:read", async (path2, callback) => { try { const result = await this.handleRead(workspacePath, path2); callback({ success: true, result }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; callback({ error: errorMessage }); } }); socket.on("fs:write", async (data, callback) => { try { await this.handleWrite(workspacePath, data.path, data.content); callback({ success: true }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; callback({ error: errorMessage }); } }); socket.on("fs:delete", async (path2, callback) => { try { await this.handleDelete(workspacePath, path2); callback({ success: true }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; callback({ error: errorMessage }); } }); socket.on("fs:mkdir", async (path2, callback) => { try { await this.handleMkdir(workspacePath, path2); callback({ success: true }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; callback({ error: errorMessage }); } }); socket.on("disconnect", () => { console.log(`Client disconnected. Cleaning up workspace: ${workspacePath}`); this.killAllProcesses(socket.id); fs.rm(workspacePath, { recursive: true, force: true }).catch((error) => console.error(`Failed to cleanup workspace: ${error}`)); this.workspaces.delete(socket.id); this.processes.delete(socket.id); }); }); } killAllProcesses(socketId) { const processes = this.processes.get(socketId) || []; for (const process2 of processes) { try { process2.kill("SIGTERM"); console.log(`Process ${process2.pid} terminated`); } catch (error) { console.error(`Failed to kill process ${process2.pid}:`, error); } } } async handleMount(workspacePath, files) { await fs.mkdir(workspacePath, { recursive: true }); await this.createFiles(workspacePath, files); } async createFiles(basePath, files) { for (const [name, item] of Object.entries(files)) { const fullPath = path.join(basePath, name); if ("file" in item) { await fs.writeFile(fullPath, item.file.contents); } else if ("directory" in item) { await fs.mkdir(fullPath, { recursive: true }); await this.createFiles(fullPath, item.directory.contents); } } } async handleSpawn(socketId, workspacePath, command, args = []) { return new Promise((resolve, reject) => { this.io.to(socketId).emit("process:output", { type: "stdout", data: `\r $ ${command} ${args.join(" ")}\r ` }); const process2 = spawn(command, args, { cwd: workspacePath, stdio: ["pipe", "pipe", "pipe"] }); const processes = this.processes.get(socketId) || []; processes.push(process2); this.processes.set(socketId, processes); let stdout = ""; let stderr = ""; process2.stdout.on("data", (data) => { const output = data.toString(); stdout += output; console.log(`[${command}] stdout:`, output); this.io.to(socketId).emit("process:output", { type: "stdout", data: output }); }); process2.stderr.on("data", (data) => { const output = data.toString(); stderr += output; console.error(`[${command}] stderr:`, output); this.io.to(socketId).emit("process:output", { type: "stderr", data: output }); }); process2.on("close", (code) => { const processes2 = this.processes.get(socketId) || []; const index = processes2.indexOf(process2); if (index > -1) { processes2.splice(index, 1); } this.io.to(socketId).emit("process:output", { type: "exit", data: `\r Process exited with code ${code}\r $ ` }); if (code === 0) { resolve({ code, stdout, stderr }); } else { reject(new Error(`Process exited with code ${code} ${stderr}`)); } }); process2.on("error", (error) => { const processes2 = this.processes.get(socketId) || []; const index = processes2.indexOf(process2); if (index > -1) { processes2.splice(index, 1); } this.io.to(socketId).emit("process:output", { type: "stderr", data: `\r Error: ${error.message}\r $ ` }); reject(error); }); }); } async handleList(workspacePath, relativePath) { const fullPath = path.join(workspacePath, relativePath); const entries = await fs.readdir(fullPath, { withFileTypes: true }); return entries.map((entry) => ({ name: entry.name, type: entry.isDirectory() ? "directory" : "file" })); } async handleRead(workspacePath, relativePath) { const fullPath = path.join(workspacePath, relativePath); return fs.readFile(fullPath, "utf-8"); } async handleWrite(workspacePath, relativePath, content) { const fullPath = path.join(workspacePath, relativePath); await fs.mkdir(path.dirname(fullPath), { recursive: true }); await fs.writeFile(fullPath, content); } async handleDelete(workspacePath, relativePath) { const fullPath = path.join(workspacePath, relativePath); await fs.rm(fullPath, { recursive: true, force: true }); } async handleMkdir(workspacePath, relativePath) { const fullPath = path.join(workspacePath, relativePath); await fs.mkdir(fullPath, { recursive: true }); } }; export { WebContainerServer };