almostnode 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +731 -0
  3. package/dist/__sw__.js +394 -0
  4. package/dist/ai-chatbot-demo-entry.d.ts +6 -0
  5. package/dist/ai-chatbot-demo-entry.d.ts.map +1 -0
  6. package/dist/ai-chatbot-demo.d.ts +42 -0
  7. package/dist/ai-chatbot-demo.d.ts.map +1 -0
  8. package/dist/assets/runtime-worker-D9x_Ddwz.js +60543 -0
  9. package/dist/assets/runtime-worker-D9x_Ddwz.js.map +1 -0
  10. package/dist/convex-app-demo-entry.d.ts +6 -0
  11. package/dist/convex-app-demo-entry.d.ts.map +1 -0
  12. package/dist/convex-app-demo.d.ts +68 -0
  13. package/dist/convex-app-demo.d.ts.map +1 -0
  14. package/dist/cors-proxy.d.ts +46 -0
  15. package/dist/cors-proxy.d.ts.map +1 -0
  16. package/dist/create-runtime.d.ts +42 -0
  17. package/dist/create-runtime.d.ts.map +1 -0
  18. package/dist/demo.d.ts +6 -0
  19. package/dist/demo.d.ts.map +1 -0
  20. package/dist/dev-server.d.ts +97 -0
  21. package/dist/dev-server.d.ts.map +1 -0
  22. package/dist/frameworks/next-dev-server.d.ts +202 -0
  23. package/dist/frameworks/next-dev-server.d.ts.map +1 -0
  24. package/dist/frameworks/vite-dev-server.d.ts +85 -0
  25. package/dist/frameworks/vite-dev-server.d.ts.map +1 -0
  26. package/dist/index.cjs +14965 -0
  27. package/dist/index.cjs.map +1 -0
  28. package/dist/index.d.ts +71 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.mjs +14867 -0
  31. package/dist/index.mjs.map +1 -0
  32. package/dist/next-demo.d.ts +49 -0
  33. package/dist/next-demo.d.ts.map +1 -0
  34. package/dist/npm/index.d.ts +71 -0
  35. package/dist/npm/index.d.ts.map +1 -0
  36. package/dist/npm/registry.d.ts +66 -0
  37. package/dist/npm/registry.d.ts.map +1 -0
  38. package/dist/npm/resolver.d.ts +52 -0
  39. package/dist/npm/resolver.d.ts.map +1 -0
  40. package/dist/npm/tarball.d.ts +29 -0
  41. package/dist/npm/tarball.d.ts.map +1 -0
  42. package/dist/runtime-interface.d.ts +90 -0
  43. package/dist/runtime-interface.d.ts.map +1 -0
  44. package/dist/runtime.d.ts +103 -0
  45. package/dist/runtime.d.ts.map +1 -0
  46. package/dist/sandbox-helpers.d.ts +43 -0
  47. package/dist/sandbox-helpers.d.ts.map +1 -0
  48. package/dist/sandbox-runtime.d.ts +65 -0
  49. package/dist/sandbox-runtime.d.ts.map +1 -0
  50. package/dist/server-bridge.d.ts +89 -0
  51. package/dist/server-bridge.d.ts.map +1 -0
  52. package/dist/shims/assert.d.ts +51 -0
  53. package/dist/shims/assert.d.ts.map +1 -0
  54. package/dist/shims/async_hooks.d.ts +37 -0
  55. package/dist/shims/async_hooks.d.ts.map +1 -0
  56. package/dist/shims/buffer.d.ts +20 -0
  57. package/dist/shims/buffer.d.ts.map +1 -0
  58. package/dist/shims/child_process-browser.d.ts +92 -0
  59. package/dist/shims/child_process-browser.d.ts.map +1 -0
  60. package/dist/shims/child_process.d.ts +93 -0
  61. package/dist/shims/child_process.d.ts.map +1 -0
  62. package/dist/shims/chokidar.d.ts +55 -0
  63. package/dist/shims/chokidar.d.ts.map +1 -0
  64. package/dist/shims/cluster.d.ts +52 -0
  65. package/dist/shims/cluster.d.ts.map +1 -0
  66. package/dist/shims/crypto.d.ts +122 -0
  67. package/dist/shims/crypto.d.ts.map +1 -0
  68. package/dist/shims/dgram.d.ts +34 -0
  69. package/dist/shims/dgram.d.ts.map +1 -0
  70. package/dist/shims/diagnostics_channel.d.ts +80 -0
  71. package/dist/shims/diagnostics_channel.d.ts.map +1 -0
  72. package/dist/shims/dns.d.ts +87 -0
  73. package/dist/shims/dns.d.ts.map +1 -0
  74. package/dist/shims/domain.d.ts +25 -0
  75. package/dist/shims/domain.d.ts.map +1 -0
  76. package/dist/shims/esbuild.d.ts +105 -0
  77. package/dist/shims/esbuild.d.ts.map +1 -0
  78. package/dist/shims/events.d.ts +37 -0
  79. package/dist/shims/events.d.ts.map +1 -0
  80. package/dist/shims/fs.d.ts +115 -0
  81. package/dist/shims/fs.d.ts.map +1 -0
  82. package/dist/shims/fsevents.d.ts +67 -0
  83. package/dist/shims/fsevents.d.ts.map +1 -0
  84. package/dist/shims/http.d.ts +217 -0
  85. package/dist/shims/http.d.ts.map +1 -0
  86. package/dist/shims/http2.d.ts +81 -0
  87. package/dist/shims/http2.d.ts.map +1 -0
  88. package/dist/shims/https.d.ts +36 -0
  89. package/dist/shims/https.d.ts.map +1 -0
  90. package/dist/shims/inspector.d.ts +25 -0
  91. package/dist/shims/inspector.d.ts.map +1 -0
  92. package/dist/shims/module.d.ts +22 -0
  93. package/dist/shims/module.d.ts.map +1 -0
  94. package/dist/shims/net.d.ts +100 -0
  95. package/dist/shims/net.d.ts.map +1 -0
  96. package/dist/shims/os.d.ts +159 -0
  97. package/dist/shims/os.d.ts.map +1 -0
  98. package/dist/shims/path.d.ts +72 -0
  99. package/dist/shims/path.d.ts.map +1 -0
  100. package/dist/shims/perf_hooks.d.ts +50 -0
  101. package/dist/shims/perf_hooks.d.ts.map +1 -0
  102. package/dist/shims/process.d.ts +93 -0
  103. package/dist/shims/process.d.ts.map +1 -0
  104. package/dist/shims/querystring.d.ts +23 -0
  105. package/dist/shims/querystring.d.ts.map +1 -0
  106. package/dist/shims/readdirp.d.ts +52 -0
  107. package/dist/shims/readdirp.d.ts.map +1 -0
  108. package/dist/shims/readline.d.ts +62 -0
  109. package/dist/shims/readline.d.ts.map +1 -0
  110. package/dist/shims/rollup.d.ts +34 -0
  111. package/dist/shims/rollup.d.ts.map +1 -0
  112. package/dist/shims/sentry.d.ts +163 -0
  113. package/dist/shims/sentry.d.ts.map +1 -0
  114. package/dist/shims/stream.d.ts +181 -0
  115. package/dist/shims/stream.d.ts.map +1 -0
  116. package/dist/shims/tls.d.ts +53 -0
  117. package/dist/shims/tls.d.ts.map +1 -0
  118. package/dist/shims/tty.d.ts +30 -0
  119. package/dist/shims/tty.d.ts.map +1 -0
  120. package/dist/shims/url.d.ts +64 -0
  121. package/dist/shims/url.d.ts.map +1 -0
  122. package/dist/shims/util.d.ts +106 -0
  123. package/dist/shims/util.d.ts.map +1 -0
  124. package/dist/shims/v8.d.ts +73 -0
  125. package/dist/shims/v8.d.ts.map +1 -0
  126. package/dist/shims/vfs-adapter.d.ts +126 -0
  127. package/dist/shims/vfs-adapter.d.ts.map +1 -0
  128. package/dist/shims/vm.d.ts +45 -0
  129. package/dist/shims/vm.d.ts.map +1 -0
  130. package/dist/shims/worker_threads.d.ts +66 -0
  131. package/dist/shims/worker_threads.d.ts.map +1 -0
  132. package/dist/shims/ws.d.ts +66 -0
  133. package/dist/shims/ws.d.ts.map +1 -0
  134. package/dist/shims/zlib.d.ts +161 -0
  135. package/dist/shims/zlib.d.ts.map +1 -0
  136. package/dist/transform.d.ts +24 -0
  137. package/dist/transform.d.ts.map +1 -0
  138. package/dist/virtual-fs.d.ts +226 -0
  139. package/dist/virtual-fs.d.ts.map +1 -0
  140. package/dist/vite-demo.d.ts +35 -0
  141. package/dist/vite-demo.d.ts.map +1 -0
  142. package/dist/vite-sw.js +132 -0
  143. package/dist/worker/runtime-worker.d.ts +8 -0
  144. package/dist/worker/runtime-worker.d.ts.map +1 -0
  145. package/dist/worker-runtime.d.ts +50 -0
  146. package/dist/worker-runtime.d.ts.map +1 -0
  147. package/package.json +85 -0
  148. package/src/ai-chatbot-demo-entry.ts +244 -0
  149. package/src/ai-chatbot-demo.ts +509 -0
  150. package/src/convex-app-demo-entry.ts +1107 -0
  151. package/src/convex-app-demo.ts +1316 -0
  152. package/src/cors-proxy.ts +81 -0
  153. package/src/create-runtime.ts +147 -0
  154. package/src/demo.ts +304 -0
  155. package/src/dev-server.ts +274 -0
  156. package/src/frameworks/next-dev-server.ts +2224 -0
  157. package/src/frameworks/vite-dev-server.ts +702 -0
  158. package/src/index.ts +101 -0
  159. package/src/next-demo.ts +1784 -0
  160. package/src/npm/index.ts +347 -0
  161. package/src/npm/registry.ts +152 -0
  162. package/src/npm/resolver.ts +385 -0
  163. package/src/npm/tarball.ts +209 -0
  164. package/src/runtime-interface.ts +103 -0
  165. package/src/runtime.ts +1046 -0
  166. package/src/sandbox-helpers.ts +173 -0
  167. package/src/sandbox-runtime.ts +252 -0
  168. package/src/server-bridge.ts +426 -0
  169. package/src/shims/assert.ts +664 -0
  170. package/src/shims/async_hooks.ts +86 -0
  171. package/src/shims/buffer.ts +75 -0
  172. package/src/shims/child_process-browser.ts +217 -0
  173. package/src/shims/child_process.ts +463 -0
  174. package/src/shims/chokidar.ts +313 -0
  175. package/src/shims/cluster.ts +67 -0
  176. package/src/shims/crypto.ts +830 -0
  177. package/src/shims/dgram.ts +47 -0
  178. package/src/shims/diagnostics_channel.ts +196 -0
  179. package/src/shims/dns.ts +172 -0
  180. package/src/shims/domain.ts +58 -0
  181. package/src/shims/esbuild.ts +805 -0
  182. package/src/shims/events.ts +195 -0
  183. package/src/shims/fs.ts +803 -0
  184. package/src/shims/fsevents.ts +63 -0
  185. package/src/shims/http.ts +904 -0
  186. package/src/shims/http2.ts +96 -0
  187. package/src/shims/https.ts +86 -0
  188. package/src/shims/inspector.ts +30 -0
  189. package/src/shims/module.ts +82 -0
  190. package/src/shims/net.ts +359 -0
  191. package/src/shims/os.ts +195 -0
  192. package/src/shims/path.ts +199 -0
  193. package/src/shims/perf_hooks.ts +92 -0
  194. package/src/shims/process.ts +346 -0
  195. package/src/shims/querystring.ts +97 -0
  196. package/src/shims/readdirp.ts +228 -0
  197. package/src/shims/readline.ts +110 -0
  198. package/src/shims/rollup.ts +80 -0
  199. package/src/shims/sentry.ts +133 -0
  200. package/src/shims/stream.ts +1126 -0
  201. package/src/shims/tls.ts +95 -0
  202. package/src/shims/tty.ts +64 -0
  203. package/src/shims/url.ts +171 -0
  204. package/src/shims/util.ts +312 -0
  205. package/src/shims/v8.ts +113 -0
  206. package/src/shims/vfs-adapter.ts +402 -0
  207. package/src/shims/vm.ts +83 -0
  208. package/src/shims/worker_threads.ts +111 -0
  209. package/src/shims/ws.ts +382 -0
  210. package/src/shims/zlib.ts +289 -0
  211. package/src/transform.ts +313 -0
  212. package/src/types/external.d.ts +67 -0
  213. package/src/virtual-fs.ts +903 -0
  214. package/src/vite-demo.ts +577 -0
  215. package/src/worker/runtime-worker.ts +128 -0
  216. package/src/worker-runtime.ts +145 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * CORS Proxy Utility
