chunk-Y6ZWNQMZ.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. // src/index.ts
  2. import { Server } from "socket.io";
  3. import { spawn } from "child_process";
  4. import fs from "fs/promises";
  5. import path from "path";
  6. var WebContainerServer = class {
  7. constructor(config = {}) {
  8. this.workspaces = /* @__PURE__ */ new Map();
  9. this.processes = /* @__PURE__ */ new Map();
  10. this.config = {
  11. port: config.port || 3e3,
  12. host: config.host || "localhost",
  13. workspaceRoot: config.workspaceRoot || path.join(process.cwd(), "workspaces")
  14. };
  15. this.io = new Server({
  16. cors: {
  17. origin: "*",
  18. methods: ["GET", "POST"]
  19. }
  20. });
  21. this.setupSocketHandlers();
  22. }
  23. start() {
  24. fs.mkdir(this.config.workspaceRoot, { recursive: true }).then(() => {
  25. this.io.listen(this.config.port);
  26. console.log(`WebContainer Server is running on http://${this.config.host}:${this.config.port}`);
  27. console.log(`Workspace root: ${this.config.workspaceRoot}`);
  28. }).catch((error) => {
  29. console.error("Failed to create workspace directory:", error);
  30. process.exit(1);
  31. });
  32. return this;
  33. }
  34. setupSocketHandlers() {
  35. this.io.on("connection", (socket) => {
  36. const workspaceId = Math.random().toString(36).substr(2, 9);
  37. const workspacePath = path.join(this.config.workspaceRoot, workspaceId);
  38. this.workspaces.set(socket.id, workspacePath);
  39. this.processes.set(socket.id, []);
  40. console.log(`Client connected. Workspace created at: ${workspacePath}`);
  41. socket.on("mount", async (files, callback) => {
  42. try {
  43. await this.handleMount(workspacePath, files);
  44. callback({ success: true });
  45. } catch (error) {
  46. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  47. callback({ error: errorMessage });
  48. }
  49. });
  50. socket.on("spawn", async (data, callback) => {
  51. try {
  52. const result = await this.handleSpawn(socket.id, workspacePath, data.command, data.args);
  53. callback({ success: true, result });
  54. } catch (error) {
  55. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  56. callback({ error: errorMessage });
  57. }
  58. });
  59. socket.on("fs:list", async (path2, callback) => {
  60. try {
  61. const result = await this.handleList(workspacePath, path2);
  62. callback({ success: true, result });
  63. } catch (error) {
  64. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  65. callback({ error: errorMessage });
  66. }
  67. });
  68. socket.on("fs:read", async (path2, callback) => {
  69. try {
  70. const result = await this.handleRead(workspacePath, path2);
  71. callback({ success: true, result });
  72. } catch (error) {
  73. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  74. callback({ error: errorMessage });
  75. }
  76. });
  77. socket.on("fs:write", async (data, callback) => {
  78. try {
  79. await this.handleWrite(workspacePath, data.path, data.content);
  80. callback({ success: true });
  81. } catch (error) {
  82. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  83. callback({ error: errorMessage });
  84. }
  85. });
  86. socket.on("fs:delete", async (path2, callback) => {
  87. try {
  88. await this.handleDelete(workspacePath, path2);
  89. callback({ success: true });
  90. } catch (error) {
  91. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  92. callback({ error: errorMessage });
  93. }
  94. });
  95. socket.on("fs:mkdir", async (path2, callback) => {
  96. try {
  97. await this.handleMkdir(workspacePath, path2);
  98. callback({ success: true });
  99. } catch (error) {
  100. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  101. callback({ error: errorMessage });
  102. }
  103. });
  104. socket.on("disconnect", () => {
  105. console.log(`Client disconnected. Cleaning up workspace: ${workspacePath}`);
  106. this.killAllProcesses(socket.id);
  107. fs.rm(workspacePath, { recursive: true, force: true }).catch((error) => console.error(`Failed to cleanup workspace: ${error}`));
  108. this.workspaces.delete(socket.id);
  109. this.processes.delete(socket.id);
  110. });
  111. });
  112. }
  113. killAllProcesses(socketId) {
  114. const processes = this.processes.get(socketId) || [];
  115. for (const process2 of processes) {
  116. try {
  117. process2.kill("SIGTERM");
  118. console.log(`Process ${process2.pid} terminated`);
  119. } catch (error) {
  120. console.error(`Failed to kill process ${process2.pid}:`, error);
  121. }
  122. }
  123. }
  124. async handleMount(workspacePath, files) {
  125. await fs.mkdir(workspacePath, { recursive: true });
  126. await this.createFiles(workspacePath, files);
  127. }
  128. async createFiles(basePath, files) {
  129. for (const [name, item] of Object.entries(files)) {
  130. const fullPath = path.join(basePath, name);
  131. if ("file" in item) {
  132. await fs.writeFile(fullPath, item.file.contents);
  133. } else if ("directory" in item) {
  134. await fs.mkdir(fullPath, { recursive: true });
  135. await this.createFiles(fullPath, item.directory.contents);
  136. }
  137. }
  138. }
  139. async handleSpawn(socketId, workspacePath, command, args = []) {
  140. return new Promise((resolve, reject) => {
  141. this.io.to(socketId).emit("process:output", {
  142. type: "stdout",
  143. data: `\r
  144. $ ${command} ${args.join(" ")}\r
  145. `
  146. });
  147. const process2 = spawn(command, args, {
  148. cwd: workspacePath,
  149. stdio: ["pipe", "pipe", "pipe"]
  150. });
  151. const processes = this.processes.get(socketId) || [];
  152. processes.push(process2);
  153. this.processes.set(socketId, processes);
  154. let stdout = "";
  155. let stderr = "";
  156. process2.stdout.on("data", (data) => {
  157. const output = data.toString();
  158. stdout += output;
  159. console.log(`[${command}] stdout:`, output);
  160. this.io.to(socketId).emit("process:output", {
  161. type: "stdout",
  162. data: output
  163. });
  164. });
  165. process2.stderr.on("data", (data) => {
  166. const output = data.toString();
  167. stderr += output;
  168. console.error(`[${command}] stderr:`, output);
  169. this.io.to(socketId).emit("process:output", {
  170. type: "stderr",
  171. data: output
  172. });
  173. });
  174. process2.on("close", (code) => {
  175. const processes2 = this.processes.get(socketId) || [];
  176. const index = processes2.indexOf(process2);
  177. if (index > -1) {
  178. processes2.splice(index, 1);
  179. }
  180. this.io.to(socketId).emit("process:output", {
  181. type: "exit",
  182. data: `\r
  183. Process exited with code ${code}\r
  184. $ `
  185. });
  186. if (code === 0) {
  187. resolve({ code, stdout, stderr });
  188. } else {
  189. reject(new Error(`Process exited with code ${code}
  190. ${stderr}`));
  191. }
  192. });
  193. process2.on("error", (error) => {
  194. const processes2 = this.processes.get(socketId) || [];
  195. const index = processes2.indexOf(process2);
  196. if (index > -1) {
  197. processes2.splice(index, 1);
  198. }
  199. this.io.to(socketId).emit("process:output", {
  200. type: "stderr",
  201. data: `\r
  202. Error: ${error.message}\r
  203. $ `
  204. });
  205. reject(error);
  206. });
  207. });
  208. }
  209. async handleList(workspacePath, relativePath) {
  210. const fullPath = path.join(workspacePath, relativePath);
  211. const entries = await fs.readdir(fullPath, { withFileTypes: true });
  212. return entries.map((entry) => ({
  213. name: entry.name,
  214. type: entry.isDirectory() ? "directory" : "file"
  215. }));
  216. }
  217. async handleRead(workspacePath, relativePath) {
  218. const fullPath = path.join(workspacePath, relativePath);
  219. return fs.readFile(fullPath, "utf-8");
  220. }
  221. async handleWrite(workspacePath, relativePath, content) {
  222. const fullPath = path.join(workspacePath, relativePath);
  223. await fs.mkdir(path.dirname(fullPath), { recursive: true });
  224. await fs.writeFile(fullPath, content);
  225. }
  226. async handleDelete(workspacePath, relativePath) {
  227. const fullPath = path.join(workspacePath, relativePath);
  228. await fs.rm(fullPath, { recursive: true, force: true });
  229. }
  230. async handleMkdir(workspacePath, relativePath) {
  231. const fullPath = path.join(workspacePath, relativePath);
  232. await fs.mkdir(fullPath, { recursive: true });
  233. }
  234. };
  235. export {
  236. WebContainerServer
  237. };