wyvrnpm 2.8.1 → 2.10.0

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 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.8.1** — Highlights since 2.4.0: non-ABI build-time `conf` block (CMake cache variables via recipe / team profile / `wyvrn.local.json` / CLI `--conf`), per-package `source` pin for multi-source defense, profile inheritance via `extends`, pattern-based `settingsOverrides` (`boost*: {...}`), `wyvrnpm cache list` / `cache prune` for selective source-build-cache cleanup, `install --dry-run` (JSON plan without side effects), `install --build=always` to force source-rebuild, `--token-env` for CI credentials, and a raft of security hardening (Zip Slip, MSVC arch allow-list). Full list in `claude/EVALUATION.md`.
7
+ > **README version: 2.8.3** — Highlights since 2.4.0: non-ABI build-time `conf` block (CMake cache variables via recipe / team profile / `wyvrn.local.json` / CLI `--conf`), per-package `source` pin for multi-source defense, profile inheritance via `extends`, pattern-based `settingsOverrides` (`boost*: {...}`), `wyvrnpm cache list` / `cache prune` for selective source-build-cache cleanup, `install --dry-run` (JSON plan without side effects), `install --build=always` to force source-rebuild, `--token-env` for CI credentials, CloudFront/WAF-compatible HTTP provider (sends `User-Agent`, surfaces non-404 errors), publish preserves dependency ranges verbatim with lock snapshot moved to informational `publishedLock`, and a raft of security hardening (Zip Slip, MSVC arch allow-list). Full list in `claude/EVALUATION.md`.
8
8
 
9
9
  ---
10
10
 
@@ -143,6 +143,62 @@ wyvrnpm init --root ./my-project
143
143
 
144
144
  ---
145
145
 
