wyvrnpm 2.1.0 → 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.
- package/README.md +257 -1
- package/bin/wyvrn.js +131 -14
- package/claude/skills/wyvrnpm.skill +0 -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/build.js +49 -8
- package/src/commands/clean.js +39 -15
- package/src/commands/install-skill.js +107 -0
- package/src/commands/install.js +201 -5
- package/src/commands/publish.js +150 -35
- package/src/commands/show.js +237 -0
- package/src/compat.js +32 -3
- 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/presets.js +88 -16
- package/src/upload-built.js +256 -0
- package/src/version-range.js +301 -0
package/README.md
CHANGED
|
@@ -518,6 +518,101 @@ wyvrnpm configure profile delete msvc_release
|
|
|
518
518
|
|
|
519
519
|
---
|
|
520
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
|
+
|
|
521
616
|
## Configuration File
|
|
522
617
|
|
|
523
618
|
wyvrnpm stores persistent configuration in a `config.json` file:
|
|
@@ -733,7 +828,97 @@ Fields use PascalCase. `Dependencies` is an array — transitive dependencies ar
|
|
|
733
828
|
|
|
734
829
|
**`kind` values:** `ConsoleApp`, `StaticLib`, `DynamicLib`, `HeaderOnlyLib`, `WebApp`, `Utility`, `Service`, `TestProject`
|
|
735
830
|
|
|
736
|
-
**`dependencies`** can be either a plain version string (`"major.minor.patch.build"`) or an object with `version` and an optional `
|
|
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.
|
|
737
922
|
|
|
738
923
|
### Source-build recipe (`build`)
|
|
739
924
|
|
|
@@ -932,6 +1117,77 @@ wyvrnpm clean --build-cache
|
|
|
932
1117
|
|
|
933
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.
|
|
934
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.
|
|
1190
|
+
|
|
935
1191
|
---
|
|
936
1192
|
|
|
937
1193
|
## Lock File (wyvrn.lock)
|
package/bin/wyvrn.js
CHANGED
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
const yargs = require('yargs');
|
|
6
|
-
const init
|
|
7
|
-
const install
|
|
8
|
-
const clean
|
|
9
|
-
const publish
|
|
10
|
-
const configure
|
|
11
|
-
const profile
|
|
12
|
-
const build
|
|
6
|
+
const init = require('../src/commands/init');
|
|
7
|
+
const install = require('../src/commands/install');
|
|
8
|
+
const clean = require('../src/commands/clean');
|
|
9
|
+
const publish = require('../src/commands/publish');
|
|
10
|
+
const configure = require('../src/commands/configure');
|
|
11
|
+
const profile = require('../src/commands/profile');
|
|
12
|
+
const build = require('../src/commands/build');
|
|
13
|
+
const show = require('../src/commands/show');
|
|
14
|
+
const installSkill = require('../src/commands/install-skill');
|
|
13
15
|
const { link, unlink } = require('../src/commands/link');
|
|
14
16
|
|
|
15
17
|
yargs
|
|
@@ -58,6 +60,36 @@ yargs
|
|
|
58
60
|
'Resolution mode when no exact profile match exists. ' +
|
|
59
61
|
'never=fail; missing=build from source (pending phase 3); always=rebuild (pending phase 3)',
|
|
60
62
|
})
|
|
63
|
+
.option('option', {
|
|
64
|
+
alias: 'o',
|
|
65
|
+
type: 'string',
|
|
66
|
+
array: true,
|
|
67
|
+
description:
|
|
68
|
+
'Override a package option. Repeatable. Format: <pkg>:<name>=<value>. ' +
|
|
69
|
+
'Example: -o zlib:minizip=false -o OpenSSL:fips=true',
|
|
70
|
+
})
|
|
71
|
+
.option('upload-built', {
|
|
72
|
+
type: 'boolean',
|
|
73
|
+
default: false,
|
|
74
|
+
description:
|
|
75
|
+
'After successful --build=missing, upload each source-built artefact ' +
|
|
76
|
+
'back to the registry under the active profileHash so the next consumer ' +
|
|
77
|
+
'gets a direct download. Requires write credentials for the upload source.',
|
|
78
|
+
})
|
|
79
|
+
.option('upload-source', {
|
|
80
|
+
type: 'string',
|
|
81
|
+
description:
|
|
82
|
+
'Destination for --upload-built (URL or configured publish-source name). ' +
|
|
83
|
+
'Defaults to the first configured publish source.',
|
|
84
|
+
})
|
|
85
|
+
.option('aws-profile', {
|
|
86
|
+
type: 'string',
|
|
87
|
+
description: 'AWS SSO profile for --upload-built when uploading to S3',
|
|
88
|
+
})
|
|
89
|
+
.option('token', {
|
|
90
|
+
type: 'string',
|
|
91
|
+
description: 'Bearer token for --upload-built when uploading to HTTP',
|
|
92
|
+
})
|
|
61
93
|
.option('platform', {
|
|
62
94
|
type: 'string',
|
|
63
95
|
description: 'v1 legacy platform path (used as fallback if v2 not found)',
|
|
@@ -68,9 +100,20 @@ yargs
|
|
|
68
100
|
type: 'number',
|
|
69
101
|
description: 'HTTP timeout in seconds',
|
|
70
102
|
default: 300,
|
|
103
|
+
})
|
|
104
|
+
.option('format', {
|
|
105
|
+
type: 'string',
|
|
106
|
+
choices: ['text', 'json'],
|
|
107
|
+
default: 'text',
|
|
108
|
+
description: 'Output format. "json" emits a single JSON object on stdout; log lines go to stderr.',
|
|
71
109
|
});
|
|
72
110
|
},
|
|
73
|
-
(argv) => install(
|
|
111
|
+
(argv) => install({
|
|
112
|
+
...argv,
|
|
113
|
+
uploadBuilt: argv['upload-built'],
|
|
114
|
+
uploadSource: argv['upload-source'],
|
|
115
|
+
awsProfile: argv['aws-profile'],
|
|
116
|
+
}),
|
|
74
117
|
)
|
|
75
118
|
|
|
76
119
|
// ── build ─────────────────────────────────────────────────────────────────
|
|
@@ -155,13 +198,60 @@ yargs
|
|
|
155
198
|
'clean',
|
|
156
199
|
'Remove downloaded packages and lock file. --build-cache also wipes the machine-wide source-build cache.',
|
|
157
200
|
(y) => {
|
|
158
|
-
y
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
201
|
+
y
|
|
202
|
+
.option('build-cache', {
|
|
203
|
+
type: 'boolean',
|
|
204
|
+
default: false,
|
|
205
|
+
description: 'Also remove the source-build cache under %LOCALAPPDATA%\\wyvrnpm\\bc\\',
|
|
206
|
+
})
|
|
207
|
+
.option('uploaded-built', {
|
|
208
|
+
type: 'boolean',
|
|
209
|
+
default: false,
|
|
210
|
+
description:
|
|
211
|
+
'Wipe only the local record of artefacts uploaded via --upload-built ' +
|
|
212
|
+
'(the .uploaded-to.json sidecars in the build cache). Does not touch ' +
|
|
213
|
+
'the registry, the artefact zips, or wyvrn_internal/.',
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
(argv) => clean({
|
|
217
|
+
...argv,
|
|
218
|
+
buildCache: argv['build-cache'],
|
|
219
|
+
uploadedBuilt: argv['uploaded-built'],
|
|
220
|
+
}),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
// ── show ──────────────────────────────────────────────────────────────────
|
|
224
|
+
.command(
|
|
225
|
+
'show <pkg>',
|
|
226
|
+
'Display registry metadata for a package. Surfaces `uploadedBy` so consumer uploads can be told apart from author publishes.',
|
|
227
|
+
(y) => {
|
|
228
|
+
y
|
|
229
|
+
.positional('pkg', {
|
|
230
|
+
type: 'string',
|
|
231
|
+
description: 'Spec: <name>[@<version>[@<profileHash>]]',
|
|
232
|
+
})
|
|
233
|
+
.option('source', {
|
|
234
|
+
alias: 's',
|
|
235
|
+
type: 'string',
|
|
236
|
+
array: true,
|
|
237
|
+
description: 'Source URL(s) to query; defaults to configured install sources',
|
|
238
|
+
})
|
|
239
|
+
.option('aws-profile', {
|
|
240
|
+
type: 'string',
|
|
241
|
+
description: 'AWS SSO profile for S3 sources',
|
|
242
|
+
})
|
|
243
|
+
.option('token', {
|
|
244
|
+
type: 'string',
|
|
245
|
+
description: 'Bearer token for HTTP sources',
|
|
246
|
+
})
|
|
247
|
+
.option('format', {
|
|
248
|
+
type: 'string',
|
|
249
|
+
choices: ['text', 'json'],
|
|
250
|
+
default: 'text',
|
|
251
|
+
description: 'Output format. "json" emits a single JSON object on stdout; log lines go to stderr.',
|
|
252
|
+
});
|
|
163
253
|
},
|
|
164
|
-
(argv) =>
|
|
254
|
+
(argv) => show({ ...argv, awsProfile: argv['aws-profile'] }),
|
|
165
255
|
)
|
|
166
256
|
|
|
167
257
|
// ── publish ───────────────────────────────────────────────────────────────
|
|
@@ -200,6 +290,20 @@ yargs
|
|
|
200
290
|
description: 'Overwrite an existing published version',
|
|
201
291
|
default: false,
|
|
202
292
|
})
|
|
293
|
+
.option('option', {
|
|
294
|
+
alias: 'o',
|
|
295
|
+
type: 'string',
|
|
296
|
+
array: true,
|
|
297
|
+
description:
|
|
298
|
+
'Override an option value at publish time. Repeatable. ' +
|
|
299
|
+
'Format: <pkg>:<name>=<value> (pkg must match the package being published).',
|
|
300
|
+
})
|
|
301
|
+
.option('format', {
|
|
302
|
+
type: 'string',
|
|
303
|
+
choices: ['text', 'json'],
|
|
304
|
+
default: 'text',
|
|
305
|
+
description: 'Output format. "json" emits a single JSON object on stdout; log lines go to stderr.',
|
|
306
|
+
})
|
|
203
307
|
;
|
|
204
308
|
},
|
|
205
309
|
(argv) => publish({ ...argv, awsProfile: argv['aws-profile'] }),
|
|
@@ -365,6 +469,19 @@ yargs
|
|
|
365
469
|
(argv) => unlink(argv),
|
|
366
470
|
)
|
|
367
471
|
|
|
472
|
+
// ── install-skill ─────────────────────────────────────────────────────────
|
|
473
|
+
.command(
|
|
474
|
+
'install-skill',
|
|
475
|
+
'Install the bundled wyvrnpm Agent Skill into Claude Code (~/.claude/skills/wyvrnpm/)',
|
|
476
|
+
(y) => {
|
|
477
|
+
y
|
|
478
|
+
.option('claude', { type: 'boolean', description: 'Install for Claude Code', default: false })
|
|
479
|
+
.option('force', { type: 'boolean', description: 'Overwrite an existing installation', default: false })
|
|
480
|
+
.option('dry-run', { type: 'boolean', description: 'Report actions without writing', default: false });
|
|
481
|
+
},
|
|
482
|
+
(argv) => installSkill(argv),
|
|
483
|
+
)
|
|
484
|
+
|
|
368
485
|
.demandCommand(1)
|
|
369
486
|
.strict()
|
|
370
487
|
.help()
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wyvrnpm",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "A simple, static-hosting-compatible C++ package manager",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"c++",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"bin/",
|
|
21
21
|
"src/",
|
|
22
22
|
"cmake/",
|
|
23
|
+
"claude/skills/wyvrnpm.skill",
|
|
23
24
|
"README.md"
|
|
24
25
|
],
|
|
25
26
|
"scripts": {
|
package/src/build/cache.js
CHANGED
|
@@ -51,6 +51,7 @@ const LAYOUT = Object.freeze({
|
|
|
51
51
|
buildDir: 'build',
|
|
52
52
|
installSubdir: 'install', // relative to buildDir — can be overridden by recipe.installDir
|
|
53
53
|
artefactZip: 'wyvrn.zip',
|
|
54
|
+
uploadSidecar: '.uploaded-to.json',
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
/**
|
|
@@ -91,11 +92,57 @@ function hasValidArtefact(args) {
|
|
|
91
92
|
return fs.existsSync(artefact);
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Find every `.uploaded-to.json` sidecar under the build cache. Each file
|
|
97
|
+
* records the destinations that a cached artefact has been pushed to via
|
|
98
|
+
* `wyvrnpm install --build=missing --upload-built`.
|
|
99
|
+
*
|
|
100
|
+
* Used by `wyvrnpm clean --uploaded-built` and for audit tooling.
|
|
101
|
+
*
|
|
102
|
+
* @returns {Array<{ cacheDir: string, sidecarPath: string, uploads: Array<object> }>}
|
|
103
|
+
*/
|
|
104
|
+
function listUploadSidecars() {
|
|
105
|
+
const root = getBuildCacheRoot();
|
|
106
|
+
if (!fs.existsSync(root)) return [];
|
|
107
|
+
const out = [];
|
|
108
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
109
|
+
if (!entry.isDirectory()) continue;
|
|
110
|
+
const cacheDir = path.join(root, entry.name);
|
|
111
|
+
const sidecarPath = path.join(cacheDir, LAYOUT.uploadSidecar);
|
|
112
|
+
if (!fs.existsSync(sidecarPath)) continue;
|
|
113
|
+
let uploads = [];
|
|
114
|
+
try {
|
|
115
|
+
const raw = JSON.parse(fs.readFileSync(sidecarPath, 'utf8'));
|
|
116
|
+
if (Array.isArray(raw?.uploads)) uploads = raw.uploads;
|
|
117
|
+
} catch { /* corrupt — surface as empty uploads */ }
|
|
118
|
+
out.push({ cacheDir, sidecarPath, uploads });
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Remove every `.uploaded-to.json` sidecar under the build cache. Used by
|
|
125
|
+
* `wyvrnpm clean --uploaded-built`. Leaves all other cache content intact
|
|
126
|
+
* (the artefact zips, source clones, and build dirs stay put).
|
|
127
|
+
*
|
|
128
|
+
* @returns {number} count of sidecar files removed
|
|
129
|
+
*/
|
|
130
|
+
function clearUploadSidecars() {
|
|
131
|
+
const entries = listUploadSidecars();
|
|
132
|
+
let removed = 0;
|
|
133
|
+
for (const { sidecarPath } of entries) {
|
|
134
|
+
try { fs.unlinkSync(sidecarPath); removed++; } catch { /* ignore */ }
|
|
135
|
+
}
|
|
136
|
+
return removed;
|
|
137
|
+
}
|
|
138
|
+
|
|
94
139
|
module.exports = {
|
|
95
140
|
getBuildCacheRoot,
|
|
96
141
|
getPackageBuildDir,
|
|
97
142
|
getBuildPaths,
|
|
98
143
|
clearBuildCache,
|
|
99
144
|
hasValidArtefact,
|
|
145
|
+
listUploadSidecars,
|
|
146
|
+
clearUploadSidecars,
|
|
100
147
|
LAYOUT,
|
|
101
148
|
};
|
package/src/build/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const { normalizeDependencies } = require('../manifest');
|
|
|
13
13
|
const { resolveDependencies } = require('../resolve');
|
|
14
14
|
const { hashProfile, mergeProfile, sha256Of } = require('../profile');
|
|
15
15
|
const { generateToolchain } = require('../toolchain');
|
|
16
|
+
const { normalizeOptionsDeclaration, resolveEffectiveOptions } = require('../options');
|
|
16
17
|
const log = require('../logger');
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -62,6 +63,8 @@ function addFolder(zip, dir, base) {
|
|
|
62
63
|
* @param {import('../profile').WyvrnProfile} args.profile
|
|
63
64
|
* @param {string} args.profileName
|
|
64
65
|
* @param {string} args.profileHash
|
|
66
|
+
* @param {Record<string, any>|null} [args.options] Effective options for this build.
|
|
67
|
+
* Used to expand `${options.*}` in the recipe.
|
|
65
68
|
* @param {string} args.gitRepo
|
|
66
69
|
* @param {string} args.gitSha
|
|
67
70
|
* @param {string} args.destZipPath Where the caller expects the artefact zip.
|
|
@@ -73,13 +76,15 @@ function addFolder(zip, dir, base) {
|
|
|
73
76
|
* all four CMake configurations).
|
|
74
77
|
* @param {object} [args.authOptions]
|
|
75
78
|
* @returns {Promise<{
|
|
76
|
-
* found:
|
|
77
|
-
* contentSha256:
|
|
78
|
-
* profileHash:
|
|
79
|
-
* gitSha:
|
|
80
|
-
* gitRepo:
|
|
81
|
-
* source:
|
|
82
|
-
* resolvedFrom:
|
|
79
|
+
* found: true,
|
|
80
|
+
* contentSha256: string,
|
|
81
|
+
* profileHash: string,
|
|
82
|
+
* gitSha: string,
|
|
83
|
+
* gitRepo: string,
|
|
84
|
+
* source: string, // build-cache path (for logging)
|
|
85
|
+
* resolvedFrom: 'v2-source-build',
|
|
86
|
+
* artefactPath: string, // absolute path to the built zip in the cache
|
|
87
|
+
* clonedManifestPath: string, // absolute path to the cloned wyvrn.json (for upload-built)
|
|
83
88
|
* }>}
|
|
84
89
|
* @throws {Error} when the cloned manifest lacks a `build` section, when
|
|
85
90
|
* CMake is missing, or when the compile itself fails.
|
|
@@ -88,6 +93,7 @@ async function buildFromSource(args) {
|
|
|
88
93
|
const {
|
|
89
94
|
name, version,
|
|
90
95
|
profile, profileName, profileHash,
|
|
96
|
+
options = null,
|
|
91
97
|
gitRepo, gitSha,
|
|
92
98
|
destZipPath,
|
|
93
99
|
packageSources,
|
|
@@ -120,6 +126,8 @@ async function buildFromSource(args) {
|
|
|
120
126
|
gitRepo,
|
|
121
127
|
source: paths.root,
|
|
122
128
|
resolvedFrom: 'v2-source-build',
|
|
129
|
+
artefactPath: paths.artefact,
|
|
130
|
+
clonedManifestPath: path.join(paths.src, 'wyvrn.json'),
|
|
123
131
|
};
|
|
124
132
|
}
|
|
125
133
|
|
|
@@ -145,7 +153,10 @@ async function buildFromSource(args) {
|
|
|
145
153
|
`Source-build is opt-in — the publisher must add a build recipe.`,
|
|
146
154
|
);
|
|
147
155
|
}
|
|
148
|
-
|
|
156
|
+
// Pass effective options into the recipe so `${options.<name>}` tokens in
|
|
157
|
+
// `build.configure` / `build.buildArgs` expand to the concrete values that
|
|
158
|
+
// produced this build's profileHash.
|
|
159
|
+
const recipe = normalizeRecipe(clonedManifest.build, options);
|
|
149
160
|
|
|
150
161
|
// ── 4. Recursively resolve + download this package's OWN deps ──────────
|
|
151
162
|
// Lazy-require: downloadDependencies is where we were called from, so
|
|
@@ -161,20 +172,38 @@ async function buildFromSource(args) {
|
|
|
161
172
|
);
|
|
162
173
|
|
|
163
174
|
log.info(` resolving ${Object.keys(depsForResolution).length} transitive dep(s)`);
|
|
175
|
+
const transitiveManifests = new Map();
|
|
164
176
|
const resolvedDeps = await resolveDependencies(
|
|
165
177
|
depsForResolution, packageSources, platform, fetch, new Map(),
|
|
178
|
+
transitiveManifests,
|
|
166
179
|
);
|
|
167
180
|
|
|
168
|
-
// Build an enriched deps map
|
|
181
|
+
// Build an enriched deps map. Same logic as install.js: per-dep settings
|
|
182
|
+
// + recipe-declared options resolved against what the *cloned* source
|
|
183
|
+
// manifest asks for. No CLI overrides — transitive options come solely
|
|
184
|
+
// from the source package's `dependencies.<n>.options`.
|
|
169
185
|
const enrichedDeps = new Map();
|
|
170
186
|
for (const [depName, depVersion] of resolvedDeps) {
|
|
171
|
-
const depOverrides
|
|
187
|
+
const depOverrides = rawPackageDeps[depName]?.settings ?? {};
|
|
172
188
|
const effectiveProfile = mergeProfile(profile, depOverrides);
|
|
173
|
-
|
|
189
|
+
|
|
190
|
+
const transitiveManifest = transitiveManifests.get(depName);
|
|
191
|
+
const declaration = transitiveManifest?.options
|
|
192
|
+
? normalizeOptionsDeclaration(transitiveManifest.options, `${depName}@${depVersion}`)
|
|
193
|
+
: null;
|
|
194
|
+
const effectiveOptions = resolveEffectiveOptions(
|
|
195
|
+
declaration,
|
|
196
|
+
rawPackageDeps[depName]?.options ?? {},
|
|
197
|
+
{},
|
|
198
|
+
`${depName}@${depVersion}`,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const depProfileHash = hashProfile(effectiveProfile, effectiveOptions);
|
|
174
202
|
enrichedDeps.set(depName, {
|
|
175
203
|
version: depVersion,
|
|
176
204
|
profileHash: depProfileHash,
|
|
177
205
|
profile: effectiveProfile,
|
|
206
|
+
options: effectiveOptions,
|
|
178
207
|
});
|
|
179
208
|
}
|
|
180
209
|
|
|
@@ -238,6 +267,8 @@ async function buildFromSource(args) {
|
|
|
238
267
|
gitRepo,
|
|
239
268
|
source: paths.root,
|
|
240
269
|
resolvedFrom: 'v2-source-build',
|
|
270
|
+
artefactPath: paths.artefact,
|
|
271
|
+
clonedManifestPath: manifestPath,
|
|
241
272
|
};
|
|
242
273
|
}
|
|
243
274
|
|