@williamthorsen/nmr 0.53.1 → 0.54.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/.meta/changelog.json +49 -0
- package/.readyup/kits/default.js +10 -10
- package/.readyup/manifest.json +19 -19
- package/CHANGELOG.md +15 -0
- package/README.md +12 -3
- package/bin/ensure-prepublish-hooks.js +6 -2
- package/bin/nmr-actionlint.js +6 -2
- package/bin/nmr-clean.js +6 -2
- package/bin/nmr-compile.js +6 -2
- package/bin/nmr-fmt.js +6 -2
- package/bin/nmr-report-catalog.js +6 -2
- package/bin/nmr-report-overrides.js +6 -2
- package/bin/nmr-taze.js +6 -2
- package/bin/nmr-vitest.js +6 -2
- package/bin/nmr.js +6 -2
- package/dist/esm/child-process.js +1 -1
- package/dist/esm/cli-actionlint.js +2 -3
- package/dist/esm/cli-build.js +2 -3
- package/dist/esm/cli-clean.js +2 -3
- package/dist/esm/cli-ensure-prepublish-hooks.js +2 -3
- package/dist/esm/cli-fmt.js +8 -1
- package/dist/esm/cli-report-catalog.js +2 -3
- package/dist/esm/cli-report-overrides.js +2 -3
- package/dist/esm/cli-taze.js +4 -4
- package/dist/esm/cli-vitest.js +8 -1
- package/dist/esm/cli.js +2 -2
- package/dist/esm/commands/actionlint.js +2 -1
- package/dist/esm/commands/build.js +9 -6
- package/dist/esm/commands/clean.js +2 -2
- package/dist/esm/commands/compiler.js +3 -3
- package/dist/esm/commands/ensure-prepublish-hooks.js +10 -3
- package/dist/esm/commands/node-pin.d.ts +30 -0
- package/dist/esm/commands/node-pin.js +127 -0
- package/dist/esm/commands/report-overrides.js +1 -2
- package/dist/esm/commands/rewrite-specifiers.js +3 -2
- package/dist/esm/commands/taze.d.ts +10 -0
- package/dist/esm/commands/taze.js +15 -0
- package/dist/esm/commands/tsconfig-chain.js +4 -3
- package/dist/esm/commands/workflow-tools.js +10 -8
- package/dist/esm/config-check-cache.js +1 -1
- package/dist/esm/config-validation.js +1 -1
- package/dist/esm/config.js +1 -1
- package/dist/esm/crossing-diagnostics.js +1 -2
- package/dist/esm/delegation-target.d.ts +5 -4
- package/dist/esm/delegation-target.js +15 -0
- package/dist/esm/entry-chain.js +1 -1
- package/dist/esm/glyphs.d.ts +1 -1
- package/dist/esm/glyphs.js +1 -0
- package/dist/esm/helpers/deriveExcerpt.js +1 -1
- package/dist/esm/helpers/duration.js +1 -1
- package/dist/esm/helpers/package-json.js +1 -1
- package/dist/esm/machine-lock.js +1 -1
- package/dist/esm/pass-manifest.js +2 -3
- package/dist/esm/remote-host-session.js +2 -2
- package/dist/esm/resolveBinStyles.js +1 -2
- package/dist/esm/user-config.js +1 -1
- package/dist/esm/vitest-source-resolution.js +1 -1
- package/dist/esm/workspace.js +1 -1
- package/docs/delegate.md +7 -5
- package/docs/upgrades.md +19 -2
- package/package.json +6 -6
- package/dist/esm/UserError.d.ts +0 -8
- package/dist/esm/UserError.js +0 -6
- package/dist/esm/reportCliFailure.d.ts +0 -9
- package/dist/esm/reportCliFailure.js +0 -11
|
@@ -17,7 +17,7 @@ export function formatSaving(durationMs) {
|
|
|
17
17
|
return `saved ~${formatDuration(durationMs)}`;
|
|
18
18
|
}
|
|
19
19
|
const MILLISECONDS_PER_MINUTE = 60_000;
|
|
20
|
-
const MILLISECONDS_PER_SECOND =
|
|
20
|
+
const MILLISECONDS_PER_SECOND = 1000;
|
|
21
21
|
const MINUTES_PER_HOUR = 60;
|
|
22
22
|
const SECONDS_PER_MINUTE = 60;
|
|
23
23
|
function joinUnits(major, minor, minorUnit) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
3
4
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
|
-
import { UserError } from "../UserError.js";
|
|
5
5
|
import { isMonorepoRoot } from "../workspace.js";
|
|
6
6
|
import { CONFIG_RELATIVE_PATH } from "./config-path.js";
|
|
7
7
|
import { readStringValues } from "./readStringValues.js";
|
package/dist/esm/machine-lock.js
CHANGED
|
@@ -28,7 +28,7 @@ export const LOCK_DIR_ENV_VAR = 'NMR_LOCK_DIR';
|
|
|
28
28
|
export const LOCK_FILE = 'heavy.lock';
|
|
29
29
|
export const LOCK_TOKEN_ENV_VAR = 'NMR_MACHINE_LOCK_TOKEN';
|
|
30
30
|
export const NO_LOCK_ENV_VAR = 'NMR_NO_LOCK';
|
|
31
|
-
export const POLL_INTERVAL_MS =
|
|
31
|
+
export const POLL_INTERVAL_MS = 1000;
|
|
32
32
|
export const STALE_AFTER_MS = 60_000;
|
|
33
33
|
export async function acquireMachineLock(options) {
|
|
34
34
|
const { command, dir, env, stderr } = options;
|
|
@@ -49,7 +49,7 @@ export async function writePassManifestEntry(options) {
|
|
|
49
49
|
writeDebugNote(`could not report ${command} to the delegating machine: ${describeError(error)}`, env, stderr);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
const MAX_MANIFEST_ENTRIES =
|
|
52
|
+
const MAX_MANIFEST_ENTRIES = 1000;
|
|
53
53
|
function findRejection(entry, options) {
|
|
54
54
|
if (!isPassManifestEntry(entry)) {
|
|
55
55
|
return 'the entry is malformed';
|
|
@@ -84,6 +84,5 @@ function isRelativeScope(scope) {
|
|
|
84
84
|
if (scope === '.') {
|
|
85
85
|
return true;
|
|
86
86
|
}
|
|
87
|
-
return
|
|
88
|
-
scope.split('/').every((segment) => segment !== '' && segment !== '.' && segment !== '..'));
|
|
87
|
+
return !path.posix.isAbsolute(scope) && scope.split('/').every((segment) => !['', '.', '..'].includes(segment));
|
|
89
88
|
}
|
|
@@ -8,12 +8,12 @@ export function runHostSession(params) {
|
|
|
8
8
|
const LOCK_PATH = params.lockPath;
|
|
9
9
|
const RECLAIM_GUARD_PATH = `${params.lockPath}.reclaim`;
|
|
10
10
|
const HEARTBEAT_INTERVAL_MS = 10_000;
|
|
11
|
-
const POLL_INTERVAL_MS =
|
|
11
|
+
const POLL_INTERVAL_MS = 1000;
|
|
12
12
|
const RECLAIM_GUARD_STALE_AFTER_MS = 10_000;
|
|
13
13
|
const OUTPUT_LIMIT_BYTES = 65_536;
|
|
14
14
|
const COMMAND_NOT_FOUND_EXIT_CODE = 127;
|
|
15
15
|
const DELEGATION_FAILURE_EXIT_CODE = 255;
|
|
16
|
-
const STOP_GRACE_MS =
|
|
16
|
+
const STOP_GRACE_MS = 5000;
|
|
17
17
|
const NODE_MODULES_DIR = path.join(params.rootDir, 'node_modules');
|
|
18
18
|
const BINS_RECORD_PATH = path.join(NODE_MODULES_DIR, '.nmr-delegate-bins');
|
|
19
19
|
const token = crypto.randomUUID();
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
|
-
import { describeInvalidOutputStyle } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { describeInvalidOutputStyle, UserError } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { resolveOutputStyles } from "./output-style.js";
|
|
4
|
-
import { UserError } from "./UserError.js";
|
|
5
4
|
export function resolveBinStyles() {
|
|
6
5
|
const { invalid: invalidStyle, styles } = resolveOutputStyles({
|
|
7
6
|
env: process.env,
|
package/dist/esm/user-config.js
CHANGED
|
@@ -9,9 +9,9 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
import { existsSync } from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { pathToFileURL } from 'node:url';
|
|
12
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
12
13
|
import { assertRecognizedKeys } from "./config-validation.js";
|
|
13
14
|
import { isObject } from "./helpers/type-guards.js";
|
|
14
|
-
import { UserError } from "./UserError.js";
|
|
15
15
|
export const DELEGATE_WHEN_VALUES = ['agent', 'always'];
|
|
16
16
|
export function formatDisplayPath(filePath, env) {
|
|
17
17
|
const home = env['HOME'];
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
2
2
|
import { isBuiltin } from 'node:module';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
4
5
|
import { findDirectoryChainMatch } from '@williamthorsen/toolbelt.filesystem';
|
|
5
6
|
import { parsePackageJson } from "./helpers/package-json.js";
|
|
6
7
|
import { isObject } from "./helpers/type-guards.js";
|
|
7
|
-
import { UserError } from "./UserError.js";
|
|
8
8
|
const SHARED_CONDITIONS = ['module', 'import', 'development', 'default'];
|
|
9
9
|
const CLIENT_CONDITION = 'browser';
|
|
10
10
|
const SERVER_CONDITION = 'node';
|
package/dist/esm/workspace.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { findMonorepoRoot as findMonorepoRootOrNothing, resolveWorkspace } from '@williamthorsen/nmr-core/workspace';
|
|
2
|
-
import { UserError } from "./UserError.js";
|
|
3
2
|
const WORKSPACE_MANIFEST = 'pnpm-workspace.yaml';
|
|
4
3
|
export { isMonorepoRoot, readWorkspaceOverrides, readWorkspacePackageNames, resolveWorkspace, } from '@williamthorsen/nmr-core/workspace';
|
|
4
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
5
5
|
export function findMonorepoRoot(startDir) {
|
|
6
6
|
const monorepoRoot = findMonorepoRootOrNothing(startDir);
|
|
7
7
|
if (monorepoRoot === undefined) {
|
package/docs/delegate.md
CHANGED
|
@@ -46,13 +46,15 @@ The first rule that applies decides where a command runs:
|
|
|
46
46
|
| 1 | `--no-delegate` | Here |
|
|
47
47
|
| 2 | `--delegate` or `--delegate-to` | On a host; a command that cannot be delegated, or a tree that cannot be snapshotted, is refused |
|
|
48
48
|
| 3 | The repo config declares `delegate: false` | Here |
|
|
49
|
-
| 4 | `
|
|
50
|
-
| 5 | `NMR_DELEGATE`
|
|
51
|
-
| 6 |
|
|
52
|
-
| 7 |
|
|
49
|
+
| 4 | `npm_lifecycle_event` names a package install hook, such as `postinstall` | Here |
|
|
50
|
+
| 5 | `NMR_DELEGATE` names hosts | On a host; a command that cannot be delegated runs here, and a tree that cannot be snapshotted, here with one line |
|
|
51
|
+
| 6 | `NMR_DELEGATE` is set but does not name a host | Here |
|
|
52
|
+
| 7 | The user config's `when` matches, `CI` is unset, and the invocation is not `-F` or `-R` | On a host, as for rule 5; here with one line when no host answers |
|
|
53
|
+
| 8 | Otherwise | Here |
|
|
53
54
|
|
|
54
|
-
- **A step and a `-F` or `-R` child run where their parent runs.** nmr gives every process that it starts an empty `NMR_DELEGATE`, which rule
|
|
55
|
+
- **A step and a `-F` or `-R` child run where their parent runs.** nmr gives every process that it starts an empty `NMR_DELEGATE`, which rule 6 reads as "here"; the run on a host already passes `--no-delegate`, so a host does not apply its own user config. A git hook that an agent's command starts is an agent run.
|
|
55
56
|
- **A repo whose checks must run on the machine that has the tree** declares `delegate: false` in `.config/nmr.config.ts`, the only value that the repo config accepts for the key. It outranks `NMR_DELEGATE` and the user config, but not a flag.
|
|
57
|
+
- **A command in a package install hook runs here unless a flag asks otherwise.** During `pnpm add`, a hook such as `postinstall` sees a manifest that its lockfile does not match yet, and the host's frozen install of that tree fails. A command in any other `package.json` script, such as `test`, delegates as usual.
|
|
56
58
|
- **`NMR_DEBUG=1` names what sent a command to a host**, such as `delegating ci per ~/.config/nmr/config.ts (when: agent)`, `per NMR_DELEGATE`, or `per --delegate-to`.
|
|
57
59
|
|
|
58
60
|
### Choosing among several hosts
|
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.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Context-aware script runner for pnpm monorepos",
|
|
6
6
|
"keywords": [
|
|
@@ -79,10 +79,10 @@
|
|
|
79
79
|
"!docs/*.v*.md"
|
|
80
80
|
],
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@williamthorsen/nmr-core": "0.
|
|
82
|
+
"@williamthorsen/nmr-core": "0.15.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,13 +90,13 @@
|
|
|
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
|
-
"readyup": "0.
|
|
95
|
+
"readyup": "0.45.0",
|
|
96
96
|
"tinyglobby": "0.2.17",
|
|
97
97
|
"typescript": "6.0.3",
|
|
98
98
|
"vite": "8.3.5",
|
|
99
|
-
"vitest": "5.0
|
|
99
|
+
"vitest": "5.1.0",
|
|
100
100
|
"yaml": "2.9.1"
|
|
101
101
|
},
|
|
102
102
|
"peerDependencies": {
|
package/dist/esm/UserError.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A failure in what the user declared -- a config file, a `package.json` -- rather than a fault in nmr.
|
|
3
|
-
*
|
|
4
|
-
* The CLI boundary prints one of these as its message alone.
|
|
5
|
-
*/
|
|
6
|
-
export declare class UserError extends Error {
|
|
7
|
-
constructor(message: string);
|
|
8
|
-
}
|
package/dist/esm/UserError.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { Writable } from 'node:stream';
|
|
2
|
-
/**
|
|
3
|
-
* Writes a failure to the CLI's error stream: as its message alone when the user's own declaration caused it,
|
|
4
|
-
* and with its stack otherwise.
|
|
5
|
-
*
|
|
6
|
-
* A stack records where nmr noticed a problem, which says nothing about the file that a user has to edit; for
|
|
7
|
-
* nmr's own fault it is where a report has to start.
|
|
8
|
-
*/
|
|
9
|
-
export declare function reportCliFailure(error: unknown, stderr: Writable): void;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { reportError } from '@williamthorsen/nmr-core';
|
|
2
|
-
import { describeError, isError } from '@williamthorsen/toolbelt.errors';
|
|
3
|
-
import { UserError } from "./UserError.js";
|
|
4
|
-
export function reportCliFailure(error, stderr) {
|
|
5
|
-
if (error instanceof UserError) {
|
|
6
|
-
reportError(error.message, stderr);
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
const detail = isError(error) && error.stack !== undefined ? error.stack : describeError(error);
|
|
10
|
-
stderr.write(`${detail}\n`);
|
|
11
|
-
}
|