@williamthorsen/nmr 0.4.0 → 0.9.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 CHANGED
@@ -8,34 +8,74 @@ Context-aware script runner for PNPM monorepos. Ships an `nmr` (node-monorepo ru
8
8
  pnpm add -D @williamthorsen/nmr
9
9
  ```
10
10
 
11
- ## CLI usage
11
+ ## Quick start
12
12
 
13
+ nmr works out of the box with no configuration. It ships with built-in scripts for common monorepo tasks.
14
+
15
+ From a package directory:
16
+
17
+ ```bash
18
+ nmr test # Run tests for the current package
19
+ nmr build # Compile + generate typings
20
+ nmr check # Typecheck, format check, lint check, and tests
13
21
  ```
14
- nmr <command> # Context-aware: root vs package
15
- nmr -F, --filter <pattern> <command> # Run in matching packages
16
- nmr -R, --recursive <command> # Run in all packages
17
- nmr -w, --workspace-root <command> # Force root script registry
18
- nmr -?, --help # Show available commands
19
- nmr --int-test <command> # Use integration test scripts
22
+
23
+ From the monorepo root:
24
+
25
+ ```bash
26
+ nmr test # Run root tests + recursive workspace tests
27
+ nmr build # Build all packages
28
+ nmr ci # Run check:strict && build (full CI pipeline)
20
29
  ```
21
30
 
22
- ### Examples
31
+ nmr detects where you are and selects the right scripts automatically — see [context-aware resolution](#context-aware-resolution) below.
32
+
33
+ ## Context-aware resolution
34
+
35
+ nmr's key feature is that the same command runs different scripts depending on where you invoke it. It walks up from your current directory to find `pnpm-workspace.yaml`, then checks whether your CWD is inside a workspace package directory.
36
+
37
+ | Where you run `nmr` | Registry used | `nmr test` runs |
38
+ | -------------------------- | ----------------- | --------------------------------------------- |
39
+ | Monorepo root | Root scripts | Root tests + `pnpm --recursive exec nmr test` |
40
+ | Inside a workspace package | Workspace scripts | `pnpm exec vitest` (for that package only) |
41
+ | Anywhere, with `-w` flag | Root scripts | Forces root registry regardless of location |
42
+
43
+ Use `-w` to escape package context:
23
44
 
24
45
  ```bash
25
- # From a package directory
26
- nmr test # Runs workspace test script
27
- nmr build # Runs compile && generate-typings
46
+ # From inside packages/core, run the root check suite
47
+ nmr -w check
48
+ ```
28
49
 
29
- # From the monorepo root
30
- nmr test # Runs root test + recursive workspace tests
31
- nmr ci # Runs check:strict && build
50
+ ## Three-tier override system
32
51
 
33
- # Targeting specific packages
34
- nmr -F core test # Test only the core package
35
- nmr -R lint # Lint all workspace packages
52
+ Scripts resolve through three tiers. Higher tiers override lower ones:
36
53
 
37
- # Force root context from anywhere
38
- nmr -w check # Run root check from a package dir
54
+ 1. **Built-in defaults** scripts shipped with this package
55
+ 2. **Repo-wide config** additions and overrides in `.config/nmr.config.ts`
56
+ 3. **Per-package overrides** — scripts in a package's `package.json`
57
+
58
+ ### Resolution example
59
+
60
+ Given the `build` command for a package that defines its own build script:
61
+
62
+ | Tier | Source | Value | Wins? |
63
+ | ---- | ----------------------- | --------------------------------- | ----- |
64
+ | 1 | Built-in default | `['compile', 'generate-typings']` | — |
65
+ | 2 | `.config/nmr.config.ts` | _(not set)_ | — |
66
+ | 3 | `package.json` scripts | `"tsx custom-build.ts"` | ✓ |
67
+
68
+ If no per-package override exists, the highest-tier value that is set wins. Set a script to `""` in `package.json` to skip it for that package.
69
+
70
+ > **Tip:** If your repo uses `eslint-plugin-package-json/valid-scripts`, empty strings are flagged as invalid. Use `":"` (the POSIX null command) instead — nmr treats it as a regular override that exits successfully and does nothing.
71
+
72
+ ### Script values
73
+
74
+ Script values can be `string` or `string[]`. Arrays expand to chained `nmr` sub-invocations:
75
+
76
+ ```ts
77
+ // "build": ["compile", "generate-typings"]
78
+ // expands to: nmr compile && nmr generate-typings
39
79
  ```
40
80
 
41
81
  ## Configuration
@@ -55,19 +95,191 @@ export default defineConfig({
55
95
  });
56
96
  ```
57
97
 
58
- ## Three-tier override system
98
+ ### `defineConfig` fields
99
+
100
+ | Field | Type | Description |
101
+ | ------------------ | ------------------------------------ | -------------------------------------------------------------- |
102
+ | `workspaceScripts` | `Record<string, string \| string[]>` | Scripts added or overridden in the workspace registry (tier 2) |
103
+ | `rootScripts` | `Record<string, string \| string[]>` | Scripts added or overridden in the root registry (tier 2) |
104
+ | `devBin` | `Record<string, string>` | Map binary names to source-repo replacement commands |
59
105
 
60
- 1. **Package defaults** built-in scripts shipped with this package
61
- 2. **Repo-wide config** — additions/overrides in `.config/nmr.config.ts`
62
- 3. **Per-package overrides** — in a package's `package.json` `scripts` field
106
+ All fields are optional. `workspaceScripts` and `rootScripts` values follow the same `string | string[]` convention described in [script values](#script-values).
63
107
 
64
- Per-package overrides take highest precedence. Set a script to `""` in `package.json` to skip it for that package.
108
+ ### `devBin` source-repo binary substitution
65
109
 
66
- Script values can be `string` or `string[]`. Arrays expand to chained `nmr` invocations:
110
+ When developing a CLI tool inside the monorepo, the published binary may not reflect your latest source changes. `devBin` lets you map a binary name to a replacement command that runs from source:
67
111
 
68
112
  ```ts
69
- // "build": ["compile", "generate-typings"]
70
- // expands to: nmr compile && nmr generate-typings
113
+ export default defineConfig({
114
+ devBin: {
115
+ 'my-cli': 'tsx packages/my-cli/src/cli.ts',
116
+ },
117
+ });
118
+ ```
119
+
120
+ When nmr resolves a command whose first token matches a `devBin` key, it replaces that token with the mapped command. Arguments are preserved. Relative paths in the replacement are resolved from the monorepo root.
121
+
122
+ 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`.
123
+
124
+ > **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.
125
+
126
+ ## Default script registries
127
+
128
+ These scripts are available out of the box. Repo-wide config (tier 2) and per-package overrides (tier 3) can add to or replace any of them.
129
+
130
+ ### Workspace scripts
131
+
132
+ | Command | Runs |
133
+ | ------------------ | -------------------------------------------------------- |
134
+ | `build` | `compile`, `generate-typings` |
135
+ | `check` | `typecheck`, `fmt:check`, `lint:check`, `test` |
136
+ | `check:strict` | `typecheck`, `fmt:check`, `lint:strict`, `test:coverage` |
137
+ | `clean` | `pnpm exec rimraf dist/*` |
138
+ | `compile` | `tsx ../../config/build.ts` |
139
+ | `fix` | `lint`, `fmt` |
140
+ | `fmt` | `prettier --list-different --write .` |
141
+ | `fmt:check` | `prettier --check .` |
142
+ | `generate-typings` | `tsc --project tsconfig.generate-typings.json` |
143
+ | `lint` | `eslint --fix .` |
144
+ | `lint:check` | `eslint .` |
145
+ | `lint:strict` | `strict-lint` |
146
+ | `test` | `pnpm exec vitest` |
147
+ | `test:coverage` | `pnpm exec vitest --coverage` |
148
+ | `test:watch` | `pnpm exec vitest --watch` |
149
+ | `typecheck` | `tsgo --noEmit` |
150
+ | `view-coverage` | `open coverage/index.html` |
151
+
152
+ #### Integration test variant
153
+
154
+ Packages with a `vitest.integration.config.ts` file get different test commands. Use the `--int-test` flag to select this variant.
155
+
156
+ | Command | Runs |
157
+ | ------------------ | ------------------------------------------------------------------ |
158
+ | `test` | `pnpm exec vitest --config=vitest.standalone.config.ts` |
159
+ | `test:coverage` | `pnpm exec vitest --config=vitest.standalone.config.ts --coverage` |
160
+ | `test:integration` | `pnpm exec vitest --config=vitest.integration.config.ts` |
161
+ | `test:watch` | `pnpm exec vitest --config=vitest.standalone.config.ts --watch` |
162
+
163
+ ### Root scripts
164
+
165
+ #### Build and CI
166
+
167
+ | Command | Runs |
168
+ | ------- | --------------------------------- |
169
+ | `build` | `pnpm --recursive exec nmr build` |
170
+ | `ci` | `build`, `check:strict` |
171
+ | `clean` | `pnpm --recursive exec nmr clean` |
172
+
173
+ #### Check and quality
174
+
175
+ | Command | Runs |
176
+ | -------------- | ----------------------------------------------------------------- |
177
+ | `check` | `typecheck`, `fmt:check`, `lint:check`, `test` |
178
+ | `check:strict` | `typecheck`, `fmt:check`, `audit`, `lint:strict`, `test:coverage` |
179
+
180
+ #### Test
181
+
182
+ | Command | Runs |
183
+ | --------------- | ---------------------------------------------------------- |
184
+ | `test` | `nmr root:test && pnpm --recursive exec nmr test` |
185
+ | `test:coverage` | `nmr root:test && pnpm --recursive exec nmr test:coverage` |
186
+ | `test:watch` | `vitest --watch` |
187
+
188
+ #### Typecheck
189
+
190
+ | Command | Runs |
191
+ | ----------- | ----------------------------------------------------------- |
192
+ | `typecheck` | `nmr root:typecheck && pnpm --recursive exec nmr typecheck` |
193
+
194
+ #### Lint
195
+
196
+ | Command | Runs |
197
+ | ------------- | --------------------------------------------------------------- |
198
+ | `lint` | `nmr root:lint && pnpm --recursive exec nmr lint` |
199
+ | `lint:check` | `nmr root:lint:check && pnpm --recursive exec nmr lint:check` |
200
+ | `lint:strict` | `nmr root:lint:strict && pnpm --recursive exec nmr lint:strict` |
201
+
202
+ #### Format
203
+
204
+ | Command | Runs |
205
+ | ----------- | ------------------------------------- |
206
+ | `fmt` | `prettier --list-different --write .` |
207
+ | `fmt:all` | `fmt`, `fmt:sh` |
208
+ | `fmt:check` | `prettier --check .` |
209
+ | `fmt:sh` | `shfmt --write **/*.sh` |
210
+
211
+ #### Audit
212
+
213
+ | Command | Runs |
214
+ | ------------ | ----------------------------------------------------------- |
215
+ | `audit` | `audit:prod`, `audit:dev` |
216
+ | `audit:dev` | `pnpm dlx audit-ci@^6 --config .audit-ci/config.dev.json5` |
217
+ | `audit:prod` | `pnpm dlx audit-ci@^6 --config .audit-ci/config.prod.json5` |
218
+
219
+ #### Dependencies
220
+
221
+ | Command | Runs |
222
+ | ----------------- | ---------------------------------------- |
223
+ | `outdated` | `pnpm outdated --compatible --recursive` |
224
+ | `outdated:latest` | `pnpm outdated --recursive` |
225
+ | `update` | `pnpm update --recursive` |
226
+ | `update:latest` | `pnpm update --latest --recursive` |
227
+
228
+ #### Root-only
229
+
230
+ These scripts operate on root-level code only (not workspace packages):
231
+
232
+ | Command | Runs |
233
+ | ------------------ | ------------------------------------------------------------- |
234
+ | `root:check` | `root:typecheck`, `fmt:check`, `root:lint:check`, `root:test` |
235
+ | `root:lint` | `eslint --fix --ignore-pattern 'packages/**' .` |
236
+ | `root:lint:check` | `eslint --ignore-pattern 'packages/**' .` |
237
+ | `root:lint:strict` | `strict-lint --ignore-pattern 'packages/**' .` |
238
+ | `root:test` | `vitest --config ./vitest.root.config.ts` |
239
+ | `root:typecheck` | `tsgo --noEmit` |
240
+
241
+ #### Utilities
242
+
243
+ | Command | Runs |
244
+ | ------------------- | ----------------------- |
245
+ | `report-overrides` | `nmr-report-overrides` |
246
+ | `sync-pnpm-version` | `nmr-sync-pnpm-version` |
247
+
248
+ ## CLI reference
249
+
250
+ ### `nmr`
251
+
252
+ ```
253
+ nmr [flags] <command> [args...]
254
+ ```
255
+
256
+ | Flag | Description | Default |
257
+ | ------------------------ | --------------------------------------------------- | ------- |
258
+ | `-F, --filter <pattern>` | Run command in matching packages | — |
259
+ | `-R, --recursive` | Run command in all packages | — |
260
+ | `-w, --workspace-root` | Force root script registry | — |
261
+ | `-q, --quiet` | Suppress info messages; show full output on failure | — |
262
+ | `-?, --help` | Show available commands | — |
263
+ | `-V, --version` | Show version number | — |
264
+ | `--int-test` | Use integration test scripts | — |
265
+
266
+ ### Examples
267
+
268
+ ```bash
269
+ # From a package directory
270
+ nmr test # Run workspace test script
271
+ nmr build # Compile + generate typings
272
+
273
+ # From the monorepo root
274
+ nmr test # Root tests + recursive workspace tests
275
+ nmr ci # check:strict + build
276
+
277
+ # Target specific packages
278
+ nmr -F core test # Test only the core package
279
+ nmr -R lint # Lint all workspace packages
280
+
281
+ # Force root context from anywhere
282
+ nmr -w check # Run root check from a package dir
71
283
  ```
72
284
 
73
285
  ## Additional subcommands
@@ -97,12 +309,15 @@ nmr sync-pnpm-version
97
309
  Verify that all publishable workspace packages have a `prepublishOnly` script. Exits non-zero if any are missing.
98
310
 
99
311
  ```bash
100
- ensure-prepublish-hooks # Check only
101
- ensure-prepublish-hooks --fix # Add missing hooks (default: "npm run build")
102
- ensure-prepublish-hooks --dry-run # Preview what --fix would do
103
- ensure-prepublish-hooks --command "pnpm build" # Use a custom hook command
312
+ ensure-prepublish-hooks
104
313
  ```
105
314
 
315
+ | Flag | Description | Default |
316
+ | --------------------- | ----------------------------- | ----------------- |
317
+ | `--fix` | Add missing hooks | — |
318
+ | `--dry-run` | Preview what `--fix` would do | — |
319
+ | `--command <command>` | Custom hook command | `"npm run build"` |
320
+
106
321
  ## Consumer migration
107
322
 
108
323
  After installing, a consuming repo's root `package.json` scripts shrink to lifecycle hooks:
@@ -1,2 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import('../dist/esm/cli-ensure-prepublish-hooks.js');
2
+ try {
3
+ await import('../dist/esm/cli-ensure-prepublish-hooks.js');
4
+ } catch (error) {
5
+ if (error.code === 'ERR_MODULE_NOT_FOUND') {
6
+ process.stderr.write('ensure-prepublish-hooks: build output not found — run `pnpm run build` first\n');
7
+ } else {
8
+ process.stderr.write(`ensure-prepublish-hooks: failed to load: ${error.message}\n`);
9
+ }
10
+ process.exit(1);
11
+ }
@@ -1,2 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import('../dist/esm/cli-report-overrides.js');
2
+ try {
3
+ await import('../dist/esm/cli-report-overrides.js');
4
+ } catch (error) {
5
+ if (error.code === 'ERR_MODULE_NOT_FOUND') {
6
+ process.stderr.write('nmr-report-overrides: build output not found — run `pnpm run build` first\n');
7
+ } else {
8
+ process.stderr.write(`nmr-report-overrides: failed to load: ${error.message}\n`);
9
+ }
10
+ process.exit(1);
11
+ }
@@ -1,2 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import('../dist/esm/cli-sync-pnpm-version.js');
2
+ try {
3
+ await import('../dist/esm/cli-sync-pnpm-version.js');
4
+ } catch (error) {
5
+ if (error.code === 'ERR_MODULE_NOT_FOUND') {
6
+ process.stderr.write('nmr-sync-pnpm-version: build output not found — run `pnpm run build` first\n');
7
+ } else {
8
+ process.stderr.write(`nmr-sync-pnpm-version: failed to load: ${error.message}\n`);
9
+ }
10
+ process.exit(1);
11
+ }
package/bin/nmr.js CHANGED
@@ -1,2 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import('../dist/esm/cli.js');
2
+ try {
3
+ await import('../dist/esm/cli.js');
4
+ } catch (error) {
5
+ if (error.code === 'ERR_MODULE_NOT_FOUND') {
6
+ process.stderr.write('nmr: build output not found — run `pnpm run build` first\n');
7
+ } else {
8
+ process.stderr.write(`nmr: failed to load: ${error.message}\n`);
9
+ }
10
+ process.exit(1);
11
+ }
package/dist/esm/.cache CHANGED
@@ -1 +1 @@
1
- e3d8619d797a79c02e27201b2f6171de0f5455f74ba2ba53b42079ea580c5d8d
1
+ d18a5c8de09fc2b69c75928a53523574089d4c79028d3d29b983ddd6072d76ab
package/dist/esm/cli.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import process from "node:process";
2
2
  import { resolveContext } from "./context.js";
3
3
  import { generateHelp } from "./help.js";
4
- import { buildRootRegistry, buildWorkspaceRegistry, resolveScript } from "./resolver.js";
4
+ import { applyDevBin, buildRootRegistry, buildWorkspaceRegistry, resolveScript } from "./resolver.js";
5
5
  import { runCommand } from "./runner.js";
6
+ import { VERSION } from "./version.js";
6
7
  function shellQuote(arg) {
7
8
  return "'" + arg.replace(/'/g, String.raw`'\''`) + "'";
8
9
  }
@@ -13,6 +14,7 @@ function parseArgs(argv) {
13
14
  recursive: false,
14
15
  workspaceRoot: false,
15
16
  help: false,
17
+ version: false,
16
18
  intTest: false,
17
19
  passthrough: []
18
20
  };
@@ -46,6 +48,11 @@ function parseArgs(argv) {
46
48
  i++;
47
49
  continue;
48
50
  }
51
+ if (arg === "-V" || arg === "--version") {
52
+ result.version = true;
53
+ i++;
54
+ continue;
55
+ }
49
56
  if (arg === "-q" || arg === "--quiet") {
50
57
  result.quiet = true;
51
58
  i++;
@@ -64,6 +71,10 @@ function parseArgs(argv) {
64
71
  }
65
72
  async function main() {
66
73
  const parsed = parseArgs(process.argv);
74
+ if (parsed.version) {
75
+ console.info(VERSION);
76
+ process.exit(0);
77
+ }
67
78
  const context = await resolveContext();
68
79
  if (parsed.help || !parsed.command) {
69
80
  console.info(generateHelp(context.config));
@@ -103,7 +114,8 @@ async function main() {
103
114
  if (resolved.source === "package" && !parsed.quiet && registry[command] !== void 0) {
104
115
  console.info(`Using override script: ${resolved.command}`);
105
116
  }
106
- const fullCommand = resolved.command + passthrough;
117
+ const substitutedCommand = applyDevBin(resolved.command, context.config.devBin, context.monorepoRoot);
118
+ const fullCommand = substitutedCommand + passthrough;
107
119
  const code = runCommand(fullCommand, void 0, runOptions);
108
120
  process.exit(code);
109
121
  }
@@ -1,4 +1,5 @@
1
1
  export interface NmrConfig {
2
+ devBin?: Record<string, string>;
2
3
  workspaceScripts?: Record<string, string | string[]>;
3
4
  rootScripts?: Record<string, string | string[]>;
4
5
  }
@@ -26,11 +26,29 @@ function validateScriptField(value, fieldName, configPath) {
26
26
  }
27
27
  return value[fieldName];
28
28
  }
29
+ function isStringRecord(value) {
30
+ if (!isObject(value)) return false;
31
+ for (const v of Object.values(value)) {
32
+ if (typeof v !== "string") return false;
33
+ }
34
+ return true;
35
+ }
36
+ function validateStringRecordField(value, fieldName, configPath) {
37
+ if (!(fieldName in value) || value[fieldName] === void 0) {
38
+ return void 0;
39
+ }
40
+ if (!isStringRecord(value[fieldName])) {
41
+ throw new Error(`Invalid nmr config at ${configPath}: \`${fieldName}\` must be a Record<string, string>`);
42
+ }
43
+ return value[fieldName];
44
+ }
29
45
  function validateConfig(value, configPath) {
30
46
  if (!isObject(value)) {
31
47
  throw new Error(`Invalid nmr config at ${configPath}: expected an object, got ${typeof value}`);
32
48
  }
33
49
  const config = {};
50
+ const devBin = validateStringRecordField(value, "devBin", configPath);
51
+ if (devBin) config.devBin = devBin;
34
52
  const workspaceScripts = validateScriptField(value, "workspaceScripts", configPath);
35
53
  if (workspaceScripts) config.workspaceScripts = workspaceScripts;
36
54
  const rootScripts = validateScriptField(value, "rootScripts", configPath);
@@ -4,6 +4,7 @@ const commonWorkspaceScripts = {
4
4
  "check:strict": ["typecheck", "fmt:check", "lint:strict", "test:coverage"],
5
5
  clean: "pnpm exec rimraf dist/*",
6
6
  compile: "tsx ../../config/build.ts",
7
+ fix: ["lint", "fmt"],
7
8
  fmt: "prettier --list-different --write .",
8
9
  "fmt:check": "prettier --check .",
9
10
  "generate-typings": "tsc --project tsconfig.generate-typings.json",
@@ -33,6 +34,7 @@ const rootScripts = {
33
34
  "check:strict": ["typecheck", "fmt:check", "audit", "lint:strict", "test:coverage"],
34
35
  ci: ["build", "check:strict"],
35
36
  clean: "pnpm --recursive exec nmr clean",
37
+ fix: ["lint", "fmt"],
36
38
  fmt: `sh -c 'prettier --list-different --write "\${@:-.}"' --`,
37
39
  "fmt:all": ["fmt", "fmt:sh"],
38
40
  "fmt:check": `sh -c 'prettier --check "\${@:-.}"' --`,
@@ -1,3 +1,3 @@
1
- export type { NmrConfig } from './config.js';
2
- export { defineConfig } from './config.js';
3
- export type { ResolvedContext } from './context.js';
1
+ export type { NmrConfig } from './config.ts';
2
+ export { defineConfig } from './config.ts';
3
+ export type { ResolvedContext } from './context.ts';
@@ -1,5 +1,6 @@
1
1
  import type { NmrConfig } from './config.js';
2
2
  import type { ScriptRegistry, ScriptValue } from './resolve-scripts.js';
3
+ export declare function applyDevBin(command: string, devBin: Record<string, string> | undefined, monorepoRoot: string): string;
3
4
  export interface ResolvedScript {
4
5
  command: string;
5
6
  source: 'default' | 'package';
@@ -2,6 +2,29 @@ import { 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";
5
+ function applyDevBin(command, devBin, monorepoRoot) {
6
+ if (!devBin) {
7
+ return command;
8
+ }
9
+ const spaceIndex = command.indexOf(" ");
10
+ const firstToken = spaceIndex === -1 ? command : command.slice(0, spaceIndex);
11
+ const rest = spaceIndex === -1 ? "" : command.slice(spaceIndex);
12
+ const replacement = devBin[firstToken];
13
+ if (replacement === void 0) {
14
+ return command;
15
+ }
16
+ const resolvedReplacement = resolveReplacementPaths(replacement, monorepoRoot);
17
+ return resolvedReplacement + rest;
18
+ }
19
+ function resolveReplacementPaths(replacement, monorepoRoot) {
20
+ const tokens = replacement.split(" ");
21
+ return tokens.map((token, index) => {
22
+ if (index === 0) return token;
23
+ if (token.startsWith("-")) return token;
24
+ if (!token.includes("/")) return token;
25
+ return path.resolve(monorepoRoot, token);
26
+ }).join(" ");
27
+ }
5
28
  function expandScript(script) {
6
29
  if (typeof script === "string") {
7
30
  return script;
@@ -63,6 +86,7 @@ function resolveScript(commandName, registry, packageDir) {
63
86
  return { command: expandScript(registryEntry), source: "default" };
64
87
  }
65
88
  export {
89
+ applyDevBin,
66
90
  buildRootRegistry,
67
91
  buildWorkspaceRegistry,
68
92
  describeScript,
@@ -1 +1,7 @@
1
+ export { findMonorepoRoot } from '../context.ts';
2
+ export declare function checkPnpmVersionConsistency(monorepoRoot: string): void;
3
+ export declare function checkNodeVersionConsistency(monorepoRoot: string): void;
4
+ export declare function getPnpmVersionFromAction(monorepoRoot: string): Promise<string>;
5
+ export declare function getPnpmVersionFromPackageJson(monorepoRoot: string): string;
6
+ export declare function getNodeVersionFromAction(monorepoRoot: string): Promise<string>;
1
7
  export declare function runConsistencyChecks(): void;
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { describe, expect, it } from "vitest";
4
4
  import { findMonorepoRoot } from "../context.js";
5
+ import { findMonorepoRoot as findMonorepoRoot2 } from "../context.js";
5
6
  import { getRuntimeVersionFromAsdf } from "./helpers/get-runtime-version-from-asdf.js";
6
7
  import { getStringFromYamlFile } from "./helpers/get-string-from-yaml-file.js";
7
8
  import { getValueAtPathOrThrow } from "./helpers/get-value-at-path.js";
@@ -55,5 +56,11 @@ function runConsistencyChecks() {
55
56
  checkNodeVersionConsistency(monorepoRoot);
56
57
  }
57
58
  export {
59
+ checkNodeVersionConsistency,
60
+ checkPnpmVersionConsistency,
61
+ findMonorepoRoot2 as findMonorepoRoot,
62
+ getNodeVersionFromAction,
63
+ getPnpmVersionFromAction,
64
+ getPnpmVersionFromPackageJson,
58
65
  runConsistencyChecks
59
66
  };
@@ -0,0 +1 @@
1
+ export declare const VERSION = "0.9.0";
@@ -0,0 +1,4 @@
1
+ const VERSION = "0.9.0";
2
+ export {
3
+ VERSION
4
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/nmr",
3
- "version": "0.4.0",
3
+ "version": "0.9.0",
4
4
  "private": false,
5
5
  "description": "Context-aware script runner for PNPM monorepos",
6
6
  "keywords": [