wyvrnpm 2.0.4 → 2.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 +639 -5
- package/bin/wyvrn.js +220 -10
- package/claude/skills/wyvrnpm.skill +0 -0
- package/cmake/cpp.cmake +9 -0
- package/cmake/functions.cmake +224 -0
- package/cmake/macros.cmake +233 -0
- package/cmake/options.cmake +23 -0
- package/cmake/variables.cmake +171 -0
- package/package.json +3 -1
- package/src/build/cache.js +148 -0
- package/src/build/clone.js +170 -0
- package/src/build/cmake.js +342 -0
- package/src/build/index.js +275 -0
- package/src/build/msvc-env.js +217 -0
- package/src/build/recipe.js +155 -0
- package/src/commands/build.js +283 -0
- package/src/commands/clean.js +56 -16
- package/src/commands/configure.js +6 -5
- package/src/commands/init.js +3 -2
- package/src/commands/install-skill.js +107 -0
- package/src/commands/install.js +262 -19
- package/src/commands/link.js +18 -15
- package/src/commands/profile.js +15 -12
- package/src/commands/publish.js +216 -65
- package/src/commands/show.js +237 -0
- package/src/compat.js +261 -0
- package/src/config.js +3 -1
- package/src/download.js +431 -87
- package/src/ignore.js +118 -0
- package/src/logger.js +94 -0
- package/src/manifest.js +12 -7
- package/src/options.js +303 -0
- package/src/profile.js +56 -4
- package/src/providers/base.js +16 -1
- package/src/providers/file.js +12 -6
- package/src/providers/http.js +15 -10
- package/src/providers/s3.js +14 -9
- package/src/resolve.js +179 -19
- package/src/toolchain/deps.js +164 -0
- package/src/toolchain/index.js +141 -0
- package/src/toolchain/presets.js +263 -0
- package/src/toolchain/template.cmake +66 -0
- package/src/upload-built.js +256 -0
- package/src/version-range.js +301 -0
package/src/providers/file.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const BaseProvider = require('./base');
|
|
6
|
+
const log = require('../logger');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* File system provider — handles local directories and SMB/UNC shares.
|
|
@@ -56,12 +57,12 @@ class FileProvider extends BaseProvider {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
_copy(src, dest) {
|
|
59
|
-
|
|
60
|
+
log.info(` → ${dest}`);
|
|
60
61
|
fs.copyFileSync(src, dest);
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
_writeJson(filePath, data) {
|
|
64
|
-
|
|
65
|
+
log.info(` → ${filePath}`);
|
|
65
66
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -87,7 +88,7 @@ class FileProvider extends BaseProvider {
|
|
|
87
88
|
this._copy(files.zip, path.join(destDir, 'wyvrn.zip'));
|
|
88
89
|
|
|
89
90
|
const latestPath = path.join(packageDir, 'latest.json');
|
|
90
|
-
|
|
91
|
+
log.info(` → ${latestPath}`);
|
|
91
92
|
fs.writeFileSync(latestPath, JSON.stringify({ version }), 'utf8');
|
|
92
93
|
}
|
|
93
94
|
|
|
@@ -106,6 +107,7 @@ class FileProvider extends BaseProvider {
|
|
|
106
107
|
const {
|
|
107
108
|
source, name, version, profileHash, buildSettings,
|
|
108
109
|
contentSha256, gitSha, gitRepo, lockedDependencies,
|
|
110
|
+
updateLatest = true, uploadedBy,
|
|
109
111
|
} = options;
|
|
110
112
|
|
|
111
113
|
const v2root = this._v2Root(source, name);
|
|
@@ -129,6 +131,7 @@ class FileProvider extends BaseProvider {
|
|
|
129
131
|
gitSha: gitSha ?? null,
|
|
130
132
|
gitRepo: gitRepo ?? null,
|
|
131
133
|
publishedAt: new Date().toISOString(),
|
|
134
|
+
...(uploadedBy ? { uploadedBy } : {}),
|
|
132
135
|
};
|
|
133
136
|
this._writeJson(path.join(buildDir, 'wyvrn.json'), v2Meta);
|
|
134
137
|
|
|
@@ -154,11 +157,14 @@ class FileProvider extends BaseProvider {
|
|
|
154
157
|
if (gitSha || gitRepo) {
|
|
155
158
|
versionsIdx.versions[version].source = { gitRepo: gitRepo ?? null, gitSha: gitSha ?? null };
|
|
156
159
|
}
|
|
157
|
-
versionsIdx.latest = version;
|
|
160
|
+
if (updateLatest) versionsIdx.latest = version;
|
|
158
161
|
this._writeJson(versionsPath, versionsIdx);
|
|
159
162
|
|
|
160
|
-
// 5) v2 latest.json
|
|
161
|
-
|
|
163
|
+
// 5) v2 latest.json — skipped for consumer uploads (upload-built) so a
|
|
164
|
+
// historical-version rebuild cannot clobber the real "latest" pointer.
|
|
165
|
+
if (updateLatest) {
|
|
166
|
+
this._writeJson(path.join(v2root, 'latest.json'), { version, profileHash, contentSha256 });
|
|
167
|
+
}
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
async v2GetMeta({ source, name, version, profileHash }) {
|
package/src/providers/http.js
CHANGED
|
@@ -6,6 +6,7 @@ const https = require('https');
|
|
|
6
6
|
const { pipeline } = require('stream/promises');
|
|
7
7
|
const { Readable } = require('stream');
|
|
8
8
|
const BaseProvider = require('./base');
|
|
9
|
+
const log = require('../logger');
|
|
9
10
|
|
|
10
11
|
function pinDependencies(rawDeps, lockedDeps) {
|
|
11
12
|
if (!rawDeps || !lockedDeps || Object.keys(lockedDeps).length === 0) return rawDeps;
|
|
@@ -45,7 +46,7 @@ class HttpProvider extends BaseProvider {
|
|
|
45
46
|
const parsed = new URL(url);
|
|
46
47
|
const headers = { 'Content-Type': contentType, 'Content-Length': body.length };
|
|
47
48
|
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
48
|
-
|
|
49
|
+
log.info(` → PUT ${url}`);
|
|
49
50
|
const req = this._lib(url).request(
|
|
50
51
|
{ hostname: parsed.hostname, port: parsed.port || undefined, path: parsed.pathname, method: 'PUT', headers },
|
|
51
52
|
(res) => {
|
|
@@ -66,7 +67,7 @@ class HttpProvider extends BaseProvider {
|
|
|
66
67
|
const parsed = new URL(url);
|
|
67
68
|
const headers = { 'Content-Type': contentType, 'Content-Length': buffer.length };
|
|
68
69
|
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
69
|
-
|
|
70
|
+
log.info(` → PUT ${url}`);
|
|
70
71
|
const req = this._lib(url).request(
|
|
71
72
|
{ hostname: parsed.hostname, port: parsed.port || undefined, path: parsed.pathname, method: 'PUT', headers },
|
|
72
73
|
(res) => {
|
|
@@ -175,6 +176,7 @@ class HttpProvider extends BaseProvider {
|
|
|
175
176
|
const {
|
|
176
177
|
source, name, version, profileHash, buildSettings,
|
|
177
178
|
contentSha256, gitSha, gitRepo, lockedDependencies, token,
|
|
179
|
+
updateLatest = true, uploadedBy,
|
|
178
180
|
} = options;
|
|
179
181
|
const base = source.replace(/\/$/, '');
|
|
180
182
|
const v2root = `${base}/v2/${name}`;
|
|
@@ -198,6 +200,7 @@ class HttpProvider extends BaseProvider {
|
|
|
198
200
|
gitSha: gitSha ?? null,
|
|
199
201
|
gitRepo: gitRepo ?? null,
|
|
200
202
|
publishedAt: new Date().toISOString(),
|
|
203
|
+
...(uploadedBy ? { uploadedBy } : {}),
|
|
201
204
|
};
|
|
202
205
|
await this._putBuffer(
|
|
203
206
|
`${buildRoot}/wyvrn.json`,
|
|
@@ -231,7 +234,7 @@ class HttpProvider extends BaseProvider {
|
|
|
231
234
|
if (gitSha || gitRepo) {
|
|
232
235
|
versionsIdx.versions[version].source = { gitRepo: gitRepo ?? null, gitSha: gitSha ?? null };
|
|
233
236
|
}
|
|
234
|
-
versionsIdx.latest = version;
|
|
237
|
+
if (updateLatest) versionsIdx.latest = version;
|
|
235
238
|
await this._putBuffer(
|
|
236
239
|
`${v2root}/versions.json`,
|
|
237
240
|
Buffer.from(JSON.stringify(versionsIdx, null, 2)),
|
|
@@ -239,13 +242,15 @@ class HttpProvider extends BaseProvider {
|
|
|
239
242
|
token,
|
|
240
243
|
);
|
|
241
244
|
|
|
242
|
-
// 5)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
245
|
+
// 5) v2 latest.json — skipped for consumer uploads (see FileProvider note).
|
|
246
|
+
if (updateLatest) {
|
|
247
|
+
await this._putBuffer(
|
|
248
|
+
`${v2root}/latest.json`,
|
|
249
|
+
Buffer.from(JSON.stringify({ version, profileHash, contentSha256 })),
|
|
250
|
+
'application/json',
|
|
251
|
+
token,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
249
254
|
}
|
|
250
255
|
|
|
251
256
|
async v2GetMeta({ source, name, version, profileHash, token }) {
|
package/src/providers/s3.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const BaseProvider = require('./base');
|
|
6
|
+
const log = require('../logger');
|
|
6
7
|
|
|
7
8
|
const RE_S3_URI = /^s3:\/\//;
|
|
8
9
|
const RE_S3_VIRTUAL_HOSTED = /^https?:\/\/([^.]+)\.s3[.-][^/]*\.amazonaws\.com/;
|
|
@@ -79,7 +80,7 @@ class S3Provider extends BaseProvider {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
async _putFile(client, bucket, key, filePath, contentType, PutObjectCommand) {
|
|
82
|
-
|
|
83
|
+
log.info(` → s3://${bucket}/${key}`);
|
|
83
84
|
await client.send(new PutObjectCommand({
|
|
84
85
|
Bucket: bucket, Key: key,
|
|
85
86
|
Body: fs.readFileSync(filePath),
|
|
@@ -88,7 +89,7 @@ class S3Provider extends BaseProvider {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
async _putBuffer(client, bucket, key, content, contentType, PutObjectCommand) {
|
|
91
|
-
|
|
92
|
+
log.info(` → s3://${bucket}/${key}`);
|
|
92
93
|
await client.send(new PutObjectCommand({
|
|
93
94
|
Bucket: bucket, Key: key,
|
|
94
95
|
Body: content,
|
|
@@ -177,6 +178,7 @@ class S3Provider extends BaseProvider {
|
|
|
177
178
|
const {
|
|
178
179
|
source, name, version, profileHash, buildSettings,
|
|
179
180
|
contentSha256, gitSha, gitRepo, lockedDependencies, awsProfile,
|
|
181
|
+
updateLatest = true, uploadedBy,
|
|
180
182
|
} = options;
|
|
181
183
|
const { PutObjectCommand, GetObjectCommand } = this._loadSdk();
|
|
182
184
|
const client = this._makeClient(awsProfile);
|
|
@@ -203,6 +205,7 @@ class S3Provider extends BaseProvider {
|
|
|
203
205
|
gitSha: gitSha ?? null,
|
|
204
206
|
gitRepo: gitRepo ?? null,
|
|
205
207
|
publishedAt: new Date().toISOString(),
|
|
208
|
+
...(uploadedBy ? { uploadedBy } : {}),
|
|
206
209
|
};
|
|
207
210
|
await this._putBuffer(
|
|
208
211
|
client, bucket, `${buildRoot}/wyvrn.json`,
|
|
@@ -231,18 +234,20 @@ class S3Provider extends BaseProvider {
|
|
|
231
234
|
if (gitSha || gitRepo) {
|
|
232
235
|
versionsIdx.versions[version].source = { gitRepo: gitRepo ?? null, gitSha: gitSha ?? null };
|
|
233
236
|
}
|
|
234
|
-
versionsIdx.latest = version;
|
|
237
|
+
if (updateLatest) versionsIdx.latest = version;
|
|
235
238
|
await this._putBuffer(
|
|
236
239
|
client, bucket, `${v2root}/versions.json`,
|
|
237
240
|
Buffer.from(JSON.stringify(versionsIdx, null, 2)), 'application/json', PutObjectCommand,
|
|
238
241
|
);
|
|
239
242
|
|
|
240
|
-
// 5) latest.json
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
// 5) latest.json — skipped for consumer uploads (see FileProvider note).
|
|
244
|
+
if (updateLatest) {
|
|
245
|
+
await this._putBuffer(
|
|
246
|
+
client, bucket, `${v2root}/latest.json`,
|
|
247
|
+
Buffer.from(JSON.stringify({ version, profileHash, contentSha256 })),
|
|
248
|
+
'application/json', PutObjectCommand,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
246
251
|
}
|
|
247
252
|
|
|
248
253
|
async v2GetMeta({ source, name, version, profileHash, awsProfile }) {
|
package/src/resolve.js
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
|
+
const log = require('./logger');
|
|
7
|
+
const {
|
|
8
|
+
parseRange,
|
|
9
|
+
matchesRange,
|
|
10
|
+
pickHighestInRange,
|
|
11
|
+
intersectRanges,
|
|
12
|
+
rangeToString,
|
|
13
|
+
} = require('./version-range');
|
|
14
|
+
|
|
6
15
|
/**
|
|
7
16
|
* Checks if a version string represents a linked package.
|
|
8
17
|
* @param {string} version
|
|
@@ -63,6 +72,33 @@ function compareVersions(a, b) {
|
|
|
63
72
|
return 0;
|
|
64
73
|
}
|
|
65
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Fetch every published version of `name` across the configured sources
|
|
77
|
+
* via each source's `versions.json` (v2 layout). Returns the union of
|
|
78
|
+
* version keys sorted ascending. Used by the range resolver to pick the
|
|
79
|
+
* highest version satisfying `^/~/>=` style specs.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} name
|
|
82
|
+
* @param {string[]} packageSources
|
|
83
|
+
* @param {Function} httpClient
|
|
84
|
+
* @returns {Promise<string[]>}
|
|
85
|
+
*/
|
|
86
|
+
async function fetchPublishedVersions(name, packageSources, httpClient) {
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
for (const source of packageSources) {
|
|
89
|
+
const base = source.replace(/\/$/, '');
|
|
90
|
+
try {
|
|
91
|
+
const resp = await httpClient(`${base}/v2/${name}/versions.json`);
|
|
92
|
+
if (!resp.ok) continue;
|
|
93
|
+
const idx = await resp.json();
|
|
94
|
+
for (const v of Object.keys(idx?.versions ?? {})) seen.add(v);
|
|
95
|
+
} catch {
|
|
96
|
+
// Try next source
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return [...seen].sort(compareVersions);
|
|
100
|
+
}
|
|
101
|
+
|
|
66
102
|
/**
|
|
67
103
|
* Resolves the "latest" tag for a package by fetching latest.json from the registry.
|
|
68
104
|
*
|
|
@@ -164,7 +200,7 @@ async function fetchPackageManifest(name, version, packageSources, platform, htt
|
|
|
164
200
|
}
|
|
165
201
|
}
|
|
166
202
|
|
|
167
|
-
|
|
203
|
+
log.warn(`could not fetch manifest for ${name}@${version} from any source`);
|
|
168
204
|
cache.set(cacheKey, null);
|
|
169
205
|
return null;
|
|
170
206
|
}
|
|
@@ -173,13 +209,21 @@ async function fetchPackageManifest(name, version, packageSources, platform, htt
|
|
|
173
209
|
* Resolves the full, flattened dependency graph starting from rootDeps.
|
|
174
210
|
* When the same package is required at multiple versions the highest version wins.
|
|
175
211
|
*
|
|
212
|
+
* Side-effect parameter: when `manifestsOut` is provided (a Map instance),
|
|
213
|
+
* the resolver populates it with `{name → recipe manifest}` for every
|
|
214
|
+
* fetched dependency. Callers that need recipe-declared `options` (see
|
|
215
|
+
* PLAN-OPTIONS) use this to avoid a second round of HTTP round-trips.
|
|
216
|
+
* Passing `null` / omitting it keeps the legacy behaviour.
|
|
217
|
+
*
|
|
176
218
|
* @param {Record<string, string>} rootDeps - Direct dependencies { name: version }.
|
|
177
219
|
* @param {string[]} packageSources - Ordered list of base URLs to try.
|
|
178
220
|
* @param {string} platform - Target platform string (e.g. "win_x64").
|
|
179
221
|
* @param {Function} httpClient - fetch-compatible function.
|
|
222
|
+
* @param {Map<string, string>} [lockedVersions]
|
|
223
|
+
* @param {Map<string, object>} [manifestsOut] - populated in-place when provided
|
|
180
224
|
* @returns {Promise<Map<string, string>>} Resolved map of name -> version.
|
|
181
225
|
*/
|
|
182
|
-
async function resolveDependencies(rootDeps, packageSources, platform, httpClient, lockedVersions = new Map()) {
|
|
226
|
+
async function resolveDependencies(rootDeps, packageSources, platform, httpClient, lockedVersions = new Map(), manifestsOut = null) {
|
|
183
227
|
/** @type {Map<string, string>} Final resolved versions. */
|
|
184
228
|
const resolved = new Map();
|
|
185
229
|
|
|
@@ -187,13 +231,45 @@ async function resolveDependencies(rootDeps, packageSources, platform, httpClien
|
|
|
187
231
|
const manifestCache = new Map();
|
|
188
232
|
|
|
189
233
|
/**
|
|
190
|
-
*
|
|
234
|
+
* Per-package effective range. For a package that only ever appears with
|
|
235
|
+
* exact versions, this stores `{ kind: 'exact', version }` — which still
|
|
236
|
+
* intersects cleanly with later range requests. For packages with range
|
|
237
|
+
* specs, we intersect contributors so conflicting ranges fail fast.
|
|
238
|
+
* @type {Map<string, ReturnType<typeof parseRange>>}
|
|
239
|
+
*/
|
|
240
|
+
const effectiveRange = new Map();
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Who contributed which raw spec for which package — used only for the
|
|
244
|
+
* conflict error message so the user knows whose declaration to fix.
|
|
245
|
+
* @type {Map<string, Array<{ by: string, spec: string }>>}
|
|
246
|
+
*/
|
|
247
|
+
const rangeContributors = new Map();
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Cache the published-versions list per package — one HTTP round trip
|
|
251
|
+
* per name regardless of how often it appears in the graph.
|
|
252
|
+
* @type {Map<string, string[]>}
|
|
253
|
+
*/
|
|
254
|
+
const publishedCache = new Map();
|
|
255
|
+
const fetchPublished = async (name) => {
|
|
256
|
+
if (publishedCache.has(name)) return publishedCache.get(name);
|
|
257
|
+
const list = await fetchPublishedVersions(name, packageSources, httpClient);
|
|
258
|
+
publishedCache.set(name, list);
|
|
259
|
+
return list;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Pending work queue: each entry is { name, version, by? }.
|
|
264
|
+
* `by` is the contributor label for conflict diagnostics.
|
|
191
265
|
* We process iteratively to avoid deep recursion on large dependency trees.
|
|
192
266
|
*/
|
|
193
|
-
const queue = Object.entries(rootDeps).map(
|
|
267
|
+
const queue = Object.entries(rootDeps).map(
|
|
268
|
+
([name, version]) => ({ name, version, by: 'wyvrn.json' }),
|
|
269
|
+
);
|
|
194
270
|
|
|
195
271
|
while (queue.length > 0) {
|
|
196
|
-
let { name, version } = queue.shift();
|
|
272
|
+
let { name, version, by } = queue.shift();
|
|
197
273
|
|
|
198
274
|
// Lock file always wins — use pinned version regardless of what was requested.
|
|
199
275
|
if (lockedVersions.has(name)) {
|
|
@@ -205,36 +281,115 @@ async function resolveDependencies(rootDeps, packageSources, platform, httpClien
|
|
|
205
281
|
if (version === 'latest') {
|
|
206
282
|
try {
|
|
207
283
|
version = await resolveLatestVersion(name, packageSources, platform, httpClient);
|
|
208
|
-
|
|
284
|
+
log.info(`Resolved "${name}@latest" → ${version}`);
|
|
209
285
|
} catch (err) {
|
|
210
|
-
|
|
286
|
+
log.warn(err.message);
|
|
211
287
|
continue;
|
|
212
288
|
}
|
|
213
289
|
}
|
|
214
290
|
|
|
291
|
+
// Parse the version spec. Ranges (`^`, `~`, explicit comparators) resolve
|
|
292
|
+
// to an exact version via `fetchPublishedVersions` + highest-in-range;
|
|
293
|
+
// intersected against any previously-seen range for the same package.
|
|
294
|
+
let parsedSpec;
|
|
295
|
+
try {
|
|
296
|
+
parsedSpec = parseRange(version);
|
|
297
|
+
} catch (err) {
|
|
298
|
+
log.error(`Dependency "${name}": ${err.message}`);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (parsedSpec.kind === 'linked') {
|
|
303
|
+
// Linked packages bypass range resolution entirely.
|
|
304
|
+
parsedSpec = { kind: 'exact', version };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Intersect with any previously-seen range for this package.
|
|
308
|
+
//
|
|
309
|
+
// Backward-compat nuance: when both the previous and current spec are
|
|
310
|
+
// exact but differ, we preserve the legacy "pick higher with warning"
|
|
311
|
+
// behaviour rather than erroring. Intersection-strict conflict only
|
|
312
|
+
// applies when at least one side is an actual range — ranges are
|
|
313
|
+
// opt-in new territory where a declared range that cannot be satisfied
|
|
314
|
+
// IS a bug, not a soft conflict.
|
|
315
|
+
let effective = parsedSpec;
|
|
316
|
+
const prev = effectiveRange.get(name);
|
|
317
|
+
if (prev) {
|
|
318
|
+
const bothExact = prev.kind === 'exact' && parsedSpec.kind === 'exact';
|
|
319
|
+
if (bothExact) {
|
|
320
|
+
// Legacy behaviour: keep the HIGHER of the two exact versions.
|
|
321
|
+
// The warning is emitted in the "already resolved" branch below.
|
|
322
|
+
effective = compareVersions(parsedSpec.version, prev.version) > 0
|
|
323
|
+
? parsedSpec
|
|
324
|
+
: prev;
|
|
325
|
+
} else {
|
|
326
|
+
const combined = intersectRanges(prev, parsedSpec);
|
|
327
|
+
if (!combined) {
|
|
328
|
+
const sources = rangeContributors.get(name) ?? [];
|
|
329
|
+
const lines = sources.map((c) => ` required by ${c.by}: ${c.spec}`);
|
|
330
|
+
lines.push(` required by ${by}: ${version}`);
|
|
331
|
+
log.error(
|
|
332
|
+
`cannot satisfy conflicting version ranges for "${name}":\n` +
|
|
333
|
+
lines.join('\n') + '\n' +
|
|
334
|
+
` no version satisfies all ranges`,
|
|
335
|
+
);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
effective = combined;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
effectiveRange.set(name, effective);
|
|
342
|
+
if (!rangeContributors.has(name)) rangeContributors.set(name, []);
|
|
343
|
+
rangeContributors.get(name).push({ by, spec: version });
|
|
344
|
+
|
|
345
|
+
// Resolve the (possibly intersected) spec down to an exact version.
|
|
346
|
+
let exactVersion;
|
|
347
|
+
if (effective.kind === 'exact') {
|
|
348
|
+
exactVersion = effective.version;
|
|
349
|
+
} else {
|
|
350
|
+
const published = await fetchPublished(name);
|
|
351
|
+
const picked = pickHighestInRange(published, effective);
|
|
352
|
+
if (!picked) {
|
|
353
|
+
log.error(
|
|
354
|
+
`no published version of "${name}" satisfies ${rangeToString(effective)}\n` +
|
|
355
|
+
(published.length > 0
|
|
356
|
+
? ` published versions: ${published.join(', ')}`
|
|
357
|
+
: ` no versions published on the configured sources`),
|
|
358
|
+
);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
exactVersion = picked;
|
|
362
|
+
if (!prev) log.info(`Resolved "${name}@${version}" → ${exactVersion}`);
|
|
363
|
+
}
|
|
364
|
+
|
|
215
365
|
if (resolved.has(name)) {
|
|
216
366
|
const existing = resolved.get(name);
|
|
217
|
-
if (existing ===
|
|
367
|
+
if (existing === exactVersion) {
|
|
218
368
|
continue;
|
|
219
369
|
}
|
|
220
370
|
// Locked packages never lose to a conflict.
|
|
221
371
|
if (lockedVersions.has(name)) {
|
|
222
372
|
continue;
|
|
223
373
|
}
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
374
|
+
// After range-intersection resolution, we prefer the
|
|
375
|
+
// intersected-range's highest over a naive version bump — the
|
|
376
|
+
// intersection already enforces "both contributors agree", so
|
|
377
|
+
// trust it.
|
|
378
|
+
if (exactVersion !== existing) {
|
|
379
|
+
log.warn(
|
|
380
|
+
`Version conflict for "${name}": ${existing} vs ${exactVersion} — using ${exactVersion}`,
|
|
229
381
|
);
|
|
230
|
-
resolved.set(name,
|
|
382
|
+
resolved.set(name, exactVersion);
|
|
231
383
|
// Re-fetch the winning version's manifest to pull in its transitive deps.
|
|
232
|
-
queue.push({ name, version:
|
|
384
|
+
queue.push({ name, version: exactVersion, by });
|
|
233
385
|
}
|
|
234
386
|
continue;
|
|
235
387
|
}
|
|
236
388
|
|
|
237
|
-
resolved.set(name,
|
|
389
|
+
resolved.set(name, exactVersion);
|
|
390
|
+
// Shadow `version` with the resolved exact for the rest of this block
|
|
391
|
+
// — linked handling, manifest fetch, etc. all expect an exact string.
|
|
392
|
+
version = exactVersion;
|
|
238
393
|
|
|
239
394
|
// Handle linked packages: read local manifest instead of fetching from remote
|
|
240
395
|
let manifest;
|
|
@@ -242,9 +397,9 @@ async function resolveDependencies(rootDeps, packageSources, platform, httpClien
|
|
|
242
397
|
const localPath = getLinkedPath(version);
|
|
243
398
|
manifest = readLocalManifest(localPath);
|
|
244
399
|
if (manifest) {
|
|
245
|
-
|
|
400
|
+
log.info(`Linked: ${name} → ${localPath}`);
|
|
246
401
|
} else {
|
|
247
|
-
|
|
402
|
+
log.info(`Linked: ${name} → ${localPath} (no manifest found)`);
|
|
248
403
|
}
|
|
249
404
|
} else {
|
|
250
405
|
manifest = await fetchPackageManifest(
|
|
@@ -261,6 +416,10 @@ async function resolveDependencies(rootDeps, packageSources, platform, httpClien
|
|
|
261
416
|
continue;
|
|
262
417
|
}
|
|
263
418
|
|
|
419
|
+
if (manifestsOut instanceof Map) {
|
|
420
|
+
manifestsOut.set(name, manifest);
|
|
421
|
+
}
|
|
422
|
+
|
|
264
423
|
// Support both formats:
|
|
265
424
|
// PascalCase array : { Dependencies: [ { Name, Version }, ... ] }
|
|
266
425
|
// string object : { dependencies: { name: "version", ... } }
|
|
@@ -279,7 +438,7 @@ async function resolveDependencies(rootDeps, packageSources, platform, httpClien
|
|
|
279
438
|
}));
|
|
280
439
|
}
|
|
281
440
|
for (const dep of deps) {
|
|
282
|
-
if (dep.version) queue.push({ name: dep.name, version: dep.version });
|
|
441
|
+
if (dep.version) queue.push({ name: dep.name, version: dep.version, by: `${name}@${version}` });
|
|
283
442
|
}
|
|
284
443
|
}
|
|
285
444
|
|
|
@@ -290,6 +449,7 @@ module.exports = {
|
|
|
290
449
|
compareVersions,
|
|
291
450
|
resolveLatestVersion,
|
|
292
451
|
resolveDependencies,
|
|
452
|
+
fetchPublishedVersions,
|
|
293
453
|
isLinkedVersion,
|
|
294
454
|
getLinkedPath,
|
|
295
455
|
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate the contents of `wyvrn_deps.cmake`.
|
|
5
|
+
*
|
|
6
|
+
* This file is loaded via `CMAKE_PROJECT_INCLUDE` — it runs after the
|
|
7
|
+
* consumer's first `project()` call, which is when CMake variables like
|
|
8
|
+
* `CMAKE_CXX_COMPILER_ID`, `CMAKE_SIZEOF_VOID_P`, `CMAKE_SYSTEM_NAME`, and
|
|
9
|
+
* `WIN32` / `APPLE` / `UNIX` become valid. The bundled utility scripts
|
|
10
|
+
* (`variables.cmake` etc.) rely on those, so they cannot be included from
|
|
11
|
+
* the toolchain body.
|
|
12
|
+
*
|
|
13
|
+
* @param {object} args
|
|
14
|
+
* @param {string[]} args.packageNames Resolved dep names in topological order.
|
|
15
|
+
* @param {string} args.profileHash 16-char profile hash (for logging).
|
|
16
|
+
* @returns {string} CMake file content.
|
|
17
|
+
*/
|
|
18
|
+
function generateDepsCmake({ packageNames, profileHash }) {
|
|
19
|
+
const findPackageCalls = packageNames
|
|
20
|
+
.map((name) => ` find_package(${name} CONFIG QUIET)`)
|
|
21
|
+
.join('\n');
|
|
22
|
+
|
|
23
|
+
const resolvedList = packageNames.map((n) => ` ${n}`).join('\n');
|
|
24
|
+
|
|
25
|
+
return `# Generated by wyvrnpm — do not edit.
|
|
26
|
+
# Loaded after project() via CMAKE_PROJECT_INCLUDE.
|
|
27
|
+
# Profile hash: ${profileHash}
|
|
28
|
+
|
|
29
|
+
if(WYVRN_DEPS_INCLUDED)
|
|
30
|
+
return()
|
|
31
|
+
endif()
|
|
32
|
+
set(WYVRN_DEPS_INCLUDED TRUE)
|
|
33
|
+
|
|
34
|
+
# List of every package resolved by \`wyvrnpm install\`, in topological order.
|
|
35
|
+
set(WYVRN_RESOLVED_PACKAGES
|
|
36
|
+
${resolvedList || ' # (none)'}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# ── Bundled wyvrnpm CMake utilities ───────────────────────────────────────────
|
|
40
|
+
# Included in the order the utility scripts expect.
|
|
41
|
+
include("\${WYVRNPM_CMAKE_DIR}/variables.cmake")
|
|
42
|
+
include("\${WYVRNPM_CMAKE_DIR}/options.cmake")
|
|
43
|
+
include("\${WYVRNPM_CMAKE_DIR}/cpp.cmake")
|
|
44
|
+
include("\${WYVRNPM_CMAKE_DIR}/functions.cmake")
|
|
45
|
+
include("\${WYVRNPM_CMAKE_DIR}/macros.cmake")
|
|
46
|
+
|
|
47
|
+
# ── Sanity checks (warn only — do not override the consumer's environment) ───
|
|
48
|
+
if(DEFINED CMAKE_CXX_COMPILER_ID AND DEFINED WYVRN_PROFILE_COMPILER)
|
|
49
|
+
set(_wyvrn_expected_compiler_id "")
|
|
50
|
+
if(WYVRN_PROFILE_COMPILER STREQUAL "msvc")
|
|
51
|
+
set(_wyvrn_expected_compiler_id "MSVC")
|
|
52
|
+
elseif(WYVRN_PROFILE_COMPILER STREQUAL "gcc")
|
|
53
|
+
set(_wyvrn_expected_compiler_id "GNU")
|
|
54
|
+
elseif(WYVRN_PROFILE_COMPILER STREQUAL "clang")
|
|
55
|
+
set(_wyvrn_expected_compiler_id "Clang")
|
|
56
|
+
elseif(WYVRN_PROFILE_COMPILER STREQUAL "apple-clang")
|
|
57
|
+
set(_wyvrn_expected_compiler_id "AppleClang")
|
|
58
|
+
endif()
|
|
59
|
+
if(_wyvrn_expected_compiler_id AND NOT CMAKE_CXX_COMPILER_ID STREQUAL _wyvrn_expected_compiler_id)
|
|
60
|
+
message(WARNING
|
|
61
|
+
"wyvrnpm profile expects compiler '\${WYVRN_PROFILE_COMPILER}' "
|
|
62
|
+
"(CMake id '\${_wyvrn_expected_compiler_id}') but the active compiler "
|
|
63
|
+
"is '\${CMAKE_CXX_COMPILER_ID}'. Installed binaries may be ABI-incompatible."
|
|
64
|
+
)
|
|
65
|
+
endif()
|
|
66
|
+
endif()
|
|
67
|
+
|
|
68
|
+
if(DEFINED CMAKE_SIZEOF_VOID_P AND DEFINED WYVRN_PROFILE_ARCH)
|
|
69
|
+
if(WYVRN_PROFILE_ARCH STREQUAL "x86_64" OR WYVRN_PROFILE_ARCH STREQUAL "armv8")
|
|
70
|
+
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
|
71
|
+
message(WARNING
|
|
72
|
+
"wyvrnpm profile expects 64-bit arch '\${WYVRN_PROFILE_ARCH}' "
|
|
73
|
+
"but CMake is configuring a 32-bit build (CMAKE_SIZEOF_VOID_P=\${CMAKE_SIZEOF_VOID_P})."
|
|
74
|
+
)
|
|
75
|
+
endif()
|
|
76
|
+
elseif(WYVRN_PROFILE_ARCH STREQUAL "x86")
|
|
77
|
+
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4)
|
|
78
|
+
message(WARNING
|
|
79
|
+
"wyvrnpm profile expects 32-bit arch 'x86' "
|
|
80
|
+
"but CMake is configuring a 64-bit build (CMAKE_SIZEOF_VOID_P=\${CMAKE_SIZEOF_VOID_P})."
|
|
81
|
+
)
|
|
82
|
+
endif()
|
|
83
|
+
endif()
|
|
84
|
+
endif()
|
|
85
|
+
|
|
86
|
+
# ── wyvrnpm_find_dependencies() ───────────────────────────────────────────────
|
|
87
|
+
# One-shot helper: resolves every package that wyvrnpm installed.
|
|
88
|
+
# Packages that don't ship a CMake config are silently skipped (CONFIG QUIET).
|
|
89
|
+
function(wyvrnpm_find_dependencies)
|
|
90
|
+
${findPackageCalls || ' # (no dependencies)'}
|
|
91
|
+
|
|
92
|
+
# Post-find aliasing: ensure every resolved package has a wyvrn::<name>
|
|
93
|
+
# target, creating an INTERFACE alias when the package exports only
|
|
94
|
+
# <name>::<name>. Skipped when wyvrn::<name> already exists.
|
|
95
|
+
foreach(_wyvrn_pkg IN LISTS WYVRN_RESOLVED_PACKAGES)
|
|
96
|
+
if(TARGET wyvrn::\${_wyvrn_pkg})
|
|
97
|
+
# Already provided by the package's own CMake config.
|
|
98
|
+
elseif(TARGET \${_wyvrn_pkg}::\${_wyvrn_pkg})
|
|
99
|
+
add_library(wyvrn::\${_wyvrn_pkg} INTERFACE IMPORTED)
|
|
100
|
+
set_target_properties(wyvrn::\${_wyvrn_pkg} PROPERTIES
|
|
101
|
+
INTERFACE_LINK_LIBRARIES \${_wyvrn_pkg}::\${_wyvrn_pkg}
|
|
102
|
+
)
|
|
103
|
+
endif()
|
|
104
|
+
endforeach()
|
|
105
|
+
endfunction()
|
|
106
|
+
|
|
107
|
+
# ── wyvrnpm_enable_runtime_dll_copy(target) ───────────────────────────────────
|
|
108
|
+
# Adds a POST_BUILD command that copies every runtime DLL the target depends on
|
|
109
|
+
# (transitively, via \$<TARGET_RUNTIME_DLLS:...>) next to the target's own
|
|
110
|
+
# binary — so consumers can run the executable straight from the build dir
|
|
111
|
+
# without \`PATH\` tinkering. No-op on non-Windows platforms and on non-
|
|
112
|
+
# executable targets.
|
|
113
|
+
#
|
|
114
|
+
# Respects the per-target opt-out property WYVRN_COPY_RUNTIME_DLLS OFF.
|
|
115
|
+
# Requires CMake ≥ 3.21 (for TARGET_RUNTIME_DLLS).
|
|
116
|
+
function(wyvrnpm_enable_runtime_dll_copy target)
|
|
117
|
+
if(NOT WIN32)
|
|
118
|
+
return()
|
|
119
|
+
endif()
|
|
120
|
+
if(NOT TARGET \${target})
|
|
121
|
+
message(WARNING "wyvrnpm_enable_runtime_dll_copy: target '\${target}' does not exist")
|
|
122
|
+
return()
|
|
123
|
+
endif()
|
|
124
|
+
get_target_property(_wyvrn_type \${target} TYPE)
|
|
125
|
+
if(NOT _wyvrn_type STREQUAL "EXECUTABLE")
|
|
126
|
+
return()
|
|
127
|
+
endif()
|
|
128
|
+
get_target_property(_wyvrn_optout \${target} WYVRN_COPY_RUNTIME_DLLS)
|
|
129
|
+
if(_wyvrn_optout STREQUAL "OFF" OR _wyvrn_optout STREQUAL "FALSE")
|
|
130
|
+
return()
|
|
131
|
+
endif()
|
|
132
|
+
add_custom_command(TARGET \${target} POST_BUILD
|
|
133
|
+
COMMAND \${CMAKE_COMMAND} -E copy_if_different
|
|
134
|
+
"\$<TARGET_RUNTIME_DLLS:\${target}>"
|
|
135
|
+
"\$<TARGET_FILE_DIR:\${target}>"
|
|
136
|
+
COMMAND_EXPAND_LISTS
|
|
137
|
+
VERBATIM
|
|
138
|
+
)
|
|
139
|
+
endfunction()
|
|
140
|
+
|
|
141
|
+
# ── wyvrnpm_finalize_targets() ────────────────────────────────────────────────
|
|
142
|
+
# Discovers all executable targets in the calling directory (top-level only,
|
|
143
|
+
# not recursive) and wires each one up for runtime-DLL copy. Call this at the
|
|
144
|
+
# end of the consumer's top-level CMakeLists.txt, AFTER all add_executable()
|
|
145
|
+
# calls, so every target is visible.
|
|
146
|
+
#
|
|
147
|
+
# Scope: uses the BUILDSYSTEM_TARGETS directory property of the calling
|
|
148
|
+
# directory. For multi-subdirectory projects where executables are added from
|
|
149
|
+
# subdirectories, consumers should either:
|
|
150
|
+
# (a) call wyvrnpm_enable_runtime_dll_copy(<tgt>) explicitly for each exe, or
|
|
151
|
+
# (b) call wyvrnpm_finalize_targets() from each subdirectory.
|
|
152
|
+
function(wyvrnpm_finalize_targets)
|
|
153
|
+
get_property(_wyvrn_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
|
|
154
|
+
foreach(_wyvrn_t IN LISTS _wyvrn_targets)
|
|
155
|
+
get_target_property(_wyvrn_type \${_wyvrn_t} TYPE)
|
|
156
|
+
if(_wyvrn_type STREQUAL "EXECUTABLE")
|
|
157
|
+
wyvrnpm_enable_runtime_dll_copy(\${_wyvrn_t})
|
|
158
|
+
endif()
|
|
159
|
+
endforeach()
|
|
160
|
+
endfunction()
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = { generateDepsCmake };
|