@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 +246 -31
- package/bin/ensure-prepublish-hooks.js +10 -1
- package/bin/nmr-report-overrides.js +10 -1
- package/bin/nmr-sync-pnpm-version.js +10 -1
- package/bin/nmr.js +10 -1
- package/dist/esm/.cache +1 -1
- package/dist/esm/cli.js +14 -2
- package/dist/esm/config.d.ts +1 -0
- package/dist/esm/config.js +18 -0
- package/dist/esm/default-scripts.js +2 -0
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/resolver.d.ts +1 -0
- package/dist/esm/resolver.js +24 -0
- package/dist/esm/tests/consistency.d.ts +6 -0
- package/dist/esm/tests/consistency.js +7 -0
- package/dist/esm/version.d.ts +1 -0
- package/dist/esm/version.js +4 -0
- package/package.json +1 -1
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
|
-
##
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
nmr
|
|
19
|
-
nmr
|
|
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
|
-
|
|
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
|
|
26
|
-
nmr
|
|
27
|
-
|
|
46
|
+
# From inside packages/core, run the root check suite
|
|
47
|
+
nmr -w check
|
|
48
|
+
```
|
|
28
49
|
|
|
29
|
-
|
|
30
|
-
nmr test # Runs root test + recursive workspace tests
|
|
31
|
-
nmr ci # Runs check:strict && build
|
|
50
|
+
## Three-tier override system
|
|
32
51
|
|
|
33
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
108
|
+
### `devBin` — source-repo binary substitution
|
|
65
109
|
|
|
66
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
}
|
package/dist/esm/config.d.ts
CHANGED
package/dist/esm/config.js
CHANGED
|
@@ -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 "\${@:-.}"' --`,
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type { NmrConfig } from './config.
|
|
2
|
-
export { defineConfig } from './config.
|
|
3
|
-
export type { ResolvedContext } from './context.
|
|
1
|
+
export type { NmrConfig } from './config.ts';
|
|
2
|
+
export { defineConfig } from './config.ts';
|
|
3
|
+
export type { ResolvedContext } from './context.ts';
|
package/dist/esm/resolver.d.ts
CHANGED
|
@@ -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';
|
package/dist/esm/resolver.js
CHANGED
|
@@ -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";
|