@selvajs/cli 4.1.0 → 4.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selvajs/cli",
3
- "version": "4.1.0",
3
+ "version": "4.2.1",
4
4
  "description": "Scaffold and operate a Selva white-label deployment. `npx @selvajs/cli <dir>` to bootstrap, `selva <cmd>` to manage.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -12,7 +12,8 @@
12
12
  // Exits 0 (green) or 1 (any red); yellow checks don't fail the run.
13
13
 
14
14
  import { existsSync, readFileSync, accessSync, constants, statSync } from 'node:fs';
15
- import { join, resolve } from 'node:path';
15
+ import { join, resolve, dirname } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
16
17
  import * as p from '@clack/prompts';
17
18
  import pc from 'picocolors';
18
19
  import { readEnvFile } from '../env.js';
@@ -94,6 +95,14 @@ export async function runDoctor() {
94
95
  // runtime package needs to be on disk.
95
96
  checks.push(checkPackage(dir, '@selvajs/selva'));
96
97
 
98
+ // ── CLI / runtime version alignment ────────────────────────────────
99
+ // The CLI and runtime ship as a `fixed` changeset group, so they SHOULD
100
+ // always share a version. They can still drift on disk: a caret pin
101
+ // (`^4`) won't cross a major, so after the group jumps to 5.x a stale
102
+ // `^4` pin silently keeps the CLI on 4.x. That's the exact failure that
103
+ // makes `selva update` look like a no-op for the CLI. Surface it.
104
+ checks.push(checkCliRuntimeAlignment(dir));
105
+
97
106
  // ── Origin (best-effort) ───────────────────────────────────────────
98
107
  if (env.ORIGIN) {
99
108
  try {
@@ -198,6 +207,50 @@ function checkPackage(dir, name) {
198
207
  return green(`${name} installed`);
199
208
  }
200
209
 
210
+ function readPackageVersion(pkgJsonPath) {
211
+ try {
212
+ return JSON.parse(readFileSync(pkgJsonPath, 'utf8')).version ?? null;
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
217
+
218
+ function majorOf(version) {
219
+ const m = /^(\d+)\./.exec(version ?? '');
220
+ return m ? Number(m[1]) : null;
221
+ }
222
+
223
+ // Compare the running CLI's major against the installed @selvajs/selva
224
+ // runtime's major. They release together (`fixed` group), so a major skew
225
+ // means the deployment's `@selvajs/cli` pin is stale — bump it and reinstall.
226
+ function checkCliRuntimeAlignment(dir) {
227
+ const here = dirname(fileURLToPath(import.meta.url));
228
+ const cliVersion = readPackageVersion(join(here, '..', '..', 'package.json'));
229
+ const runtimeVersion = readPackageVersion(
230
+ join(dir, 'node_modules', '@selvajs', 'selva', 'package.json')
231
+ );
232
+
233
+ // Runtime missing is already reported red by checkPackage; stay quiet here.
234
+ if (!runtimeVersion) return yellow('CLI/runtime version check skipped (runtime not installed)');
235
+ if (!cliVersion) return yellow('CLI/runtime version check skipped (could not read CLI version)');
236
+
237
+ const cliMajor = majorOf(cliVersion);
238
+ const runtimeMajor = majorOf(runtimeVersion);
239
+ if (cliMajor === null || runtimeMajor === null) {
240
+ return yellow(`CLI ${cliVersion} / runtime ${runtimeVersion} — unparseable version, skipping`);
241
+ }
242
+
243
+ if (cliMajor === runtimeMajor) {
244
+ return green(`CLI ${cliVersion} aligned with runtime ${runtimeVersion} (major ${cliMajor})`);
245
+ }
246
+
247
+ return red(
248
+ `CLI major (${cliVersion}) != runtime major (${runtimeVersion}) — the @selvajs/cli pin ` +
249
+ `is stale. A caret range won't cross a major, so update it explicitly:\n ` +
250
+ `npm install @selvajs/cli@^${runtimeMajor} && selva restart`
251
+ );
252
+ }
253
+
201
254
  function checkLayoutDrift(dir) {
202
255
  const pkgPath = join(dir, 'package.json');
203
256
  if (!existsSync(pkgPath)) return yellow('package.json missing — cannot check layout');