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.
- package/LICENSE +21 -0
- package/README.md +731 -0
- package/dist/__sw__.js +394 -0
- package/dist/ai-chatbot-demo-entry.d.ts +6 -0
- package/dist/ai-chatbot-demo-entry.d.ts.map +1 -0
- package/dist/ai-chatbot-demo.d.ts +42 -0
- package/dist/ai-chatbot-demo.d.ts.map +1 -0
- package/dist/assets/runtime-worker-D9x_Ddwz.js +60543 -0
- package/dist/assets/runtime-worker-D9x_Ddwz.js.map +1 -0
- package/dist/convex-app-demo-entry.d.ts +6 -0
- package/dist/convex-app-demo-entry.d.ts.map +1 -0
- package/dist/convex-app-demo.d.ts +68 -0
- package/dist/convex-app-demo.d.ts.map +1 -0
- package/dist/cors-proxy.d.ts +46 -0
- package/dist/cors-proxy.d.ts.map +1 -0
- package/dist/create-runtime.d.ts +42 -0
- package/dist/create-runtime.d.ts.map +1 -0
- package/dist/demo.d.ts +6 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/dev-server.d.ts +97 -0
- package/dist/dev-server.d.ts.map +1 -0
- package/dist/frameworks/next-dev-server.d.ts +202 -0
- package/dist/frameworks/next-dev-server.d.ts.map +1 -0
- package/dist/frameworks/vite-dev-server.d.ts +85 -0
- package/dist/frameworks/vite-dev-server.d.ts.map +1 -0
- package/dist/index.cjs +14965 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +14867 -0
- package/dist/index.mjs.map +1 -0
- package/dist/next-demo.d.ts +49 -0
- package/dist/next-demo.d.ts.map +1 -0
- package/dist/npm/index.d.ts +71 -0
- package/dist/npm/index.d.ts.map +1 -0
- package/dist/npm/registry.d.ts +66 -0
- package/dist/npm/registry.d.ts.map +1 -0
- package/dist/npm/resolver.d.ts +52 -0
- package/dist/npm/resolver.d.ts.map +1 -0
- package/dist/npm/tarball.d.ts +29 -0
- package/dist/npm/tarball.d.ts.map +1 -0
- package/dist/runtime-interface.d.ts +90 -0
- package/dist/runtime-interface.d.ts.map +1 -0
- package/dist/runtime.d.ts +103 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/sandbox-helpers.d.ts +43 -0
- package/dist/sandbox-helpers.d.ts.map +1 -0
- package/dist/sandbox-runtime.d.ts +65 -0
- package/dist/sandbox-runtime.d.ts.map +1 -0
- package/dist/server-bridge.d.ts +89 -0
- package/dist/server-bridge.d.ts.map +1 -0
- package/dist/shims/assert.d.ts +51 -0
- package/dist/shims/assert.d.ts.map +1 -0
- package/dist/shims/async_hooks.d.ts +37 -0
- package/dist/shims/async_hooks.d.ts.map +1 -0
- package/dist/shims/buffer.d.ts +20 -0
- package/dist/shims/buffer.d.ts.map +1 -0
- package/dist/shims/child_process-browser.d.ts +92 -0
- package/dist/shims/child_process-browser.d.ts.map +1 -0
- package/dist/shims/child_process.d.ts +93 -0
- package/dist/shims/child_process.d.ts.map +1 -0
- package/dist/shims/chokidar.d.ts +55 -0
- package/dist/shims/chokidar.d.ts.map +1 -0
- package/dist/shims/cluster.d.ts +52 -0
- package/dist/shims/cluster.d.ts.map +1 -0
- package/dist/shims/crypto.d.ts +122 -0
- package/dist/shims/crypto.d.ts.map +1 -0
- package/dist/shims/dgram.d.ts +34 -0
- package/dist/shims/dgram.d.ts.map +1 -0
- package/dist/shims/diagnostics_channel.d.ts +80 -0
- package/dist/shims/diagnostics_channel.d.ts.map +1 -0
- package/dist/shims/dns.d.ts +87 -0
- package/dist/shims/dns.d.ts.map +1 -0
- package/dist/shims/domain.d.ts +25 -0
- package/dist/shims/domain.d.ts.map +1 -0
- package/dist/shims/esbuild.d.ts +105 -0
- package/dist/shims/esbuild.d.ts.map +1 -0
- package/dist/shims/events.d.ts +37 -0
- package/dist/shims/events.d.ts.map +1 -0
- package/dist/shims/fs.d.ts +115 -0
- package/dist/shims/fs.d.ts.map +1 -0
- package/dist/shims/fsevents.d.ts +67 -0
- package/dist/shims/fsevents.d.ts.map +1 -0
- package/dist/shims/http.d.ts +217 -0
- package/dist/shims/http.d.ts.map +1 -0
- package/dist/shims/http2.d.ts +81 -0
- package/dist/shims/http2.d.ts.map +1 -0
- package/dist/shims/https.d.ts +36 -0
- package/dist/shims/https.d.ts.map +1 -0
- package/dist/shims/inspector.d.ts +25 -0
- package/dist/shims/inspector.d.ts.map +1 -0
- package/dist/shims/module.d.ts +22 -0
- package/dist/shims/module.d.ts.map +1 -0
- package/dist/shims/net.d.ts +100 -0
- package/dist/shims/net.d.ts.map +1 -0
- package/dist/shims/os.d.ts +159 -0
- package/dist/shims/os.d.ts.map +1 -0
- package/dist/shims/path.d.ts +72 -0
- package/dist/shims/path.d.ts.map +1 -0
- package/dist/shims/perf_hooks.d.ts +50 -0
- package/dist/shims/perf_hooks.d.ts.map +1 -0
- package/dist/shims/process.d.ts +93 -0
- package/dist/shims/process.d.ts.map +1 -0
- package/dist/shims/querystring.d.ts +23 -0
- package/dist/shims/querystring.d.ts.map +1 -0
- package/dist/shims/readdirp.d.ts +52 -0
- package/dist/shims/readdirp.d.ts.map +1 -0
- package/dist/shims/readline.d.ts +62 -0
- package/dist/shims/readline.d.ts.map +1 -0
- package/dist/shims/rollup.d.ts +34 -0
- package/dist/shims/rollup.d.ts.map +1 -0
- package/dist/shims/sentry.d.ts +163 -0
- package/dist/shims/sentry.d.ts.map +1 -0
- package/dist/shims/stream.d.ts +181 -0
- package/dist/shims/stream.d.ts.map +1 -0
- package/dist/shims/tls.d.ts +53 -0
- package/dist/shims/tls.d.ts.map +1 -0
- package/dist/shims/tty.d.ts +30 -0
- package/dist/shims/tty.d.ts.map +1 -0
- package/dist/shims/url.d.ts +64 -0
- package/dist/shims/url.d.ts.map +1 -0
- package/dist/shims/util.d.ts +106 -0
- package/dist/shims/util.d.ts.map +1 -0
- package/dist/shims/v8.d.ts +73 -0
- package/dist/shims/v8.d.ts.map +1 -0
- package/dist/shims/vfs-adapter.d.ts +126 -0
- package/dist/shims/vfs-adapter.d.ts.map +1 -0
- package/dist/shims/vm.d.ts +45 -0
- package/dist/shims/vm.d.ts.map +1 -0
- package/dist/shims/worker_threads.d.ts +66 -0
- package/dist/shims/worker_threads.d.ts.map +1 -0
- package/dist/shims/ws.d.ts +66 -0
- package/dist/shims/ws.d.ts.map +1 -0
- package/dist/shims/zlib.d.ts +161 -0
- package/dist/shims/zlib.d.ts.map +1 -0
- package/dist/transform.d.ts +24 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/virtual-fs.d.ts +226 -0
- package/dist/virtual-fs.d.ts.map +1 -0
- package/dist/vite-demo.d.ts +35 -0
- package/dist/vite-demo.d.ts.map +1 -0
- package/dist/vite-sw.js +132 -0
- package/dist/worker/runtime-worker.d.ts +8 -0
- package/dist/worker/runtime-worker.d.ts.map +1 -0
- package/dist/worker-runtime.d.ts +50 -0
- package/dist/worker-runtime.d.ts.map +1 -0
- package/package.json +85 -0
- package/src/ai-chatbot-demo-entry.ts +244 -0
- package/src/ai-chatbot-demo.ts +509 -0
- package/src/convex-app-demo-entry.ts +1107 -0
- package/src/convex-app-demo.ts +1316 -0
- package/src/cors-proxy.ts +81 -0
- package/src/create-runtime.ts +147 -0
- package/src/demo.ts +304 -0
- package/src/dev-server.ts +274 -0
- package/src/frameworks/next-dev-server.ts +2224 -0
- package/src/frameworks/vite-dev-server.ts +702 -0
- package/src/index.ts +101 -0
- package/src/next-demo.ts +1784 -0
- package/src/npm/index.ts +347 -0
- package/src/npm/registry.ts +152 -0
- package/src/npm/resolver.ts +385 -0
- package/src/npm/tarball.ts +209 -0
- package/src/runtime-interface.ts +103 -0
- package/src/runtime.ts +1046 -0
- package/src/sandbox-helpers.ts +173 -0
- package/src/sandbox-runtime.ts +252 -0
- package/src/server-bridge.ts +426 -0
- package/src/shims/assert.ts +664 -0
- package/src/shims/async_hooks.ts +86 -0
- package/src/shims/buffer.ts +75 -0
- package/src/shims/child_process-browser.ts +217 -0
- package/src/shims/child_process.ts +463 -0
- package/src/shims/chokidar.ts +313 -0
- package/src/shims/cluster.ts +67 -0
- package/src/shims/crypto.ts +830 -0
- package/src/shims/dgram.ts +47 -0
- package/src/shims/diagnostics_channel.ts +196 -0
- package/src/shims/dns.ts +172 -0
- package/src/shims/domain.ts +58 -0
- package/src/shims/esbuild.ts +805 -0
- package/src/shims/events.ts +195 -0
- package/src/shims/fs.ts +803 -0
- package/src/shims/fsevents.ts +63 -0
- package/src/shims/http.ts +904 -0
- package/src/shims/http2.ts +96 -0
- package/src/shims/https.ts +86 -0
- package/src/shims/inspector.ts +30 -0
- package/src/shims/module.ts +82 -0
- package/src/shims/net.ts +359 -0
- package/src/shims/os.ts +195 -0
- package/src/shims/path.ts +199 -0
- package/src/shims/perf_hooks.ts +92 -0
- package/src/shims/process.ts +346 -0
- package/src/shims/querystring.ts +97 -0
- package/src/shims/readdirp.ts +228 -0
- package/src/shims/readline.ts +110 -0
- package/src/shims/rollup.ts +80 -0
- package/src/shims/sentry.ts +133 -0
- package/src/shims/stream.ts +1126 -0
- package/src/shims/tls.ts +95 -0
- package/src/shims/tty.ts +64 -0
- package/src/shims/url.ts +171 -0
- package/src/shims/util.ts +312 -0
- package/src/shims/v8.ts +113 -0
- package/src/shims/vfs-adapter.ts +402 -0
- package/src/shims/vm.ts +83 -0
- package/src/shims/worker_threads.ts +111 -0
- package/src/shims/ws.ts +382 -0
- package/src/shims/zlib.ts +289 -0
- package/src/transform.ts +313 -0
- package/src/types/external.d.ts +67 -0
- package/src/virtual-fs.ts +903 -0
- package/src/vite-demo.ts +577 -0
- package/src/worker/runtime-worker.ts +128 -0
- 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
|
+
}
|