wyvrnpm 2.10.2 → 2.11.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
@@ -235,6 +235,51 @@ wyvrnpm add zlib fmt@10.2.0.0 spdlog
235
235
 
236
236
  ---
237
237
 
238
+ ### `wyvrnpm version`
239
+
240
+ Read, set, or partially update the top-level `version` field in `wyvrn.json`. Designed for CI/CD — bumps the build component from a pipeline variable, prints the result for capture, and never touches git (CI pipelines own tagging and commits).
241
+
242
+ - **Bare** — prints the current version on stdout, naked (no `[wyvrn]` prefix), so `VER=$(wyvrnpm version)` works straight away.
243
+ - **Full set** — `wyvrnpm version 1.2.3.4` writes the value verbatim. Strict 4-part `x.y.z.b` validation; leading zeros and 3-part semver are rejected.
244
+ - **Component flags** — `--major / --minor / --patch / --build N` replace one component while leaving the others alone. Combine flags freely (`--minor 3 --build 0`).
245
+ - Positional and component flags are mutually exclusive — pick a mode.
246
+
247
+ ```bash
248
+ # Read the current version (CI capture)
249
+ VER=$(wyvrnpm version)
250
+
251
+ # Inject the build number from a pipeline variable
252
+ wyvrnpm version --build "$BUILD_ID"
253
+
254
+ # Cut a minor release — reset the build component to 0
255
+ wyvrnpm version --minor 3 --build 0
256
+
257
+ # Set a full version verbatim
258
+ wyvrnpm version 2.0.0.0
259
+
260
+ # CI-friendly capture
261
+ wyvrnpm version --build "$BUILD_ID" --format json
262
+ # {"command":"version","previous":"1.2.3.4","version":"1.2.3.99","written":true,...}
263
+
264
+ # Preview without writing
265
+ wyvrnpm version --build 99 --dry-run
266
+ ```
267
+
268
+ **Options**
269
+
270
+ | Option | Default | Description |
271
+ |---|---|---|
272
+ | `<newVersion>` positional | — | Full 4-part version (e.g. `1.2.3.4`). Mutex with `--major/--minor/--patch/--build`. |
273
+ | `--major` | — | Replace the 1st component. |
274
+ | `--minor` | — | Replace the 2nd component. |
275
+ | `--patch` | — | Replace the 3rd component. |
276
+ | `--build` | — | Replace the 4th component (typical CI use). |
277
+ | `--dry-run` | `false` | Compute and print the proposed version without writing. |
278
+ | `--format` | `text` | Output format: `text` (human) or `json` (CI — single object on stdout). |
279
+ | `--manifest` | `./wyvrn.json` | Path to the manifest file. |
280
+
281
+ ---
282
+
238
283
  ### `wyvrnpm install`
239
284
 
240
285
  Resolves the full dependency graph and downloads all packages.
package/bin/wyvrn.js CHANGED
@@ -5,6 +5,7 @@
5
5
  const yargs = require('yargs');
6
6
  const init = require('../src/commands/init');
7
7
  const add = require('../src/commands/add');
8
+ const versionCmd = require('../src/commands/version');
8
9
  const install = require('../src/commands/install');
9
10
  const clean = require('../src/commands/clean');
10
11
  const publish = require('../src/commands/publish');
@@ -143,6 +144,40 @@ yargs
143
144
  (argv) => add(argv),
144
145
  )
145
146
 
