@williamthorsen/nmr 0.16.0 → 0.17.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.
Files changed (41) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +41 -44
  3. package/dist/esm/.cache +1 -1
  4. package/dist/esm/cli-build.js +5 -5
  5. package/dist/esm/cli-ensure-prepublish-hooks.js +33 -34
  6. package/dist/esm/cli-report-overrides.js +7 -6
  7. package/dist/esm/cli-sync-agent-files.js +20 -22
  8. package/dist/esm/cli-sync-pnpm-version.js +7 -6
  9. package/dist/esm/cli.js +13 -13
  10. package/dist/esm/commands/build.d.ts +2 -8
  11. package/dist/esm/commands/build.js +270 -118
  12. package/dist/esm/commands/ensure-prepublish-hooks.js +66 -69
  13. package/dist/esm/commands/report-overrides.js +10 -13
  14. package/dist/esm/commands/sync-agent-files.js +46 -52
  15. package/dist/esm/commands/sync-pnpm-version.js +35 -42
  16. package/dist/esm/config.js +59 -57
  17. package/dist/esm/context.d.ts +1 -1
  18. package/dist/esm/context.js +70 -72
  19. package/dist/esm/default-scripts.js +63 -68
  20. package/dist/esm/help.d.ts +1 -1
  21. package/dist/esm/help.js +65 -67
  22. package/dist/esm/helpers/code-quality-pnpm-action.js +20 -24
  23. package/dist/esm/helpers/hook-name.js +2 -5
  24. package/dist/esm/helpers/package-json.js +46 -41
  25. package/dist/esm/helpers/type-guards.js +2 -5
  26. package/dist/esm/index.d.ts +3 -3
  27. package/dist/esm/index.js +1 -4
  28. package/dist/esm/resolve-scripts.d.ts +2 -2
  29. package/dist/esm/resolve-scripts.js +7 -11
  30. package/dist/esm/resolver.d.ts +2 -2
  31. package/dist/esm/resolver.js +84 -86
  32. package/dist/esm/runCli.js +136 -144
  33. package/dist/esm/runner.js +35 -34
  34. package/dist/esm/scripts.d.ts +2 -2
  35. package/dist/esm/scripts.js +1 -5
  36. package/dist/esm/tests/consistency.d.ts +1 -1
  37. package/dist/esm/tests/consistency.js +45 -54
  38. package/dist/esm/tests/helpers/get-runtime-version-from-asdf.js +11 -14
  39. package/dist/esm/tests/helpers/get-string-from-yaml-file.js +10 -13
  40. package/dist/esm/tests/helpers/get-value-at-path.js +30 -30
  41. package/package.json +5 -3
package/AGENTS.md CHANGED
@@ -38,7 +38,7 @@ 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 esbuild-compiles a package's `src` to `dist/esm`, rewriting `~/` (package-root) import aliases and `.ts`→`.js` specifiers, and skipping work when inputs are unchanged. There is no repo-local build script to maintain; `nmr build` runs `compile` then `generate-typings`. To find or debug the build, look to `nmr-compile`, not a `config/build.ts` in the consuming repo.
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`). 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
42
 
43
43
  ## Override behaviors
44
44
 
package/README.md CHANGED
@@ -3,19 +3,13 @@
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.16.0 (2026-06-30)
6
+ ## Release notes — v0.17.0 (2026-07-01)
7
7
 
8
8
  ### 🎉 Features
9
9
 
10
- - 🚨 **Breaking:** Auto-activate integration test variant from config presence (#448)
10
+ - 🚨 **Breaking:** Rebuild nmr-compile on a unified tsc emit (#455)
11
11
 
12
- A package can now separate its integration tests from its standalone suite simply by including a `vitest.integration.config.ts` (alongside a `vitest.standalone.config.ts`). The `--int-test` flag that previously enabled this is removed — that config-file pairing is now the only way to activate the separation. In such a package, `test` and `test:coverage` run only the standalone suite and skip integration tests, while a new `test:all` runs both suites together. The separation now holds even when tests run across every package at once, so a full-workspace `test` run still keeps integration tests out of the default suite. Packages that previously hand-copied these test scripts no longer need to.
13
-
14
- ### 🐛 Bug fixes
15
-
16
- - Prevent CLI output truncation when piped before exit (#446)
17
-
18
- Fixes an issue where large output from the `nmr` and `v11y` commands could be truncated when captured through a pipe — for example, by a CI job.
12
+ `nmr` now compiles a package's JavaScript and type declarations in one step, rewriting every import form — static, re-export, dynamic `import()`, bare side-effect, and tsconfig `paths` aliases — to runnable `.js` in both outputs. It now requires TypeScript 5.7 or newer as a peer dependency.
19
13
  <!-- /section:release-notes -->
20
14
 
21
15
  ## Installation
@@ -32,7 +26,7 @@ From a package directory:
32
26
 
33
27
  ```bash
