@williamthorsen/nmr 1.0.0 → 2.0.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 +30 -0
- package/.readyup/kits/default.js +2 -18
- package/.readyup/manifest.json +7 -12
- package/CHANGELOG.md +12 -0
- package/README.md +9 -3
- package/dist/esm/check-cache-gate.d.ts +0 -3
- package/dist/esm/check-cache-gate.js +1 -5
- package/dist/esm/commands/clean.js +3 -4
- package/dist/esm/config-validation.js +1 -14
- package/dist/esm/config.d.ts +1 -1
- package/dist/esm/crossing-diagnostics.d.ts +1 -1
- package/dist/esm/resolver.d.ts +0 -12
- package/dist/esm/resolver.js +0 -35
- package/dist/esm/run-chain.d.ts +1 -1
- package/dist/esm/run-chain.js +1 -5
- package/dist/esm/types.d.ts +0 -1
- package/docs/check-cache.md +0 -1
- package/docs/scripts.md +17 -12
- package/package.json +2 -2
package/.meta/changelog.json
CHANGED
|
@@ -1,4 +1,34 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"date": "2026-11-10",
|
|
5
|
+
"sections": [
|
|
6
|
+
{
|
|
7
|
+
"title": "🪦 Removed",
|
|
8
|
+
"audience": "all",
|
|
9
|
+
"items": [
|
|
10
|
+
{
|
|
11
|
+
"description": "Removes the `devBin` root-config field of `@williamthorsen/nmr`, which replaced the first token of a resolved command with a command that ran a bin from source. (#1269)",
|
|
12
|
+
"breaking": true,
|
|
13
|
+
"migration": "Replace each `devBin` entry by overriding the script that runs the bin in `rootScripts` or `workspaceScripts` with `node <path to the bin's source>`, and build an absolute path for a workspace script, because nmr no longer resolves relative paths from the monorepo root and a workspace script runs in each package's directory; a `package.json` script that runs the bin outranks the config, so change it in that `package.json`.",
|
|
14
|
+
"hash": "5a95865afe2560a4bb9e3f66448bd68f6a32c312",
|
|
15
|
+
"entry": 1
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"title": "♻️ Refactoring",
|
|
21
|
+
"audience": "dev",
|
|
22
|
+
"items": [
|
|
23
|
+
{
|
|
24
|
+
"description": "Move the duplicated output-style resolver into nmr-core's cli entry (#1248)",
|
|
25
|
+
"body": "Replaces the duplicate `resolveStyles` modules in `release-kit` and `v11y-check` with one `resolveCliStyles` function, exported from `@williamthorsen/nmr-core/cli`. Each package still reads its own `RELEASE_KIT_OUTPUT_STYLE` or `V11Y_CHECK_OUTPUT_STYLE` variable and still reports a usage error for a value other than `auto`, `plain`, or `rich`.",
|
|
26
|
+
"hash": "c5f4ac68360ce550a7e53006a2a633b79f179f5f"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
2
32
|
{
|
|
3
33
|
"version": "1.0.0",
|
|
4
34
|
"date": "2026-11-08",
|
package/.readyup/kits/default.js
CHANGED
|
@@ -8733,11 +8733,6 @@ function readPackageScripts(packageDir) {
|
|
|
8733
8733
|
}
|
|
8734
8734
|
}
|
|
8735
8735
|
|
|
8736
|
-
// src/helpers/type-guards.ts
|
|
8737
|
-
function isStringRecord(value) {
|
|
8738
|
-
return isRecord2(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
8739
|
-
}
|
|
8740
|
-
|
|
8741
8736
|
// src/verbosity.ts
|
|
8742
8737
|
var COMMAND_VERBOSITIES = ["full", "quiet"];
|
|
8743
8738
|
function formatVerbosityRejection(source, value) {
|
|
@@ -8752,7 +8747,7 @@ function isCommandVerbosity(value) {
|
|
|
8752
8747
|
var CONFIG_TIERS = {
|
|
8753
8748
|
root: {
|
|
8754
8749
|
label: "a monorepo-root config",
|
|
8755
|
-
honoredKeys: ["checkCache", "delegate", "
|
|
8750
|
+
honoredKeys: ["checkCache", "delegate", "output", "rootScripts", "selfHostedRunner", "workspaceScripts"],
|
|
8756
8751
|
elsewhere: "the package's own config"
|
|
8757
8752
|
},
|
|
8758
8753
|
workspace: {
|
|
@@ -8788,8 +8783,6 @@ function validateConfig(value, configPath, baseDir) {
|
|
|
8788
8783
|
const checkCache = validateCheckCacheField(value, configPath);
|
|
8789
8784
|
if (checkCache) config.checkCache = checkCache;
|
|
8790
8785
|
if (value["delegate"] !== void 0) config.delegate = validateDelegateField(value["delegate"], configPath);
|
|
8791
|
-
const devBin = validateStringRecordField(value, "devBin", configPath);
|
|
8792
|
-
if (devBin) config.devBin = devBin;
|
|
8793
8786
|
const output = validateOutputField(value, configPath);
|
|
8794
8787
|
if (output) config.output = output;
|
|
8795
8788
|
const workspaceScripts2 = validateScriptField(value, "workspaceScripts", configPath);
|
|
@@ -8960,15 +8953,6 @@ function validateScriptField(value, fieldName, configPath) {
|
|
|
8960
8953
|
assertValidElements(scripts, fieldName, configPath);
|
|
8961
8954
|
return scripts;
|
|
8962
8955
|
}
|
|
8963
|
-
function validateStringRecordField(value, fieldName, configPath) {
|
|
8964
|
-
if (!Object.hasOwn(value, fieldName) || value[fieldName] === void 0) {
|
|
8965
|
-
return void 0;
|
|
8966
|
-
}
|
|
8967
|
-
if (!isStringRecord(value[fieldName])) {
|
|
8968
|
-
throw new UserError(`Invalid nmr config at ${configPath}: \`${fieldName}\` must be a Record<string, string>`);
|
|
8969
|
-
}
|
|
8970
|
-
return value[fieldName];
|
|
8971
|
-
}
|
|
8972
8956
|
|
|
8973
8957
|
// src/helpers/escapeControlCharacters.ts
|
|
8974
8958
|
var NAMED_ESCAPES = /* @__PURE__ */ new Map([
|
|
@@ -9611,7 +9595,7 @@ function hasSupportedStrictLintVersion() {
|
|
|
9611
9595
|
});
|
|
9612
9596
|
}
|
|
9613
9597
|
function getMinVersion() {
|
|
9614
|
-
const pickedFields = { "version": "
|
|
9598
|
+
const pickedFields = { "version": "2.0.0" };
|
|
9615
9599
|
if (typeof pickedFields["version"] !== "string") {
|
|
9616
9600
|
throw new TypeError("nmr/package.json: 'version' must be a string");
|
|
9617
9601
|
}
|
package/.readyup/manifest.json
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"checklist": "nmr",
|
|
33
33
|
"checks": [
|
|
34
34
|
"@williamthorsen/nmr in devDependencies",
|
|
35
|
-
"@williamthorsen/nmr >=
|
|
35
|
+
"@williamthorsen/nmr >= 2.0.0",
|
|
36
36
|
"pnpm-workspace.yaml exists",
|
|
37
37
|
"catalogued dependencies use catalog:",
|
|
38
38
|
"package.json has packageManager field",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"path": "../../nmr-core/dist/esm/cache/cache-store.js"
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
|
-
"hash": "
|
|
89
|
+
"hash": "6ea2c3c0",
|
|
90
90
|
"kind": "module",
|
|
91
91
|
"path": "../../nmr-core/dist/esm/cli.js"
|
|
92
92
|
},
|
|
@@ -236,7 +236,7 @@
|
|
|
236
236
|
"path": "lib/workspace-build.ts"
|
|
237
237
|
},
|
|
238
238
|
{
|
|
239
|
-
"hash": "
|
|
239
|
+
"hash": "6933f29e",
|
|
240
240
|
"kind": "inline",
|
|
241
241
|
"path": "../package.json",
|
|
242
242
|
"paths": [
|
|
@@ -254,12 +254,12 @@
|
|
|
254
254
|
"path": "../src/config-check-cache.ts"
|
|
255
255
|
},
|
|
256
256
|
{
|
|
257
|
-
"hash": "
|
|
257
|
+
"hash": "d25aef4f",
|
|
258
258
|
"kind": "module",
|
|
259
259
|
"path": "../src/config-validation.ts"
|
|
260
260
|
},
|
|
261
261
|
{
|
|
262
|
-
"hash": "
|
|
262
|
+
"hash": "d345a285",
|
|
263
263
|
"kind": "module",
|
|
264
264
|
"path": "../src/config.ts"
|
|
265
265
|
},
|
|
@@ -308,11 +308,6 @@
|
|
|
308
308
|
"kind": "module",
|
|
309
309
|
"path": "../src/helpers/reportClosing.ts"
|
|
310
310
|
},
|
|
311
|
-
{
|
|
312
|
-
"hash": "3f8448c9",
|
|
313
|
-
"kind": "module",
|
|
314
|
-
"path": "../src/helpers/type-guards.ts"
|
|
315
|
-
},
|
|
316
311
|
{
|
|
317
312
|
"hash": "77f6bb50",
|
|
318
313
|
"kind": "module",
|
|
@@ -324,7 +319,7 @@
|
|
|
324
319
|
"path": "../src/resolve-scripts.ts"
|
|
325
320
|
},
|
|
326
321
|
{
|
|
327
|
-
"hash": "
|
|
322
|
+
"hash": "0112952e",
|
|
328
323
|
"kind": "module",
|
|
329
324
|
"path": "../src/resolver.ts"
|
|
330
325
|
},
|
|
@@ -364,7 +359,7 @@
|
|
|
364
359
|
"readyupVersion": "0.45.0",
|
|
365
360
|
"source": "kits/default.ts",
|
|
366
361
|
"sourceHash": "9fd61bf4",
|
|
367
|
-
"targetHash": "
|
|
362
|
+
"targetHash": "7ddb0518"
|
|
368
363
|
}
|
|
369
364
|
]
|
|
370
365
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 2.0.0 — 2026-11-10
|
|
6
|
+
|
|
7
|
+
### 🪦 Removed
|
|
8
|
+
|
|
9
|
+
- 🚨 **Breaking:** Removes the `devBin` root-config field of `@williamthorsen/nmr`, which replaced the first token of a resolved command with a command that ran a bin from source. (#1269)
|
|
10
|
+
|
|
11
|
+
### ♻️ Refactoring
|
|
12
|
+
|
|
13
|
+
- Move the duplicated output-style resolver into nmr-core's cli entry (#1248)
|
|
14
|
+
|
|
15
|
+
Replaces the duplicate `resolveStyles` modules in `release-kit` and `v11y-check` with one `resolveCliStyles` function, exported from `@williamthorsen/nmr-core/cli`. Each package still reads its own `RELEASE_KIT_OUTPUT_STYLE` or `V11Y_CHECK_OUTPUT_STYLE` variable and still reports a usage error for a value other than `auto`, `plain`, or `rich`.
|
|
16
|
+
|
|
5
17
|
## 1.0.0 — 2026-11-08
|
|
6
18
|
|
|
7
19
|
### 🧪 Tests
|
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 — v2.0.0 (2026-11-10)
|
|
9
|
+
|
|
10
|
+
### 🪦 Removed
|
|
11
|
+
|
|
12
|
+
- 🚨 **Breaking:** Removes the `devBin` root-config field of `@williamthorsen/nmr`, which replaced the first token of a resolved command with a command that ran a bin from source. (#1269)
|
|
13
|
+
<!-- /section:release-notes -->
|
|
8
14
|
|
|
9
15
|
## Installation
|
|
10
16
|
|
|
@@ -141,7 +147,7 @@ export default defineConfig({
|
|
|
141
147
|
});
|
|
142
148
|
```
|
|
143
149
|
|
|
144
|
-
[Scripts and configuration](docs/scripts.md#configuration) lists every `defineConfig` field
|
|
150
|
+
[Scripts and configuration](docs/scripts.md#configuration) lists every `defineConfig` field, covers package-level configuration, and shows how to run a workspace bin from source.
|
|
145
151
|
|
|
146
152
|
## What nmr reports
|
|
147
153
|
|
|
@@ -304,7 +310,7 @@ rdy list --from npm:@williamthorsen/nmr # what nmr publishes
|
|
|
304
310
|
|
|
305
311
|
## Documentation
|
|
306
312
|
|
|
307
|
-
- [Scripts and configuration](docs/scripts.md): Override tiers, script values, hooks, `defineConfig` fields,
|
|
313
|
+
- [Scripts and configuration](docs/scripts.md): Override tiers, script values, hooks, `defineConfig` fields, running a workspace bin from source, and the default commands
|
|
308
314
|
- [What nmr reports](docs/reporting.md): Verdicts, `--json`, output style, and verbosity
|
|
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
|
|
@@ -25,8 +25,6 @@ export declare function openGate(options: {
|
|
|
25
25
|
}): TreeSnapshot | undefined;
|
|
26
26
|
/**
|
|
27
27
|
* Computes the key under which this invocation would be recorded, or `undefined` when the cache does not apply.
|
|
28
|
-
* The cache does not apply to a `devBin` substitution: the substitute is built from a location that the tree id
|
|
29
|
-
* does not describe, so a pass by it is not a pass by the command that the key names.
|
|
30
28
|
*/
|
|
31
29
|
export declare function resolveCacheKey(options: {
|
|
32
30
|
anchorDir: string;
|
|
@@ -36,7 +34,6 @@ export declare function resolveCacheKey(options: {
|
|
|
36
34
|
monorepoRoot: string;
|
|
37
35
|
snapshot: TreeSnapshot | undefined;
|
|
38
36
|
stderr: Writable;
|
|
39
|
-
substitution: string | undefined;
|
|
40
37
|
}): string | undefined;
|
|
41
38
|
/**
|
|
42
39
|
* Runs the resolved steps behind the check-result cache: skips them when a recorded pass covers this
|
|
@@ -33,14 +33,10 @@ export function openGate(options) {
|
|
|
33
33
|
return snapshot.snapshot;
|
|
34
34
|
}
|
|
35
35
|
export function resolveCacheKey(options) {
|
|
36
|
-
const { env, snapshot, stderr
|
|
36
|
+
const { env, snapshot, stderr } = options;
|
|
37
37
|
if (snapshot === undefined) {
|
|
38
38
|
return undefined;
|
|
39
39
|
}
|
|
40
|
-
if (substitution !== undefined) {
|
|
41
|
-
writeDebugNote(`gate disabled: devBin substituted \`${substitution}\``, env, stderr);
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
40
|
const result = computeCacheKey({
|
|
45
41
|
anchorDir: options.anchorDir,
|
|
46
42
|
command: options.command,
|
|
@@ -8,7 +8,7 @@ import { findContainingPackageDir } from "../context.js";
|
|
|
8
8
|
import { NMR_GLYPHS } from "../glyphs.js";
|
|
9
9
|
import { reportClosing } from "../helpers/reportClosing.js";
|
|
10
10
|
import { resolveChannel } from "../output-channel.js";
|
|
11
|
-
import {
|
|
11
|
+
import { buildWorkspaceRegistry, resolveScript } from "../resolver.js";
|
|
12
12
|
import { runCommand } from "../runner.js";
|
|
13
13
|
import { renderChain } from "../steps.js";
|
|
14
14
|
import { findMonorepoRoot, getWorkspacePackageDirs } from "../workspace.js";
|
|
@@ -67,13 +67,12 @@ async function sweepWorkspace(monorepoRoot, workspacePackageDirs, style) {
|
|
|
67
67
|
cleanedCount++;
|
|
68
68
|
continue;
|
|
69
69
|
}
|
|
70
|
-
const
|
|
71
|
-
const { exitCode } = await runCommand(command, packageDir, {
|
|
70
|
+
const { exitCode } = await runCommand(resolvedCommand, packageDir, {
|
|
72
71
|
bin: NMR_CLEAN_BIN,
|
|
73
72
|
channels: { stderr: resolveChannel(process.stderr, false), stdout: resolveChannel(process.stdout, false) },
|
|
74
73
|
});
|
|
75
74
|
if (exitCode !== 0) {
|
|
76
|
-
throw new UserError(`\`${
|
|
75
|
+
throw new UserError(`\`${resolvedCommand}\` failed in ${path.basename(packageDir)} with exit code ${exitCode}.`);
|
|
77
76
|
}
|
|
78
77
|
cleanedCount++;
|
|
79
78
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { isRecord, UserError } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { assertResolvableCheckCacheCommands } from "./config-check-cache.js";
|
|
3
|
-
import { isStringRecord } from "./helpers/type-guards.js";
|
|
4
3
|
import { findUnexpressibleToken } from "./steps.js";
|
|
5
4
|
import { formatVerbosityRejection, isCommandVerbosity } from "./verbosity.js";
|
|
6
5
|
export const CONFIG_TIERS = {
|
|
7
6
|
root: {
|
|
8
7
|
label: 'a monorepo-root config',
|
|
9
|
-
honoredKeys: ['checkCache', 'delegate', '
|
|
8
|
+
honoredKeys: ['checkCache', 'delegate', 'output', 'rootScripts', 'selfHostedRunner', 'workspaceScripts'],
|
|
10
9
|
elsewhere: "the package's own config",
|
|
11
10
|
},
|
|
12
11
|
workspace: {
|
|
@@ -44,9 +43,6 @@ export function validateConfig(value, configPath, baseDir) {
|
|
|
44
43
|
config.checkCache = checkCache;
|
|
45
44
|
if (value['delegate'] !== undefined)
|
|
46
45
|
config.delegate = validateDelegateField(value['delegate'], configPath);
|
|
47
|
-
const devBin = validateStringRecordField(value, 'devBin', configPath);
|
|
48
|
-
if (devBin)
|
|
49
|
-
config.devBin = devBin;
|
|
50
46
|
const output = validateOutputField(value, configPath);
|
|
51
47
|
if (output)
|
|
52
48
|
config.output = output;
|
|
@@ -225,12 +221,3 @@ function validateScriptField(value, fieldName, configPath) {
|
|
|
225
221
|
assertValidElements(scripts, fieldName, configPath);
|
|
226
222
|
return scripts;
|
|
227
223
|
}
|
|
228
|
-
function validateStringRecordField(value, fieldName, configPath) {
|
|
229
|
-
if (!Object.hasOwn(value, fieldName) || value[fieldName] === undefined) {
|
|
230
|
-
return undefined;
|
|
231
|
-
}
|
|
232
|
-
if (!isStringRecord(value[fieldName])) {
|
|
233
|
-
throw new UserError(`Invalid nmr config at ${configPath}: \`${fieldName}\` must be a Record<string, string>`);
|
|
234
|
-
}
|
|
235
|
-
return value[fieldName];
|
|
236
|
-
}
|
package/dist/esm/config.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { NmrConfig } from './types.js';
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function loadConfig(baseDir: string): Promise<NmrConfig>;
|
|
8
8
|
/**
|
|
9
|
-
* Loads the monorepo-root `.config/nmr.config.ts`, the tier that feeds script resolution
|
|
9
|
+
* Loads the monorepo-root `.config/nmr.config.ts`, the tier that feeds script resolution.
|
|
10
10
|
* Returns an empty config if the file doesn't exist.
|
|
11
11
|
*
|
|
12
12
|
* Throws on a recognized key that this tier does not honor. `build` governs one package's compile, which reads the
|
|
@@ -24,7 +24,7 @@ export declare function assertNoSelfReference(options: {
|
|
|
24
24
|
*
|
|
25
25
|
* Ahead of the gate, so that a command that usually skips still reports its crossing. A `--log` reports
|
|
26
26
|
* none, running nothing that could cross. Reads the resolved steps rather than the full chain: the line names
|
|
27
|
-
* a declaration to edit, and a hook
|
|
27
|
+
* a declaration to edit, and neither a hook nor a passthrough is one.
|
|
28
28
|
*/
|
|
29
29
|
export declare function reportNmrCrossing(options: {
|
|
30
30
|
config: NmrConfig;
|
package/dist/esm/resolver.d.ts
CHANGED
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
import { type ScriptRegistry, type ScriptValue } from './resolve-scripts.js';
|
|
2
2
|
import { type Step } from './steps.js';
|
|
3
3
|
import type { NmrConfig } from './types.js';
|
|
4
|
-
/**
|
|
5
|
-
* Replaces the first token of a command with a `devBin` substitute.
|
|
6
|
-
* Relative paths in the replacement are resolved from `monorepoRoot`.
|
|
7
|
-
*/
|
|
8
|
-
export declare function applyDevBin(command: string, devBin: Record<string, string> | undefined, monorepoRoot: string): string;
|
|
9
|
-
/**
|
|
10
|
-
* Applies a `devBin` substitution to each opaque step, leaving nmr's own compositions alone.
|
|
11
|
-
*
|
|
12
|
-
* A structural step names a command that nmr resolves for itself, so substituting its first token would replace
|
|
13
|
-
* the `nmr` that runs the composite rather than the leaf tool that `devBin` documents replacing.
|
|
14
|
-
*/
|
|
15
|
-
export declare function applyDevBinToSteps(steps: readonly Step[], devBin: Record<string, string> | undefined, monorepoRoot: string): readonly Step[];
|
|
16
4
|
/**
|
|
17
5
|
* Where a resolved script was read from.
|
|
18
6
|
*
|
package/dist/esm/resolver.js
CHANGED
|
@@ -1,44 +1,9 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
2
|
import { hasErrnoCode, isRecord } from '@williamthorsen/nmr-core';
|
|
4
3
|
import { parsePackageJson, readScriptRecord, resolvePackageJsonPath } from "./helpers/package-json.js";
|
|
5
4
|
import { getDefaultRootScripts, getDefaultWorkspaceScripts, } from "./resolve-scripts.js";
|
|
6
5
|
import { composeNmrStep, readSelfReference } from "./steps.js";
|
|
7
6
|
import { isMonorepoRoot } from "./workspace.js";
|
|
8
|
-
export function applyDevBin(command, devBin, monorepoRoot) {
|
|
9
|
-
if (!devBin) {
|
|
10
|
-
return command;
|
|
11
|
-
}
|
|
12
|
-
const spaceIndex = command.indexOf(' ');
|
|
13
|
-
const firstToken = spaceIndex === -1 ? command : command.slice(0, spaceIndex);
|
|
14
|
-
const restOfCommand = spaceIndex === -1 ? '' : command.slice(spaceIndex);
|
|
15
|
-
const replacement = devBin[firstToken];
|
|
16
|
-
if (replacement === undefined) {
|
|
17
|
-
return command;
|
|
18
|
-
}
|
|
19
|
-
const resolvedReplacement = resolveReplacementPaths(replacement, monorepoRoot);
|
|
20
|
-
return resolvedReplacement + restOfCommand;
|
|
21
|
-
}
|
|
22
|
-
export function applyDevBinToSteps(steps, devBin, monorepoRoot) {
|
|
23
|
-
if (!devBin) {
|
|
24
|
-
return steps;
|
|
25
|
-
}
|
|
26
|
-
return steps.map((step) => step.kind === 'opaque' ? { kind: 'opaque', command: applyDevBin(step.command, devBin, monorepoRoot) } : step);
|
|
27
|
-
}
|
|
28
|
-
function resolveReplacementPaths(replacement, monorepoRoot) {
|
|
29
|
-
const tokens = replacement.split(' ');
|
|
30
|
-
return tokens
|
|
31
|
-
.map((token, index) => {
|
|
32
|
-
if (index === 0)
|
|
33
|
-
return token;
|
|
34
|
-
if (token.startsWith('-'))
|
|
35
|
-
return token;
|
|
36
|
-
if (!token.includes('/'))
|
|
37
|
-
return token;
|
|
38
|
-
return path.resolve(monorepoRoot, token);
|
|
39
|
-
})
|
|
40
|
-
.join(' ');
|
|
41
|
-
}
|
|
42
7
|
export function expandScript(script, isWorkspaceRoot) {
|
|
43
8
|
if (typeof script === 'string') {
|
|
44
9
|
return [{ kind: 'opaque', command: script }];
|
package/dist/esm/run-chain.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
|
2
2
|
import type { TreeSnapshot } from './check-cache.js';
|
|
3
3
|
import { type Invocation, type PhaseResult, type RunPlan } from './invocation.js';
|
|
4
4
|
import type { ScriptRegistry } from './resolve-scripts.js';
|
|
5
|
-
import {
|
|
5
|
+
import type { ResolvedScript } from './resolver.js';
|
|
6
6
|
/**
|
|
7
7
|
* Assembles the chain that a resolved script runs at this scope, the hooks wrapped around it included, and the key
|
|
8
8
|
* under which a pass by that chain is recorded. Settles a no-op override and an argument that no step can take.
|
package/dist/esm/run-chain.js
CHANGED
|
@@ -9,7 +9,6 @@ import { quoteForShell } from "./helpers/quoteForShell.js";
|
|
|
9
9
|
import { wrapWithHooks } from "./hooks.js";
|
|
10
10
|
import { USAGE_EXIT_CODE } from "./invocation.js";
|
|
11
11
|
import { reportVerdict } from "./presentation.js";
|
|
12
|
-
import { applyDevBinToSteps } from "./resolver.js";
|
|
13
12
|
import { dropRecursiveSteps, renderChain } from "./steps.js";
|
|
14
13
|
export function assembleChain(options) {
|
|
15
14
|
const { invocation, registry, resolvedScript, snapshot } = options;
|
|
@@ -21,9 +20,7 @@ export function assembleChain(options) {
|
|
|
21
20
|
reportVerdict({ command, scope: invocation.scope, outcome: 'no-op', reason: noOpReason }, stdout, invocation.format, styles.stdout);
|
|
22
21
|
return { settled: true, result: { exitCode: 0 } };
|
|
23
22
|
}
|
|
24
|
-
const
|
|
25
|
-
const substitutedCommand = renderChain(substitutedSteps);
|
|
26
|
-
const bindResult = bindPassthrough(substitutedSteps, parsedArgs.passthrough, command);
|
|
23
|
+
const bindResult = bindPassthrough(runnableSteps, parsedArgs.passthrough, command);
|
|
27
24
|
if (!bindResult.ok) {
|
|
28
25
|
reportError(NMR_BIN, bindResult.error, stderr);
|
|
29
26
|
return { settled: true, result: { exitCode: USAGE_EXIT_CODE } };
|
|
@@ -53,7 +50,6 @@ export function assembleChain(options) {
|
|
|
53
50
|
monorepoRoot: context.monorepoRoot,
|
|
54
51
|
snapshot,
|
|
55
52
|
stderr,
|
|
56
|
-
substitution: substitutedCommand === resolvedCommand ? undefined : substitutedCommand,
|
|
57
53
|
});
|
|
58
54
|
return { settled: false, value: { commandString: fullCommand, key, ownSteps: mainSteps, steps: fullSteps } };
|
|
59
55
|
}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -59,7 +59,6 @@ export interface NmrConfig {
|
|
|
59
59
|
* config says. A delegation flag still sends a run elsewhere.
|
|
60
60
|
*/
|
|
61
61
|
delegate?: false;
|
|
62
|
-
devBin?: Record<string, string>;
|
|
63
62
|
output?: OutputConfig;
|
|
64
63
|
workspaceScripts?: Record<string, ScriptValue>;
|
|
65
64
|
rootScripts?: Record<string, ScriptValue>;
|
package/docs/check-cache.md
CHANGED
|
@@ -63,7 +63,6 @@ The gate never wrongly skips. When it cannot be sure, it does nothing and the co
|
|
|
63
63
|
- When the monorepo root is not the git toplevel.
|
|
64
64
|
- When a changed path cannot be read, contains a newline or bytes that are not UTF-8, or is neither a file nor a symlink (an untracked nested repository, for instance).
|
|
65
65
|
- When the repository does not have a pnpm install to fingerprint.
|
|
66
|
-
- Under a `devBin` substitution, which runs a binary built from a location that the tree id does not describe.
|
|
67
66
|
- For any invocation carrying arguments after the command name, and for every step below it: the arguments narrow what runs, and a step served from a recorded pass is a step that did not.
|
|
68
67
|
|
|
69
68
|
`NMR_DEBUG=1` reports why a run did not skip and why the cache did not apply.
|
package/docs/scripts.md
CHANGED
|
@@ -79,7 +79,7 @@ The line names the declaration to which it is an edit (the file, the field, and
|
|
|
79
79
|
|
|
80
80
|
One kind of `package.json` entry is resolved in its own file: an entry whose whole value is `nmr <name>`, without flags, arguments, assignments, or a launcher, where `<name>` is a script in the same `package.json` that runs a command other than nmr and does not have a `:pre` or `:post` hook. Replace the entry's value with that command, and delete the script: `"build:pre": "nmr compile:ports"` beside `"compile:ports": "node scripts/compile-ports.ts"` becomes `"build:pre": "node scripts/compile-ports.ts"`. Keep the script when the registry also defines `<name>`, because the script then overrides nmr's own command. The script must come from the same file: Inlining a command that resolves from the registry would copy nmr's own command text into the package, where nmr's later changes do not reach it.
|
|
81
81
|
|
|
82
|
-
nmr recognizes `nmr` in command position: at the start of a step, after an unquoted `&&`, `||`, `;`, `|`, or a newline, past any leading `NAME=value` assignments, and behind `npx`, `bunx`, or an `exec`, `dlx`, or `x` subcommand of `npm`, `pnpm`, `yarn`, or `bun`. This line reports a boundary rather than enforcing a rule: It covers a step that nmr runs as written, and only the reporting of the nested run is degraded. The line is also partial in
|
|
82
|
+
nmr recognizes `nmr` in command position: at the start of a step, after an unquoted `&&`, `||`, `;`, `|`, or a newline, past any leading `NAME=value` assignments, and behind `npx`, `bunx`, or an `exec`, `dlx`, or `x` subcommand of `npm`, `pnpm`, `yarn`, or `bun`. This line reports a boundary rather than enforcing a rule: It covers a step that nmr runs as written, and only the reporting of the nested run is degraded. The line is also partial in two ways: a value-taking flag standing immediately before the program name hides it (`npx -p foo nmr`), and a launcher outside that set goes unreported.
|
|
83
83
|
|
|
84
84
|
A [self-referential entry](#three-tier-override-system), in `package.json` or in `.config/nmr.config.ts`, is the shape that nmr cannot run as written, and it is rejected rather than reported. The same recognition finds it, so the same residuals apply: What goes unrecognized there re-enters nmr once, and the run then fails with an error naming the command and the directory that it re-entered.
|
|
85
85
|
|
|
@@ -125,7 +125,6 @@ A minimal `.config/nmr.config.ts` is in the [README](../README.md#configuration)
|
|
|
125
125
|
| `delegate` | `false` | Keeps every check on the machine that has the tree, whatever `NMR_DELEGATE` or the [user config](delegate.md#the-user-config) says; a delegation flag still sends a run elsewhere (root config only) |
|
|
126
126
|
| `workspaceScripts` | `Record<string, ScriptValue>` | Scripts added or overridden in the workspace registry (tier 2) |
|
|
127
127
|
| `rootScripts` | `Record<string, ScriptValue>` | Scripts added or overridden in the root registry (tier 2) |
|
|
128
|
-
| `devBin` | `Record<string, string>` | Map binary names to source-repo replacement commands |
|
|
129
128
|
| `output` | `{ commandVerbosity?: CommandVerbosity; extraAgentEnvVars?: string[] }` | How loudly runs report command output; see [configuring](reporting.md#configuring) |
|
|
130
129
|
| `selfHostedRunner` | `{ exempt: string }` | Why a qualifying repo keeps CI off the self-hosted runners; stops [nmr's readyup kit](../README.md#conformance-checks) from recommending `CI_RUNS_ON` (root config only). The reason is structural: a circular dependency, a platform or capacity the runners lack, or network reach. A missing tool is installed by a workflow step instead |
|
|
131
130
|
|
|
@@ -135,7 +134,7 @@ Each field belongs to exactly one tier, and nmr loads from a config only the fie
|
|
|
135
134
|
|
|
136
135
|
### Package-level configuration
|
|
137
136
|
|
|
138
|
-
A package may have its own `.config/nmr.config.ts`, which `nmr-compile` reads from the package that it is compiling. This tier honors `build` alone; declaring `workspaceScripts`, `rootScripts`, or
|
|
137
|
+
A package may have its own `.config/nmr.config.ts`, which `nmr-compile` reads from the package that it is compiling. This tier honors `build` alone; declaring `workspaceScripts`, `rootScripts`, or any other root-config field there fails with a message naming them, rather than being quietly ignored: Those belong in the monorepo-root config, and a package that appeared to set them would otherwise build on settings that nothing applied. The monorepo-root config has the mirror restriction: `build` there fails the same way, since only a package's own config affects the compile.
|
|
139
138
|
|
|
140
139
|
```ts
|
|
141
140
|
// packages/my-package/.config/nmr.config.ts
|
|
@@ -150,23 +149,29 @@ export default defineConfig({
|
|
|
150
149
|
|
|
151
150
|
> **Building nmr from source:** A config file that has to load while nmr itself is being built (before its `dist` exists) must use the type-only form, `import type { NmrConfig } from '@williamthorsen/nmr/config'` with `satisfies NmrConfig`, which does not produce a runtime import. `defineConfig` is unavailable at that moment, because it resolves through nmr's build output. Every other config, including every consumer's, imports nmr from an installed tarball that contains `dist`, and can use `defineConfig` freely.
|
|
152
151
|
|
|
153
|
-
###
|
|
152
|
+
### Running a workspace bin from source
|
|
154
153
|
|
|
155
|
-
|
|
154
|
+
A script that runs a bin built in this monorepo runs its build output, which can lag the source or be missing before the first build. To run the source instead, override the script whose value runs the bin, and call `node` on the bin's source file: Node runs TypeScript directly.
|
|
155
|
+
|
|
156
|
+
A resolved script runs in the directory of its scope: A root script runs from the monorepo root, so a path relative to the root works there, and a workspace script runs in each package's directory, so the config builds an absolute path from `import.meta.dirname`, the config's own `.config` directory.
|
|
156
157
|
|
|
157
158
|
```ts
|
|
159
|
+
// .config/nmr.config.ts
|
|
160
|
+
import { defineConfig } from '@williamthorsen/nmr/config';
|
|
161
|
+
|
|
162
|
+
const myCli = `${import.meta.dirname}/../packages/my-cli/src/cli.ts`;
|
|
163
|
+
|
|
158
164
|
export default defineConfig({
|
|
159
|
-
|
|
160
|
-
'
|
|
165
|
+
rootScripts: {
|
|
166
|
+
'build:post': 'node packages/my-cli/src/cli.ts compile',
|
|
167
|
+
},
|
|
168
|
+
workspaceScripts: {
|
|
169
|
+
lint: `node "${myCli}" lint`,
|
|
161
170
|
},
|
|
162
171
|
});
|
|
163
172
|
```
|
|
164
173
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
For example, if a workspace script resolves to `my-cli --verbose`, nmr rewrites it to `tsx /absolute/path/to/packages/my-cli/src/cli.ts --verbose`.
|
|
168
|
-
|
|
169
|
-
> **Note:** Path resolution uses a heuristic: Any non-flag token containing `/` is treated as a relative path. This works well for typical dev-tool commands but may incorrectly resolve URL-like values or glob patterns. Flags using `--flag=value` syntax are not resolved; use the spaced form `--flag value` for paths that need resolution.
|
|
174
|
+
A package's own `package.json` script outranks the config (tier 3), so a package script that runs the bin is changed in that `package.json`.
|
|
170
175
|
|
|
171
176
|
## Default commands
|
|
172
177
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/nmr",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Context-aware script runner for pnpm monorepos",
|
|
6
6
|
"keywords": [
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"!docs/*.v*.md"
|
|
81
81
|
],
|
|
82
82
|
"dependencies": {
|
|
83
|
-
"@williamthorsen/nmr-core": "0.16.
|
|
83
|
+
"@williamthorsen/nmr-core": "0.16.1",
|
|
84
84
|
"@williamthorsen/toolbelt.errors": "0.9.0",
|
|
85
85
|
"@williamthorsen/toolbelt.filesystem": "0.10.0",
|
|
86
86
|
"glob": "14.0.4",
|