147
+ // ── version ───────────────────────────────────────────────────────────────
148
+ .command(
149
+ 'version [newVersion]',
150
+ 'Read, set, or partially update the top-level `version` field in wyvrn.json. ' +
151
+ 'Bare `wyvrnpm version` prints the current value on stdout (for `VER=$(...)` ' +
152
+ 'capture). Pass a full 4-part value (1.2.3.4) or component flags (--major / ' +
153
+ '--minor / --patch / --build) to tweak individual parts. Designed for CI/CD — ' +
154
+ 'pair with --format json to capture { previous, version } on stdout.',
155
+ (y) => {
156
+ y
157
+ .positional('newVersion', {
158
+ type: 'string',
159
+ describe: 'Full 4-part version to write verbatim (e.g. 1.2.3.4). ' +
160
+ 'Mutually exclusive with --major/--minor/--patch/--build.',
161
+ })
162
+ .option('major', { type: 'number', description: 'Replace the 1st component (major).' })
163
+ .option('minor', { type: 'number', description: 'Replace the 2nd component (minor).' })
164
+ .option('patch', { type: 'number', description: 'Replace the 3rd component (patch).' })
165
+ .option('build', { type: 'number', description: 'Replace the 4th component (build) — typical CI use.' })
166
+ .option('dry-run', {
167
+ type: 'boolean',
168
+ default: false,
169
+ description: 'Compute and print the proposed version without writing wyvrn.json.',
170
+ })
171
+ .option('format', {
172
+ type: 'string',
173
+ choices: ['text', 'json'],
174
+ default: 'text',
175
+ description: 'Output format. "json" emits { command, previous, version, ... } on stdout.',
176
+ });
177
+ },
178
+ (argv) => versionCmd({ ...argv, dryRun: argv['dry-run'] }),
179
+ )
180
+
146
181
  // ── install ───────────────────────────────────────────────────────────────
