@williamthorsen/nmr 0.53.0 → 0.54.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.
@@ -1,4 +1,38 @@
1
1
  [
2
+ {
3
+ "version": "0.54.0",
4
+ "date": "2026-10-31",
5
+ "sections": [
6
+ {
7
+ "title": "🎉 Features",
8
+ "audience": "all",
9
+ "items": [
10
+ {
11
+ "description": "Adds a check of the `nodejs` pin in `.tool-versions` to `nmr upgrade`, which reports the newest release at least 24 hours old in the pin's major line and, under `--write`, rewrites the pin to it. (#1141)",
12
+ "hash": "d6459b4f867e39162a3e41dd8639993b12ba579f",
13
+ "entry": 1
14
+ }
15
+ ]
16
+ }
17
+ ]
18
+ },
19
+ {
20
+ "version": "0.53.1",
21
+ "date": "2026-10-28",
22
+ "sections": [
23
+ {
24
+ "title": "♻️ Refactoring",
25
+ "audience": "dev",
26
+ "items": [
27
+ {
28
+ "description": "Lead release-kit's function names with a transitive verb (#1137)",
29
+ "body": "Renames the functions in `release-kit`'s source and tests so that each name starts with a transitive verb that states its action, such as `releasePrepareTargets` to `prepareReleaseTargets`, `releaseWorkflow` to `renderReleaseWorkflow`, and `topologicalSort` to `sortTopologically`. The exports of `.` and `./config` stay the same.",
30
+ "hash": "a84d9ce0652754a417b85c3f9cd14a6e400c7b51"
31
+ }
32
+ ]
33
+ }
34
+ ]
35
+ },
2
36
  {
3
37
  "version": "0.53.0",
4
38
  "date": "2026-10-27",
@@ -9190,7 +9190,7 @@ function hasSupportedStrictLintVersion() {
9190
9190
  });
9191
9191
  }
