@williamthorsen/nmr 0.13.0 → 0.14.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/AGENTS.md +15 -3
- package/README.md +7 -25
- package/dist/esm/.cache +1 -1
- package/dist/esm/cli.js +11 -157
- package/dist/esm/context.d.ts +1 -1
- package/dist/esm/default-scripts.js +2 -2
- package/dist/esm/help.d.ts +1 -1
- package/dist/esm/resolve-scripts.d.ts +2 -2
- package/dist/esm/resolver.d.ts +2 -2
- package/dist/esm/runCli.d.ts +12 -0
- package/dist/esm/runCli.js +169 -0
- package/dist/esm/runner.d.ts +4 -0
- package/dist/esm/runner.js +29 -27
- package/dist/esm/scripts.d.ts +2 -2
- package/package.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -4,7 +4,7 @@ source: '@williamthorsen/nmr@0.0.0-source'
|
|
|
4
4
|
|
|
5
5
|
# nmr: agent guidance
|
|
6
6
|
|
|
7
|
-
This file is managed by `@williamthorsen/nmr`. Do not edit — re-run `
|
|
7
|
+
This file is managed by `@williamthorsen/nmr`. Do not edit — re-run `nmr sync-agent-files` after an nmr upgrade to refresh it.
|
|
8
8
|
|
|
9
9
|
## Discover scripts by running nmr
|
|
10
10
|
|
|
@@ -13,8 +13,20 @@ Run `nmr` with no command (from the monorepo root or any workspace package) to l
|
|
|
13
13
|
## Invocation rules
|
|
14
14
|
|
|
15
15
|
- Use `nmr <command>` for anything nmr provides. Do not use `pnpm run <command>`.
|
|
16
|
-
-
|
|
17
|
-
|
|
16
|
+
- You can invoke nmr from the monorepo root or any workspace package.
|
|
17
|
+
|
|
18
|
+
### How to make `nmr` resolvable
|
|
19
|
+
|
|
20
|
+
`nmr` ships as a workspace bin. The bare `nmr` command works only when your shell can find `<root>/node_modules/.bin/nmr`. Choose one:
|
|
21
|
+
|
|
22
|
+
- **direnv** (recommended for contributors). With [direnv](https://direnv.net/) installed, the repo's `.envrc` adds `node_modules/.bin` to your `PATH` automatically. From any subdirectory, bare `nmr` works.
|
|
23
|
+
- **`pnpm exec nmr <command>`** — works without setup. pnpm resolves the bin from the workspace root.
|
|
24
|
+
|
|
25
|
+
Avoid `npx nmr`. Inside git worktrees, `npx` can resolve a different nmr binary from outside the working tree.
|
|
26
|
+
|
|
27
|
+
### Bootstrap fallback
|
|
28
|
+
|
|
29
|
+
If `nmr` itself fails to run (fresh clone, missing build output), run `pnpm run bootstrap` from the repo root first.
|
|
18
30
|
|
|
19
31
|
## Root vs. workspace context
|
|
20
32
|
|
package/README.md
CHANGED
|
@@ -2,25 +2,7 @@
|
|
|
2
2
|
|
|
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
|
-
<!-- section:release-notes -->
|
|
6
|
-
## Release notes — v0.13.0 (2026-05-04)
|
|
7
|
-
|
|
8
|
-
### 🎉 Features
|
|
9
|
-
|
|
10
|
-
- Add :pre and :post hook conventions to nmr commands (#339)
|
|
11
|
-
|
|
12
|
-
Adds `:pre` and `:post` hook conventions to nmr's command runner. Consumers can declare `X:pre` or `X:post` scripts that nmr runs automatically before and after script `X`. The hook commands are optional and ignored if missing. Hooks fire even when the main command is overridden, and direct invocations like `nmr build:pre` are treated as leaf operations rather than recursively cascading into `build:pre:pre` lookups.
|
|
13
|
-
|
|
14
|
-
- Filter `nmr --help` to nmr commands (#348)
|
|
15
|
-
|
|
16
|
-
Restricts `nmr --help` to nmr commands only. Hook scripts (names ending in `:pre` or `:post`) are now hidden from help, and generic `package.json` lifecycle scripts (`prepare`, `postinstall`, `bootstrap`) no longer appear. When a `package.json` script overrides a built-in nmr command, the override is shown inline alongside the command name with a `*` marker, and a single `* Overridden by package.json` footnote is appended whenever any marker is rendered. Help also now reflects the active resolution context: invoked from a subpackage you see workspace-level overrides; invoked from the repo root (or with `-w`) you see root-level overrides.
|
|
17
|
-
|
|
18
|
-
### 🐛 Bug fixes
|
|
19
|
-
|
|
20
|
-
- Honor -w flag in composite-script step subprocesses (#346)
|
|
21
|
-
|
|
22
|
-
Fixes an issue where invoking `nmr -w <command>` from inside a workspace package, with `<command>` resolving to a composite (multi-step) script defined at the root level, failed with `Unknown command` for any step that was reachable only via the root script registry. The `-w` flag is now propagated to every step's subprocess invocation, so composite scripts run end-to-end regardless of which directory they are invoked from.
|
|
23
|
-
<!-- /section:release-notes -->
|
|
5
|
+
<!-- section:release-notes --><!-- /section:release-notes -->
|
|
24
6
|
|
|
25
7
|
## Installation
|
|
26
8
|
|
|
@@ -63,7 +45,7 @@ nmr's key feature is that the same command runs different scripts depending on w
|
|
|
63
45
|
Use `-w` to escape package context:
|
|
64
46
|
|
|
65
47
|
```bash
|
|
66
|
-
# From inside packages/core, run the root check suite
|
|
48
|
+
# From inside packages/nmr-core, run the root check suite
|
|
67
49
|
nmr -w check
|
|
68
50
|
```
|
|
69
51
|
|
|
@@ -238,11 +220,11 @@ Packages with a `vitest.integration.config.ts` file get different test commands.
|
|
|
238
220
|
|
|
239
221
|
#### Audit
|
|
240
222
|
|
|
241
|
-
| Command | Runs
|
|
242
|
-
| ------------ |
|
|
243
|
-
| `audit` | `audit:prod`, `audit:dev`
|
|
244
|
-
| `audit:dev` | `pnpm exec
|
|
245
|
-
| `audit:prod` | `pnpm exec
|
|
223
|
+
| Command | Runs |
|
|
224
|
+
| ------------ | ------------------------- |
|
|
225
|
+
| `audit` | `audit:prod`, `audit:dev` |
|
|
226
|
+
| `audit:dev` | `pnpm exec v11y --dev` |
|
|
227
|
+
| `audit:prod` | `pnpm exec v11y --prod` |
|
|
246
228
|
|
|
247
229
|
#### Dependencies
|
|
248
230
|
|
package/dist/esm/.cache
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
100ea1821185a3e234d52dd9a44609bf7ead7593a0e877d8d1c331754f7883a4
|
package/dist/esm/cli.js
CHANGED
|
@@ -1,162 +1,16 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
1
|
import process from "node:process";
|
|
3
|
-
import {
|
|
4
|
-
import { resolveContext } from "./context.js";
|
|
5
|
-
import { generateHelp } from "./help.js";
|
|
6
|
-
import { isHookName } from "./helpers/hook-name.js";
|
|
7
|
-
import { applyDevBin, buildRootRegistry, buildWorkspaceRegistry, resolveScript } from "./resolver.js";
|
|
8
|
-
import { runCommand } from "./runner.js";
|
|
9
|
-
const VERSION = readPackageVersion(import.meta.url);
|
|
10
|
-
function shellQuote(arg) {
|
|
11
|
-
return "'" + arg.replace(/'/g, String.raw`'\''`) + "'";
|
|
12
|
-
}
|
|
13
|
-
function parseArgs(argv) {
|
|
14
|
-
const args = argv.slice(2);
|
|
15
|
-
const result = {
|
|
16
|
-
quiet: false,
|
|
17
|
-
recursive: false,
|
|
18
|
-
workspaceRoot: false,
|
|
19
|
-
help: false,
|
|
20
|
-
version: false,
|
|
21
|
-
intTest: false,
|
|
22
|
-
passthrough: []
|
|
23
|
-
};
|
|
24
|
-
let i = 0;
|
|
25
|
-
while (i < args.length) {
|
|
26
|
-
const arg = args[i];
|
|
27
|
-
if (arg === void 0) break;
|
|
28
|
-
if (arg === "-F" || arg === "--filter") {
|
|
29
|
-
i++;
|
|
30
|
-
const filterValue = args[i];
|
|
31
|
-
if (filterValue === void 0) {
|
|
32
|
-
console.error("Error: -F/--filter requires a pattern argument");
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
result.filter = filterValue;
|
|
36
|
-
i++;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (arg === "-R" || arg === "--recursive") {
|
|
40
|
-
result.recursive = true;
|
|
41
|
-
i++;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (arg === "-w" || arg === "--workspace-root") {
|
|
45
|
-
result.workspaceRoot = true;
|
|
46
|
-
i++;
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (arg === "-?" || arg === "--help") {
|
|
50
|
-
result.help = true;
|
|
51
|
-
i++;
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (arg === "-V" || arg === "--version") {
|
|
55
|
-
result.version = true;
|
|
56
|
-
i++;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (arg === "-q" || arg === "--quiet") {
|
|
60
|
-
result.quiet = true;
|
|
61
|
-
i++;
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (arg === "--int-test") {
|
|
65
|
-
result.intTest = true;
|
|
66
|
-
i++;
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
result.command = arg;
|
|
70
|
-
result.passthrough = args.slice(i + 1);
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
return result;
|
|
74
|
-
}
|
|
75
|
-
async function main() {
|
|
76
|
-
const parsed = parseArgs(process.argv);
|
|
77
|
-
if (parsed.version) {
|
|
78
|
-
console.info(VERSION);
|
|
79
|
-
process.exit(0);
|
|
80
|
-
}
|
|
81
|
-
const context = await resolveContext();
|
|
82
|
-
const useRoot = parsed.workspaceRoot || context.isRoot;
|
|
83
|
-
const packageDir = useRoot ? context.monorepoRoot : context.packageDir ?? context.monorepoRoot;
|
|
84
|
-
if (parsed.help || !parsed.command) {
|
|
85
|
-
console.info(generateHelp(context.config, packageDir, useRoot));
|
|
86
|
-
process.exit(0);
|
|
87
|
-
}
|
|
88
|
-
const { command } = parsed;
|
|
89
|
-
const passthrough = parsed.passthrough.length > 0 ? " " + parsed.passthrough.map(shellQuote).join(" ") : "";
|
|
90
|
-
const runOptions = { quiet: parsed.quiet };
|
|
91
|
-
if (parsed.filter) {
|
|
92
|
-
const delegateCmd = `pnpm --filter ${shellQuote(parsed.filter)} exec nmr ${command}${passthrough}`;
|
|
93
|
-
const code2 = runCommand(delegateCmd, context.monorepoRoot, runOptions);
|
|
94
|
-
process.exit(code2);
|
|
95
|
-
}
|
|
96
|
-
if (parsed.recursive) {
|
|
97
|
-
process.env.NMR_RUN_IF_PRESENT = "1";
|
|
98
|
-
const delegateCmd = `pnpm --recursive exec nmr ${command}${passthrough}`;
|
|
99
|
-
const code2 = runCommand(delegateCmd, context.monorepoRoot, runOptions);
|
|
100
|
-
process.exit(code2);
|
|
101
|
-
}
|
|
102
|
-
const registry = useRoot ? buildRootRegistry(context.config) : buildWorkspaceRegistry(context.config, parsed.intTest);
|
|
103
|
-
const resolved = resolveScript(command, registry, packageDir, parsed.workspaceRoot);
|
|
104
|
-
if (!resolved) {
|
|
105
|
-
if (process.env.NMR_RUN_IF_PRESENT === "1") {
|
|
106
|
-
process.exit(0);
|
|
107
|
-
}
|
|
108
|
-
console.error(`Unknown command: ${command}`);
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
const skipExitCode = handleSkipMessage(resolved.command, packageDir, parsed.quiet);
|
|
112
|
-
if (skipExitCode !== void 0) {
|
|
113
|
-
process.exit(skipExitCode);
|
|
114
|
-
}
|
|
115
|
-
if (resolved.source === "package" && !parsed.quiet && registry[command] !== void 0) {
|
|
116
|
-
console.info(`\u{1F4E6} ${path.basename(packageDir)}: Using override script: ${resolved.command}`);
|
|
117
|
-
}
|
|
118
|
-
const substitutedCommand = applyDevBin(resolved.command, context.config.devBin, context.monorepoRoot);
|
|
119
|
-
const mainCommand = substitutedCommand + passthrough;
|
|
120
|
-
const isHookInvocation = isHookName(command);
|
|
121
|
-
const fullCommand = isHookInvocation ? mainCommand : wrapWithHooks(command, mainCommand, registry, packageDir, parsed.workspaceRoot);
|
|
122
|
-
const code = runCommand(fullCommand, void 0, runOptions);
|
|
123
|
-
process.exit(code);
|
|
124
|
-
}
|
|
125
|
-
function handleSkipMessage(resolvedCommand, packageDir, quiet) {
|
|
126
|
-
if (resolvedCommand === "") {
|
|
127
|
-
if (!quiet) {
|
|
128
|
-
console.info(`\u26D4 ${path.basename(packageDir)}: Override script is defined but empty. Skipping.`);
|
|
129
|
-
}
|
|
130
|
-
return 0;
|
|
131
|
-
}
|
|
132
|
-
if (resolvedCommand === ":") {
|
|
133
|
-
if (!quiet) {
|
|
134
|
-
console.info(`\u26D4 ${path.basename(packageDir)}: Override script is a no-op. Skipping.`);
|
|
135
|
-
}
|
|
136
|
-
return 0;
|
|
137
|
-
}
|
|
138
|
-
return void 0;
|
|
139
|
-
}
|
|
140
|
-
function wrapWithHooks(command, mainCommand, registry, packageDir, workspaceRoot) {
|
|
141
|
-
const segments = [];
|
|
142
|
-
const flag = workspaceRoot ? "-w " : "";
|
|
143
|
-
if (hasRunnableHook(`${command}:pre`, registry, packageDir, workspaceRoot)) {
|
|
144
|
-
segments.push(`nmr ${flag}${command}:pre`);
|
|
145
|
-
}
|
|
146
|
-
segments.push(mainCommand);
|
|
147
|
-
if (hasRunnableHook(`${command}:post`, registry, packageDir, workspaceRoot)) {
|
|
148
|
-
segments.push(`nmr ${flag}${command}:post`);
|
|
149
|
-
}
|
|
150
|
-
return segments.join(" && ");
|
|
151
|
-
}
|
|
152
|
-
function hasRunnableHook(hookName, registry, packageDir, workspaceRoot) {
|
|
153
|
-
const resolved = resolveScript(hookName, registry, packageDir, workspaceRoot);
|
|
154
|
-
if (!resolved) return false;
|
|
155
|
-
return resolved.command !== "" && resolved.command !== ":";
|
|
156
|
-
}
|
|
2
|
+
import { runCli } from "./runCli.js";
|
|
157
3
|
try {
|
|
158
|
-
await
|
|
4
|
+
const { exitCode } = await runCli({
|
|
5
|
+
args: process.argv.slice(2),
|
|
6
|
+
cwd: process.cwd(),
|
|
7
|
+
env: process.env,
|
|
8
|
+
stdout: process.stdout,
|
|
9
|
+
stderr: process.stderr
|
|
10
|
+
});
|
|
11
|
+
process.exit(exitCode);
|
|
159
12
|
} catch (error) {
|
|
160
|
-
|
|
13
|
+
process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}
|
|
14
|
+
`);
|
|
161
15
|
process.exit(1);
|
|
162
16
|
}
|
package/dist/esm/context.d.ts
CHANGED
|
@@ -28,8 +28,8 @@ const standardTestScripts = {
|
|
|
28
28
|
};
|
|
29
29
|
const rootScripts = {
|
|
30
30
|
audit: ["audit:prod", "audit:dev"],
|
|
31
|
-
"audit:dev": "pnpm exec
|
|
32
|
-
"audit:prod": "pnpm exec
|
|
31
|
+
"audit:dev": "pnpm exec v11y --dev",
|
|
32
|
+
"audit:prod": "pnpm exec v11y --prod",
|
|
33
33
|
build: "pnpm --recursive exec nmr build",
|
|
34
34
|
check: ["typecheck", "fmt:check", "lint:check", "test"],
|
|
35
35
|
"check:agent-files": "nmr-sync-agent-files --check",
|
package/dist/esm/help.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { NmrConfig } from './config.
|
|
1
|
+
import type { NmrConfig } from './config.ts';
|
|
2
2
|
export declare function generateHelp(config: NmrConfig, packageDir: string | undefined, useRoot: boolean): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ScriptRegistry } from './default-scripts.
|
|
2
|
-
export type { ScriptRegistry, ScriptValue } from './default-scripts.
|
|
1
|
+
import type { ScriptRegistry } from './default-scripts.ts';
|
|
2
|
+
export type { ScriptRegistry, ScriptValue } from './default-scripts.ts';
|
|
3
3
|
export declare function getDefaultWorkspaceScripts(useIntTests: boolean): ScriptRegistry;
|
|
4
4
|
export declare function getDefaultRootScripts(): ScriptRegistry;
|
package/dist/esm/resolver.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { NmrConfig } from './config.
|
|
2
|
-
import type { ScriptRegistry, ScriptValue } from './resolve-scripts.
|
|
1
|
+
import type { NmrConfig } from './config.ts';
|
|
2
|
+
import type { ScriptRegistry, ScriptValue } from './resolve-scripts.ts';
|
|
3
3
|
export declare function applyDevBin(command: string, devBin: Record<string, string> | undefined, monorepoRoot: string): string;
|
|
4
4
|
export interface ResolvedScript {
|
|
5
5
|
command: string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Writable } from 'node:stream';
|
|
2
|
+
export interface RunCliOptions {
|
|
3
|
+
args: string[];
|
|
4
|
+
cwd: string;
|
|
5
|
+
env: NodeJS.ProcessEnv;
|
|
6
|
+
stdout: Writable;
|
|
7
|
+
stderr: Writable;
|
|
8
|
+
}
|
|
9
|
+
export interface RunCliResult {
|
|
10
|
+
exitCode: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function runCli(options: RunCliOptions): Promise<RunCliResult>;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readPackageVersion } from "@williamthorsen/nmr-core";
|
|
3
|
+
import { resolveContext } from "./context.js";
|
|
4
|
+
import { generateHelp } from "./help.js";
|
|
5
|
+
import { isHookName } from "./helpers/hook-name.js";
|
|
6
|
+
import { applyDevBin, buildRootRegistry, buildWorkspaceRegistry, resolveScript } from "./resolver.js";
|
|
7
|
+
import { runCommand } from "./runner.js";
|
|
8
|
+
const VERSION = readPackageVersion(import.meta.url);
|
|
9
|
+
async function runCli(options) {
|
|
10
|
+
const { args, cwd, env, stdout, stderr } = options;
|
|
11
|
+
const parseResult = parseArgs(args);
|
|
12
|
+
if (!parseResult.ok) {
|
|
13
|
+
stderr.write(`${parseResult.error}
|
|
14
|
+
`);
|
|
15
|
+
return { exitCode: 1 };
|
|
16
|
+
}
|
|
17
|
+
const { parsed } = parseResult;
|
|
18
|
+
if (parsed.version) {
|
|
19
|
+
stdout.write(`${VERSION}
|
|
20
|
+
`);
|
|
21
|
+
return { exitCode: 0 };
|
|
22
|
+
}
|
|
23
|
+
const context = await resolveContext(cwd);
|
|
24
|
+
const useRoot = parsed.workspaceRoot || context.isRoot;
|
|
25
|
+
const packageDir = useRoot ? context.monorepoRoot : context.packageDir ?? context.monorepoRoot;
|
|
26
|
+
if (parsed.help || !parsed.command) {
|
|
27
|
+
stdout.write(`${generateHelp(context.config, packageDir, useRoot)}
|
|
28
|
+
`);
|
|
29
|
+
return { exitCode: 0 };
|
|
30
|
+
}
|
|
31
|
+
const { command } = parsed;
|
|
32
|
+
const passthrough = parsed.passthrough.length > 0 ? " " + parsed.passthrough.map(shellQuote).join(" ") : "";
|
|
33
|
+
const runOptions = { quiet: parsed.quiet, stdout, stderr, env };
|
|
34
|
+
if (parsed.filter) {
|
|
35
|
+
const delegateCmd = `pnpm --filter ${shellQuote(parsed.filter)} exec nmr ${command}${passthrough}`;
|
|
36
|
+
const exitCode2 = runCommand(delegateCmd, context.monorepoRoot, runOptions);
|
|
37
|
+
return { exitCode: exitCode2 };
|
|
38
|
+
}
|
|
39
|
+
if (parsed.recursive) {
|
|
40
|
+
const childEnv = { ...env, NMR_RUN_IF_PRESENT: "1" };
|
|
41
|
+
const delegateCmd = `pnpm --recursive exec nmr ${command}${passthrough}`;
|
|
42
|
+
const exitCode2 = runCommand(delegateCmd, context.monorepoRoot, { ...runOptions, env: childEnv });
|
|
43
|
+
return { exitCode: exitCode2 };
|
|
44
|
+
}
|
|
45
|
+
const registry = useRoot ? buildRootRegistry(context.config) : buildWorkspaceRegistry(context.config, parsed.intTest);
|
|
46
|
+
const resolved = resolveScript(command, registry, packageDir, parsed.workspaceRoot);
|
|
47
|
+
if (!resolved) {
|
|
48
|
+
if (env.NMR_RUN_IF_PRESENT === "1") {
|
|
49
|
+
return { exitCode: 0 };
|
|
50
|
+
}
|
|
51
|
+
stderr.write(`Unknown command: ${command}
|
|
52
|
+
`);
|
|
53
|
+
return { exitCode: 1 };
|
|
54
|
+
}
|
|
55
|
+
const skipExitCode = handleSkipMessage(resolved.command, packageDir, parsed.quiet, stdout);
|
|
56
|
+
if (skipExitCode !== void 0) {
|
|
57
|
+
return { exitCode: skipExitCode };
|
|
58
|
+
}
|
|
59
|
+
if (resolved.source === "package" && !parsed.quiet && registry[command] !== void 0) {
|
|
60
|
+
stdout.write(`\u{1F4E6} ${path.basename(packageDir)}: Using override script: ${resolved.command}
|
|
61
|
+
`);
|
|
62
|
+
}
|
|
63
|
+
const substitutedCommand = applyDevBin(resolved.command, context.config.devBin, context.monorepoRoot);
|
|
64
|
+
const mainCommand = substitutedCommand + passthrough;
|
|
65
|
+
const isHookInvocation = isHookName(command);
|
|
66
|
+
const fullCommand = isHookInvocation ? mainCommand : wrapWithHooks(command, mainCommand, registry, packageDir, parsed.workspaceRoot);
|
|
67
|
+
const exitCode = runCommand(fullCommand, cwd, runOptions);
|
|
68
|
+
return { exitCode };
|
|
69
|
+
}
|
|
70
|
+
function parseArgs(args) {
|
|
71
|
+
const parsed = {
|
|
72
|
+
quiet: false,
|
|
73
|
+
recursive: false,
|
|
74
|
+
workspaceRoot: false,
|
|
75
|
+
help: false,
|
|
76
|
+
version: false,
|
|
77
|
+
intTest: false,
|
|
78
|
+
passthrough: []
|
|
79
|
+
};
|
|
80
|
+
let i = 0;
|
|
81
|
+
while (i < args.length) {
|
|
82
|
+
const arg = args[i];
|
|
83
|
+
if (arg === void 0) break;
|
|
84
|
+
if (arg === "-F" || arg === "--filter") {
|
|
85
|
+
i++;
|
|
86
|
+
const filterValue = args[i];
|
|
87
|
+
if (filterValue === void 0) {
|
|
88
|
+
return { ok: false, error: "Error: -F/--filter requires a pattern argument" };
|
|
89
|
+
}
|
|
90
|
+
parsed.filter = filterValue;
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (arg === "-R" || arg === "--recursive") {
|
|
95
|
+
parsed.recursive = true;
|
|
96
|
+
i++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (arg === "-w" || arg === "--workspace-root") {
|
|
100
|
+
parsed.workspaceRoot = true;
|
|
101
|
+
i++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (arg === "-?" || arg === "--help") {
|
|
105
|
+
parsed.help = true;
|
|
106
|
+
i++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (arg === "-V" || arg === "--version") {
|
|
110
|
+
parsed.version = true;
|
|
111
|
+
i++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (arg === "-q" || arg === "--quiet") {
|
|
115
|
+
parsed.quiet = true;
|
|
116
|
+
i++;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (arg === "--int-test") {
|
|
120
|
+
parsed.intTest = true;
|
|
121
|
+
i++;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
parsed.command = arg;
|
|
125
|
+
parsed.passthrough = args.slice(i + 1);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
return { ok: true, parsed };
|
|
129
|
+
}
|
|
130
|
+
function handleSkipMessage(resolvedCommand, packageDir, quiet, stdout) {
|
|
131
|
+
if (resolvedCommand === "") {
|
|
132
|
+
if (!quiet) {
|
|
133
|
+
stdout.write(`\u26D4 ${path.basename(packageDir)}: Override script is defined but empty. Skipping.
|
|
134
|
+
`);
|
|
135
|
+
}
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
if (resolvedCommand === ":") {
|
|
139
|
+
if (!quiet) {
|
|
140
|
+
stdout.write(`\u26D4 ${path.basename(packageDir)}: Override script is a no-op. Skipping.
|
|
141
|
+
`);
|
|
142
|
+
}
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
return void 0;
|
|
146
|
+
}
|
|
147
|
+
function wrapWithHooks(command, mainCommand, registry, packageDir, workspaceRoot) {
|
|
148
|
+
const segments = [];
|
|
149
|
+
const flag = workspaceRoot ? "-w " : "";
|
|
150
|
+
if (hasRunnableHook(`${command}:pre`, registry, packageDir, workspaceRoot)) {
|
|
151
|
+
segments.push(`nmr ${flag}${command}:pre`);
|
|
152
|
+
}
|
|
153
|
+
segments.push(mainCommand);
|
|
154
|
+
if (hasRunnableHook(`${command}:post`, registry, packageDir, workspaceRoot)) {
|
|
155
|
+
segments.push(`nmr ${flag}${command}:post`);
|
|
156
|
+
}
|
|
157
|
+
return segments.join(" && ");
|
|
158
|
+
}
|
|
159
|
+
function hasRunnableHook(hookName, registry, packageDir, workspaceRoot) {
|
|
160
|
+
const resolved = resolveScript(hookName, registry, packageDir, workspaceRoot);
|
|
161
|
+
if (!resolved) return false;
|
|
162
|
+
return resolved.command !== "" && resolved.command !== ":";
|
|
163
|
+
}
|
|
164
|
+
function shellQuote(arg) {
|
|
165
|
+
return "'" + arg.replace(/'/g, String.raw`'\''`) + "'";
|
|
166
|
+
}
|
|
167
|
+
export {
|
|
168
|
+
runCli
|
|
169
|
+
};
|
package/dist/esm/runner.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import type { Writable } from 'node:stream';
|
|
1
2
|
export interface RunCommandOptions {
|
|
2
3
|
quiet?: boolean;
|
|
4
|
+
stdout?: Writable;
|
|
5
|
+
stderr?: Writable;
|
|
6
|
+
env?: NodeJS.ProcessEnv;
|
|
3
7
|
}
|
|
4
8
|
export declare function runCommand(command: string, cwd?: string, options?: RunCommandOptions): number;
|
package/dist/esm/runner.js
CHANGED
|
@@ -1,37 +1,39 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
function runCommand(command, cwd, options) {
|
|
4
4
|
const quiet = options?.quiet === true;
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
5
|
+
const stdout = options?.stdout ?? process.stdout;
|
|
6
|
+
const stderr = options?.stderr ?? process.stderr;
|
|
7
|
+
const env = options?.env ?? process.env;
|
|
8
|
+
const stdoutChannel = quiet ? "pipe" : streamFdOrPipe(stdout);
|
|
9
|
+
const stderrChannel = quiet ? "pipe" : streamFdOrPipe(stderr);
|
|
10
|
+
const result = spawnSync(command, [], {
|
|
11
|
+
shell: true,
|
|
12
|
+
stdio: ["inherit", stdoutChannel, stderrChannel],
|
|
13
|
+
cwd,
|
|
14
|
+
env
|
|
15
|
+
});
|
|
16
|
+
if (result.error) {
|
|
17
|
+
stderr.write(`${result.error.message}
|
|
18
|
+
`);
|
|
19
19
|
return 1;
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (Buffer.isBuffer(stdout) && stdout.length > 0) {
|
|
26
|
-
process.stderr.write(stdout);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
if ("stderr" in error) {
|
|
30
|
-
const { stderr } = error;
|
|
31
|
-
if (Buffer.isBuffer(stderr) && stderr.length > 0) {
|
|
32
|
-
process.stderr.write(stderr);
|
|
21
|
+
if (quiet) {
|
|
22
|
+
if (result.status !== 0) {
|
|
23
|
+
writeBuffer(result.stdout, stderr);
|
|
24
|
+
writeBuffer(result.stderr, stderr);
|
|
33
25
|
}
|
|
26
|
+
} else {
|
|
27
|
+
if (stdoutChannel === "pipe") writeBuffer(result.stdout, stdout);
|
|
28
|
+
if (stderrChannel === "pipe") writeBuffer(result.stderr, stderr);
|
|
34
29
|
}
|
|
30
|
+
return result.status ?? 1;
|
|
31
|
+
}
|
|
32
|
+
function streamFdOrPipe(stream) {
|
|
33
|
+
return "fd" in stream && typeof stream.fd === "number" ? stream.fd : "pipe";
|
|
34
|
+
}
|
|
35
|
+
function writeBuffer(buffer, dest) {
|
|
36
|
+
if (buffer.length > 0) dest.write(buffer);
|
|
35
37
|
}
|
|
36
38
|
export {
|
|
37
39
|
runCommand
|
package/dist/esm/scripts.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { ScriptRegistry, ScriptValue } from './default-scripts.
|
|
2
|
-
export { getDefaultRootScripts, getDefaultWorkspaceScripts } from './resolve-scripts.
|
|
1
|
+
export type { ScriptRegistry, ScriptValue } from './default-scripts.ts';
|
|
2
|
+
export { getDefaultRootScripts, getDefaultWorkspaceScripts } from './resolve-scripts.ts';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/nmr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Context-aware script runner for PNPM monorepos",
|
|
6
6
|
"keywords": [
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"dist"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"jiti": "2.
|
|
42
|
+
"jiti": "2.7.0",
|
|
43
43
|
"js-yaml": "4.1.1",
|
|
44
44
|
"zod": "4.4.3",
|
|
45
|
-
"@williamthorsen/nmr-core": "0.3.
|
|
45
|
+
"@williamthorsen/nmr-core": "0.3.2"
|
|
46
46
|
},
|
|
47
47
|
"publishConfig": {
|
|
48
48
|
"access": "public"
|