wyvrnpm 2.1.0 → 2.4.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 +325 -6
- package/bin/wyvrn.js +167 -14
- package/claude/skills/wyvrnpm.skill +0 -0
- package/cmake/macros.cmake +51 -0
- package/package.json +2 -1
- package/src/build/cache.js +47 -0
- package/src/build/index.js +42 -11
- package/src/build/recipe.js +30 -4
- package/src/commands/add.js +140 -0
- package/src/commands/build.js +96 -12
- package/src/commands/clean.js +39 -15
- package/src/commands/install-skill.js +107 -0
- package/src/commands/install.js +202 -5
- package/src/commands/publish.js +150 -35
- package/src/commands/show.js +237 -0
- package/src/compat.js +53 -12
- package/src/download.js +65 -3
- package/src/ignore.js +118 -0
- package/src/logger.js +26 -10
- package/src/manifest.js +12 -7
- package/src/options.js +303 -0
- package/src/profile.js +28 -4
- package/src/providers/base.js +16 -1
- package/src/providers/file.js +8 -3
- package/src/providers/http.js +12 -8
- package/src/providers/s3.js +11 -7
- package/src/resolve.js +171 -13
- package/src/toolchain/index.js +71 -0
- package/src/toolchain/presets.js +125 -16
- package/src/toolchain/template.cmake +11 -0
- package/src/upload-built.js +256 -0
- package/src/version-range.js +301 -0
package/src/commands/clean.js
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
clearBuildCache,
|
|
8
|
+
getBuildCacheRoot,
|
|
9
|
+
clearUploadSidecars,
|
|
10
|
+
} = require('../build/cache');
|
|
7
11
|
const log = require('../logger');
|
|
8
12
|
|
|
9
13
|
/**
|
|
@@ -11,10 +15,17 @@ const log = require('../logger');
|
|
|
11
15
|
* With `--build-cache`, also clears the machine-wide source-build cache
|
|
12
16
|
* under `%LOCALAPPDATA%\wyvrnpm\bc\` (shared across projects).
|
|
13
17
|
*
|
|
18
|
+
* `--uploaded-built` wipes just the consumer's local RECORD of what they
|
|
19
|
+
* have uploaded via `wyvrnpm install --build=missing --upload-built` (the
|
|
20
|
+
* `.uploaded-to.json` sidecars under the build cache). It does NOT touch
|
|
21
|
+
* the registry, the artefact zips, or the source clones. Privacy / local
|
|
22
|
+
* audit cleanup only.
|
|
23
|
+
*
|
|
14
24
|
* @param {object} argv
|
|
15
|
-
* @param {string} argv.root
|
|
16
|
-
* @param {string} argv.manifest
|
|
17
|
-
* @param {boolean} [argv.buildCache]
|
|
25
|
+
* @param {string} argv.root Project root directory.
|
|
26
|
+
* @param {string} argv.manifest Path to the manifest file (used to locate wyvrn.lock).
|
|
27
|
+
* @param {boolean} [argv.buildCache] When true, also nukes the source-build cache.
|
|
28
|
+
* @param {boolean} [argv.uploadedBuilt] When true, wipes upload sidecars only.
|
|
18
29
|
* @returns {Promise<void>}
|
|
19
30
|
*/
|
|
20
31
|
async function clean(argv) {
|
|
@@ -22,20 +33,27 @@ async function clean(argv) {
|
|
|
22
33
|
const razerDir = path.join(rootDir, 'wyvrn_internal');
|
|
23
34
|
const lockPath = path.join(path.dirname(path.resolve(argv.manifest)), 'wyvrn.lock');
|
|
24
35
|
|
|
36
|
+
// --uploaded-built is a targeted cleanup mode. When passed on its own,
|
|
37
|
+
// skip the default wyvrn_internal/ + wyvrn.lock scrub so a user can
|
|
38
|
+
// "forget uploads" without also wiping their project-local install.
|
|
39
|
+
const targetedOnly = argv.uploadedBuilt && !argv.buildCache;
|
|
40
|
+
|
|
25
41
|
let removedAnything = false;
|
|
26
42
|
|
|
27
|
-
if (
|
|
28
|
-
fs.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
if (!targetedOnly) {
|
|
44
|
+
if (fs.existsSync(razerDir)) {
|
|
45
|
+
fs.rmSync(razerDir, { recursive: true, force: true });
|
|
46
|
+
log.info(`Removed ${razerDir}`);
|
|
47
|
+
removedAnything = true;
|
|
48
|
+
} else {
|
|
49
|
+
log.info(`Nothing to clean at ${razerDir}`);
|
|
50
|
+
}
|
|
34
51
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
if (fs.existsSync(lockPath)) {
|
|
53
|
+
fs.unlinkSync(lockPath);
|
|
54
|
+
log.info(`Removed ${lockPath}`);
|
|
55
|
+
removedAnything = true;
|
|
56
|
+
}
|
|
39
57
|
}
|
|
40
58
|
|
|
41
59
|
if (argv.buildCache) {
|
|
@@ -46,6 +64,12 @@ async function clean(argv) {
|
|
|
46
64
|
} else {
|
|
47
65
|
log.info(`No source-build cache at ${getBuildCacheRoot()}`);
|
|
48
66
|
}
|
|
67
|
+
} else if (argv.uploadedBuilt) {
|
|
68
|
+
// clearBuildCache() would have nuked the sidecars as a side-effect, so
|
|
69
|
+
// only bother with the sidecar sweep when the cache itself is surviving.
|
|
70
|
+
const removed = clearUploadSidecars();
|
|
71
|
+
log.info(`Removed ${removed} upload record${removed === 1 ? '' : 's'} from the build cache`);
|
|
72
|
+
if (removed > 0) removedAnything = true;
|
|
49
73
|
}
|
|
50
74
|
|
|
51
75
|
if (!removedAnything) {
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const AdmZip = require('adm-zip');
|
|
7
|
+
|
|
8
|
+
const log = require('../logger');
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// `wyvrnpm install-skill --claude`
|
|
12
|
+
//
|
|
13
|
+
// Copies the bundled wyvrnpm Agent Skill (claude/skills/wyvrnpm.skill) into
|
|
14
|
+
// the user's Claude Code skills directory so Claude picks it up automatically.
|
|
15
|
+
//
|
|
16
|
+
// Works for both install shapes:
|
|
17
|
+
// - `npm i -g wyvrnpm` → skill zip ships under <pkg>/claude/skills/
|
|
18
|
+
// - `git clone` of this repo → same path, just not from node_modules
|
|
19
|
+
//
|
|
20
|
+
// Destination, per target:
|
|
21
|
+
// --claude → %USERPROFILE%\.claude\skills\wyvrnpm\ (Windows)
|
|
22
|
+
// ~/.claude/skills/wyvrnpm/ (macOS / Linux)
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
const SKILL_NAME = 'wyvrnpm';
|
|
26
|
+
|
|
27
|
+
function bundledSkillPath() {
|
|
28
|
+
// __dirname = <pkg>/src/commands; the .skill zip lives at
|
|
29
|
+
// <pkg>/claude/skills/wyvrnpm.skill.
|
|
30
|
+
return path.resolve(__dirname, '..', '..', 'claude', 'skills', `${SKILL_NAME}.skill`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function claudeSkillsDir() {
|
|
34
|
+
const home = os.homedir();
|
|
35
|
+
if (!home) throw new Error('cannot resolve user home directory');
|
|
36
|
+
return path.join(home, '.claude', 'skills');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function rmrf(target) {
|
|
40
|
+
if (!fs.existsSync(target)) return;
|
|
41
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function installForClaude({ force, dryRun }) {
|
|
45
|
+
const zipPath = bundledSkillPath();
|
|
46
|
+
if (!fs.existsSync(zipPath)) {
|
|
47
|
+
log.error(`bundled skill not found at ${zipPath}`);
|
|
48
|
+
log.error('this is a packaging bug — please file an issue');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const skillsDir = claudeSkillsDir();
|
|
53
|
+
const destDir = path.join(skillsDir, SKILL_NAME);
|
|
54
|
+
const destZip = path.join(skillsDir, `${SKILL_NAME}.skill`);
|
|
55
|
+
|
|
56
|
+
log.info(`Source : ${zipPath}`);
|
|
57
|
+
log.info(`Destination : ${destDir}`);
|
|
58
|
+
|
|
59
|
+
if (fs.existsSync(destDir) && !force) {
|
|
60
|
+
log.warn(`${destDir} already exists`);
|
|
61
|
+
log.warn('re-run with --force to overwrite');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (dryRun) {
|
|
66
|
+
log.info('(--dry-run) would remove existing skill dir and zip, then extract fresh copy');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
71
|
+
rmrf(destDir);
|
|
72
|
+
rmrf(destZip);
|
|
73
|
+
|
|
74
|
+
const zip = new AdmZip(zipPath);
|
|
75
|
+
zip.extractAllTo(skillsDir, /* overwrite */ true);
|
|
76
|
+
fs.copyFileSync(zipPath, destZip);
|
|
77
|
+
|
|
78
|
+
if (!fs.existsSync(path.join(destDir, 'SKILL.md'))) {
|
|
79
|
+
log.error(`extraction produced no SKILL.md under ${destDir}`);
|
|
80
|
+
log.error('the bundled .skill zip may be malformed');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
log.success(`Installed ${SKILL_NAME} skill to ${destDir}`);
|
|
85
|
+
log.info('Claude Code will pick it up on next session start.');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function installSkill(argv) {
|
|
89
|
+
const targets = [];
|
|
90
|
+
if (argv.claude) targets.push('claude');
|
|
91
|
+
|
|
92
|
+
if (targets.length === 0) {
|
|
93
|
+
log.error('no target specified — pass --claude');
|
|
94
|
+
log.error('(more targets may be added in future releases)');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const target of targets) {
|
|
99
|
+
if (target === 'claude') {
|
|
100
|
+
installForClaude({ force: !!argv.force, dryRun: !!argv['dry-run'] });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = installSkill;
|
|
106
|
+
module.exports.bundledSkillPath = bundledSkillPath;
|
|
107
|
+
module.exports.claudeSkillsDir = claudeSkillsDir;
|
package/src/commands/install.js
CHANGED
|
@@ -9,6 +9,17 @@ const { downloadDependencies } = require('../downlo
|
|
|
9
9
|
const { readConfig } = require('../config');
|
|
10
10
|
const { loadProfile, hashProfile, formatProfile, mergeProfile } = require('../profile');
|
|
11
11
|
const { generateToolchain, generateCMakePresets } = require('../toolchain');
|
|
12
|
+
const { normalizeRecipe, hasBuildRecipe } = require('../build/recipe');
|
|
13
|
+
const {
|
|
14
|
+
normalizeOptionsDeclaration,
|
|
15
|
+
resolveEffectiveOptions,
|
|
16
|
+
parseCliOptions,
|
|
17
|
+
} = require('../options');
|
|
18
|
+
const {
|
|
19
|
+
resolveUploadDestination,
|
|
20
|
+
createUploadStats,
|
|
21
|
+
formatUploadSummary,
|
|
22
|
+
} = require('../upload-built');
|
|
12
23
|
const log = require('../logger');
|
|
13
24
|
|
|
14
25
|
/**
|
|
@@ -26,6 +37,23 @@ async function install(argv) {
|
|
|
26
37
|
const manifestPath = path.resolve(argv.manifest);
|
|
27
38
|
const rootDir = path.resolve(argv.root);
|
|
28
39
|
const buildMode = argv.build ?? 'never';
|
|
40
|
+
const uploadBuilt = argv.uploadBuilt === true;
|
|
41
|
+
const jsonOut = argv.format === 'json';
|
|
42
|
+
|
|
43
|
+
// Under --format=json, stdout is reserved for the final JSON payload.
|
|
44
|
+
// All info/success lines route to stderr via the logger.
|
|
45
|
+
if (jsonOut) log.setJsonMode(true);
|
|
46
|
+
|
|
47
|
+
// --upload-built implies a build step happened. `--build=never` +
|
|
48
|
+
// `--upload-built` is a usage error — fail fast rather than silently
|
|
49
|
+
// accept something that cannot upload anything.
|
|
50
|
+
if (uploadBuilt && buildMode === 'never') {
|
|
51
|
+
log.error(
|
|
52
|
+
'--upload-built requires --build=missing (or --build=always).\n' +
|
|
53
|
+
' Nothing will be built, so there is nothing to upload.',
|
|
54
|
+
);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
29
57
|
|
|
30
58
|
if (buildMode === 'always') {
|
|
31
59
|
log.error(
|
|
@@ -95,22 +123,44 @@ async function install(argv) {
|
|
|
95
123
|
`Installing dependencies for "${manifest.name}" (platform: ${argv.platform})`,
|
|
96
124
|
);
|
|
97
125
|
|
|
126
|
+
// Manifests collected during resolution — reused below to read each dep's
|
|
127
|
+
// recipe-declared `options` block without a second HTTP round trip.
|
|
128
|
+
const depManifests = new Map();
|
|
129
|
+
|
|
98
130
|
const resolvedDeps = await resolveDependencies(
|
|
99
131
|
depsForResolution,
|
|
100
132
|
packageSources,
|
|
101
133
|
argv.platform,
|
|
102
134
|
fetch,
|
|
103
135
|
lockedVersions,
|
|
136
|
+
depManifests,
|
|
104
137
|
);
|
|
105
138
|
|
|
139
|
+
// CLI `-o <pkg>:<name>=<value>` overrides, parsed into { pkg → {name: value} }.
|
|
140
|
+
const cliOptionsByPkg = parseCliOptions(argv.option);
|
|
141
|
+
|
|
106
142
|
// ── Build per-dependency enriched map ─────────────────────────────────────
|
|
107
|
-
// Each dep gets
|
|
108
|
-
//
|
|
143
|
+
// Each dep gets:
|
|
144
|
+
// - an effective profile (base + per-dep settings overrides)
|
|
145
|
+
// - effective options (recipe defaults + consumer overrides + CLI overrides)
|
|
146
|
+
// - profileHash computed from both (options only hashed in when declared)
|
|
109
147
|
const enrichedDeps = new Map();
|
|
110
148
|
for (const [name, version] of resolvedDeps) {
|
|
111
|
-
const depOverrides
|
|
149
|
+
const depOverrides = normalizedDeps[name]?.settings ?? {};
|
|
112
150
|
const effectiveProfile = mergeProfile(baseProfile, depOverrides);
|
|
113
|
-
|
|
151
|
+
|
|
152
|
+
const depManifest = depManifests.get(name);
|
|
153
|
+
const declaration = depManifest?.options
|
|
154
|
+
? normalizeOptionsDeclaration(depManifest.options, `${name}@${version}`)
|
|
155
|
+
: null;
|
|
156
|
+
const effectiveOptions = resolveEffectiveOptions(
|
|
157
|
+
declaration,
|
|
158
|
+
normalizedDeps[name]?.options ?? {},
|
|
159
|
+
cliOptionsByPkg[name] ?? {},
|
|
160
|
+
`${name}@${version}`,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const profileHash = hashProfile(effectiveProfile, effectiveOptions);
|
|
114
164
|
|
|
115
165
|
if (Object.keys(depOverrides).length > 0) {
|
|
116
166
|
log.info(
|
|
@@ -118,8 +168,16 @@ async function install(argv) {
|
|
|
118
168
|
` (${JSON.stringify(depOverrides)})`,
|
|
119
169
|
);
|
|
120
170
|
}
|
|
171
|
+
if (effectiveOptions) {
|
|
172
|
+
log.info(`${name}: options → ${JSON.stringify(effectiveOptions)}`);
|
|
173
|
+
}
|
|
121
174
|
|
|
122
|
-
enrichedDeps.set(name, {
|
|
175
|
+
enrichedDeps.set(name, {
|
|
176
|
+
version,
|
|
177
|
+
profileHash,
|
|
178
|
+
profile: effectiveProfile,
|
|
179
|
+
options: effectiveOptions,
|
|
180
|
+
});
|
|
123
181
|
}
|
|
124
182
|
|
|
125
183
|
const razerDir = path.join(rootDir, 'wyvrn_internal');
|
|
@@ -133,6 +191,40 @@ async function install(argv) {
|
|
|
133
191
|
profileName,
|
|
134
192
|
};
|
|
135
193
|
|
|
194
|
+
// ── Resolve upload destination up-front when --upload-built is set ──────
|
|
195
|
+
// Failing here (no publish source configured) avoids spending minutes on
|
|
196
|
+
// a source-build whose output we can't deliver.
|
|
197
|
+
if (uploadBuilt) {
|
|
198
|
+
const resolved = resolveUploadDestination(
|
|
199
|
+
{
|
|
200
|
+
uploadSource: argv.uploadSource,
|
|
201
|
+
awsProfile: argv.awsProfile ?? firstInstallSrc?.profile,
|
|
202
|
+
token: argv.token ?? firstInstallSrc?.token,
|
|
203
|
+
},
|
|
204
|
+
config,
|
|
205
|
+
);
|
|
206
|
+
if (!resolved) {
|
|
207
|
+
log.error(
|
|
208
|
+
'--upload-built was passed but no upload destination could be resolved.\n' +
|
|
209
|
+
' Pass --upload-source <url-or-name> or configure a publish source:\n' +
|
|
210
|
+
' wyvrnpm configure add-source --kind publish --name ... --url ...',
|
|
211
|
+
);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
downloadOptions.uploadBuilt = true;
|
|
215
|
+
downloadOptions.uploadSource = resolved.uploadSource;
|
|
216
|
+
downloadOptions.uploadAuth = resolved.uploadAuth;
|
|
217
|
+
downloadOptions.uploadStats = createUploadStats();
|
|
218
|
+
|
|
219
|
+
if (resolved.uploadSource !== (packageSources[0] ?? '')) {
|
|
220
|
+
log.info(
|
|
221
|
+
`upload-built: install source(s) and upload source differ —\n` +
|
|
222
|
+
` install: ${packageSources.join(', ') || '(none)'}\n` +
|
|
223
|
+
` upload : ${resolved.uploadSource}`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
136
228
|
const lockEntries = await downloadDependencies(
|
|
137
229
|
enrichedDeps,
|
|
138
230
|
packageSources,
|
|
@@ -177,11 +269,71 @@ async function install(argv) {
|
|
|
177
269
|
const toolchainRelPath = path
|
|
178
270
|
.relative(rootDir, toolchain)
|
|
179
271
|
.replace(/\\/g, '/');
|
|
272
|
+
|
|
273
|
+
// Derive generator + cacheVariables from the project's own `build` recipe
|
|
274
|
+
// so `wyvrnpm build` locally honours the same knobs the source-build /
|
|
275
|
+
// publish paths use. Without this, `build.configure` is advisory metadata
|
|
276
|
+
// and CMake falls back to its default generator (VS on Windows) — which
|
|
277
|
+
// breaks publishers whose recipe disables example/test subprojects (F15).
|
|
278
|
+
let presetGenerator = null;
|
|
279
|
+
let presetCacheVariables = null;
|
|
280
|
+
let presetInstallDir = null;
|
|
281
|
+
if (hasBuildRecipe(manifest)) {
|
|
282
|
+
try {
|
|
283
|
+
// Resolve the project's own options to expand ${options.*} in its
|
|
284
|
+
// configure args. When the project declares no options, this is null
|
|
285
|
+
// and the recipe's configure / buildArgs must not contain templates
|
|
286
|
+
// (normalizeRecipe enforces that).
|
|
287
|
+
const projectDeclaration = manifest.options
|
|
288
|
+
? normalizeOptionsDeclaration(manifest.options, manifest.name)
|
|
289
|
+
: null;
|
|
290
|
+
const projectCliOverrides = cliOptionsByPkg[manifest.name] ?? {};
|
|
291
|
+
const projectEffectiveOptions = resolveEffectiveOptions(
|
|
292
|
+
projectDeclaration, {}, projectCliOverrides, manifest.name,
|
|
293
|
+
);
|
|
294
|
+
const recipe = normalizeRecipe(manifest.build, projectEffectiveOptions);
|
|
295
|
+
|
|
296
|
+
if (recipe.generators.length > 0) {
|
|
297
|
+
// First entry in the generator fallback chain. Users whose system
|
|
298
|
+
// lacks the first pick can override via CMakeUserPresets.json
|
|
299
|
+
// (which CMake merges in automatically).
|
|
300
|
+
[presetGenerator] = recipe.generators;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Fold -DKEY=VALUE from recipe.configure into cacheVariables. Other
|
|
304
|
+
// configure args (anything not matching `-DKEY=VALUE`) can't be
|
|
305
|
+
// expressed in a CMake preset and are silently skipped here — the
|
|
306
|
+
// source-build path still sees them verbatim.
|
|
307
|
+
const cacheVars = {};
|
|
308
|
+
for (const arg of recipe.configure) {
|
|
309
|
+
const match = arg.match(/^-D([^=]+)=(.*)$/);
|
|
310
|
+
if (match) cacheVars[match[1]] = match[2];
|
|
311
|
+
}
|
|
312
|
+
if (Object.keys(cacheVars).length > 0) presetCacheVariables = cacheVars;
|
|
313
|
+
|
|
314
|
+
// Propagate `build.installDir` so that `wyvrnpm build --install` lands
|
|
315
|
+
// under the project's build tree instead of CMake's system default
|
|
316
|
+
// (C:/Program Files on Windows, /usr/local on Unix). Same semantics as
|
|
317
|
+
// the source-build path which passes -DCMAKE_INSTALL_PREFIX explicitly.
|
|
318
|
+
presetInstallDir = recipe.installDir;
|
|
319
|
+
} catch (err) {
|
|
320
|
+
log.warn(
|
|
321
|
+
`could not derive CMake preset config from the project's build recipe: ${err.message}\n` +
|
|
322
|
+
` the preset will be emitted without a generator or recipe cacheVariables. ` +
|
|
323
|
+
`Fix the recipe or override via CMakeUserPresets.json.`,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
180
328
|
const presetResult = generateCMakePresets({
|
|
181
329
|
projectRoot: rootDir,
|
|
182
330
|
profileName,
|
|
183
331
|
profileHash: hashProfile(baseProfile),
|
|
184
332
|
toolchainRelPath,
|
|
333
|
+
generator: presetGenerator,
|
|
334
|
+
extraCacheVariables: presetCacheVariables,
|
|
335
|
+
installDir: presetInstallDir,
|
|
336
|
+
profileArch: baseProfile.arch ?? null,
|
|
185
337
|
});
|
|
186
338
|
if (presetResult.action === 'skipped') {
|
|
187
339
|
log.warn(
|
|
@@ -198,6 +350,51 @@ async function install(argv) {
|
|
|
198
350
|
|
|
199
351
|
const count = resolvedDeps.size;
|
|
200
352
|
log.success(`Done — ${count} package${count !== 1 ? 's' : ''} installed.`);
|
|
353
|
+
|
|
354
|
+
// ── upload-built summary ────────────────────────────────────────────────
|
|
355
|
+
const uploadSummary = formatUploadSummary(downloadOptions.uploadStats);
|
|
356
|
+
if (uploadSummary) log.info(uploadSummary);
|
|
357
|
+
|
|
358
|
+
// ── JSON payload (--format=json) ────────────────────────────────────────
|
|
359
|
+
// All log.info/success above have gone to stderr thanks to setJsonMode.
|
|
360
|
+
// stdout gets exactly one JSON object, no trailing text.
|
|
361
|
+
if (jsonOut) {
|
|
362
|
+
const packages = [...resolvedDeps.keys()].sort().map((name) => {
|
|
363
|
+
const entry = lockEntries?.get(name) ?? {};
|
|
364
|
+
const consumerSpec = normalizedDeps[name]?.version ?? null;
|
|
365
|
+
const resolvedVer = entry.version ?? resolvedDeps.get(name);
|
|
366
|
+
const versionRange = (consumerSpec && consumerSpec !== resolvedVer) ? consumerSpec : null;
|
|
367
|
+
return {
|
|
368
|
+
name,
|
|
369
|
+
version: resolvedVer,
|
|
370
|
+
versionRange,
|
|
371
|
+
profileHash: entry.profileHash ?? null,
|
|
372
|
+
options: entry.options ?? null,
|
|
373
|
+
contentSha256: entry.contentSha256 ?? null,
|
|
374
|
+
gitSha: entry.gitSha ?? null,
|
|
375
|
+
resolvedFrom: entry.resolvedFrom ?? null,
|
|
376
|
+
};
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const stats = downloadOptions.uploadStats;
|
|
380
|
+
const uploadSummaryJson = stats ? {
|
|
381
|
+
uploaded: stats.uploaded.length,
|
|
382
|
+
skipped: stats.skipped.length,
|
|
383
|
+
failed: stats.failed.length,
|
|
384
|
+
} : null;
|
|
385
|
+
|
|
386
|
+
const payload = {
|
|
387
|
+
command: 'install',
|
|
388
|
+
wyvrnpmVersion: require('../../package.json').version,
|
|
389
|
+
profile: baseProfile,
|
|
390
|
+
profileName,
|
|
391
|
+
profileHash: hashProfile(baseProfile),
|
|
392
|
+
packages,
|
|
393
|
+
lockFile: lockPath,
|
|
394
|
+
uploadSummary: uploadSummaryJson,
|
|
395
|
+
};
|
|
396
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
397
|
+
}
|
|
201
398
|
}
|
|
202
399
|
|
|
203
400
|
module.exports = install;
|