start.cjs 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025
  1. #!/usr/bin/env node
  2. 'use strict';
  3. var socket_io = require('socket.io');
  4. var child_process = require('child_process');
  5. var fs = require('fs/promises');
  6. var path = require('path');
  7. var net = require('net');
  8. var events = require('events');
  9. var createTunnel = require('@d8d-localtunnel/client');
  10. var http = require('http');
  11. var chokidar = require('chokidar');
  12. var fs$1 = require('fs');
  13. var anymatch = require('anymatch');
  14. function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
  15. var fs__default = /*#__PURE__*/_interopDefault(fs);
  16. var path__default = /*#__PURE__*/_interopDefault(path);
  17. var net__default = /*#__PURE__*/_interopDefault(net);
  18. var createTunnel__default = /*#__PURE__*/_interopDefault(createTunnel);
  19. var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
  20. var anymatch__default = /*#__PURE__*/_interopDefault(anymatch);
  21. var ServerDetector = class extends events.EventEmitter {
  22. constructor(config) {
  23. super();
  24. this.checkInterval = null;
  25. this.workspacePath = config.workspacePath;
  26. this.intervalTime = config.checkInterval || 1e3;
  27. this.timeout = config.timeout || 5 * 60 * 1e3;
  28. this.portRange = {
  29. start: 3e3,
  30. end: 9999
  31. };
  32. }
  33. start() {
  34. if (this.checkInterval) {
  35. return;
  36. }
  37. this.checkInterval = setInterval(async () => {
  38. await this.detectServer();
  39. }, this.intervalTime);
  40. setTimeout(() => {
  41. this.stop();
  42. }, this.timeout);
  43. }
  44. stop() {
  45. if (this.checkInterval) {
  46. clearInterval(this.checkInterval);
  47. this.checkInterval = null;
  48. }
  49. }
  50. async detectServer() {
  51. try {
  52. const vitePort = await this.findVitePort();
  53. if (vitePort) {
  54. await this.handleServerFound(vitePort);
  55. return;
  56. }
  57. const commonPorts = [3e3, 8080, 4e3, 3001, 8e3];
  58. for (const port2 of commonPorts) {
  59. const isActive = await this.checkPort(port2);
  60. if (isActive && await this.isProcessFromWorkspace(port2)) {
  61. await this.handleServerFound(port2);
  62. return;
  63. }
  64. }
  65. } catch (error) {
  66. console.error("[ServerDetector] Error detecting server:", error);
  67. }
  68. }
  69. async detectServerByPid(process2) {
  70. if (!process2.pid) return;
  71. let portFromOutput = null;
  72. const outputHandler = async (data) => {
  73. const output = data.toString();
  74. console.log(`[ServerDetector] \u89E3\u6790\u8F93\u51FA: ${output}`);
  75. const portMatches = output.match(/(?:localhost|127\.0\.0\.1):(\d+)/g);
  76. if (portMatches) {
  77. for (const match of portMatches) {
  78. const port2 = parseInt(match.split(":")[1], 10);
  79. console.log(`[ServerDetector] \u4ECE\u8F93\u51FA\u4E2D\u53D1\u73B0\u7AEF\u53E3: ${port2}`);
  80. const isActive = await this.checkPort(port2);
  81. if (isActive) {
  82. console.log(`[ServerDetector] \u7AEF\u53E3 ${port2} \u53EF\u7528\uFF0C\u521B\u5EFA\u96A7\u9053...`);
  83. portFromOutput = port2;
  84. await this.handleServerFound(port2);
  85. process2.stdout?.removeListener("data", outputHandler);
  86. return;
  87. }
  88. }
  89. }
  90. };
  91. process2.stdout?.on("data", outputHandler);
  92. await new Promise((resolve) => setTimeout(resolve, 500));
  93. if (portFromOutput) {
  94. return;
  95. }
  96. const maxRetries = 5;
  97. const retryDelay = 1e3;
  98. let currentTry = 0;
  99. const checkPorts = async () => {
  100. try {
  101. const ports = await this.getProcessPorts(process2.pid);
  102. console.log(`[ServerDetector] \u8FDB\u7A0B ${process2.pid} \u7684\u7AEF\u53E3\u4FE1\u606F (\u5C1D\u8BD5 ${currentTry + 1}):`, ports);
  103. if (ports.length > 0) {
  104. for (const { port: port2, protocol } of ports) {
  105. if (protocol === "tcp") {
  106. await this.handleServerFound(port2);
  107. return true;
  108. }
  109. }
  110. }
  111. const childPorts = await this.getChildProcessPorts(process2.pid);
  112. console.log(`[ServerDetector] \u5B50\u8FDB\u7A0B\u7AEF\u53E3\u4FE1\u606F (\u5C1D\u8BD5 ${currentTry + 1}):`, childPorts);
  113. if (childPorts.length > 0) {
  114. for (const { port: port2, protocol } of childPorts) {
  115. if (protocol === "tcp") {
  116. await this.handleServerFound(port2);
  117. return true;
  118. }
  119. }
  120. }
  121. return false;
  122. } catch (error) {
  123. console.error("[ServerDetector] \u68C0\u6D4B\u7AEF\u53E3\u5931\u8D25:", error);
  124. return false;
  125. }
  126. };
  127. while (currentTry < maxRetries && !portFromOutput) {
  128. const found = await checkPorts();
  129. if (found) {
  130. process2.stdout?.removeListener("data", outputHandler);
  131. return;
  132. }
  133. currentTry++;
  134. if (currentTry < maxRetries) {
  135. console.log(`[ServerDetector] \u7B49\u5F85 ${retryDelay}ms \u540E\u8FDB\u884C\u7B2C ${currentTry + 1} \u6B21\u5C1D\u8BD5...`);
  136. await new Promise((resolve) => setTimeout(resolve, retryDelay));
  137. }
  138. }
  139. console.log("[ServerDetector] \u7AEF\u53E3\u68C0\u6D4B\u91CD\u8BD5\u7ED3\u675F\uFF0C\u7EE7\u7EED\u76D1\u542C\u8FDB\u7A0B\u8F93\u51FA...");
  140. }
  141. async getChildProcessPorts(parentPid) {
  142. return new Promise((resolve) => {
  143. child_process.exec(`pgrep -P ${parentPid}`, async (error, stdout) => {
  144. if (error) {
  145. console.error("[ServerDetector] pgrep \u6267\u884C\u5931\u8D25:", error);
  146. resolve([]);
  147. return;
  148. }
  149. const childPids = stdout.trim().split("\n").filter(Boolean);
  150. const allPorts = [];
  151. const portPromises = childPids.map(
  152. (childPid) => this.getProcessPorts(parseInt(childPid, 10))
  153. );
  154. try {
  155. const results = await Promise.all(portPromises);
  156. results.forEach((ports) => allPorts.push(...ports));
  157. } catch (error2) {
  158. console.error("[ServerDetector] \u68C0\u67E5\u5B50\u8FDB\u7A0B\u7AEF\u53E3\u5931\u8D25:", error2);
  159. }
  160. resolve(allPorts);
  161. });
  162. });
  163. }
  164. async getProcessPorts(pid) {
  165. return new Promise((resolve) => {
  166. child_process.exec(`lsof -i -P -n -p ${pid} | grep LISTEN`, (error, stdout) => {
  167. if (error) {
  168. resolve([]);
  169. return;
  170. }
  171. const ports = [];
  172. const lines = stdout.split("\n").filter(Boolean);
  173. for (const line of lines) {
  174. const match = line.match(/\s+(\w+):(\d+)\s+\(LISTEN\)$/);
  175. if (match) {
  176. ports.push({
  177. pid,
  178. port: parseInt(match[2], 10),
  179. protocol: match[1].toLowerCase()
  180. });
  181. }
  182. }
  183. resolve(ports);
  184. });
  185. });
  186. }
  187. // 抽取处理找到服务器的逻辑
  188. async handleServerFound(port2) {
  189. try {
  190. const tunnel = await createTunnel__default.default({
  191. port: port2,
  192. host: "https://pre.d8d.fun"
  193. });
  194. this.emit("server-ready", {
  195. port: port2,
  196. url: tunnel.url,
  197. localUrl: `http://localhost:${port2}`
  198. });
  199. } catch (error) {
  200. console.error("Failed to create tunnel:", error);
  201. this.emit("server-ready", {
  202. port: port2,
  203. url: `http://localhost:${port2}`
  204. });
  205. }
  206. this.stop();
  207. }
  208. async checkPort(port2) {
  209. return new Promise((resolve) => {
  210. const tester = net__default.default.createConnection({
  211. port: port2,
  212. host: "localhost",
  213. timeout: 1e3
  214. });
  215. tester.on("connect", () => {
  216. tester.end();
  217. resolve(true);
  218. });
  219. tester.on("error", () => {
  220. resolve(false);
  221. });
  222. tester.on("timeout", () => {
  223. tester.destroy();
  224. resolve(false);
  225. });
  226. });
  227. }
  228. async isProcessFromWorkspace(port2) {
  229. const maxRetries = 3;
  230. const retryDelay = 1e3;
  231. for (let i = 0; i < maxRetries; i++) {
  232. try {
  233. const controller = new AbortController();
  234. const timeoutId = setTimeout(() => controller.abort(), 5e3);
  235. const response = await fetch(`http://localhost:${port2}`, {
  236. signal: controller.signal
  237. });
  238. clearTimeout(timeoutId);
  239. return response.ok;
  240. } catch (error) {
  241. if (i === maxRetries - 1) {
  242. console.log(`[ServerDetector] Failed to check port ${port2} after ${maxRetries} retries`);
  243. return false;
  244. }
  245. await new Promise((resolve) => setTimeout(resolve, retryDelay));
  246. }
  247. }
  248. return false;
  249. }
  250. // 修改 findActualPort 方法
  251. async findActualPort() {
  252. const vitePort = await this.findVitePort();
  253. if (vitePort) {
  254. return vitePort;
  255. }
  256. return new Promise((resolve) => {
  257. child_process.exec(`lsof -i -P -n | grep LISTEN | grep node`, (error, stdout) => {
  258. if (error) {
  259. console.log("[ServerDetector] Error finding actual port:", error);
  260. resolve(null);
  261. return;
  262. }
  263. try {
  264. const lines = stdout.split("\n");
  265. for (const line of lines) {
  266. const match = line.match(/:51[7-9][0-9]/);
  267. if (match) {
  268. const port2 = parseInt(match[0].substring(1));
  269. if (port2 >= 5173 && port2 <= 5179) {
  270. console.log(`[ServerDetector] Found Vite server port from lsof: ${port2}`);
  271. resolve(port2);
  272. return;
  273. }
  274. }
  275. }
  276. resolve(null);
  277. } catch (parseError) {
  278. console.error("[ServerDetector] Error parsing port:", parseError);
  279. resolve(null);
  280. }
  281. });
  282. });
  283. }
  284. // 添加新方法来从 Vite 输出解析端口
  285. async findVitePort() {
  286. try {
  287. for (let port2 = 5173; port2 <= 5179; port2++) {
  288. const isActive = await this.checkPort(port2);
  289. if (isActive) {
  290. try {
  291. const controller = new AbortController();
  292. const timeoutId = setTimeout(() => controller.abort(), 3e3);
  293. const response = await fetch(`http://localhost:${port2}`, {
  294. signal: controller.signal
  295. });
  296. clearTimeout(timeoutId);
  297. const text = await response.text();
  298. if (text.includes("vite") || response.headers.get("server")?.includes("vite")) {
  299. console.log(`[ServerDetector] Found Vite server on port: ${port2}`);
  300. return port2;
  301. }
  302. } catch (error) {
  303. continue;
  304. }
  305. }
  306. }
  307. } catch (error) {
  308. console.error("[ServerDetector] Error in findVitePort:", error);
  309. }
  310. return null;
  311. }
  312. static isDevServerCommand(command, args = []) {
  313. const devCommands = [
  314. ["npm", ["run", "dev"]],
  315. ["npm", ["run", "start"]],
  316. ["npm", ["start"]],
  317. ["yarn", ["dev"]],
  318. ["yarn", ["start"]],
  319. ["pnpm", ["dev"]],
  320. ["pnpm", ["start"]],
  321. ["vite"],
  322. ["next"],
  323. ["nuxt"]
  324. ];
  325. return devCommands.some(([cmd, cmdArgs]) => {
  326. if (!cmdArgs) {
  327. return command === cmd;
  328. }
  329. return command === cmd && JSON.stringify(args) === JSON.stringify(cmdArgs);
  330. });
  331. }
  332. };
  333. // src/lib/constants.ts
  334. var PROCESS_CONFIG = {
  335. // 进程终止超时时间(毫秒)
  336. KILL_TIMEOUT: 5e3,
  337. // 进程清理重试次数
  338. CLEANUP_RETRIES: 3
  339. };
  340. var FS_CONFIG = {
  341. // 文件编码
  342. ENCODING: "utf-8",
  343. // 目录权限
  344. DIR_MODE: 493,
  345. // 文件权限
  346. FILE_MODE: 420
  347. };
  348. var SERVER_CONFIG = {
  349. DEFAULT_PORT: 3e3,
  350. DEFAULT_HOST: "localhost",
  351. DEFAULT_WORKSPACE_DIR: "workspaces",
  352. CORS: {
  353. origin: "*",
  354. methods: ["GET", "POST"]
  355. }
  356. };
  357. var WATCH_CONFIG = {
  358. DEFAULT_IGNORE: [
  359. "**/node_modules/**",
  360. "**/.git/**",
  361. "**/dist/**",
  362. "**/.DS_Store",
  363. "**/thumbs.db"
  364. ],
  365. WATCH_OPTIONS: {
  366. persistent: true,
  367. ignoreInitial: false,
  368. followSymlinks: false,
  369. disableGlobbing: false,
  370. usePolling: false
  371. }
  372. };
  373. var WebContainerServer = class {
  374. constructor(config = {}) {
  375. this.workspaces = /* @__PURE__ */ new Map();
  376. this.processes = /* @__PURE__ */ new Map();
  377. this.processHistory = /* @__PURE__ */ new Map();
  378. this.watchers = /* @__PURE__ */ new Map();
  379. this.config = {
  380. port: config.port || SERVER_CONFIG.DEFAULT_PORT,
  381. host: config.host || SERVER_CONFIG.DEFAULT_HOST,
  382. workspaceRoot: config.workspaceRoot || path__default.default.join(process.cwd(), SERVER_CONFIG.DEFAULT_WORKSPACE_DIR)
  383. };
  384. this.httpServer = http.createServer();
  385. this.io = new socket_io.Server(this.httpServer, {
  386. cors: SERVER_CONFIG.CORS
  387. });
  388. this.setupSocketHandlers();
  389. }
  390. async start() {
  391. try {
  392. await fs__default.default.mkdir(this.config.workspaceRoot, { recursive: true });
  393. const requiredCommands = ["git", "npm", "node"];
  394. for (const cmd of requiredCommands) {
  395. const exists = await this.checkCommandExists(cmd);
  396. if (!exists) {
  397. console.warn(`\u8B66\u544A: \u547D\u4EE4 '${cmd}' \u672A\u5B89\u88C5\uFF0C\u8FD9\u53EF\u80FD\u4F1A\u5F71\u54CD\u67D0\u4E9B\u529F\u80FD\u7684\u4F7F\u7528`);
  398. }
  399. }
  400. return new Promise((resolve) => {
  401. this.httpServer.listen(this.config.port, this.config.host, () => {
  402. console.log(
  403. `WebContainer Server is running on http://${this.config.host}:${this.config.port}`
  404. );
  405. console.log(`Workspace root: ${this.config.workspaceRoot}`);
  406. resolve();
  407. });
  408. });
  409. } catch (error) {
  410. console.error("Failed to start server:", error);
  411. process.exit(1);
  412. }
  413. }
  414. setupSocketHandlers() {
  415. this.io.on("connection", async (socket) => {
  416. const workspaceId = Math.random().toString(36).substr(2, 9);
  417. const workspacePath = path__default.default.join(this.config.workspaceRoot, workspaceId);
  418. this.workspaces.set(socket.id, workspacePath);
  419. this.processes.set(socket.id, /* @__PURE__ */ new Map());
  420. console.log(`Client connected. Workspace created at: ${workspacePath}`);
  421. try {
  422. await fs__default.default.mkdir(workspacePath, { recursive: true });
  423. } catch (error) {
  424. console.error("Failed to configure NPM mirrors:", error);
  425. }
  426. socket.on(
  427. "mount",
  428. async (files, callback) => {
  429. try {
  430. await this.handleMount(workspacePath, files);
  431. callback({ success: true });
  432. } catch (error) {
  433. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  434. callback({ error: errorMessage });
  435. }
  436. }
  437. );
  438. socket.on(
  439. "spawn",
  440. async ({ command, args = [], terminalId = "default" }, callback) => {
  441. try {
  442. const result = await this.handleSpawn(
  443. socket.id,
  444. workspacePath,
  445. command,
  446. args,
  447. terminalId
  448. );
  449. callback({ result });
  450. } catch (error) {
  451. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  452. callback({ error: errorMessage });
  453. }
  454. }
  455. );
  456. socket.on("fs:list", async (path3, callback) => {
  457. try {
  458. const result = await this.handleList(workspacePath, path3);
  459. callback({ success: true, result });
  460. } catch (error) {
  461. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  462. callback({ error: errorMessage });
  463. }
  464. });
  465. socket.on("fs:read", async (path3, callback) => {
  466. try {
  467. const result = await this.handleRead(workspacePath, path3);
  468. callback({ success: true, result });
  469. } catch (error) {
  470. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  471. callback({ error: errorMessage });
  472. }
  473. });
  474. socket.on(
  475. "fs:write",
  476. async (data, callback) => {
  477. try {
  478. await this.handleWrite(workspacePath, data.path, data.content);
  479. callback({ success: true });
  480. } catch (error) {
  481. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  482. callback({ error: errorMessage });
  483. }
  484. }
  485. );
  486. socket.on("fs:delete", async (path3, callback) => {
  487. try {
  488. await this.handleDelete(workspacePath, path3);
  489. callback({ success: true });
  490. } catch (error) {
  491. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  492. callback({ error: errorMessage });
  493. }
  494. });
  495. socket.on("fs:mkdir", async (path3, callback) => {
  496. try {
  497. await this.handleMkdir(workspacePath, path3);
  498. callback({ success: true });
  499. } catch (error) {
  500. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  501. callback({ error: errorMessage });
  502. }
  503. });
  504. socket.on(
  505. "fs:watch",
  506. async ({ glob, ignore = WATCH_CONFIG.DEFAULT_IGNORE, ignoreInitial }, callback = () => {
  507. }) => {
  508. try {
  509. console.log(`[Watch] Socket ${socket.id} \u5F00\u59CB\u76D1\u542C\u6587\u4EF6: ${glob}`);
  510. console.log(`[Watch] \u5FFD\u7565\u7684\u6587\u4EF6\u6A21\u5F0F:`, ignore);
  511. console.log(`[Watch] ignoreInitial:`, ignoreInitial);
  512. const watchPatterns = [glob, `${glob}/*`, `${glob}/**/*`];
  513. const watcher = chokidar__default.default.watch(watchPatterns, {
  514. cwd: workspacePath,
  515. ...WATCH_CONFIG.WATCH_OPTIONS,
  516. ...ignoreInitial !== void 0 ? { ignoreInitial } : {},
  517. ignored: (path3) => {
  518. const shouldIgnore = anymatch__default.default(ignore, path3);
  519. if (shouldIgnore) {
  520. console.log(`[Watch] \u5FFD\u7565\u8DEF\u5F84: ${path3}`);
  521. }
  522. return shouldIgnore;
  523. }
  524. });
  525. watcher.on("all", async (event, path3) => {
  526. const fullPath = `${workspacePath}/${path3}`;
  527. let stats = null;
  528. try {
  529. stats = await fs$1.promises.stat(fullPath).catch(() => null);
  530. } catch (error) {
  531. }
  532. socket.emit("fs:change", {
  533. type: event === "add" || event === "addDir" ? "create" : event === "change" ? "update" : event === "unlink" ? "delete" : event,
  534. path: path3,
  535. kind: stats?.isDirectory() ? "directory" : "file"
  536. });
  537. });
  538. watcher.on("error", (error) => {
  539. console.error(`[Watch] \u76D1\u542C\u9519\u8BEF:`, error);
  540. });
  541. watcher.on("ready", () => {
  542. console.log(`[Watch] \u6587\u4EF6\u76D1\u542C\u5668\u5DF2\u5C31\u7EEA`);
  543. });
  544. this.watchers.set(socket.id, watcher);
  545. if (typeof callback === "function") {
  546. callback({ success: true });
  547. }
  548. } catch (error) {
  549. console.error(`[Watch] \u8BBE\u7F6E\u76D1\u542C\u5931\u8D25:`, error);
  550. const errorMessage = error instanceof Error ? error.message : "\u8BBE\u7F6E\u76D1\u542C\u5931\u8D25";
  551. if (typeof callback === "function") {
  552. callback({ error: errorMessage });
  553. }
  554. }
  555. }
  556. );
  557. socket.on(
  558. "fs:unwatch",
  559. async ({ glob }, callback = () => {
  560. }) => {
  561. try {
  562. const watcher = this.watchers.get(socket.id);
  563. if (watcher) {
  564. await watcher.close();
  565. this.watchers.delete(socket.id);
  566. }
  567. if (typeof callback === "function") {
  568. callback({ success: true });
  569. }
  570. } catch (error) {
  571. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  572. if (typeof callback === "function") {
  573. callback({ error: errorMessage });
  574. }
  575. }
  576. }
  577. );
  578. socket.on("disconnect", async () => {
  579. console.log(`\u5BA2\u6237\u7AEF\u65AD\u5F00\u8FDE\u63A5. \u5DE5\u4F5C\u533A: ${workspacePath}`);
  580. try {
  581. const watcher = this.watchers.get(socket.id);
  582. if (watcher) {
  583. await watcher.close();
  584. this.watchers.delete(socket.id);
  585. console.log(`\u6587\u4EF6\u76D1\u542C\u5668\u5DF2\u6E05\u7406: ${socket.id}`);
  586. }
  587. console.log(`\u6B63\u5728\u7EC8\u6B62\u5DE5\u4F5C\u533A\u8FDB\u7A0B: ${workspacePath}`);
  588. await this.killAllProcesses(socket.id);
  589. console.log(`\u6B63\u5728\u6E05\u7406\u5DE5\u4F5C\u533A\u76EE\u5F55: ${workspacePath}`);
  590. const exists = await fs__default.default.access(workspacePath).then(() => true).catch(() => false);
  591. if (exists) {
  592. await fs__default.default.rm(workspacePath, { recursive: true, force: true });
  593. console.log(`\u5DE5\u4F5C\u533A\u76EE\u5F55\u5DF2\u6E05\u7406: ${workspacePath}`);
  594. } else {
  595. console.log(`\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u6E05\u7406: ${workspacePath}`);
  596. }
  597. this.workspaces.delete(socket.id);
  598. this.processes.delete(socket.id);
  599. console.log(`\u5BA2\u6237\u7AEF\u8D44\u6E90\u6E05\u7406\u5B8C\u6210: ${socket.id}`);
  600. } catch (error) {
  601. console.error(`\u5BA2\u6237\u7AEF\u6E05\u7406\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:`, error);
  602. }
  603. });
  604. socket.on(
  605. "process:kill",
  606. async ({ pid, terminalId = "default" }, callback) => {
  607. try {
  608. await this.killProcess(socket.id, pid, terminalId);
  609. callback({ success: true });
  610. } catch (error) {
  611. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  612. callback({ error: errorMessage });
  613. }
  614. }
  615. );
  616. });
  617. }
  618. async killAllProcesses(socketId) {
  619. const processMap = this.processes.get(socketId);
  620. if (!processMap) {
  621. console.log(`\u6CA1\u6709\u627E\u5230 socket ${socketId} \u7684\u8FDB\u7A0B\u8BB0\u5F55`);
  622. return;
  623. }
  624. await Promise.all(
  625. Array.from(processMap.entries()).map(async ([terminalId, process2]) => {
  626. try {
  627. if (!process2.pid) {
  628. console.log(`\u8FDB\u7A0B (\u7EC8\u7AEF ${terminalId}) \u6CA1\u6709\u6709\u6548\u7684 PID`);
  629. return;
  630. }
  631. await new Promise((resolve, reject) => {
  632. const pid = process2.pid;
  633. const pkill = child_process.spawn("pkill", ["-TERM", "-P", pid.toString()]);
  634. pkill.on("close", async () => {
  635. try {
  636. process2.kill("SIGINT");
  637. console.log(
  638. `\u8FDB\u7A0B ${pid} (\u7EC8\u7AEF ${terminalId}) \u5DF2\u53D1\u9001 SIGINT \u4FE1\u53F7`
  639. );
  640. await new Promise((resolveWait) => {
  641. const timeout = setTimeout(() => {
  642. try {
  643. child_process.spawn("pkill", ["-KILL", "-P", pid.toString()]);
  644. process2.kill("SIGKILL");
  645. console.log(
  646. `\u8FDB\u7A0B ${pid} (\u7EC8\u7AEF ${terminalId}) \u5DF2\u5F3A\u5236\u7EC8\u6B62`
  647. );
  648. } catch (error) {
  649. console.error(
  650. `\u5F3A\u5236\u7EC8\u6B62\u8FDB\u7A0B ${pid} (\u7EC8\u7AEF ${terminalId}) \u5931\u8D25:`,
  651. error
  652. );
  653. }
  654. resolveWait();
  655. }, PROCESS_CONFIG.KILL_TIMEOUT);
  656. process2.once("exit", (code) => {
  657. clearTimeout(timeout);
  658. console.log(
  659. `\u8FDB\u7A0B ${pid} (\u7EC8\u7AEF ${terminalId}) \u5DF2\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}`
  660. );
  661. resolveWait();
  662. });
  663. });
  664. resolve();
  665. } catch (error) {
  666. reject(error);
  667. }
  668. });
  669. });
  670. } catch (error) {
  671. if (error instanceof Error && error.message.includes("ESRCH")) {
  672. console.log(`\u8FDB\u7A0B ${process2.pid} (\u7EC8\u7AEF ${terminalId}) \u5DF2\u4E0D\u5B58\u5728`);
  673. } else {
  674. console.error(
  675. `\u7EC8\u6B62\u8FDB\u7A0B ${process2.pid} (\u7EC8\u7AEF ${terminalId}) \u65F6\u53D1\u751F\u9519\u8BEF:`,
  676. error
  677. );
  678. }
  679. }
  680. })
  681. );
  682. processMap.clear();
  683. this.processHistory.set(socketId, []);
  684. console.log(`Socket ${socketId} \u7684\u6240\u6709\u8FDB\u7A0B\u5DF2\u6E05\u7406\u5B8C\u6210`);
  685. }
  686. async handleMount(workspacePath, files) {
  687. await fs__default.default.mkdir(workspacePath, { recursive: true });
  688. await this.createFiles(workspacePath, files);
  689. }
  690. async createFiles(basePath, files) {
  691. for (const [name, item] of Object.entries(files)) {
  692. const fullPath = path__default.default.join(basePath, name);
  693. if ("file" in item) {
  694. await fs__default.default.writeFile(fullPath, item.file.contents);
  695. } else if ("directory" in item) {
  696. await fs__default.default.mkdir(fullPath, { recursive: true });
  697. await this.createFiles(fullPath, item.directory.contents);
  698. }
  699. }
  700. }
  701. async checkCommandExists(command) {
  702. try {
  703. await new Promise((resolve, reject) => {
  704. const check = child_process.spawn("which", [command]);
  705. check.on("close", (code) => {
  706. if (code === 0) {
  707. resolve();
  708. } else {
  709. reject();
  710. }
  711. });
  712. });
  713. return true;
  714. } catch {
  715. return false;
  716. }
  717. }
  718. async handleSpawn(socketId, workspacePath, command, args, terminalId) {
  719. const processes = this.processes.get(socketId);
  720. if (!processes) {
  721. throw new Error("No process map found for socket");
  722. }
  723. const commandExists = await this.checkCommandExists(command);
  724. if (!commandExists) {
  725. 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`);
  726. }
  727. return new Promise((resolve, reject) => {
  728. const process2 = child_process.spawn(command, args, {
  729. cwd: workspacePath,
  730. stdio: ["pipe", "pipe", "pipe"],
  731. shell: true
  732. });
  733. if (!process2.pid) {
  734. reject(new Error("Failed to get process ID"));
  735. return;
  736. }
  737. const processInfo = {
  738. pid: process2.pid,
  739. command,
  740. args,
  741. timestamp: Date.now()
  742. };
  743. const history = this.processHistory.get(socketId) || [];
  744. history.push(processInfo);
  745. this.processHistory.set(socketId, history);
  746. this.io.to(socketId).emit("process:started", { ...processInfo, terminalId });
  747. if (ServerDetector.isDevServerCommand(command, args)) {
  748. console.log(
  749. `[ServerDetector] \u7B49\u5F85\u5F00\u53D1\u670D\u52A1\u5668\u542F\u52A8: ${command} ${args.join(" ")}`
  750. );
  751. const detector = new ServerDetector({ workspacePath });
  752. detector.on("server-ready", (event) => {
  753. console.log(`[ServerDetector] \u68C0\u6D4B\u5230\u670D\u52A1\u5668\u5C31\u7EEA:`, event);
  754. this.io.to(socketId).emit("server:ready", {
  755. ...event,
  756. command,
  757. args
  758. });
  759. });
  760. let detectionStarted = false;
  761. process2.stdout.on("data", () => {
  762. if (!detectionStarted) {
  763. detectionStarted = true;
  764. console.log(`[ServerDetector] \u68C0\u6D4B\u5230\u8FDB\u7A0B\u8F93\u51FA\uFF0C\u5F00\u59CB\u7AEF\u53E3\u68C0\u6D4B...`);
  765. detector.detectServerByPid(process2);
  766. }
  767. });
  768. }
  769. process2.on("SIGINT", () => {
  770. console.log(`Process ${process2.pid} received SIGINT`);
  771. process2.kill("SIGINT");
  772. });
  773. process2.stdout.on("data", (data) => {
  774. const output = data.toString();
  775. console.log(`[${command}] Process ${process2.pid} stdout:`, output);
  776. this.io.to(socketId).emit("process:output", {
  777. type: "stdout",
  778. data: output,
  779. terminalId
  780. });
  781. });
  782. process2.stderr.on("data", (data) => {
  783. const output = data.toString();
  784. console.error(`[${command}] stderr:`, output);
  785. this.io.to(socketId).emit("process:output", {
  786. type: "stderr",
  787. data: output,
  788. terminalId
  789. });
  790. });
  791. process2.on("close", (code) => {
  792. const processHistory = this.processHistory.get(socketId) || [];
  793. const historyIndex = processHistory.findIndex(
  794. (p) => p.pid === process2.pid
  795. );
  796. if (historyIndex > -1) {
  797. processHistory.splice(historyIndex, 1);
  798. }
  799. this.processHistory.set(socketId, processHistory);
  800. this.io.to(socketId).emit("process:ended", { pid: process2.pid, code });
  801. if (process2.pid) {
  802. processes.delete(terminalId);
  803. }
  804. if (code !== 0) {
  805. this.io.to(socketId).emit("process:output", {
  806. type: "exit",
  807. data: `Process exited with code ${code}`,
  808. terminalId
  809. });
  810. }
  811. if (code === 0 && process2.pid) {
  812. resolve({ pid: process2.pid });
  813. } else {
  814. reject(new Error(`Process exited with code ${code}`));
  815. }
  816. });
  817. process2.on("error", (error) => {
  818. processes.delete(terminalId);
  819. this.io.to(socketId).emit("process:output", {
  820. type: "stderr",
  821. data: error.message,
  822. terminalId
  823. });
  824. reject(error);
  825. });
  826. processes.set(terminalId, process2);
  827. });
  828. }
  829. async handleList(workspacePath, relativePath) {
  830. const fullPath = path__default.default.join(workspacePath, relativePath);
  831. const entries = await fs__default.default.readdir(fullPath, { withFileTypes: true });
  832. return entries.map((entry) => ({
  833. name: entry.name,
  834. type: entry.isDirectory() ? "directory" : "file"
  835. }));
  836. }
  837. async handleRead(workspacePath, relativePath) {
  838. const fullPath = path__default.default.join(workspacePath, relativePath);
  839. return fs__default.default.readFile(fullPath, "utf-8");
  840. }
  841. async handleWrite(workspacePath, relativePath, content) {
  842. const fullPath = path__default.default.join(workspacePath, relativePath);
  843. await fs__default.default.mkdir(path__default.default.dirname(fullPath), {
  844. recursive: true,
  845. mode: FS_CONFIG.DIR_MODE
  846. });
  847. await fs__default.default.writeFile(fullPath, content, {
  848. encoding: FS_CONFIG.ENCODING,
  849. mode: FS_CONFIG.FILE_MODE
  850. });
  851. }
  852. async handleDelete(workspacePath, relativePath) {
  853. const fullPath = path__default.default.join(workspacePath, relativePath);
  854. await fs__default.default.rm(fullPath, { recursive: true, force: true });
  855. }
  856. async handleMkdir(workspacePath, relativePath) {
  857. const fullPath = path__default.default.join(workspacePath, relativePath);
  858. await fs__default.default.mkdir(fullPath, { recursive: true });
  859. }
  860. stop() {
  861. return new Promise(async (resolve) => {
  862. console.log("\u6B63\u5728\u505C\u6B62 WebContainer Server...");
  863. for (const [socketId, watcher] of this.watchers.entries()) {
  864. try {
  865. await watcher.close();
  866. this.watchers.delete(socketId);
  867. console.log(`\u6587\u4EF6\u76D1\u542C\u5668\u5DF2\u6E05\u7406: ${socketId}`);
  868. } catch (error) {
  869. console.error(`\u6E05\u7406\u6587\u4EF6\u76D1\u542C\u5668\u5931\u8D25 ${socketId}:`, error);
  870. }
  871. }
  872. for (const socketId of this.processes.keys()) {
  873. this.killAllProcesses(socketId);
  874. }
  875. console.log("\u6B63\u5728\u6E05\u7406\u5DE5\u4F5C\u533A\u76EE\u5F55...");
  876. const workspaces = Array.from(this.workspaces.values());
  877. console.log(`\u9700\u8981\u6E05\u7406\u7684\u5DE5\u4F5C\u533A: ${workspaces.join(", ")}`);
  878. try {
  879. const rootExists = await fs__default.default.access(this.config.workspaceRoot).then(() => true).catch(() => false);
  880. if (rootExists) {
  881. await Promise.all(
  882. workspaces.map(async (workspacePath) => {
  883. try {
  884. const exists = await fs__default.default.access(workspacePath).then(() => true).catch(() => false);
  885. if (exists) {
  886. console.log(`\u6B63\u5728\u6E05\u7406\u5DE5\u4F5C\u533A: ${workspacePath}`);
  887. await fs__default.default.rm(workspacePath, { recursive: true, force: true });
  888. console.log(`\u5DE5\u4F5C\u533A\u5DF2\u6E05\u7406: ${workspacePath}`);
  889. } else {
  890. console.log(`\u5DE5\u4F5C\u533A\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u6E05\u7406: ${workspacePath}`);
  891. }
  892. } catch (error) {
  893. console.error(`\u6E05\u7406\u5DE5\u4F5C\u533A\u5931\u8D25 ${workspacePath}:`, error);
  894. }
  895. })
  896. );
  897. try {
  898. const rootContents = await fs__default.default.readdir(this.config.workspaceRoot);
  899. if (rootContents.length === 0) {
  900. await fs__default.default.rm(this.config.workspaceRoot, {
  901. recursive: true,
  902. force: true
  903. });
  904. console.log(`\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\u5DF2\u6E05\u7406: ${this.config.workspaceRoot}`);
  905. } else {
  906. console.log(
  907. `\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\u975E\u7A7A\uFF0C\u4FDD\u7559: ${this.config.workspaceRoot}`
  908. );
  909. }
  910. } catch (error) {
  911. console.error("\u6E05\u7406\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\u5931\u8D25:", error);
  912. }
  913. } else {
  914. console.log(`\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\u4E0D\u5B58\u5728: ${this.config.workspaceRoot}`);
  915. }
  916. } catch (error) {
  917. console.error("\u6E05\u7406\u5DE5\u4F5C\u533A\u65F6\u53D1\u751F\u9519\u8BEF:", error);
  918. }
  919. this.io.close(() => {
  920. console.log("Socket.IO \u670D\u52A1\u5668\u5DF2\u5173\u95ED");
  921. this.httpServer.close(() => {
  922. console.log("HTTP \u670D\u52A1\u5668\u5DF2\u5173\u95ED");
  923. this.workspaces.clear();
  924. this.processes.clear();
  925. console.log("\u5185\u5B58\u4E2D\u7684\u5DE5\u4F5C\u533A\u8BB0\u5F55\u5DF2\u6E05\u7406");
  926. resolve();
  927. });
  928. });
  929. });
  930. }
  931. async killProcess(socketId, pid, terminalId) {
  932. const processes = this.processes.get(socketId);
  933. if (!processes) {
  934. throw new Error("No processes found for socket");
  935. }
  936. const process2 = processes.get(terminalId);
  937. if (!process2) {
  938. console.warn(`No process found for terminal ${terminalId}`);
  939. return;
  940. }
  941. try {
  942. await new Promise((resolve, reject) => {
  943. const pkill = child_process.spawn("pkill", ["-TERM", "-P", pid.toString()]);
  944. pkill.on("close", async () => {
  945. try {
  946. process2.kill("SIGINT");
  947. console.log(`\u8FDB\u7A0B ${pid} \u5DF2\u53D1\u9001 SIGINT \u4FE1\u53F7`);
  948. await new Promise((resolveWait) => {
  949. const timeout = setTimeout(() => {
  950. try {
  951. child_process.spawn("pkill", ["-KILL", "-P", pid.toString()]);
  952. process2.kill("SIGKILL");
  953. console.log(`\u8FDB\u7A0B ${pid} \u5DF2\u5F3A\u5236\u7EC8\u6B62`);
  954. } catch (error) {
  955. console.error(`\u5F3A\u5236\u7EC8\u6B62\u8FDB\u7A0B ${pid} \u5931\u8D25:`, error);
  956. }
  957. resolveWait();
  958. }, 5e3);
  959. process2.once("exit", (code) => {
  960. clearTimeout(timeout);
  961. console.log(`\u8FDB\u7A0B ${pid} \u5DF2\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}`);
  962. resolveWait();
  963. });
  964. });
  965. resolve();
  966. } catch (error) {
  967. reject(error);
  968. }
  969. });
  970. });
  971. processes.delete(terminalId);
  972. const history = this.processHistory.get(socketId) || [];
  973. const historyIndex = history.findIndex((p) => p.pid === pid);
  974. if (historyIndex > -1) {
  975. history.splice(historyIndex, 1);
  976. }
  977. this.processHistory.set(socketId, history);
  978. this.io.to(socketId).emit("process:ended", { pid, code: 0 });
  979. } catch (error) {
  980. console.error(`\u7EC8\u6B62\u8FDB\u7A0B ${pid} \u5931\u8D25:`, error);
  981. throw error;
  982. }
  983. }
  984. };
  985. var port = parseInt(process.env.PORT || "3000", 10);
  986. var host = process.env.HOST || "localhost";
  987. var workspaceRoot = process.env.WORKSPACE_ROOT || path__default.default.join(process.cwd(), "workspaces");
  988. var server = new WebContainerServer({
  989. port,
  990. host,
  991. workspaceRoot
  992. });
  993. var isShuttingDown = false;
  994. server.start();
  995. async function handleShutdown(signal) {
  996. if (isShuttingDown) {
  997. return;
  998. }
  999. isShuttingDown = true;
  1000. console.log(`
  1001. \u6536\u5230 ${signal} \u4FE1\u53F7\uFF0C\u6B63\u5728\u4F18\u96C5\u9000\u51FA...`);
  1002. try {
  1003. await server.stop();
  1004. console.log("\u670D\u52A1\u5668\u5DF2\u6210\u529F\u505C\u6B62");
  1005. setImmediate(() => process.exit(0));
  1006. } catch (error) {
  1007. console.error("\u670D\u52A1\u5668\u505C\u6B62\u65F6\u53D1\u751F\u9519\u8BEF:", error);
  1008. setImmediate(() => process.exit(1));
  1009. }
  1010. }
  1011. process.on("SIGTERM", () => handleShutdown("SIGTERM"));
  1012. process.on("SIGINT", () => handleShutdown("SIGINT"));
  1013. process.on("uncaughtException", (error) => {
  1014. console.error("\u672A\u6355\u83B7\u7684\u5F02\u5E38:", error);
  1015. handleShutdown("uncaughtException");
  1016. });
  1017. process.on("unhandledRejection", (reason) => {
  1018. console.error("\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:", reason);
  1019. handleShutdown("unhandledRejection");
  1020. });