@scelar/nodepod 1.0.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 (134) hide show
  1. package/LICENSE +43 -0
  2. package/README.md +240 -0
  3. package/dist/child_process-BJOMsZje.js +8233 -0
  4. package/dist/child_process-BJOMsZje.js.map +1 -0
  5. package/dist/child_process-Cj8vOcuc.cjs +7434 -0
  6. package/dist/child_process-Cj8vOcuc.cjs.map +1 -0
  7. package/dist/index-Cb1Cgdnd.js +35308 -0
  8. package/dist/index-Cb1Cgdnd.js.map +1 -0
  9. package/dist/index-DsMGS-xc.cjs +37195 -0
  10. package/dist/index-DsMGS-xc.cjs.map +1 -0
  11. package/dist/index.cjs +65 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.mjs +59 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/package.json +95 -0
  16. package/src/__tests__/smoke.test.ts +11 -0
  17. package/src/constants/cdn-urls.ts +18 -0
  18. package/src/constants/config.ts +236 -0
  19. package/src/cross-origin.ts +26 -0
  20. package/src/engine-factory.ts +176 -0
  21. package/src/engine-types.ts +56 -0
  22. package/src/helpers/byte-encoding.ts +39 -0
  23. package/src/helpers/digest.ts +9 -0
  24. package/src/helpers/event-loop.ts +96 -0
  25. package/src/helpers/wasm-cache.ts +133 -0
  26. package/src/iframe-sandbox.ts +141 -0
  27. package/src/index.ts +192 -0
  28. package/src/isolation-helpers.ts +148 -0
  29. package/src/memory-volume.ts +941 -0
  30. package/src/module-transformer.ts +368 -0
  31. package/src/packages/archive-extractor.ts +248 -0
  32. package/src/packages/browser-bundler.ts +284 -0
  33. package/src/packages/installer.ts +396 -0
  34. package/src/packages/registry-client.ts +131 -0
  35. package/src/packages/version-resolver.ts +411 -0
  36. package/src/polyfills/assert.ts +384 -0
  37. package/src/polyfills/async_hooks.ts +144 -0
  38. package/src/polyfills/buffer.ts +628 -0
  39. package/src/polyfills/child_process.ts +2288 -0
  40. package/src/polyfills/chokidar.ts +336 -0
  41. package/src/polyfills/cluster.ts +106 -0
  42. package/src/polyfills/console.ts +136 -0
  43. package/src/polyfills/constants.ts +123 -0
  44. package/src/polyfills/crypto.ts +885 -0
  45. package/src/polyfills/dgram.ts +87 -0
  46. package/src/polyfills/diagnostics_channel.ts +76 -0
  47. package/src/polyfills/dns.ts +134 -0
  48. package/src/polyfills/domain.ts +68 -0
  49. package/src/polyfills/esbuild.ts +854 -0
  50. package/src/polyfills/events.ts +276 -0
  51. package/src/polyfills/fs.ts +2888 -0
  52. package/src/polyfills/fsevents.ts +79 -0
  53. package/src/polyfills/http.ts +1449 -0
  54. package/src/polyfills/http2.ts +199 -0
  55. package/src/polyfills/https.ts +76 -0
  56. package/src/polyfills/inspector.ts +62 -0
  57. package/src/polyfills/lightningcss.ts +105 -0
  58. package/src/polyfills/module.ts +191 -0
  59. package/src/polyfills/net.ts +353 -0
  60. package/src/polyfills/os.ts +238 -0
  61. package/src/polyfills/path.ts +206 -0
  62. package/src/polyfills/perf_hooks.ts +102 -0
  63. package/src/polyfills/process.ts +690 -0
  64. package/src/polyfills/punycode.ts +159 -0
  65. package/src/polyfills/querystring.ts +93 -0
  66. package/src/polyfills/quic.ts +118 -0
  67. package/src/polyfills/readdirp.ts +229 -0
  68. package/src/polyfills/readline.ts +692 -0
  69. package/src/polyfills/repl.ts +134 -0
  70. package/src/polyfills/rollup.ts +119 -0
  71. package/src/polyfills/sea.ts +33 -0
  72. package/src/polyfills/sqlite.ts +78 -0
  73. package/src/polyfills/stream.ts +1620 -0
  74. package/src/polyfills/string_decoder.ts +25 -0
  75. package/src/polyfills/tailwindcss-oxide.ts +309 -0
  76. package/src/polyfills/test.ts +197 -0
  77. package/src/polyfills/timers.ts +32 -0
  78. package/src/polyfills/tls.ts +105 -0
  79. package/src/polyfills/trace_events.ts +50 -0
  80. package/src/polyfills/tty.ts +71 -0
  81. package/src/polyfills/url.ts +174 -0
  82. package/src/polyfills/util.ts +559 -0
  83. package/src/polyfills/v8.ts +126 -0
  84. package/src/polyfills/vm.ts +132 -0
  85. package/src/polyfills/volume-registry.ts +15 -0
  86. package/src/polyfills/wasi.ts +44 -0
  87. package/src/polyfills/worker_threads.ts +326 -0
  88. package/src/polyfills/ws.ts +595 -0
  89. package/src/polyfills/zlib.ts +881 -0
  90. package/src/request-proxy.ts +716 -0
  91. package/src/script-engine.ts +3375 -0
  92. package/src/sdk/nodepod-fs.ts +93 -0
  93. package/src/sdk/nodepod-process.ts +86 -0
  94. package/src/sdk/nodepod-terminal.ts +350 -0
  95. package/src/sdk/nodepod.ts +509 -0
  96. package/src/sdk/types.ts +70 -0
  97. package/src/shell/commands/bun.ts +121 -0
  98. package/src/shell/commands/directory.ts +297 -0
  99. package/src/shell/commands/file-ops.ts +525 -0
  100. package/src/shell/commands/git.ts +2142 -0
  101. package/src/shell/commands/node.ts +80 -0
  102. package/src/shell/commands/npm.ts +198 -0
  103. package/src/shell/commands/pm-types.ts +45 -0
  104. package/src/shell/commands/pnpm.ts +82 -0
  105. package/src/shell/commands/search.ts +264 -0
  106. package/src/shell/commands/shell-env.ts +352 -0
  107. package/src/shell/commands/text-processing.ts +1152 -0
  108. package/src/shell/commands/yarn.ts +84 -0
  109. package/src/shell/shell-builtins.ts +19 -0
  110. package/src/shell/shell-helpers.ts +250 -0
  111. package/src/shell/shell-interpreter.ts +514 -0
  112. package/src/shell/shell-parser.ts +429 -0
  113. package/src/shell/shell-types.ts +85 -0
  114. package/src/syntax-transforms.ts +561 -0
  115. package/src/threading/engine-worker.ts +64 -0
  116. package/src/threading/inline-worker.ts +372 -0
  117. package/src/threading/offload-types.ts +112 -0
  118. package/src/threading/offload-worker.ts +383 -0
  119. package/src/threading/offload.ts +271 -0
  120. package/src/threading/process-context.ts +92 -0
  121. package/src/threading/process-handle.ts +275 -0
  122. package/src/threading/process-manager.ts +956 -0
  123. package/src/threading/process-worker-entry.ts +854 -0
  124. package/src/threading/shared-vfs.ts +352 -0
  125. package/src/threading/sync-channel.ts +135 -0
  126. package/src/threading/task-queue.ts +177 -0
  127. package/src/threading/vfs-bridge.ts +231 -0
  128. package/src/threading/worker-pool.ts +233 -0
  129. package/src/threading/worker-protocol.ts +358 -0
  130. package/src/threading/worker-vfs.ts +218 -0
  131. package/src/types/externals.d.ts +38 -0
  132. package/src/types/fs-streams.ts +142 -0
  133. package/src/types/manifest.ts +17 -0
  134. package/src/worker-sandbox.ts +90 -0
