@nemus-cli/nemus 0.11.0 → 0.12.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
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.12.0] - 2026-09-02
11
+
12
+ ### Added
13
+
14
+ - **`nemus version` subcommand** — a companion to the `-V/--version` flag for
15
+ people who type `nemus version`. `--json` additionally reports the Node.js
16
+ version, platform, and arch (handy for bug reports), as a single JSON document
17
+ to stdout. (#74)
18
+ - **`NEMUS_NO_UPDATE_CHECK`** — opt out of the background "update available"
19
+ check entirely (no cache read, no network). Also honors the de-facto
20
+ `NO_UPDATE_NOTIFIER`. An explicit falsey value (`0`/`false`/empty) does not
21
+ disable it. Documented in the README env-var table. (#76)
22
+
10
23
  ## [0.11.0] - 2026-09-02
11
24
 
12
25
  ### Added
package/README.md CHANGED
@@ -288,6 +288,7 @@ Everything Nemus reads from the environment (all optional):
288
288
  | `NEMUS_JUDGE_TIMEOUT_MS` | Timeout for the `reflect` judge call. |
289
289
  | `NEMUS_BUG_REPORT_REPO` | Repo that `report-bug` files issues against. |
290
290
  | `NEMUS_SKIP_CONFIGURE` | Skip the one-time post-install `configure` prompt. |
291
+ | `NEMUS_NO_UPDATE_CHECK` | Disable the background "update available" check (also honors `NO_UPDATE_NOTIFIER`). |
291
292
  | `WORKSPACE_CLONE_TIMEOUT_MS` | Git clone timeout (default 15 min). |
292
293
  | `NO_COLOR` / `FORCE_COLOR` | Disable / force ANSI color (see [Global flags](#global-flags)). |
293
294
  | `VISUAL` / `EDITOR` | Editor launched by `nemus config edit`. |
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildVersionInfo = buildVersionInfo;
4
+ exports.registerVersionCommand = registerVersionCommand;
5
+ const output_1 = require("../utils/output");
6
+ /**
7
+ * Build the version payload. Pure and process-injectable so the JSON shape is
8
+ * unit-testable without reading the real runtime.
9
+ */
10
+ function buildVersionInfo(version, proc = process) {
11
+ return {
12
+ version,
13
+ node: proc.versions.node,
14
+ platform: proc.platform,
15
+ arch: proc.arch,
16
+ };
17
+ }
18
+ /**
19
+ * `nemus version` — a subcommand companion to the `-V/--version` flag, for
20
+ * people who type `nemus version`. `--json` also reports the Node/OS runtime
21
+ * (handy for bug reports), emitting a single JSON document to stdout.
22
+ */
23
+ function registerVersionCommand(program, version) {
24
+ program
25
+ .command('version')
26
+ .description('Print the Nemus version (with --json for version + runtime info)')
27
+ .option('--json', 'Output version + runtime info as JSON')
28
+ .action((opts) => {
29
+ const info = buildVersionInfo(version);
30
+ if (opts.json) {
31
+ (0, output_1.outputJson)(info);
32
+ }
33
+ else {
34
+ process.stdout.write(`nemus ${info.version}\n`);
35
+ }
36
+ });
37
+ }
package/dist/program.js CHANGED
@@ -40,6 +40,7 @@ const fs = __importStar(require("fs"));
40
40
  const colors_1 = require("./utils/colors");
41
41
  const banner_1 = require("./utils/banner");
42
42
  const global_flags_1 = require("./utils/global-flags");
43
+ const version_1 = require("./commands/version");
43
44
  // Read version from package.json
44
45
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
45
46
  // --no-color must be applied BEFORE commander parses so it reaches the help
@@ -66,6 +67,7 @@ exports.program
66
67
  // also pre-scanned above.
67
68
  exports.program.hook('preAction', () => (0, global_flags_1.applyGlobalFlags)(exports.program.opts()));
68
69
  // Register top-level commands
70
+ (0, version_1.registerVersionCommand)(exports.program, pkg.version);
69
71
  const create_1 = require("./commands/create");
70
72
  const list_1 = require("./commands/list");
71
73
  const update_1 = require("./commands/update");
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.checkForUpdate = checkForUpdate;
37
+ exports.updateCheckDisabled = updateCheckDisabled;
37
38
  const fs = __importStar(require("fs/promises"));
38
39
  const path = __importStar(require("path"));
39
40
  const child_process_1 = require("child_process");
@@ -89,6 +90,11 @@ async function fetchLatestVersion() {
89
90
  * This is designed to be non-blocking and best-effort — failures are silent.
90
91
  */
91
92
  async function checkForUpdate() {
93
+ // Opt-out: skip the check entirely (no cache read, no network) when the user
94
+ // asks for it. NEMUS_NO_UPDATE_CHECK is ours; NO_UPDATE_NOTIFIER is the
95
+ // de-facto convention several Node CLIs honor.
96
+ if (updateCheckDisabled(process.env))
97
+ return null;
92
98
  try {
93
99
  const currentVersion = (0, config_1.getPackageVersion)();
94
100
  const cache = await loadCache();
@@ -116,6 +122,15 @@ async function checkForUpdate() {
116
122
  return null;
117
123
  }
118
124
  }
125
+ /**
126
+ * Whether the update check is opted out via env. A value is "set" unless it is
127
+ * empty or an explicit falsey token (`0`/`false`), so `NEMUS_NO_UPDATE_CHECK=0`
128
+ * does NOT disable the check. Pure + exported for testing.
129
+ */
130
+ function updateCheckDisabled(env) {
131
+ const isSet = (v) => v !== undefined && v !== '' && v !== '0' && v.toLowerCase() !== 'false';
132
+ return isSet(env.NEMUS_NO_UPDATE_CHECK) || isSet(env.NO_UPDATE_NOTIFIER);
133
+ }
119
134
  function formatUpdateMessage(current, latest) {
120
135
  return `\x1b[33m[nemus] Update available: ${current} -> ${latest}. Run: npm install -g @nemus-cli/nemus@latest\x1b[0m`;
121
136
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nemus-cli/nemus",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "workspaces": [
5
5
  "packages/*"
6
6
  ],
@@ -0,0 +1,21 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { buildVersionInfo } from './version';
3
+
4
+ describe('buildVersionInfo', () => {
5
+ it('combines the given version with the injected runtime fields', () => {
6
+ const info = buildVersionInfo('1.2.3', {
7
+ versions: { node: '22.13.0' } as NodeJS.ProcessVersions,
8
+ platform: 'linux',
9
+ arch: 'x64',
10
+ });
11
+ expect(info).toEqual({ version: '1.2.3', node: '22.13.0', platform: 'linux', arch: 'x64' });
12
+ });
13
+
14
+ it('defaults to the real process runtime', () => {
15
+ const info = buildVersionInfo('9.9.9');
16
+ expect(info.version).toBe('9.9.9');
17
+ expect(info.node).toBe(process.versions.node);
18
+ expect(info.platform).toBe(process.platform);
19
+ expect(info.arch).toBe(process.arch);
20
+ });
21
+ });
@@ -0,0 +1,45 @@
1
+ import { Command } from 'commander';
2
+ import { outputJson } from '../utils/output';
3
+
4
+ export interface VersionInfo {
5
+ version: string;
6
+ node: string;
7
+ platform: string;
8
+ arch: string;
9
+ }
10
+
11
+ /**
12
+ * Build the version payload. Pure and process-injectable so the JSON shape is
13
+ * unit-testable without reading the real runtime.
14
+ */
15
+ export function buildVersionInfo(
16
+ version: string,
17
+ proc: Pick<NodeJS.Process, 'versions' | 'platform' | 'arch'> = process,
18
+ ): VersionInfo {
19
+ return {
20
+ version,
21
+ node: proc.versions.node,
22
+ platform: proc.platform,
23
+ arch: proc.arch,
24
+ };
25
+ }
26
+
27
+ /**
28
+ * `nemus version` — a subcommand companion to the `-V/--version` flag, for
29
+ * people who type `nemus version`. `--json` also reports the Node/OS runtime
30
+ * (handy for bug reports), emitting a single JSON document to stdout.
31
+ */
32
+ export function registerVersionCommand(program: Command, version: string) {
33
+ program
34
+ .command('version')
35
+ .description('Print the Nemus version (with --json for version + runtime info)')
36
+ .option('--json', 'Output version + runtime info as JSON')
37
+ .action((opts: { json?: boolean }) => {
38
+ const info = buildVersionInfo(version);
39
+ if (opts.json) {
40
+ outputJson(info);
41
+ } else {
42
+ process.stdout.write(`nemus ${info.version}\n`);
43
+ }
44
+ });
45
+ }
package/src/program.ts CHANGED
@@ -4,6 +4,7 @@ import * as fs from 'fs';
4
4
  import { setColorEnabled } from './utils/colors';
5
5
  import { renderHelpBanner } from './utils/banner';
6
6
  import { applyGlobalFlags } from './utils/global-flags';
7
+ import { registerVersionCommand } from './commands/version';
7
8
 
8
9
  // Read version from package.json
9
10
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
@@ -35,6 +36,7 @@ program
35
36
  program.hook('preAction', () => applyGlobalFlags(program.opts()));
36
37
 
37
38
  // Register top-level commands
39
+ registerVersionCommand(program, pkg.version);
38
40
  import { registerCreateCommand } from './commands/create';
39
41
  import { registerListCommand } from './commands/list';
40
42
  import { registerUpdateCommand } from './commands/update';
@@ -24,14 +24,41 @@ vi.mock('./config', () => ({
24
24
  getPackageVersion: () => '2.20.0',
25
25
  }));
26
26
 
27
- import { checkForUpdate } from './version-check';
27
+ import { checkForUpdate, updateCheckDisabled } from './version-check';
28
28
  import * as fs from 'fs/promises';
29
29
 
30
+ describe('updateCheckDisabled', () => {
31
+ it('is true when NEMUS_NO_UPDATE_CHECK is set to a truthy value', () => {
32
+ expect(updateCheckDisabled({ NEMUS_NO_UPDATE_CHECK: '1' })).toBe(true);
33
+ expect(updateCheckDisabled({ NEMUS_NO_UPDATE_CHECK: 'yes' })).toBe(true);
34
+ });
35
+ it('honors the de-facto NO_UPDATE_NOTIFIER', () => {
36
+ expect(updateCheckDisabled({ NO_UPDATE_NOTIFIER: 'true' })).toBe(true);
37
+ });
38
+ it('is false when unset, empty, or an explicit falsey token', () => {
39
+ expect(updateCheckDisabled({})).toBe(false);
40
+ expect(updateCheckDisabled({ NEMUS_NO_UPDATE_CHECK: '' })).toBe(false);
41
+ expect(updateCheckDisabled({ NEMUS_NO_UPDATE_CHECK: '0' })).toBe(false);
42
+ expect(updateCheckDisabled({ NEMUS_NO_UPDATE_CHECK: 'false' })).toBe(false);
43
+ });
44
+ });
45
+
30
46
  describe('checkForUpdate', () => {
31
47
  beforeEach(() => {
32
48
  vi.clearAllMocks();
33
49
  });
34
50
 
51
+ it('returns null immediately when opted out via env (no fetch)', async () => {
52
+ process.env.NEMUS_NO_UPDATE_CHECK = '1';
53
+ try {
54
+ const result = await checkForUpdate();
55
+ expect(result).toBeNull();
56
+ expect(mockExecFile).not.toHaveBeenCalled();
57
+ } finally {
58
+ delete process.env.NEMUS_NO_UPDATE_CHECK;
59
+ }
60
+ });
61
+
35
62
  it('returns null when current version matches latest', async () => {
36
63
  // No cached check
37
64
  vi.mocked(fs.readFile).mockRejectedValueOnce(new Error('ENOENT'));
@@ -60,6 +60,10 @@ async function fetchLatestVersion(): Promise<string | null> {
60
60
  * This is designed to be non-blocking and best-effort — failures are silent.
61
61
  */
62
62
  export async function checkForUpdate(): Promise<string | null> {
63
+ // Opt-out: skip the check entirely (no cache read, no network) when the user
64
+ // asks for it. NEMUS_NO_UPDATE_CHECK is ours; NO_UPDATE_NOTIFIER is the
65
+ // de-facto convention several Node CLIs honor.
66
+ if (updateCheckDisabled(process.env)) return null;
63
67
  try {
64
68
  const currentVersion = getPackageVersion();
65
69
  const cache = await loadCache();
@@ -91,6 +95,17 @@ export async function checkForUpdate(): Promise<string | null> {
91
95
  }
92
96
  }
93
97
 
98
+ /**
99
+ * Whether the update check is opted out via env. A value is "set" unless it is
100
+ * empty or an explicit falsey token (`0`/`false`), so `NEMUS_NO_UPDATE_CHECK=0`
101
+ * does NOT disable the check. Pure + exported for testing.
102
+ */
103
+ export function updateCheckDisabled(env: NodeJS.ProcessEnv): boolean {
104
+ const isSet = (v: string | undefined) =>
105
+ v !== undefined && v !== '' && v !== '0' && v.toLowerCase() !== 'false';
106
+ return isSet(env.NEMUS_NO_UPDATE_CHECK) || isSet(env.NO_UPDATE_NOTIFIER);
107
+ }
108
+
94
109
  function formatUpdateMessage(current: string, latest: string): string {
95
110
  return `\x1b[33m[nemus] Update available: ${current} -> ${latest}. Run: npm install -g @nemus-cli/nemus@latest\x1b[0m`;
96
111
  }