@williamthorsen/nmr 0.53.1 → 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.
- package/.meta/changelog.json +17 -0
- package/.readyup/kits/default.js +1 -1
- package/.readyup/manifest.json +4 -4
- package/CHANGELOG.md +6 -0
- package/README.md +9 -3
- package/dist/esm/cli-taze.js +2 -1
- package/dist/esm/commands/node-pin.d.ts +30 -0
- package/dist/esm/commands/node-pin.js +127 -0
- package/dist/esm/commands/taze.d.ts +10 -0
- package/dist/esm/commands/taze.js +15 -0
- package/dist/esm/glyphs.d.ts +1 -1
- package/dist/esm/glyphs.js +1 -0
- package/docs/upgrades.md +19 -2
- package/package.json +3 -3
package/.meta/changelog.json
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
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
|
+
},
|
|
2
19
|
{
|
|
3
20
|
"version": "0.53.1",
|
|
4
21
|
"date": "2026-10-28",
|
package/.readyup/kits/default.js
CHANGED
|
@@ -9190,7 +9190,7 @@ function hasSupportedStrictLintVersion() {
|
|
|
9190
9190
|
});
|
|
9191
9191
|
}
|
|
9192
9192
|
function getMinVersion() {
|
|
9193
|
-
const pickedFields = { "version": "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
|
}
|
package/.readyup/manifest.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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": "
|
|
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": "
|
|
330
|
+
"targetHash": "5689b2d3"
|
|
331
331
|
}
|
|
332
332
|
]
|
|
333
333
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
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
|
+
|
|
5
11
|
## 0.53.1 — 2026-10-28
|
|
6
12
|
|
|
7
13
|
### ♻️ Refactoring
|
package/README.md
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
|
|
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
|
-
<!-- section:release-notes
|
|
7
|
+
<!-- section:release-notes -->
|
|
8
|
+
## Release notes — v0.54.0 (2026-10-31)
|
|
9
|
+
|
|
10
|
+
### 🎉 Features
|
|
11
|
+
|
|
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)
|
|
13
|
+
<!-- /section:release-notes -->
|
|
8
14
|
|
|
9
15
|
## Installation
|
|
10
16
|
|
|
@@ -234,7 +240,7 @@ export default definePrettierConfig();
|
|
|
234
240
|
|
|
235
241
|
### Dependency upgrades
|
|
236
242
|
|
|
237
|
-
`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.
|
|
238
244
|
|
|
239
245
|
### Workspace introspection
|
|
240
246
|
|
|
@@ -309,7 +315,7 @@ rdy list --from npm:@williamthorsen/nmr # what nmr publishes
|
|
|
309
315
|
- [Check-result cache](docs/check-cache.md): What is skipped, what a hit requires, and replaying, reading, and bypassing recorded runs
|
|
310
316
|
- [Machine lock](docs/machine-lock.md): Which commands wait for one another, where the lock lives, and bypassing it
|
|
311
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
|
|
312
|
-
- [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`
|
|
313
319
|
- [Standalone utilities](docs/utilities.md): `nmr-actionlint`, `nmr-clean`, `nmr-compile`, `nmr-fmt`, `nmr-vitest`, and `ensure-prepublish-hooks`
|
|
314
320
|
- [Shared Vitest config](docs/vitest.md): Test tiers, test selections, the conventions check, and customization
|
|
315
321
|
- [Shared Prettier config](docs/prettier.md): What it formats and how to customize it
|
package/dist/esm/cli-taze.js
CHANGED
|
@@ -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) {
|
package/dist/esm/glyphs.d.ts
CHANGED
|
@@ -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'];
|
package/dist/esm/glyphs.js
CHANGED
|
@@ -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.
|
|
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": "14.0.
|
|
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,7 +90,7 @@
|
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@williamthorsen/toolbelt.testing": "0.8.0",
|
|
93
|
-
"@williamthorsen/toolbelt.vitest": "0.13.
|
|
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",
|