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.
Files changed (44) hide show
  1. package/README.md +639 -5
  2. package/bin/wyvrn.js +220 -10
  3. package/claude/skills/wyvrnpm.skill +0 -0
  4. package/cmake/cpp.cmake +9 -0
  5. package/cmake/functions.cmake +224 -0
  6. package/cmake/macros.cmake +233 -0
  7. package/cmake/options.cmake +23 -0
  8. package/cmake/variables.cmake +171 -0
  9. package/package.json +3 -1
  10. package/src/build/cache.js +148 -0
  11. package/src/build/clone.js +170 -0
  12. package/src/build/cmake.js +342 -0
  13. package/src/build/index.js +275 -0
  14. package/src/build/msvc-env.js +217 -0
  15. package/src/build/recipe.js +155 -0
  16. package/src/commands/build.js +283 -0
  17. package/src/commands/clean.js +56 -16
  18. package/src/commands/configure.js +6 -5
  19. package/src/commands/init.js +3 -2
  20. package/src/commands/install-skill.js +107 -0
  21. package/src/commands/install.js +262 -19
  22. package/src/commands/link.js +18 -15
  23. package/src/commands/profile.js +15 -12
  24. package/src/commands/publish.js +216 -65
  25. package/src/commands/show.js +237 -0
  26. package/src/compat.js +261 -0
  27. package/src/config.js +3 -1
  28. package/src/download.js +431 -87
  29. package/src/ignore.js +118 -0
  30. package/src/logger.js +94 -0
  31. package/src/manifest.js +12 -7
  32. package/src/options.js +303 -0
  33. package/src/profile.js +56 -4
  34. package/src/providers/base.js +16 -1
  35. package/src/providers/file.js +12 -6
  36. package/src/providers/http.js +15 -10
  37. package/src/providers/s3.js +14 -9
  38. package/src/resolve.js +179 -19
  39. package/src/toolchain/deps.js +164 -0
  40. package/src/toolchain/index.js +141 -0
  41. package/src/toolchain/presets.js +263 -0
  42. package/src/toolchain/template.cmake +66 -0
  43. package/src/upload-built.js +256 -0
  44. package/src/version-range.js +301 -0
