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
package/src/npm/index.ts
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm Package Manager
|
|
3
|
+
* Orchestrates package installation into the virtual file system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { VirtualFS } from '../virtual-fs';
|
|
7
|
+
import { Registry, RegistryOptions } from './registry';
|
|
8
|
+
import {
|
|
9
|
+
resolveDependencies,
|
|
10
|
+
resolveFromPackageJson,
|
|
11
|
+
ResolvedPackage,
|
|
12
|
+
ResolveOptions,
|
|
13
|
+
} from './resolver';
|
|
14
|
+
import { downloadAndExtract, extractTarball } from './tarball';
|
|
15
|
+
import * as path from '../shims/path';
|
|
16
|
+
import { initTransformer, transformPackage, isTransformerReady } from '../transform';
|
|
17
|
+
|
|
18
|
+
export interface InstallOptions {
|
|
19
|
+
registry?: string;
|
|
20
|
+
save?: boolean;
|
|
21
|
+
saveDev?: boolean;
|
|
22
|
+
includeDev?: boolean;
|
|
23
|
+
includeOptional?: boolean;
|
|
24
|
+
onProgress?: (message: string) => void;
|
|
25
|
+
/** Transform ESM packages to CJS after install (default: true) */
|
|
26
|
+
transform?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface InstallResult {
|
|
30
|
+
installed: Map<string, ResolvedPackage>;
|
|
31
|
+
added: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* npm Package Manager for VirtualFS
|
|
36
|
+
*/
|
|
37
|
+
export class PackageManager {
|
|
38
|
+
private vfs: VirtualFS;
|
|
39
|
+
private registry: Registry;
|
|
40
|
+
private cwd: string;
|
|
41
|
+
|
|
42
|
+
constructor(vfs: VirtualFS, options: { cwd?: string } & RegistryOptions = {}) {
|
|
43
|
+
this.vfs = vfs;
|
|
44
|
+
this.registry = new Registry(options);
|
|
45
|
+
this.cwd = options.cwd || '/';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Install a package and its dependencies
|
|
50
|
+
*/
|
|
51
|
+
async install(
|
|
52
|
+
packageSpec: string,
|
|
53
|
+
options: InstallOptions = {}
|
|
54
|
+
): Promise<InstallResult> {
|
|
55
|
+
const { onProgress } = options;
|
|
56
|
+
|
|
57
|
+
// Parse package spec (name@version)
|
|
58
|
+
const { name, version } = parsePackageSpec(packageSpec);
|
|
59
|
+
|
|
60
|
+
onProgress?.(`Resolving ${name}@${version || 'latest'}...`);
|
|
61
|
+
|
|
62
|
+
// Resolve dependencies
|
|
63
|
+
const resolved = await resolveDependencies(name, version || 'latest', {
|
|
64
|
+
registry: this.registry,
|
|
65
|
+
includeDev: options.includeDev,
|
|
66
|
+
includeOptional: options.includeOptional,
|
|
67
|
+
onProgress,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Install all resolved packages
|
|
71
|
+
const added = await this.installResolved(resolved, options);
|
|
72
|
+
|
|
73
|
+
// Update package.json if save option is set
|
|
74
|
+
if (options.save || options.saveDev) {
|
|
75
|
+
const pkgToAdd = resolved.get(name);
|
|
76
|
+
if (pkgToAdd) {
|
|
77
|
+
await this.updatePackageJson(
|
|
78
|
+
name,
|
|
79
|
+
`^${pkgToAdd.version}`,
|
|
80
|
+
options.saveDev || false
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onProgress?.(`Installed ${resolved.size} packages`);
|
|
86
|
+
|
|
87
|
+
return { installed: resolved, added };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Install all dependencies from package.json
|
|
92
|
+
*/
|
|
93
|
+
async installFromPackageJson(options: InstallOptions = {}): Promise<InstallResult> {
|
|
94
|
+
const { onProgress } = options;
|
|
95
|
+
|
|
96
|
+
const pkgJsonPath = path.join(this.cwd, 'package.json');
|
|
97
|
+
|
|
98
|
+
if (!this.vfs.existsSync(pkgJsonPath)) {
|
|
99
|
+
throw new Error('No package.json found');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const pkgJson = JSON.parse(this.vfs.readFileSync(pkgJsonPath, 'utf8'));
|
|
103
|
+
|
|
104
|
+
onProgress?.('Resolving dependencies...');
|
|
105
|
+
|
|
106
|
+
// Resolve all dependencies
|
|
107
|
+
const resolved = await resolveFromPackageJson(pkgJson, {
|
|
108
|
+
registry: this.registry,
|
|
109
|
+
includeDev: options.includeDev,
|
|
110
|
+
includeOptional: options.includeOptional,
|
|
111
|
+
onProgress,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Install all resolved packages
|
|
115
|
+
const added = await this.installResolved(resolved, options);
|
|
116
|
+
|
|
117
|
+
onProgress?.(`Installed ${resolved.size} packages`);
|
|
118
|
+
|
|
119
|
+
return { installed: resolved, added };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Install resolved packages to node_modules
|
|
124
|
+
*/
|
|
125
|
+
private async installResolved(
|
|
126
|
+
resolved: Map<string, ResolvedPackage>,
|
|
127
|
+
options: InstallOptions
|
|
128
|
+
): Promise<string[]> {
|
|
129
|
+
const { onProgress } = options;
|
|
130
|
+
const added: string[] = [];
|
|
131
|
+
|
|
132
|
+
// Ensure node_modules exists
|
|
133
|
+
const nodeModulesPath = path.join(this.cwd, 'node_modules');
|
|
134
|
+
this.vfs.mkdirSync(nodeModulesPath, { recursive: true });
|
|
135
|
+
|
|
136
|
+
// Filter packages that need to be installed
|
|
137
|
+
const toInstall: Array<{ name: string; pkg: ResolvedPackage; pkgPath: string }> = [];
|
|
138
|
+
|
|
139
|
+
for (const [name, pkg] of resolved) {
|
|
140
|
+
const pkgPath = path.join(nodeModulesPath, name);
|
|
141
|
+
|
|
142
|
+
// Skip if already installed with same version
|
|
143
|
+
const existingPkgJson = path.join(pkgPath, 'package.json');
|
|
144
|
+
if (this.vfs.existsSync(existingPkgJson)) {
|
|
145
|
+
try {
|
|
146
|
+
const existing = JSON.parse(
|
|
147
|
+
this.vfs.readFileSync(existingPkgJson, 'utf8')
|
|
148
|
+
);
|
|
149
|
+
if (existing.version === pkg.version) {
|
|
150
|
+
onProgress?.(`Skipping ${name}@${pkg.version} (already installed)`);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
// Continue with installation if package.json is invalid
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
toInstall.push({ name, pkg, pkgPath });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Initialize transformer if transform option is enabled (default: true)
|
|
162
|
+
const shouldTransform = options.transform !== false;
|
|
163
|
+
if (shouldTransform && !isTransformerReady()) {
|
|
164
|
+
onProgress?.('Initializing ESM transformer...');
|
|
165
|
+
await initTransformer();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Install packages in parallel (limit concurrency to avoid overwhelming the browser)
|
|
169
|
+
const CONCURRENCY = 6;
|
|
170
|
+
onProgress?.(`Installing ${toInstall.length} packages...`);
|
|
171
|
+
|
|
172
|
+
for (let i = 0; i < toInstall.length; i += CONCURRENCY) {
|
|
173
|
+
const batch = toInstall.slice(i, i + CONCURRENCY);
|
|
174
|
+
|
|
175
|
+
await Promise.all(
|
|
176
|
+
batch.map(async ({ name, pkg, pkgPath }) => {
|
|
177
|
+
onProgress?.(` Downloading ${name}@${pkg.version}...`);
|
|
178
|
+
|
|
179
|
+
// Download and extract tarball
|
|
180
|
+
await downloadAndExtract(pkg.tarballUrl, this.vfs, pkgPath, {
|
|
181
|
+
stripComponents: 1, // Strip "package/" prefix
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Transform ESM to CJS
|
|
185
|
+
if (shouldTransform) {
|
|
186
|
+
try {
|
|
187
|
+
const count = await transformPackage(this.vfs, pkgPath, onProgress);
|
|
188
|
+
if (count > 0) {
|
|
189
|
+
onProgress?.(` Transformed ${count} files in ${name}`);
|
|
190
|
+
}
|
|
191
|
+
} catch (transformError) {
|
|
192
|
+
onProgress?.(` Warning: Transform failed for ${name}: ${transformError}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
added.push(name);
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Create .package-lock.json for tracking
|
|
202
|
+
await this.writeLockfile(resolved);
|
|
203
|
+
|
|
204
|
+
return added;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Write lockfile with resolved versions
|
|
209
|
+
*/
|
|
210
|
+
private async writeLockfile(
|
|
211
|
+
resolved: Map<string, ResolvedPackage>
|
|
212
|
+
): Promise<void> {
|
|
213
|
+
const lockfile: Record<string, { version: string; resolved: string }> = {};
|
|
214
|
+
|
|
215
|
+
for (const [name, pkg] of resolved) {
|
|
216
|
+
lockfile[name] = {
|
|
217
|
+
version: pkg.version,
|
|
218
|
+
resolved: pkg.tarballUrl,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const lockfilePath = path.join(this.cwd, 'node_modules', '.package-lock.json');
|
|
223
|
+
this.vfs.writeFileSync(lockfilePath, JSON.stringify(lockfile, null, 2));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Update package.json with new dependency
|
|
228
|
+
*/
|
|
229
|
+
private async updatePackageJson(
|
|
230
|
+
packageName: string,
|
|
231
|
+
version: string,
|
|
232
|
+
isDev: boolean
|
|
233
|
+
): Promise<void> {
|
|
234
|
+
const pkgJsonPath = path.join(this.cwd, 'package.json');
|
|
235
|
+
|
|
236
|
+
let pkgJson: Record<string, unknown> = {};
|
|
237
|
+
|
|
238
|
+
if (this.vfs.existsSync(pkgJsonPath)) {
|
|
239
|
+
pkgJson = JSON.parse(this.vfs.readFileSync(pkgJsonPath, 'utf8'));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const field = isDev ? 'devDependencies' : 'dependencies';
|
|
243
|
+
|
|
244
|
+
if (!pkgJson[field]) {
|
|
245
|
+
pkgJson[field] = {};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
(pkgJson[field] as Record<string, string>)[packageName] = version;
|
|
249
|
+
|
|
250
|
+
this.vfs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* List installed packages
|
|
255
|
+
*/
|
|
256
|
+
list(): Record<string, string> {
|
|
257
|
+
const nodeModulesPath = path.join(this.cwd, 'node_modules');
|
|
258
|
+
|
|
259
|
+
if (!this.vfs.existsSync(nodeModulesPath)) {
|
|
260
|
+
return {};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const packages: Record<string, string> = {};
|
|
264
|
+
const entries = this.vfs.readdirSync(nodeModulesPath);
|
|
265
|
+
|
|
266
|
+
for (const entry of entries) {
|
|
267
|
+
// Skip hidden files and non-package entries
|
|
268
|
+
if (entry.startsWith('.')) continue;
|
|
269
|
+
|
|
270
|
+
// Handle scoped packages (@org/pkg)
|
|
271
|
+
if (entry.startsWith('@')) {
|
|
272
|
+
const scopePath = path.join(nodeModulesPath, entry);
|
|
273
|
+
const scopedPkgs = this.vfs.readdirSync(scopePath);
|
|
274
|
+
|
|
275
|
+
for (const scopedPkg of scopedPkgs) {
|
|
276
|
+
const pkgJsonPath = path.join(scopePath, scopedPkg, 'package.json');
|
|
277
|
+
if (this.vfs.existsSync(pkgJsonPath)) {
|
|
278
|
+
const pkgJson = JSON.parse(this.vfs.readFileSync(pkgJsonPath, 'utf8'));
|
|
279
|
+
packages[`${entry}/${scopedPkg}`] = pkgJson.version;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
const pkgJsonPath = path.join(nodeModulesPath, entry, 'package.json');
|
|
284
|
+
if (this.vfs.existsSync(pkgJsonPath)) {
|
|
285
|
+
const pkgJson = JSON.parse(this.vfs.readFileSync(pkgJsonPath, 'utf8'));
|
|
286
|
+
packages[entry] = pkgJson.version;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return packages;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Parse a package specifier into name and version
|
|
297
|
+
* Examples: "express", "express@4.18.2", "@types/node@18"
|
|
298
|
+
*/
|
|
299
|
+
function parsePackageSpec(spec: string): { name: string; version?: string } {
|
|
300
|
+
// Handle scoped packages
|
|
301
|
+
if (spec.startsWith('@')) {
|
|
302
|
+
const slashIndex = spec.indexOf('/');
|
|
303
|
+
if (slashIndex === -1) {
|
|
304
|
+
throw new Error(`Invalid package spec: ${spec}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const afterSlash = spec.slice(slashIndex + 1);
|
|
308
|
+
const atIndex = afterSlash.indexOf('@');
|
|
309
|
+
|
|
310
|
+
if (atIndex === -1) {
|
|
311
|
+
return { name: spec };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
name: spec.slice(0, slashIndex + 1 + atIndex),
|
|
316
|
+
version: afterSlash.slice(atIndex + 1),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Regular packages
|
|
321
|
+
const atIndex = spec.indexOf('@');
|
|
322
|
+
if (atIndex === -1) {
|
|
323
|
+
return { name: spec };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
name: spec.slice(0, atIndex),
|
|
328
|
+
version: spec.slice(atIndex + 1),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Convenience function for quick installs
|
|
333
|
+
export async function install(
|
|
334
|
+
packageSpec: string,
|
|
335
|
+
vfs: VirtualFS,
|
|
336
|
+
options?: InstallOptions
|
|
337
|
+
): Promise<InstallResult> {
|
|
338
|
+
const pm = new PackageManager(vfs);
|
|
339
|
+
return pm.install(packageSpec, options);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Re-export types and modules
|
|
343
|
+
export { Registry } from './registry';
|
|
344
|
+
export type { RegistryOptions, PackageVersion, PackageManifest } from './registry';
|
|
345
|
+
export type { ResolvedPackage, ResolveOptions } from './resolver';
|
|
346
|
+
export type { ExtractOptions } from './tarball';
|
|
347
|
+
export { parsePackageSpec };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm Registry Client
|
|
3
|
+
* Fetches package metadata from npm registry
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface PackageVersion {
|
|
7
|
+
name: string;
|
|
8
|
+
version: string;
|
|
9
|
+
dependencies?: Record<string, string>;
|
|
10
|
+
devDependencies?: Record<string, string>;
|
|
11
|
+
peerDependencies?: Record<string, string>;
|
|
12
|
+
optionalDependencies?: Record<string, string>;
|
|
13
|
+
dist: {
|
|
14
|
+
tarball: string;
|
|
15
|
+
shasum: string;
|
|
16
|
+
integrity?: string;
|
|
17
|
+
};
|
|
18
|
+
main?: string;
|
|
19
|
+
module?: string;
|
|
20
|
+
exports?: Record<string, unknown>;
|
|
21
|
+
bin?: Record<string, string> | string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PackageManifest {
|
|
25
|
+
name: string;
|
|
26
|
+
'dist-tags': {
|
|
27
|
+
latest: string;
|
|
28
|
+
[tag: string]: string;
|
|
29
|
+
};
|
|
30
|
+
versions: Record<string, PackageVersion>;
|
|
31
|
+
time?: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RegistryOptions {
|
|
35
|
+
registry?: string;
|
|
36
|
+
cache?: Map<string, PackageManifest>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
|
|
40
|
+
|
|
41
|
+
export class Registry {
|
|
42
|
+
private registryUrl: string;
|
|
43
|
+
private cache: Map<string, PackageManifest>;
|
|
44
|
+
|
|
45
|
+
constructor(options: RegistryOptions = {}) {
|
|
46
|
+
this.registryUrl = options.registry || DEFAULT_REGISTRY;
|
|
47
|
+
this.cache = options.cache || new Map();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Fetch package manifest (all versions metadata)
|
|
52
|
+
*/
|
|
53
|
+
async getPackageManifest(packageName: string): Promise<PackageManifest> {
|
|
54
|
+
// Check cache first
|
|
55
|
+
if (this.cache.has(packageName)) {
|
|
56
|
+
return this.cache.get(packageName)!;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const url = `${this.registryUrl}/${encodePackageName(packageName)}`;
|
|
60
|
+
|
|
61
|
+
const response = await fetch(url, {
|
|
62
|
+
headers: {
|
|
63
|
+
Accept: 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8',
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
if (response.status === 404) {
|
|
69
|
+
throw new Error(`Package not found: ${packageName}`);
|
|
70
|
+
}
|
|
71
|
+
throw new Error(`Failed to fetch package ${packageName}: ${response.status}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const manifest = (await response.json()) as PackageManifest;
|
|
75
|
+
|
|
76
|
+
// Cache the result
|
|
77
|
+
this.cache.set(packageName, manifest);
|
|
78
|
+
|
|
79
|
+
return manifest;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get specific version metadata
|
|
84
|
+
*/
|
|
85
|
+
async getPackageVersion(
|
|
86
|
+
packageName: string,
|
|
87
|
+
version: string
|
|
88
|
+
): Promise<PackageVersion> {
|
|
89
|
+
const manifest = await this.getPackageManifest(packageName);
|
|
90
|
+
|
|
91
|
+
// Handle dist-tags (like "latest", "next", etc.)
|
|
92
|
+
if (manifest['dist-tags'][version]) {
|
|
93
|
+
version = manifest['dist-tags'][version];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const versionData = manifest.versions[version];
|
|
97
|
+
if (!versionData) {
|
|
98
|
+
throw new Error(`Version ${version} not found for package ${packageName}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return versionData;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get latest version number
|
|
106
|
+
*/
|
|
107
|
+
async getLatestVersion(packageName: string): Promise<string> {
|
|
108
|
+
const manifest = await this.getPackageManifest(packageName);
|
|
109
|
+
return manifest['dist-tags'].latest;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get all available versions
|
|
114
|
+
*/
|
|
115
|
+
async getVersions(packageName: string): Promise<string[]> {
|
|
116
|
+
const manifest = await this.getPackageManifest(packageName);
|
|
117
|
+
return Object.keys(manifest.versions);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Download tarball as ArrayBuffer
|
|
122
|
+
*/
|
|
123
|
+
async downloadTarball(tarballUrl: string): Promise<ArrayBuffer> {
|
|
124
|
+
const response = await fetch(tarballUrl);
|
|
125
|
+
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
throw new Error(`Failed to download tarball: ${response.status}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return response.arrayBuffer();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Clear the cache
|
|
135
|
+
*/
|
|
136
|
+
clearCache(): void {
|
|
137
|
+
this.cache.clear();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Encode scoped package names for URL
|
|
143
|
+
* @scoped/package -> @scoped%2fpackage
|
|
144
|
+
*/
|
|
145
|
+
function encodePackageName(name: string): string {
|
|
146
|
+
return name.replace('/', '%2f');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Default registry instance
|
|
150
|
+
export const registry = new Registry();
|
|
151
|
+
|
|
152
|
+
export default Registry;
|