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
package/README.md CHANGED
@@ -4,7 +4,7 @@ A simple, private C++ package manager that works with any static file hosting pr
4
4
 
5
5
  There is no central registry. You control where packages are hosted.
6
6
 
7
- > **README version: 2.0.1** — Last updated to reflect the v2 build-profile system.
7
+ > **README version: 2.1.0** — Adds CMake toolchain generation, the `wyvrnpm build` command, declarative compatibility, and `--build=missing` source-build.
8
8
 
9
9
  ---
10
10
 
@@ -40,10 +40,13 @@ wyvrnpm configure profile detect
40
40
  # 4. Install dependencies (resolved against your build profile)
41
41
  wyvrnpm install
42
42
 
43
- # 5. Publish your package (uses the active profile automatically)
43
+ # 5. Configure and build via the generated CMake preset
44
+ wyvrnpm build
45
+
46
+ # 6. Publish your package (uses the active profile automatically)
44
47
  wyvrnpm publish
45
48
 
46
- # 6. (Optional) Link local packages for development
49
+ # 7. (Optional) Link local packages for development
47
50
  cd ../my-local-lib && wyvrnpm link
48
51
  cd ../my-app && wyvrnpm link my-local-lib
49
52
  ```
@@ -112,7 +115,14 @@ Resolves the full dependency graph and downloads all packages.
112
115
 
113
116
  **Source priority:** CLI `--source` values are used if provided. If omitted, sources are loaded from the [configuration file](#configuration-file).
114
117
 
115
- **Profile resolution:** `--profile` selects the named build profile. If omitted, `config.defaultProfile` is used, falling back to `"default"`. v2 binaries are matched first by exact profile hash; if no exact match is found, the tool looks for a compatible build (same OS + arch, any compiler), then falls back to the v1 legacy path.
118
+ **Profile resolution:** `--profile` selects the named build profile. If omitted, `config.defaultProfile` is used, falling back to `"default"`. v2 binaries are resolved in this order:
119
+
120
+ 1. **Exact profile-hash match** — the preferred path.
121
+ 2. **Declarative compatibility** — each published build may advertise a `compatibility` block (e.g. "compatible with any `msvc.version ≥ 193` on `Windows/x86_64`"). The best compatible candidate is picked deterministically.
122
+ 3. **Source build** (with `--build=missing`) — clones the package's source at the published `gitSha`, resolves its own dependencies, invokes CMake through the shared toolchain generator, and caches the result under `%LOCALAPPDATA%\wyvrnpm\bc\`. On Windows/MSVC with a Ninja or Make generator, `vcvarsall.bat` is auto-activated.
123
+ 4. **v1 legacy path** — only when no v2 entry exists for the package at all.
124
+
125
+ The pre-2.1 "same OS + arch, any compiler" loose fallback is removed from the default path. It remains reachable for one release cycle via `WYVRNPM_ALLOW_LOOSE_COMPAT=1` and prints a deprecation warning.
116
126
 
117
127
  ```bash
118
128
  # Use configured sources and the default build profile
@@ -128,6 +138,9 @@ wyvrnpm install --source https://pkg.example.com/cpp
128
138
  wyvrnpm install \
129
139
  --source https://primary.example.com/cpp \
130
140
  --source https://mirror.example.com/cpp
