@williamthorsen/nmr 0.15.0 → 0.16.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/README.md +7 -15
- package/dist/esm/.cache +1 -1
- package/dist/esm/cli-build.js +3 -2
- package/dist/esm/cli-ensure-prepublish-hooks.js +13 -31
- package/dist/esm/cli-report-overrides.js +3 -2
- package/dist/esm/cli-sync-agent-files.js +5 -12
- package/dist/esm/cli-sync-pnpm-version.js +3 -2
- package/dist/esm/cli.js +2 -2
- package/dist/esm/default-scripts.js +1 -0
- package/dist/esm/help.js +0 -1
- package/dist/esm/resolver.d.ts +1 -0
- package/dist/esm/resolver.js +5 -1
- package/dist/esm/runCli.js +12 -14
- package/dist/esm/runner.js +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,27 +3,19 @@
|
|
|
3
3
|
Context-aware script runner for PNPM monorepos. Ships an `nmr` (node-monorepo run) binary that provides centralized, consistent script execution across workspace packages and the monorepo root.
|
|
4
4
|
|
|
5
5
|
<!-- section:release-notes -->
|
|
6
|
-
## Release notes — v0.
|
|
6
|
+
## Release notes — v0.16.0 (2026-06-30)
|
|
7
7
|
|
|
8
8
|
### 🎉 Features
|
|
9
9
|
|
|
10
|
-
-
|
|
10
|
+
- 🚨 **Breaking:** Auto-activate integration test variant from config presence (#448)
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
A package can now separate its integration tests from its standalone suite simply by including a `vitest.integration.config.ts` (alongside a `vitest.standalone.config.ts`). The `--int-test` flag that previously enabled this is removed — that config-file pairing is now the only way to activate the separation. In such a package, `test` and `test:coverage` run only the standalone suite and skip integration tests, while a new `test:all` runs both suites together. The separation now holds even when tests run across every package at once, so a full-workspace `test` run still keeps integration tests out of the default suite. Packages that previously hand-copied these test scripts no longer need to.
|
|
13
13
|
|
|
14
14
|
### 🐛 Bug fixes
|
|
15
15
|
|
|
16
|
-
-
|
|
16
|
+
- Prevent CLI output truncation when piped before exit (#446)
|
|
17
17
|
|
|
18
|
-
Fixes an issue where
|
|
19
|
-
|
|
20
|
-
- Stop sync-agent-files --check failing on version-only bumps (#424)
|
|
21
|
-
|
|
22
|
-
Fixes an issue where `nmr sync-agent-files --check` failed after an nmr upgrade even when the managed agent guidance was identical. The check now passes whenever the guidance content is current, even if the version has changed.
|
|
23
|
-
|
|
24
|
-
- Remove inapplicable bootstrap fallback from agent guidance (#425)
|
|
25
|
-
|
|
26
|
-
Fixes an issue where the agent guidance bundled with nmr told coding agents to run a recovery step that exists only in the nmr repo itself.
|
|
18
|
+
Fixes an issue where large output from the `nmr` and `v11y` commands could be truncated when captured through a pipe — for example, by a CI job.
|
|
27
19
|
<!-- /section:release-notes -->
|
|
28
20
|
|
|
29
21
|
## Installation
|
|
@@ -176,11 +168,12 @@ These scripts are available out of the box. Repo-wide config (tier 2) and per-pa
|
|
|
176
168
|
|
|
177
169
|
#### Integration test variant
|
|
178
170
|
|
|
179
|
-
|
|
171
|
+
A package gets this variant automatically when it contains a `vitest.integration.config.ts` file: `test` and `test:coverage` run the standalone (non-integration) suite, and integration tests run only when invoked explicitly. Activation is detected per package, so it applies under the recursive `nmr ci` fan-out. Opting in requires **both** `vitest.integration.config.ts` and `vitest.standalone.config.ts`, since the standalone scripts reference the latter.
|
|
180
172
|
|
|
181
173
|
| Command | Runs |
|
|
182
174
|
| ------------------ | ------------------------------------------------------------------ |
|
|
183
175
|
| `test` | `pnpm exec vitest --config=vitest.standalone.config.ts` |
|
|
176
|
+
| `test:all` | `pnpm exec vitest` |
|
|
184
177
|
| `test:coverage` | `pnpm exec vitest --config=vitest.standalone.config.ts --coverage` |
|
|
185
178
|
| `test:integration` | `pnpm exec vitest --config=vitest.integration.config.ts` |
|
|
186
179
|
| `test:watch` | `pnpm exec vitest --config=vitest.standalone.config.ts --watch` |
|
|
@@ -294,7 +287,6 @@ nmr [flags] <command> [args...]
|
|
|
294
287
|
| `-q, --quiet` | Suppress info messages; show full output on failure | — |
|
|
295
288
|
| `-?, --help` | Show available commands | — |
|
|
296
289
|
| `-V, --version` | Show version number | — |
|
|
297
|
-
| `--int-test` | Use integration test scripts | — |
|
|
298
290
|
|
|
299
291
|
### Examples
|
|
300
292
|
|
package/dist/esm/.cache
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
47af840104236371ef384e3368ae463ca3d05d525fcffd96134d4c9c11257796
|
package/dist/esm/cli-build.js
CHANGED
|
@@ -2,6 +2,7 @@ import { buildPackage } from "./commands/build.js";
|
|
|
2
2
|
try {
|
|
3
3
|
await buildPackage(process.cwd());
|
|
4
4
|
} catch (error) {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
6
|
+
`);
|
|
7
|
+
process.exitCode = 1;
|
|
7
8
|
}
|
|
@@ -1,31 +1,12 @@
|
|
|
1
|
+
import { parseArgsOrExit, reportError } from "@williamthorsen/nmr-core";
|
|
1
2
|
import { DEFAULT_HOOK, ensurePrepublishHooks } from "./commands/ensure-prepublish-hooks.js";
|
|
2
3
|
import { findMonorepoRoot } from "./context.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
switch (arg) {
|
|
10
|
-
case "--fix":
|
|
11
|
-
fix = true;
|
|
12
|
-
break;
|
|
13
|
-
case "--dry-run":
|
|
14
|
-
dryRun = true;
|
|
15
|
-
break;
|
|
16
|
-
case "--command":
|
|
17
|
-
i++;
|
|
18
|
-
command = args[i];
|
|
19
|
-
if (!command) {
|
|
20
|
-
console.error("--command requires a value");
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
break;
|
|
24
|
-
default:
|
|
25
|
-
console.error(`Unknown argument: ${arg}`);
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
4
|
+
const flagSchema = {
|
|
5
|
+
fix: { long: "--fix", type: "boolean" },
|
|
6
|
+
dryRun: { long: "--dry-run", type: "boolean" },
|
|
7
|
+
command: { long: "--command", type: "string" }
|
|
8
|
+
};
|
|
9
|
+
const { fix, dryRun, command } = parseArgsOrExit(process.argv.slice(2), flagSchema).flags;
|
|
29
10
|
try {
|
|
30
11
|
const monorepoRoot = findMonorepoRoot();
|
|
31
12
|
const hookCommand = command ?? DEFAULT_HOOK;
|
|
@@ -52,11 +33,12 @@ try {
|
|
|
52
33
|
}
|
|
53
34
|
if (result.hasFailures) {
|
|
54
35
|
const missing = result.packages.filter((p) => p.action === "missing").length;
|
|
55
|
-
|
|
56
|
-
${missing} package(s) missing prepublishOnly. Use --fix to add it
|
|
57
|
-
|
|
36
|
+
process.stderr.write(`
|
|
37
|
+
${missing} package(s) missing prepublishOnly. Use --fix to add it.
|
|
38
|
+
`);
|
|
39
|
+
process.exitCode = 1;
|
|
58
40
|
}
|
|
59
41
|
} catch (error) {
|
|
60
|
-
|
|
61
|
-
process.
|
|
42
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
43
|
+
process.exitCode = 1;
|
|
62
44
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { reportError } from "@williamthorsen/nmr-core";
|
|
1
2
|
import { reportOverrides } from "./commands/report-overrides.js";
|
|
2
3
|
import { findMonorepoRoot } from "./context.js";
|
|
3
4
|
try {
|
|
4
5
|
const monorepoRoot = findMonorepoRoot();
|
|
5
6
|
reportOverrides(monorepoRoot);
|
|
6
7
|
} catch (error) {
|
|
7
|
-
|
|
8
|
-
process.
|
|
8
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
9
|
+
process.exitCode = 1;
|
|
9
10
|
}
|
|
@@ -1,24 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseArgsOrExit, reportError } from "@williamthorsen/nmr-core";
|
|
2
2
|
import { check, sync } from "./commands/sync-agent-files.js";
|
|
3
3
|
import { findMonorepoRoot } from "./context.js";
|
|
4
4
|
const flagSchema = {
|
|
5
5
|
check: { long: "--check", type: "boolean" }
|
|
6
6
|
};
|
|
7
|
-
const { flags } = parseArgsOrExit(process.argv.slice(2));
|
|
7
|
+
const { flags } = parseArgsOrExit(process.argv.slice(2), flagSchema);
|
|
8
8
|
try {
|
|
9
9
|
runCommand(flags.check);
|
|
10
10
|
} catch (error) {
|
|
11
|
-
|
|
11
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
12
12
|
process.exit(1);
|
|
13
13
|
}
|
|
14
|
-
function parseArgsOrExit(argv) {
|
|
15
|
-
try {
|
|
16
|
-
return parseArgs(argv, flagSchema);
|
|
17
|
-
} catch (error) {
|
|
18
|
-
console.error(`Error: ${translateParseError(error)}`);
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
14
|
function runCommand(checkOnly) {
|
|
23
15
|
const monorepoRoot = findMonorepoRoot();
|
|
24
16
|
if (!checkOnly) {
|
|
@@ -33,6 +25,7 @@ function runCommand(checkOnly) {
|
|
|
33
25
|
console.info("\u2713 .agents/nmr/AGENTS.md is in sync");
|
|
34
26
|
process.exit(0);
|
|
35
27
|
}
|
|
36
|
-
|
|
28
|
+
process.stderr.write(`${result.reason}
|
|
29
|
+
`);
|
|
37
30
|
process.exit(1);
|
|
38
31
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { reportError } from "@williamthorsen/nmr-core";
|
|
1
2
|
import { syncPnpmVersion } from "./commands/sync-pnpm-version.js";
|
|
2
3
|
import { findMonorepoRoot } from "./context.js";
|
|
3
4
|
try {
|
|
4
5
|
const monorepoRoot = findMonorepoRoot();
|
|
5
6
|
syncPnpmVersion(monorepoRoot);
|
|
6
7
|
} catch (error) {
|
|
7
|
-
|
|
8
|
-
process.
|
|
8
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
9
|
+
process.exitCode = 1;
|
|
9
10
|
}
|
package/dist/esm/cli.js
CHANGED
|
@@ -8,9 +8,9 @@ try {
|
|
|
8
8
|
stdout: process.stdout,
|
|
9
9
|
stderr: process.stderr
|
|
10
10
|
});
|
|
11
|
-
process.
|
|
11
|
+
process.exitCode = exitCode;
|
|
12
12
|
} catch (error) {
|
|
13
13
|
process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}
|
|
14
14
|
`);
|
|
15
|
-
process.
|
|
15
|
+
process.exitCode = 1;
|
|
16
16
|
}
|
|
@@ -17,6 +17,7 @@ const commonWorkspaceScripts = {
|
|
|
17
17
|
};
|
|
18
18
|
const integrationTestScripts = {
|
|
19
19
|
test: "pnpm exec vitest --config=vitest.standalone.config.ts",
|
|
20
|
+
"test:all": "pnpm exec vitest",
|
|
20
21
|
"test:coverage": "pnpm exec vitest --config=vitest.standalone.config.ts --coverage",
|
|
21
22
|
"test:integration": "pnpm exec vitest --config=vitest.integration.config.ts",
|
|
22
23
|
"test:watch": "pnpm exec vitest --config=vitest.standalone.config.ts --watch"
|
package/dist/esm/help.js
CHANGED
|
@@ -16,7 +16,6 @@ function generateHelp(config, packageDir, useRoot) {
|
|
|
16
16
|
" -w, --workspace-root Run root command regardless of cwd",
|
|
17
17
|
" -q, --quiet Suppress output on success; show full output on failure",
|
|
18
18
|
" -?, --help Show this help",
|
|
19
|
-
" --int-test Use integration test scripts",
|
|
20
19
|
"",
|
|
21
20
|
"Workspace commands:"
|
|
22
21
|
];
|
package/dist/esm/resolver.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface ResolvedScript {
|
|
|
8
8
|
export declare function expandScript(script: ScriptValue, workspaceRoot: boolean): string;
|
|
9
9
|
export declare function describeScript(script: ScriptValue): string;
|
|
10
10
|
export declare function readPackageJsonScripts(packageDir: string): Record<string, string> | undefined;
|
|
11
|
+
export declare function hasIntegrationTestConfig(packageDir: string): boolean;
|
|
11
12
|
export declare function buildWorkspaceRegistry(config: NmrConfig, useIntTests: boolean): ScriptRegistry;
|
|
12
13
|
export declare function buildRootRegistry(config: NmrConfig): ScriptRegistry;
|
|
13
14
|
export declare function isSelfReferential(script: string, commandName: string): boolean;
|
package/dist/esm/resolver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { isObject } from "./helpers/type-guards.js";
|
|
4
4
|
import { getDefaultRootScripts, getDefaultWorkspaceScripts } from "./resolve-scripts.js";
|
|
@@ -54,6 +54,9 @@ function readPackageJsonScripts(packageDir) {
|
|
|
54
54
|
throw error;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
function hasIntegrationTestConfig(packageDir) {
|
|
58
|
+
return existsSync(path.join(packageDir, "vitest.integration.config.ts"));
|
|
59
|
+
}
|
|
57
60
|
function buildWorkspaceRegistry(config, useIntTests) {
|
|
58
61
|
return {
|
|
59
62
|
...getDefaultWorkspaceScripts(useIntTests),
|
|
@@ -92,6 +95,7 @@ export {
|
|
|
92
95
|
buildWorkspaceRegistry,
|
|
93
96
|
describeScript,
|
|
94
97
|
expandScript,
|
|
98
|
+
hasIntegrationTestConfig,
|
|
95
99
|
isSelfReferential,
|
|
96
100
|
readPackageJsonScripts,
|
|
97
101
|
resolveScript
|
package/dist/esm/runCli.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { readPackageVersion } from "@williamthorsen/nmr-core";
|
|
2
|
+
import { readPackageVersion, reportError } from "@williamthorsen/nmr-core";
|
|
3
3
|
import { resolveContext } from "./context.js";
|
|
4
4
|
import { generateHelp } from "./help.js";
|
|
5
5
|
import { isHookName } from "./helpers/hook-name.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
applyDevBin,
|
|
8
|
+
buildRootRegistry,
|
|
9
|
+
buildWorkspaceRegistry,
|
|
10
|
+
hasIntegrationTestConfig,
|
|
11
|
+
resolveScript
|
|
12
|
+
} from "./resolver.js";
|
|
7
13
|
import { runCommand } from "./runner.js";
|
|
8
14
|
const VERSION = readPackageVersion(import.meta.url);
|
|
9
15
|
async function runCli(options) {
|
|
10
16
|
const { args, cwd, env, stdout, stderr } = options;
|
|
11
17
|
const parseResult = parseArgs(args);
|
|
12
18
|
if (!parseResult.ok) {
|
|
13
|
-
|
|
14
|
-
`);
|
|
19
|
+
reportError(parseResult.error, stderr);
|
|
15
20
|
return { exitCode: 1 };
|
|
16
21
|
}
|
|
17
22
|
const { parsed } = parseResult;
|
|
@@ -42,14 +47,13 @@ async function runCli(options) {
|
|
|
42
47
|
const exitCode2 = runCommand(delegateCmd, context.monorepoRoot, { ...runOptions, env: childEnv });
|
|
43
48
|
return { exitCode: exitCode2 };
|
|
44
49
|
}
|
|
45
|
-
const registry = useRoot ? buildRootRegistry(context.config) : buildWorkspaceRegistry(context.config,
|
|
50
|
+
const registry = useRoot ? buildRootRegistry(context.config) : buildWorkspaceRegistry(context.config, hasIntegrationTestConfig(packageDir));
|
|
46
51
|
const resolved = resolveScript(command, registry, packageDir, parsed.workspaceRoot);
|
|
47
52
|
if (!resolved) {
|
|
48
53
|
if (env.NMR_RUN_IF_PRESENT === "1") {
|
|
49
54
|
return { exitCode: 0 };
|
|
50
55
|
}
|
|
51
|
-
|
|
52
|
-
`);
|
|
56
|
+
reportError(`Unknown command: ${command}`, stderr);
|
|
53
57
|
return { exitCode: 1 };
|
|
54
58
|
}
|
|
55
59
|
const skipExitCode = handleSkipMessage(resolved.command, packageDir, parsed.quiet, stdout);
|
|
@@ -74,7 +78,6 @@ function parseArgs(args) {
|
|
|
74
78
|
workspaceRoot: false,
|
|
75
79
|
help: false,
|
|
76
80
|
version: false,
|
|
77
|
-
intTest: false,
|
|
78
81
|
passthrough: []
|
|
79
82
|
};
|
|
80
83
|
let i = 0;
|
|
@@ -85,7 +88,7 @@ function parseArgs(args) {
|
|
|
85
88
|
i++;
|
|
86
89
|
const filterValue = args[i];
|
|
87
90
|
if (filterValue === void 0) {
|
|
88
|
-
return { ok: false, error: "
|
|
91
|
+
return { ok: false, error: "-F/--filter requires a pattern argument" };
|
|
89
92
|
}
|
|
90
93
|
parsed.filter = filterValue;
|
|
91
94
|
i++;
|
|
@@ -116,11 +119,6 @@ function parseArgs(args) {
|
|
|
116
119
|
i++;
|
|
117
120
|
continue;
|
|
118
121
|
}
|
|
119
|
-
if (arg === "--int-test") {
|
|
120
|
-
parsed.intTest = true;
|
|
121
|
-
i++;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
122
|
parsed.command = arg;
|
|
125
123
|
parsed.passthrough = args.slice(i + 1);
|
|
126
124
|
break;
|
package/dist/esm/runner.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import process from "node:process";
|
|
3
|
+
import { reportError } from "@williamthorsen/nmr-core";
|
|
3
4
|
function runCommand(command, cwd, options) {
|
|
4
5
|
const quiet = options?.quiet === true;
|
|
5
6
|
const stdout = options?.stdout ?? process.stdout;
|
|
@@ -14,8 +15,7 @@ function runCommand(command, cwd, options) {
|
|
|
14
15
|
env
|
|
15
16
|
});
|
|
16
17
|
if (result.error) {
|
|
17
|
-
|
|
18
|
-
`);
|
|
18
|
+
reportError(result.error.message, stderr);
|
|
19
19
|
return 1;
|
|
20
20
|
}
|
|
21
21
|
if (quiet) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/nmr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Context-aware script runner for PNPM monorepos",
|
|
6
6
|
"keywords": [
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"jiti": "2.7.0",
|
|
46
46
|
"yaml": "2.9.0",
|
|
47
47
|
"zod": "4.4.3",
|
|
48
|
-
"@williamthorsen/nmr-core": "0.
|
|
48
|
+
"@williamthorsen/nmr-core": "0.5.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@williamthorsen/toolbelt.strings": "3.1.4"
|