@@ -0,0 +1,141 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const { generateDepsCmake } = require('./deps');
7
+ const { generateCMakePresets } = require('./presets');
8
+
9
+ /**
10
+ * Absolute path to the bundled `cmake/` directory shipped inside the
11
+ * wyvrnpm npm package. Returned in CMake-friendly form (forward slashes).
12
+ *
13
+ * @returns {string}
14
+ */
15
+ function getBundledCmakeDir() {
16
+ const dir = path.resolve(__dirname, '..', '..', 'cmake');
17
+ return dir.replace(/\\/g, '/');
18
+ }
19
+
20
+ /**
21
+ * Substitute `{{KEY}}` placeholders in a template string.
22
+ *
23
+ * @param {string} template
24
+ * @param {Record<string, string>} values
25
+ * @returns {string}
26
+ */
27
+ function renderTemplate(template, values) {
28
+ return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
29
+ if (Object.prototype.hasOwnProperty.call(values, key)) {
30
+ return values[key];
31
+ }
32
+ throw new Error(`Toolchain template has no value for placeholder {{${key}}}`);
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Produce the toolchain template's placeholder substitutions.
38
+ *
39
+ * @param {object} args
40
+ * @param {import('../profile').WyvrnProfile} args.profile
41
+ * @param {string} args.profileName
42
+ * @param {string} args.profileHash
43
+ * @param {string[]} args.packageNames
44
+ * @returns {Record<string, string>}
45
+ */
46
+ function buildPlaceholders({ profile, profileName, profileHash, packageNames }) {
47
+ const prefixPathEntries = packageNames
48
+ .map((name) => ` "\${CMAKE_CURRENT_LIST_DIR}/${name}"`)
49
+ .join('\n');
50
+
51
+ return {
52
+ GENERATED_AT: new Date().toISOString(),
53
+ PROFILE_NAME: profileName,
54
+ PROFILE_HASH: profileHash,
55
+ PROFILE_OS: profile.os ?? '',
56
+ PROFILE_ARCH: profile.arch ?? '',
57
+ PROFILE_COMPILER: profile.compiler ?? '',
58
+ PROFILE_COMPILER_VERSION: profile['compiler.version'] ?? '',
59
+ PROFILE_CPPSTD: profile['compiler.cppstd'] ?? '20',
60
+ PROFILE_RUNTIME: profile['compiler.runtime'] ?? '',
61
+ WYVRNPM_CMAKE_DIR: getBundledCmakeDir(),
62
+ PREFIX_PATH_ENTRIES: prefixPathEntries || ' # (no dependencies)',
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Generate the three toolchain artefacts under `destDir`:
68
+ * - wyvrn_toolchain.cmake
69
+ * - wyvrn_deps.cmake
70
+ * - wyvrn_profile.json
71
+ *
72
+ * ───────────────────────────────────────────────────────────────────────────
73
+ * CONTRACT (PLAN-CMAKE-TOOLCHAIN Phase F ↔ PLAN-BUILD-MISSING Phase 3)
74
+ * ───────────────────────────────────────────────────────────────────────────
75
+ * This function is the **single source of truth** for translating a build
76
+ * profile into a CMake configuration. Two callers exist:
77
+ *
78
+ * 1. `install` — writes the toolchain into a consumer project's
79
+ * `wyvrn_internal/`, so `cmake --preset` picks it
80
+ * up at configure time.
81
+ * 2. `buildFromSource` — (not yet implemented; PLAN-BUILD-MISSING §3)
82
+ * writes the toolchain into a scratch build dir
83
+ * when we source-build a package via
84
+ * `--build=missing`. The package is compiled
85
+ * with THIS function's output, guaranteeing the
86
+ * resulting binary's profile → flag mapping is
87
+ * bit-for-bit identical to what a consumer sees.
88
+ *
89
+ * Do NOT replicate this mapping elsewhere. If PLAN-BUILD-MISSING §3 finds it
90
+ * needs additional behaviour (e.g. extra CMake cache variables), extend this
91
+ * function — do not branch.
92
+ *
93
+ * Callers choose `destDir` and `packageNames`:
94
+ * - `destDir` — any directory; in source-build, points at the scratch
95
+ * `wyvrn_internal/` co-located with the cloned source.
96
+ * - `packageNames` — in `install`, the consumer's resolved deps; in
97
+ * source-build, the *package's own* resolved deps.
98
+ *
99
+ * Profile arguments (`profile`, `profileName`, `profileHash`) are always the
100
+ * active consumer profile — source-build still uses the consumer's profile,
101
+ * not the package's. That is the whole point of the source-build path.
102
+ * ───────────────────────────────────────────────────────────────────────────
103
+ *
104
+ * @param {object} args
105
+ * @param {string} args.destDir Where to write the three files.
106
+ * @param {import('../profile').WyvrnProfile} args.profile
107
+ * @param {string} args.profileName
108
+ * @param {string} args.profileHash
109
+ * @param {string[]} args.packageNames Resolved dep names in topological order.
110
+ * @returns {{ toolchain: string, deps: string, profile: string }}
111
+ * Absolute paths of the three files written.
112
+ */
113
+ function generateToolchain({ destDir, profile, profileName, profileHash, packageNames }) {
114
+ fs.mkdirSync(destDir, { recursive: true });
115
+
116
+ const templatePath = path.join(__dirname, 'template.cmake');
117
+ const template = fs.readFileSync(templatePath, 'utf8');
118
+ const placeholders = buildPlaceholders({ profile, profileName, profileHash, packageNames });
119
+ const toolchainContent = renderTemplate(template, placeholders);
120
+ const depsContent = generateDepsCmake({ packageNames, profileHash });
121
+ const profileContent = JSON.stringify({ name: profileName, hash: profileHash, ...profile }, null, 2) + '\n';
122
+
123
+ const toolchainPath = path.join(destDir, 'wyvrn_toolchain.cmake');
124
+ const depsPath = path.join(destDir, 'wyvrn_deps.cmake');
125
+ const profilePath = path.join(destDir, 'wyvrn_profile.json');
126
+
127
+ fs.writeFileSync(toolchainPath, toolchainContent, 'utf8');
128
+ fs.writeFileSync(depsPath, depsContent, 'utf8');
129
+ fs.writeFileSync(profilePath, profileContent, 'utf8');
130
+
131
+ return { toolchain: toolchainPath, deps: depsPath, profile: profilePath };
132
+ }
133
+
134
+ module.exports = {
135
+ generateToolchain,
136
+ generateCMakePresets,
137
+ getBundledCmakeDir,
138
+ // Exported for tests
139
+ buildPlaceholders,
140
+ renderTemplate,
141
+ };
@@ -0,0 +1,263 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const VENDOR_KEY = 'wyvrnpm/generated';
7
+ const SCHEMA_VERSION = 3; // requires CMake ≥ 3.21
8
+
9
+ /**
10
+ * Safely parse a JSON file; returns null on any error (missing, malformed, …).
11
+ * @param {string} p
12
+ * @returns {object|null}
13
+ */
14
+ function safeReadJson(p) {
15
+ try {
16
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * True if the parsed presets object was produced by wyvrnpm (i.e. carries our
24
+ * vendor marker). Hand-edited or user-owned files return false.
25
+ *
26
+ * @param {object|null} presets
27
+ * @returns {boolean}
28
+ */
29
+ function isWyvrnGenerated(presets) {
30
+ return !!presets && typeof presets.vendor === 'object' && !!presets.vendor[VENDOR_KEY];
31
+ }
32
+
33
+ /**
34
+ * Build the configure preset object for the active profile.
35
+ *
36
+ * When the project has its own `build` recipe (i.e. it's a publishable
37
+ * library with `build.generator` / `build.configure` declared), those are
38
+ * propagated into the generated preset so `wyvrnpm build` locally honours
39
+ * the same knobs the source-build + publish paths use. Without this, a
40
+ * publisher's recipe is effectively advisory at install-time (F15 fix).
41
+ *
42
+ * @param {object} args
43
+ * @param {string} args.profileName
44
+ * @param {string} args.profileHash
45
+ * @param {string} args.toolchainRelPath Relative from project root, CMake-style
46
+ * forward slashes.
47
+ * @param {string|null} [args.generator] Single generator name to pin on the
48
+ * preset (e.g. "Ninja Multi-Config").
49
+ * Null / undefined → let CMake pick.
50
+ * @param {Record<string,string>|null} [args.extraCacheVariables]
51
+ * Merged into cacheVariables alongside
52
+ * `CMAKE_TOOLCHAIN_FILE`. Usually
53
+ * derived from the project's own
54
+ * `build.configure` -D args.
55
+ */
56
+ function buildConfigurePreset({
57
+ profileName,
58
+ profileHash,
59
+ toolchainRelPath,
60
+ generator = null,
61
+ extraCacheVariables = null,
62
+ installDir = null,
63
+ }) {
64
+ const binaryDir = `\${sourceDir}/build/wyvrn-${profileName}`;
65
+ // Spread `extraCacheVariables` FIRST, then the wyvrn-owned entries. This
66
+ // means the recipe cannot redirect `CMAKE_TOOLCHAIN_FILE` — our dep
67
+ // injection is always the authoritative toolchain pointer. A malicious
68
+ // or sloppy recipe trying to override it is silently ignored, not
69
+ // quietly obeyed. Same protection applies to `CMAKE_INSTALL_PREFIX`:
70
+ // if a recipe declares `build.installDir`, we resolve it under the
71
+ // binary dir; any cacheVariables override is overwritten below.
72
+ const cacheVariables = {
73
+ ...(extraCacheVariables ?? {}),
74
+ CMAKE_TOOLCHAIN_FILE: `\${sourceDir}/${toolchainRelPath}`,
75
+ };
76
+ if (installDir) {
77
+ // `installDir` in wyvrn.json is defined as relative to the binary dir
78
+ // (same semantics as the source-build path, which passes
79
+ // -DCMAKE_INSTALL_PREFIX=<buildDir>/<installDir>). Without this, a
80
+ // `wyvrnpm build --install` falls back to CMake's system default —
81
+ // on Windows that's `C:/Program Files (x86)/<Project>`, which needs
82
+ // admin rights and almost always fails.
83
+ cacheVariables.CMAKE_INSTALL_PREFIX = `${binaryDir}/${installDir}`;
84
+ }
85
+ const preset = {
86
+ name: `wyvrn-${profileName}`,
87
+ displayName: `wyvrnpm (${profileName})`,
88
+ description: `Generated by wyvrnpm install — profile hash ${profileHash}`,
89
+ binaryDir,
90
+ cacheVariables,
91
+ };
92
+ if (generator) preset.generator = generator;
93
+ return preset;
94
+ }
95
+
96
+ /**
97
+ * Two build presets per configure preset: Debug and Release.
98
+ *
99
+ * @param {string} profileName
100
+ * @returns {Array<object>}
101
+ */
102
+ function buildBuildPresets(profileName) {
103
+ const config = `wyvrn-${profileName}`;
104
+ return [
105
+ {
106
+ name: `${config}-debug`,
107
+ displayName: `wyvrn ${profileName} (Debug)`,
108
+ configurePreset: config,
109
+ configuration: 'Debug',
110
+ },
111
+ {
112
+ name: `${config}-release`,
113
+ displayName: `wyvrn ${profileName} (Release)`,
114
+ configurePreset: config,
115
+ configuration: 'Release',
116
+ },
117
+ ];
118
+ }
119
+
120
+ /**
121
+ * Build a fresh presets file from scratch (no prior content to merge).
122
+ */
123
+ function buildFreshPresetsFile({
124
+ profileName,
125
+ profileHash,
126
+ toolchainRelPath,
127
+ generator = null,
128
+ extraCacheVariables = null,
129
+ installDir = null,
130
+ }) {
131
+ return {
132
+ version: SCHEMA_VERSION,
133
+ vendor: {
134
+ [VENDOR_KEY]: {
135
+ profileName,
136
+ profileHash,
137
+ generatedAt: new Date().toISOString(),
138
+ },
139
+ },
140
+ configurePresets: [buildConfigurePreset({
141
+ profileName, profileHash, toolchainRelPath, generator, extraCacheVariables, installDir,
142
+ })],
143
+ buildPresets: buildBuildPresets(profileName),
144
+ };
145
+ }
146
+
147
+ /**
148
+ * Merge the current profile's presets into an existing wyvrnpm-generated
149
+ * file. Presets with matching `name` are replaced; others are preserved so
150
+ * `wyvrnpm install --profile release` followed by `wyvrnpm install --profile
151
+ * debug` leaves both profiles available in the file.
152
+ *
153
+ * @param {object} existing
154
+ * @param {object} args forwarded to the preset builders
155
+ * @returns {object}
156
+ */
157
+ function mergeIntoExisting(existing, {
158
+ profileName,
159
+ profileHash,
160
+ toolchainRelPath,
161
+ generator = null,
162
+ extraCacheVariables = null,
163
+ installDir = null,
164
+ }) {
165
+ const newConfigure = buildConfigurePreset({
166
+ profileName, profileHash, toolchainRelPath, generator, extraCacheVariables, installDir,
167
+ });
168
+ const newBuilds = buildBuildPresets(profileName);
169
+ const newConfigureNames = new Set([newConfigure.name]);
170
+ const newBuildNames = new Set(newBuilds.map((b) => b.name));
171
+
172
+ const configurePresets = (existing.configurePresets ?? []).filter((p) => !newConfigureNames.has(p.name));
173
+ configurePresets.push(newConfigure);
174
+
175
+ const buildPresets = (existing.buildPresets ?? []).filter((p) => !newBuildNames.has(p.name));
176
+ buildPresets.push(...newBuilds);
177
+
178
+ return {
179
+ ...existing,
180
+ version: existing.version ?? SCHEMA_VERSION,
181
+ vendor: {
182
+ ...(existing.vendor ?? {}),
183
+ [VENDOR_KEY]: {
184
+ profileName,
185
+ profileHash,
186
+ generatedAt: new Date().toISOString(),
187
+ },
188
+ },
189
+ configurePresets,
190
+ buildPresets,
191
+ };
192
+ }
193
+
194
+ /**
195
+ * Write a CMakePresets.json (or CMakeUserPresets.json, as fallback) under
196
+ * `projectRoot`. Never overwrites a user-owned file.
197
+ *
198
+ * Resolution order:
199
+ * 1. CMakePresets.json
200
+ * - Does not exist → create it.
201
+ * - Exists, wyvrn-gen → merge into it.
202
+ * - Exists, user-owned → leave alone, fall through.
203
+ * 2. CMakeUserPresets.json
204
+ * - Does not exist → create it.
205
+ * - Exists, wyvrn-gen → merge into it.
206
+ * - Exists, user-owned → skip (action: "skipped").
207
+ *
208
+ * @param {object} args
209
+ * @param {string} args.projectRoot Directory containing top-level CMakeLists.txt.
210
+ * @param {string} args.profileName
211
+ * @param {string} args.profileHash
212
+ * @param {string} args.toolchainRelPath CMake-style forward slashes, relative to projectRoot.
213
+ * @param {string|null} [args.generator] Propagated onto the configure preset's `generator` field.
214
+ * @param {Record<string,string>|null} [args.extraCacheVariables]
215
+ * Merged into the configure preset's `cacheVariables`.
216
+ * @returns {{ path: string|null, action: 'created'|'updated'|'skipped', isUser: boolean }}
217
+ */
218
+ function generateCMakePresets({
219
+ projectRoot,
220
+ profileName,
221
+ profileHash,
222
+ toolchainRelPath,
223
+ generator = null,
224
+ extraCacheVariables = null,
225
+ installDir = null,
226
+ }) {
227
+ const args = { profileName, profileHash, toolchainRelPath, generator, extraCacheVariables, installDir };
228
+ const candidates = [
229
+ { filePath: path.join(projectRoot, 'CMakePresets.json'), isUser: false },
230
+ { filePath: path.join(projectRoot, 'CMakeUserPresets.json'), isUser: true },
231
+ ];
232
+
233
+ for (const { filePath, isUser } of candidates) {
234
+ if (!fs.existsSync(filePath)) {
235
+ const content = buildFreshPresetsFile(args);
236
+ fs.writeFileSync(filePath, JSON.stringify(content, null, 2) + '\n', 'utf8');
237
+ return { path: filePath, action: 'created', isUser };
238
+ }
239
+
240
+ const existing = safeReadJson(filePath);
241
+ if (isWyvrnGenerated(existing)) {
242
+ const merged = mergeIntoExisting(existing, args);
243
+ fs.writeFileSync(filePath, JSON.stringify(merged, null, 2) + '\n', 'utf8');
244
+ return { path: filePath, action: 'updated', isUser };
245
+ }
246
+
247
+ // User-owned — do not touch, try next candidate.
248
+ }
249
+
250
+ return { path: null, action: 'skipped', isUser: false };
251
+ }
252
+
253
+ module.exports = {
254
+ generateCMakePresets,
255
+ // Exported for tests
256
+ isWyvrnGenerated,
257
+ buildConfigurePreset,
258
+ buildBuildPresets,
259
+ buildFreshPresetsFile,
260
+ mergeIntoExisting,
261
+ VENDOR_KEY,
262
+ SCHEMA_VERSION,
263
+ };
@@ -0,0 +1,66 @@
1
+ # Generated by wyvrnpm — do not edit.
2
+ # Regenerated on every `wyvrnpm install`.
3
+ #
4
+ # Usage:
5
+ # cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=wyvrn_internal/wyvrn_toolchain.cmake
6
+ #
7
+ # Profile : {{PROFILE_NAME}} [{{PROFILE_HASH}}]
8
+ # Generated: {{GENERATED_AT}}
9
+
10
+ if(WYVRNPM_TOOLCHAIN_INCLUDED)
11
+ return()
12
+ endif()
13
+ set(WYVRNPM_TOOLCHAIN_INCLUDED TRUE)
14
+
15
+ # ── Active build profile (copied verbatim from wyvrn_profile.json) ────────────
16
+ set(WYVRN_PROFILE_NAME "{{PROFILE_NAME}}")
17
+ set(WYVRN_PROFILE_HASH "{{PROFILE_HASH}}")
18
+ set(WYVRN_PROFILE_OS "{{PROFILE_OS}}")
19
+ set(WYVRN_PROFILE_ARCH "{{PROFILE_ARCH}}")
20
+ set(WYVRN_PROFILE_COMPILER "{{PROFILE_COMPILER}}")
21
+ set(WYVRN_PROFILE_COMPILER_VERSION "{{PROFILE_COMPILER_VERSION}}")
22
+ set(WYVRN_PROFILE_CPPSTD "{{PROFILE_CPPSTD}}")
23
+ set(WYVRN_PROFILE_RUNTIME "{{PROFILE_RUNTIME}}")
24
+
25
+ # ── Path to wyvrnpm's bundled CMake utilities (variables.cmake, etc.) ─────────
26
+ # Resolved at generation time so the toolchain works regardless of whether
27
+ # wyvrnpm was installed globally, locally, or via npx.
28
+ set(WYVRNPM_CMAKE_DIR "{{WYVRNPM_CMAKE_DIR}}")
29
+
30
+ # ── C++ standard ──────────────────────────────────────────────────────────────
31
+ if(NOT DEFINED CMAKE_CXX_STANDARD)
32
+ set(CMAKE_CXX_STANDARD {{PROFILE_CPPSTD}})
33
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
34
+ set(CMAKE_CXX_EXTENSIONS OFF)
35
+ endif()
36
+
37
+ # ── MSVC runtime library ──────────────────────────────────────────────────────
38
+ # Only meaningful when the actual compiler is MSVC. CMake ignores this on other
39
+ # compilers, so setting it unconditionally is safe.
40
+ if("${WYVRN_PROFILE_RUNTIME}" STREQUAL "dynamic")
41
+ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
42
+ elseif("${WYVRN_PROFILE_RUNTIME}" STREQUAL "static")
43
+ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
44
+ endif()
45
+
46
+ # ── Position-independent code (sensible default for mixed static/shared) ──────
47
+ if(NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE)
48
+ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
49
+ endif()
50
+
51
+ # ── CMAKE_PREFIX_PATH: so find_package(<dep> CONFIG) finds installed deps ─────
52
+ list(PREPEND CMAKE_PREFIX_PATH
53
+ {{PREFIX_PATH_ENTRIES}}
54
+ )
55
+
56
+ # ── CMAKE_MODULE_PATH: so include(<script>) finds bundled *.cmake files ───────
57
+ list(PREPEND CMAKE_MODULE_PATH
58
+ "${WYVRNPM_CMAKE_DIR}"
59
+ )
60
+
61
+ # ── Post-project() hook ───────────────────────────────────────────────────────
62
+ # CMAKE_PROJECT_INCLUDE runs after the first project() call, which is when
63
+ # CMAKE_SYSTEM_NAME / CMAKE_CXX_COMPILER_ID / CMAKE_SIZEOF_VOID_P become valid.
64
+ # The bundled utilities (variables.cmake etc.) rely on those — so they must be
65
+ # included from here, not from the toolchain body above.
66
+ set(CMAKE_PROJECT_INCLUDE "${CMAKE_CURRENT_LIST_DIR}/wyvrn_deps.cmake")