wyvrnpm 1.2.0 → 1.3.2

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/src/resolve.js CHANGED
@@ -1,200 +1,257 @@
1
- 'use strict';
2
-
3
- /**
4
- * Compares two version strings of the form "major.minor.patch.build".
5
- * Each missing component is treated as 0.
6
- *
7
- * @param {string} a - First version string.
8
- * @param {string} b - Second version string.
9
- * @returns {-1|0|1} -1 if a < b, 0 if equal, 1 if a > b.
10
- */
11
- function compareVersions(a, b) {
12
- const parsePart = (s) => s.split('.').map((n) => parseInt(n, 10) || 0);
13
- const pa = parsePart(a);
14
- const pb = parsePart(b);
15
- const len = Math.max(pa.length, pb.length);
16
- for (let i = 0; i < len; i++) {
17
- const na = pa[i] !== undefined ? pa[i] : 0;
18
- const nb = pb[i] !== undefined ? pb[i] : 0;
19
- if (na < nb) return -1;
20
- if (na > nb) return 1;
21
- }
22
- return 0;
23
- }
24
-
25
- /**
26
- * Resolves the "latest" tag for a package by fetching latest.json from the registry.
27
- *
28
- * @param {string} name
29
- * @param {string[]} packageSources
30
- * @param {string} platform
31
- * @param {Function} httpClient
32
- * @returns {Promise<string>} The resolved version string.
33
- */
34
- async function resolveLatestVersion(name, packageSources, platform, httpClient) {
35
- for (const source of packageSources) {
36
- const base = source.replace(/\/$/, '');
37
- const candidates = [
38
- `${base}/${platform}/${name}/latest.json`,
39
- `${base}/common/${name}/latest.json`,
40
- ];
41
- for (const url of candidates) {
42
- try {
43
- const response = await httpClient(url);
44
- if (!response.ok) continue;
45
- const data = await response.json();
46
- if (data && data.version) return data.version;
47
- } catch {
48
- // Try next candidate.
49
- }
50
- }
51
- }
52
- throw new Error(`Could not resolve "latest" version for "${name}" from any source`);
53
- }
54
-
55
- /**
56
- * Fetches razer.json for a single package from the first source that responds.
57
- *
58
- * @param {string} name
59
- * @param {string} version
60
- * @param {string[]} packageSources
61
- * @param {string} platform
62
- * @param {Function} httpClient
63
- * @param {Map<string, object>} cache - In-flight / completed fetch cache.
64
- * @returns {Promise<object|null>} Parsed razer.json or null if not found anywhere.
65
- */
66
- async function fetchPackageManifest(name, version, packageSources, platform, httpClient, cache) {
67
- const cacheKey = `${name}@${version}`;
68
- if (cache.has(cacheKey)) {
69
- return cache.get(cacheKey);
70
- }
71
-
72
- for (const source of packageSources) {
73
- const base = source.replace(/\/$/, '');
74
- const candidates = [
75
- `${base}/${platform}/${name}/${version}/wyvrn.json`,
76
- `${base}/${platform}/${name}/${version}/razer.json`,
77
- `${base}/common/${name}/${version}/wyvrn.json`,
78
- `${base}/common/${name}/${version}/razer.json`,
79
- ];
80
- for (const url of candidates) {
81
- try {
82
- const response = await httpClient(url);
83
- if (!response.ok) continue;
84
- const data = await response.json();
85
- cache.set(cacheKey, data);
86
- return data;
87
- } catch {
88
- // Try next candidate.
89
- }
90
- }
91
- }
92
-
93
- console.warn(`[wyvrn] Warning: could not fetch manifest for ${name}@${version} from any source`);
94
- cache.set(cacheKey, null);
95
- return null;
96
- }
97
-
98
- /**
99
- * Resolves the full, flattened dependency graph starting from rootDeps.
100
- * When the same package is required at multiple versions the highest version wins.
101
- *
102
- * @param {Record<string, string>} rootDeps - Direct dependencies { name: version }.
103
- * @param {string[]} packageSources - Ordered list of base URLs to try.
104
- * @param {string} platform - Target platform string (e.g. "win_x64").
105
- * @param {Function} httpClient - fetch-compatible function.
106
- * @returns {Promise<Map<string, string>>} Resolved map of name -> version.
107
- */
108
- async function resolveDependencies(rootDeps, packageSources, platform, httpClient, lockedVersions = new Map()) {
109
- /** @type {Map<string, string>} Final resolved versions. */
110
- const resolved = new Map();
111
-
112
- /** @type {Map<string, object|null>} Manifest cache to avoid duplicate fetches. */
113
- const manifestCache = new Map();
114
-
115
- /**
116
- * Pending work queue: each entry is { name, version }.
117
- * We process iteratively to avoid deep recursion on large dependency trees.
118
- */
119
- const queue = Object.entries(rootDeps).map(([name, version]) => ({ name, version }));
120
-
121
- while (queue.length > 0) {
122
- let { name, version } = queue.shift();
123
-
124
- // Lock file always wins — use pinned version regardless of what was requested.
125
- if (lockedVersions.has(name)) {
126
- version = lockedVersions.get(name);
127
- }
128
-
129
- // Resolve the "latest" tag to a concrete version before proceeding.
130
- if (version === 'latest') {
131
- try {
132
- version = await resolveLatestVersion(name, packageSources, platform, httpClient);
133
- console.log(`[wyvrn] Resolved "${name}@latest" → ${version}`);
134
- } catch (err) {
135
- console.warn(`[wyvrn] Warning: ${err.message}`);
136
- continue;
137
- }
138
- }
139
-
140
- if (resolved.has(name)) {
141
- const existing = resolved.get(name);
142
- if (existing === version) {
143
- continue;
144
- }
145
- // Locked packages never lose to a conflict.
146
- if (lockedVersions.has(name)) {
147
- continue;
148
- }
149
- // Conflict: keep the latest version.
150
- const winner = compareVersions(version, existing) > 0 ? version : existing;
151
- if (winner !== existing) {
152
- console.warn(
153
- `[wyvrn] Version conflict for "${name}": ${existing} vs ${version} using ${winner}`,
154
- );
155
- resolved.set(name, winner);
156
- // Re-fetch the winning version's manifest to pull in its transitive deps.
157
- queue.push({ name, version: winner });
158
- }
159
- continue;
160
- }
161
-
162
- resolved.set(name, version);
163
-
164
- const manifest = await fetchPackageManifest(
165
- name,
166
- version,
167
- packageSources,
168
- platform,
169
- httpClient,
170
- manifestCache,
171
- );
172
- if (!manifest) {
173
- continue;
174
- }
175
-
176
- // Support both formats:
177
- // PascalCase array: { Dependencies: [ { Name, Version }, ... ] }
178
- // lowercase object: { dependencies: { name: version, ... } }
179
- let deps = [];
180
- if (Array.isArray(manifest.Dependencies)) {
181
- deps = manifest.Dependencies.filter((d) => d.Name && d.Version).map((d) => ({
182
- name: d.Name,
183
- version: d.Version,
184
- }));
185
- } else if (manifest.dependencies && typeof manifest.dependencies === 'object') {
186
- deps = Object.entries(manifest.dependencies).map(([name, version]) => ({ name, version }));
187
- }
188
- for (const dep of deps) {
189
- queue.push({ name: dep.name, version: dep.version });
190
- }
191
- }
192
-
193
- return resolved;
194
- }
195
-
196
- module.exports = {
197
- compareVersions,
198
- resolveLatestVersion,
199
- resolveDependencies,
200
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ /**
7
+ * Checks if a version string represents a linked package.
8
+ * @param {string} version
9
+ * @returns {boolean}
10
+ */
11
+ function isLinkedVersion(version) {
12
+ return typeof version === 'string' && version.startsWith('linked:');
13
+ }
14
+
15
+ /**
16
+ * Extracts the local path from a linked version string.
17
+ * @param {string} version - e.g. "linked:C:\\Dev\\my-package"
18
+ * @returns {string} The local path.
19
+ */
20
+ function getLinkedPath(version) {
21
+ return version.slice('linked:'.length);
22
+ }
23
+
24
+ /**
25
+ * Reads a local package's manifest (wyvrn.json or razer.json).
26
+ * @param {string} packagePath - Path to the package directory.
27
+ * @returns {object|null} Parsed manifest or null if not found.
28
+ */
29
+ function readLocalManifest(packagePath) {
30
+ const candidates = ['wyvrn.json', 'razer.json'];
31
+ for (const filename of candidates) {
32
+ const manifestPath = path.join(packagePath, filename);
33
+ if (fs.existsSync(manifestPath)) {
34
+ try {
35
+ return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
36
+ } catch {
37
+ // Try next candidate
38
+ }
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+
44
+ /**
45
+ * Compares two version strings of the form "major.minor.patch.build".
46
+ * Each missing component is treated as 0.
47
+ *
48
+ * @param {string} a - First version string.
49
+ * @param {string} b - Second version string.
50
+ * @returns {-1|0|1} -1 if a < b, 0 if equal, 1 if a > b.
51
+ */
52
+ function compareVersions(a, b) {
53
+ const parsePart = (s) => s.split('.').map((n) => parseInt(n, 10) || 0);
54
+ const pa = parsePart(a);
55
+ const pb = parsePart(b);
56
+ const len = Math.max(pa.length, pb.length);
57
+ for (let i = 0; i < len; i++) {
58
+ const na = pa[i] !== undefined ? pa[i] : 0;
59
+ const nb = pb[i] !== undefined ? pb[i] : 0;
60
+ if (na < nb) return -1;
61
+ if (na > nb) return 1;
62
+ }
63
+ return 0;
64
+ }
65
+
66
+ /**
67
+ * Resolves the "latest" tag for a package by fetching latest.json from the registry.
68
+ *
69
+ * @param {string} name
70
+ * @param {string[]} packageSources
71
+ * @param {string} platform
72
+ * @param {Function} httpClient
73
+ * @returns {Promise<string>} The resolved version string.
74
+ */
75
+ async function resolveLatestVersion(name, packageSources, platform, httpClient) {
76
+ for (const source of packageSources) {
77
+ const base = source.replace(/\/$/, '');
78
+ const candidates = [
79
+ `${base}/${platform}/${name}/latest.json`,
80
+ `${base}/common/${name}/latest.json`,
81
+ ];
82
+ for (const url of candidates) {
83
+ try {
84
+ const response = await httpClient(url);
85
+ if (!response.ok) continue;
86
+ const data = await response.json();
87
+ if (data && data.version) return data.version;
88
+ } catch {
89
+ // Try next candidate.
90
+ }
91
+ }
92
+ }
93
+ throw new Error(`Could not resolve "latest" version for "${name}" from any source`);
94
+ }
95
+
96
+ /**
97
+ * Fetches razer.json for a single package from the first source that responds.
98
+ *
99
+ * @param {string} name
100
+ * @param {string} version
101
+ * @param {string[]} packageSources
102
+ * @param {string} platform
103
+ * @param {Function} httpClient
104
+ * @param {Map<string, object>} cache - In-flight / completed fetch cache.
105
+ * @returns {Promise<object|null>} Parsed razer.json or null if not found anywhere.
106
+ */
107
+ async function fetchPackageManifest(name, version, packageSources, platform, httpClient, cache) {
108
+ const cacheKey = `${name}@${version}`;
109
+ if (cache.has(cacheKey)) {
110
+ return cache.get(cacheKey);
111
+ }
112
+
113
+ for (const source of packageSources) {
114
+ const base = source.replace(/\/$/, '');
115
+ const candidates = [
116
+ `${base}/${platform}/${name}/${version}/wyvrn.json`,
117
+ `${base}/${platform}/${name}/${version}/razer.json`,
118
+ `${base}/common/${name}/${version}/wyvrn.json`,
119
+ `${base}/common/${name}/${version}/razer.json`,
120
+ ];
121
+ for (const url of candidates) {
122
+ try {
123
+ const response = await httpClient(url);
124
+ if (!response.ok) continue;
125
+ const data = await response.json();
126
+ cache.set(cacheKey, data);
127
+ return data;
128
+ } catch {
129
+ // Try next candidate.
130
+ }
131
+ }
132
+ }
133
+
134
+ console.warn(`[wyvrn] Warning: could not fetch manifest for ${name}@${version} from any source`);
135
+ cache.set(cacheKey, null);
136
+ return null;
137
+ }
138
+
139
+ /**
140
+ * Resolves the full, flattened dependency graph starting from rootDeps.
141
+ * When the same package is required at multiple versions the highest version wins.
142
+ *
143
+ * @param {Record<string, string>} rootDeps - Direct dependencies { name: version }.
144
+ * @param {string[]} packageSources - Ordered list of base URLs to try.
145
+ * @param {string} platform - Target platform string (e.g. "win_x64").
146
+ * @param {Function} httpClient - fetch-compatible function.
147
+ * @returns {Promise<Map<string, string>>} Resolved map of name -> version.
148
+ */
149
+ async function resolveDependencies(rootDeps, packageSources, platform, httpClient, lockedVersions = new Map()) {
150
+ /** @type {Map<string, string>} Final resolved versions. */
151
+ const resolved = new Map();
152
+
153
+ /** @type {Map<string, object|null>} Manifest cache to avoid duplicate fetches. */
154
+ const manifestCache = new Map();
155
+
156
+ /**
157
+ * Pending work queue: each entry is { name, version }.
158
+ * We process iteratively to avoid deep recursion on large dependency trees.
159
+ */
160
+ const queue = Object.entries(rootDeps).map(([name, version]) => ({ name, version }));
161
+
162
+ while (queue.length > 0) {
163
+ let { name, version } = queue.shift();
164
+
165
+ // Lock file always wins — use pinned version regardless of what was requested.
166
+ if (lockedVersions.has(name)) {
167
+ version = lockedVersions.get(name);
168
+ }
169
+
170
+ // Resolve the "latest" tag to a concrete version before proceeding.
171
+ // Skip this for linked packages which have "linked:" prefix.
172
+ if (version === 'latest') {
173
+ try {
174
+ version = await resolveLatestVersion(name, packageSources, platform, httpClient);
175
+ console.log(`[wyvrn] Resolved "${name}@latest" → ${version}`);
176
+ } catch (err) {
177
+ console.warn(`[wyvrn] Warning: ${err.message}`);
178
+ continue;
179
+ }
180
+ }
181
+
182
+ if (resolved.has(name)) {
183
+ const existing = resolved.get(name);
184
+ if (existing === version) {
185
+ continue;
186
+ }
187
+ // Locked packages never lose to a conflict.
188
+ if (lockedVersions.has(name)) {
189
+ continue;
190
+ }
191
+ // Conflict: keep the latest version.
192
+ const winner = compareVersions(version, existing) > 0 ? version : existing;
193
+ if (winner !== existing) {
194
+ console.warn(
195
+ `[wyvrn] Version conflict for "${name}": ${existing} vs ${version} — using ${winner}`,
196
+ );
197
+ resolved.set(name, winner);
198
+ // Re-fetch the winning version's manifest to pull in its transitive deps.
199
+ queue.push({ name, version: winner });
200
+ }
201
+ continue;
202
+ }
203
+
204
+ resolved.set(name, version);
205
+
206
+ // Handle linked packages: read local manifest instead of fetching from remote
207
+ let manifest;
208
+ if (isLinkedVersion(version)) {
209
+ const localPath = getLinkedPath(version);
210
+ manifest = readLocalManifest(localPath);
211
+ if (manifest) {
212
+ console.log(`[wyvrn] Linked: ${name} → ${localPath}`);
213
+ } else {
214
+ console.log(`[wyvrn] Linked: ${name} → ${localPath} (no manifest found)`);
215
+ }
216
+ } else {
217
+ manifest = await fetchPackageManifest(
218
+ name,
219
+ version,
220
+ packageSources,
221
+ platform,
222
+ httpClient,
223
+ manifestCache,
224
+ );
225
+ }
226
+
227
+ if (!manifest) {
228
+ continue;
229
+ }
230
+
231
+ // Support both formats:
232
+ // PascalCase array: { Dependencies: [ { Name, Version }, ... ] }
233
+ // lowercase object: { dependencies: { name: version, ... } }
234
+ let deps = [];
235
+ if (Array.isArray(manifest.Dependencies)) {
236
+ deps = manifest.Dependencies.filter((d) => d.Name && d.Version).map((d) => ({
237
+ name: d.Name,
238
+ version: d.Version,
239
+ }));
240
+ } else if (manifest.dependencies && typeof manifest.dependencies === 'object') {
241
+ deps = Object.entries(manifest.dependencies).map(([name, version]) => ({ name, version }));
242
+ }
243
+ for (const dep of deps) {
244
+ queue.push({ name: dep.name, version: dep.version });
245
+ }
246
+ }
247
+
248
+ return resolved;
249
+ }
250
+
251
+ module.exports = {
252
+ compareVersions,
253
+ resolveLatestVersion,
254
+ resolveDependencies,
255
+ isLinkedVersion,
256
+ getLinkedPath,
257
+ };