34
28
  nmr test # Run tests for the current package
35
- nmr build # Compile + generate typings
29
+ nmr build # Compile to .js and .d.ts in one pass
36
30
  nmr check # Typecheck, format check, lint check, and tests
37
31
  ```
38
32
 
@@ -75,11 +69,11 @@ Scripts resolve through three tiers. Higher tiers override lower ones:
75
69
 
76
70
  Given the `build` command for a package that defines its own build script:
77
71
 
78
- | Tier | Source | Value | Wins? |
79
- | ---- | ----------------------- | --------------------------------- | ----- |
80
- | 1 | Built-in default | `['compile', 'generate-typings']` | — |
81
- | 2 | `.config/nmr.config.ts` | _(not set)_ | — |
82
- | 3 | `package.json` scripts | `"tsx custom-build.ts"` | ✓ |
72
+ | Tier | Source | Value | Wins? |
73
+ | ---- | ----------------------- | ----------------------- | ----- |
74
+ | 1 | Built-in default | `['compile']` | — |
75
+ | 2 | `.config/nmr.config.ts` | _(not set)_ | — |
76
+ | 3 | `package.json` scripts | `"tsx custom-build.ts"` | ✓ |
83
77
 
84
78
  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.
85
79
 
@@ -90,8 +84,8 @@ If no per-package override exists, the highest-tier value that is set wins. Set
90
84
  Script values can be `string` or `string[]`. Arrays expand to chained `nmr` sub-invocations:
91
85
 
92
86
  ```ts
