@williamthorsen/nmr 0.54.0 → 0.55.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 +65 -0
- package/.readyup/kits/default.js +25 -10
- package/.readyup/manifest.json +26 -20
- package/CHANGELOG.md +22 -0
- package/README.md +10 -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-shellcheck.js +15 -0
- package/bin/nmr-taze.js +6 -2
- package/bin/nmr-vitest.js +6 -2
- package/bin/nmr.js +6 -2
- package/dist/esm/check-cache.js +1 -0
- 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-shellcheck.d.ts +1 -0
- package/dist/esm/cli-shellcheck.js +11 -0
- package/dist/esm/cli-taze.js +2 -3
- 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.js +1 -1
- package/dist/esm/commands/report-overrides.js +1 -2
- package/dist/esm/commands/rewrite-specifiers.js +3 -2
- package/dist/esm/commands/shellcheck.d.ts +25 -0
- package/dist/esm/commands/shellcheck.js +41 -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/default-scripts.js +1 -0
- 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/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/check-cache.md +1 -1
- package/docs/delegate.md +7 -5
- package/docs/scripts.md +4 -0
- package/docs/utilities.md +9 -1
- package/package.json +6 -5
- 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
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/check-cache.md
CHANGED
|
@@ -10,7 +10,7 @@ The cache is a working-tree cache, not a build cache: It answers "has this exact
|
|
|
10
10
|
|
|
11
11
|
## What is cached
|
|
12
12
|
|
|
13
|
-
Cacheable by default: `check`, `check:strict`, `ci`, `fix:check`, `fmt:check`, `lint:check`, `lint:strict`, `lint:workflows`, `test`, `test:coverage`, `test:tool`, `test:unit`, `typecheck`, and the `root:` variants `root:check`, `root:lint:check`, `root:lint:strict`, `root:test`, `root:test:tool`, `root:test:unit`, and `root:typecheck`.
|
|
13
|
+
Cacheable by default: `check`, `check:strict`, `ci`, `fix:check`, `fmt:check`, `lint:check`, `lint:shell`, `lint:strict`, `lint:workflows`, `test`, `test:coverage`, `test:tool`, `test:unit`, `typecheck`, and the `root:` variants `root:check`, `root:lint:check`, `root:lint:strict`, `root:test`, `root:test:tool`, `root:test:unit`, and `root:typecheck`.
|
|
14
14
|
|
|
15
15
|
Everything else runs every time:
|
|
16
16
|
|
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/scripts.md
CHANGED
|
@@ -202,6 +202,10 @@ The result does not depend on what the machine has installed. nmr pins actionlin
|
|
|
202
202
|
|
|
203
203
|
The supported platforms are macOS and Linux on x64 and arm64; on any other, the command fails and names the platform. actionlint reads its own configuration from `.github/actionlint.yaml`, through which a repo can ignore a finding.
|
|
204
204
|
|
|
205
|
+
### Shell lint
|
|
206
|
+
|
|
207
|
+
`lint:shell`, a root command, lints every `*.sh` file that git tracks under the monorepo root with [shellcheck](https://www.shellcheck.net/) and fails on any finding. A repo that does not track any `*.sh` file passes without downloading shellcheck, and a tracked file that is missing from the working tree is skipped. The command runs `nmr-shellcheck`, whose trailing arguments replace the file list, so `nmr lint:shell scripts/release.sh` lints that file alone. It uses the shellcheck that [Workflow lint](#workflow-lint) pins, downloads, and caches. Neither `check`, `check:strict`, nor `root:check` runs it.
|
|
208
|
+
|
|
205
209
|
### Root-only
|
|
206
210
|
|
|
207
211
|
The `root:`-prefixed commands operate on root-level code only (not workspace packages).
|
package/docs/utilities.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Standalone utilities
|
|
2
2
|
|
|
3
|
-
The binaries behind nmr's default `clean`, `compile`, `fmt`, `lint:workflows`, and package `test` commands, and a check for the `prepublishOnly` hooks of publishable packages.
|
|
3
|
+
The binaries behind nmr's default `clean`, `compile`, `fmt`, `lint:shell`, `lint:workflows`, and package `test` commands, and a check for the `prepublishOnly` hooks of publishable packages.
|
|
4
4
|
|
|
5
5
|
## `nmr-actionlint`
|
|
6
6
|
|
|
@@ -74,6 +74,14 @@ Pass `--no-cache`, or set `NMR_NO_CACHE=1`, to run uncached. `nmr --no-cache fmt
|
|
|
74
74
|
nmr-fmt --check --no-cache
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
## `nmr-shellcheck`
|
|
78
|
+
|
|
79
|
+
Lint the monorepo's tracked `*.sh` files with the pinned shellcheck binary, passing when the repo does not track any. Any arguments replace the file list and reach shellcheck as given. This is the default root `lint:shell` script; [Shell lint](scripts.md#shell-lint) covers its behavior.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
nmr-shellcheck
|
|
83
|
+
```
|
|
84
|
+
|
|
77
85
|
## `nmr-vitest`
|
|
78
86
|
|
|
79
87
|
Run Vitest from a package directory with the monorepo's shared config. Vitest 5 does not search parent directories for a config, so `nmr-vitest` names it: When the working directory does not contain a `vitest.config.*` or `vite.config.*` of its own, it passes `--config` naming the `vitest.config.*` at the monorepo root, the directory that contains `pnpm-workspace.yaml`. It does not search the directories in between. A package with a config of its own, a monorepo root without a `vitest.config.*`, and a directory outside any monorepo all run Vitest without the flag. An explicit `--config` or `-c` ahead of any `--` passes through unchanged, and so does every other argument. This is the command behind every package-scope `test*` script.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/nmr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Context-aware script runner for pnpm monorepos",
|
|
6
6
|
"keywords": [
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"nmr-fmt": "bin/nmr-fmt.js",
|
|
65
65
|
"nmr-report-catalog": "bin/nmr-report-catalog.js",
|
|
66
66
|
"nmr-report-overrides": "bin/nmr-report-overrides.js",
|
|
67
|
+
"nmr-shellcheck": "bin/nmr-shellcheck.js",
|
|
67
68
|
"nmr-taze": "bin/nmr-taze.js",
|
|
68
69
|
"nmr-vitest": "bin/nmr-vitest.js"
|
|
69
70
|
},
|
|
@@ -79,7 +80,7 @@
|
|
|
79
80
|
"!docs/*.v*.md"
|
|
80
81
|
],
|
|
81
82
|
"dependencies": {
|
|
82
|
-
"@williamthorsen/nmr-core": "0.
|
|
83
|
+
"@williamthorsen/nmr-core": "0.15.1",
|
|
83
84
|
"@williamthorsen/toolbelt.errors": "0.9.0",
|
|
84
85
|
"@williamthorsen/toolbelt.filesystem": "0.10.0",
|
|
85
86
|
"glob": "14.0.3",
|
|
@@ -92,11 +93,11 @@
|
|
|
92
93
|
"@williamthorsen/toolbelt.testing": "0.8.0",
|
|
93
94
|
"@williamthorsen/toolbelt.vitest": "0.13.1",
|
|
94
95
|
"esbuild": "0.28.2",
|
|
95
|
-
"readyup": "0.
|
|
96
|
+
"readyup": "0.45.0",
|
|
96
97
|
"tinyglobby": "0.2.17",
|
|
97
98
|
"typescript": "6.0.3",
|
|
98
|
-
"vite": "8.3.
|
|
99
|
-
"vitest": "5.0
|
|
99
|
+
"vite": "8.3.6",
|
|
100
|
+
"vitest": "5.1.0",
|
|
100
101
|
"yaml": "2.9.1"
|
|
101
102
|
},
|
|
102
103
|
"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
|
-
}
|