wyvrnpm 2.12.2 → 2.13.11
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 +163 -2
- package/bin/wyvrnpm.js +167 -11
- package/cmake/cpp.cmake +6 -8
- package/package.json +1 -1
- package/src/build/recipe.js +25 -3
- package/src/commands/configure.js +61 -1
- package/src/commands/install.js +76 -1
- package/src/commands/key.js +112 -0
- package/src/commands/publish.js +402 -49
- package/src/commands/show.js +23 -0
- package/src/commands/trust.js +136 -0
- package/src/conf/index.js +131 -11
- package/src/config.js +35 -4
- package/src/download.js +408 -11
- package/src/providers/base.js +110 -15
- package/src/providers/file.js +90 -29
- package/src/providers/http.js +109 -32
- package/src/providers/s3.js +103 -31
- package/src/publish/slice.js +414 -0
- package/src/signing/attestation.js +301 -0
- package/src/signing/canonical.js +80 -0
- package/src/signing/ed25519.js +123 -0
- package/src/signing/index.js +201 -0
- package/src/signing/resolve-context.js +73 -0
- package/src/signing/trust.js +215 -0
- package/src/toolchain/template.cmake +7 -5
- package/src/upload-built.js +88 -14
- package/src/v2-meta.js +98 -0
package/src/commands/install.js
CHANGED
|
@@ -6,6 +6,7 @@ const path = require('path');
|
|
|
6
6
|
const { normalizeDependencies } = require('../manifest');
|
|
7
7
|
const { resolveDependencies } = require('../resolve');
|
|
8
8
|
const { downloadDependencies } = require('../download');
|
|
9
|
+
const { resolveSigningContext } = require('../signing/resolve-context');
|
|
9
10
|
const { wyvrnFetch } = require('../http-fetch');
|
|
10
11
|
const { resolveEffectiveConf, cmakeCacheVariables, unflattenConf } = require('../conf');
|
|
11
12
|
const { resolveBinaryDirRoot } = require('../binary-dir');
|
|
@@ -350,6 +351,47 @@ async function install(argv) {
|
|
|
350
351
|
|
|
351
352
|
const razerDir = path.join(rootDir, 'wyvrn_internal');
|
|
352
353
|
|
|
354
|
+
// ── Resolve requestConfigs for per-config dep slicing ─────────────────────
|
|
355
|
+
// Precedence: CLI > wyvrn.local.json > consumer's recipe build.configs.
|
|
356
|
+
// When all three are absent, falls through to null which means "download
|
|
357
|
+
// every config the dep author published" (preserves pre-feature semantics
|
|
358
|
+
// for fat-zip deps; for sliced deps, equivalent to "no override, fetch all").
|
|
359
|
+
let requestConfigs = null;
|
|
360
|
+
const cliRaw = typeof argv.requestConfigs === 'string' ? argv.requestConfigs.trim() : null;
|
|
361
|
+
if (cliRaw) {
|
|
362
|
+
requestConfigs = cliRaw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
363
|
+
if (requestConfigs.length === 0) {
|
|
364
|
+
log.error('--request-configs was empty after parsing');
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
} else if (effectiveConfResult.localRequestConfigs) {
|
|
368
|
+
requestConfigs = effectiveConfResult.localRequestConfigs.slice();
|
|
369
|
+
} else if (manifest && manifest.build && (Array.isArray(manifest.build.configs) || typeof manifest.build.configs === 'string')) {
|
|
370
|
+
// Read configs directly from the consumer's manifest. Avoid normalizeRecipe
|
|
371
|
+
// here because it can throw on `${options.X}` references that this caller
|
|
372
|
+
// hasn't resolved yet — request-configs only depends on the configs field.
|
|
373
|
+
const raw = manifest.build.configs;
|
|
374
|
+
requestConfigs = (typeof raw === 'string' ? [raw] : raw)
|
|
375
|
+
.filter((s) => typeof s === 'string' && s.trim())
|
|
376
|
+
.slice();
|
|
377
|
+
if (requestConfigs.length === 0) requestConfigs = null;
|
|
378
|
+
}
|
|
379
|
+
if (requestConfigs) {
|
|
380
|
+
log.info(`Request configs: ${requestConfigs.join(', ')}`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Per-dep override map (wyvrn.local.json: depRequestConfigs). When a
|
|
384
|
+
// dep is named in this map, its slice selection is the listed configs;
|
|
385
|
+
// otherwise it inherits the global `requestConfigs`. Use case: "fetch
|
|
386
|
+
// all four configs of fmt + zlib, but only MinSizeRel of grpc."
|
|
387
|
+
const depRequestConfigs = effectiveConfResult.localDepRequestConfigs ?? null;
|
|
388
|
+
if (depRequestConfigs && Object.keys(depRequestConfigs).length > 0) {
|
|
389
|
+
const summary = Object.entries(depRequestConfigs)
|
|
390
|
+
.map(([n, c]) => `${n}=[${c.join(',')}]`)
|
|
391
|
+
.join(' ');
|
|
392
|
+
log.info(`Per-dep request configs: ${summary}`);
|
|
393
|
+
}
|
|
394
|
+
|
|
353
395
|
// Extract auth from the first install source (if any). Prefer
|
|
354
396
|
// `tokenEnv` over a literal `token` stored in config — closes
|
|
355
397
|
// EVALUATION.md S8 (no plaintext secret persisted, value is looked up
|
|
@@ -360,6 +402,12 @@ async function install(argv) {
|
|
|
360
402
|
token: ctx.auth.for(firstInstallSrc).token,
|
|
361
403
|
buildMode,
|
|
362
404
|
profileName,
|
|
405
|
+
// S1: forwarded straight through to download.js's verify tail.
|
|
406
|
+
requireSignatures: Boolean(argv.requireSignatures),
|
|
407
|
+
// Per-config slice selection. null = "no override, fetch all available".
|
|
408
|
+
requestConfigs,
|
|
409
|
+
// Per-dep override; null when the overlay omits it.
|
|
410
|
+
depRequestConfigs,
|
|
363
411
|
};
|
|
364
412
|
|
|
365
413
|
// ── Resolve upload destination up-front when --upload-built is set ──────
|
|
@@ -387,6 +435,33 @@ async function install(argv) {
|
|
|
387
435
|
downloadOptions.uploadAuth = resolved.uploadAuth;
|
|
388
436
|
downloadOptions.uploadStats = createUploadStats();
|
|
389
437
|
|
|
438
|
+
// S1: resolve signing context for the consumer-upload tail. Same
|
|
439
|
+
// precedence as `wyvrnpm publish` — per-source signing on the
|
|
440
|
+
// resolved upload entry, then defaultSigning, then CLI overrides.
|
|
441
|
+
// When null, the upload is unsigned and downstream consumers warn /
|
|
442
|
+
// abort per the destination registry's mode.json.
|
|
443
|
+
const uploadSourceEntry = (config.publishSources ?? [])
|
|
444
|
+
.find((s) => s.url === resolved.uploadSource || s.name === argv.uploadSource) ?? null;
|
|
445
|
+
let uploadSigningContext = null;
|
|
446
|
+
try {
|
|
447
|
+
const ctxOrSkip = resolveSigningContext(
|
|
448
|
+
{
|
|
449
|
+
noSign: argv.noSign,
|
|
450
|
+
signingKeyEnv: argv.signingKeyEnv,
|
|
451
|
+
signingKeyFile: argv.signingKeyFile,
|
|
452
|
+
},
|
|
453
|
+
config,
|
|
454
|
+
uploadSourceEntry,
|
|
455
|
+
);
|
|
456
|
+
// `skip: true` (--no-sign) is treated identically to "no context"
|
|
457
|
+
// here — the consumer just doesn't sign.
|
|
458
|
+
uploadSigningContext = (ctxOrSkip && !ctxOrSkip.skip) ? ctxOrSkip : null;
|
|
459
|
+
} catch (err) {
|
|
460
|
+
log.error(`upload-built signing config invalid: ${err.message}`);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
downloadOptions.uploadSigningContext = uploadSigningContext;
|
|
464
|
+
|
|
390
465
|
if (resolved.uploadSource !== (packageSources[0] ?? '')) {
|
|
391
466
|
log.info(
|
|
392
467
|
`upload-built: install source(s) and upload source differ —\n` +
|
|
@@ -569,7 +644,7 @@ async function install(argv) {
|
|
|
569
644
|
// here so CI doesn't take "Done — N installed" at face value when one of
|
|
570
645
|
// the deps actually failed.
|
|
571
646
|
const SUCCESS_RESOLVED = new Set([
|
|
572
|
-
'v1', 'v2', 'v2-compat', 'v2-source-build', 'cached', 'link',
|
|
647
|
+
'v1', 'v2', 'v2-slice', 'v2-compat', 'v2-source-build', 'cached', 'link',
|
|
573
648
|
]);
|
|
574
649
|
const failed = [];
|
|
575
650
|
for (const [name, entry] of lockEntries ?? []) {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const ed25519 = require('../signing/ed25519');
|
|
7
|
+
const log = require('../logger');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `wyvrnpm key gen --out <dir> [--id <publisher-id>]`
|
|
11
|
+
*
|
|
12
|
+
* Generates a fresh Ed25519 keypair and writes:
|
|
13
|
+
* <dir>/<id>.priv.pem — PKCS#8 private key (publisher keeps secret)
|
|
14
|
+
* <dir>/<id>.pub.pem — SPKI PEM public key (paste into trust anchor)
|
|
15
|
+
* <dir>/<id>.pub.b64 — Base64 SPKI DER (the literal value for keys.json)
|
|
16
|
+
*
|
|
17
|
+
* Refuses to overwrite existing files — rotation events are rare and
|
|
18
|
+
* destructive enough that the user should explicitly remove the old keys
|
|
19
|
+
* (or pick a fresh `--id`) rather than have the command stomp them.
|
|
20
|
+
*/
|
|
21
|
+
async function gen(argv) {
|
|
22
|
+
const outDir = argv.out;
|
|
23
|
+
const id = argv.id ?? 'wyvrnpm';
|
|
24
|
+
if (!outDir) { log.error('--out <dir> is required'); process.exit(1); }
|
|
25
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(id)) {
|
|
26
|
+
log.error(`--id must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/ (got "${id}")`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const privPath = path.join(outDir, `${id}.priv.pem`);
|
|
31
|
+
const pubPath = path.join(outDir, `${id}.pub.pem`);
|
|
32
|
+
const b64Path = path.join(outDir, `${id}.pub.b64`);
|
|
33
|
+
|
|
34
|
+
for (const p of [privPath, pubPath, b64Path]) {
|
|
35
|
+
if (fs.existsSync(p)) {
|
|
36
|
+
log.error(`refusing to overwrite existing file: ${p}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
42
|
+
const k = ed25519.generateKeyPair();
|
|
43
|
+
// Private key permissions: chmod 0600 on POSIX so a stray process or
|
|
44
|
+
// backup tool doesn't accidentally include it. No-op on Windows.
|
|
45
|
+
fs.writeFileSync(privPath, k.privateKeyPem, { mode: 0o600 });
|
|
46
|
+
fs.writeFileSync(pubPath, k.publicKeyPem);
|
|
47
|
+
fs.writeFileSync(b64Path, k.publicKeyB64 + '\n');
|
|
48
|
+
|
|
49
|
+
if (argv.format === 'json') {
|
|
50
|
+
process.stdout.write(JSON.stringify({
|
|
51
|
+
command: 'key gen',
|
|
52
|
+
wyvrnpmVersion: require('../../package.json').version,
|
|
53
|
+
id,
|
|
54
|
+
out: outDir,
|
|
55
|
+
privateKeyFile: privPath,
|
|
56
|
+
publicKeyPem: pubPath,
|
|
57
|
+
publicKeyB64: b64Path,
|
|
58
|
+
publicKey: k.publicKeyB64,
|
|
59
|
+
}, null, 2) + '\n');
|
|
60
|
+
} else {
|
|
61
|
+
log.success(`Generated ed25519 keypair "${id}"`);
|
|
62
|
+
log.info(` private key : ${privPath}`);
|
|
63
|
+
log.info(` public PEM : ${pubPath}`);
|
|
64
|
+
log.info(` public b64 : ${b64Path}`);
|
|
65
|
+
console.log();
|
|
66
|
+
console.log('Add this entry to your registry\'s `.trust/keys.json`:');
|
|
67
|
+
console.log(JSON.stringify({
|
|
68
|
+
id, alg: 'ed25519',
|
|
69
|
+
publicKey: k.publicKeyB64,
|
|
70
|
+
identityHint: '<your.email@example.com>',
|
|
71
|
+
addedAt: new Date().toISOString(),
|
|
72
|
+
retiredAt: null,
|
|
73
|
+
}, null, 2));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* `wyvrnpm key show-pub --in <priv-file>`
|
|
79
|
+
*
|
|
80
|
+
* Re-derives the public key from a private key file. Handy for verifying
|
|
81
|
+
* that a CI-supplied key matches the keyId pinned in `keys.json` before
|
|
82
|
+
* publishing — derives + prints, without ever writing back to disk.
|
|
83
|
+
*/
|
|
84
|
+
async function showPub(argv) {
|
|
85
|
+
const inPath = argv.in;
|
|
86
|
+
if (!inPath) { log.error('--in <priv-file> is required'); process.exit(1); }
|
|
87
|
+
if (!fs.existsSync(inPath)) {
|
|
88
|
+
log.error(`private key file not found: ${inPath}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
let derived;
|
|
92
|
+
try {
|
|
93
|
+
derived = ed25519.derivePublicKey(fs.readFileSync(inPath, 'utf8'));
|
|
94
|
+
} catch (err) {
|
|
95
|
+
log.error(err.message);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
if (argv.format === 'json') {
|
|
99
|
+
process.stdout.write(JSON.stringify({
|
|
100
|
+
command: 'key show-pub',
|
|
101
|
+
wyvrnpmVersion: require('../../package.json').version,
|
|
102
|
+
in: inPath,
|
|
103
|
+
publicKeyPem: derived.publicKeyPem,
|
|
104
|
+
publicKey: derived.publicKeyB64,
|
|
105
|
+
}, null, 2) + '\n');
|
|
106
|
+
} else {
|
|
107
|
+
process.stdout.write(derived.publicKeyPem);
|
|
108
|
+
process.stdout.write(`\nbase64: ${derived.publicKeyB64}\n`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = { gen, showPub };
|