146
+ ### `wyvrnpm bootstrap`
147
+
148
+ Scaffolds a first-draft `wyvrn.json` for an open-source C++ library so it can be packaged into your wyvrnpm registry with a single review pass instead of hand-writing a manifest from scratch. Clones the upstream git repo, detects `name` / `version` / `kind` from the checkout, applies known-library CMake flag hints (skip tests, skip examples, flip install gates), and prints a TODO report for everything a machine can't decide for you.
149
+
150
+ Designed for batch-converting a list of forked upstreams (fmt, spdlog, zlib, Catch2, etc.) into your private registry.
151
+
152
+ ```bash
153
+ # Typical single-repo conversion
154
+ wyvrnpm bootstrap https://github.com/fmtlib/fmt.git --dest ./pkgs/fmt
155
+ cd ./pkgs/fmt # review wyvrn.json, settle TODOs
156
+ wyvrnpm install
157
+ wyvrnpm build --install --all-configs
158
+ wyvrnpm publish --source team-s3
159
+
160
+ # Override the derived name / version / kind
161
+ wyvrnpm bootstrap https://github.com/nlohmann/json.git \
162
+ --dest ./pkgs/nlohmann-json \
163
+ --name nlohmann-json \
164
+ --kind HeaderOnlyLib
165
+
166
+ # Work on a directory you already cloned
167
+ wyvrnpm bootstrap --no-clone --dest ./pkgs/fmt --force
168
+
169
+ # Preview without writing — TODO report only
170
+ wyvrnpm bootstrap https://github.com/google/re2.git --dest /tmp/re2 --dry-run
171
+
172
+ # Batch driver — one shell loop, no wyvrnpm subcommand needed
173
+ while read url; do
174
+ wyvrnpm bootstrap "$url" --dest "./pkgs/$(basename "$url" .git)" --force
175
+ done < urls.txt
176
+ # then review, build, and publish each in a loop of your own
177
+ ```
178
+
179
+ **Options**
180
+
181
+ | Option | Default | Description |
182
+ |---|---|---|
183
+ | `<url>` positional | *(required)* | Upstream git URL. Accepts HTTPS or SSH form. |
184
+ | `--dest <dir>` | `basename(url)` | Destination directory for the clone. |
185
+ | `--ref <ref>` | *(remote HEAD)* | Clone at a specific branch / tag / SHA. |
186
+ | `--name` | *(from URL)* | Override the derived package name. |
187
+ | `--pkg-version` | *(nearest git tag)* | Override the version. Named oddly so yargs doesn't confuse it with the built-in `--version`. |
188
+ | `--kind` | *(auto-detect)* | Force `ConsoleApp` / `StaticLib` / `DynamicLib` / `HeaderOnlyLib` / etc. |
189
+ | `--description` | *(TODO placeholder)* | One-line description written into `wyvrn.json`. |
190
+ | `--no-clone` | `false` | Skip the clone step; operate on an already-populated `--dest`. |
191
+ | `--shallow` | `false` | `--depth 1` clone. Faster, but git tag detection degrades — pair with `--pkg-version` when using this. |
192
+ | `--dry-run` | `false` | Print the proposed manifest + TODOs; don't write `wyvrn.json`. |
193
+ | `--force` | `false` | Overwrite an existing destination and/or `wyvrn.json`. |
194
+ | `--format` | `text` | Output format: `text` (human) or `json` (CI — one object on stdout). |
195
+
196
+ The cookbook of per-library hints lives in [src/bootstrap/cookbook.js](src/bootstrap/cookbook.js). Current coverage is ~30 popular libraries (fmt, spdlog, nlohmann/json, yaml-cpp, zlib, Catch2, lz4, brotli, hiredis, redis-plus-plus, GLM, SQLiteCpp, re2, …). Adding a new entry is a two-line change — submit a PR.
197
+
198
+ The patterns bootstrap detects (header-only with INTERFACE target, standard CMake static lib, CMake with wyvrnpm dep, ABI-option'd lib, non-CMake upstream) are catalogued in the bundled Agent Skill at [claude/skills/wyvrnpm/references/oss-conversion-patterns.md](claude/skills/wyvrnpm/references/oss-conversion-patterns.md) — Claude reads it when you ask "how do I convert X to a wyvrnpm package?".
199
+
200
+ ---
201
+
146
202
  ### `wyvrnpm add`
147
203
 
148
204
  Adds one or more dependencies to `wyvrn.json` without downloading them. Useful when you know the package name but not the latest version.
@@ -285,6 +341,13 @@ wyvrnpm build
285
341
  # Build Debug, verbose compiler output
286
342
  wyvrnpm build --config Debug --verbose
287
343
 
344
+ # Build multiple configs in one go — configure runs once, build loops per config
345
+ wyvrnpm build --config Debug,Release --install
346
+
347
+ # Build every config the recipe declares (recipe's build.configs, or all four
348
+ # canonical configs if no recipe is present). Pairs naturally with --install.
349
+ wyvrnpm build --all-configs --install
350
+
288
351
  # Use a specific profile's preset
289
352
  wyvrnpm build --profile release
290
353
 
@@ -318,7 +381,8 @@ wyvrnpm build -- -j 8
318
381
  |---|---|---|
319
382
  | `--preset` / `-P` | `wyvrn-<profile>` | CMake configure preset name. |
320
383
  | `--profile` / `-p` | *(config / "default")* | Build profile — determines the default preset name. |
321
- | `--config` / `-c` | `Release` | Build configuration (`Debug` \| `Release` \| `RelWithDebInfo` \| `MinSizeRel`). |
384
+ | `--config` / `-c` | `Release` | Build configuration (`Debug` \| `Release` \| `RelWithDebInfo` \| `MinSizeRel`). Comma-separated to build multiple in one invocation (e.g. `Debug,Release`) — configure runs once, build (+ `--install`) loops per config. |
385
+ | `--all-configs` | `false` | Build every config from the recipe's `build.configs` (or all four canonical configs when no recipe is present). Takes precedence over `--config`. |
322
386
  | `--target` / `-t` | *(all)* | Specific CMake target to build. |
323
387
  | `--verbose` / `-v` | `false` | Pass `--verbose` to `cmake --build`. |
324
388
  | `--clean` | `false` | Remove the build directory before configuring. |
@@ -546,8 +610,6 @@ You only need this if you want Claude Code to pick up the skill. The CLI itself
546
610
  | `--force` | `false` | Overwrite an existing skill install. Without it, the command refuses to clobber. |
547
611
  | `--dry-run` | `false` | Print the destination paths and intended actions without writing. |
548
612
 
549
- **CLAUDE.md §9 note for contributors:** the tested path for deploying a locally-edited skill is `node scripts/repack-skill.js && node bin/wyvrn.js install-skill --claude --force` — never `rm -rf` + `cp -r`.
550
-
551
613
  ---
552
614
 
553
615
  ### `wyvrnpm link`
@@ -1736,6 +1798,15 @@ wyvrnpm build --install --install-prefix C:\out\stage
1736
1798
 
1737
1799
  # Install a specific config
1738
1800
  wyvrnpm build --install --config Debug
1801
+
1802
+ # Install multiple configs in one invocation — configure runs once,
1803
+ # build + install loops per config. Before 2.9.0 this required two
1804
+ # separate `wyvrnpm build --install ...` invocations.
1805
+ wyvrnpm build --install --config Debug,Release
1806
+
1807
+ # Install every config declared in the recipe's build.configs
1808
+ # (or all four canonical configs when no recipe is present).
1809
+ wyvrnpm build --install --all-configs
1739
1810
  ```
1740
1811
 
1741
1812
  ### What it does, in order
package/bin/wyvrn.js CHANGED
@@ -14,6 +14,7 @@ const build = require('../src/commands/build');
14
14
  const show = require('../src/commands/show');
15
15
  const installSkill = require('../src/commands/install-skill');
16
16
  const cache = require('../src/commands/cache');
17
+ const bootstrap = require('../src/commands/bootstrap');
17
18
  const { link, unlink } = require('../src/commands/link');
18
19
 
19
20
  yargs
@@ -37,6 +38,83 @@ yargs
37
38
  (argv) => init(argv),
38
39
  )
39
40
 
41
+ // ── bootstrap ─────────────────────────────────────────────────────────────
42
+ .command(
43
+ 'bootstrap <url> [dest]',
44
+ 'Scaffold a first-draft wyvrn.json for an OSS C++ library. Shallow-clones ' +
45
+ '(or reuses with --no-clone), detects name/version/kind, applies known-library ' +
46
+ 'cookbook hints, and prints TODOs for gotchas worth a human look before publish. ' +
47
+ 'Designed for batch-converting upstream repos to wyvrnpm packages.',
48
+ (y) => {
49
+ y
50
+ .positional('url', {
51
+ type: 'string',
52
+ describe: 'Upstream git URL (e.g. https://github.com/fmtlib/fmt.git)',
53
+ })
54
+ .positional('dest', {
55
+ type: 'string',
56
+ describe: 'Destination directory (default: basename(url))',
57
+ })
58
+ .option('dest', {
59
+ type: 'string',
60
+ description: 'Destination directory (alias for the positional).',
61
+ })
62
+ .option('ref', {
63
+ type: 'string',
64
+ description: 'Clone at this branch/tag/SHA (default: the remote HEAD).',
65
+ })
66
+ .option('name', {
67
+ type: 'string',
68
+ description: 'Override the derived package name.',
69
+ })
70
+ .option('pkg-version', {
71
+ type: 'string',
72
+ description: 'Override the wyvrnpm package version (otherwise: nearest git tag padded to 4 parts). Not named --version to avoid colliding with yargs\'s built-in --version flag.',
73
+ })
74
+ .option('shallow', {
75
+ type: 'boolean',
76
+ default: false,
77
+ description: 'Use a shallow (`--depth 1`) clone. Faster, but git tag detection degrades to a TODO — pass --pkg-version when combining with this.',
78
+ })
79
+ .option('kind', {
80
+ type: 'string',
81
+ choices: ['ConsoleApp', 'StaticLib', 'DynamicLib', 'HeaderOnlyLib', 'WebApp', 'Utility', 'Service', 'TestProject'],
82
+ description: 'Override the detected kind.',
83
+ })
84
+ .option('description', {
85
+ type: 'string',
86
+ description: 'One-line description written into the manifest.',
87
+ })
88
+ .option('no-clone', {
89
+ type: 'boolean',
90
+ default: false,
91
+ description: 'Skip cloning — operate on an existing directory at --dest.',
92
+ })
93
+ .option('dry-run', {
94
+ type: 'boolean',
95
+ default: false,
96
+ description: 'Print the proposed manifest + TODO report, do not write wyvrn.json.',
97
+ })
98
+ .option('force', {
99
+ type: 'boolean',
100
+ default: false,
101
+ description: 'Overwrite an existing destination or existing wyvrn.json.',
102
+ })
103
+ .option('format', {
104
+ type: 'string',
105
+ choices: ['text', 'json'],
106
+ default: 'text',
107
+ description: 'Output format for CI pipelines. `json` emits the manifest + TODOs as one JSON object on stdout.',
108
+ });
109
+ },
110
+ (argv) => bootstrap({
111
+ ...argv,
112
+ version: argv['pkg-version'],
113
+ noClone: argv['no-clone'],
114
+ dryRun: argv['dry-run'],
115
+ }),
116
+ )
117
+
40
118
  // ── add ───────────────────────────────────────────────────────────────────
41
119
  .command(
42
120
  'add <packages...>',
@@ -191,9 +269,21 @@ yargs
191
269
  .option('config', {
192
270
  alias: 'c',
193
271
  type: 'string',
194
- choices: ['Debug', 'Release', 'RelWithDebInfo', 'MinSizeRel'],
195
272
  default: 'Release',
196
- description: 'Build configuration',
273
+ description:
274
+ 'Build configuration. Accepts a single value (Release) or a ' +
275
+ 'comma-separated list (Debug,Release) to build multiple configs ' +
276
+ 'in one invocation — configure runs once, build (+ --install) ' +
277
+ 'loops per config. Valid: Debug | Release | RelWithDebInfo | MinSizeRel.',
278
+ })
279
+ .option('all-configs', {
280
+ type: 'boolean',
281
+ default: false,
282
+ description:
283
+ 'Build every config declared in the recipe\'s build.configs ' +
284
+ '(falls back to all four canonical configs when no recipe is ' +
285
+ 'present). Equivalent to spelling out --config Debug,Release,... ' +
286
+ 'by hand. Pairs naturally with --install.',
197
287
  })
198
288
  .option('target', {
199
289
  alias: 't',
@@ -262,6 +352,7 @@ yargs
262
352
  ...argv,
263
353
  autoInstall: argv['auto-install'],
264
354
  installPrefix: argv['install-prefix'],
355
+ allConfigs: argv['all-configs'],
265
356
  }),
266
357
  )
267
358
 
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wyvrnpm",
3
- "version": "2.8.1",
3
+ "version": "2.10.0",
4
4
  "description": "A simple, static-hosting-compatible C++ package manager",
5
5
  "keywords": [
6
6
  "c++",
@@ -0,0 +1,196 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Known-library cookbook for `wyvrnpm bootstrap`.
5
+ *
6
+ * Each entry captures the non-obvious tweaks a wyvrnpm recipe needs for a
7
+ * popular upstream — the CMake cache variables that disable tests /
8
+ * examples / docs so the install tree doesn't balloon, install gates the
9
+ * upstream hides behind a flag (the `ZLIB_INSTALL=ON` class of gotcha,
10
+ * see SKILL.md references/manifest-authoring.md §build), and any
11
+ * transitive wyvrnpm deps the upstream already depends on.
12
+ *
13
+ * Keyed by a case-insensitive match on the repo tail. Matching is
14
+ * restricted to exact tails so `wyvrnpm bootstrap` never surprises a
15
+ * user whose fork name collides coincidentally — unmatched tails get
16
+ * the safe-default recipe.
17
+ *
18
+ * Keep this file short and dumb. Rich per-library behaviour belongs in
19
+ * the author's actual `wyvrn.json` — the cookbook is just the "everyone
20
+ * hits these flags on day one" cheat sheet.
21
+ *
22
+ * @typedef {Object} CookbookEntry
23
+ * @property {'HeaderOnlyLib'|'StaticLib'|'DynamicLib'} [kind]
24
+ * Override `detect.js` when upstream's CMakeLists lies (e.g. fmt has
25
+ * both a STATIC lib and an INTERFACE `fmt-header-only`).
26
+ * @property {string[]} [configure]
27
+ * `-DKEY=VAL` args threaded into build.configure. Preserves order so a
28
+ * `-DBUILD_SHARED_LIBS=OFF` comes before a `-DXYZ_OPTION=...` override.
29
+ * @property {Record<string,string>} [dependencies]
30
+ * Known wyvrnpm deps — merged into manifest.dependencies. Versions
31
+ * use `"latest"` so the user picks up whatever the team's registry
32
+ * publishes; they can pin in review.
33
+ * @property {string[]} [notes]
34
+ * Human-readable reminders appended to the TODO report.
35
+ */
36
+
37
+ /** @type {Record<string, CookbookEntry>} */
38
+ const KNOWN_LIBRARIES = Object.freeze({
39
+ // ── Formatting / logging ─────────────────────────────────────────────
40
+ fmt: {
41
+ kind: 'StaticLib',
42
+ configure: ['-DFMT_TEST=OFF', '-DFMT_DOC=OFF'],
43
+ notes: [
44
+ 'fmt also ships a `fmt-header-only` INTERFACE target. If you want ' +
45
+ 'the header-only variant, declare kind=HeaderOnlyLib and add -DFMT_INSTALL=ON.',
46
+ ],
47
+ },
48
+ spdlog: {
49
+ kind: 'StaticLib',
50
+ configure: [
51
+ '-DSPDLOG_BUILD_TESTS=OFF',
52
+ '-DSPDLOG_BUILD_EXAMPLE=OFF',
53
+ '-DSPDLOG_INSTALL=ON',
54
+ '-DSPDLOG_FMT_EXTERNAL=ON',
55
+ ],
56
+ dependencies: { fmt: 'latest' },
57
+ notes: [
58
+ 'SPDLOG_FMT_EXTERNAL=ON makes spdlog link against the wyvrnpm fmt package ' +
59
+ 'instead of its bundled fork. Drop if you want the bundled copy.',
60
+ ],
61
+ },
62
+
63
+ // ── JSON / config / serialization ────────────────────────────────────
64
+ json: { kind: 'HeaderOnlyLib', configure: ['-DJSON_BuildTests=OFF'] },
65
+ rapidjson: { kind: 'HeaderOnlyLib', configure: ['-DRAPIDJSON_BUILD_TESTS=OFF', '-DRAPIDJSON_BUILD_EXAMPLES=OFF', '-DRAPIDJSON_BUILD_DOC=OFF'] },
66
+ jsoncpp: { kind: 'StaticLib', configure: ['-DJSONCPP_WITH_TESTS=OFF', '-DJSONCPP_WITH_POST_BUILD_UNITTEST=OFF'] },
67
+ 'yaml-cpp':{ kind: 'StaticLib', configure: ['-DYAML_CPP_BUILD_TESTS=OFF', '-DYAML_CPP_BUILD_TOOLS=OFF', '-DYAML_CPP_INSTALL=ON'] },
68
+ pugixml: { kind: 'StaticLib', configure: ['-DPUGIXML_BUILD_TESTS=OFF'] },
69
+ tinyxml2: { kind: 'StaticLib', configure: ['-Dtinyxml2_BUILD_TESTING=OFF'] },
70
+ cereal: { kind: 'HeaderOnlyLib', configure: ['-DJUST_INSTALL_CEREAL=ON'] },
71
+ 'msgpack-c': { kind: 'StaticLib', configure: ['-DMSGPACK_BUILD_TESTS=OFF', '-DMSGPACK_BUILD_EXAMPLES=OFF'] },
72
+
73
+ // ── CLI / utilities ──────────────────────────────────────────────────
74
+ cli11: { kind: 'HeaderOnlyLib', configure: ['-DCLI11_BUILD_TESTS=OFF', '-DCLI11_BUILD_EXAMPLES=OFF'] },
75
+ cxxopts: { kind: 'HeaderOnlyLib', configure: ['-DCXXOPTS_BUILD_TESTS=OFF', '-DCXXOPTS_BUILD_EXAMPLES=OFF'] },
76
+ magic_enum: { kind: 'HeaderOnlyLib', configure: ['-DMAGIC_ENUM_OPT_BUILD_EXAMPLES=OFF', '-DMAGIC_ENUM_OPT_BUILD_TESTS=OFF'] },
77
+ date: { kind: 'StaticLib', configure: ['-DBUILD_TZ_LIB=ON', '-DUSE_SYSTEM_TZ_DB=OFF'] },
78
+ utfcpp: { kind: 'HeaderOnlyLib', configure: ['-DUTF8_TESTS=OFF', '-DUTF8_SAMPLES=OFF'] },
79
+
80
+ // ── Testing / benchmarking ──────────────────────────────────────────
81
+ catch2: { kind: 'StaticLib', configure: ['-DCATCH_INSTALL_DOCS=OFF', '-DCATCH_BUILD_TESTING=OFF'] },
82
+ doctest: { kind: 'HeaderOnlyLib', configure: ['-DDOCTEST_WITH_TESTS=OFF', '-DDOCTEST_WITH_MAIN_IN_STATIC_LIB=OFF'] },
83
+ benchmark: {
84
+ kind: 'StaticLib',
85
+ configure: ['-DBENCHMARK_ENABLE_TESTING=OFF', '-DBENCHMARK_ENABLE_GTEST_TESTS=OFF'],
86
+ notes: ['google/benchmark — only one of several projects named "benchmark"; confirm the repo URL.'],
87
+ },
88
+
89
+ // ── Compression ──────────────────────────────────────────────────────
90
+ zlib: {
91
+ kind: 'StaticLib',
92
+ configure: ['-DZLIB_BUILD_EXAMPLES=OFF'],
93
+ notes: [
94
+ 'zlib is the textbook example for an `options.shared` toggle — see ' +
95
+ 'references/worked-example-zlib.md. Consider declaring it before you publish.',
96
+ ],
97
+ },
98
+ lz4: { kind: 'StaticLib', configure: ['-DLZ4_BUILD_LEGACY_LZ4C=OFF', '-DLZ4_BUILD_CLI=OFF', '-DBUILD_SHARED_LIBS=OFF'] },
99
+ snappy: { kind: 'StaticLib', configure: ['-DSNAPPY_BUILD_TESTS=OFF', '-DSNAPPY_BUILD_BENCHMARKS=OFF'] },
100
+ brotli: { kind: 'StaticLib', configure: ['-DBROTLI_DISABLE_TESTS=ON'] },
101
+ bzip2: {
102
+ notes: [
103
+ 'bzip2 uses autotools (no CMakeLists.txt). Either ship a wrapper CMakeLists.txt ' +
104
+ 'in your fork or prebuild + publish as a binary-only artefact.',
105
+ ],
106
+ },
107
+
108
+ // ── Networking / HTTP ────────────────────────────────────────────────
109
+ 'cpp-httplib': { kind: 'HeaderOnlyLib', configure: ['-DHTTPLIB_TEST=OFF'] },
110
+ cpr: {
111
+ kind: 'StaticLib',
112
+ configure: ['-DCPR_BUILD_TESTS=OFF', '-DCPR_USE_SYSTEM_CURL=ON'],
113
+ notes: ['cpr needs libcurl — add a wyvrnpm `libcurl` dep if your registry publishes one, or rely on system curl.'],
114
+ },
115
+ websocketpp: {
116
+ kind: 'HeaderOnlyLib',
117
+ notes: ['websocketpp requires boost::asio or standalone Asio — add the appropriate dep.'],
118
+ },
119
+ cppzmq: {
120
+ kind: 'HeaderOnlyLib',
121
+ notes: ['cppzmq needs libzmq — add a wyvrnpm dep for libzmq before publish.'],
122
+ },
123
+ hiredis: { kind: 'StaticLib', configure: ['-DDISABLE_TESTS=ON', '-DENABLE_EXAMPLES=OFF'] },
124
+ 'redis-plus-plus': {
125
+ kind: 'StaticLib',
126
+ configure: ['-DREDIS_PLUS_PLUS_BUILD_TEST=OFF', '-DREDIS_PLUS_PLUS_USE_TLS=OFF'],
127
+ dependencies: { hiredis: 'latest' },
128
+ },
129
+
130
+ // ── Concurrency ──────────────────────────────────────────────────────
131
+ concurrentqueue: { kind: 'HeaderOnlyLib' },
132
+ readerwriterqueue: { kind: 'HeaderOnlyLib' },
133
+
134
+ // ── Graphics / image ─────────────────────────────────────────────────
135
+ stb: {
136
+ kind: 'HeaderOnlyLib',
137
+ notes: [
138
+ 'stb is a header drop — no CMakeLists.txt upstream. Ship a small wrapper ' +
139
+ 'CMakeLists.txt in your fork that declares an INTERFACE target with ' +
140
+ 'target_include_directories(... INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) and install() rules.',
141
+ ],
142
+ },
143
+ glm: { kind: 'HeaderOnlyLib', configure: ['-DGLM_TEST_ENABLE=OFF', '-DGLM_BUILD_LIBRARY=OFF'] },
144
+ imgui: {
145
+ notes: [
146
+ 'imgui ships no CMakeLists.txt. Ship a wrapper CMakeLists.txt in your fork ' +
147
+ 'that picks the backends you need (OpenGL3/Win32/DX11/etc) and declares a ' +
148
+ 'StaticLib with them.',
149
+ ],
150
+ },
151
+ nuklear: {
152
+ kind: 'HeaderOnlyLib',
153
+ notes: [
154
+ 'Nuklear is a single-header drop. Same pattern as stb: ship an INTERFACE ' +
155
+ 'wrapper CMakeLists.txt in your fork.',
156
+ ],
157
+ },
158
+
159
+ // ── Database ─────────────────────────────────────────────────────────
160
+ sqlitecpp: { kind: 'StaticLib', configure: ['-DSQLITECPP_RUN_CPPLINT=OFF', '-DSQLITECPP_RUN_CPPCHECK=OFF', '-DSQLITECPP_BUILD_TESTS=OFF', '-DSQLITECPP_BUILD_EXAMPLES=OFF'] },
161
+
162
+ // ── Crypto ───────────────────────────────────────────────────────────
163
+ libsodium: {
164
+ notes: [
165
+ 'libsodium uses autotools (configure.ac, Makefile.am). wyvrnpm source-build ' +
166
+ 'supports cmake only — ship a wrapper CMakeLists.txt in your fork, or ' +
167
+ 'prebuild + publish as a binary-only artefact.',
168
+ ],
169
+ },
170
+
171
+ // ── Text / regex ─────────────────────────────────────────────────────
172
+ re2: { kind: 'StaticLib', configure: ['-DRE2_BUILD_TESTING=OFF'] },
173
+ });
174
+
175
+ /**
176
+ * Look up cookbook hints for a given package tail (e.g. "fmt", "zlib").
177
+ * Case-insensitive; returns a defensive copy so callers can merge freely.
178
+ *
179
+ * @param {string} key
180
+ * @returns {CookbookEntry | null}
181
+ */
182
+ function lookupCookbook(key) {
183
+ if (typeof key !== 'string') return null;
184
+ const normal = key.trim().toLowerCase();
185
+ if (!normal) return null;
186
+ const entry = KNOWN_LIBRARIES[normal];
187
+ if (!entry) return null;
188
+ return {
189
+ ...entry,
190
+ configure: entry.configure ? [...entry.configure] : undefined,
191
+ dependencies: entry.dependencies ? { ...entry.dependencies } : undefined,
192
+ notes: entry.notes ? [...entry.notes] : undefined,
193
+ };
194
+ }
195
+
196
+ module.exports = { KNOWN_LIBRARIES, lookupCookbook };
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ /**
7
+ * Inspect a cloned repo directory and infer the wyvrnpm `kind`, plus
8
+ * anything else a bootstrap flow can read off the filesystem cheaply.
9
+ *
10
+ * We explicitly do NOT fully parse CMakeLists.txt — a regex scan is good
11
+ * enough for the 80% case, and the bootstrap command's job is to produce
12
+ * a *first draft* that the human reviews before publish. When the signal
13
+ * is ambiguous we return `unknown` and let the TODO report surface it.
14
+ *
15
+ * @typedef {Object} DetectResult
16
+ * @property {'HeaderOnlyLib' | 'StaticLib' | 'DynamicLib' | 'unknown'} kind
17
+ * @property {boolean} hasCMakeLists — root CMakeLists.txt present?
18
+ * @property {boolean} hasConfigureAc — autotools (libsodium, bzip2)?
19
+ * @property {boolean} hasMeson — meson.build present?
20
+ * @property {string[]} notes — human-readable signals for the TODO report.
21
+ */
22
+
23
+ /**
24
+ * @param {string} repoDir
25
+ * @returns {DetectResult}
26
+ */
27
+ function detectFromRepo(repoDir) {
28
+ const notes = [];
29
+ const result = {
30
+ kind: 'unknown',
31
+ hasCMakeLists: false,
32
+ hasConfigureAc: false,
33
+ hasMeson: false,
34
+ notes,
35
+ };
36
+
37
+ if (!fs.existsSync(repoDir) || !fs.statSync(repoDir).isDirectory()) {
38
+ notes.push(`directory does not exist: ${repoDir}`);
39
+ return result;
40
+ }
41
+
42
+ const cmakePath = path.join(repoDir, 'CMakeLists.txt');
43
+ result.hasCMakeLists = fs.existsSync(cmakePath);
44
+ result.hasConfigureAc = fs.existsSync(path.join(repoDir, 'configure.ac'))
45
+ || fs.existsSync(path.join(repoDir, 'configure'));
46
+ result.hasMeson = fs.existsSync(path.join(repoDir, 'meson.build'));
47
+
48
+ if (!result.hasCMakeLists) {
49
+ if (result.hasConfigureAc) {
50
+ notes.push(
51
+ 'no CMakeLists.txt — upstream uses autotools. wyvrnpm source-build ' +
52
+ 'supports cmake only; ship a wrapper CMakeLists.txt in your fork or ' +
53
+ 'prebuild and publish as a binary-only artefact.',
54
+ );
55
+ } else if (result.hasMeson) {
56
+ notes.push(
57
+ 'no CMakeLists.txt — upstream uses meson. Same workaround as ' +
58
+ 'autotools: ship a wrapper CMakeLists.txt in your fork.',
59
+ );
60
+ } else {
61
+ notes.push(
62
+ 'no CMakeLists.txt and no recognised build system — likely a header-' +
63
+ 'drop upstream (e.g. imgui, nuklear, stb). Add a small CMakeLists.txt ' +
64
+ 'in the fork that declares an INTERFACE target + install() rules.',
65
+ );
66
+ }
67
+ return result;
68
+ }
69
+
70
+ const kindFromCmake = detectKindFromCMakeLists(cmakePath);
71
+ result.kind = kindFromCmake.kind;
72
+ if (kindFromCmake.note) notes.push(kindFromCmake.note);
73
+ return result;
74
+ }
75
+
76
+ /**
77
+ * Scan a CMakeLists.txt for the most specific `add_library(<name> <type>)`
78
+ * signal we can find. In order of preference we look for:
79
+ * INTERFACE → HeaderOnlyLib
80
+ * SHARED → DynamicLib
81
+ * STATIC → StaticLib
82
+ * MODULE → StaticLib (rare; treat as static for now)
83
+ *
84
+ * If we find `add_library(<name>)` with no explicit type, CMake's default
85
+ * depends on `BUILD_SHARED_LIBS`. wyvrnpm's convention is to default to
86
+ * `StaticLib` — the consumer can flip via an `options.shared` toggle later.
87
+ *
88
+ * `add_executable(...)` with no `add_library(...)` → the repo ships only a
89
+ * tool, not a library. We return `unknown` so the TODO report surfaces it.
90
+ *
91
+ * @param {string} cmakePath
92
+ * @returns {{ kind: DetectResult['kind'], note?: string }}
93
+ */
94
+ function detectKindFromCMakeLists(cmakePath) {
95
+ let text;
96
+ try { text = fs.readFileSync(cmakePath, 'utf8'); }
97
+ catch { return { kind: 'unknown', note: `could not read ${cmakePath}` }; }
98
+
99
+ // Strip line comments so an old "# add_library(foo SHARED ...)" doesn't
100
+ // poison detection. Block comments in CMake (#[[ ... ]]) are rare; not
101
+ // worth handling here.
102
+ const stripped = text.replace(/#[^\n]*/g, '');
103
+
104
+ const has = (re) => re.test(stripped);
105
+
106
+ // add_library(<name> [STATIC|SHARED|MODULE|OBJECT|INTERFACE|IMPORTED] ...)
107
+ if (has(/add_library\s*\(\s*[A-Za-z0-9_.+-]+\s+INTERFACE\b/i)) {
108
+ return { kind: 'HeaderOnlyLib' };
109
+ }
110
+ if (has(/add_library\s*\(\s*[A-Za-z0-9_.+-]+\s+SHARED\b/i)) {
111
+ return {
112
+ kind: 'DynamicLib',
113
+ note: 'detected SHARED library — consider declaring an `options.shared` toggle if the upstream respects BUILD_SHARED_LIBS.',
114
+ };
115
+ }
116
+ if (has(/add_library\s*\(\s*[A-Za-z0-9_.+-]+\s+STATIC\b/i)) {
117
+ return { kind: 'StaticLib' };
118
+ }
119
+ if (has(/add_library\s*\(\s*[A-Za-z0-9_.+-]+\s+(MODULE|OBJECT)\b/i)) {
120
+ return {
121
+ kind: 'StaticLib',
122
+ note: 'detected MODULE/OBJECT library — treating as StaticLib. Review.',
123
+ };
124
+ }
125
+ // Implicit-default form: `add_library(<name> <sources>...)` — no type
126
+ // keyword after the name. Negative lookahead on the recognised types
127
+ // distinguishes this from the explicit-type cases above (which we've
128
+ // already checked and returned from).
129
+ if (has(/add_library\s*\(\s*[A-Za-z0-9_.+-]+\s+(?!(STATIC|SHARED|MODULE|OBJECT|INTERFACE|IMPORTED)\b)/i)) {
130
+ return {
131
+ kind: 'StaticLib',
132
+ note: 'add_library() with no explicit type — defaulting to StaticLib. ' +
133
+ 'If the upstream respects BUILD_SHARED_LIBS, add an `options.shared` toggle.',
134
+ };
135
+ }
136
+ if (has(/add_executable\s*\(/i)) {
137
+ return {
138
+ kind: 'unknown',
139
+ note: 'repo declares add_executable() but no add_library() — looks like ' +
140
+ 'a tool, not a library. Set kind: ConsoleApp in the manifest.',
141
+ };
142
+ }
143
+ return {
144
+ kind: 'unknown',
145
+ note: 'no add_library() / add_executable() found at the root CMakeLists.txt. ' +
146
+ 'The library may be declared in a subdirectory — inspect and set kind manually.',
147
+ };
148
+ }
149
+
150
+ module.exports = { detectFromRepo, detectKindFromCMakeLists };