141
+
142
+ # Build any missing binary from source
143
+ wyvrnpm install --build=missing
131
144
  ```
132
145
 
133
146
  **Options**
@@ -136,6 +149,7 @@ wyvrnpm install \
136
149
  |---|---|---|
137
150
  | `--source` / `-s` | *(config)* | Base URL of a package source. Repeat for multiple. Overrides config when provided. |
138
151
  | `--profile` / `-p` | *(config / "default")* | Named build profile to use for v2 binary resolution. |
152
+ | `--build` | `never` | Resolution mode when no exact or compatible profile match exists: `never` fails; `missing` clones and builds the package from its published `gitRepo@gitSha`; `always` rebuilds even when a binary exists. |
139
153
  | `--platform` | `win_x64` | v1 legacy platform sub-path (used as a fallback when no v2 binary is found). |
140
154
  | `--timeout` | `300` | HTTP request timeout in seconds. |
141
155
  | `--manifest` | `./wyvrn.json` | Path to the manifest file. |
@@ -143,6 +157,66 @@ wyvrnpm install \
143
157
 
144
158
  Packages are extracted to `wyvrn_internal/{name}/`. A `wyvrn.lock` file is written alongside the manifest recording the exact resolved versions, profile, and per-package SHA256. On subsequent installs the lock file pins all previously resolved versions — only newly added dependencies are resolved fresh.
145
159
 
160
+ After download, `install` also generates:
161
+
162
+ - `wyvrn_internal/wyvrn_toolchain.cmake` — CMake toolchain pointing at the resolved packages.
163
+ - `wyvrn_internal/wyvrn_deps.cmake` — post-`project()` include for `find_package()` wiring and runtime DLL copy.
164
+ - `wyvrn_internal/wyvrn_profile.json` — snapshot of the active build profile.
165
+ - `CMakePresets.json` (or `CMakeUserPresets.json` if the project root file is user-owned) with a `wyvrn-<profile>` configure preset. Presets for other profiles are preserved so multiple profiles coexist.
166
+
167
+ ---
168
+
169
+ ### `wyvrnpm build`
170
+
171
+ Configures and builds the project through the wyvrnpm-generated CMake preset. Auto-runs `install` first if `wyvrn_toolchain.cmake` is missing or `wyvrn.lock` is newer than it.
172
+
173
+ ```bash
174
+ # Configure + build with the default profile's preset (wyvrn-default)
175
+ wyvrnpm build
176
+
177
+ # Build Debug, verbose compiler output
178
+ wyvrnpm build --config Debug --verbose
179
+
180
+ # Use a specific profile's preset
181
+ wyvrnpm build --profile release
182
+
183
+ # Explicit preset name (overrides profile-derived default)
184
+ wyvrnpm build --preset wyvrn-release
185
+
186
+ # Build a single target
187
+ wyvrnpm build --target my-lib
188
+
189
+ # Wipe the build directory first
190
+ wyvrnpm build --clean
191
+
192
+ # Build, then install to the CMake install prefix
193
+ wyvrnpm build --install
194
+ wyvrnpm build --install --install-prefix C:\out\stage
195
+
196
+ # Skip the auto-install step
197
+ wyvrnpm build --no-auto-install
198
+
199
+ # Pass extra args through to cmake --build
200
+ wyvrnpm build -- -j 8
201
+ ```
202
+
203
+ **Options**
204
+
205
+ | Option | Default | Description |
206
+ |---|---|---|
207
+ | `--preset` / `-P` | `wyvrn-<profile>` | CMake configure preset name. |
208
+ | `--profile` / `-p` | *(config / "default")* | Build profile — determines the default preset name. |
209
+ | `--config` / `-c` | `Release` | Build configuration (`Debug` \| `Release` \| `RelWithDebInfo` \| `MinSizeRel`). |
210
+ | `--target` / `-t` | *(all)* | Specific CMake target to build. |
211
+ | `--verbose` / `-v` | `false` | Pass `--verbose` to `cmake --build`. |
212
+ | `--clean` | `false` | Remove the build directory before configuring. |
213
+ | `--install` | `false` | Run `cmake --install` after a successful build. |
214
+ | `--install-prefix` | *(baked in)* | Override `CMAKE_INSTALL_PREFIX` for `--install` without reconfiguring. |
215
+ | `--auto-install` | `true` | Auto-run `wyvrnpm install` when the toolchain is stale. Disable with `--no-auto-install`. |
216
+ | `--source` / `-s` | *(config)* | Package sources — passed through to `install` when auto-installing. |
217
+
218
+ Everything after `--` is forwarded verbatim to `cmake --build` (e.g. `-j 8`).
219
+
146
220
  ---
147
221
 
148
222
  ### `wyvrnpm publish`
@@ -221,9 +295,19 @@ build/
221
295
  Removes the `wyvrn_internal/` package cache and `wyvrn.lock`.
222
296
 
223
297
  ```bash
298
+ # Project-local cleanup
224
299
  wyvrnpm clean