147
182
  .command(
148
183
  'install',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wyvrnpm",
3
- "version": "2.10.2",
3
+ "version": "2.11.0",
4
4
  "description": "A simple, static-hosting-compatible C++ package manager",
5
5
  "keywords": [
6
6
  "c++",
@@ -0,0 +1,187 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+
5
+ const { readManifest, writeManifest } = require('../manifest');
6
+ const log = require('../logger');
7
+
8
+ /** Strict 4-part wyvrnpm version: `x.y.z.b`, all non-negative integers, no leading zeros. */
9
+ const VERSION_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
10
+
11
+ /**
12
+ * Parse a 4-part version string into `[major, minor, patch, build]`.
13
+ * Throws with a clear message when the string fails the wyvrnpm format.
14
+ *
15
+ * @param {string} v
16
+ * @returns {[number, number, number, number]}
17
+ */
18
+ function parseVersion(v) {
19
+ if (typeof v !== 'string' || v.length === 0) {
20
+ throw new Error('version is empty');
21
+ }
22
+ const m = VERSION_RE.exec(v);
23
+ if (!m) {
24
+ throw new Error(
25
+ `"${v}" is not a valid wyvrnpm version. ` +
26
+ 'Expected 4-part x.y.z.b with non-negative integers (e.g. 1.2.3.4).',
27
+ );
28
+ }
29
+ return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
30
+ }
31
+
32
+ const COMPONENT_INDEX = { major: 0, minor: 1, patch: 2, build: 3 };
33
+
34
+ function isNonNegInt(n) {
35
+ return Number.isInteger(n) && n >= 0;
36
+ }
37
+
38
+ /**
39
+ * `wyvrnpm version [<new-version>]` — read, set, or partially update the
40
+ * top-level `version` field in wyvrn.json. Designed for CI/CD (build-number
41
+ * injection + tag-the-build readback).
42
+ *
43
+ * wyvrnpm version → print the current version
44
+ * wyvrnpm version 1.2.3.4 → set verbatim
45
+ * wyvrnpm version --build 99 → only the 4th component
46
+ * wyvrnpm version --minor 3 --build 0 → multiple components, others left alone
47
+ *
48
+ * Positional and component flags are mutually exclusive — pick one mode.
49
+ * Bare invocation is read-only (never writes, never warns about an unset
50
+ * version on disk — it prints `(unset)` to stderr and exits 1 in that
51
+ * case so scripts notice). CI scripts use --format json to capture the
52
+ * `{ previous, version }` payload on stdout.
53
+ *
54
+ * @param {object} argv
55
+ */
56
+ function versionCmd(argv) {
57
+ const manifestPath = path.resolve(argv.manifest);
58
+ const jsonOut = argv.format === 'json';
59
+ if (jsonOut) log.setJsonMode(true);
60
+
61
+ const positional = argv.newVersion;
62
+
63
+ // Component flags arrive on argv as `argv.major` / `argv.minor` / etc.
64
+ // because yargs lowercases option names. Keep insertion order so error
65
+ // messages list flags in the same order as the CLI surface.
66
+ const partial = {};
67
+ for (const key of Object.keys(COMPONENT_INDEX)) {
68
+ if (argv[key] !== undefined) partial[key] = argv[key];
69
+ }
70
+ const partialKeys = Object.keys(partial);
71
+
72
+ if (positional && partialKeys.length > 0) {
73
+ log.error(
74
+ 'cannot combine a positional version with --major/--minor/--patch/--build. ' +
75
+ 'Use one or the other.',
76
+ );
77
+ process.exit(1);
78
+ }
79
+
80
+ // Read-only mode: no positional + no component flags. Prints the current
81
+ // version and returns without touching disk. Matches `npm version` behaviour
82
+ // when called bare.
83
+ const readOnly = !positional && partialKeys.length === 0;
84
+
85
+ let manifest;
86
+ try {
87
+ manifest = readManifest(manifestPath);
88
+ } catch (err) {
89
+ log.error(err.message);
90
+ process.exit(1);
91
+ }
92
+
93
+ const previous =
94
+ typeof manifest.version === 'string' && manifest.version.length > 0
95
+ ? manifest.version
96
+ : null;
97
+
98
+ if (readOnly) {
99
+ const wyvrnpmVersion = require('../../package.json').version;
100
+ if (jsonOut) {
101
+ process.stdout.write(JSON.stringify({
102
+ command: 'version',
103
+ wyvrnpmVersion,
104
+ path: manifestPath,
105
+ version: previous,
106
+ written: false,
107
+ }) + '\n');
108
+ } else if (previous) {
109
+ // Bare stdout for `VER=$(wyvrnpm version)` — no `[wyvrn]` prefix here.
110
+ process.stdout.write(previous + '\n');
111
+ } else {
112
+ log.error(`no \`version\` field in ${manifestPath}`);
113
+ process.exit(1);
114
+ }
115
+ return;
116
+ }
117
+
118
+ let newVersion;
119
+ if (positional) {
120
+ try {
121
+ parseVersion(positional);
122
+ } catch (err) {
123
+ log.error(err.message);
124
+ process.exit(1);
125
+ }
126
+ newVersion = positional;
127
+ } else {
128
+ let parts;
129
+ try {
130
+ parts = parseVersion(previous ?? '');
131
+ } catch (err) {
132
+ log.error(
133
+ `cannot tweak components — current manifest version is invalid or missing: ${err.message}\n` +
134
+ ' Set a full version first: wyvrnpm version <x.y.z.b>',
135
+ );
136
+ process.exit(1);
137
+ }
138
+ for (const key of partialKeys) {
139
+ const n = partial[key];
140
+ if (!isNonNegInt(n)) {
141
+ log.error(`--${key} must be a non-negative integer (got ${JSON.stringify(n)})`);
142
+ process.exit(1);
143
+ }
144
+ parts[COMPONENT_INDEX[key]] = n;
145
+ }
146
+ newVersion = parts.join('.');
147
+ }
148
+
149
+ const wyvrnpmVersion = require('../../package.json').version;
150
+
151
+ if (argv.dryRun) {
152
+ if (jsonOut) {
153
+ process.stdout.write(JSON.stringify({
154
+ command: 'version',
155
+ wyvrnpmVersion,
156
+ path: manifestPath,
157
+ previous,
158
+ version: newVersion,
159
+ dryRun: true,
160
+ written: false,
161
+ }) + '\n');
162
+ } else {
163
+ log.info(`would set version: ${previous ?? '(unset)'} → ${newVersion} (dry-run)`);
164
+ }
165
+ return;
166
+ }
167
+
168
+ manifest.version = newVersion;
169
+ writeManifest(manifestPath, manifest);
170
+
171
+ if (jsonOut) {
172
+ process.stdout.write(JSON.stringify({
173
+ command: 'version',
174
+ wyvrnpmVersion,
175
+ path: manifestPath,
176
+ previous,
177
+ version: newVersion,
178
+ dryRun: false,
179
+ written: true,
180
+ }) + '\n');
181
+ } else {
182
+ log.success(`${manifestPath}: ${previous ?? '(unset)'} → ${newVersion}`);
183
+ }
184
+ }
185
+
186
+ module.exports = versionCmd;
187
+ module.exports.parseVersion = parseVersion;