93
- // "build": ["compile", "generate-typings"]
94
- // expands to: nmr compile && nmr generate-typings
87
+ // "fix": ["lint", "fmt"]
88
+ // expands to: nmr lint && nmr fmt
95
89
  ```
96
90
 
97
91
  ## Configuration
@@ -145,26 +139,26 @@ These scripts are available out of the box. Repo-wide config (tier 2) and per-pa
145
139
 
146
140
  ### Workspace scripts
147
141
 
148
- | Command | Runs |
149
- | ------------------ | -------------------------------------------------------- |
150
- | `build` | `compile`, `generate-typings` |
151
- | `check` | `typecheck`, `fmt:check`, `lint:check`, `test` |
152
- | `check:strict` | `typecheck`, `fmt:check`, `lint:strict`, `test:coverage` |
153
- | `clean` | `pnpm exec rimraf dist/*` |
154
- | `compile` | `nmr-compile` |
155
- | `fix` | `lint`, `fmt` |
156
- | `fix:check` | `fmt:check`, `lint:check` |
157
- | `fmt` | `prettier --list-different --write .` |
158
- | `fmt:check` | `prettier --check .` |
159
- | `generate-typings` | `tsc --project tsconfig.generate-typings.json` |
160
- | `lint` | `eslint --fix .` |
161
- | `lint:check` | `eslint .` |
162
- | `lint:strict` | `strict-lint` |
163
- | `test` | `pnpm exec vitest` |
164
- | `test:coverage` | `pnpm exec vitest --coverage` |
165
- | `test:watch` | `pnpm exec vitest --watch` |
166
- | `typecheck` | `tsgo --noEmit` |
167
- | `view-coverage` | `open coverage/index.html` |
142
+ | Command | Runs |
143
+ | --------------- | ---------------------------------------------------------------- |
144
+ | `attw` | `attw --pack --profile esm-only` |
145
+ | `build` | `compile` |
146
+ | `check` | `typecheck`, `fmt:check`, `lint:check`, `test` |
147
+ | `check:strict` | `typecheck`, `fmt:check`, `lint:strict`, `test:coverage`, `attw` |
148
+ | `clean` | `pnpm exec rimraf dist/*` |
149
+ | `compile` | `nmr-compile` |
150
+ | `fix` | `lint`, `fmt` |
151
+ | `fix:check` | `fmt:check`, `lint:check` |
152
+ | `fmt` | `prettier --list-different --write .` |
153
+ | `fmt:check` | `prettier --check .` |
154
+ | `lint` | `eslint --fix .` |
155
+ | `lint:check` | `eslint .` |
156
+ | `lint:strict` | `strict-lint` |
157
+ | `test` | `pnpm exec vitest` |
158
+ | `test:coverage` | `pnpm exec vitest --coverage` |
159
+ | `test:watch` | `pnpm exec vitest --watch` |
160
+ | `typecheck` | `tsgo --noEmit` |
161
+ | `view-coverage` | `open coverage/index.html` |
168
162
 
169
163
  #### Integration test variant
170
164
 
@@ -190,11 +184,12 @@ A package gets this variant automatically when it contains a `vitest.integration
190
184
 
191
185
  #### Check and quality
192
186
 
193
- | Command | Runs |
194
- | ------------------- | ----------------------------------------------------------------------------- |
195
- | `check` | `typecheck`, `fmt:check`, `lint:check`, `test` |
196
- | `check:agent-files` | `nmr-sync-agent-files --check` |
197
- | `check:strict` | `typecheck`, `fmt:check`, `lint:strict`, `test:coverage`, `check:agent-files` |
187
+ | Command | Runs |
188
+ | ------------------- | ------------------------------------------------------------------------------------- |
189
+ | `attw` | `pnpm --recursive exec nmr attw` |
190
+ | `check` | `typecheck`, `fmt:check`, `lint:check`, `test` |
191
+ | `check:agent-files` | `nmr-sync-agent-files --check` |
192
+ | `check:strict` | `typecheck`, `fmt:check`, `lint:strict`, `test:coverage`, `attw`, `check:agent-files` |
198
193
 
199
194
  #### Fix
200
195
 
@@ -293,7 +288,7 @@ nmr [flags] <command> [args...]
293
288
  ```bash
294
289
  # From a package directory
295
290
  nmr test # Run workspace test script
296
- nmr build # Compile + generate typings
291
+ nmr build # Compile to .js and .d.ts in one pass
297
292
 
298
293
  # From the monorepo root
299
294
  nmr test # Root tests + recursive workspace tests
@@ -350,7 +345,9 @@ nmr sync-pnpm-version
350
345
 
351
346
  ### `nmr-compile`
352
347
 
353
- Compile a single package's `src` tree to `dist/esm` with esbuild. Rewrites `~/` (package-root) import aliases and `.ts`→`.js` specifiers to match the emitted output, and skips the build when no input has changed (a content-and-path hash is cached in `dist/esm/.cache`). This is the default `compile` script — run it from a package directory.
348
+ 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 (a content-and-path hash is cached in `dist/esm/.cache`). This is the default `compile` script — run it from a package directory.
349
+
350
+ `typescript` is a peer dependency (`>=5.7.0`, required for `rewriteRelativeImportExtensions`); the consuming repo provides it. Relative imports in source must carry explicit `.ts` extensions for them to be rewritten.
354
351
 
355
352
  ```bash
356
353
  nmr-compile
package/dist/esm/.cache CHANGED
@@ -1 +1 @@
1
- 47af840104236371ef384e3368ae463ca3d05d525fcffd96134d4c9c11257796
1
+ 49f79592247d14646b1445473aa445a8bcbeafb478c6dc9949eb31cee8e2a627
@@ -1,8 +1,8 @@
1
1
  import { buildPackage } from "./commands/build.js";
2
2
  try {
3
- await buildPackage(process.cwd());
4
- } catch (error) {
5
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}
6
- `);
7
- process.exitCode = 1;
3
+ await buildPackage(process.cwd());
4
+ }
5
+ catch (error) {
6
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
7
+ process.exitCode = 1;
8
8
  }
@@ -1,44 +1,43 @@
1
- import { parseArgsOrExit, reportError } from "@williamthorsen/nmr-core";
1
+ import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
2
2
  import { DEFAULT_HOOK, ensurePrepublishHooks } from "./commands/ensure-prepublish-hooks.js";
3
3
  import { findMonorepoRoot } from "./context.js";
4
4
  const flagSchema = {
5
- fix: { long: "--fix", type: "boolean" },
6
- dryRun: { long: "--dry-run", type: "boolean" },
7
- command: { long: "--command", type: "string" }
5
+ fix: { long: '--fix', type: 'boolean' },
6
+ dryRun: { long: '--dry-run', type: 'boolean' },
7
+ command: { long: '--command', type: 'string' },
8
8
  };
9
9
  const { fix, dryRun, command } = parseArgsOrExit(process.argv.slice(2), flagSchema).flags;
10
10
  try {
11
- const monorepoRoot = findMonorepoRoot();
12
- const hookCommand = command ?? DEFAULT_HOOK;
13
- const options = command ? { fix, dryRun, command } : { fix, dryRun };
14
- const result = ensurePrepublishHooks(monorepoRoot, options);
15
- for (const pkg of result.packages) {
16
- if (pkg.isPrivate) {
17
- continue;
11
+ const monorepoRoot = findMonorepoRoot();
12
+ const hookCommand = command ?? DEFAULT_HOOK;
13
+ const options = command ? { fix, dryRun, command } : { fix, dryRun };
14
+ const result = ensurePrepublishHooks(monorepoRoot, options);
15
+ for (const pkg of result.packages) {
16
+ if (pkg.isPrivate) {
17
+ continue;
18
+ }
19
+ switch (pkg.action) {
20
+ case 'ok':
21
+ console.info(`✓ ${pkg.packageName}: prepublishOnly = "${pkg.prepublishOnly}"`);
22
+ break;
23
+ case 'missing':
24
+ console.warn(`✗ ${pkg.packageName}: missing prepublishOnly`);
25
+ break;
26
+ case 'fixed':
27
+ console.info(`✓ ${pkg.packageName}: added prepublishOnly = "${hookCommand}"`);
28
+ break;
29
+ case 'would-fix':
30
+ console.info(`~ ${pkg.packageName}: would add prepublishOnly = "${hookCommand}"`);
31
+ break;
32
+ }
18
33
  }
19
- switch (pkg.action) {
20
- case "ok":
21
- console.info(`\u2713 ${pkg.packageName}: prepublishOnly = "${pkg.prepublishOnly}"`);
22
- break;
23
- case "missing":
24
- console.warn(`\u2717 ${pkg.packageName}: missing prepublishOnly`);
25
- break;
26
- case "fixed":
27
- console.info(`\u2713 ${pkg.packageName}: added prepublishOnly = "${hookCommand}"`);
28
- break;
29
- case "would-fix":
30
- console.info(`~ ${pkg.packageName}: would add prepublishOnly = "${hookCommand}"`);
31
- break;
34
+ if (result.hasFailures) {
35
+ const missing = result.packages.filter((p) => p.action === 'missing').length;
36
+ process.stderr.write(`\n${missing} package(s) missing prepublishOnly. Use --fix to add it.\n`);
37
+ process.exitCode = 1;
32
38
  }
33
- }
34
- if (result.hasFailures) {
35
- const missing = result.packages.filter((p) => p.action === "missing").length;
36
- process.stderr.write(`
37
- ${missing} package(s) missing prepublishOnly. Use --fix to add it.
38
- `);
39
+ }
40
+ catch (error) {
41
+ reportError(error instanceof Error ? error.message : String(error));
39
42
  process.exitCode = 1;
40
- }
41
- } catch (error) {
42
- reportError(error instanceof Error ? error.message : String(error));
43
- process.exitCode = 1;
44
43
  }
@@ -1,10 +1,11 @@
1
- import { reportError } from "@williamthorsen/nmr-core";
1
+ import { reportError } from '@williamthorsen/nmr-core';
2
2
  import { reportOverrides } from "./commands/report-overrides.js";
3
3
  import { findMonorepoRoot } from "./context.js";
4
4
  try {
5
- const monorepoRoot = findMonorepoRoot();
6
- reportOverrides(monorepoRoot);
7
- } catch (error) {
8
- reportError(error instanceof Error ? error.message : String(error));
9
- process.exitCode = 1;
5
+ const monorepoRoot = findMonorepoRoot();
6
+ reportOverrides(monorepoRoot);
7
+ }
8
+ catch (error) {
9
+ reportError(error instanceof Error ? error.message : String(error));
10
+ process.exitCode = 1;
10
11
  }
@@ -1,31 +1,29 @@
1
- import { parseArgsOrExit, reportError } from "@williamthorsen/nmr-core";
1
+ import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
2
2
  import { check, sync } from "./commands/sync-agent-files.js";
3
3
  import { findMonorepoRoot } from "./context.js";
4
4
  const flagSchema = {
5
- check: { long: "--check", type: "boolean" }
5
+ check: { long: '--check', type: 'boolean' },
6
6
  };
7
7
  const { flags } = parseArgsOrExit(process.argv.slice(2), flagSchema);
8
8
  try {
9
- runCommand(flags.check);
10
- } catch (error) {
11
- reportError(error instanceof Error ? error.message : String(error));
12
- process.exit(1);
9
+ runCommand(flags.check);
10
+ }
11
+ catch (error) {
12
+ reportError(error instanceof Error ? error.message : String(error));
13
+ process.exit(1);
13
14
  }
14
15
  function runCommand(checkOnly) {
15
- const monorepoRoot = findMonorepoRoot();
16
- if (!checkOnly) {
17
- const { path: destinationPath, packageSpecifier, changed } = sync(monorepoRoot);
18
- console.info(
19
- changed ? `\u2713 Wrote ${destinationPath} (${packageSpecifier})` : `\u2713 ${destinationPath} already up to date`
20
- );
21
- process.exit(0);
22
- }
23
- const result = check(monorepoRoot);
24
- if (result.ok) {
25
- console.info("\u2713 .agents/nmr/AGENTS.md is in sync");
26
- process.exit(0);
27
- }
28
- process.stderr.write(`${result.reason}
29
- `);
30
- process.exit(1);
16
+ const monorepoRoot = findMonorepoRoot();
17
+ if (!checkOnly) {
18
+ const { path: destinationPath, packageSpecifier, changed } = sync(monorepoRoot);
19
+ console.info(changed ? `✓ Wrote ${destinationPath} (${packageSpecifier})` : `✓ ${destinationPath} already up to date`);
20
+ process.exit(0);
21
+ }
22
+ const result = check(monorepoRoot);
23
+ if (result.ok) {
24
+ console.info('✓ .agents/nmr/AGENTS.md is in sync');
25
+ process.exit(0);
26
+ }
27
+ process.stderr.write(`${result.reason}\n`);
28
+ process.exit(1);
31
29
  }
@@ -1,10 +1,11 @@
1
- import { reportError } from "@williamthorsen/nmr-core";
1
+ import { reportError } from '@williamthorsen/nmr-core';
2
2
  import { syncPnpmVersion } from "./commands/sync-pnpm-version.js";
3
3
  import { findMonorepoRoot } from "./context.js";
4
4
  try {
5
- const monorepoRoot = findMonorepoRoot();
6
- syncPnpmVersion(monorepoRoot);
7
- } catch (error) {
8
- reportError(error instanceof Error ? error.message : String(error));
9
- process.exitCode = 1;
5
+ const monorepoRoot = findMonorepoRoot();
6
+ syncPnpmVersion(monorepoRoot);
7
+ }
8
+ catch (error) {
9
+ reportError(error instanceof Error ? error.message : String(error));
10
+ process.exitCode = 1;
10
11
  }
package/dist/esm/cli.js CHANGED
@@ -1,16 +1,16 @@
1
- import process from "node:process";
1
+ import process from 'node:process';
2
2
  import { runCli } from "./runCli.js";
3
3
  try {
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.exitCode = exitCode;
12
- } catch (error) {
13
- process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}
14
- `);
15
- process.exitCode = 1;
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.exitCode = exitCode;
12
+ }
13
+ catch (error) {
14
+ process.stderr.write(`${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
15
+ process.exitCode = 1;
16
16
  }
@@ -1,15 +1,9 @@
1
- import { type Format, type Platform } from 'esbuild';
2
1
  export interface BuildOptions {
3
- aliases?: Record<string, string>;
4
2
  entryGlobs?: string[];
5
3
  ignore?: string[];
6
4
  outdir?: string;
7
5
  cacheFile?: string;
8
- format?: Format;
9
- platform?: Platform;
10
- target?: string;
11
6
  }
12
7
  export declare function buildPackage(packageDir: string, options?: BuildOptions): Promise<void>;
13
- export declare function computeBuildHash(packageDir: string, files: string[], outputConfig: object): Promise<string>;
14
- export declare function resolveAliasImports(code: string, fileDir: string, aliasMap: Record<string, string>, packageDir: string): string;
15
- export declare function rewriteTsImportExtensions(code: string): string;
8
+ export declare function computeBuildHash(packageDir: string, files: string[], emitConfig: object): Promise<string>;
9
+ export declare function resolveTsconfigChain(packageDir: string, configFileName?: string): string[];