claude-nomad 0.22.2 → 0.23.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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.23.0](https://github.com/funkadelic/claude-nomad/compare/v0.22.3...v0.23.0) (2026-05-23)
4
+
5
+
6
+ ### Added
7
+
8
+ * **doctor:** warn when host node is below engines.node minimum ([#116](https://github.com/funkadelic/claude-nomad/issues/116)) ([3caf8d0](https://github.com/funkadelic/claude-nomad/commit/3caf8d09362b2f290af3d0efd9ea79a64aac39c1))
9
+
10
+
11
+ ### Changed
12
+
13
+ * **tests:** add lockfile drift gate to catch release-please mismatches ([#114](https://github.com/funkadelic/claude-nomad/issues/114)) ([5847553](https://github.com/funkadelic/claude-nomad/commit/58475538ed405e1d2d1d00662ea6730ad124da42))
14
+
15
+ ## [0.22.3](https://github.com/funkadelic/claude-nomad/compare/v0.22.2...v0.22.3) (2026-05-23)
16
+
17
+
18
+ ### Changed
19
+
20
+ * **deps:** resync package-lock with manifest after 0.22.2 ([#109](https://github.com/funkadelic/claude-nomad/issues/109)) ([7317fd4](https://github.com/funkadelic/claude-nomad/commit/7317fd4bb375c5a5fb9c05c327fc75423946b0f0))
21
+
3
22
  ## [0.22.2](https://github.com/funkadelic/claude-nomad/compare/v0.22.1...v0.22.2) (2026-05-23)
4
23
 
5
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-nomad",
3
- "version": "0.22.2",
3
+ "version": "0.23.0",
4
4
  "type": "module",
5
5
  "description": "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
6
6
  "keywords": [
@@ -0,0 +1,90 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+
4
+ import { green, okGlyph, warnGlyph, yellow } from './color.ts';
5
+ import { addItem, type DoctorSection } from './commands.doctor.format.ts';
6
+ import { compareSemver } from './commands.doctor.version.ts';
7
+
8
+ /**
9
+ * Soft host-fitness check appended to the Version section of `nomad doctor`.
10
+ * Compares the running node version (`process.version`) to the minimum required
11
+ * by `engines.node` in `package.json`, emitting one of:
12
+ * - `✓ node: vX.Y.Z (satisfies >=A.B.C)` when current >= required
13
+ * - `⚠︎ node: vX.Y.Z (below required >=A.B.C, run \`nvm install\`)` when below
14
+ * Every failure path (missing engines, unsupported range syntax, unreadable
15
+ * `package.json`) is a SILENT skip; this module never sets `process.exitCode`
16
+ * and never writes to stderr. Mirrors the philosophy of the sibling release-
17
+ * version check in `commands.doctor.version.ts`.
18
+ */
19
+
20
+ /** Strict `>=X.Y.Z` matcher. The project's `engines.node` field has always
21
+ * used this form; anything more exotic (`^`, `~`, exact pin, OR ranges) is
22
+ * out of scope and triggers the silent-skip path so we never falsely PASS or
23
+ * falsely WARN on a spec we cannot parse with full confidence. */
24
+ const ENGINES_GTE = /^>=\s*(\d+\.\d+\.\d+)$/;
25
+
26
+ /**
27
+ * Peel the minimum strict-semver out of an `engines.node` spec when the spec
28
+ * is `>=X.Y.Z` (optional whitespace after `>=`). Returns the bare `X.Y.Z`
29
+ * string. Any other shape (`^X.Y.Z`, `~X.Y.Z`, exact pins, OR ranges, bare
30
+ * versions) returns `null` so the caller silently skips the diagnostic.
31
+ */
32
+ export function parseMinVersion(spec: string): string | null {
33
+ const m = ENGINES_GTE.exec(spec);
34
+ return m ? m[1] : null;
35
+ }
36
+
37
+ /**
38
+ * Locate and parse the local `package.json`, returning the `engines.node`
39
+ * string when present, non-empty, and a string. Any throw (missing file,
40
+ * parse error, etc.) becomes a `null` return so the caller silently skips.
41
+ */
42
+ function readEnginesNode(): string | null {
43
+ try {
44
+ const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url));
45
+ const parsed = JSON.parse(readFileSync(pkgPath, 'utf8')) as {
46
+ engines?: { node?: unknown };
47
+ };
48
+ const node = parsed.engines?.node;
49
+ if (typeof node === 'string' && node.length > 0) return node;
50
+ return null;
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Emit a single, non-fatal node-engine diagnostic for `nomad doctor` by
58
+ * comparing `process.version` to the minimum required by `engines.node`.
59
+ *
60
+ * Logs one of:
61
+ * - `✓ node: vX.Y.Z (satisfies >=A.B.C)` when current is at or above the minimum
62
+ * - `⚠︎ node: vX.Y.Z (below required >=A.B.C, run \`nvm install\`)` when below
63
+ *
64
+ * Any failure to read `package.json`, locate `engines.node`, or parse the
65
+ * range spec results in no output and does not change `process.exitCode`.
66
+ */
67
+ export function reportNodeEngineCheck(section: DoctorSection): void {
68
+ const required = readEnginesNode();
69
+ if (required === null) return;
70
+ const min = parseMinVersion(required);
71
+ if (min === null) return;
72
+ const current = process.version.replace(/^v/, '');
73
+ // Belt-and-suspenders: official Node release builds always produce a strict
74
+ // `vX.Y.Z`, but prerelease/nightly builds can carry a suffix
75
+ // (e.g. `v22.0.0-rc.1`) that compareSemver classifies as undecidable
76
+ // (returns 0). Without this guard, an undecidable comparison would fall
77
+ // through to the green PASS branch and falsely claim the host satisfies the
78
+ // engine constraint. Silent-skip matches the module's "err on the side of
79
+ // saying nothing" philosophy.
80
+ if (!/^\d+\.\d+\.\d+$/.test(current)) return;
81
+ const cmp = compareSemver(current, min);
82
+ if (cmp === -1) {
83
+ addItem(
84
+ section,
85
+ `${yellow(warnGlyph)} node: ${process.version} (below required >=${min}, run \`nvm install\`)`,
86
+ );
87
+ return;
88
+ }
89
+ addItem(section, `${green(okGlyph)} node: ${process.version} (satisfies >=${min})`);
90
+ }
@@ -12,6 +12,7 @@ import {
12
12
  reportRepoState,
13
13
  reportSharedLinks,
14
14
  } from './commands.doctor.checks.ts';
15
+ import { reportNodeEngineCheck } from './commands.doctor.engine.ts';
15
16
  import { renderDoctor, section } from './commands.doctor.format.ts';
16
17
  import { reportVersionCheck } from './commands.doctor.version.ts';
17
18
 
@@ -51,6 +52,7 @@ export function cmdDoctor(): void {
51
52
 
52
53
  const version = section('Version');
53
54
  reportVersionCheck(version);
55
+ reportNodeEngineCheck(version);
54
56
 
55
57
  renderDoctor([version, host, links, settings, pathMap, neverSync, repository]);
56
58
  }