index.cjs 10 KB

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