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,385 @@
1
+ /**
2
+ * Dependency Resolver
3
+ * Resolves full dependency tree with semver version constraints
4
+ */
5
+
6
+ import { Registry, PackageVersion } from './registry';
7
+
8
+ export interface ResolvedPackage {
9
+ name: string;
10
+ version: string;
11
+ tarballUrl: string;
12
+ dependencies: Record<string, string>;
13
+ }
14
+
15
+ export interface ResolveOptions {
16
+ registry?: Registry;
17
+ includeDev?: boolean;
18
+ includeOptional?: boolean;
19
+ onProgress?: (message: string) => void;
20
+ }
21
+
22
+ interface ResolveContext {
23
+ registry: Registry;
24
+ resolved: Map<string, ResolvedPackage>;
25
+ resolving: Set<string>;
26
+ options: ResolveOptions;
27
+ }
28
+
29
+ /**
30
+ * Parse a semver version string into components
31
+ */
32
+ function parseVersion(version: string): {
33
+ major: number;
34
+ minor: number;
35
+ patch: number;
36
+ prerelease?: string;
37
+ } | null {
38
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
39
+ if (!match) return null;
40
+
41
+ return {
42
+ major: parseInt(match[1], 10),
43
+ minor: parseInt(match[2], 10),
44
+ patch: parseInt(match[3], 10),
45
+ prerelease: match[4],
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Compare two semver versions
51
+ * Returns: -1 if a < b, 0 if a == b, 1 if a > b
52
+ */
53
+ function compareVersions(a: string, b: string): number {
54
+ const parsedA = parseVersion(a);
55
+ const parsedB = parseVersion(b);
56
+
57
+ if (!parsedA || !parsedB) {
58
+ return a.localeCompare(b);
59
+ }
60
+
61
+ if (parsedA.major !== parsedB.major) {
62
+ return parsedA.major - parsedB.major;
63
+ }
64
+ if (parsedA.minor !== parsedB.minor) {
65
+ return parsedA.minor - parsedB.minor;
66
+ }
67
+ if (parsedA.patch !== parsedB.patch) {
68
+ return parsedA.patch - parsedB.patch;
69
+ }
70
+
71
+ // Prerelease versions are lower than release versions
72
+ if (parsedA.prerelease && !parsedB.prerelease) return -1;
73
+ if (!parsedA.prerelease && parsedB.prerelease) return 1;
74
+ if (parsedA.prerelease && parsedB.prerelease) {
75
+ return parsedA.prerelease.localeCompare(parsedB.prerelease);
76
+ }
77
+
78
+ return 0;
79
+ }
80
+
81
+ /**
82
+ * Check if a version satisfies a semver range
83
+ */
84
+ function satisfies(version: string, range: string): boolean {
85
+ const parsed = parseVersion(version);
86
+ if (!parsed) return false;
87
+
88
+ // Skip prerelease versions unless explicitly requested
89
+ if (parsed.prerelease && !range.includes('-')) {
90
+ return false;
91
+ }
92
+
93
+ range = range.trim();
94
+
95
+ // Exact version
96
+ if (/^\d+\.\d+\.\d+/.test(range) && !range.includes(' ')) {
97
+ const rangeMatch = range.match(/^(\d+\.\d+\.\d+(?:-[^\s]+)?)/);
98
+ if (rangeMatch) {
99
+ return compareVersions(version, rangeMatch[1]) === 0;
100
+ }
101
+ }
102
+
103
+ // Latest or * - any version
104
+ if (range === '*' || range === 'latest' || range === '') {
105
+ return true;
106
+ }
107
+
108
+ // Multiple ranges with ||
109
+ if (range.includes('||')) {
110
+ return range.split('||').some((r) => satisfies(version, r.trim()));
111
+ }
112
+
113
+ // Range with hyphen: 1.0.0 - 2.0.0
114
+ if (range.includes(' - ')) {
115
+ const [min, max] = range.split(' - ').map((s) => s.trim());
116
+ return compareVersions(version, min) >= 0 && compareVersions(version, max) <= 0;
117
+ }
118
+
119
+ // Compound ranges with operators: >= 2.1.2 < 3.0.0
120
+ // Parse all operators and versions from the range
121
+ const operatorMatches = range.match(/(>=|<=|>|<|=)?\s*(\d+\.\d+\.\d+(?:-[^\s]*)?)/g);
122
+ if (operatorMatches && operatorMatches.length > 1) {
123
+ return operatorMatches.every((match) => {
124
+ const m = match.match(/^(>=|<=|>|<|=)?\s*(\d+\.\d+\.\d+(?:-[^\s]*)?)$/);
125
+ if (!m) return true;
126
+ const op = m[1] || '=';
127
+ const ver = m[2];
128
+ switch (op) {
129
+ case '>=': return compareVersions(version, ver) >= 0;
130
+ case '<=': return compareVersions(version, ver) <= 0;
131
+ case '>': return compareVersions(version, ver) > 0;
132
+ case '<': return compareVersions(version, ver) < 0;
133
+ case '=': return compareVersions(version, ver) === 0;
134
+ default: return compareVersions(version, ver) === 0;
135
+ }
136
+ });
137
+ }
138
+
139
+ // Caret range: ^1.2.3 means >=1.2.3 <2.0.0 (or <1.3.0 if major is 0)
140
+ if (range.startsWith('^')) {
141
+ const base = range.slice(1);
142
+ const baseParsed = parseVersion(base);
143
+ if (!baseParsed) return false;
144
+
145
+ if (parsed.major !== baseParsed.major) {
146
+ return false;
147
+ }
148
+
149
+ if (baseParsed.major === 0) {
150
+ // ^0.x.y is more restrictive
151
+ if (baseParsed.minor !== 0 && parsed.minor !== baseParsed.minor) {
152
+ return false;
153
+ }
154
+ if (baseParsed.minor === 0 && parsed.minor !== 0) {
155
+ return false;
156
+ }
157
+ }
158
+
159
+ return compareVersions(version, base) >= 0;
160
+ }
161
+
162
+ // Tilde range: ~1.2.3 means >=1.2.3 <1.3.0
163
+ if (range.startsWith('~')) {
164
+ const base = range.slice(1);
165
+ const baseParsed = parseVersion(base);
166
+ if (!baseParsed) return false;
167
+
168
+ if (parsed.major !== baseParsed.major || parsed.minor !== baseParsed.minor) {
169
+ return false;
170
+ }
171
+
172
+ return compareVersions(version, base) >= 0;
173
+ }
174
+
175
+ // Greater than or equal: >=1.2.3
176
+ if (range.startsWith('>=')) {
177
+ const base = range.slice(2).trim();
178
+ return compareVersions(version, base) >= 0;
179
+ }
180
+
181
+ // Greater than: >1.2.3
182
+ if (range.startsWith('>')) {
183
+ const base = range.slice(1).trim();
184
+ return compareVersions(version, base) > 0;
185
+ }
186
+
187
+ // Less than or equal: <=1.2.3
188
+ if (range.startsWith('<=')) {
189
+ const base = range.slice(2).trim();
190
+ return compareVersions(version, base) <= 0;
191
+ }
192
+
193
+ // Less than: <1.2.3
194
+ if (range.startsWith('<')) {
195
+ const base = range.slice(1).trim();
196
+ return compareVersions(version, base) < 0;
197
+ }
198
+
199
+ // X-ranges: 1.x, 1.2.x, 1, 1.2
200
+ if (range.includes('x') || range.includes('X') || /^\d+$/.test(range) || /^\d+\.\d+$/.test(range)) {
201
+ const parts = range.replace(/[xX]/g, '').split('.').filter(Boolean);
202
+
203
+ if (parts.length === 1) {
204
+ return parsed.major === parseInt(parts[0], 10);
205
+ }
206
+ if (parts.length === 2) {
207
+ return (
208
+ parsed.major === parseInt(parts[0], 10) &&
209
+ parsed.minor === parseInt(parts[1], 10)
210
+ );
211
+ }
212
+ }
213
+
214
+ // Multiple conditions with space (AND) - handle simple cases
215
+ if (range.includes(' ')) {
216
+ const conditions = range.split(/\s+/).filter(Boolean);
217
+ return conditions.every((r) => satisfies(version, r));
218
+ }
219
+
220
+ // Fallback: try exact match
221
+ return compareVersions(version, range) === 0;
222
+ }
223
+
224
+ /**
225
+ * Find the best matching version from available versions
226
+ */
227
+ function findBestVersion(versions: string[], range: string): string | null {
228
+ // Sort versions in descending order
229
+ const sorted = [...versions].sort((a, b) => compareVersions(b, a));
230
+
231
+ // Find the first version that satisfies the range
232
+ for (const version of sorted) {
233
+ if (satisfies(version, range)) {
234
+ return version;
235
+ }
236
+ }
237
+
238
+ return null;
239
+ }
240
+
241
+ /**
242
+ * Resolve all dependencies for a package
243
+ */
244
+ export async function resolveDependencies(
245
+ packageName: string,
246
+ versionRange: string = 'latest',
247
+ options: ResolveOptions = {}
248
+ ): Promise<Map<string, ResolvedPackage>> {
249
+ const registry = options.registry || new Registry();
250
+ const context: ResolveContext = {
251
+ registry,
252
+ resolved: new Map(),
253
+ resolving: new Set(),
254
+ options,
255
+ };
256
+
257
+ await resolvePackage(packageName, versionRange, context);
258
+
259
+ return context.resolved;
260
+ }
261
+
262
+ /**
263
+ * Resolve dependencies from a package.json
264
+ */
265
+ export async function resolveFromPackageJson(
266
+ packageJson: {
267
+ dependencies?: Record<string, string>;
268
+ devDependencies?: Record<string, string>;
269
+ },
270
+ options: ResolveOptions = {}
271
+ ): Promise<Map<string, ResolvedPackage>> {
272
+ const registry = options.registry || new Registry();
273
+ const context: ResolveContext = {
274
+ registry,
275
+ resolved: new Map(),
276
+ resolving: new Set(),
277
+ options,
278
+ };
279
+
280
+ const deps = { ...packageJson.dependencies };
281
+
282
+ if (options.includeDev && packageJson.devDependencies) {
283
+ Object.assign(deps, packageJson.devDependencies);
284
+ }
285
+
286
+ for (const [name, range] of Object.entries(deps)) {
287
+ await resolvePackage(name, range, context);
288
+ }
289
+
290
+ return context.resolved;
291
+ }
292
+
293
+ /**
294
+ * Recursively resolve a single package and its dependencies
295
+ */
296
+ async function resolvePackage(
297
+ packageName: string,
298
+ versionRange: string,
299
+ context: ResolveContext
300
+ ): Promise<void> {
301
+ const { registry, resolved, resolving, options } = context;
302
+
303
+ // Create a key for this package request
304
+ const key = `${packageName}@${versionRange}`;
305
+
306
+ // Check if we're already resolving this (circular dependency)
307
+ if (resolving.has(key)) {
308
+ return;
309
+ }
310
+
311
+ // Check if we've already resolved a compatible version
312
+ if (resolved.has(packageName)) {
313
+ const existing = resolved.get(packageName)!;
314
+ if (satisfies(existing.version, versionRange)) {
315
+ return;
316
+ }
317
+ // If existing version doesn't satisfy, we might need nested deps
318
+ // For MVP, we'll just use the existing version (flat node_modules)
319
+ return;
320
+ }
321
+
322
+ resolving.add(key);
323
+
324
+ try {
325
+ options.onProgress?.(`Resolving ${packageName}@${versionRange}`);
326
+
327
+ // Fetch package manifest
328
+ const manifest = await registry.getPackageManifest(packageName);
329
+
330
+ // Find best matching version
331
+ const versions = Object.keys(manifest.versions);
332
+ let targetVersion: string;
333
+
334
+ if (versionRange === 'latest' || versionRange === '*') {
335
+ targetVersion = manifest['dist-tags'].latest;
336
+ } else if (manifest['dist-tags'][versionRange]) {
337
+ targetVersion = manifest['dist-tags'][versionRange];
338
+ } else {
339
+ const best = findBestVersion(versions, versionRange);
340
+ if (!best) {
341
+ throw new Error(
342
+ `No matching version found for ${packageName}@${versionRange}`
343
+ );
344
+ }
345
+ targetVersion = best;
346
+ }
347
+
348
+ // Get version metadata
349
+ const versionData = manifest.versions[targetVersion];
350
+
351
+ // Store resolved package
352
+ const resolvedPackage: ResolvedPackage = {
353
+ name: packageName,
354
+ version: targetVersion,
355
+ tarballUrl: versionData.dist.tarball,
356
+ dependencies: versionData.dependencies || {},
357
+ };
358
+
359
+ resolved.set(packageName, resolvedPackage);
360
+
361
+ // Resolve dependencies in parallel
362
+ const deps = { ...versionData.dependencies };
363
+
364
+ if (options.includeOptional && versionData.optionalDependencies) {
365
+ Object.assign(deps, versionData.optionalDependencies);
366
+ }
367
+
368
+ const depEntries = Object.entries(deps);
369
+ if (depEntries.length > 0) {
370
+ // Resolve dependencies in parallel batches
371
+ const CONCURRENCY = 8;
372
+ for (let i = 0; i < depEntries.length; i += CONCURRENCY) {
373
+ const batch = depEntries.slice(i, i + CONCURRENCY);
374
+ await Promise.all(
375
+ batch.map(([depName, depRange]) => resolvePackage(depName, depRange, context))
376
+ );
377
+ }
378
+ }
379
+ } finally {
380
+ resolving.delete(key);
381
+ }
382
+ }
383
+
384
+ // Export utilities for testing
385
+ export { parseVersion, compareVersions, satisfies, findBestVersion };
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Tarball Extractor
3
+ * Downloads and extracts npm package tarballs into the virtual file system
4
+ */
5
+
6
+ import pako from 'pako';
7
+ import { VirtualFS } from '../virtual-fs';
8
+ import * as path from '../shims/path';
9
+
10
+ export interface ExtractOptions {
11
+ stripComponents?: number; // Number of leading path components to strip (default: 1 for npm's "package/" prefix)
12
+ filter?: (path: string) => boolean;
13
+ onProgress?: (message: string) => void;
14
+ }
15
+
16
+ interface TarEntry {
17
+ name: string;
18
+ type: 'file' | 'directory' | 'symlink' | 'unknown';
19
+ size: number;
20
+ mode: number;
21
+ content?: Uint8Array;
22
+ linkTarget?: string;
23
+ }
24
+
25
+ /**
26
+ * Parse a tar archive from raw bytes
27
+ */
28
+ function* parseTar(data: Uint8Array): Generator<TarEntry> {
29
+ const decoder = new TextDecoder();
30
+ let offset = 0;
31
+
32
+ while (offset < data.length - 512) {
33
+ // Read 512-byte header
34
+ const header = data.slice(offset, offset + 512);
35
+ offset += 512;
36
+
37
+ // Check for end of archive (two zero blocks)
38
+ if (header.every((b) => b === 0)) {
39
+ break;
40
+ }
41
+
42
+ // Parse header fields
43
+ const name = parseString(header, 0, 100);
44
+ const mode = parseOctal(header, 100, 8);
45
+ const size = parseOctal(header, 124, 12);
46
+ const typeFlag = String.fromCharCode(header[156]);
47
+ const linkName = parseString(header, 157, 100);
48
+ const prefix = parseString(header, 345, 155);
49
+
50
+ // Skip empty entries
51
+ if (!name) {
52
+ continue;
53
+ }
54
+
55
+ // Combine prefix and name for long paths
56
+ const fullName = prefix ? `${prefix}/${name}` : name;
57
+
58
+ // Determine entry type
59
+ let type: TarEntry['type'];
60
+ switch (typeFlag) {
61
+ case '0':
62
+ case '\0':
63
+ case '':
64
+ type = 'file';
65
+ break;
66
+ case '5':
67
+ type = 'directory';
68
+ break;
69
+ case '1':
70
+ case '2':
71
+ type = 'symlink';
72
+ break;
73
+ default:
74
+ type = 'unknown';
75
+ }
76
+
77
+ // Read file content
78
+ let content: Uint8Array | undefined;
79
+ if (type === 'file' && size > 0) {
80
+ content = data.slice(offset, offset + size);
81
+ // Move past content, rounded up to 512-byte boundary
82
+ offset += Math.ceil(size / 512) * 512;
83
+ }
84
+
85
+ yield {
86
+ name: fullName,
87
+ type,
88
+ size,
89
+ mode,
90
+ content,
91
+ linkTarget: type === 'symlink' ? linkName : undefined,
92
+ };
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Parse a null-terminated string from tar header
98
+ */
99
+ function parseString(data: Uint8Array, offset: number, length: number): string {
100
+ const bytes = data.slice(offset, offset + length);
101
+ const nullIndex = bytes.indexOf(0);
102
+ const actualBytes = nullIndex >= 0 ? bytes.slice(0, nullIndex) : bytes;
103
+ return new TextDecoder().decode(actualBytes);
104
+ }
105
+
106
+ /**
107
+ * Parse an octal number from tar header
108
+ */
109
+ function parseOctal(data: Uint8Array, offset: number, length: number): number {
110
+ const str = parseString(data, offset, length).trim();
111
+ return parseInt(str, 8) || 0;
112
+ }
113
+
114
+ /**
115
+ * Decompress gzipped data
116
+ */
117
+ export function decompress(data: ArrayBuffer | Uint8Array): Uint8Array {
118
+ const input = data instanceof Uint8Array ? data : new Uint8Array(data);
119
+ return pako.inflate(input);
120
+ }
121
+
122
+ /**
123
+ * Extract a tarball to the virtual file system
124
+ */
125
+ export function extractTarball(
126
+ tarballData: ArrayBuffer | Uint8Array,
127
+ vfs: VirtualFS,
128
+ destPath: string,
129
+ options: ExtractOptions = {}
130
+ ): string[] {
131
+ const { stripComponents = 1, filter, onProgress } = options;
132
+
133
+ // Decompress gzip
134
+ onProgress?.('Decompressing...');
135
+ const tarData = decompress(tarballData);
136
+
137
+ // Parse and extract tar entries
138
+ const extractedFiles: string[] = [];
139
+
140
+ for (const entry of parseTar(tarData)) {
141
+ // Skip non-file/directory entries for now
142
+ if (entry.type !== 'file' && entry.type !== 'directory') {
143
+ continue;
144
+ }
145
+
146
+ // Strip leading path components (npm packages have "package/" prefix)
147
+ let entryPath = entry.name;
148
+ if (stripComponents > 0) {
149
+ const parts = entryPath.split('/').filter(Boolean);
150
+ if (parts.length <= stripComponents) {
151
+ continue;
152
+ }
153
+ entryPath = parts.slice(stripComponents).join('/');
154
+ }
155
+
156
+ // Apply filter if provided
157
+ if (filter && !filter(entryPath)) {
158
+ continue;
159
+ }
160
+
161
+ // Build destination path
162
+ const fullPath = path.join(destPath, entryPath);
163
+
164
+ if (entry.type === 'directory') {
165
+ vfs.mkdirSync(fullPath, { recursive: true });
166
+ } else if (entry.type === 'file' && entry.content) {
167
+ // Ensure parent directory exists
168
+ const parentDir = path.dirname(fullPath);
169
+ vfs.mkdirSync(parentDir, { recursive: true });
170
+
171
+ // Write file
172
+ vfs.writeFileSync(fullPath, entry.content);
173
+ extractedFiles.push(fullPath);
174
+ }
175
+ }
176
+
177
+ onProgress?.(`Extracted ${extractedFiles.length} files`);
178
+
179
+ return extractedFiles;
180
+ }
181
+
182
+ /**
183
+ * Download and extract a tarball from URL
184
+ */
185
+ export async function downloadAndExtract(
186
+ url: string,
187
+ vfs: VirtualFS,
188
+ destPath: string,
189
+ options: ExtractOptions = {}
190
+ ): Promise<string[]> {
191
+ const { onProgress } = options;
192
+
193
+ onProgress?.(`Downloading ${url}...`);
194
+
195
+ const response = await fetch(url);
196
+ if (!response.ok) {
197
+ throw new Error(`Failed to download tarball: ${response.status}`);
198
+ }
199
+
200
+ const data = await response.arrayBuffer();
201
+
202
+ return extractTarball(data, vfs, destPath, options);
203
+ }
204
+
205
+ export default {
206
+ decompress,
207
+ extractTarball,
208
+ downloadAndExtract,
209
+ };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Runtime Interface - Common interface for main-thread and worker runtimes
3
+ */
4
+
5
+ import type { VirtualFS } from './virtual-fs';
6
+
7
+ export interface IRuntimeOptions {
8
+ cwd?: string;
9
+ env?: Record<string, string>;
10
+ onConsole?: (method: string, args: unknown[]) => void;
11
+ }
12
+
13
+ export interface IModule {
14
+ id: string;
15
+ filename: string;
16
+ exports: unknown;
17
+ loaded: boolean;
18
+ children: IModule[];
19
+ paths: string[];
20
+ }
21
+
22
+ export interface IExecuteResult {
23
+ exports: unknown;
24
+ module: IModule;
25
+ }
26
+
27
+ /**
28
+ * Common runtime interface implemented by both MainThreadRuntime and WorkerRuntime
29
+ */
30
+ export interface IRuntime {
31
+ /**
32
+ * Execute code as a module
33
+ */
34
+ execute(code: string, filename?: string): Promise<IExecuteResult>;
35
+
36
+ /**
37
+ * Run a file from the virtual file system
38
+ */
39
+ runFile(filename: string): Promise<IExecuteResult>;
40
+
41
+ /**
42
+ * Clear the module cache
43
+ */
44
+ clearCache(): void;
45
+
46
+ /**
47
+ * Get the virtual file system (only available on main thread runtime)
48
+ */
49
+ getVFS?(): VirtualFS;
50
+
51
+ /**
52
+ * Terminate the runtime (only applicable to worker runtime)
53
+ */
54
+ terminate?(): void;
55
+ }
56
+
57
+ /**
58
+ * Options for creating a runtime
59
+ */
60
+ export interface CreateRuntimeOptions extends IRuntimeOptions {
61
+ /**
62
+ * Cross-origin sandbox URL for secure code execution.
63
+ * When set, code runs in a cross-origin iframe, providing browser-enforced
64
+ * isolation from cookies, localStorage, and IndexedDB.
65
+ *
66
+ * Example: 'https://myapp-sandbox.vercel.app'
67
+ */
68
+ sandbox?: string;
69
+
70
+ /**
71
+ * Explicitly allow same-origin execution (less secure).
72
+ * Required when not using sandbox mode.
73
+ *
74
+ * WARNING: Same-origin execution allows untrusted code to access
75
+ * cookies, localStorage, and other same-origin resources.
76
+ * Only use this for trusted code or demos.
77
+ */
78
+ dangerouslyAllowSameOrigin?: boolean;
79
+
80
+ /**
81
+ * Whether to use a Web Worker for code execution (same-origin only)
82
+ * - false (default): Execute on main thread
83
+ * - true: Execute in a Web Worker
84
+ * - 'auto': Use worker if available, fallback to main thread
85
+ *
86
+ * Note: Workers provide thread isolation but NOT origin isolation.
87
+ * They still have access to IndexedDB and can make network requests.
88
+ */
89
+ useWorker?: boolean | 'auto';
90
+ }
91
+
92
+ /**
93
+ * VFS snapshot for transferring to worker
94
+ */
95
+ export interface VFSSnapshot {
96
+ files: VFSFileEntry[];
97
+ }
98
+
99
+ export interface VFSFileEntry {
100
+ path: string;
101
+ type: 'file' | 'directory';
102
+ content?: string; // base64 encoded for binary files
103
+ }