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/README.md +413 -413
- package/bin/wyvrn.js +224 -157
- package/package.json +37 -37
- package/src/commands/clean.js +40 -40
- package/src/commands/configure.js +80 -80
- package/src/commands/init.js +33 -33
- package/src/commands/install.js +109 -109
- package/src/commands/link.js +316 -0
- package/src/commands/publish.js +180 -167
- package/src/config.js +61 -60
- package/src/download.js +172 -164
- package/src/index.js +9 -9
- package/src/link-utils.js +95 -0
- package/src/manifest.js +77 -77
- package/src/providers/base.js +56 -45
- package/src/providers/file.js +71 -65
- package/src/providers/http.js +118 -102
- package/src/providers/index.js +43 -43
- package/src/providers/s3.js +136 -109
- package/src/resolve.js +257 -200
package/src/resolve.js
CHANGED
|
@@ -1,200 +1,257 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
* @param {string}
|
|
9
|
-
* @returns {
|
|
10
|
-
*/
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* @param {
|
|
103
|
-
* @param {
|
|
104
|
-
* @param {string}
|
|
105
|
-
* @
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
+
};
|