@@ -0,0 +1,131 @@
1
+ // Registry Client — fetches package metadata, versions, and tarballs from npm.
2
+
3
+ export interface VersionDetail {
4
+ name: string;
5
+ version: string;
6
+ dependencies?: Record<string, string>;
7
+ devDependencies?: Record<string, string>;
8
+ peerDependencies?: Record<string, string>;
9
+ peerDependenciesMeta?: Record<string, { optional?: boolean }>;
10
+ optionalDependencies?: Record<string, string>;
11
+ dist: {
12
+ tarball: string;
13
+ shasum: string;
14
+ integrity?: string;
15
+ };
16
+ main?: string;
17
+ module?: string;
18
+ exports?: Record<string, unknown>;
19
+ bin?: Record<string, string> | string;
20
+ }
21
+
22
+ export interface PackageMetadata {
23
+ name: string;
24
+ 'dist-tags': {
25
+ latest: string;
26
+ [label: string]: string;
27
+ };
28
+ versions: Record<string, VersionDetail>;
29
+ time?: Record<string, string>;
30
+ }
31
+
32
+ export interface RegistryConfig {
33
+ endpoint?: string;
34
+ metadataCache?: Map<string, PackageMetadata>;
35
+ }
36
+
37
+ import { NPM_REGISTRY_URL } from "../constants/config";
38
+
39
+ const NPM_REGISTRY_BASE = NPM_REGISTRY_URL;
40
+
41
+ // @scope/pkg -> @scope%2fpkg
42
+ function encodeForUrl(pkgName: string): string {
43
+ return pkgName.replace(/\//g, '%2f');
44
+ }
45
+
46
+ export class RegistryClient {
47
+ private baseUrl: string;
48
+ private metadataStore: Map<string, PackageMetadata>;
49
+
50
+ constructor(config: RegistryConfig = {}) {
51
+ this.baseUrl = (config.endpoint || NPM_REGISTRY_BASE).replace(/\/+$/, '');
52
+ this.metadataStore = config.metadataCache || new Map();
53
+ }
54
+
55
+ // Cached per client instance
56
+ async fetchManifest(name: string): Promise<PackageMetadata> {
57
+ const cached = this.metadataStore.get(name);
58
+ if (cached) {
59
+ return cached;
60
+ }
61
+
62
+ const requestUrl = `${this.baseUrl}/${encodeForUrl(name)}`;
63
+
64
+ const resp = await fetch(requestUrl, {
65
+ headers: {
66
+ Accept:
67
+ 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8',
68
+ },
69
+ });
70
+
71
+ if (resp.status === 404) {
72
+ throw new Error(`Package "${name}" does not exist in the registry`);
73
+ }
74
+ if (!resp.ok) {
75
+ throw new Error(
76
+ `Registry request for "${name}" failed with HTTP ${resp.status}`
77
+ );
78
+ }
79
+
80
+ const metadata = (await resp.json()) as PackageMetadata;
81
+ this.metadataStore.set(name, metadata);
82
+
83
+ return metadata;
84
+ }
85
+
86
+ // Resolves dist-tags (e.g. "latest", "next") to concrete versions
87
+ async fetchVersion(name: string, version: string): Promise<VersionDetail> {
88
+ const metadata = await this.fetchManifest(name);
89
+
90
+ const taggedVersion = metadata['dist-tags'][version];
91
+ const resolvedVersion = taggedVersion || version;
92
+
93
+ const detail = metadata.versions[resolvedVersion];
94
+ if (!detail) {
95
+ throw new Error(
96
+ `Version "${version}" does not exist for package "${name}"`
97
+ );
98
+ }
99
+
100
+ return detail;
101
+ }
102
+
103
+ async getLatestVersion(name: string): Promise<string> {
104
+ const metadata = await this.fetchManifest(name);
105
+ return metadata['dist-tags'].latest;
106
+ }
107
+
108
+ async listVersions(name: string): Promise<string[]> {
109
+ const metadata = await this.fetchManifest(name);
110
+ return Object.keys(metadata.versions);
111
+ }
112
+
113
+ async getTarballUrl(name: string, version: string): Promise<string> {
114
+ const detail = await this.fetchVersion(name, version);
115
+ return detail.dist.tarball;
116
+ }
117
+
118
+ async downloadArchive(tarballUrl: string): Promise<ArrayBuffer> {
119
+ const resp = await fetch(tarballUrl);
120
+ if (!resp.ok) {
121
+ throw new Error(`Tarball download failed (HTTP ${resp.status}): ${tarballUrl}`);
122
+ }
123
+ return resp.arrayBuffer();
124
+ }
125
+
126
+ flushCache(): void {
127
+ this.metadataStore.clear();
128
+ }
129
+ }
130
+
131
+ export default RegistryClient;
@@ -0,0 +1,411 @@
1
+ // Version Resolver — semver parsing, range matching, and dependency tree resolution.
2
+
3
+ import { RegistryClient, VersionDetail } from "./registry-client";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Public types
7
+ // ---------------------------------------------------------------------------
8
+
9
+ export interface ResolvedDependency {
10
+ name: string;
11
+ version: string;
12
+ tarballUrl: string;
13
+ dependencies: Record<string, string>;
14
+ }
15
+
16
+ export interface ResolutionConfig {
17
+ registry?: RegistryClient;
18
+ devDependencies?: boolean;
19
+ optionalDependencies?: boolean;
20
+ onProgress?: (msg: string) => void;
21
+ }
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Semver data structures
25
+ // ---------------------------------------------------------------------------
26
+
27
+ export interface SemverComponents {
28
+ major: number;
29
+ minor: number;
30
+ patch: number;
31
+ prerelease?: string;
32
+ }
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Semver parsing and comparison
36
+ // ---------------------------------------------------------------------------
37
+
38
+ const SEMVER_PATTERN = /^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/;
39
+
40
+ // Returns null for unparseable strings
41
+ export function parseSemver(raw: string): SemverComponents | null {
42
+ const m = raw.match(SEMVER_PATTERN);
43
+ if (!m) return null;
44
+ return {
45
+ major: Number(m[1]),
46
+ minor: Number(m[2]),
47
+ patch: Number(m[3]),
48
+ prerelease: m[4],
49
+ };
50
+ }
51
+
52
+ // Standard three-way comparison: negative if left < right, 0 if equal, positive if left > right
53
+ export function compareSemver(left: string, right: string): number {
54
+ const a = parseSemver(left);
55
+ const b = parseSemver(right);
56
+
57
+ if (!a || !b) return left.localeCompare(right);
58
+
59
+ const majorDiff = a.major - b.major;
60
+ if (majorDiff !== 0) return majorDiff;
61
+
62
+ const minorDiff = a.minor - b.minor;
63
+ if (minorDiff !== 0) return minorDiff;
64
+
65
+ const patchDiff = a.patch - b.patch;
66
+ if (patchDiff !== 0) return patchDiff;
67
+
68
+ // pre-release has lower precedence than release
69
+ if (a.prerelease && !b.prerelease) return -1;
70
+ if (!a.prerelease && b.prerelease) return 1;
71
+ if (a.prerelease && b.prerelease) {
72
+ return a.prerelease.localeCompare(b.prerelease);
73
+ }
74
+
75
+ return 0;
76
+ }
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // Range satisfaction
80
+ // ---------------------------------------------------------------------------
81
+
82
+ // Supports: exact, ^, ~, *, x-ranges, comparators, compound, hyphen, || unions
83
+ export function satisfiesRange(version: string, range: string): boolean {
84
+ const sv = parseSemver(version);
85
+ if (!sv) return false;
86
+
87
+ // pre-release versions only match ranges that explicitly include one
88
+ if (sv.prerelease && !range.includes("-")) return false;
89
+
90
+ range = range.trim();
91
+
92
+ if (range === "*" || range === "latest" || range === "") return true;
93
+
94
+ if (range.includes("||")) {
95
+ return range.split("||").some((sub) => satisfiesRange(version, sub.trim()));
96
+ }
97
+
98
+ if (range.includes(" - ")) {
99
+ const [lo, hi] = range.split(" - ").map((s) => s.trim());
100
+ return compareSemver(version, lo) >= 0 && compareSemver(version, hi) <= 0;
101
+ }
102
+
103
+ // compound comparators like ">=1.2.0 <3.0.0"
104
+ const comparatorSegments = range.match(
105
+ /(>=|<=|>|<|=)\s*(\d+(?:\.\d+)?(?:\.\d+)?(?:-[^\s]*)?)/g,
106
+ );
107
+ if (comparatorSegments && comparatorSegments.length > 1) {
108
+ return comparatorSegments.every((seg) => {
109
+ const parts = seg.match(
110
+ /^(>=|<=|>|<|=)\s*(\d+(?:\.\d+)?(?:\.\d+)?(?:-[^\s]*)?)$/,
111
+ );
112
+ if (!parts) return true;
113
+ const op = parts[1];
114
+ let target = parts[2];
115
+ // pad partial versions: "3" -> "3.0.0"
116
+ const dots = (target.match(/\./g) || []).length;
117
+ if (dots === 0) target += ".0.0";
118
+ else if (dots === 1) target += ".0";
119
+ return applyOperator(version, op, target);
120
+ });
121
+ }
122
+
123
+ if (range.startsWith("^")) {
124
+ const base = padVersion(range.slice(1));
125
+ const bv = parseSemver(base);
126
+ if (!bv) return false;
127
+
128
+ if (sv.major !== bv.major) return false;
129
+ if (bv.major === 0) {
130
+ if (bv.minor !== 0 && sv.minor !== bv.minor) return false;
131
+ if (bv.minor === 0 && sv.minor !== 0) return false;
132
+ }
133
+ return compareSemver(version, base) >= 0;
134
+ }
135
+
136
+ if (range.startsWith("~")) {
137
+ const base = padVersion(range.slice(1));
138
+ const bv = parseSemver(base);
139
+ if (!bv) return false;
140
+ return (
141
+ sv.major === bv.major &&
142
+ sv.minor === bv.minor &&
143
+ compareSemver(version, base) >= 0
144
+ );
145
+ }
146
+
147
+ if (range.startsWith(">="))
148
+ return compareSemver(version, padVersion(range.slice(2).trim())) >= 0;
149
+ if (range.startsWith(">"))
150
+ return compareSemver(version, padVersion(range.slice(1).trim())) > 0;
151
+ if (range.startsWith("<="))
152
+ return compareSemver(version, padVersion(range.slice(2).trim())) <= 0;
153
+ if (range.startsWith("<"))
154
+ return compareSemver(version, padVersion(range.slice(1).trim())) < 0;
155
+ if (range.startsWith("="))
156
+ return compareSemver(version, padVersion(range.slice(1).trim())) === 0;
157
+
158
+ if (
159
+ range.includes("x") ||
160
+ range.includes("X") ||
161
+ /^\d+$/.test(range) ||
162
+ /^\d+\.\d+$/.test(range)
163
+ ) {
164
+ const segments = range.replace(/[xX]/g, "").split(".").filter(Boolean);
165
+ if (segments.length === 1) return sv.major === Number(segments[0]);
166
+ if (segments.length === 2) {
167
+ return (
168
+ sv.major === Number(segments[0]) && sv.minor === Number(segments[1])
169
+ );
170
+ }
171
+ }
172
+
173
+ if (range.includes(" ")) {
174
+ return range
175
+ .split(/\s+/)
176
+ .filter(Boolean)
177
+ .every((part) => satisfiesRange(version, part));
178
+ }
179
+
180
+ if (/^\d+\.\d+\.\d+/.test(range)) {
181
+ const exact = range.match(/^(\d+\.\d+\.\d+(?:-[^\s]+)?)/);
182
+ if (exact) return compareSemver(version, exact[1]) === 0;
183
+ }
184
+
185
+ return compareSemver(version, range) === 0;
186
+ }
187
+
188
+ // "3" -> "3.0.0", "0.10.x" -> "0.10.0"
189
+ function padVersion(v: string): string {
190
+ const parts = v.replace(/[xX*]/g, "0").split(".");
191
+ while (parts.length < 3) parts.push("0");
192
+ return parts.join(".");
193
+ }
194
+
195
+ function applyOperator(ver: string, op: string, target: string): boolean {
196
+ const cmp = compareSemver(ver, target);
197
+ switch (op) {
198
+ case ">=":
199
+ return cmp >= 0;
200
+ case "<=":
201
+ return cmp <= 0;
202
+ case ">":
203
+ return cmp > 0;
204
+ case "<":
205
+ return cmp < 0;
206
+ default:
207
+ return cmp === 0;
208
+ }
209
+ }
210
+
211
+ // Pick the highest version satisfying the range, or null
212
+ export function pickBestMatch(
213
+ available: string[],
214
+ range: string,
215
+ ): string | null {
216
+ const descending = [...available].sort((a, b) => compareSemver(b, a));
217
+ for (const candidate of descending) {
218
+ if (satisfiesRange(candidate, range)) return candidate;
219
+ }
220
+ return null;
221
+ }
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // npm alias handling
225
+ // ---------------------------------------------------------------------------
226
+
227
+ // Parse "npm:strip-ansi@^6.0.1" into { realName, realRange }
228
+ function parseNpmAlias(range: string): { realName: string; realRange: string } | null {
229
+ if (!range.startsWith("npm:")) return null;
230
+ const rest = range.slice(4);
231
+ let atIdx: number;
232
+ if (rest.startsWith("@")) {
233
+ // scoped: find the second @ after the scope
234
+ atIdx = rest.indexOf("@", 1);
235
+ } else {
236
+ atIdx = rest.indexOf("@");
237
+ }
238
+ if (atIdx === -1) {
239
+ return { realName: rest, realRange: "latest" };
240
+ }
241
+ return {
242
+ realName: rest.slice(0, atIdx),
243
+ realRange: rest.slice(atIdx + 1),
244
+ };
245
+ }
246
+
247
+ // ---------------------------------------------------------------------------
248
+ // Full dependency tree resolution
249
+ // ---------------------------------------------------------------------------
250
+
251
+ interface TreeWalkState {
252
+ registry: RegistryClient;
253
+ completed: Map<string, ResolvedDependency>;
254
+ inFlight: Set<string>;
255
+ config: ResolutionConfig;
256
+ }
257
+
258
+ export async function resolveDependencyTree(
259
+ rootName: string,
260
+ versionRange: string = "latest",
261
+ config: ResolutionConfig = {},
262
+ ): Promise<Map<string, ResolvedDependency>> {
263
+ const client = config.registry || new RegistryClient();
264
+ const state: TreeWalkState = {
265
+ registry: client,
266
+ completed: new Map(),
267
+ inFlight: new Set(),
268
+ config,
269
+ };
270
+
271
+ await walkDependency(rootName, versionRange, state);
272
+ return state.completed;
273
+ }
274
+
275
+ export async function resolveFromManifest(
276
+ manifest: {
277
+ dependencies?: Record<string, string>;
278
+ devDependencies?: Record<string, string>;
279
+ },
280
+ config: ResolutionConfig = {},
281
+ ): Promise<Map<string, ResolvedDependency>> {
282
+ const client = config.registry || new RegistryClient();
283
+ const state: TreeWalkState = {
284
+ registry: client,
285
+ completed: new Map(),
286
+ inFlight: new Set(),
287
+ config,
288
+ };
289
+
290
+ const allDeps: Record<string, string> = { ...manifest.dependencies };
291
+ if (config.devDependencies && manifest.devDependencies) {
292
+ Object.assign(allDeps, manifest.devDependencies);
293
+ }
294
+
295
+ for (const [depName, depRange] of Object.entries(allDeps)) {
296
+ await walkDependency(depName, depRange, state);
297
+ }
298
+
299
+ return state.completed;
300
+ }
301
+
302
+ // Recursively resolve a package and its transitive deps. Uses `inFlight` to break cycles.
303
+ async function walkDependency(
304
+ pkgName: string,
305
+ versionConstraint: string,
306
+ state: TreeWalkState,
307
+ ): Promise<void> {
308
+ const { registry, completed, inFlight, config } = state;
309
+
310
+ // npm aliases: fetch the real package but install under the alias name
311
+ const alias = parseNpmAlias(versionConstraint);
312
+ const installName = pkgName;
313
+ const fetchName = alias?.realName ?? pkgName;
314
+ versionConstraint = alias?.realRange ?? versionConstraint;
315
+
316
+ const trackingKey = `${installName}@${versionConstraint}`;
317
+
318
+ if (inFlight.has(trackingKey)) return;
319
+
320
+ if (completed.has(installName)) {
321
+ const existing = completed.get(installName)!;
322
+ if (satisfiesRange(existing.version, versionConstraint)) return;
323
+ // flat node_modules — accept what we have even if not perfect
324
+ return;
325
+ }
326
+
327
+ inFlight.add(trackingKey);
328
+
329
+ try {
330
+ config.onProgress?.(`Resolving ${fetchName}@${versionConstraint}`);
331
+
332
+ const metadata = await registry.fetchManifest(fetchName);
333
+ const allVersions = Object.keys(metadata.versions);
334
+
335
+ let chosenVersion: string;
336
+ if (versionConstraint === "latest" || versionConstraint === "*") {
337
+ chosenVersion = metadata["dist-tags"].latest;
338
+ } else if (metadata["dist-tags"][versionConstraint]) {
339
+ chosenVersion = metadata["dist-tags"][versionConstraint];
340
+ } else {
341
+ const best = pickBestMatch(allVersions, versionConstraint);
342
+ if (!best) {
343
+ throw new Error(
344
+ `Could not find a version of "${fetchName}" matching "${versionConstraint}"`,
345
+ );
346
+ }
347
+ chosenVersion = best;
348
+ }
349
+
350
+ const versionInfo: VersionDetail = metadata.versions[chosenVersion];
351
+
352
+ completed.set(installName, {
353
+ name: installName,
354
+ version: chosenVersion,
355
+ tarballUrl: versionInfo.dist.tarball,
356
+ dependencies: versionInfo.dependencies || {},
357
+ });
358
+
359
+ // non-optional peers are included (npm v7+ behaviour)
360
+ const edges: Record<string, string> = {};
361
+
362
+ if (versionInfo.peerDependencies) {
363
+ const peerMeta = versionInfo.peerDependenciesMeta || {};
364
+ for (const [peer, peerRange] of Object.entries(
365
+ versionInfo.peerDependencies,
366
+ )) {
367
+ if (!peerMeta[peer]?.optional) {
368
+ edges[peer] = peerRange;
369
+ }
370
+ }
371
+ }
372
+
373
+ // regular deps take precedence over peers
374
+ if (versionInfo.dependencies) {
375
+ Object.assign(edges, versionInfo.dependencies);
376
+ }
377
+
378
+ if (config.optionalDependencies && versionInfo.optionalDependencies) {
379
+ Object.assign(edges, versionInfo.optionalDependencies);
380
+ }
381
+
382
+ const edgeList = Object.entries(edges);
383
+ const PARALLEL_LIMIT = 8;
384
+
385
+ for (let start = 0; start < edgeList.length; start += PARALLEL_LIMIT) {
386
+ const chunk = edgeList.slice(start, start + PARALLEL_LIMIT);
387
+ await Promise.all(
388
+ chunk.map(([childName, childRange]) =>
389
+ walkDependency(childName, childRange, state),
390
+ ),
391
+ );
392
+ }
393
+ } finally {
394
+ inFlight.delete(trackingKey);
395
+ }
396
+ }
397
+
398
+ // ---------------------------------------------------------------------------
399
+ // Class facade
400
+ // ---------------------------------------------------------------------------
401
+
402
+ export class VersionResolver {
403
+ parse = parseSemver;
404
+ compare = compareSemver;
405
+ satisfies = satisfiesRange;
406
+ pickBest = pickBestMatch;
407
+ resolveTree = resolveDependencyTree;
408
+ resolveManifest = resolveFromManifest;
409
+ }
410
+
411
+ export default VersionResolver;