3
+ *
4
+ * Provides optional CORS proxy support for fetching external APIs
5
+ * that don't allow browser origins.
6
+ *
7
+ * No default proxy is configured for security reasons.
8
+ * Users must explicitly set a proxy URL if needed.
9
+ *
10
+ * Example usage:
11
+ * setCorsProxy('https://corsproxy.io/?');
12
+ * const response = await proxyFetch('https://api.example.com/data');
13
+ */
14
+
15
+ // No default proxy - must be explicitly set
16
+ let proxyUrl: string | null = null;
17
+
18
+ /**
19
+ * Set the CORS proxy URL
20
+ * @param url - Proxy URL (e.g., 'https://corsproxy.io/?')
21
+ * The target URL will be appended as an encoded parameter.
22
+ * Set to null to disable proxy and use direct fetch.
23
+ */
24
+ export function setCorsProxy(url: string | null): void {
25
+ proxyUrl = url;
26
+ if (url) {
27
+ console.log(`[cors-proxy] Proxy configured: ${url}`);
28
+ } else {
29
+ console.log('[cors-proxy] Proxy disabled, using direct fetch');
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Get the current CORS proxy URL
35
+ * @returns The configured proxy URL, or null if not set
36
+ */
37
+ export function getCorsProxy(): string | null {
38
+ return proxyUrl;
39
+ }
40
+
41
+ /**
42
+ * Check if a proxy is configured
43
+ */
44
+ export function hasProxy(): boolean {
45
+ return proxyUrl !== null;
46
+ }
47
+
48
+ /**
49
+ * Fetch with optional CORS proxy
50
+ *
51
+ * If a proxy is configured, the request goes through the proxy.
52
+ * Otherwise, a direct fetch is performed (may hit CORS issues).
53
+ *
54
+ * @param url - The target URL to fetch
55
+ * @param options - Standard fetch options
56
+ * @returns Promise<Response>
57
+ */
58
+ export async function proxyFetch(
59
+ url: string,
60
+ options?: RequestInit
61
+ ): Promise<Response> {
62
+ if (proxyUrl) {
63
+ // Route through proxy
64
+ const proxiedUrl = proxyUrl + encodeURIComponent(url);
65
+ return fetch(proxiedUrl, options);
66
+ }
67
+
68
+ // No proxy configured - direct fetch
69
+ return fetch(url, options);
70
+ }
71
+
72
+ /**
73
+ * Build a proxied URL without fetching
74
+ * Useful for displaying the URL or using with other fetch mechanisms
75
+ */
76
+ export function buildProxyUrl(url: string): string {
77
+ if (proxyUrl) {
78
+ return proxyUrl + encodeURIComponent(url);
79
+ }
80
+ return url;
81
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Runtime Factory - Create sandboxed, worker, or main-thread runtime
3
+ *
4
+ * SECURITY: By default, createRuntime requires either:
5
+ * 1. A sandbox URL for cross-origin isolation (recommended)
6
+ * 2. Explicit opt-in via dangerouslyAllowSameOrigin for trusted code
7
+ *
8
+ * Usage:
9
+ * // Secure: Cross-origin sandbox (recommended for untrusted code)
10
+ * const runtime = await createRuntime(vfs, {
11
+ * sandbox: 'https://myapp-sandbox.vercel.app'
12
+ * });
13
+ *
14
+ * // Same-origin with Worker (for demos/trusted code)
15
+ * const runtime = await createRuntime(vfs, {
16
+ * dangerouslyAllowSameOrigin: true,
17
+ * useWorker: true
18
+ * });
19
+ *
20
+ * // Same-origin main thread (least secure, trusted code only)
21
+ * const runtime = await createRuntime(vfs, {
22
+ * dangerouslyAllowSameOrigin: true
23
+ * });
24
+ */
25
+
26
+ import { Runtime } from './runtime';
27
+ import { WorkerRuntime } from './worker-runtime';
28
+ import { SandboxRuntime } from './sandbox-runtime';
29
+ import type { VirtualFS } from './virtual-fs';
30
+ import type { IRuntime, IExecuteResult, CreateRuntimeOptions, IRuntimeOptions } from './runtime-interface';
31
+
32
+ /**
33
+ * Check if Web Workers are available in the current environment
34
+ */
35
+ function isWorkerAvailable(): boolean {
36
+ return typeof Worker !== 'undefined';
37
+ }
38
+
39
+ /**
40
+ * Wrapper that makes the synchronous Runtime conform to the async IRuntime interface
41
+ */
42
+ class AsyncRuntimeWrapper implements IRuntime {
43
+ private runtime: Runtime;
44
+
45
+ constructor(vfs: VirtualFS, options: IRuntimeOptions = {}) {
46
+ this.runtime = new Runtime(vfs, options);
47
+ }
48
+
49
+ async execute(code: string, filename?: string): Promise<IExecuteResult> {
50
+ return Promise.resolve(this.runtime.execute(code, filename));
51
+ }
52
+
53
+ async runFile(filename: string): Promise<IExecuteResult> {
54
+ return Promise.resolve(this.runtime.runFile(filename));
55
+ }
56
+
57
+ clearCache(): void {
58
+ this.runtime.clearCache();
59
+ }
60
+
61
+ getVFS(): VirtualFS {
62
+ return this.runtime.getVFS();
63
+ }
64
+
65
+ /**
66
+ * Get the underlying sync Runtime for direct access to sync methods
67
+ */
68
+ getSyncRuntime(): Runtime {
69
+ return this.runtime;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Create a runtime instance based on configuration
75
+ *
76
+ * SECURITY: Requires either sandbox URL or explicit dangerouslyAllowSameOrigin.
77
+ *
78
+ * @param vfs - Virtual file system instance
79
+ * @param options - Runtime options including sandbox/security settings
80
+ * @returns Promise resolving to IRuntime instance
81
+ * @throws Error if neither sandbox nor dangerouslyAllowSameOrigin is specified
82
+ */
83
+ export async function createRuntime(
84
+ vfs: VirtualFS,
85
+ options: CreateRuntimeOptions = {}
86
+ ): Promise<IRuntime> {
87
+ const { sandbox, dangerouslyAllowSameOrigin, useWorker = false, ...runtimeOptions } = options;
88
+
89
+ // SECURE: Cross-origin sandbox mode
90
+ if (sandbox) {
91
+ console.log('[createRuntime] Creating SandboxRuntime (cross-origin isolated)');
92
+ const sandboxRuntime = new SandboxRuntime(sandbox, vfs, runtimeOptions);
93
+ // Wait for sandbox to be ready by executing a simple command
94
+ await sandboxRuntime.execute('/* sandbox ready check */', '/__sandbox_init__.js');
95
+ return sandboxRuntime;
96
+ }
97
+
98
+ // SECURITY CHECK: Same-origin execution requires explicit opt-in
99
+ if (!dangerouslyAllowSameOrigin) {
100
+ throw new Error(
101
+ 'almostnode: For security, you must either:\n' +
102
+ ' 1. Use sandbox mode: { sandbox: "https://your-sandbox.vercel.app" }\n' +
103
+ ' 2. Explicitly opt-in to same-origin: { dangerouslyAllowSameOrigin: true }\n' +
104
+ '\n' +
105
+ 'Same-origin execution allows code to access cookies, localStorage, and IndexedDB.\n' +
106
+ 'Only use dangerouslyAllowSameOrigin for trusted code or demos.\n' +
107
+ '\n' +
108
+ 'For sandbox setup instructions, see: https://github.com/anthropics/almostnode#sandbox-setup'
109
+ );
110
+ }
111
+
112
+ // Same-origin modes (requires explicit opt-in)
113
+ let shouldUseWorker = false;
114
+
115
+ if (useWorker === true) {
116
+ shouldUseWorker = isWorkerAvailable();
117
+ if (!shouldUseWorker) {
118
+ console.warn('[createRuntime] Worker requested but not available, falling back to main thread');
119
+ }
120
+ } else if (useWorker === 'auto') {
121
+ shouldUseWorker = isWorkerAvailable();
122
+ console.log(`[createRuntime] Auto mode: using ${shouldUseWorker ? 'worker' : 'main thread'}`);
123
+ }
124
+
125
+ if (shouldUseWorker) {
126
+ console.log('[createRuntime] Creating WorkerRuntime (same-origin, thread-isolated)');
127
+ const workerRuntime = new WorkerRuntime(vfs, runtimeOptions);
128
+ // Wait for worker to be ready by executing a simple command
129
+ await workerRuntime.execute('/* worker ready check */', '/__worker_init__.js');
130
+ return workerRuntime;
131
+ }
132
+
133
+ console.log('[createRuntime] Creating main-thread Runtime (same-origin, least secure)');
134
+ return new AsyncRuntimeWrapper(vfs, runtimeOptions);
135
+ }
136
+
137
+ // Re-export types and classes for convenience
138
+ export { Runtime } from './runtime';
139
+ export { WorkerRuntime } from './worker-runtime';
140
+ export { SandboxRuntime } from './sandbox-runtime';
141
+ export type {
142
+ IRuntime,
143
+ IExecuteResult,
144
+ IRuntimeOptions,
145
+ CreateRuntimeOptions,
146
+ VFSSnapshot,
147
+ } from './runtime-interface';
package/src/demo.ts ADDED
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Mini WebContainer Demo
3
+ * Demonstrates running an HTTP server in the browser
4
+ */
5
+
6
+ import { VirtualFS } from './virtual-fs';
7
+ import { Runtime } from './runtime';
8
+ import { ServerBridge, getServerBridge, resetServerBridge } from './server-bridge';
9
+ import { PackageManager } from './npm';
10
+
11
+ // DOM elements
12
+ const editorEl = document.getElementById('editor') as HTMLTextAreaElement;
13
+ const previewEl = document.getElementById('preview') as HTMLIFrameElement;
14
+ const terminalEl = document.getElementById('terminal') as HTMLDivElement;
15
+ const statusEl = document.getElementById('status') as HTMLSpanElement;
16
+ const runBtn = document.getElementById('runBtn') as HTMLButtonElement;
17
+
18
+ // State
19
+ let vfs: VirtualFS;
20
+ let runtime: Runtime;
21
+ let serverBridge: ServerBridge;
22
+ let npm: PackageManager;
23
+ let currentServerPort: number | null = null;
24
+
25
+ // Log to terminal
26
+ function log(message: string, type: 'info' | 'error' | 'success' = 'info') {
27
+ const colors = {
28
+ info: '#888',
29
+ error: '#e74c3c',
30
+ success: '#27ae60',
31
+ };
32
+ const time = new Date().toLocaleTimeString();
33
+ terminalEl.innerHTML += `<span style="color: ${colors[type]}">[${time}] ${escapeHtml(message)}</span>\n`;
34
+ terminalEl.scrollTop = terminalEl.scrollHeight;
35
+ }
36
+
37
+ function escapeHtml(text: string): string {
38
+ return text
39
+ .replace(/&/g, '&amp;')
40
+ .replace(/</g, '&lt;')
41
+ .replace(/>/g, '&gt;');
42
+ }
43
+
44
+ // Update status indicator
45
+ function setStatus(text: string, className: 'loading' | 'ready' | 'error') {
46
+ statusEl.textContent = text;
47
+ statusEl.className = 'status ' + className;
48
+ }
49
+
50
+ // Initialize the container
51
+ function initContainer() {
52
+ vfs = new VirtualFS();
53
+ runtime = new Runtime(vfs, {
54
+ onConsole: (method, args) => {
55
+ const message = args.map(arg =>
56
+ typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
57
+ ).join(' ');
58
+ log(message, method === 'error' ? 'error' : 'info');
59
+ },
60
+ });
61
+
62
+ npm = new PackageManager(vfs);
63
+
64
+ // Reset the server bridge to get fresh callbacks
65
+ resetServerBridge();
66
+
67
+ serverBridge = getServerBridge({
68
+ baseUrl: window.location.origin,
69
+ onServerReady: (port, url) => {
70
+ currentServerPort = port;
71
+ log(`Server ready on port ${port}`, 'success');
72
+ setStatus('Running', 'ready');
73
+
74
+ // Load the preview
75
+ loadPreview('/');
76
+ },
77
+ });
78
+
79
+ log('Container initialized', 'success');
80
+ }
81
+
82
+ // Load content from virtual server into preview
83
+ async function loadPreview(path: string) {
84
+ if (!currentServerPort) {
85
+ log('No server running', 'error');
86
+ return;
87
+ }
88
+
89
+ try {
90
+ const response = await serverBridge.handleRequest(
91
+ currentServerPort,
92
+ 'GET',
93
+ path,
94
+ {
95
+ host: 'localhost',
96
+ 'user-agent': 'MiniWebContainer/1.0',
97
+ }
98
+ );
99
+
100
+ const contentType = response.headers['content-type'] || 'text/plain';
101
+ const body = response.body.toString();
102
+
103
+ if (contentType.includes('text/html')) {
104
+ // Inject script to intercept link clicks
105
+ const htmlWithInterceptor = injectLinkInterceptor(body);
106
+ const blob = new Blob([htmlWithInterceptor], { type: 'text/html' });
107
+ previewEl.src = URL.createObjectURL(blob);
108
+ } else if (contentType.includes('application/json')) {
109
+ // Display JSON nicely
110
+ const html = `
111
+ <!DOCTYPE html>
112
+ <html>
113
+ <head>
114
+ <style>
115
+ body { font-family: monospace; padding: 1rem; background: #1a1a2e; color: #0f0; }
116
+ pre { white-space: pre-wrap; word-wrap: break-word; }
117
+ </style>
118
+ </head>
119
+ <body>
120
+ <pre>${escapeHtml(JSON.stringify(JSON.parse(body), null, 2))}</pre>
121
+ </body>
122
+ </html>
123
+ `;
124
+ const blob = new Blob([html], { type: 'text/html' });
125
+ previewEl.src = URL.createObjectURL(blob);
126
+ } else {
127
+ // Display as plain text
128
+ const html = `
129
+ <!DOCTYPE html>
130
+ <html>
131
+ <head>
132
+ <style>
133
+ body { font-family: monospace; padding: 1rem; background: #1a1a2e; color: #eee; }
134
+ pre { white-space: pre-wrap; word-wrap: break-word; }
135
+ </style>
136
+ </head>
137
+ <body>
138
+ <pre>${escapeHtml(body)}</pre>
139
+ </body>
140
+ </html>
141
+ `;
142
+ const blob = new Blob([html], { type: 'text/html' });
143
+ previewEl.src = URL.createObjectURL(blob);
144
+ }
145
+ } catch (error) {
146
+ log(`Error loading preview: ${error}`, 'error');
147
+ }
148
+ }
149
+
150
+ // Inject script to intercept link clicks in the preview
151
+ function injectLinkInterceptor(html: string): string {
152
+ const script = `
153
+ <script>
154
+ document.addEventListener('click', function(e) {
155
+ const link = e.target.closest('a');
156
+ if (link) {
157
+ e.preventDefault();
158
+ // Get the href attribute directly (not the resolved href property)
159
+ const href = link.getAttribute('href');
160
+ if (href) {
161
+ // Handle relative URLs directly
162
+ if (href.startsWith('/')) {
163
+ window.parent.postMessage({ type: 'navigate', path: href }, '*');
164
+ } else if (href.startsWith('http')) {
165
+ // Absolute URL - extract pathname
166
+ try {
167
+ const url = new URL(href);
168
+ window.parent.postMessage({ type: 'navigate', path: url.pathname + url.search }, '*');
169
+ } catch (e) {
170
+ console.error('Invalid URL:', href);
171
+ }
172
+ } else {
173
+ // Relative path without leading slash
174
+ window.parent.postMessage({ type: 'navigate', path: '/' + href }, '*');
175
+ }
176
+ }
177
+ }
178
+ });
179
+ </script>
180
+ `;
181
+
182
+ // Insert before </body> or at the end
183
+ if (html.includes('</body>')) {
184
+ return html.replace('</body>', script + '</body>');
185
+ }
186
+ return html + script;
187
+ }
188
+
189
+ // Check if code uses Express
190
+ function usesExpress(code: string): boolean {
191
+ return code.includes("require('express')") || code.includes('require("express")');
192
+ }
193
+
194
+ // Check if express is installed in VFS
195
+ function isExpressInstalled(): boolean {
196
+ return vfs.existsSync('/node_modules/express/package.json');
197
+ }
198
+
199
+ // Install dependencies
200
+ async function installDependencies() {
201
+ setStatus('Installing...', 'loading');
202
+ runBtn.disabled = true;
203
+
204
+ try {
205
+ log('Installing express (this may take a moment)...', 'info');
206
+
207
+ await npm.install('express', {
208
+ onProgress: (msg) => log(msg, 'info'),
209
+ });
210
+
211
+ log('Express installed successfully!', 'success');
212
+ } catch (error) {
213
+ log(`Install failed: ${error}`, 'error');
214
+ setStatus('Install Failed', 'error');
215
+ throw error;
216
+ } finally {
217
+ runBtn.disabled = false;
218
+ }
219
+ }
220
+
221
+ // Reset runtime for a fresh execution (but keep VFS with installed packages)
222
+ function resetRuntime() {
223
+ runtime = new Runtime(vfs, {
224
+ onConsole: (method, args) => {
225
+ const message = args.map(arg =>
226
+ typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
227
+ ).join(' ');
228
+ log(message, method === 'error' ? 'error' : 'info');
229
+ },
230
+ });
231
+
232
+ // Reset the server bridge to get fresh callbacks
233
+ resetServerBridge();
234
+
235
+ serverBridge = getServerBridge({
236
+ baseUrl: window.location.origin,
237
+ onServerReady: (port, url) => {
238
+ currentServerPort = port;
239
+ log(`Server ready on port ${port}`, 'success');
240
+ setStatus('Running', 'ready');
241
+
242
+ // Load the preview
243
+ loadPreview('/');
244
+ },
245
+ });
246
+ }
247
+
248
+ // Run the server code
249
+ async function runServer() {
250
+ const code = editorEl.value;
251
+
252
+ // Reset state
253
+ currentServerPort = null;
254
+ setStatus('Starting...', 'loading');
255
+ runBtn.disabled = true;
256
+
257
+ // Clear terminal
258
+ terminalEl.innerHTML = '';
259
+ log('Running script...');
260
+
261
+ // Reset runtime (keeps VFS with installed packages)
262
+ resetRuntime();
263
+
264
+ try {
265
+ // Check if Express is needed and not installed
266
+ if (usesExpress(code) && !isExpressInstalled()) {
267
+ await installDependencies();
268
+ }
269
+
270
+ // Execute the code
271
+ log('Executing code...', 'info');
272
+ runtime.execute(code, '/server.js');
273
+ log('Code executed successfully', 'success');
274
+
275
+ // If no server started after a short delay, mark as completed
276
+ setTimeout(() => {
277
+ if (!currentServerPort) {
278
+ setStatus('Completed', 'ready');
279
+ }
280
+ }, 500);
281
+ } catch (error) {
282
+ log(`Error: ${error}`, 'error');
283
+ setStatus('Error', 'error');
284
+ } finally {
285
+ runBtn.disabled = false;
286
+ }
287
+ }
288
+
289
+ // Handle messages from preview iframe
290
+ window.addEventListener('message', (event) => {
291
+ if (event.data?.type === 'navigate') {
292
+ log(`Navigating to ${event.data.path}`);
293
+ loadPreview(event.data.path);
294
+ }
295
+ });
296
+
297
+ // Initialize on load
298
+ runBtn.addEventListener('click', runServer);
299
+
300
+ // Initialize container
301
+ initContainer();
302
+ setStatus('Ready', 'ready');
303
+ log('Click "Run Server" to start the HTTP server');
304
+ log('Express will be auto-installed if you use require("express")');