start.cjs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. #!/usr/bin/env node
  2. "use strict";
  3. var __create = Object.create;
  4. var __defProp = Object.defineProperty;
  5. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  6. var __getOwnPropNames = Object.getOwnPropertyNames;
  7. var __getProtoOf = Object.getPrototypeOf;
  8. var __hasOwnProp = Object.prototype.hasOwnProperty;
  9. var __copyProps = (to, from, except, desc) => {
  10. if (from && typeof from === "object" || typeof from === "function") {
  11. for (let key of __getOwnPropNames(from))
  12. if (!__hasOwnProp.call(to, key) && key !== except)
  13. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  14. }
  15. return to;
  16. };
  17. var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  18. // If the importer is in node compatibility mode or this is not an ESM
  19. // file that has been converted to a CommonJS file using a Babel-
  20. // compatible transform (i.e. "__esModule" has not been set), then set
  21. // "default" to the CommonJS "module.exports" for node compatibility.
  22. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  23. mod
  24. ));
  25. // src/index.ts
  26. var import_socket = require("socket.io");
  27. var import_child_process = require("child_process");
  28. var import_promises = __toESM(require("fs/promises"), 1);
  29. var import_path = __toESM(require("path"), 1);
  30. var WebContainerServer = class {
  31. constructor(config = {}) {
  32. this.workspaces = /* @__PURE__ */ new Map();
  33. this.processes = /* @__PURE__ */ new Map();
  34. this.config = {
  35. port: config.port || 3e3,
  36. host: config.host || "localhost",
  37. workspaceRoot: config.workspaceRoot || import_path.default.join(process.cwd(), "workspaces")
  38. };
  39. this.io = new import_socket.Server({
  40. cors: {
  41. origin: "*",
  42. methods: ["GET", "POST"]
  43. }
  44. });
  45. this.setupSocketHandlers();
  46. }
  47. start() {
  48. import_promises.default.mkdir(this.config.workspaceRoot, { recursive: true }).then(() => {
  49. this.io.listen(this.config.port);
  50. console.log(`WebContainer Server is running on http://${this.config.host}:${this.config.port}`);
  51. console.log(`Workspace root: ${this.config.workspaceRoot}`);
  52. }).catch((error) => {
  53. console.error("Failed to create workspace directory:", error);
  54. process.exit(1);
  55. });
  56. return this;
  57. }
  58. setupSocketHandlers() {
  59. this.io.on("connection", (socket) => {
  60. const workspaceId = Math.random().toString(36).substr(2, 9);
  61. const workspacePath = import_path.default.join(this.config.workspaceRoot, workspaceId);
  62. this.workspaces.set(socket.id, workspacePath);
  63. this.processes.set(socket.id, []);
  64. console.log(`Client connected. Workspace created at: ${workspacePath}`);
  65. socket.on("mount", async (files, callback) => {
  66. try {
  67. await this.handleMount(workspacePath, files);
  68. callback({ success: true });
  69. } catch (error) {
  70. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  71. callback({ error: errorMessage });
  72. }
  73. });
  74. socket.on("spawn", async (data, callback) => {
  75. try {
  76. const result = await this.handleSpawn(socket.id, workspacePath, data.command, data.args);
  77. callback({ success: true, result });
  78. } catch (error) {
  79. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  80. callback({ error: errorMessage });
  81. }
  82. });
  83. socket.on("fs:list", async (path3, callback) => {
  84. try {
  85. const result = await this.handleList(workspacePath, path3);
  86. callback({ success: true, result });
  87. } catch (error) {
  88. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  89. callback({ error: errorMessage });
  90. }
  91. });
  92. socket.on("fs:read", async (path3, callback) => {
  93. try {
  94. const result = await this.handleRead(workspacePath, path3);
  95. callback({ success: true, result });
  96. } catch (error) {
  97. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  98. callback({ error: errorMessage });
  99. }
  100. });
  101. socket.on("fs:write", async (data, callback) => {
  102. try {
  103. await this.handleWrite(workspacePath, data.path, data.content);
  104. callback({ success: true });
  105. } catch (error) {
  106. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  107. callback({ error: errorMessage });
  108. }
  109. });
  110. socket.on("fs:delete", async (path3, callback) => {
  111. try {
  112. await this.handleDelete(workspacePath, path3);
  113. callback({ success: true });
  114. } catch (error) {
  115. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  116. callback({ error: errorMessage });
  117. }
  118. });
  119. socket.on("fs:mkdir", async (path3, callback) => {
  120. try {
  121. await this.handleMkdir(workspacePath, path3);
  122. callback({ success: true });
  123. } catch (error) {
  124. const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
  125. callback({ error: errorMessage });
  126. }
  127. });
  128. socket.on("disconnect", () => {
  129. console.log(`Client disconnected. Cleaning up workspace: ${workspacePath}`);
  130. this.killAllProcesses(socket.id);
  131. import_promises.default.rm(workspacePath, { recursive: true, force: true }).catch((error) => console.error(`Failed to cleanup workspace: ${error}`));
  132. this.workspaces.delete(socket.id);
  133. this.processes.delete(socket.id);
  134. });
  135. });
  136. }
  137. killAllProcesses(socketId) {
  138. const processes = this.processes.get(socketId) || [];
  139. for (const process2 of processes) {
  140. try {
  141. process2.kill("SIGTERM");
  142. console.log(`Process ${process2.pid} terminated`);
  143. } catch (error) {
  144. console.error(`Failed to kill process ${process2.pid}:`, error);
  145. }
  146. }
  147. }
  148. async handleMount(workspacePath, files) {
  149. await import_promises.default.mkdir(workspacePath, { recursive: true });
  150. await this.createFiles(workspacePath, files);
  151. }
  152. async createFiles(basePath, files) {
  153. for (const [name, item] of Object.entries(files)) {
  154. const fullPath = import_path.default.join(basePath, name);
  155. if ("file" in item) {
  156. await import_promises.default.writeFile(fullPath, item.file.contents);
  157. } else if ("directory" in item) {
  158. await import_promises.default.mkdir(fullPath, { recursive: true });
  159. await this.createFiles(fullPath, item.directory.contents);
  160. }
  161. }
  162. }
  163. async handleSpawn(socketId, workspacePath, command, args = []) {
  164. return new Promise((resolve, reject) => {
  165. this.io.to(socketId).emit("process:output", {
  166. type: "stdout",
  167. data: `\r
  168. $ ${command} ${args.join(" ")}\r
  169. `
  170. });
  171. const process2 = (0, import_child_process.spawn)(command, args, {
  172. cwd: workspacePath,
  173. stdio: ["pipe", "pipe", "pipe"]
  174. });
  175. const processes = this.processes.get(socketId) || [];
  176. processes.push(process2);
  177. this.processes.set(socketId, processes);
  178. let stdout = "";
  179. let stderr = "";
  180. process2.stdout.on("data", (data) => {
  181. const output = data.toString();
  182. stdout += output;
  183. console.log(`[${command}] stdout:`, output);
  184. this.io.to(socketId).emit("process:output", {
  185. type: "stdout",
  186. data: output
  187. });
  188. });
  189. process2.stderr.on("data", (data) => {
  190. const output = data.toString();
  191. stderr += output;
  192. console.error(`[${command}] stderr:`, output);
  193. this.io.to(socketId).emit("process:output", {
  194. type: "stderr",
  195. data: output
  196. });
  197. });
  198. process2.on("close", (code) => {
  199. const processes2 = this.processes.get(socketId) || [];
  200. const index = processes2.indexOf(process2);
  201. if (index > -1) {
  202. processes2.splice(index, 1);
  203. }
  204. this.io.to(socketId).emit("process:output", {
  205. type: "exit",
  206. data: `\r
  207. Process exited with code ${code}\r
  208. $ `
  209. });
  210. if (code === 0) {
  211. resolve({ code, stdout, stderr });
  212. } else {
  213. reject(new Error(`Process exited with code ${code}
  214. ${stderr}`));
  215. }
  216. });
  217. process2.on("error", (error) => {
  218. const processes2 = this.processes.get(socketId) || [];
  219. const index = processes2.indexOf(process2);
  220. if (index > -1) {
  221. processes2.splice(index, 1);
  222. }
  223. this.io.to(socketId).emit("process:output", {
  224. type: "stderr",
  225. data: `\r
  226. Error: ${error.message}\r
  227. $ `
  228. });
  229. reject(error);
  230. });
  231. });
  232. }
  233. async handleList(workspacePath, relativePath) {
  234. const fullPath = import_path.default.join(workspacePath, relativePath);
  235. const entries = await import_promises.default.readdir(fullPath, { withFileTypes: true });
  236. return entries.map((entry) => ({
  237. name: entry.name,
  238. type: entry.isDirectory() ? "directory" : "file"
  239. }));
  240. }
  241. async handleRead(workspacePath, relativePath) {
  242. const fullPath = import_path.default.join(workspacePath, relativePath);
  243. return import_promises.default.readFile(fullPath, "utf-8");
  244. }
  245. async handleWrite(workspacePath, relativePath, content) {
  246. const fullPath = import_path.default.join(workspacePath, relativePath);
  247. await import_promises.default.mkdir(import_path.default.dirname(fullPath), { recursive: true });
  248. await import_promises.default.writeFile(fullPath, content);
  249. }
  250. async handleDelete(workspacePath, relativePath) {
  251. const fullPath = import_path.default.join(workspacePath, relativePath);
  252. await import_promises.default.rm(fullPath, { recursive: true, force: true });
  253. }
  254. async handleMkdir(workspacePath, relativePath) {
  255. const fullPath = import_path.default.join(workspacePath, relativePath);
  256. await import_promises.default.mkdir(fullPath, { recursive: true });
  257. }
  258. };
  259. // src/start.ts
  260. var import_path2 = __toESM(require("path"), 1);
  261. var port = parseInt(process.env.PORT || "3000", 10);
  262. var host = process.env.HOST || "localhost";
  263. var workspaceRoot = process.env.WORKSPACE_ROOT || import_path2.default.join(process.cwd(), "workspaces");
  264. var server = new WebContainerServer({
  265. port,
  266. host,
  267. workspaceRoot
  268. }).start();
  269. console.log(`WebContainer Server is running on http://${host}:${port}`);