#!/usr/bin/env node "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // src/lib/index.ts var import_socket = require("socket.io"); var import_child_process2 = require("child_process"); var import_promises = __toESM(require("fs/promises"), 1); var import_path = __toESM(require("path"), 1); // src/lib/ServerDetector.ts var import_net = __toESM(require("net"), 1); var import_child_process = require("child_process"); var import_events = require("events"); var import_client = __toESM(require("@d8d-localtunnel/client"), 1); var ServerDetector = class extends import_events.EventEmitter { constructor(config) { super(); this.checkInterval = null; this.workspacePath = config.workspacePath; this.intervalTime = config.checkInterval || 1e3; this.timeout = config.timeout || 5 * 60 * 1e3; this.portRange = { start: 3e3, end: 9999 }; } start() { if (this.checkInterval) { return; } this.checkInterval = setInterval(async () => { await this.detectServer(); }, this.intervalTime); setTimeout(() => { this.stop(); }, this.timeout); } stop() { if (this.checkInterval) { clearInterval(this.checkInterval); this.checkInterval = null; } } async detectServer() { try { const vitePort = await this.findVitePort(); if (vitePort) { await this.handleServerFound(vitePort); return; } const commonPorts = [3e3, 8080, 4e3, 3001, 8e3]; for (const port2 of commonPorts) { const isActive = await this.checkPort(port2); if (isActive && await this.isProcessFromWorkspace(port2)) { await this.handleServerFound(port2); return; } } } catch (error) { console.error("[ServerDetector] Error detecting server:", error); } } async detectServerByPid(process2) { if (!process2.pid) return; let portFromOutput = null; const outputHandler = async (data) => { const output = data.toString(); console.log(`[ServerDetector] \u89E3\u6790\u8F93\u51FA: ${output}`); const portMatches = output.match(/(?:localhost|127\.0\.0\.1):(\d+)/g); if (portMatches) { for (const match of portMatches) { const port2 = parseInt(match.split(":")[1], 10); console.log(`[ServerDetector] \u4ECE\u8F93\u51FA\u4E2D\u53D1\u73B0\u7AEF\u53E3: ${port2}`); const isActive = await this.checkPort(port2); if (isActive) { console.log(`[ServerDetector] \u7AEF\u53E3 ${port2} \u53EF\u7528\uFF0C\u521B\u5EFA\u96A7\u9053...`); portFromOutput = port2; await this.handleServerFound(port2); process2.stdout?.removeListener("data", outputHandler); return; } } } }; process2.stdout?.on("data", outputHandler); await new Promise((resolve) => setTimeout(resolve, 500)); if (portFromOutput) { return; } const maxRetries = 5; const retryDelay = 1e3; let currentTry = 0; const checkPorts = async () => { try { const ports = await this.getProcessPorts(process2.pid); console.log(`[ServerDetector] \u8FDB\u7A0B ${process2.pid} \u7684\u7AEF\u53E3\u4FE1\u606F (\u5C1D\u8BD5 ${currentTry + 1}):`, ports); if (ports.length > 0) { for (const { port: port2, protocol } of ports) { if (protocol === "tcp") { await this.handleServerFound(port2); return true; } } } const childPorts = await this.getChildProcessPorts(process2.pid); console.log(`[ServerDetector] \u5B50\u8FDB\u7A0B\u7AEF\u53E3\u4FE1\u606F (\u5C1D\u8BD5 ${currentTry + 1}):`, childPorts); if (childPorts.length > 0) { for (const { port: port2, protocol } of childPorts) { if (protocol === "tcp") { await this.handleServerFound(port2); return true; } } } return false; } catch (error) { console.error("[ServerDetector] \u68C0\u6D4B\u7AEF\u53E3\u5931\u8D25:", error); return false; } }; while (currentTry < maxRetries && !portFromOutput) { const found = await checkPorts(); if (found) { process2.stdout?.removeListener("data", outputHandler); return; } currentTry++; if (currentTry < maxRetries) { console.log(`[ServerDetector] \u7B49\u5F85 ${retryDelay}ms \u540E\u8FDB\u884C\u7B2C ${currentTry + 1} \u6B21\u5C1D\u8BD5...`); await new Promise((resolve) => setTimeout(resolve, retryDelay)); } } console.log("[ServerDetector] \u7AEF\u53E3\u68C0\u6D4B\u91CD\u8BD5\u7ED3\u675F\uFF0C\u7EE7\u7EED\u76D1\u542C\u8FDB\u7A0B\u8F93\u51FA..."); } async getChildProcessPorts(parentPid) { return new Promise((resolve) => { (0, import_child_process.exec)(`pgrep -P ${parentPid}`, async (error, stdout) => { if (error) { console.error("[ServerDetector] pgrep \u6267\u884C\u5931\u8D25:", error); resolve([]); return; } const childPids = stdout.trim().split("\n").filter(Boolean); const allPorts = []; const portPromises = childPids.map( (childPid) => this.getProcessPorts(parseInt(childPid, 10)) ); try { const results = await Promise.all(portPromises); results.forEach((ports) => allPorts.push(...ports)); } catch (error2) { console.error("[ServerDetector] \u68C0\u67E5\u5B50\u8FDB\u7A0B\u7AEF\u53E3\u5931\u8D25:", error2); } resolve(allPorts); }); }); } async getProcessPorts(pid) { return new Promise((resolve) => { (0, import_child_process.exec)(`lsof -i -P -n -p ${pid} | grep LISTEN`, (error, stdout) => { if (error) { resolve([]); return; } const ports = []; const lines = stdout.split("\n").filter(Boolean); for (const line of lines) { const match = line.match(/\s+(\w+):(\d+)\s+\(LISTEN\)$/); if (match) { ports.push({ pid, port: parseInt(match[2], 10), protocol: match[1].toLowerCase() }); } } resolve(ports); }); }); } // 抽取处理找到服务器的逻辑 async handleServerFound(port2) { try { const tunnel = await (0, import_client.default)({ port: port2, host: "https://pre.d8d.fun" }); this.emit("server-ready", { port: port2, url: tunnel.url, localUrl: `http://localhost:${port2}` }); } catch (error) { console.error("Failed to create tunnel:", error); this.emit("server-ready", { port: port2, url: `http://localhost:${port2}` }); } this.stop(); } async checkPort(port2) { return new Promise((resolve) => { const tester = import_net.default.createConnection({ port: port2, host: "localhost", timeout: 1e3 }); tester.on("connect", () => { tester.end(); resolve(true); }); tester.on("error", () => { resolve(false); }); tester.on("timeout", () => { tester.destroy(); resolve(false); }); }); } async isProcessFromWorkspace(port2) { const maxRetries = 3; const retryDelay = 1e3; for (let i = 0; i < maxRetries; i++) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5e3); const response = await fetch(`http://localhost:${port2}`, { signal: controller.signal }); clearTimeout(timeoutId); return response.ok; } catch (error) { if (i === maxRetries - 1) { console.log(`[ServerDetector] Failed to check port ${port2} after ${maxRetries} retries`); return false; } await new Promise((resolve) => setTimeout(resolve, retryDelay)); } } return false; } // 修改 findActualPort 方法 async findActualPort() { const vitePort = await this.findVitePort(); if (vitePort) { return vitePort; } return new Promise((resolve) => { (0, import_child_process.exec)(`lsof -i -P -n | grep LISTEN | grep node`, (error, stdout) => { if (error) { console.log("[ServerDetector] Error finding actual port:", error); resolve(null); return; } try { const lines = stdout.split("\n"); for (const line of lines) { const match = line.match(/:51[7-9][0-9]/); if (match) { const port2 = parseInt(match[0].substring(1)); if (port2 >= 5173 && port2 <= 5179) { console.log(`[ServerDetector] Found Vite server port from lsof: ${port2}`); resolve(port2); return; } } } resolve(null); } catch (parseError) { console.error("[ServerDetector] Error parsing port:", parseError); resolve(null); } }); }); } // 添加新方法来从 Vite 输出解析端口 async findVitePort() { try { for (let port2 = 5173; port2 <= 5179; port2++) { const isActive = await this.checkPort(port2); if (isActive) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 3e3); const response = await fetch(`http://localhost:${port2}`, { signal: controller.signal }); clearTimeout(timeoutId); const text = await response.text(); if (text.includes("vite") || response.headers.get("server")?.includes("vite")) { console.log(`[ServerDetector] Found Vite server on port: ${port2}`); return port2; } } catch (error) { continue; } } } } catch (error) { console.error("[ServerDetector] Error in findVitePort:", error); } return null; } static isDevServerCommand(command, args = []) { const devCommands = [ ["npm", ["run", "dev"]], ["npm", ["run", "start"]], ["npm", ["start"]], ["yarn", ["dev"]], ["yarn", ["start"]], ["pnpm", ["dev"]], ["pnpm", ["start"]], ["vite"], ["next"], ["nuxt"] ]; return devCommands.some(([cmd, cmdArgs]) => { if (!cmdArgs) { return command === cmd; } return command === cmd && JSON.stringify(args) === JSON.stringify(cmdArgs); }); } }; // src/lib/index.ts var import_http = require("http"); var import_chokidar = __toESM(require("chokidar"), 1); var import_fs = require("fs"); // src/lib/constants.ts var PROCESS_CONFIG = { // 进程终止超时时间(毫秒) KILL_TIMEOUT: 5e3, // 进程清理重试次数 CLEANUP_RETRIES: 3 }; var FS_CONFIG = { // 文件编码 ENCODING: "utf-8", // 目录权限 DIR_MODE: 493, // 文件权限 FILE_MODE: 420 }; var SERVER_CONFIG = { DEFAULT_PORT: 3e3, DEFAULT_HOST: "localhost", DEFAULT_WORKSPACE_DIR: "workspaces", CORS: { origin: "*", methods: ["GET", "POST"] } }; var WATCH_CONFIG = { DEFAULT_IGNORE: [ "**/node_modules/**", "**/.git/**", "**/dist/**", "**/.DS_Store", "**/thumbs.db" ], WATCH_OPTIONS: { persistent: true, ignoreInitial: false, followSymlinks: false, disableGlobbing: false, usePolling: false } }; // src/lib/index.ts var import_anymatch = __toESM(require("anymatch"), 1); var WebContainerServer = class { constructor(config = {}) { this.workspaces = /* @__PURE__ */ new Map(); this.processes = /* @__PURE__ */ new Map(); this.processHistory = /* @__PURE__ */ new Map(); this.watchers = /* @__PURE__ */ new Map(); this.config = { port: config.port || SERVER_CONFIG.DEFAULT_PORT, host: config.host || SERVER_CONFIG.DEFAULT_HOST, workspaceRoot: config.workspaceRoot || import_path.default.join(process.cwd(), SERVER_CONFIG.DEFAULT_WORKSPACE_DIR) }; this.httpServer = (0, import_http.createServer)(); this.io = new import_socket.Server(this.httpServer, { cors: SERVER_CONFIG.CORS }); this.setupSocketHandlers(); } async start() { try { await import_promises.default.mkdir(this.config.workspaceRoot, { recursive: true }); const requiredCommands = ["git", "npm", "node"]; for (const cmd of requiredCommands) { const exists = await this.checkCommandExists(cmd); if (!exists) { console.warn(`\u8B66\u544A: \u547D\u4EE4 '${cmd}' \u672A\u5B89\u88C5\uFF0C\u8FD9\u53EF\u80FD\u4F1A\u5F71\u54CD\u67D0\u4E9B\u529F\u80FD\u7684\u4F7F\u7528`); } } return new Promise((resolve) => { this.httpServer.listen(this.config.port, this.config.host, () => { console.log( `WebContainer Server is running on http://${this.config.host}:${this.config.port}` ); console.log(`Workspace root: ${this.config.workspaceRoot}`); resolve(); }); }); } catch (error) { console.error("Failed to start server:", error); process.exit(1); } } setupSocketHandlers() { this.io.on("connection", async (socket) => { const workspaceId = Math.random().toString(36).substr(2, 9); const workspacePath = import_path.default.join(this.config.workspaceRoot, workspaceId); this.workspaces.set(socket.id, workspacePath); this.processes.set(socket.id, /* @__PURE__ */ new Map()); console.log(`Client connected. Workspace created at: ${workspacePath}`); try { await import_promises.default.mkdir(workspacePath, { recursive: true }); } catch (error) { console.error("Failed to configure NPM mirrors:", error); } 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 ({ command, args = [], terminalId = "default" }, callback) => { try { const result = await this.handleSpawn( socket.id, workspacePath, command, args, terminalId ); callback({ result }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; callback({ error: errorMessage }); } } ); socket.on("fs:list", async (path3, callback) => { try { const result = await this.handleList(workspacePath, path3); callback({ success: true, result }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; callback({ error: errorMessage }); } }); socket.on("fs:read", async (path3, callback) => { try { const result = await this.handleRead(workspacePath, path3); 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 (path3, callback) => { try { await this.handleDelete(workspacePath, path3); callback({ success: true }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; callback({ error: errorMessage }); } }); socket.on("fs:mkdir", async (path3, callback) => { try { await this.handleMkdir(workspacePath, path3); callback({ success: true }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; callback({ error: errorMessage }); } }); socket.on( "fs:watch", async ({ glob, ignore = WATCH_CONFIG.DEFAULT_IGNORE, ignoreInitial }, callback = () => { }) => { try { console.log(`[Watch] Socket ${socket.id} \u5F00\u59CB\u76D1\u542C\u6587\u4EF6: ${glob}`); console.log(`[Watch] \u5FFD\u7565\u7684\u6587\u4EF6\u6A21\u5F0F:`, ignore); console.log(`[Watch] ignoreInitial:`, ignoreInitial); const watchPatterns = [glob, `${glob}/*`, `${glob}/**/*`]; const watcher = import_chokidar.default.watch(watchPatterns, { cwd: workspacePath, ...WATCH_CONFIG.WATCH_OPTIONS, ...ignoreInitial !== void 0 ? { ignoreInitial } : {}, ignored: (path3) => { const shouldIgnore = (0, import_anymatch.default)(ignore, path3); if (shouldIgnore) { console.log(`[Watch] \u5FFD\u7565\u8DEF\u5F84: ${path3}`); } return shouldIgnore; } }); watcher.on("all", async (event, path3) => { const fullPath = `${workspacePath}/${path3}`; let stats = null; try { stats = await import_fs.promises.stat(fullPath).catch(() => null); } catch (error) { } socket.emit("fs:change", { type: event === "add" || event === "addDir" ? "create" : event === "change" ? "update" : event === "unlink" ? "delete" : event, path: path3, kind: stats?.isDirectory() ? "directory" : "file" }); }); watcher.on("error", (error) => { console.error(`[Watch] \u76D1\u542C\u9519\u8BEF:`, error); }); watcher.on("ready", () => { console.log(`[Watch] \u6587\u4EF6\u76D1\u542C\u5668\u5DF2\u5C31\u7EEA`); }); this.watchers.set(socket.id, watcher); if (typeof callback === "function") { callback({ success: true }); } } catch (error) { console.error(`[Watch] \u8BBE\u7F6E\u76D1\u542C\u5931\u8D25:`, error); const errorMessage = error instanceof Error ? error.message : "\u8BBE\u7F6E\u76D1\u542C\u5931\u8D25"; if (typeof callback === "function") { callback({ error: errorMessage }); } } } ); socket.on( "fs:unwatch", async ({ glob }, callback = () => { }) => { try { const watcher = this.watchers.get(socket.id); if (watcher) { await watcher.close(); this.watchers.delete(socket.id); } if (typeof callback === "function") { callback({ success: true }); } } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; if (typeof callback === "function") { callback({ error: errorMessage }); } } } ); socket.on("disconnect", async () => { console.log(`\u5BA2\u6237\u7AEF\u65AD\u5F00\u8FDE\u63A5. \u5DE5\u4F5C\u533A: ${workspacePath}`); try { const watcher = this.watchers.get(socket.id); if (watcher) { await watcher.close(); this.watchers.delete(socket.id); console.log(`\u6587\u4EF6\u76D1\u542C\u5668\u5DF2\u6E05\u7406: ${socket.id}`); } console.log(`\u6B63\u5728\u7EC8\u6B62\u5DE5\u4F5C\u533A\u8FDB\u7A0B: ${workspacePath}`); await this.killAllProcesses(socket.id); console.log(`\u6B63\u5728\u6E05\u7406\u5DE5\u4F5C\u533A\u76EE\u5F55: ${workspacePath}`); const exists = await import_promises.default.access(workspacePath).then(() => true).catch(() => false); if (exists) { await import_promises.default.rm(workspacePath, { recursive: true, force: true }); console.log(`\u5DE5\u4F5C\u533A\u76EE\u5F55\u5DF2\u6E05\u7406: ${workspacePath}`); } else { console.log(`\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u6E05\u7406: ${workspacePath}`); } this.workspaces.delete(socket.id); this.processes.delete(socket.id); console.log(`\u5BA2\u6237\u7AEF\u8D44\u6E90\u6E05\u7406\u5B8C\u6210: ${socket.id}`); } catch (error) { console.error(`\u5BA2\u6237\u7AEF\u6E05\u7406\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:`, error); } }); socket.on( "process:kill", async ({ pid, terminalId = "default" }, callback) => { try { await this.killProcess(socket.id, pid, terminalId); callback({ success: true }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; callback({ error: errorMessage }); } } ); }); } async killAllProcesses(socketId) { const processMap = this.processes.get(socketId); if (!processMap) { console.log(`\u6CA1\u6709\u627E\u5230 socket ${socketId} \u7684\u8FDB\u7A0B\u8BB0\u5F55`); return; } await Promise.all( Array.from(processMap.entries()).map(async ([terminalId, process2]) => { try { if (!process2.pid) { console.log(`\u8FDB\u7A0B (\u7EC8\u7AEF ${terminalId}) \u6CA1\u6709\u6709\u6548\u7684 PID`); return; } await new Promise((resolve, reject) => { const pid = process2.pid; const pkill = (0, import_child_process2.spawn)("pkill", ["-TERM", "-P", pid.toString()]); pkill.on("close", async () => { try { process2.kill("SIGINT"); console.log( `\u8FDB\u7A0B ${pid} (\u7EC8\u7AEF ${terminalId}) \u5DF2\u53D1\u9001 SIGINT \u4FE1\u53F7` ); await new Promise((resolveWait) => { const timeout = setTimeout(() => { try { (0, import_child_process2.spawn)("pkill", ["-KILL", "-P", pid.toString()]); process2.kill("SIGKILL"); console.log( `\u8FDB\u7A0B ${pid} (\u7EC8\u7AEF ${terminalId}) \u5DF2\u5F3A\u5236\u7EC8\u6B62` ); } catch (error) { console.error( `\u5F3A\u5236\u7EC8\u6B62\u8FDB\u7A0B ${pid} (\u7EC8\u7AEF ${terminalId}) \u5931\u8D25:`, error ); } resolveWait(); }, PROCESS_CONFIG.KILL_TIMEOUT); process2.once("exit", (code) => { clearTimeout(timeout); console.log( `\u8FDB\u7A0B ${pid} (\u7EC8\u7AEF ${terminalId}) \u5DF2\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}` ); resolveWait(); }); }); resolve(); } catch (error) { reject(error); } }); }); } catch (error) { if (error instanceof Error && error.message.includes("ESRCH")) { console.log(`\u8FDB\u7A0B ${process2.pid} (\u7EC8\u7AEF ${terminalId}) \u5DF2\u4E0D\u5B58\u5728`); } else { console.error( `\u7EC8\u6B62\u8FDB\u7A0B ${process2.pid} (\u7EC8\u7AEF ${terminalId}) \u65F6\u53D1\u751F\u9519\u8BEF:`, error ); } } }) ); processMap.clear(); this.processHistory.set(socketId, []); console.log(`Socket ${socketId} \u7684\u6240\u6709\u8FDB\u7A0B\u5DF2\u6E05\u7406\u5B8C\u6210`); } async handleMount(workspacePath, files) { await import_promises.default.mkdir(workspacePath, { recursive: true }); await this.createFiles(workspacePath, files); } async createFiles(basePath, files) { for (const [name, item] of Object.entries(files)) { const fullPath = import_path.default.join(basePath, name); if ("file" in item) { await import_promises.default.writeFile(fullPath, item.file.contents); } else if ("directory" in item) { await import_promises.default.mkdir(fullPath, { recursive: true }); await this.createFiles(fullPath, item.directory.contents); } } } async checkCommandExists(command) { try { await new Promise((resolve, reject) => { const check = (0, import_child_process2.spawn)("which", [command]); check.on("close", (code) => { if (code === 0) { resolve(); } else { reject(); } }); }); return true; } catch { return false; } } async handleSpawn(socketId, workspacePath, command, args, terminalId) { const processes = this.processes.get(socketId); if (!processes) { throw new Error("No process map found for socket"); } const commandExists = await this.checkCommandExists(command); if (!commandExists) { throw new Error(`\u547D\u4EE4 '${command}' \u672A\u5B89\u88C5\u3002\u8BF7\u786E\u4FDD\u7CFB\u7EDF\u5DF2\u5B89\u88C5\u8BE5\u547D\u4EE4\u5E76\u6DFB\u52A0\u5230 PATH \u73AF\u5883\u53D8\u91CF\u4E2D\u3002`); } return new Promise((resolve, reject) => { const process2 = (0, import_child_process2.spawn)(command, args, { cwd: workspacePath, stdio: ["pipe", "pipe", "pipe"], shell: true }); if (!process2.pid) { reject(new Error("Failed to get process ID")); return; } const processInfo = { pid: process2.pid, command, args, timestamp: Date.now() }; const history = this.processHistory.get(socketId) || []; history.push(processInfo); this.processHistory.set(socketId, history); this.io.to(socketId).emit("process:started", { ...processInfo, terminalId }); if (ServerDetector.isDevServerCommand(command, args)) { console.log( `[ServerDetector] \u7B49\u5F85\u5F00\u53D1\u670D\u52A1\u5668\u542F\u52A8: ${command} ${args.join(" ")}` ); const detector = new ServerDetector({ workspacePath }); detector.on("server-ready", (event) => { console.log(`[ServerDetector] \u68C0\u6D4B\u5230\u670D\u52A1\u5668\u5C31\u7EEA:`, event); this.io.to(socketId).emit("server:ready", { ...event, command, args }); }); let detectionStarted = false; process2.stdout.on("data", () => { if (!detectionStarted) { detectionStarted = true; console.log(`[ServerDetector] \u68C0\u6D4B\u5230\u8FDB\u7A0B\u8F93\u51FA\uFF0C\u5F00\u59CB\u7AEF\u53E3\u68C0\u6D4B...`); detector.detectServerByPid(process2); } }); } let stdout = ""; let stderr = ""; process2.on("SIGINT", () => { console.log(`Process ${process2.pid} received SIGINT`); process2.kill("SIGINT"); }); process2.stdout.on("data", (data) => { const output = data.toString(); stdout += output; console.log(`[${command}] Process ${process2.pid} stdout:`, output); this.io.to(socketId).emit("process:output", { type: "stdout", data: output, terminalId }); }); 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, terminalId }); }); process2.on("close", (code) => { const processHistory = this.processHistory.get(socketId) || []; const historyIndex = processHistory.findIndex( (p) => p.pid === process2.pid ); if (historyIndex > -1) { processHistory.splice(historyIndex, 1); } this.processHistory.set(socketId, processHistory); this.io.to(socketId).emit("process:ended", { pid: process2.pid, code }); if (process2.pid) { processes.delete(terminalId); } if (code !== 0) { this.io.to(socketId).emit("process:output", { type: "exit", data: `Process exited with code ${code}`, terminalId }); } if (code === 0 && process2.pid) { resolve({ pid: process2.pid }); } else { reject(new Error(`Process exited with code ${code}`)); } }); process2.on("error", (error) => { processes.delete(terminalId); this.io.to(socketId).emit("process:output", { type: "stderr", data: error.message, terminalId }); reject(error); }); processes.set(terminalId, process2); }); } async handleList(workspacePath, relativePath) { const fullPath = import_path.default.join(workspacePath, relativePath); const entries = await import_promises.default.readdir(fullPath, { withFileTypes: true }); return entries.map((entry) => ({ name: entry.name, type: entry.isDirectory() ? "directory" : "file" })); } async handleRead(workspacePath, relativePath) { const fullPath = import_path.default.join(workspacePath, relativePath); return import_promises.default.readFile(fullPath, "utf-8"); } async handleWrite(workspacePath, relativePath, content) { const fullPath = import_path.default.join(workspacePath, relativePath); await import_promises.default.mkdir(import_path.default.dirname(fullPath), { recursive: true, mode: FS_CONFIG.DIR_MODE }); await import_promises.default.writeFile(fullPath, content, { encoding: FS_CONFIG.ENCODING, mode: FS_CONFIG.FILE_MODE }); } async handleDelete(workspacePath, relativePath) { const fullPath = import_path.default.join(workspacePath, relativePath); await import_promises.default.rm(fullPath, { recursive: true, force: true }); } async handleMkdir(workspacePath, relativePath) { const fullPath = import_path.default.join(workspacePath, relativePath); await import_promises.default.mkdir(fullPath, { recursive: true }); } stop() { return new Promise(async (resolve) => { console.log("\u6B63\u5728\u505C\u6B62 WebContainer Server..."); for (const [socketId, watcher] of this.watchers.entries()) { try { await watcher.close(); this.watchers.delete(socketId); console.log(`\u6587\u4EF6\u76D1\u542C\u5668\u5DF2\u6E05\u7406: ${socketId}`); } catch (error) { console.error(`\u6E05\u7406\u6587\u4EF6\u76D1\u542C\u5668\u5931\u8D25 ${socketId}:`, error); } } for (const socketId of this.processes.keys()) { this.killAllProcesses(socketId); } console.log("\u6B63\u5728\u6E05\u7406\u5DE5\u4F5C\u533A\u76EE\u5F55..."); const workspaces = Array.from(this.workspaces.values()); console.log(`\u9700\u8981\u6E05\u7406\u7684\u5DE5\u4F5C\u533A: ${workspaces.join(", ")}`); try { const rootExists = await import_promises.default.access(this.config.workspaceRoot).then(() => true).catch(() => false); if (rootExists) { await Promise.all( workspaces.map(async (workspacePath) => { try { const exists = await import_promises.default.access(workspacePath).then(() => true).catch(() => false); if (exists) { console.log(`\u6B63\u5728\u6E05\u7406\u5DE5\u4F5C\u533A: ${workspacePath}`); await import_promises.default.rm(workspacePath, { recursive: true, force: true }); console.log(`\u5DE5\u4F5C\u533A\u5DF2\u6E05\u7406: ${workspacePath}`); } else { console.log(`\u5DE5\u4F5C\u533A\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u6E05\u7406: ${workspacePath}`); } } catch (error) { console.error(`\u6E05\u7406\u5DE5\u4F5C\u533A\u5931\u8D25 ${workspacePath}:`, error); } }) ); try { const rootContents = await import_promises.default.readdir(this.config.workspaceRoot); if (rootContents.length === 0) { await import_promises.default.rm(this.config.workspaceRoot, { recursive: true, force: true }); console.log(`\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\u5DF2\u6E05\u7406: ${this.config.workspaceRoot}`); } else { console.log( `\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\u975E\u7A7A\uFF0C\u4FDD\u7559: ${this.config.workspaceRoot}` ); } } catch (error) { console.error("\u6E05\u7406\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\u5931\u8D25:", error); } } else { console.log(`\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\u4E0D\u5B58\u5728: ${this.config.workspaceRoot}`); } } catch (error) { console.error("\u6E05\u7406\u5DE5\u4F5C\u533A\u65F6\u53D1\u751F\u9519\u8BEF:", error); } this.io.close(() => { console.log("Socket.IO \u670D\u52A1\u5668\u5DF2\u5173\u95ED"); this.httpServer.close(() => { console.log("HTTP \u670D\u52A1\u5668\u5DF2\u5173\u95ED"); this.workspaces.clear(); this.processes.clear(); console.log("\u5185\u5B58\u4E2D\u7684\u5DE5\u4F5C\u533A\u8BB0\u5F55\u5DF2\u6E05\u7406"); resolve(); }); }); }); } async killProcess(socketId, pid, terminalId) { const processes = this.processes.get(socketId); if (!processes) { throw new Error("No processes found for socket"); } const process2 = processes.get(terminalId); if (!process2) { console.warn(`No process found for terminal ${terminalId}`); return; } try { await new Promise((resolve, reject) => { const pkill = (0, import_child_process2.spawn)("pkill", ["-TERM", "-P", pid.toString()]); pkill.on("close", async () => { try { process2.kill("SIGINT"); console.log(`\u8FDB\u7A0B ${pid} \u5DF2\u53D1\u9001 SIGINT \u4FE1\u53F7`); await new Promise((resolveWait) => { const timeout = setTimeout(() => { try { (0, import_child_process2.spawn)("pkill", ["-KILL", "-P", pid.toString()]); process2.kill("SIGKILL"); console.log(`\u8FDB\u7A0B ${pid} \u5DF2\u5F3A\u5236\u7EC8\u6B62`); } catch (error) { console.error(`\u5F3A\u5236\u7EC8\u6B62\u8FDB\u7A0B ${pid} \u5931\u8D25:`, error); } resolveWait(); }, 5e3); process2.once("exit", (code) => { clearTimeout(timeout); console.log(`\u8FDB\u7A0B ${pid} \u5DF2\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}`); resolveWait(); }); }); resolve(); } catch (error) { reject(error); } }); }); processes.delete(terminalId); const history = this.processHistory.get(socketId) || []; const historyIndex = history.findIndex((p) => p.pid === pid); if (historyIndex > -1) { history.splice(historyIndex, 1); } this.processHistory.set(socketId, history); this.io.to(socketId).emit("process:ended", { pid, code: 0 }); } catch (error) { console.error(`\u7EC8\u6B62\u8FDB\u7A0B ${pid} \u5931\u8D25:`, error); throw error; } } }; // src/start.ts var import_path2 = __toESM(require("path"), 1); var port = parseInt(process.env.PORT || "3000", 10); var host = process.env.HOST || "localhost"; var workspaceRoot = process.env.WORKSPACE_ROOT || import_path2.default.join(process.cwd(), "workspaces"); var server = new WebContainerServer({ port, host, workspaceRoot }); var isShuttingDown = false; server.start(); async function handleShutdown(signal) { if (isShuttingDown) { return; } isShuttingDown = true; console.log(` \u6536\u5230 ${signal} \u4FE1\u53F7\uFF0C\u6B63\u5728\u4F18\u96C5\u9000\u51FA...`); try { await server.stop(); console.log("\u670D\u52A1\u5668\u5DF2\u6210\u529F\u505C\u6B62"); setImmediate(() => process.exit(0)); } catch (error) { console.error("\u670D\u52A1\u5668\u505C\u6B62\u65F6\u53D1\u751F\u9519\u8BEF:", error); setImmediate(() => process.exit(1)); } } process.on("SIGTERM", () => handleShutdown("SIGTERM")); process.on("SIGINT", () => handleShutdown("SIGINT")); process.on("uncaughtException", (error) => { console.error("\u672A\u6355\u83B7\u7684\u5F02\u5E38:", error); handleShutdown("uncaughtException"); }); process.on("unhandledRejection", (reason) => { console.error("\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:", reason); handleShutdown("unhandledRejection"); });