wyvrnpm 2.3.2 → 2.8.1
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 +465 -19
- package/bin/wyvrn.js +175 -9
- package/claude/skills/wyvrnpm.skill +0 -0
- package/cmake/macros.cmake +51 -0
- package/cmake/variables.cmake +13 -3
- package/package.json +1 -1
- package/src/auth.js +66 -0
- package/src/build/cache.js +196 -0
- package/src/build/index.js +23 -2
- package/src/build/msvc-env.js +50 -7
- package/src/build/recipe.js +36 -3
- package/src/commands/add.js +140 -0
- package/src/commands/build.js +111 -7
- package/src/commands/cache.js +189 -0
- package/src/commands/configure.js +15 -4
- package/src/commands/init.js +36 -0
- package/src/commands/install-skill.js +8 -0
- package/src/commands/install.js +271 -5
- package/src/commands/profile.js +70 -7
- package/src/commands/publish.js +41 -4
- package/src/commands/show.js +14 -4
- package/src/compat.js +21 -9
- package/src/conf/index.js +391 -0
- package/src/conf/namespaces.js +94 -0
- package/src/download.js +136 -148
- package/src/logger.js +30 -2
- package/src/manifest.js +34 -7
- package/src/profile.js +148 -12
- package/src/resolve.js +47 -7
- package/src/settings-overrides.js +152 -0
- package/src/toolchain/index.js +71 -0
- package/src/toolchain/presets.js +108 -31
- package/src/toolchain/template.cmake +11 -0
- package/src/upload-built.js +11 -2
- package/src/zip-safe.js +52 -0
package/src/commands/profile.js
CHANGED
|
@@ -10,8 +10,10 @@ const {
|
|
|
10
10
|
listProfiles,
|
|
11
11
|
getProfilesDir,
|
|
12
12
|
mergeProfile,
|
|
13
|
+
resolveProfileChain,
|
|
13
14
|
} = require('../profile');
|
|
14
15
|
const { readConfig, writeConfig } = require('../config');
|
|
16
|
+
const { parseCliConf, flattenConf, unflattenConf } = require('../conf');
|
|
15
17
|
const log = require('../logger');
|
|
16
18
|
|
|
17
19
|
// ---------------------------------------------------------------------------
|
|
@@ -39,11 +41,13 @@ function list() {
|
|
|
39
41
|
|
|
40
42
|
for (const name of names) {
|
|
41
43
|
const marker = name === defName ? ' *' : '';
|
|
42
|
-
|
|
44
|
+
let p;
|
|
45
|
+
try { p = resolveProfileChain(name); }
|
|
46
|
+
catch { p = null; } // cycle / missing parent — fall through to (unresolved)
|
|
43
47
|
const summary = p
|
|
44
48
|
? `${p.os}/${p.arch} ${p.compiler} ${p['compiler.version']} C++${p['compiler.cppstd']}` +
|
|
45
49
|
(p['compiler.runtime'] ? ` [${p['compiler.runtime']}]` : '')
|
|
46
|
-
: '(
|
|
50
|
+
: '(unresolved — check `extends`)';
|
|
47
51
|
console.log(` ${(name + marker).padEnd(24)} ${summary}`);
|
|
48
52
|
}
|
|
49
53
|
}
|
|
@@ -59,12 +63,26 @@ function list() {
|
|
|
59
63
|
function show(argv) {
|
|
60
64
|
const config = readConfig();
|
|
61
65
|
const name = argv.name ?? config.defaultProfile ?? 'default';
|
|
62
|
-
const stored = readProfile(name);
|
|
63
66
|
|
|
64
|
-
|
|
67
|
+
// Raw → displays `extends:` chain for visibility. Resolved → what
|
|
68
|
+
// loadProfile() actually hands to install / publish / build.
|
|
69
|
+
const raw = readProfile(name);
|
|
70
|
+
let resolved;
|
|
71
|
+
try {
|
|
72
|
+
resolved = raw ? resolveProfileChain(name) : null;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
log.error(`Cannot resolve profile "${name}": ${err.message}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (resolved) {
|
|
65
79
|
log.info(`Profile "${name}":`);
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
if (raw.extends !== undefined && raw.extends !== null) {
|
|
81
|
+
const chain = Array.isArray(raw.extends) ? raw.extends.join(', ') : raw.extends;
|
|
82
|
+
log.info(` extends: ${chain}`);
|
|
83
|
+
}
|
|
84
|
+
console.log(formatProfile(resolved));
|
|
85
|
+
console.log(`\nProfile hash : ${hashProfile(resolved)}`);
|
|
68
86
|
} else {
|
|
69
87
|
const detected = detectProfile();
|
|
70
88
|
log.info(`Profile "${name}" not found — showing auto-detected environment:`);
|
|
@@ -103,9 +121,16 @@ function detect(argv) {
|
|
|
103
121
|
// ---------------------------------------------------------------------------
|
|
104
122
|
|
|
105
123
|
/**
|
|
106
|
-
* wyvrnpm configure profile set [--name <name>] [field options]
|
|
124
|
+
* wyvrnpm configure profile set [--name <name>] [field options] [--conf KEY=VAL]
|
|
107
125
|
* Manually override individual fields in a named profile.
|
|
108
126
|
* Starts from the existing saved profile, or auto-detects if none exists yet.
|
|
127
|
+
*
|
|
128
|
+
* --conf <namespace.leaf>=<value> (repeatable) — set a non-ABI build-time
|
|
129
|
+
* knob in the profile's `[conf]` block (PLAN-CONF.md phase 2). Same
|
|
130
|
+
* syntax as `install --conf` / `build --conf`; `KEY` alone means
|
|
131
|
+
* `KEY=ON`, `KEY=` removes any inherited value from this profile.
|
|
132
|
+
* Profile conf never folds into `profileHash`. Allow-listed
|
|
133
|
+
* namespaces only — unknown keys fail fast.
|
|
109
134
|
*/
|
|
110
135
|
function set(argv) {
|
|
111
136
|
const config = readConfig();
|
|
@@ -121,6 +146,44 @@ function set(argv) {
|
|
|
121
146
|
'compiler.runtime': argv.runtime ?? null,
|
|
122
147
|
});
|
|
123
148
|
|
|
149
|
+
// Apply --conf edits, if any. Writes at the leaf level, preserving
|
|
150
|
+
// sibling keys that weren't mentioned. `KEY=` removes the leaf.
|
|
151
|
+
const cliConf = Array.isArray(argv.conf) ? argv.conf : (argv.conf ? [argv.conf] : []);
|
|
152
|
+
if (cliConf.length > 0) {
|
|
153
|
+
let { sets, unsets } = { sets: {}, unsets: new Set() };
|
|
154
|
+
try { ({ sets, unsets } = parseCliConf(cliConf)); }
|
|
155
|
+
catch (err) {
|
|
156
|
+
log.error(`--conf: ${err.message}`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const existingFlat = (() => {
|
|
161
|
+
try { return updated.conf ? flattenConf(updated.conf) : {}; }
|
|
162
|
+
catch (err) {
|
|
163
|
+
log.error(`existing profile conf is malformed: ${err.message}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
})();
|
|
167
|
+
|
|
168
|
+
const nextFlat = { ...existingFlat, ...sets };
|
|
169
|
+
for (const k of unsets) delete nextFlat[k];
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
// Re-validate as a whole so namespace/leaf checks catch both the
|
|
173
|
+
// edits AND any previously-saved pre-phase-2 garbage.
|
|
174
|
+
require('../conf/namespaces').validateFlatConfKeys(nextFlat);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
log.error(`--conf: ${err.message}`);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (Object.keys(nextFlat).length === 0) {
|
|
181
|
+
delete updated.conf;
|
|
182
|
+
} else {
|
|
183
|
+
updated.conf = unflattenConf(nextFlat);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
124
187
|
writeProfile(name, updated);
|
|
125
188
|
|
|
126
189
|
log.info(`Profile "${name}" updated:`);
|
package/src/commands/publish.js
CHANGED
|
@@ -8,6 +8,13 @@ const AdmZip = require('adm-zip');
|
|
|
8
8
|
const { readManifest } = require('../manifest');
|
|
9
9
|
const { getProvider } = require('../providers');
|
|
10
10
|
const { readConfig } = require('../config');
|
|
11
|
+
const { resolveSourceAuth } = require('../auth');
|
|
12
|
+
const {
|
|
13
|
+
readRecipeConf,
|
|
14
|
+
parseCliConf,
|
|
15
|
+
mergeConfLayers,
|
|
16
|
+
unflattenConf,
|
|
17
|
+
} = require('../conf');
|
|
11
18
|
const { loadProfile, hashProfile, sha256Of, getGitSha, getGitRepo, isShaOnRemote } = require('../profile');
|
|
12
19
|
const { defaultCompatBlock } = require('../compat');
|
|
13
20
|
const {
|
|
@@ -89,8 +96,9 @@ function addInstallTree(zip, installDir) {
|
|
|
89
96
|
// ---------------------------------------------------------------------------
|
|
90
97
|
|
|
91
98
|
function resolveSource(argv) {
|
|
92
|
-
let { source, awsProfile
|
|
99
|
+
let { source, awsProfile } = argv;
|
|
93
100
|
const config = readConfig();
|
|
101
|
+
let sourceEntry = null;
|
|
94
102
|
|
|
95
103
|
if (source) {
|
|
96
104
|
const named = config.publishSources.find((s) => s.name === source);
|
|
@@ -98,7 +106,7 @@ function resolveSource(argv) {
|
|
|
98
106
|
log.info(`Using publish source "${named.name}" from config`);
|
|
99
107
|
source = named.url;
|
|
100
108
|
awsProfile = awsProfile ?? named.profile;
|
|
101
|
-
|
|
109
|
+
sourceEntry = named;
|
|
102
110
|
}
|
|
103
111
|
} else {
|
|
104
112
|
if (config.publishSources.length === 0) {
|
|
@@ -112,9 +120,15 @@ function resolveSource(argv) {
|
|
|
112
120
|
log.info(`Using publish source "${first.name}" from config`);
|
|
113
121
|
source = first.url;
|
|
114
122
|
awsProfile = awsProfile ?? first.profile;
|
|
115
|
-
|
|
123
|
+
sourceEntry = first;
|
|
116
124
|
}
|
|
117
125
|
|
|
126
|
+
// Literal --token beats --token-env beats config.token beats config.tokenEnv.
|
|
127
|
+
const { token } = resolveSourceAuth(
|
|
128
|
+
{ token: argv.token, tokenEnv: argv.tokenEnv },
|
|
129
|
+
sourceEntry,
|
|
130
|
+
);
|
|
131
|
+
|
|
118
132
|
return { source, awsProfile, token };
|
|
119
133
|
}
|
|
120
134
|
|
|
@@ -265,9 +279,32 @@ async function publish(argv) {
|
|
|
265
279
|
os.tmpdir(),
|
|
266
280
|
`wyvrnpm-manifest-${name}-${version}-${Date.now()}.json`,
|
|
267
281
|
);
|
|
282
|
+
// ── publishedConf (PLAN-CONF.md §4.6) ────────────────────────────────────
|
|
283
|
+
// Publish reads ONLY the recipe conf + CLI --conf — wyvrn.local.json is
|
|
284
|
+
// intentionally ignored (plan §4.5 / §8.4) so dev-local overlays never
|
|
285
|
+
// leak into shared artefacts. Recorded on the published manifest as
|
|
286
|
+
// informational metadata: it tells maintainers what the publisher's
|
|
287
|
+
// CMake cache vars were at build time but does NOT drive resolution on
|
|
288
|
+
// the consumer side.
|
|
289
|
+
let publishedConf = {};
|
|
290
|
+
try {
|
|
291
|
+
const recipeConfFlat = readRecipeConf(manifest);
|
|
292
|
+
const cli = parseCliConf(argv.conf);
|
|
293
|
+
publishedConf = mergeConfLayers(recipeConfFlat, {}, cli);
|
|
294
|
+
} catch (err) {
|
|
295
|
+
log.error(`conf resolution failed: ${err.message}`);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
if (Object.keys(publishedConf).length > 0) {
|
|
299
|
+
log.info(`Published conf: ${JSON.stringify(publishedConf)}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
268
302
|
const manifestForPublish = {
|
|
269
303
|
...manifest,
|
|
270
304
|
compatibility: manifest.compatibility ?? defaultCompatBlock(),
|
|
305
|
+
...(Object.keys(publishedConf).length > 0
|
|
306
|
+
? { publishedConf: unflattenConf(publishedConf) }
|
|
307
|
+
: {}),
|
|
271
308
|
};
|
|
272
309
|
fs.writeFileSync(tmpManifestPath, JSON.stringify(manifestForPublish, null, 2), 'utf8');
|
|
273
310
|
if (!manifest.compatibility) {
|
|
@@ -288,7 +325,7 @@ async function publish(argv) {
|
|
|
288
325
|
let installOverlayPaths = new Set();
|
|
289
326
|
if (hasBuildRecipe(manifest)) {
|
|
290
327
|
try {
|
|
291
|
-
const recipe = normalizeRecipe(manifest.build, effectiveOptions);
|
|
328
|
+
const recipe = normalizeRecipe(manifest.build, effectiveOptions, manifest);
|
|
292
329
|
const candidate = path.join(
|
|
293
330
|
srcDir, 'build', `wyvrn-${activeProfileName}`, recipe.installDir,
|
|
294
331
|
);
|
package/src/commands/show.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { readConfig }
|
|
4
|
-
const { getProvider }
|
|
5
|
-
const
|
|
3
|
+
const { readConfig } = require('../config');
|
|
4
|
+
const { getProvider } = require('../providers');
|
|
5
|
+
const { resolveSourceAuth } = require('../auth');
|
|
6
|
+
const log = require('../logger');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* `wyvrnpm show <pkg>` — surface registry metadata for a package.
|
|
@@ -145,6 +146,11 @@ async function show(argv) {
|
|
|
145
146
|
if (meta?.options && Object.keys(meta.options).length > 0) {
|
|
146
147
|
console.log(` declared: ${formatDeclaredOptions(meta.options)}`);
|
|
147
148
|
}
|
|
149
|
+
// PLAN-CONF.md §4.6 — surface publisher's publishedConf.
|
|
150
|
+
// Informational only; absent for pre-2.6.0 author publishes.
|
|
151
|
+
if (meta?.publishedConf && Object.keys(meta.publishedConf).length > 0) {
|
|
152
|
+
console.log(` conf : ${JSON.stringify(meta.publishedConf)}`);
|
|
153
|
+
}
|
|
148
154
|
}
|
|
149
155
|
}
|
|
150
156
|
|
|
@@ -157,6 +163,7 @@ async function show(argv) {
|
|
|
157
163
|
uploadedBy: meta?.uploadedBy ?? null,
|
|
158
164
|
compatibility: meta?.compatibility ?? null,
|
|
159
165
|
declaredOptions: meta?.options ?? null,
|
|
166
|
+
publishedConf: meta?.publishedConf ?? null,
|
|
160
167
|
});
|
|
161
168
|
}
|
|
162
169
|
|
|
@@ -202,7 +209,10 @@ function resolveSources(argv) {
|
|
|
202
209
|
return {
|
|
203
210
|
sources,
|
|
204
211
|
awsProfile: argv.awsProfile ?? firstConfig?.profile,
|
|
205
|
-
token:
|
|
212
|
+
token: resolveSourceAuth(
|
|
213
|
+
{ token: argv.token, tokenEnv: argv.tokenEnv },
|
|
214
|
+
firstConfig,
|
|
215
|
+
).token,
|
|
206
216
|
};
|
|
207
217
|
}
|
|
208
218
|
|
package/src/compat.js
CHANGED
|
@@ -34,12 +34,15 @@
|
|
|
34
34
|
* | version fields and leaves room for future "same major"
|
|
35
35
|
* | semantics without renaming.
|
|
36
36
|
*
|
|
37
|
-
* Missing fields default to `exact`. `os` and `arch` are
|
|
38
|
-
*
|
|
39
|
-
* binaries are never ABI-compatible.
|
|
37
|
+
* Missing fields default to `exact`. `os` and `arch` are normally forced
|
|
38
|
+
* to `exact` regardless of what the block says — cross-arch/cross-OS
|
|
39
|
+
* binaries are never ABI-compatible. The one exception is
|
|
40
|
+
* `kind: "HeaderOnlyLib"`: header-only packages ship no binary, so the
|
|
41
|
+
* publisher's declared modes for `os`/`arch` are respected. Binary kinds
|
|
42
|
+
* (`StaticLib`, `DynamicLib`, `ConsoleApp`, …) keep the hard guarantee.
|
|
40
43
|
*/
|
|
41
44
|
|
|
42
|
-
const
|
|
45
|
+
const OS_ARCH_FIELDS = new Set(['os', 'arch']);
|
|
43
46
|
|
|
44
47
|
const DEFAULT_COMPAT_FIELDS = [
|
|
45
48
|
'os',
|
|
@@ -55,19 +58,27 @@ const VALID_MODES = new Set(['exact', 'any', 'lte', 'gte', 'exact-or-newer']);
|
|
|
55
58
|
/**
|
|
56
59
|
* Normalise a raw compatibility block into `{ field → mode }`.
|
|
57
60
|
* Missing fields fall back to `exact`. Unknown modes fall back to `exact`.
|
|
58
|
-
*
|
|
61
|
+
*
|
|
62
|
+
* For binary kinds (`StaticLib`, `DynamicLib`, `ConsoleApp`, …) `os` and
|
|
63
|
+
* `arch` are forced to `exact` — publisher-declared modes are silently
|
|
64
|
+
* dropped because cross-arch binaries are never ABI-safe. For
|
|
65
|
+
* `kind: "HeaderOnlyLib"` the publisher's declared modes are respected,
|
|
66
|
+
* since there is no binary to be ABI-incompatible with.
|
|
59
67
|
*
|
|
60
68
|
* @param {object|null|undefined} rawCompat
|
|
69
|
+
* @param {string|null|undefined} [packageKind] Package manifest `kind` field.
|
|
61
70
|
* @returns {Record<string, string>}
|
|
62
71
|
*/
|
|
63
|
-
function normalizeCompat(rawCompat) {
|
|
72
|
+
function normalizeCompat(rawCompat, packageKind) {
|
|
64
73
|
const result = {};
|
|
65
74
|
for (const field of DEFAULT_COMPAT_FIELDS) result[field] = 'exact';
|
|
66
75
|
|
|
67
76
|
if (!rawCompat || typeof rawCompat !== 'object') return result;
|
|
68
77
|
|
|
78
|
+
const headerOnly = packageKind === 'HeaderOnlyLib';
|
|
79
|
+
|
|
69
80
|
for (const [field, spec] of Object.entries(rawCompat)) {
|
|
70
|
-
if (
|
|
81
|
+
if (!headerOnly && OS_ARCH_FIELDS.has(field)) continue;
|
|
71
82
|
const mode = typeof spec === 'string' ? spec : (spec && spec.mode);
|
|
72
83
|
if (mode && VALID_MODES.has(mode)) result[field] = mode;
|
|
73
84
|
}
|
|
@@ -147,13 +158,14 @@ function evaluateField(mode, consumerVal, packageVal) {
|
|
|
147
158
|
* @param {Record<string, any>} consumerProfile may include `options: {...}`
|
|
148
159
|
* @param {Record<string, any>} packageProfile may include `options: {...}`
|
|
149
160
|
* @param {object|null|undefined} rawCompat compatibility block from the package manifest
|
|
161
|
+
* @param {string|null|undefined} [packageKind] package manifest `kind` field — unlocks `os`/`arch` modes for HeaderOnlyLib
|
|
150
162
|
* @returns {{
|
|
151
163
|
* compatible: boolean,
|
|
152
164
|
* reasons: Array<{ field: string, mode: string, consumer: any, package: any, outcome: string }>,
|
|
153
165
|
* }}
|
|
154
166
|
*/
|
|
155
|
-
function evaluateCompat(consumerProfile, packageProfile, rawCompat) {
|
|
156
|
-
const compat = normalizeCompat(rawCompat);
|
|
167
|
+
function evaluateCompat(consumerProfile, packageProfile, rawCompat, packageKind) {
|
|
168
|
+
const compat = normalizeCompat(rawCompat, packageKind);
|
|
157
169
|
const reasons = [];
|
|
158
170
|
let compatible = true;
|
|
159
171
|
|