@williamthorsen/nmr 0.18.1 → 0.19.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/AGENTS.md +13 -1
- package/README.md +69 -17
- package/bin/nmr-taze.js +11 -0
- package/dist/esm/cli-taze.d.ts +1 -0
- package/dist/esm/cli-taze.js +10 -0
- package/dist/esm/commands/build.d.ts +1 -1
- package/dist/esm/commands/build.js +17 -7
- package/dist/esm/commands/taze.d.ts +12 -0
- package/dist/esm/commands/taze.js +32 -0
- package/dist/esm/context.js +1 -1
- package/dist/esm/default-scripts.js +3 -4
- package/dist/esm/help.js +4 -3
- package/dist/esm/taze.d.ts +5 -0
- package/dist/esm/taze.js +6 -0
- package/dist/esm/tests/consistency.js +1 -1
- package/dist/esm/tests/helpers/get-runtime-version-from-asdf.js +1 -1
- package/package.json +14 -5
package/AGENTS.md
CHANGED
|
@@ -38,7 +38,19 @@ Some root scripts (e.g. `lint`, `typecheck`, `test`) expand to `nmr root:X && pn
|
|
|
38
38
|
|
|
39
39
|
## Managed build
|
|
40
40
|
|
|
41
|
-
The default `compile` script runs `nmr-compile`, a standalone bin that uses the TypeScript compiler API to emit a package's `src` to `dist/esm` as `.js` and `.d.ts` in one pass, rewriting relative `.ts`→`.js` specifiers and tsconfig `paths` aliases in both outputs, and skipping work when inputs are unchanged. `typescript` is a peer dependency (`>=5.7.0
|
|
41
|
+
The default `compile` script runs `nmr-compile`, a standalone bin that uses the TypeScript compiler API to emit a package's `src` to `dist/esm` as `.js` and `.d.ts` in one pass, rewriting relative `.ts`→`.js` specifiers and tsconfig `paths` aliases in both outputs, and skipping work when inputs are unchanged. `typescript` is a peer dependency (`>=5.7.0 <7`; TypeScript 7 ships no compiler API). There is no repo-local build script to maintain, and no separate typings step; `nmr build` runs `compile` alone. To find or debug the build, look to `nmr-compile`, not a `config/build.ts` in the consuming repo.
|
|
42
|
+
|
|
43
|
+
## Dependency upgrades
|
|
44
|
+
|
|
45
|
+
`nmr upgrade` reports available dependency upgrades, honoring the version ceilings the repo declares in its root `taze.config.ts`. Scope follows context: from the root it covers the root `package.json` and every workspace in one pass; from inside a package, or under `nmr -F <package> upgrade`, it covers that package. `nmr root:upgrade` covers the root `package.json` alone. Run the root-context commands from the monorepo root.
|
|
46
|
+
|
|
47
|
+
There are no `outdated` or `update` commands; `upgrade` replaced them. Reach for `pnpm outdated` or `pnpm update` directly if you need a report that ignores the ceilings.
|
|
48
|
+
|
|
49
|
+
Arguments pass through to the underlying tool, including the range mode: `nmr upgrade major` proposes major upgrades, still inside the ceilings. Every form above is report-only until you add `--write`.
|
|
50
|
+
|
|
51
|
+
**`-w` is not the same on both sides of the command.** `nmr upgrade -w` passes `--write` through and rewrites `package.json`; `nmr -w upgrade` is nmr's own `--workspace-root` and writes nothing. Use the long forms.
|
|
52
|
+
|
|
53
|
+
To change what a repo holds back, edit its `taze.config.ts`. It imports `defineConfig` from `@williamthorsen/nmr/taze`, which supplies the shared upgrade policy — so that file should carry only the ceilings specific to that repo, and the repo needs no dependency on the upgrade tool itself.
|
|
42
54
|
|
|
43
55
|
## Override behaviors
|
|
44
56
|
|
package/README.md
CHANGED
|
@@ -3,22 +3,21 @@
|
|
|
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.19.0 (2026-07-22)
|
|
7
7
|
|
|
8
|
-
###
|
|
8
|
+
### 🎉 Features
|
|
9
9
|
|
|
10
|
-
-
|
|
10
|
+
- 🚨 **Breaking:** Replace the dependency commands with nmr upgrade (#491)
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- If run from a monorepo root, `nmr clean` ignored a package's own `clean` script.
|
|
16
|
-
- `nmr compile` skipped the build for a package whose output had been deleted, because the sources were unchanged. The package compiled to an empty output directory with no error and no warning, and could be published as an empty tarball.
|
|
17
|
-
- `nmr compile` reported missing output and recompiled on every run for a package whose sources emit nothing, such as a source tree of only declaration files.
|
|
12
|
+
Adds `nmr upgrade` as an ergonomic wrapper for displaying and applying available dependency upgrades. Taze is used under the hood to set a soaking period (customizable, defaulting to 7 days) before recently published dependencies will be accepted.
|
|
13
|
+
|
|
14
|
+
Removes the four dependency commands it replaces: `outdated`, `outdated:latest`, `update`, and `update:latest`.
|
|
18
15
|
<!-- /section:release-notes -->
|
|
19
16
|
|
|
20
17
|
## Installation
|
|
21
18
|
|
|
19
|
+
Requires Node.js 24 or later.
|
|
20
|
+
|
|
22
21
|
```bash
|
|
23
22
|
pnpm add -D @williamthorsen/nmr
|
|
24
23
|
```
|
|
@@ -138,6 +137,48 @@ For example, if a workspace script resolves to `my-cli --verbose`, nmr rewrites
|
|
|
138
137
|
|
|
139
138
|
> **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.
|
|
140
139
|
|
|
140
|
+
## Dependency upgrades
|
|
141
|
+
|
|
142
|
+
`nmr upgrade` reports the dependency upgrades available to your repo. What it covers depends on where you run it:
|
|
143
|
+
|
|
144
|
+
| Invocation | Covers |
|
|
145
|
+
| ---------------------------- | ------------------------------------------- |
|
|
146
|
+
| `nmr upgrade` (from root) | the root `package.json` and every workspace |
|
|
147
|
+
| `nmr upgrade` (in a package) | that package |
|
|
148
|
+
| `nmr -F <package> upgrade` | that package, from anywhere |
|
|
149
|
+
| `nmr root:upgrade` | the root `package.json` alone |
|
|
150
|
+
|
|
151
|
+
The upgrade tool ([taze](https://github.com/antfu-collective/taze)) arrives with nmr, so your repo declares no dependency on it. Everything after the command name is passed through, including the range mode:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
nmr upgrade # upgrades available within each package's version ceilings
|
|
155
|
+
nmr upgrade major # major upgrades, still inside the ceilings
|
|
156
|
+
nmr upgrade --write # apply the proposals to package.json
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
> **Note:** `-w` means different things on either side of the command. `nmr upgrade -w` passes `--write` to the upgrade tool; `nmr -w upgrade` is nmr's own `--workspace-root` flag and applies no write. Prefer the long forms to keep them apart. Run the root-context commands from the monorepo root: like nmr's other `root:` scripts, they take the current directory as their starting point.
|
|
160
|
+
|
|
161
|
+
### Configuring upgrades
|
|
162
|
+
|
|
163
|
+
Declare version ceilings in a `taze.config.ts` at the monorepo root:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { defineConfig } from '@williamthorsen/nmr/taze';
|
|
167
|
+
|
|
168
|
+
export default defineConfig({
|
|
169
|
+
packageMode: {
|
|
170
|
+
// Hold @types/node at the Node major your `engines` floor requires.
|
|
171
|
+
'@types/node': 'minor',
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`defineConfig` supplies nmr's shared upgrade policy — currently a seven-day quarantine on brand-new releases — so your config carries only what is specific to your repo. Any setting you declare wins over nmr's default. Passing `undefined` clears a default rather than falling back to it, which is how you hand the quarantine policy back to `pnpm-workspace.yaml`'s `minimumReleaseAge`.
|
|
177
|
+
|
|
178
|
+
Everything the [taze configuration](https://github.com/antfu-collective/taze#config-file) accepts is accepted here.
|
|
179
|
+
|
|
180
|
+
> **Note:** `--include-locked` is part of both registry entries because a repo that pins dependencies to exact versions (pnpm's `savePrefix: ''`) has no dependency the tool would otherwise consider — without it, an upgrade pass reports nothing at all. Drop it via `.config/nmr.config.ts` if your repo declares version ranges instead.
|
|
181
|
+
|
|
141
182
|
## Default script registries
|
|
142
183
|
|
|
143
184
|
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.
|
|
@@ -162,6 +203,7 @@ These scripts are available out of the box. Repo-wide config (tier 2) and per-pa
|
|
|
162
203
|
| `test:coverage` | `pnpm exec vitest --coverage` |
|
|
163
204
|
| `test:watch` | `pnpm exec vitest --watch` |
|
|
164
205
|
| `typecheck` | `tsgo --noEmit` |
|
|
206
|
+
| `upgrade` | `nmr-taze --include-locked` |
|
|
165
207
|
| `view-coverage` | `open coverage/index.html` |
|
|
166
208
|
|
|
167
209
|
#### Integration test variant
|
|
@@ -241,12 +283,11 @@ A package gets this variant automatically when it contains a `vitest.integration
|
|
|
241
283
|
|
|
242
284
|
#### Dependencies
|
|
243
285
|
|
|
244
|
-
| Command
|
|
245
|
-
|
|
|
246
|
-
| `
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
| `update:latest` | `pnpm update --latest --recursive` |
|
|
286
|
+
| Command | Runs |
|
|
287
|
+
| --------- | --------------------------------------- |
|
|
288
|
+
| `upgrade` | `nmr-taze --include-locked --recursive` |
|
|
289
|
+
|
|
290
|
+
See [dependency upgrades](#dependency-upgrades) for the workflow and its configuration.
|
|
250
291
|
|
|
251
292
|
#### Root-only
|
|
252
293
|
|
|
@@ -260,6 +301,7 @@ These scripts operate on root-level code only (not workspace packages):
|
|
|
260
301
|
| `root:lint:strict` | `strict-lint --ignore-pattern 'packages/**' .` |
|
|
261
302
|
| `root:test` | `vitest --config ./vitest.root.config.ts` |
|
|
262
303
|
| `root:typecheck` | `tsgo --noEmit` |
|
|
304
|
+
| `root:upgrade` | `nmr-taze --include-locked` |
|
|
263
305
|
|
|
264
306
|
#### Utilities
|
|
265
307
|
|
|
@@ -356,14 +398,24 @@ nmr-clean
|
|
|
356
398
|
|
|
357
399
|
### `nmr-compile`
|
|
358
400
|
|
|
359
|
-
Compile a single package's `src` tree to `dist/esm` with the TypeScript compiler API, emitting `.js` and `.d.ts` in one pass. Because the compiler parses each source file, every relative import form — static, re-export, dynamic `import()`, and bare side-effect — is rewritten from `.ts` to `.js` in both outputs, and `.ts` occurrences inside strings and comments are left intact. tsconfig `paths` aliases are resolved to runnable relative `.js` specifiers in both outputs, sourced from the package's tsconfig. The build is skipped when no input has changed and the previous output is still on disk (a content-and-path hash is cached under `node_modules/.cache/nmr-compile/`, outside the published output). Deleting the output by any means — `nmr clean`, `rm -rf dist`, `git clean` — therefore forces a rebuild rather than a skip. This is the default `compile` script — run it from a package directory.
|
|
401
|
+
Compile a single package's `src` tree to `dist/esm` with the TypeScript compiler API, emitting `.js` and `.d.ts` in one pass. Because the compiler parses each source file, every relative import form — static, re-export, dynamic `import()`, and bare side-effect — is rewritten from `.ts` to `.js` in both outputs, and `.ts` occurrences inside strings and comments are left intact. tsconfig `paths` aliases are resolved to runnable relative `.js` specifiers in both outputs, sourced from the package's tsconfig. An aliased import whose target resolves outside the package's `src/` and is not resolvable without the alias mapping fails the build with a diagnostic, rather than being emitted verbatim to produce output that fails at runtime. The build is skipped when no input has changed and the previous output is still on disk (a content-and-path hash is cached under `node_modules/.cache/nmr-compile/`, outside the published output). Deleting the output by any means — `nmr clean`, `rm -rf dist`, `git clean` — therefore forces a rebuild rather than a skip. This is the default `compile` script — run it from a package directory.
|
|
360
402
|
|
|
361
|
-
`typescript` is a peer dependency (`>=5.7.0
|
|
403
|
+
`typescript` is a peer dependency (`>=5.7.0 <7`); the consuming repo provides it. The floor is what `rewriteRelativeImportExtensions` requires; the ceiling is because TypeScript 7 ships no compiler API — its root export is a version constant, so `nmr-compile` cannot run on it. Relative imports in source must carry explicit `.ts` extensions for them to be rewritten.
|
|
362
404
|
|
|
363
405
|
```bash
|
|
364
406
|
nmr-compile
|
|
365
407
|
```
|
|
366
408
|
|
|
409
|
+
### `nmr-taze`
|
|
410
|
+
|
|
411
|
+
Run the [taze](https://github.com/antfu-collective/taze) dependency-upgrade tool, forwarding every argument to it untouched. This is what the `upgrade` and `root:upgrade` scripts resolve to — see [dependency upgrades](#dependency-upgrades) for the workflow.
|
|
412
|
+
|
|
413
|
+
Under pnpm's isolated `node_modules`, a transitive package's binary is absent from the consuming repo's `node_modules/.bin`, so a repo that depends on nmr cannot run `taze` directly. `nmr-taze` can, because nmr is a direct dependency, and it resolves the tool from the tree nmr controls.
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
nmr-taze --include-locked --recursive
|
|
417
|
+
```
|
|
418
|
+
|
|
367
419
|
### `ensure-prepublish-hooks`
|
|
368
420
|
|
|
369
421
|
Verify that all publishable workspace packages have a `prepublishOnly` script. Exits non-zero if any are missing.
|
package/bin/nmr-taze.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
try {
|
|
3
|
+
await import('../dist/esm/cli-taze.js');
|
|
4
|
+
} catch (error) {
|
|
5
|
+
if (error.code === 'ERR_MODULE_NOT_FOUND') {
|
|
6
|
+
process.stderr.write('nmr-taze: build output not found — run `pnpm run build` first\n');
|
|
7
|
+
} else {
|
|
8
|
+
process.stderr.write(`nmr-taze: failed to load: ${error.message}\n`);
|
|
9
|
+
}
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { reportError } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { runTaze } from "./commands/taze.js";
|
|
4
|
+
try {
|
|
5
|
+
process.exitCode = runTaze(process.argv.slice(2));
|
|
6
|
+
}
|
|
7
|
+
catch (error) {
|
|
8
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
}
|
|
@@ -4,6 +4,6 @@ export interface BuildOptions {
|
|
|
4
4
|
outdir?: string;
|
|
5
5
|
}
|
|
6
6
|
export declare function buildPackage(packageDir: string, options?: BuildOptions): Promise<void>;
|
|
7
|
-
export declare function computeBuildHash(packageDir: string, files: string[], emitConfig: object): Promise<string>;
|
|
7
|
+
export declare function computeBuildHash(packageDir: string, files: string[], emitConfig: object, compilerVersion: string): Promise<string>;
|
|
8
8
|
export declare function resolveTsconfigChain(packageDir: string, configFileName?: string): string[];
|
|
9
9
|
export declare function resolveBuildCachePath(packageDir: string): string;
|
|
@@ -24,14 +24,14 @@ export async function buildPackage(packageDir, options = {}) {
|
|
|
24
24
|
ignore: options.ignore ?? DEFAULT_IGNORE,
|
|
25
25
|
});
|
|
26
26
|
const dependencies = ['package.json', ...resolveTsconfigChain(packageDir)];
|
|
27
|
-
const { changed, currentHash } = await detectBuildChanges(packageDir, [...entryPoints, ...dependencies], emitConfig, cachePath, hasExpectedBuildOutput(packageDir, outdir, entryPoints));
|
|
27
|
+
const { changed, currentHash } = await detectBuildChanges(packageDir, [...entryPoints, ...dependencies], emitConfig, ts.version, cachePath, hasExpectedBuildOutput(packageDir, outdir, entryPoints));
|
|
28
28
|
if (!changed) {
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
emitPackage(packageDir, entryPoints, outdir);
|
|
32
32
|
await writeBuildCache(cachePath, currentHash);
|
|
33
33
|
}
|
|
34
|
-
export async function computeBuildHash(packageDir, files, emitConfig) {
|
|
34
|
+
export async function computeBuildHash(packageDir, files, emitConfig, compilerVersion) {
|
|
35
35
|
const hash = createHash('sha256');
|
|
36
36
|
for (const file of [...files].sort()) {
|
|
37
37
|
hash.update(file);
|
|
@@ -39,6 +39,8 @@ export async function computeBuildHash(packageDir, files, emitConfig) {
|
|
|
39
39
|
hash.update(await readFile(path.join(packageDir, file)));
|
|
40
40
|
}
|
|
41
41
|
hash.update(JSON.stringify(emitConfig));
|
|
42
|
+
hash.update('\0');
|
|
43
|
+
hash.update(compilerVersion);
|
|
42
44
|
return hash.digest('hex');
|
|
43
45
|
}
|
|
44
46
|
export function resolveTsconfigChain(packageDir, configFileName = 'tsconfig.json') {
|
|
@@ -140,7 +142,7 @@ function resolveSpecifierReplacement(specifier, sourceContainingFile, compilerOp
|
|
|
140
142
|
const rewritten = swapTypeScriptExtension(specifier);
|
|
141
143
|
return rewritten === specifier ? undefined : rewritten;
|
|
142
144
|
}
|
|
143
|
-
if (
|
|
145
|
+
if (aliasPrefixes.every((prefix) => !(specifier === prefix || specifier.startsWith(prefix)))) {
|
|
144
146
|
return undefined;
|
|
145
147
|
}
|
|
146
148
|
const resolved = ts.resolveModuleName(specifier, sourceContainingFile, compilerOptions, ts.sys).resolvedModule;
|
|
@@ -149,7 +151,15 @@ function resolveSpecifierReplacement(specifier, sourceContainingFile, compilerOp
|
|
|
149
151
|
`Verify the tsconfig 'paths' mapping and that the target file exists.`);
|
|
150
152
|
}
|
|
151
153
|
if (!isWithin(sourceRoot, resolved.resolvedFileName)) {
|
|
152
|
-
|
|
154
|
+
const { paths: _paths, baseUrl: _baseUrl, rootDirs: _rootDirs, ...nodeResolutionOptions } = compilerOptions;
|
|
155
|
+
const bareResolved = ts.resolveModuleName(specifier, sourceContainingFile, nodeResolutionOptions, ts.sys).resolvedModule;
|
|
156
|
+
if (bareResolved) {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
throw new Error(`nmr-compile: aliased import '${specifier}' from ${sourceContainingFile} resolves to ` +
|
|
160
|
+
`${resolved.resolvedFileName}, outside the package source root ${sourceRoot}, and does not resolve ` +
|
|
161
|
+
`the way Node will at runtime, which ignores tsconfig 'paths', 'baseUrl', and 'rootDirs'. The ` +
|
|
162
|
+
`emitted specifier would fail at runtime; re-anchor the alias inside the package.`);
|
|
153
163
|
}
|
|
154
164
|
const relative = toRelativeSpecifier(path.dirname(sourceContainingFile), resolved.resolvedFileName);
|
|
155
165
|
return swapTypeScriptExtension(relative);
|
|
@@ -199,10 +209,10 @@ function findNearestNodeModulesHost(startDir) {
|
|
|
199
209
|
}
|
|
200
210
|
return current;
|
|
201
211
|
}
|
|
202
|
-
async function detectBuildChanges(packageDir, files, emitConfig, cachePath, outputPresent) {
|
|
212
|
+
async function detectBuildChanges(packageDir, files, emitConfig, compilerVersion, cachePath, outputPresent) {
|
|
203
213
|
const packageName = path.basename(packageDir);
|
|
204
214
|
const previousHash = existsSync(cachePath) ? readFileSync(cachePath, 'utf8') : undefined;
|
|
205
|
-
const currentHash = await computeBuildHash(packageDir, files, emitConfig);
|
|
215
|
+
const currentHash = await computeBuildHash(packageDir, files, emitConfig, compilerVersion);
|
|
206
216
|
if (previousHash === currentHash) {
|
|
207
217
|
if (outputPresent) {
|
|
208
218
|
console.info(`${SKIPPED_ICON} ${packageName}: No changes detected. Skipping build.`);
|
|
@@ -227,7 +237,7 @@ async function writeBuildCache(cachePath, hash) {
|
|
|
227
237
|
await writeFile(cachePath, hash);
|
|
228
238
|
}
|
|
229
239
|
function assertSupportedTypeScript() {
|
|
230
|
-
const [majorPart, minorPart] = ts.versionMajorMinor.split('.');
|
|
240
|
+
const [majorPart, minorPart] = ts.versionMajorMinor.split('.', 2);
|
|
231
241
|
const major = majorPart === undefined ? 0 : Number(majorPart);
|
|
232
242
|
const minor = minorPart === undefined ? 0 : Number(minorPart);
|
|
233
243
|
const tooOld = major < MINIMUM_TYPESCRIPT_MAJOR || (major === MINIMUM_TYPESCRIPT_MAJOR && minor < MINIMUM_TYPESCRIPT_MINOR);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Writable } from 'node:stream';
|
|
2
|
+
export interface SpawnOutcome {
|
|
3
|
+
status: number | null;
|
|
4
|
+
error?: Error | undefined;
|
|
5
|
+
}
|
|
6
|
+
export interface RunTazeOptions {
|
|
7
|
+
resolveCliPath?: () => string;
|
|
8
|
+
spawn?: (nodePath: string, argv: string[]) => SpawnOutcome;
|
|
9
|
+
stderr?: Writable;
|
|
10
|
+
}
|
|
11
|
+
export declare function runTaze(args: string[], options?: RunTazeOptions): number;
|
|
12
|
+
export declare function resolveTazeCliPath(): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { reportError } from '@williamthorsen/nmr-core';
|
|
5
|
+
const TAZE_CLI_SPECIFIER = 'taze/cli';
|
|
6
|
+
export function runTaze(args, options = {}) {
|
|
7
|
+
const stderr = options.stderr ?? process.stderr;
|
|
8
|
+
const resolveCliPath = options.resolveCliPath ?? resolveTazeCliPath;
|
|
9
|
+
const spawn = options.spawn ?? spawnNode;
|
|
10
|
+
let cliPath;
|
|
11
|
+
try {
|
|
12
|
+
cliPath = resolveCliPath();
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
16
|
+
reportError(`Could not resolve '${TAZE_CLI_SPECIFIER}' from nmr's dependencies. Reinstall the workspace to restore it. (${detail})`, stderr);
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
19
|
+
const outcome = spawn(process.execPath, [cliPath, ...args]);
|
|
20
|
+
if (outcome.error) {
|
|
21
|
+
reportError(`Failed to run taze: ${outcome.error.message}`, stderr);
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
return outcome.status ?? 1;
|
|
25
|
+
}
|
|
26
|
+
export function resolveTazeCliPath() {
|
|
27
|
+
return fileURLToPath(import.meta.resolve(TAZE_CLI_SPECIFIER));
|
|
28
|
+
}
|
|
29
|
+
function spawnNode(nodePath, argv) {
|
|
30
|
+
const result = spawnSync(nodePath, argv, { stdio: 'inherit' });
|
|
31
|
+
return { status: result.status, error: result.error };
|
|
32
|
+
}
|
package/dist/esm/context.js
CHANGED
|
@@ -12,6 +12,7 @@ export const commonWorkspaceScripts = {
|
|
|
12
12
|
'lint:check': 'eslint .',
|
|
13
13
|
'lint:strict': 'strict-lint',
|
|
14
14
|
typecheck: 'tsgo --noEmit',
|
|
15
|
+
upgrade: 'nmr-taze --include-locked',
|
|
15
16
|
'view-coverage': 'open coverage/index.html',
|
|
16
17
|
};
|
|
17
18
|
export const integrationTestScripts = {
|
|
@@ -45,8 +46,6 @@ export const rootScripts = {
|
|
|
45
46
|
lint: 'nmr root:lint && pnpm --recursive exec nmr lint',
|
|
46
47
|
'lint:check': 'nmr root:lint:check && pnpm --recursive exec nmr lint:check',
|
|
47
48
|
'lint:strict': 'nmr root:lint:strict && pnpm --recursive exec nmr lint:strict',
|
|
48
|
-
outdated: 'pnpm outdated --compatible --recursive',
|
|
49
|
-
'outdated:latest': 'pnpm outdated --recursive',
|
|
50
49
|
'report-overrides': 'nmr-report-overrides',
|
|
51
50
|
'root:check': ['root:typecheck', 'fmt:check', 'root:lint:check', 'root:test'],
|
|
52
51
|
'root:lint': "eslint --fix --ignore-pattern 'packages/**' .",
|
|
@@ -54,12 +53,12 @@ export const rootScripts = {
|
|
|
54
53
|
'root:lint:strict': "strict-lint --ignore-pattern 'packages/**' .",
|
|
55
54
|
'root:test': 'vitest --config ./vitest.root.config.ts',
|
|
56
55
|
'root:typecheck': 'tsgo --noEmit',
|
|
56
|
+
'root:upgrade': 'nmr-taze --include-locked',
|
|
57
57
|
'sync-agent-files': 'nmr-sync-agent-files',
|
|
58
58
|
'sync-pnpm-version': 'nmr-sync-pnpm-version',
|
|
59
59
|
test: 'nmr root:test && pnpm --recursive exec nmr test',
|
|
60
60
|
'test:coverage': 'nmr root:test && pnpm --recursive exec nmr test:coverage',
|
|
61
61
|
'test:watch': 'vitest --watch',
|
|
62
62
|
typecheck: 'nmr root:typecheck && pnpm --recursive exec nmr typecheck',
|
|
63
|
-
|
|
64
|
-
'update:latest': 'pnpm update --latest --recursive',
|
|
63
|
+
upgrade: 'nmr-taze --include-locked --recursive',
|
|
65
64
|
};
|
package/dist/esm/help.js
CHANGED
|
@@ -48,10 +48,11 @@ function collectOverrides(packageDir) {
|
|
|
48
48
|
function applyOverrides(registry, overrides) {
|
|
49
49
|
const marked = new Set();
|
|
50
50
|
for (const [name, value] of Object.entries(overrides)) {
|
|
51
|
-
if (name in registry) {
|
|
52
|
-
|
|
53
|
-
marked.add(name);
|
|
51
|
+
if (!(name in registry)) {
|
|
52
|
+
continue;
|
|
54
53
|
}
|
|
54
|
+
registry[name] = value;
|
|
55
|
+
marked.add(name);
|
|
55
56
|
}
|
|
56
57
|
return marked;
|
|
57
58
|
}
|
package/dist/esm/taze.js
ADDED
|
@@ -37,7 +37,7 @@ export function getPnpmVersionFromPackageJson(monorepoRoot) {
|
|
|
37
37
|
if (typeof pm !== 'string') {
|
|
38
38
|
throw new TypeError('"packageManager" field missing or not a string in package.json.');
|
|
39
39
|
}
|
|
40
|
-
const [name, version] = pm.split('@');
|
|
40
|
+
const [name, version] = pm.split('@', 2);
|
|
41
41
|
if (name !== 'pnpm') {
|
|
42
42
|
throw new Error('packageManager is not pnpm.');
|
|
43
43
|
}
|
|
@@ -6,7 +6,7 @@ export async function getRuntimeVersionFromAsdf(runtime, monorepoRoot) {
|
|
|
6
6
|
const toolVersions = await fs.promises.readFile(toolVersionsPath, { encoding: 'utf8' });
|
|
7
7
|
const versionLine = toolVersions.split('\n').find((line) => line.trim().startsWith(runtime));
|
|
8
8
|
assert.ok(versionLine, `${runtime} not found in .tool-versions`);
|
|
9
|
-
const [, version] = versionLine.trim().split(/\s
|
|
9
|
+
const [, version] = versionLine.trim().split(/\s+/, 2);
|
|
10
10
|
assert.ok(version, `${runtime} version missing in .tool-versions`);
|
|
11
11
|
return version;
|
|
12
12
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/nmr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Context-aware script runner for PNPM monorepos",
|
|
6
6
|
"keywords": [
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
"types": "./dist/esm/scripts.d.ts",
|
|
31
31
|
"default": "./dist/esm/scripts.js"
|
|
32
32
|
},
|
|
33
|
+
"./taze": {
|
|
34
|
+
"types": "./dist/esm/taze.d.ts",
|
|
35
|
+
"default": "./dist/esm/taze.js"
|
|
36
|
+
},
|
|
33
37
|
"./tests": {
|
|
34
38
|
"types": "./dist/esm/tests/consistency.d.ts",
|
|
35
39
|
"default": "./dist/esm/tests/consistency.js"
|
|
@@ -42,7 +46,8 @@
|
|
|
42
46
|
"nmr-compile": "bin/nmr-compile.js",
|
|
43
47
|
"nmr-report-overrides": "bin/nmr-report-overrides.js",
|
|
44
48
|
"nmr-sync-agent-files": "bin/nmr-sync-agent-files.js",
|
|
45
|
-
"nmr-sync-pnpm-version": "bin/nmr-sync-pnpm-version.js"
|
|
49
|
+
"nmr-sync-pnpm-version": "bin/nmr-sync-pnpm-version.js",
|
|
50
|
+
"nmr-taze": "bin/nmr-taze.js"
|
|
46
51
|
},
|
|
47
52
|
"files": [
|
|
48
53
|
"AGENTS.md",
|
|
@@ -52,15 +57,19 @@
|
|
|
52
57
|
"dependencies": {
|
|
53
58
|
"glob": "13.0.6",
|
|
54
59
|
"jiti": "2.7.0",
|
|
60
|
+
"taze": "19.15.1",
|
|
55
61
|
"yaml": "2.9.0",
|
|
56
62
|
"zod": "4.4.3",
|
|
57
|
-
"@williamthorsen/nmr-core": "0.7.
|
|
63
|
+
"@williamthorsen/nmr-core": "0.7.1"
|
|
58
64
|
},
|
|
59
65
|
"devDependencies": {
|
|
60
|
-
"@williamthorsen/toolbelt.strings": "3.1.
|
|
66
|
+
"@williamthorsen/toolbelt.strings": "3.1.6"
|
|
61
67
|
},
|
|
62
68
|
"peerDependencies": {
|
|
63
|
-
"typescript": ">=5.7.0"
|
|
69
|
+
"typescript": ">=5.7.0 <7"
|
|
70
|
+
},
|
|
71
|
+
"engines": {
|
|
72
|
+
"node": ">=24"
|
|
64
73
|
},
|
|
65
74
|
"publishConfig": {
|
|
66
75
|
"access": "public"
|