300
+
301
+ # Also wipe the machine-wide source-build cache used by --build=missing
302
+ wyvrnpm clean --build-cache
225
303
  ```
226
304
 
305
+ **Options**
306
+
307
+ | Option | Default | Description |
308
+ |---|---|---|
309
+ | `--build-cache` | `false` | Also remove the source-build cache under `%LOCALAPPDATA%\wyvrnpm\bc\` (Unix: `~/.cache/wyvrnpm/bc/`). |
310
+
227
311
  ---
228
312
 
229
313
  ### `wyvrnpm link`
@@ -434,6 +518,101 @@ wyvrnpm configure profile delete msvc_release
434
518
 
435
519
  ---
436
520
 
521
+ ## JSON output (`--format json`)
522
+
523
+ `install`, `publish`, and `show` accept `--format json` to emit a single machine-readable JSON object on stdout. Log lines (`[wyvrn]` prefix) move to **stderr** in this mode, so stdout is reserved exclusively for the JSON payload — no scraping required.
524
+
525
+ ```bash
526
+ wyvrnpm install --format json > install-result.json
527
+ wyvrnpm publish --format json 2>/dev/null # swallow logs, keep JSON
528
+ wyvrnpm show zlib@1.3.0.0 --format json | jq '.versions["1.3.0.0"].profiles[].origin'
529
+ ```
530
+
531
+ ### Shape — `install`
532
+
533
+ ```json
534
+ {
535
+ "command": "install",
536
+ "wyvrnpmVersion": "2.2.1",
537
+ "profile": { "os": "Windows", "arch": "x86_64", "compiler": "msvc", "compiler.version": "193", "compiler.cppstd": "23", "compiler.runtime": "dynamic" },
538
+ "profileName": "default",
539
+ "profileHash": "bf341c08af0aee2d",
540
+ "packages": [
541
+ {
542
+ "name": "zlib",
543
+ "version": "1.3.0.0",
544
+ "versionRange": "^1.3.0.0",
545
+ "profileHash": "a1b2c3d4e5f60718",
546
+ "options": { "shared": false, "minizip": true },
547
+ "contentSha256": "…",
548
+ "gitSha": "…",
549
+ "resolvedFrom": "v2"
550
+ }
551
+ ],
552
+ "lockFile": "/abs/path/to/wyvrn.lock",
553
+ "uploadSummary": { "uploaded": 0, "skipped": 0, "failed": 0 }
554
+ }
555
+ ```
556
+
557
+ - `versionRange` is `null` when the user pinned an exact version.
558
+ - `options` is `null` for packages that declare no options.
559
+ - `uploadSummary` is `null` unless `--upload-built` was passed.
560
+ - `packages` is sorted by name.
561
+
562
+ ### Shape — `publish`
563
+
564
+ ```json
565
+ {
566
+ "command": "publish",
567
+ "wyvrnpmVersion": "2.2.1",
568
+ "name": "zlib",
569
+ "version": "1.3.0.0",
570
+ "profileHash": "a1b2c3d4e5f60718",
571
+ "source": "s3://team-pkgs",
572
+ "contentSha256": "…",
573
+ "options": { "shared": false, "minizip": true },
574
+ "gitSha": "…",
575
+ "gitRepo": "…",
576
+ "publishedAt": "2026-04-21T12:34:56.789Z"
577
+ }
578
+ ```
579
+
580
+ ### Shape — `show`
581
+
582
+ ```json
583
+ {
584
+ "command": "show",
585
+ "wyvrnpmVersion": "2.2.1",
586
+ "source": "s3://team-pkgs",
587
+ "package": "zlib",
588
+ "latest": "1.3.0.0",
589
+ "versions": {
590
+ "1.3.0.0": {
591
+ "source": { "gitRepo": "…", "gitSha": "…" },
592
+ "profiles": [
593
+ {
594
+ "profileHash": "a1b2c3d4e5f60718",
595
+ "contentSha256": "…",
596
+ "publishedAt": "…",
597
+ "buildSettings": { "os": "Windows", "...": "...", "options": { "minizip": true } },
598
+ "origin": "author-publish",
599
+ "uploadedBy": null,
600
+ "compatibility": { "compiler.cppstd": "lte" },
601
+ "declaredOptions": { "minizip": { "default": true, "allowed": [true, false] } }
602
+ }
603
+ ]
604
+ }
605
+ }
606
+ }
607
+ ```
608
+
609
+ - `origin` is `"author-publish"` | `"consumer-source-build"` | `null` (the last when the summary view can't tell without a deep fetch).
610
+ - Bare-name queries (`wyvrnpm show zlib --format json`) skip the per-profile deep fetch; `origin`, `uploadedBy`, `compatibility`, `declaredOptions` are `null` for each profile.
611
+
612
+ Exit codes and error behaviour are identical to text mode. Fatal errors leave stdout empty rather than emitting partial JSON.
613
+
614
+ ---
615
+
437
616
  ## Configuration File
438
617
 
439
618
  wyvrnpm stores persistent configuration in a `config.json` file:
@@ -649,7 +828,365 @@ Fields use PascalCase. `Dependencies` is an array — transitive dependencies ar
649
828
 
650
829
  **`kind` values:** `ConsoleApp`, `StaticLib`, `DynamicLib`, `HeaderOnlyLib`, `WebApp`, `Utility`, `Service`, `TestProject`
651
830
 
652
- **`dependencies`** can be either a plain version string (`"major.minor.patch.build"`) or an object with `version` and an optional `settings` map. The `settings` map overrides individual build profile fields for that specific dependency — useful when a dependency must be consumed with a different runtime linkage or C++ standard than your default profile.
831
+ **`dependencies`** can be either a plain version string (`"major.minor.patch.build"`) or an object with `version`, an optional `settings` map, and an optional `options` map. The `settings` map overrides individual build profile fields for that specific dependency — useful when a dependency must be consumed with a different runtime linkage or C++ standard than your default profile. The `options` map is covered in the next section.
832
+
833
+ ### Version ranges
834
+
835
+ The `version` field accepts ranges in addition to exact versions and the `"latest"` tag:
836
+
837
+ | Spec | Meaning |
838
+ |---|---|
839
+ | `"1.2.3.4"` | exact — must match byte-identically |
840
+ | `"latest"` | resolves to whatever `latest.json` points at |
841
+ | `"^1.2.3.4"` | major-pinned — `>= 1.2.3.4` and `< 2.0.0.0` |
842
+ | `"~1.2.3.4"` | minor-pinned — `>= 1.2.3.4` and `< 1.3.0.0` |
843
+ | `">=1.2.3.4 <2.0.0.0"` | explicit comparators — space-separated, supports `>=`, `>`, `<=`, `<`, `=` |
844
+
845
+ Ranges resolve at install time against each source's `versions.json`. wyvrnpm picks the **highest version** that satisfies the range. The resolved exact version is pinned into `wyvrn.lock` alongside the original range string, so re-installs are byte-reproducible — ranges are only re-evaluated when the lock file's entry for that dependency is removed.
846
+
847
+ **Error handling:**
848
+
849
+ - **No version satisfies the range** → strict failure with every published version listed so you can see what's available.
850
+ - **Conflicting ranges across the graph** (direct consumer wants `^1.x` but a transitive wants `^2.x`) → precise error naming both contributors and their ranges.
851
+ - **Malformed range string** → fail fast with a cheat-sheet of supported syntaxes.
852
+
853
+ Example:
854
+
855
+ ```json
856
+ {
857
+ "dependencies": {
858
+ "OpenSSL": "^3.0.0.0",
859
+ "zlib": "~1.3.0.0",
860
+ "boost": ">=1.80.0.0 <2.0.0.0"
861
+ }
862
+ }
863
+ ```
864
+
865
+ ### Package options (`options`)
866
+
867
+ A library author can declare **options** — compile-time feature toggles that change the produced binary's ABI. Examples: `shared` (shared vs static library), `minizip` (zlib's minizip support), `fips` (OpenSSL's FIPS mode). Each option produces a distinct `profileHash`, so every option combination gets its own artefact on the registry.
868
+
869
+ **Author side — declare in the recipe's `wyvrn.json`:**
870
+
871
+ ```json
872
+ {
873
+ "name": "zlib",
874
+ "version": "1.3.0.0",
875
+ "options": {
876
+ "shared": { "default": false, "allowed": [true, false] },
877
+ "minizip": { "default": true, "allowed": [true, false] },
878
+ "api": { "default": "default", "allowed": ["default", "legacy"] }
879
+ },
880
+ "build": {
881
+ "configure": [
882
+ "-DBUILD_SHARED_LIBS=${options.shared}",
883
+ "-DZLIB_BUILD_MINIZIP=${options.minizip}",
884
+ "-DZLIB_API_MODE=${options.api}"
885
+ ]
886
+ }
887
+ }
888
+ ```
889
+
890
+ Rules for the declaration:
891
+
892
+ - Option names must match `/^[a-z][a-z0-9_-]*$/`.
893
+ - Each option needs `default` + `allowed`. `default` must be a member of `allowed`.
894
+ - `allowed` values are `boolean` or `string`. No integers, no lists.
895
+ - `${options.<name>}` tokens in `build.configure` / `build.buildArgs` are expanded at source-build time. Booleans expand to `ON`/`OFF`; strings expand verbatim. A typo (`${options.fips}` with no `fips` declared) is a hard error — it will not silently reach CMake.
896
+
897
+ **Consumer side — override per dependency:**
898
+
899
+ ```json
900
+ {
901
+ "dependencies": {
902
+ "zlib": {
903
+ "version": "1.3.0.0",
904
+ "options": { "minizip": false }
905
+ }
906
+ }
907
+ }
908
+ ```
909
+
910
+ **Consumer side — override via CLI (precedence: CLI > wyvrn.json > recipe defaults):**
911
+
912
+ ```bash
913
+ wyvrnpm install -o zlib:minizip=false -o zlib:shared=true
914
+ wyvrnpm publish -o zlib:minizip=false # publishing a specific variant
915
+ ```
916
+
917
+ Option overrides are validated against the recipe's `allowed` list at resolve time; unknown option names surface a "did you mean?" suggestion.
918
+
919
+ **What happens on the registry.** Options fold into `profileHash`, so `zlib@1.3.0.0` with `minizip=true` and `zlib@1.3.0.0` with `minizip=false` are two distinct artefacts at two different URLs — same version, different hashes. `wyvrnpm show zlib@1.3.0.0` lists all published variants and prints each one's resolved options.
920
+
921
+ **Backward compatibility.** Packages that do not declare an `options` block hash byte-identically to how they did before options existed. No registry migration required.
922
+
923
+ ### Source-build recipe (`build`)
924
+
925
+ A library package may include an optional `build` section that tells `wyvrnpm install --build=missing` how to rebuild it from source. Only `system: "cmake"` is supported today.
926
+
927
+ ```json
928
+ {
929
+ "name": "zlib",
930
+ "version": "1.3.0.0",
931
+ "kind": "StaticLib",
932
+ "build": {
933
+ "system": "cmake",
934
+ "generator": ["Ninja", "Visual Studio 17 2022"],
935
+ "configs": ["Release", "Debug"],
936
+ "configure": ["-DZLIB_BUILD_EXAMPLES=OFF"],
937
+ "buildArgs": ["--parallel"],
938
+ "installDir": "install",
939
+ "sourceSubdir": "."
940
+ }
941
+ }
942
+ ```
943
+
944
+ | Field | Default | Description |
945
+ |---|---|---|
946
+ | `system` | `cmake` | Build system. Only `cmake` is supported. |
947
+ | `generator` | *(CMake default)* | Single generator (`"Ninja"`) or an array tried in order — first available wins. |
948
+ | `configs` | `["Debug","Release","RelWithDebInfo","MinSizeRel"]` | CMake configurations to build and install. |
949
+ | `configure` | `[]` | Extra args appended to `cmake` at configure time. |
950
+ | `buildArgs` | `[]` | Extra args appended to `cmake --build`. |
951
+ | `installDir` | `install` | Install prefix relative to the binary dir. |
952
+ | `sourceSubdir` | `.` | Where `CMakeLists.txt` lives relative to the clone root. |
953
+ | `requiredTools` | `[]` | Tools that must be on `PATH` before the build starts (fail-fast check). |
954
+
955
+ On Windows with MSVC and a non-Visual-Studio generator (Ninja, Make), `vcvarsall.bat` is auto-activated for the spawned CMake process — no need to launch from a Developer Prompt.
956
+
957
+ ---
958
+
959
+ ## Compatibility Rules
960
+
961
+ Publishers declare an optional `compatibility` block in a package's `wyvrn.json`. It tells wyvrnpm which profile fields must match the consumer's profile exactly, and which may legitimately differ without breaking ABI. When no exact `profileHash` match exists on the registry, `install` evaluates this block against every published build for the requested `(name, version)` and picks the best compatible candidate.
962
+
963
+ ### Block format
964
+
965
+ Each field maps to a mode — either as a string shorthand or an object:
966
+
967
+ ```json
968
+ {
969
+ "name": "zlib",
970
+ "version": "1.3.0.0",
971
+ "compatibility": {
972
+ "compiler.cppstd": "lte",
973
+ "compiler.runtime": "exact",
974
+ "compiler.version": "exact-or-newer"
975
+ }
976
+ }
977
+ ```
978
+
979
+ Object form is accepted too (`{ "mode": "lte" }`) and leaves room for future per-field options.
980
+
981
+ ### Supported modes
982
+
983
+ | Mode | Meaning |
984
+ |---|---|
985
+ | `exact` | Package and consumer values must be equal. |
986
+ | `any` | Field is ignored for compatibility purposes. |
987
+ | `lte` | Package value ≤ consumer value — e.g. a `cppstd=20` package is usable by a `cppstd=23` consumer. |
988
+ | `gte` | Package value ≥ consumer value (symmetry; rarely useful). |
989
+ | `exact-or-newer` | Same as `lte` at MVP — kept as a distinct name because it reads naturally for compiler-version fields. |
990
+
991
+ **Defaults:**
992
+
993
+ - Any field not listed is treated as `exact`.
994
+ - `os` and `arch` are **always `exact`** regardless of what the block says. Cross-OS / cross-arch binaries are never ABI-compatible.
995
+
996
+ ### Where to author it
997
+
998
+ The block lives in the **source** `wyvrn.json` (the one you commit). It is read at publish time and written into the uploaded manifest. If the source `wyvrn.json` has no `compatibility` block, `wyvrnpm publish` injects a conservative default (all fields `exact`) so existing packages behave strict-by-default.
999
+
1000
+ Consumers never author compatibility — it is a property of the published package.
1001
+
1002
+ ### Tiebreaker
1003
+
1004
+ When multiple published builds satisfy the consumer's profile, the winner is picked deterministically:
1005
+
1006
+ 1. Prefer the candidate whose `compiler` matches the consumer's.
1007
+ 2. Prefer the highest numeric `compiler.version`.
1008
+ 3. Prefer the candidate whose `compiler.runtime` matches the consumer's.
1009
+ 4. Fall back to the lexicographically earliest `profileHash`.
1010
+
1011
+ The chosen candidate is logged along with a per-field summary (e.g. `compiler.cppstd 20→23 (lte), compiler.runtime=dynamic`).
1012
+
1013
+ ### Legacy loose fallback
1014
+
1015
+ The pre-2.1 "same OS + arch, any compiler" fallback is removed from the default resolution path. For one release cycle it remains reachable via an env var:
1016
+
1017
+ ```bash
1018
+ WYVRNPM_ALLOW_LOOSE_COMPAT=1 wyvrnpm install
1019
+ ```
1020
+
1021
+ This prints a deprecation warning every time it fires and will be removed in the next minor release. Prefer adding a real `compatibility` block to affected packages, or rebuilding with `--build=missing`.
1022
+
1023
+ ---
1024
+
1025
+ ## Source Builds
1026
+
1027
+ `wyvrnpm install --build=missing` clones a package's source at its published `gitSha` and builds it locally when no prebuilt binary matches (exact or compatible) the consumer's profile. This is the Conan-2.0 `--build=missing` parity feature.
1028
+
1029
+ ### When it fires
1030
+
1031
+ 1. You run `wyvrnpm install --build=missing`.
1032
+ 2. For each dependency, wyvrnpm tries `v2 exact → v2 compat` first.
1033
+ 3. If neither hits, it looks up `gitRepo` + `gitSha` from **any** existing build of the same `(name, version)` on the registry (they are equal across profiles for a given version).
1034
+ 4. It clones, resolves the package's own transitive deps, invokes CMake through the shared toolchain generator, zips the install dir, and treats the result as a v2 download.
1035
+
1036
+ If the package has no prior publish on the registry at all, there is no `gitSha` to clone — source-build fails fast with a clear error. Source-builds cascade transitively: if package A needs source-build and depends on package B which also needs source-build, both are built.
1037
+
1038
+ ### Authoring the `build` recipe
1039
+
1040
+ Libraries that want to be source-buildable declare a `build` section in their own `wyvrn.json`. See [Project Manifest §Source-build recipe](#source-build-recipe-build) for the field list. Only `system: "cmake"` is supported today.
1041
+
1042
+ ### Generator fallback
1043
+
1044
+ `build.generator` accepts either a string or an array:
1045
+
1046
+ | Form | Behaviour |
1047
+ |---|---|
1048
+ | `"Ninja"` | Strict — fails if Ninja is not available. |
1049
+ | `["Ninja", "Visual Studio 17 2022"]` | Tried in order — first available wins. Unknown entries are trusted (CMake decides at configure time). |
1050
+ | *(omitted)* | Uses CMake's platform default. |
1051
+
1052
+ ### MSVC environment auto-activation (Windows)
1053
+
1054
+ On Windows, when **all** of the following hold:
1055
+
1056
+ - `profile.compiler === "msvc"`
1057
+ - The chosen generator is **not** a Visual Studio generator (e.g. Ninja, Make)
1058
+ - `cl.exe` is not on `PATH`
1059
+
1060
+ …wyvrnpm locates Visual Studio via `vswhere.exe`, runs `vcvarsall.bat <arch>` in a sub-shell, captures the resulting environment, and uses it for the CMake spawn. You do not need to launch from a Developer Prompt.
1061
+
1062
+ Arch mapping follows the active profile:
1063
+
1064
+ | `profile.arch` | `vcvarsall` arg |
1065
+ |---|---|
1066
+ | `x86_64` | `x64` |
1067
+ | `x86` | `x86` |
1068
+ | `armv8` | `arm64` |
1069
+ | `armv7` | `arm` |
1070
+
1071
+ VS generators skip this path entirely — MSBuild resolves MSVC itself.
1072
+
1073
+ ### Cache
1074
+
1075
+ Source builds are cached machine-wide, keyed by `(name, version, profileHash)`:
1076
+
1077
+ | Platform | Path |
1078
+ |---|---|
1079
+ | Windows | `%LOCALAPPDATA%\wyvrnpm\bc\{name}-{version}-{profileHash}\` |
1080
+ | Linux / macOS | `~/.cache/wyvrnpm/bc/{name}-{version}-{profileHash}/` |
1081
+
1082
+ Re-running `install --build=missing` with the same inputs short-circuits to the cached artefact. The cache root is deliberately shallow (`bc/`, not `build-cache/`) to keep nested CMake paths under Windows' 260-char limit.
1083
+
1084
+ Clone behaviour is tri-tier:
1085
+
1086
+ 1. **Shallow-SHA fetch** (`git fetch origin <sha> --depth=1`) — fastest; works when the git server allows it.
1087
+ 2. **Full fetch** — falls back to a full `git clone --tags` when the server rejects shallow-SHA (older Bitbucket installs disable `uploadpack.allowReachableSHA1InWant`).
1088
+ 3. **Rebuild from scratch** — if the cached clone is broken, wipes and retries.
1089
+
1090
+ After every clone/fetch, wyvrnpm runs `git cat-file -e <sha>^{commit}` to verify the SHA is actually reachable. If it is not, the error points at the likely cause: the publisher forgot to push before running `wyvrnpm publish`.
1091
+
1092
+ ### Publish-time guard
1093
+
1094
+ To catch the unpushed-commit case before consumers hit it, `wyvrnpm publish` checks whether the current `HEAD` is reachable from any remote branch or tag and warns if it is not:
1095
+
1096
+ ```
1097
+ [wyvrn] warn: git SHA abc1234 is not on origin — source-build consumers will fail until pushed
1098
+ ```
1099
+
1100
+ The warning does **not** block publishing.
1101
+
1102
+ ### Header-only libraries
1103
+
1104
+ `kind: "HeaderOnlyLib"` packages still need a `build` section and a CMake `install()` rule that copies headers. There is no fast-path that zips the source tree directly — consumers get exactly what the `install()` step produces, same as any other kind.
1105
+
1106
+ ### Clearing the cache
1107
+
1108
+ ```bash
1109
+ # Remove the project-local cache (wyvrn_internal/ + wyvrn.lock)
1110
+ wyvrnpm clean
1111
+
1112
+ # Also wipe the machine-wide source-build cache
1113
+ wyvrnpm clean --build-cache
1114
+ ```
1115
+
1116
+ ### Credentials for private repos
1117
+
1118
+ wyvrnpm uses your ambient git configuration (SSH keys, credential helpers, Windows Credential Manager). It does not manage git credentials itself. If `git clone <gitRepo>` works from your shell, source-build works.
1119
+
1120
+ ### Upload back to the registry (`--upload-built`)
1121
+
1122
+ `--build=missing` warms *your* cache. `--build=missing --upload-built` warms the *team's* cache: after a successful source-build, the freshly-zipped artefact is pushed back to the registry under the consumer's `profileHash`, so the next teammate on the same profile gets a direct v2 download.
1123
+
1124
+ ```bash
1125
+ # Build missing artefacts locally AND publish them back under your profile.
1126
+ wyvrnpm install --build=missing --upload-built
1127
+
1128
+ # Override the upload destination (URL or configured publish-source name).
1129
+ wyvrnpm install --build=missing --upload-built --upload-source team-s3
1130
+
1131
+ # Upload auth is resolved in order: CLI flag → named-source config → first configured publish source.
1132
+ wyvrnpm install --build=missing --upload-built --aws-profile my-sso
1133
+ wyvrnpm install --build=missing --upload-built --token mytoken
1134
+ ```
1135
+
1136
+ Semantics:
1137
+
1138
+ - **Opt-in.** Nothing uploads unless `--upload-built` is passed. Requires `--build=missing`.
1139
+ - **Skip if exists.** If `(name, version, profileHash)` already lives on the upload source, the upload is a silent no-op. A consumer cannot overwrite an existing artefact; use `wyvrnpm publish --force` from the source repo for that.
1140
+ - **`latest.json` is untouched.** A consumer upload is a new `profileHash` of an existing version, not a new version release.
1141
+ - **Upload failures never break the install.** The consumer already has a working `wyvrn_internal/` on disk — a broken upload is logged as a warning and the install continues.
1142
+ - **Provenance.** Uploaded manifests carry a small `uploadedBy` block so maintainers can tell consumer builds apart from author publishes:
1143
+
1144
+ ```json
1145
+ {
1146
+ "uploadedBy": {
1147
+ "kind": "source-build",
1148
+ "profileHash": "a1b2c3d4e5f60718",
1149
+ "builtFromGit": "https://github.com/madler/zlib",
1150
+ "builtFromSha": "abcdef01...",
1151
+ "uploadedAt": "2026-04-21T12:34:56.789Z",
1152
+ "wyvrnpmVersion": "2.1.0"
1153
+ }
1154
+ }
1155
+ ```
1156
+
1157
+ Absence of `uploadedBy` means the artefact was published by the author via `wyvrnpm publish`.
1158
+
1159
+ At the end of an install with `--upload-built`, a one-line summary is printed:
1160
+
1161
+ ```
1162
+ [wyvrn] upload-built summary: 2 artefacts uploaded, 1 skipped (already existed), 0 failed
1163
+ ```
1164
+
1165
+ **Trust model.** Registry write auth is the gate — only consumers with credentials for the upload destination can upload at all. The same `v2Publish` path is used as `wyvrnpm publish`, so the resulting artefact is byte-identical in layout. SHA256 verification on the download side protects against in-transit tampering. Uploading deliberately opts into "your machine's build of this package is trusted by everyone else on this profile" — use CI with clean toolchains rather than developer workstations if you care about reproducibility.
1166
+
1167
+ #### Auditing consumer uploads (`wyvrnpm show`)
1168
+
1169
+ Maintainers can list every build of a package and tell author-published artefacts apart from consumer uploads:
1170
+
1171
+ ```bash
1172
+ wyvrnpm show zlib # all versions + their profile hashes
1173
+ wyvrnpm show zlib@1.3.0.0 # every profile for that version, with origin + builtFromSha
1174
+ wyvrnpm show zlib@1.3.0.0@a1b2c3d4... # one specific build, full metadata
1175
+ ```
1176
+
1177
+ With a version specified, `show` fetches each build's `wyvrn.json` and reports `author publish` or `consumer upload (source-build)` alongside the relevant `uploadedBy` fields. Sources default to the configured install sources; override with `--source <url>`.
1178
+
1179
+ #### Forgetting uploads (`clean --uploaded-built`)
1180
+
1181
+ Each successful upload writes a `.uploaded-to.json` sidecar alongside the cached artefact under `%LOCALAPPDATA%\wyvrnpm\bc\{name}-{version}-{profileHash}\`. It records destination, timestamp, and SHA — purely informational, nothing reads it during install.
1182
+
1183
+ To wipe just those local records (no effect on the registry, the artefact zips, or `wyvrn_internal/`):
1184
+
1185
+ ```bash
1186
+ wyvrnpm clean --uploaded-built
1187
+ ```
1188
+
1189
+ This is a privacy / local-audit cleanup knob — `clean --build-cache` already removes sidecars as a side effect of nuking the cache.
653
1190
 
654
1191
  ---
655
1192
 
@@ -687,6 +1224,103 @@ Locked versions always take priority over transitive requests. A warning is prin
687
1224
 
688
1225
  ---
689
1226
 
1227
+ ## CMake Integration
1228
+
1229
+ `wyvrnpm install` emits a ready-to-use CMake toolchain alongside the downloaded packages. Most projects only need to run:
1230
+
1231
+ ```bash
1232
+ wyvrnpm install
1233
+ wyvrnpm build
1234
+ ```
1235
+
1236
+ …which is equivalent to:
1237
+
1238
+ ```bash
1239
+ cmake --preset wyvrn-default
1240
+ cmake --build build/wyvrn-default --config Release
1241
+ ```
1242
+
1243
+ **Generated files (inside the project)**
1244
+
1245
+ | Path | Purpose |
1246
+ |---|---|
1247
+ | `wyvrn_internal/wyvrn_toolchain.cmake` | Toolchain file — sets `CMAKE_PREFIX_PATH`, compiler/runtime, and standards from the active profile. Include via the preset or `-DCMAKE_TOOLCHAIN_FILE=…`. |
1248
+ | `wyvrn_internal/wyvrn_deps.cmake` | Include **after** `project()` (e.g. `include(${CMAKE_BINARY_DIR}/../wyvrn_internal/wyvrn_deps.cmake)`). Wires `find_package()` calls and defines `wyvrnpm_finalize_targets(<tgt>…)` for runtime DLL copy next to your executable. |
1249
+ | `wyvrn_internal/wyvrn_profile.json` | Snapshot of the profile the toolchain was generated against. |
1250
+ | `CMakePresets.json` / `CMakeUserPresets.json` | `wyvrn-<profile>` configure preset. Regenerating preserves other profiles' presets. |
1251
+
1252
+ **Bundled CMake utilities** (shipped in the npm tarball under `cmake/`) are auto-included by `wyvrn_toolchain.cmake`:
1253
+
1254
+ | File | Provides |
1255
+ |---|---|
1256
+ | `cpp.cmake` | C++ standard defaults (guarded — toolchain wins). |
1257
+ | `variables.cmake` | Platform detection (`WYVRN_PLATFORM_WIN`, …) and compiler flags. |
1258
+ | `options.cmake` | Feature toggles (`WYVRN_ENABLE_LOG`, …). |
1259
+ | `functions.cmake` | `SETUP_LIBRARY` / `SETUP_EXE` plus HAL source-collection helpers. |
1260
+ | `macros.cmake` | `WYVRN_INSTALL_MODULE`, `WYVRN_CONFIGURE_VERSION_HEADER`. |
1261
+
1262
+ **Minimal `CMakeLists.txt`:**
1263
+
1264
+ ```cmake
1265
+ cmake_minimum_required(VERSION 3.21)
1266
+ project(my-app CXX)
1267
+
1268
+ include(${CMAKE_SOURCE_DIR}/wyvrn_internal/wyvrn_deps.cmake)
1269
+
1270
+ add_executable(my-app src/main.cpp)
1271
+ target_link_libraries(my-app PRIVATE OpenSSL::SSL zlib::zlib)
1272
+
1273
+ wyvrnpm_finalize_targets(my-app) # copies runtime DLLs next to the .exe
1274
+ ```
1275
+
1276
+ ---
1277
+
1278
+ ## Installing Build Artifacts (`wyvrnpm build --install`)
1279
+
1280
+ `wyvrnpm build --install` runs `cmake --install <binaryDir>` after a successful build, invoking every `install()` rule in your `CMakeLists.txt`. This is the step that produces a redistributable layout (headers + libs + runtime files in a tidy tree) rather than the raw CMake build directory.
1281
+
1282
+ ### Typical use
1283
+
1284
+ ```bash
1285
+ # Configure, build, and install to the default prefix (baked in at configure time)
1286
+ wyvrnpm build --install
1287
+
1288
+ # Override the install prefix without reconfiguring
1289
+ wyvrnpm build --install --install-prefix C:\out\stage
1290
+
1291
+ # Install a specific config
1292
+ wyvrnpm build --install --config Debug
1293
+ ```
1294
+
1295
+ ### What it does, in order
1296
+
1297
+ 1. **Staleness check** — if `wyvrn_internal/wyvrn_toolchain.cmake` is missing or `wyvrn.lock` is newer than it, auto-runs `wyvrnpm install` first. Disable with `--no-auto-install`.
1298
+ 2. **Optional clean** — `--clean` wipes the build directory before configuring.
1299
+ 3. **Configure** — `cmake --preset wyvrn-<profile>` (or the value of `--preset`).
1300
+ 4. **Build** — `cmake --build <binaryDir> --config <config>`.
1301
+ 5. **Install** — `cmake --install <binaryDir> --config <config> [--prefix <install-prefix>]`.
1302
+
1303
+ ### `--auto-install` vs `--install`
1304
+
1305
+ These two flags are easy to confuse — they control unrelated things:
1306
+
1307
+ | Flag | What it does | When it runs |
1308
+ |---|---|---|
1309
+ | `--auto-install` *(default: on)* | Runs `wyvrnpm install` if the toolchain is stale. Disable with `--no-auto-install`. | **Before** configure. |
1310
+ | `--install` *(default: off)* | Runs `cmake --install` after a successful build. | **After** build. |
1311
+
1312
+ ### `--install-prefix`
1313
+
1314
+ CMake bakes `CMAKE_INSTALL_PREFIX` into the build cache at configure time. Passing `--install-prefix` at install time uses `cmake --install --prefix <path>` to override the baked value **without** re-running configure — cheaper than a full reconfigure when you only want to stage to a different location.
1315
+
1316
+ If you omit `--install-prefix`, the baked default is used. The baked default comes from the generated preset, typically under `build/wyvrn-<profile>/install`.
1317
+
1318
+ ### Permissions
1319
+
1320
+ Installing to a system path (`/usr/local`, `C:\Program Files`) requires the usual elevated permissions — wyvrnpm does not auto-elevate. For staging or packaging, point `--install-prefix` at a writable user directory instead.
1321
+
1322
+ ---
1323
+
690
1324
  ## Version Conflict Resolution
691
1325
 
692
1326
  When the dependency graph requires the same package at multiple versions, the highest version wins. If a `wyvrn.lock` exists, locked versions always take priority over any conflicting transitive request. A warning is printed for every conflict detected.