9192
9192
  function getMinVersion() {
9193
- const pickedFields = { "version": "0.53.0" };
9193
+ const pickedFields = { "version": "0.54.0" };
9194
9194
  if (typeof pickedFields["version"] !== "string") {
9195
9195
  throw new TypeError("nmr/package.json: 'version' must be a string");
9196
9196
  }
@@ -10,7 +10,7 @@
10
10
  "@williamthorsen/toolbelt.objects": "4.4.0",
11
11
  "@williamthorsen/toolbelt.packaging": "0.7.0",
12
12
  "@williamthorsen/toolbelt.terminal": "0.3.0",
13
- "ansi-regex": "6.4.0",
13
+ "ansi-regex": "6.5.0",
14
14
  "ansi-styles": "6.2.3",
15
15
  "cli-truncate": "6.1.1",
16
16
  "diff": "9.0.0",
@@ -27,7 +27,7 @@
27
27
  "checklist": "nmr",
28
28
  "checks": [
29
29
  "@williamthorsen/nmr in devDependencies",
30
- "@williamthorsen/nmr >= 0.53.0",
30
+ "@williamthorsen/nmr >= 0.54.0",
31
31
  "pnpm-workspace.yaml exists",
32
32
  "catalogued dependencies use catalog:",
33
33
  "package.json has packageManager field",
@@ -209,7 +209,7 @@
209
209
  "path": "lib/workspace-build.ts"
210
210
  },
211
211
  {
212
- "hash": "aa5cf58e",
212
+ "hash": "79b213aa",
213
213
  "kind": "inline",
214
214
  "path": "../package.json",
215
215
  "paths": [
@@ -327,7 +327,7 @@
327
327
  "readyupVersion": "0.44.0",
328
328
  "source": "kits/default.ts",
329
329
  "sourceHash": "9fd61bf4",
330
- "targetHash": "ad118293"
330
+ "targetHash": "5689b2d3"
331
331
  }
332
332
  ]
333
333
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 0.54.0 — 2026-10-31
6
+
7
+ ### 🎉 Features
8
+
9
+ - Adds a check of the `nodejs` pin in `.tool-versions` to `nmr upgrade`, which reports the newest release at least 24 hours old in the pin's major line and, under `--write`, rewrites the pin to it. (#1141)
10
+
11
+ ## 0.53.1 — 2026-10-28
12
+
13
+ ### ♻️ Refactoring
14
+
15
+ - Lead release-kit's function names with a transitive verb (#1137)
16
+
17
+ Renames the functions in `release-kit`'s source and tests so that each name starts with a transitive verb that states its action, such as `releasePrepareTargets` to `prepareReleaseTargets`, `releaseWorkflow` to `renderReleaseWorkflow`, and `topologicalSort` to `sortTopologically`. The exports of `.` and `./config` stay the same.
18
+
5
19
  ## 0.53.0 — 2026-10-27
6
20
 
7
21
  ### 🎉 Features
package/README.md CHANGED
@@ -5,19 +5,11 @@
5
5
  Context-aware script runner for pnpm monorepos. Includes an `nmr` (node-monorepo run) binary that provides centralized, consistent script execution across workspace packages and the monorepo root.
6
6
 
7
7
  <!-- section:release-notes -->
8
- ## Release notes — v0.53.0 (2026-10-27)
8
+ ## Release notes — v0.54.0 (2026-10-31)
9
9
 
10
10
  ### 🎉 Features
11
11
 
12
- - Adds the time saved to the line that `nmr-compile` prints when it skips an unchanged package, as in `Skipping build, saved ~2.1s.`, when the skipped emit took at least a second. (#1112)
13
- - Adds `build skipped as unchanged` to the verdict line of a compile that hits the build cache, followed by the saved time when the recorded emit took at least a second, which nmr prints in a quiet run as well. (#1113)
14
- - Adds a `stated` field to the `--json` record of a passing command that stated its own outcome, such as `{"kind":"build-unchanged","savedMs":2100}`, which records the saving whatever its size. (#1113)
15
- - Removes the 7-day `maturityPeriod` from the shared policy that `defineConfig` in `@williamthorsen/nmr/taze` supplies, so that `nmr upgrade` waits for the `minimumReleaseAge` set in `pnpm-workspace.yaml` (or pnpm's default) and no longer withholds releases that pnpm already resolves for other packages. (#1129)
16
-
17
- ### 🐛 Bug fixes
18
-
19
- - Fixes the issue that nmr reported a short or negative duration for a command's run, a delegated run, or a remote lock wait when the system clock was adjusted during that span. (#1112)
20
- - Fixes the issue that `nmr-fmt` never formatted a file whose name starts with `-` and rejected `--` as an unknown option, by making `nmr-fmt` accept `--` before its pathspecs and pass `--` to Prettier before the file list. (#1130)
12
+ - Adds a check of the `nodejs` pin in `.tool-versions` to `nmr upgrade`, which reports the newest release at least 24 hours old in the pin's major line and, under `--write`, rewrites the pin to it. (#1141)
21
13
  <!-- /section:release-notes -->
22
14
 
23
15
  ## Installation
@@ -248,7 +240,7 @@ export default definePrettierConfig();
248
240
 
249
241
  ### Dependency upgrades
250
242
 
251
- `nmr upgrade` reports available dependency upgrades. A `taze.config.ts` at the monorepo root, built with the `defineConfig` exported by `@williamthorsen/nmr/taze`, declares the upgrade policy. [Dependency upgrades](docs/upgrades.md) covers what each invocation reaches, the policy and its ceilings, per-package maturity periods, and the request timeout.
243
+ `nmr upgrade` reports available dependency upgrades, and newer releases in the major line of the `.tool-versions` Node pin. A `taze.config.ts` at the monorepo root, built with the `defineConfig` exported by `@williamthorsen/nmr/taze`, declares the upgrade policy. [Dependency upgrades](docs/upgrades.md) covers what each invocation reaches, the policy and its ceilings, per-package maturity periods, the Node version pin, and the request timeout.
252
244
 
253
245
  ### Workspace introspection
254
246
 
@@ -323,7 +315,7 @@ rdy list --from npm:@williamthorsen/nmr # what nmr publishes
323
315
  - [Check-result cache](docs/check-cache.md): What is skipped, what a hit requires, and replaying, reading, and bypassing recorded runs
324
316
  - [Machine lock](docs/machine-lock.md): Which commands wait for one another, where the lock lives, and bypassing it
325
317
  - [Delegating to another machine](docs/delegate.md): The user config, `--delegate`, `--delegate-to`, `NMR_DELEGATE`, where a command runs, choosing among several hosts, what the host runs, and setting it up
326
- - [Dependency upgrades](docs/upgrades.md): `nmr upgrade`, the upgrade policy, `report-catalog`, `report-overrides`, and `nmr-taze`
318
+ - [Dependency upgrades](docs/upgrades.md): `nmr upgrade`, the upgrade policy, the Node version pin, `report-catalog`, `report-overrides`, and `nmr-taze`
327
319
  - [Standalone utilities](docs/utilities.md): `nmr-actionlint`, `nmr-clean`, `nmr-compile`, `nmr-fmt`, `nmr-vitest`, and `ensure-prepublish-hooks`
328
320
  - [Shared Vitest config](docs/vitest.md): Test tiers, test selections, the conventions check, and customization
329
321
  - [Shared Prettier config](docs/prettier.md): What it formats and how to customize it
@@ -2,8 +2,9 @@ import process from 'node:process';
2
2
  import { reportError } from '@williamthorsen/nmr-core';
3
3
  import { describeError } from '@williamthorsen/toolbelt.errors';
4
4
  import { runTaze } from "./commands/taze.js";
5
+ import { resolveBinStyles } from "./resolveBinStyles.js";
5
6
  try {
6
- process.exitCode = await runTaze(process.argv.slice(2));
7
+ process.exitCode = await runTaze(process.argv.slice(2), { stderrStyle: resolveBinStyles().stderr });
7
8
  }
8
9
  catch (error) {
9
10
  reportError(describeError(error));
@@ -0,0 +1,30 @@
1
+ import type { Writable } from 'node:stream';
2
+ import { type OutputStyle } from '@williamthorsen/nmr-core';
3
+ /** Node's release index, which lists every release with its version and its UTC release day. */
4
+ export declare const NODE_RELEASE_INDEX_URL = "https://nodejs.org/dist/index.json";
5
+ export interface CheckNodePinOptions {
6
+ /** Returns the parsed release index. Injected by tests; defaults to fetching `NODE_RELEASE_INDEX_URL`. */
7
+ fetchReleases?: () => Promise<unknown>;
8
+ /** Suppresses the report, but not the warnings. */
9
+ isSilent: boolean;
10
+ /** The current time, against which the soak is measured. Injected by tests. */
11
+ now?: Date;
12
+ shouldWrite: boolean;
13
+ /** Stream that the report and the warnings are written to. Defaults to `process.stderr`. */
14
+ stderr?: Writable;
15
+ style: OutputStyle;
16
+ }
17
+ /**
18
+ * Reports the newest release in the major line of the `nodejs` pin in `dir`'s `.tool-versions` that is newer than
19
+ * the pin and at least 24 hours old, and rewrites the pin to it when `shouldWrite` is set.
20
+ *
21
+ * Reports nothing when the file or its `nodejs` entry is absent, or when the pin is current. Every failure is a
22
+ * warning, so that the check never fails the upgrade that runs it.
23
+ */
24
+ export declare function checkNodePin(dir: string, options: CheckNodePinOptions): Promise<void>;
25
+ /**
26
+ * Returns the newest plain `X.Y.Z` release in `pin`'s major line that is newer than `pin` and whose UTC release day
27
+ * began at least two days before `now`, or `undefined` when none is. Ignores an entry that lacks a plain version or
28
+ * a `YYYY-MM-DD` date.
29
+ */
30
+ export declare function selectNodeUpgrade(pin: string, releases: readonly unknown[], now: Date): string | undefined;
@@ -0,0 +1,127 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import process from 'node:process';
4
+ import { formatGlyphLine } from '@williamthorsen/nmr-core';
5
+ import { describeError } from '@williamthorsen/toolbelt.errors';
6
+ import { NMR_GLYPHS } from "../glyphs.js";
7
+ import { isObject } from "../helpers/type-guards.js";
8
+ export const NODE_RELEASE_INDEX_URL = 'https://nodejs.org/dist/index.json';
9
+ const TOOL_VERSIONS_FILE = '.tool-versions';
10
+ const SOAK_MS = 2 * 24 * 60 * 60 * 1_000;
11
+ const FETCH_TIMEOUT_MS = 10_000;
12
+ const NODEJS_ENTRY_PATTERN = /^[ \t]*nodejs[ \t]+([^\s#]+)/m;
13
+ const PLAIN_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
14
+ const RELEASE_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
15
+ export async function checkNodePin(dir, options) {
16
+ const stderr = options.stderr ?? process.stderr;
17
+ function report(message) {
18
+ stderr.write(`${formatGlyphLine(NMR_GLYPHS, options.style, 'pin', message)}\n`);
19
+ }
20
+ function warn(detail) {
21
+ report(`WARN: Could not check the nodejs pin: ${detail}`);
22
+ }
23
+ const filePath = path.join(dir, TOOL_VERSIONS_FILE);
24
+ let content;
25
+ try {
26
+ content = fs.readFileSync(filePath, 'utf8');
27
+ }
28
+ catch (error) {
29
+ if (isMissingFileError(error))
30
+ return;
31
+ warn(describeError(error));
32
+ return;
33
+ }
34
+ const pin = findNodePin(content);
35
+ if (pin === undefined)
36
+ return;
37
+ if (!PLAIN_VERSION_PATTERN.test(pin.version)) {
38
+ if (!options.isSilent) {
39
+ report(`Skipped the nodejs pin '${pin.version}' in ${TOOL_VERSIONS_FILE}: Only an X.Y.Z version is checked.`);
40
+ }
41
+ return;
42
+ }
43
+ let releases;
44
+ try {
45
+ releases = await (options.fetchReleases ?? fetchNodeReleaseIndex)();
46
+ }
47
+ catch (error) {
48
+ warn(`${NODE_RELEASE_INDEX_URL} is unreachable (${describeError(error)})`);
49
+ return;
50
+ }
51
+ if (!Array.isArray(releases)) {
52
+ warn(`${NODE_RELEASE_INDEX_URL} did not return a list of releases`);
53
+ return;
54
+ }
55
+ const target = selectNodeUpgrade(pin.version, releases, options.now ?? new Date());
56
+ if (target === undefined)
57
+ return;
58
+ if (!options.shouldWrite) {
59
+ if (!options.isSilent) {
60
+ report(`nodejs ${pin.version} → ${target} is available. Run with --write to update ${TOOL_VERSIONS_FILE}.`);
61
+ }
62
+ return;
63
+ }
64
+ const updated = content.slice(0, pin.start) + target + content.slice(pin.start + pin.version.length);
65
+ try {
66
+ fs.writeFileSync(filePath, updated);
67
+ }
68
+ catch (error) {
69
+ warn(describeError(error));
70
+ return;
71
+ }
72
+ if (!options.isSilent)
73
+ report(`Updated nodejs ${pin.version} → ${target} in ${TOOL_VERSIONS_FILE}.`);
74
+ }
75
+ export function selectNodeUpgrade(pin, releases, now) {
76
+ const pinParts = parseVersion(pin);
77
+ let best;
78
+ let bestVersion;
79
+ for (const release of releases) {
80
+ if (!isObject(release))
81
+ continue;
82
+ const { date, version } = release;
83
+ if (typeof version !== 'string' || typeof date !== 'string' || !RELEASE_DATE_PATTERN.test(date))
84
+ continue;
85
+ const bareVersion = version.replace(/^v/, '');
86
+ if (!PLAIN_VERSION_PATTERN.test(bareVersion))
87
+ continue;
88
+ const parts = parseVersion(bareVersion);
89
+ if (parts[0] !== pinParts[0] || compareVersions(parts, pinParts) <= 0)
90
+ continue;
91
+ if (Date.parse(`${date}T00:00:00Z`) + SOAK_MS > now.getTime())
92
+ continue;
93
+ if (best !== undefined && compareVersions(parts, best) <= 0)
94
+ continue;
95
+ best = parts;
96
+ bestVersion = bareVersion;
97
+ }
98
+ return bestVersion;
99
+ }
100
+ function compareVersions(a, b) {
101
+ for (let index = 0; index < 3; index++) {
102
+ const difference = (a[index] ?? 0) - (b[index] ?? 0);
103
+ if (difference !== 0)
104
+ return difference;
105
+ }
106
+ return 0;
107
+ }
108
+ async function fetchNodeReleaseIndex() {
109
+ const response = await fetch(NODE_RELEASE_INDEX_URL, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
110
+ if (!response.ok)
111
+ throw new Error(`HTTP ${response.status}`);
112
+ const body = await response.json();
113
+ return body;
114
+ }
115
+ function findNodePin(content) {
116
+ const match = NODEJS_ENTRY_PATTERN.exec(content);
117
+ const version = match?.[1];
118
+ if (match === null || version === undefined)
119
+ return undefined;
120
+ return { start: match.index + match[0].length - version.length, version };
121
+ }
122
+ function isMissingFileError(error) {
123
+ return isObject(error) && error['code'] === 'ENOENT';
124
+ }
125
+ function parseVersion(version) {
126
+ return version.split('.').map(Number);
127
+ }
@@ -1,9 +1,13 @@
1
1
  import type { Writable } from 'node:stream';
2
+ import { type OutputStyle } from '@williamthorsen/nmr-core';
3
+ import { type CheckNodePinOptions } from './node-pin.js';
2
4
  export interface SpawnOutcome {
3
5
  status: number | null;
4
6
  error?: Error | undefined;
5
7
  }
6
8
  export interface RunTazeOptions {
9
+ /** Checks the `.tool-versions` Node pin in a directory. Injected by tests. */
10
+ checkNodePin?: (dir: string, options: CheckNodePinOptions) => Promise<void>;
7
11
  /** Returns the `packageMaturityPeriod` that the taze config governing a directory declares. Injected by tests. */
8
12
  loadConfig?: (cwd: string) => Promise<unknown>;
9
13
  /** Resolves the absolute path of taze's CLI entry. Injected by tests. */
@@ -12,6 +16,8 @@ export interface RunTazeOptions {
12
16
  spawn?: (nodePath: string, argv: string[]) => SpawnOutcome;
13
17
  /** Stream that error output and pass headings are written to. Defaults to `process.stderr`. */
14
18
  stderr?: Writable;
19
+ /** Style of `stderr`, in which the Node pin check reports. Defaults to `'plain'`. */
20
+ stderrStyle?: OutputStyle;
15
21
  }
16
22
  /**
17
23
  * Runs taze from nmr's own dependency tree, forwarding `args`, and returns its exit code.
@@ -24,6 +30,10 @@ export interface RunTazeOptions {
24
30
  * root `node_modules/.bin`. This launcher runs taze without that bin: pnpm links nmr's own bins into the
25
31
  * consumer root, and nmr resolves taze from the tree that it does control.
26
32
  *
33
+ * Before the passes, checks the `nodejs` pin in the `.tool-versions` of the directory from which taze resolves its
34
+ * config, reporting a newer release in the pin's major line and rewriting the pin under `--write`. The check only
35
+ * warns on failure, so it does not affect the exit code.
36
+ *
27
37
  * Every pass also receives a request timeout, unless `args` sets one. Neither other site can declare it: Because
28
38
  * an invocation's trailing arguments are appended to the resolved command string, the same flag in the `upgrade`
29
39
  * script would be doubled by `nmr upgrade --request-timeout ...`, and taze's parser collects the pair into an
@@ -5,12 +5,14 @@ import { fileURLToPath } from 'node:url';
5
5
  import { reportError } from '@williamthorsen/nmr-core';
6
6
  import { describeError } from '@williamthorsen/toolbelt.errors';
7
7
  import { loadPackageMaturityPeriod } from "./load-package-maturity-period.js";
8
+ import { checkNodePin as checkNodePinDefault } from "./node-pin.js";
8
9
  import { isSilent, readFlag } from "./taze-args.js";
9
10
  import { planTazePasses } from "./taze-passes.js";
10
11
  const TAZE_CLI_SPECIFIER = 'taze/cli';
11
12
  const REQUEST_TIMEOUT_FLAG = '--request-timeout';
12
13
  const REQUEST_TIMEOUT_FLAGS = [REQUEST_TIMEOUT_FLAG, '--requestTimeout'];
13
14
  const REQUEST_TIMEOUT_MS = 30_000;
15
+ const NODE_PIN_SKIP_FLAGS = ['--no-node-version', '--help', '-h', '--version', '-v'];
14
16
  export async function runTaze(args, options = {}) {
15
17
  const stderr = options.stderr ?? process.stderr;
16
18
  const resolveCliPath = options.resolveCliPath ?? resolveTazeCliPath;
@@ -33,6 +35,19 @@ export async function runTaze(args, options = {}) {
33
35
  reportError(`Could not read the taze config: ${describeError(error)}`, stderr);
34
36
  return 1;
35
37
  }
38
+ if (readFlag(args, NODE_PIN_SKIP_FLAGS) === undefined) {
39
+ try {
40
+ await (options.checkNodePin ?? checkNodePinDefault)(resolveConfigDir(args), {
41
+ isSilent: isSilent(args),
42
+ shouldWrite: readFlag(args, ['--write', '-w']) !== undefined,
43
+ stderr,
44
+ style: options.stderrStyle ?? 'plain',
45
+ });
46
+ }
47
+ catch (error) {
48
+ reportError(`Could not check the nodejs pin: ${describeError(error)}`, stderr);
49
+ }
50
+ }
36
51
  const showHeadings = !isSilent(args);
37
52
  let exitCode = 0;
38
53
  for (const pass of passes) {
@@ -5,5 +5,5 @@
5
5
  * `noop` is a neutral circle: Nothing ran because the repo asked for nothing to run, which is neither a
6
6
  * failure nor a block.
7
7
  */
8
- export declare const NMR_GLYPHS: Readonly<Record<"plain" | "rich", Readonly<Record<"overrides" | "clean" | "host" | "catalog" | "noop" | "package" | "recording", import("@williamthorsen/toolbelt.terminal/candidate").Glyph>>>>;
8
+ export declare const NMR_GLYPHS: Readonly<Record<"plain" | "rich", Readonly<Record<"overrides" | "clean" | "host" | "catalog" | "noop" | "package" | "pin" | "recording", import("@williamthorsen/toolbelt.terminal/candidate").Glyph>>>>;
9
9
  export type NmrGlyphName = keyof (typeof NMR_GLYPHS)['rich'];
@@ -6,5 +6,6 @@ export const NMR_GLYPHS = defineGlyphSet({
6
6
  noop: { plain: 'NOOP', rich: '\u{26AA}' },
7
7
  overrides: { plain: '', rich: '\u{1F512}' },
8
8
  package: { plain: '', rich: '\u{1F4E6}' },
9
+ pin: { plain: '', rich: '\u{1F4CC}' },
9
10
  recording: { plain: '', rich: '\u{1F4BE}' },
10
11
  });
package/docs/upgrades.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Dependency upgrades
2
2
 
3
- What `nmr upgrade` covers from each scope, how to declare the upgrade policy and its ceilings, and the subcommands and binary that the upgrade chains run.
3
+ What `nmr upgrade` covers from each scope, how to declare the upgrade policy and its ceilings, how it checks the Node version pin, and the subcommands and binary that the upgrade chains run.
4
4
 
5
5
  `nmr upgrade` reports the dependency upgrades available to the repo. What it covers depends on where it runs:
6
6
 
@@ -81,6 +81,23 @@ nmr upgrade --maturity-period 0
81
81
 
82
82
  Every other argument reaches every run, and `--include` and `--exclude` narrow each run as they narrow a single one. Because each run reports separately, `--json` prints one document per run, `--interactive` opens one selection per run, and `--install` installs after each run.
83
83
 
84
+ ## Node version pin
85
+
86
+ Before taze runs, `nmr upgrade` checks the `nodejs` entry of `.tool-versions` in the directory from which taze reads its config: the monorepo root for `upgrade` and `root:upgrade` run there, and a package's own directory for a package-scoped run, which checks nothing unless the package has its own `.tool-versions`. It compares the pin with [Node's release index](https://nodejs.org/dist/index.json) and reports the newest release in the pin's major line:
87
+
88
+ ```text
89
+ nodejs 24.22.1 → 24.22.3 is available. Run with --write to update .tool-versions.
90
+ ```
91
+
92
+ `--write` rewrites that version and leaves the rest of the file unchanged. A newer major line is never proposed, whatever range mode the invocation passes: Changing majors is left to an edit of `.tool-versions`.
93
+
94
+ - A release is proposed once it is at least 24 hours old. The index dates a release by UTC day alone, so a release dated D is proposed from the start of D+2, UTC.
95
+ - Only a plain `X.Y.Z` pin is checked, and only the first version on the `nodejs` line. Any other pin, such as `lts`, is skipped with a note.
96
+ - A repo without `.tool-versions`, or without a `nodejs` entry in it, is skipped silently.
97
+ - `--no-node-version`, which turns off taze's own checks of `.node-version` and `.nvmrc`, turns off this check too.
98
+ - An unreachable index, or a file that cannot be read or written, produces a warning and does not fail the upgrade.
99
+ - `--silent` suppresses the report but not the warnings. The report goes to stderr, so `--json` output is unaffected.
100
+
84
101
  ## Request timeout
85
102
 
86
103
  Every `upgrade` script calls the upgrade tool with a 30-second request timeout, in place of the tool's own 5 seconds.
@@ -131,7 +148,7 @@ nmr report-overrides
131
148
 
132
149
  ## `nmr-taze`
133
150
 
134
- Run the [taze](https://github.com/antfu-collective/taze) dependency-upgrade tool, forwarding every argument to it and adding a 30-second request timeout, unless the invocation already includes one. When the taze config declares [per-package maturity periods](#per-package-maturity-periods), it runs taze once per period, adding to each run the filters and the period that confine it. Every `upgrade` chain ends with it, `root:upgrade` included; see [dependency upgrades](#dependency-upgrades) for the workflow, and [request timeout](#request-timeout) for why the timeout is set here.
151
+ Run the [taze](https://github.com/antfu-collective/taze) dependency-upgrade tool, forwarding every argument to it and adding a 30-second request timeout, unless the invocation already includes one. When the taze config declares [per-package maturity periods](#per-package-maturity-periods), it runs taze once per period, adding to each run the filters and the period that confine it. Before the first run, it checks the [Node version pin](#node-version-pin). Every `upgrade` chain ends with it, `root:upgrade` included; see [dependency upgrades](#dependency-upgrades) for the workflow, and [request timeout](#request-timeout) for why the timeout is set here.
135
152
 
136
153
  Under pnpm's isolated `node_modules`, a transitive package's binary is absent from the consuming repo's `node_modules/.bin`, so a repo that depends on nmr cannot run `taze` directly. `nmr-taze` can, because nmr is a direct dependency, and it resolves the tool from the tree that nmr controls.
137
154
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/nmr",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "private": false,
5
5
  "description": "Context-aware script runner for pnpm monorepos",
6
6
  "keywords": [
@@ -82,7 +82,7 @@
82
82
  "@williamthorsen/nmr-core": "0.14.0",
83
83
  "@williamthorsen/toolbelt.errors": "0.9.0",
84
84
  "@williamthorsen/toolbelt.filesystem": "0.10.0",
85
- "glob": "13.0.6",
85
+ "glob": "14.0.3",
86
86
  "prettier": "3.9.10",
87
87
  "prettier-plugin-sh": "0.20.2",
88
88
  "taze": "21.3.0",
@@ -90,12 +90,12 @@
90
90
  },
91
91
  "devDependencies": {
92
92
  "@williamthorsen/toolbelt.testing": "0.8.0",
93
- "@williamthorsen/toolbelt.vitest": "0.13.0",
93
+ "@williamthorsen/toolbelt.vitest": "0.13.1",
94
94
  "esbuild": "0.28.2",
95
95
  "readyup": "0.44.0",
96
96
  "tinyglobby": "0.2.17",
97
97
  "typescript": "6.0.3",
98
- "vite": "8.3.4",
98
+ "vite": "8.3.5",
99
99
  "vitest": "5.0.5",
100
100
  "yaml": "2.9.1"
101
101
  },