@williamthorsen/nmr 1.0.0 โ†’ 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.meta/changelog.json +104 -0
  2. package/.readyup/kits/default.js +12 -28
  3. package/.readyup/manifest.json +19 -24
  4. package/CHANGELOG.md +33 -0
  5. package/README.md +9 -3
  6. package/dist/esm/check-cache-gate.d.ts +0 -3
  7. package/dist/esm/check-cache-gate.js +1 -5
  8. package/dist/esm/cli-build.js +3 -10
  9. package/dist/esm/cli-clean.js +1 -2
  10. package/dist/esm/cli-report-catalog.js +2 -3
  11. package/dist/esm/cli-report-overrides.js +3 -5
  12. package/dist/esm/commands/build-output.d.ts +7 -32
  13. package/dist/esm/commands/build-output.js +8 -13
  14. package/dist/esm/commands/build-report.d.ts +6 -0
  15. package/dist/esm/commands/build-report.js +21 -0
  16. package/dist/esm/commands/build.d.ts +27 -4
  17. package/dist/esm/commands/build.js +24 -86
  18. package/dist/esm/commands/clean-report.d.ts +6 -0
  19. package/dist/esm/commands/clean-report.js +20 -0
  20. package/dist/esm/commands/clean.d.ts +39 -7
  21. package/dist/esm/commands/clean.js +57 -34
  22. package/dist/esm/commands/compile.d.ts +5 -0
  23. package/dist/esm/commands/compile.js +32 -0
  24. package/dist/esm/commands/fmt.d.ts +11 -0
  25. package/dist/esm/commands/fmt.js +6 -2
  26. package/dist/esm/commands/publish-output.d.ts +40 -0
  27. package/dist/esm/commands/publish-output.js +66 -0
  28. package/dist/esm/commands/report-catalog.d.ts +5 -0
  29. package/dist/esm/commands/report-catalog.js +19 -0
  30. package/dist/esm/commands/report-overrides.d.ts +5 -0
  31. package/dist/esm/commands/report-overrides.js +20 -1
  32. package/dist/esm/config-validation.js +1 -14
  33. package/dist/esm/config.d.ts +1 -1
  34. package/dist/esm/crossing-diagnostics.d.ts +1 -1
  35. package/dist/esm/resolver.d.ts +0 -12
  36. package/dist/esm/resolver.js +0 -35
  37. package/dist/esm/run-chain.d.ts +1 -1
  38. package/dist/esm/run-chain.js +1 -5
  39. package/dist/esm/types.d.ts +0 -1
  40. package/docs/check-cache.md +0 -1
  41. package/docs/scripts.md +17 -12
  42. package/docs/upgrades.md +1 -1
  43. package/docs/utilities.md +2 -2
  44. package/package.json +8 -8
@@ -1,4 +1,108 @@
1
1
  [
2
+ {
3
+ "version": "2.0.1",
4
+ "date": "2026-11-11",
5
+ "sections": [
6
+ {
7
+ "title": "๐Ÿ› Bug fixes",
8
+ "audience": "all",
9
+ "items": [
10
+ {
11
+ "description": "Fixes the issue that `nmr-clean`, `nmr-compile`, `nmr-report-catalog`, and `nmr-report-overrides` ignored their arguments, so `--help` or a mistyped option ran the command, and `nmr-clean --help` removed build output instead of printing usage. (#1273)",
12
+ "hash": "a4bfd2bc9c298a919c15070bae4437188c0edb37",
13
+ "entry": 1
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "title": "โ™ป๏ธ Refactoring",
19
+ "audience": "dev",
20
+ "items": [
21
+ {
22
+ "description": "Adds an optional `spawn` adapter to `runPrettier` in `packages/nmr/src/commands/fmt.ts`, which defaults to `spawnSync` with inherited stdio, so that a test can replace the Prettier process without mocking `node:child_process`. (#1271)",
23
+ "hash": "18227bf9c7d8fb0fb11bf3382602be131603419a",
24
+ "entry": 1
25
+ },
26
+ {
27
+ "description": "Extracts the unchanged-build skip report from `detectBuildChanges` into the exported `reportUnchangedBuild`, which takes the environment as a parameter rather than reading `process.env`. (#1272)",
28
+ "hash": "7708c82fe5b528709dd3c822c1449b6a843292c4",
29
+ "entry": 1
30
+ },
31
+ {
32
+ "description": "Replaces the copies of the entry-point `glob` call in `buildPackage` and `hasBuildOutput` with one function, `resolveEntryPoints` in `build-output.ts`, so the build and the check cache's probe for build output cannot select different files. (#1274)",
33
+ "hash": "aadd160714f8a4b00aa9965897b97fb15ee8500b",
34
+ "entry": 1
35
+ },
36
+ {
37
+ "description": "Moves the printing of the build's progress and outcome lines from `buildPackage` to `nmr-compile`, which renders the `BuildOutcome` returned by `buildPackage` and the events from its new `onProgress` callback through `build-report.ts`. (#1277)",
38
+ "hash": "7204e9ebbb7ef2d60f0ddf74a1ea4de4b62e50c0",
39
+ "entry": 1
40
+ },
41
+ {
42
+ "description": "Moves the emit's staging, swap by rename, and scratch-directory handling from `build.ts` and `build-output.ts` into `publish-output.ts`, which `build.ts` now calls with the emit directory. (#1278)",
43
+ "hash": "27bc6915156ae739206679b977baf91267f73fa9",
44
+ "entry": 1
45
+ },
46
+ {
47
+ "description": "Makes `cleanFrom` in `clean.ts` return a `CleanOutcome` and report each completed step through an `onProgress` callback rather than print, leaving `runClean` as the only function that prints, through the renderers in the new `clean-report.ts`. (#1279)",
48
+ "hash": "5188dd2411cd737d337ae8bf232fc68d5bc911c6",
49
+ "entry": 1
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "title": "๐Ÿงช Tests",
55
+ "audience": "dev",
56
+ "items": [
57
+ {
58
+ "description": "Moves the `runPrettier` cases from `fmt.tool.test.ts` to a new `fmt.unit.test.ts` that asserts on the invocations recorded by a spawn fake, and adds cases for a spawn error and for a Prettier process killed by a signal. (#1271)",
59
+ "hash": "18227bf9c7d8fb0fb11bf3382602be131603419a",
60
+ "entry": 2
61
+ },
62
+ {
63
+ "description": "Replaces six skip-report cases in `build.publication.tool.test.ts`, each of which compiled a package to seed the cache, with unit tests of `reportUnchangedBuild` in `build.unit.test.ts` that write the recorded emit duration directly. (#1272)",
64
+ "hash": "7708c82fe5b528709dd3c822c1449b6a843292c4",
65
+ "entry": 2
66
+ },
67
+ {
68
+ "description": "Tests `resolveEntryPoints` directly in `build-output.unit.test.ts` and removes the tool-tier tests in `build.tool.test.ts` that checked the same default exclusions, `ignorePatterns` override, and `extraIgnorePatterns` composition through a full build. (#1274)",
69
+ "hash": "aadd160714f8a4b00aa9965897b97fb15ee8500b",
70
+ "entry": 2
71
+ }
72
+ ]
73
+ }
74
+ ]
75
+ },
76
+ {
77
+ "version": "2.0.0",
78
+ "date": "2026-11-10",
79
+ "sections": [
80
+ {
81
+ "title": "๐Ÿชฆ Removed",
82
+ "audience": "all",
83
+ "items": [
84
+ {
85
+ "description": "Removes the `devBin` root-config field of `@williamthorsen/nmr`, which replaced the first token of a resolved command with a command that ran a bin from source. (#1269)",
86
+ "breaking": true,
87
+ "migration": "Replace each `devBin` entry by overriding the script that runs the bin in `rootScripts` or `workspaceScripts` with `node <path to the bin's source>`, and build an absolute path for a workspace script, because nmr no longer resolves relative paths from the monorepo root and a workspace script runs in each package's directory; a `package.json` script that runs the bin outranks the config, so change it in that `package.json`.",
88
+ "hash": "5a95865afe2560a4bb9e3f66448bd68f6a32c312",
89
+ "entry": 1
90
+ }
91
+ ]
92
+ },
93
+ {
94
+ "title": "โ™ป๏ธ Refactoring",
95
+ "audience": "dev",
96
+ "items": [
97
+ {
98
+ "description": "Move the duplicated output-style resolver into nmr-core's cli entry (#1248)",
99
+ "body": "Replaces the duplicate `resolveStyles` modules in `release-kit` and `v11y-check` with one `resolveCliStyles` function, exported from `@williamthorsen/nmr-core/cli`. Each package still reads its own `RELEASE_KIT_OUTPUT_STYLE` or `V11Y_CHECK_OUTPUT_STYLE` variable and still reports a usage error for a value other than `auto`, `plain`, or `rich`.",
100
+ "hash": "c5f4ac68360ce550a7e53006a2a633b79f179f5f"
101
+ }
102
+ ]
103
+ }
104
+ ]
105
+ },
2
106
  {
3
107
  "version": "1.0.0",
4
108
  "date": "2026-11-08",
@@ -1,6 +1,6 @@
1
1
  /** @noformat -- @generated. Do not edit. Compiled by rdy. */
2
2
  /* eslint-disable */
3
- export const __readyupVersion = "0.45.0";
3
+ export const __readyupVersion = "0.45.1";
4
4
  import { createRequire as __rdyCreateRequire } from 'node:module';
5
5
  const require = __rdyCreateRequire(import.meta.url);
6
6
 
@@ -7382,12 +7382,12 @@ import { isRecord, listTrackedFiles } from "readyup/check-utils";
7382
7382
  import { globSync } from "node:fs";
7383
7383
  import { basename, join, sep } from "node:path";
7384
7384
 
7385
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.9.0/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/isError.js
7385
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.9.1/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/isError.js
7386
7386
  function isError(error) {
7387
7387
  return error instanceof Error || Object.prototype.toString.call(error) === "[object Error]";
7388
7388
  }
7389
7389
 
7390
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.9.0/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/describeError.js
7390
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.9.1/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/describeError.js
7391
7391
  function describeError(error) {
7392
7392
  try {
7393
7393
  if (isError(error) && typeof error.message === "string" && error.message !== "") {
@@ -8162,11 +8162,11 @@ function formatMalformedScript(dir, key, value) {
8162
8162
  return `${rejection} A step list belongs in \`${CONFIG_RELATIVE_PATH}\` under \`${field}\`.`;
8163
8163
  }
8164
8164
 
8165
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8165
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8166
8166
  import path6 from "node:path";
8167
8167
  import process2 from "node:process";
8168
8168
 
8169
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/UsageError.js
8169
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/UsageError.js
8170
8170
  var UsageError = class extends Error {
8171
8171
  constructor(message, options) {
8172
8172
  super(message, options);
@@ -8174,7 +8174,7 @@ var UsageError = class extends Error {
8174
8174
  }
8175
8175
  };
8176
8176
 
8177
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/ParseError.js
8177
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/ParseError.js
8178
8178
  var ParseError = class extends UsageError {
8179
8179
  kind;
8180
8180
  token;
@@ -8186,12 +8186,12 @@ var ParseError = class extends UsageError {
8186
8186
  }
8187
8187
  };
8188
8188
 
8189
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/resolveLongName.js
8189
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/resolveLongName.js
8190
8190
  function resolveLongName(key, definition) {
8191
8191
  return definition.long ?? key.replaceAll(/[A-Z]/g, (capital) => `-${capital.toLowerCase()}`);
8192
8192
  }
8193
8193
 
8194
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/tokenizeArgs.js
8194
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/tokenizeArgs.js
8195
8195
  import { parseArgs as nodeParseArgs } from "node:util";
8196
8196
  function tokenizeArgs(argv, schema, extraBooleans = []) {
8197
8197
  const nodeOptions = {};
@@ -8221,7 +8221,7 @@ function findFlagEntry(flagsByName, token) {
8221
8221
  return entry.definition.short === token.rawName.slice(1) ? entry : void 0;
8222
8222
  }
8223
8223
 
8224
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/validateSpec.js
8224
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/validateSpec.js
8225
8225
  var CAMEL_CASE_KEY = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]+)*$/;
8226
8226
  function validateSpec(spec) {
8227
8227
  validateFlags(spec.flags ?? {});
@@ -8302,7 +8302,7 @@ function validateOperands(operands) {
8302
8302
  }
8303
8303
  }
8304
8304
 
8305
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.0/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8305
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8306
8306
  function parseArgs(argv, spec, options = {}) {
8307
8307
  validateSpec(spec);
8308
8308
  return parseValidatedArgs(argv, spec, options);
@@ -8733,11 +8733,6 @@ function readPackageScripts(packageDir) {
8733
8733
  }
8734
8734
  }
8735
8735
 
8736
- // src/helpers/type-guards.ts
8737
- function isStringRecord(value) {
8738
- return isRecord2(value) && Object.values(value).every((entry) => typeof entry === "string");
8739
- }
8740
-
8741
8736
  // src/verbosity.ts
8742
8737
  var COMMAND_VERBOSITIES = ["full", "quiet"];
8743
8738
  function formatVerbosityRejection(source, value) {
@@ -8752,7 +8747,7 @@ function isCommandVerbosity(value) {
8752
8747
  var CONFIG_TIERS = {
8753
8748
  root: {
8754
8749
  label: "a monorepo-root config",
8755
- honoredKeys: ["checkCache", "delegate", "devBin", "output", "rootScripts", "selfHostedRunner", "workspaceScripts"],
8750
+ honoredKeys: ["checkCache", "delegate", "output", "rootScripts", "selfHostedRunner", "workspaceScripts"],
8756
8751
  elsewhere: "the package's own config"
8757
8752
  },
8758
8753
  workspace: {
@@ -8788,8 +8783,6 @@ function validateConfig(value, configPath, baseDir) {
8788
8783
  const checkCache = validateCheckCacheField(value, configPath);
8789
8784
  if (checkCache) config.checkCache = checkCache;
8790
8785
  if (value["delegate"] !== void 0) config.delegate = validateDelegateField(value["delegate"], configPath);
8791
- const devBin = validateStringRecordField(value, "devBin", configPath);
8792
- if (devBin) config.devBin = devBin;
8793
8786
  const output = validateOutputField(value, configPath);
8794
8787
  if (output) config.output = output;
8795
8788
  const workspaceScripts2 = validateScriptField(value, "workspaceScripts", configPath);
@@ -8960,15 +8953,6 @@ function validateScriptField(value, fieldName, configPath) {
8960
8953
  assertValidElements(scripts, fieldName, configPath);
8961
8954
  return scripts;
8962
8955
  }
8963
- function validateStringRecordField(value, fieldName, configPath) {
8964
- if (!Object.hasOwn(value, fieldName) || value[fieldName] === void 0) {
8965
- return void 0;
8966
- }
8967
- if (!isStringRecord(value[fieldName])) {
8968
- throw new UserError(`Invalid nmr config at ${configPath}: \`${fieldName}\` must be a Record<string, string>`);
8969
- }
8970
- return value[fieldName];
8971
- }
8972
8956
 
8973
8957
  // src/helpers/escapeControlCharacters.ts
8974
8958
  var NAMED_ESCAPES = /* @__PURE__ */ new Map([
@@ -9611,7 +9595,7 @@ function hasSupportedStrictLintVersion() {
9611
9595
  });
9612
9596
  }
9613
9597
  function getMinVersion() {
9614
- const pickedFields = { "version": "1.0.0" };
9598
+ const pickedFields = { "version": "2.0.1" };
9615
9599
  if (typeof pickedFields["version"] !== "string") {
9616
9600
  throw new TypeError("nmr/package.json: 'version' must be a string");
9617
9601
  }
@@ -3,19 +3,19 @@
3
3
  "kits": [
4
4
  {
5
5
  "bundledDependencies": {
6
- "@williamthorsen/toolbelt.arrays": "6.4.1",
7
- "@williamthorsen/toolbelt.cli": "0.8.0",
8
- "@williamthorsen/toolbelt.datetime": "4.1.1",
9
- "@williamthorsen/toolbelt.errors": "0.9.0",
10
- "@williamthorsen/toolbelt.filesystem": "0.10.0",
11
- "@williamthorsen/toolbelt.guards": "4.2.0",
12
- "@williamthorsen/toolbelt.numbers": "7.3.1",
13
- "@williamthorsen/toolbelt.objects": "4.4.0",
14
- "@williamthorsen/toolbelt.packaging": "0.7.0",
6
+ "@williamthorsen/toolbelt.arrays": "6.4.2",
7
+ "@williamthorsen/toolbelt.cli": "0.8.1",
8
+ "@williamthorsen/toolbelt.datetime": "4.1.2",
9
+ "@williamthorsen/toolbelt.errors": "0.9.1",
10
+ "@williamthorsen/toolbelt.filesystem": "0.10.1",
11
+ "@williamthorsen/toolbelt.guards": "4.2.1",
12
+ "@williamthorsen/toolbelt.numbers": "7.3.2",
13
+ "@williamthorsen/toolbelt.objects": "4.4.1",
14
+ "@williamthorsen/toolbelt.packaging": "0.7.1",
15
15
  "@williamthorsen/toolbelt.sets": "3.1.0",
16
- "@williamthorsen/toolbelt.strings": "8.2.1",
16
+ "@williamthorsen/toolbelt.strings": "8.2.2",
17
17
  "@williamthorsen/toolbelt.terminal": "0.3.0",
18
- "ansi-regex": "6.5.0",
18
+ "ansi-regex": "6.6.0",
19
19
  "ansi-styles": "6.2.3",
20
20
  "cli-truncate": "6.1.1",
21
21
  "diff": "9.0.0",
@@ -32,7 +32,7 @@
32
32
  "checklist": "nmr",
33
33
  "checks": [
34
34
  "@williamthorsen/nmr in devDependencies",
35
- "@williamthorsen/nmr >= 1.0.0",
35
+ "@williamthorsen/nmr >= 2.0.1",
36
36
  "pnpm-workspace.yaml exists",
37
37
  "catalogued dependencies use catalog:",
38
38
  "package.json has packageManager field",
@@ -86,7 +86,7 @@
86
86
  "path": "../../nmr-core/dist/esm/cache/cache-store.js"
87
87
  },
88
88
  {
89
- "hash": "d2edf9e9",
89
+ "hash": "6ea2c3c0",
90
90
  "kind": "module",
91
91
  "path": "../../nmr-core/dist/esm/cli.js"
92
92
  },
@@ -236,7 +236,7 @@
236
236
  "path": "lib/workspace-build.ts"
237
237
  },
238
238
  {
239
- "hash": "2afa0f3c",
239
+ "hash": "c0b5a504",
240
240
  "kind": "inline",
241
241
  "path": "../package.json",
242
242
  "paths": [
@@ -254,12 +254,12 @@
254
254
  "path": "../src/config-check-cache.ts"
255
255
  },
256
256
  {
257
- "hash": "378fb666",
257
+ "hash": "d25aef4f",
258
258
  "kind": "module",
259
259
  "path": "../src/config-validation.ts"
260
260
  },
261
261
  {
262
- "hash": "8cb3c082",
262
+ "hash": "d345a285",
263
263
  "kind": "module",
264
264
  "path": "../src/config.ts"
265
265
  },
@@ -308,11 +308,6 @@
308
308
  "kind": "module",
309
309
  "path": "../src/helpers/reportClosing.ts"
310
310
  },
311
- {
312
- "hash": "3f8448c9",
313
- "kind": "module",
314
- "path": "../src/helpers/type-guards.ts"
315
- },
316
311
  {
317
312
  "hash": "77f6bb50",
318
313
  "kind": "module",
@@ -324,7 +319,7 @@
324
319
  "path": "../src/resolve-scripts.ts"
325
320
  },
326
321
  {
327
- "hash": "5112becc",
322
+ "hash": "0112952e",
328
323
  "kind": "module",
329
324
  "path": "../src/resolver.ts"
330
325
  },
@@ -361,10 +356,10 @@
361
356
  ],
362
357
  "name": "default",
363
358
  "path": "kits/default.js",
364
- "readyupVersion": "0.45.0",
359
+ "readyupVersion": "0.45.1",
365
360
  "source": "kits/default.ts",
366
361
  "sourceHash": "9fd61bf4",
367
- "targetHash": "5818d99b"
362
+ "targetHash": "8eca943d"
368
363
  }
369
364
  ]
370
365
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 2.0.1 โ€” 2026-11-11
6
+
7
+ ### ๐Ÿ› Bug fixes
8
+
9
+ - Fixes the issue that `nmr-clean`, `nmr-compile`, `nmr-report-catalog`, and `nmr-report-overrides` ignored their arguments, so `--help` or a mistyped option ran the command, and `nmr-clean --help` removed build output instead of printing usage. (#1273)
10
+
11
+ ### โ™ป๏ธ Refactoring
12
+
13
+ - Adds an optional `spawn` adapter to `runPrettier` in `packages/nmr/src/commands/fmt.ts`, which defaults to `spawnSync` with inherited stdio, so that a test can replace the Prettier process without mocking `node:child_process`. (#1271)
14
+ - Extracts the unchanged-build skip report from `detectBuildChanges` into the exported `reportUnchangedBuild`, which takes the environment as a parameter rather than reading `process.env`. (#1272)
15
+ - Replaces the copies of the entry-point `glob` call in `buildPackage` and `hasBuildOutput` with one function, `resolveEntryPoints` in `build-output.ts`, so the build and the check cache's probe for build output cannot select different files. (#1274)
16
+ - Moves the printing of the build's progress and outcome lines from `buildPackage` to `nmr-compile`, which renders the `BuildOutcome` returned by `buildPackage` and the events from its new `onProgress` callback through `build-report.ts`. (#1277)
17
+ - Moves the emit's staging, swap by rename, and scratch-directory handling from `build.ts` and `build-output.ts` into `publish-output.ts`, which `build.ts` now calls with the emit directory. (#1278)
18
+ - Makes `cleanFrom` in `clean.ts` return a `CleanOutcome` and report each completed step through an `onProgress` callback rather than print, leaving `runClean` as the only function that prints, through the renderers in the new `clean-report.ts`. (#1279)
19
+
20
+ ### ๐Ÿงช Tests
21
+
22
+ - Moves the `runPrettier` cases from `fmt.tool.test.ts` to a new `fmt.unit.test.ts` that asserts on the invocations recorded by a spawn fake, and adds cases for a spawn error and for a Prettier process killed by a signal. (#1271)
23
+ - Replaces six skip-report cases in `build.publication.tool.test.ts`, each of which compiled a package to seed the cache, with unit tests of `reportUnchangedBuild` in `build.unit.test.ts` that write the recorded emit duration directly. (#1272)
24
+ - Tests `resolveEntryPoints` directly in `build-output.unit.test.ts` and removes the tool-tier tests in `build.tool.test.ts` that checked the same default exclusions, `ignorePatterns` override, and `extraIgnorePatterns` composition through a full build. (#1274)
25
+
26
+ ## 2.0.0 โ€” 2026-11-10
27
+
28
+ ### ๐Ÿชฆ Removed
29
+
30
+ - ๐Ÿšจ **Breaking:** Removes the `devBin` root-config field of `@williamthorsen/nmr`, which replaced the first token of a resolved command with a command that ran a bin from source. (#1269)
31
+
32
+ ### โ™ป๏ธ Refactoring
33
+
34
+ - Move the duplicated output-style resolver into nmr-core's cli entry (#1248)
35
+
36
+ Replaces the duplicate `resolveStyles` modules in `release-kit` and `v11y-check` with one `resolveCliStyles` function, exported from `@williamthorsen/nmr-core/cli`. Each package still reads its own `RELEASE_KIT_OUTPUT_STYLE` or `V11Y_CHECK_OUTPUT_STYLE` variable and still reports a usage error for a value other than `auto`, `plain`, or `rich`.
37
+
5
38
  ## 1.0.0 โ€” 2026-11-08
6
39
 
7
40
  ### ๐Ÿงช Tests
package/README.md CHANGED
@@ -4,7 +4,13 @@
4
4
 
5
5
  Context-aware script runner for pnpm monorepos. Includes an `nmr` (node-monorepo run) binary that provides centralized, consistent script execution across workspace packages and the monorepo root.
6
6
 
7
- <!-- section:release-notes --><!-- /section:release-notes -->
7
+ <!-- section:release-notes -->
8
+ ## Release notes โ€” v2.0.1 (2026-11-11)
9
+
10
+ ### ๐Ÿ› Bug fixes
11
+
12
+ - Fixes the issue that `nmr-clean`, `nmr-compile`, `nmr-report-catalog`, and `nmr-report-overrides` ignored their arguments, so `--help` or a mistyped option ran the command, and `nmr-clean --help` removed build output instead of printing usage. (#1273)
13
+ <!-- /section:release-notes -->
8
14
 
9
15
  ## Installation
10
16
 
@@ -141,7 +147,7 @@ export default defineConfig({
141
147
  });
142
148
  ```
143
149
 
144
- [Scripts and configuration](docs/scripts.md#configuration) lists every `defineConfig` field and covers package-level configuration and `devBin`.
150
+ [Scripts and configuration](docs/scripts.md#configuration) lists every `defineConfig` field, covers package-level configuration, and shows how to run a workspace bin from source.
145
151
 
146
152
  ## What nmr reports
147
153
 
@@ -304,7 +310,7 @@ rdy list --from npm:@williamthorsen/nmr # what nmr publishes
304
310
 
305
311
  ## Documentation
306
312
 
307
- - [Scripts and configuration](docs/scripts.md): Override tiers, script values, hooks, `defineConfig` fields, `devBin`, and the default commands
313
+ - [Scripts and configuration](docs/scripts.md): Override tiers, script values, hooks, `defineConfig` fields, running a workspace bin from source, and the default commands
308
314
  - [What nmr reports](docs/reporting.md): Verdicts, `--json`, output style, and verbosity
309
315
  - [Check-result cache](docs/check-cache.md): What is skipped, what a hit requires, and replaying, reading, and bypassing recorded runs
310
316
  - [Machine lock](docs/machine-lock.md): Which commands wait for one another, where the lock lives, and bypassing it
@@ -25,8 +25,6 @@ export declare function openGate(options: {
25
25
  }): TreeSnapshot | undefined;
26
26
  /**
27
27
  * Computes the key under which this invocation would be recorded, or `undefined` when the cache does not apply.
28
- * The cache does not apply to a `devBin` substitution: the substitute is built from a location that the tree id
29
- * does not describe, so a pass by it is not a pass by the command that the key names.
30
28
  */
31
29
  export declare function resolveCacheKey(options: {
32
30
  anchorDir: string;
@@ -36,7 +34,6 @@ export declare function resolveCacheKey(options: {
36
34
  monorepoRoot: string;
37
35
  snapshot: TreeSnapshot | undefined;
38
36
  stderr: Writable;
39
- substitution: string | undefined;
40
37
  }): string | undefined;
41
38
  /**
42
39
  * Runs the resolved steps behind the check-result cache: skips them when a recorded pass covers this
@@ -33,14 +33,10 @@ export function openGate(options) {
33
33
  return snapshot.snapshot;
34
34
  }
35
35
  export function resolveCacheKey(options) {
36
- const { env, snapshot, stderr, substitution } = options;
36
+ const { env, snapshot, stderr } = options;
37
37
  if (snapshot === undefined) {
38
38
  return undefined;
39
39
  }
40
- if (substitution !== undefined) {
41
- writeDebugNote(`gate disabled: devBin substituted \`${substitution}\``, env, stderr);
42
- return undefined;
43
- }
44
40
  const result = computeCacheKey({
45
41
  anchorDir: options.anchorDir,
46
42
  command: options.command,
@@ -1,16 +1,9 @@
1
+ import process from 'node:process';
1
2
  import { reportFailure } from '@williamthorsen/nmr-core';
2
3
  import { NMR_COMPILE_BIN } from "./bins.js";
3
- import { buildPackage } from "./commands/build.js";
4
- import { loadWorkspaceConfig } from "./config.js";
5
- import { resolveBinStyles } from "./resolveBinStyles.js";
4
+ import { runCompile } from "./commands/compile.js";
6
5
  try {
7
- const { stdout } = resolveBinStyles();
8
- const packageDir = process.cwd();
9
- const { build } = await loadWorkspaceConfig(packageDir);
10
- await buildPackage(packageDir, {
11
- style: stdout,
12
- ...(build?.extraIgnorePatterns !== undefined && { extraIgnorePatterns: build.extraIgnorePatterns }),
13
- });
6
+ process.exitCode = await runCompile(process.argv.slice(2), process.cwd());
14
7
  }
15
8
  catch (error) {
16
9
  reportFailure(NMR_COMPILE_BIN, error);
@@ -2,9 +2,8 @@ import process from 'node:process';
2
2
  import { reportFailure } from '@williamthorsen/nmr-core';
3
3
  import { NMR_CLEAN_BIN } from "./bins.js";
4
4
  import { runClean } from "./commands/clean.js";
5
- import { resolveBinStyles } from "./resolveBinStyles.js";
6
5
  try {
7
- await runClean(process.cwd(), resolveBinStyles().stdout);
6
+ process.exitCode = await runClean(process.argv.slice(2), process.cwd());
8
7
  }
9
8
  catch (error) {
10
9
  reportFailure(NMR_CLEAN_BIN, error);
@@ -1,10 +1,9 @@
1
1
  import process from 'node:process';
2
2
  import { reportFailure } from '@williamthorsen/nmr-core';
3
3
  import { NMR_REPORT_CATALOG_BIN } from "./bins.js";
4
- import { reportCatalog } from "./commands/report-catalog.js";
5
- import { resolveBinStyles } from "./resolveBinStyles.js";
4
+ import { runReportCatalog } from "./commands/report-catalog.js";
6
5
  try {
7
- reportCatalog(process.cwd(), resolveBinStyles().stderr);
6
+ process.exitCode = await runReportCatalog(process.argv.slice(2), process.cwd());
8
7
  }
9
8
  catch (error) {
10
9
  reportFailure(NMR_REPORT_CATALOG_BIN, error);
@@ -1,11 +1,9 @@
1
+ import process from 'node:process';
1
2
  import { reportFailure } from '@williamthorsen/nmr-core';
2
3
  import { NMR_REPORT_OVERRIDES_BIN } from "./bins.js";
3
- import { reportOverrides } from "./commands/report-overrides.js";
4
- import { resolveBinStyles } from "./resolveBinStyles.js";
5
- import { findMonorepoRoot } from "./workspace.js";
4
+ import { runReportOverrides } from "./commands/report-overrides.js";
6
5
  try {
7
- const monorepoRoot = findMonorepoRoot();
8
- reportOverrides(monorepoRoot, resolveBinStyles().stderr);
6
+ process.exitCode = await runReportOverrides(process.argv.slice(2), process.cwd());
9
7
  }
10
8
  catch (error) {
11
9
  reportFailure(NMR_REPORT_OVERRIDES_BIN, error);
@@ -6,28 +6,13 @@ export interface BuildOptions {
6
6
  ignorePatterns?: string[];
7
7
  outdir?: string;
8
8
  }
9
- /** The pair of directories through which a build publishes, both siblings of the emit directory. */
10
- export interface ScratchDirs {
11
- previousDir: string;
12
- stagingDir: string;
13
- }
14
- export declare const DEFAULT_ENTRY_GLOBS: string[];
15
- /**
16
- * Directories holding test scaffolding rather than published code, excluded from entry-point selection so that a
17
- * package does not publish its own helpers. The list differs from the vitest factory's `COVERAGE_EXCLUDE`, which
18
- * leaves `test-utils/` inside the coverage include set, and neither list can be derived from the other.
19
- *
20
- * Ignoring a file removes it as an entry point, not from the emit. The compiler still emits whatever the
21
- * surviving entry points import, which keeps a production module that uses a helper from emitting a dangling
22
- * specifier. Widening this list can therefore only drop files that production code does not import.
23
- */
24
- export declare const DEFAULT_IGNORE_PATTERNS: string[];
25
9
  export declare const DEFAULT_OUTDIR = "dist/esm/";
26
10
  /**
27
- * Reports whether the output that a build of `packageDir` would produce is currently on disk. Applies the build's
28
- * own rule, on the same options that the build was given, so a caller probing for output and the build deciding
29
- * whether to rebuild cannot disagree: A package whose entry points emit nothing does not expect any output, and
30
- * reports present. Passing options that the build was not given would make the two disagree.
11
+ * Reports whether the output that a build of `packageDir` would produce is currently on disk. Selects entry points
12
+ * through `resolveEntryPoints` and tests them through `hasExpectedBuildOutput`, as the build does, so a caller
13
+ * probing for output and the build deciding whether to rebuild cannot disagree: A package whose entry points emit
14
+ * nothing does not expect any output, and reports present. Passing options that the build was not given would make
15
+ * the two disagree.
31
16
  */
32
17
  export declare function hasBuildOutput(packageDir: string, options?: BuildOptions): Promise<boolean>;
33
18
  /**
@@ -65,18 +50,8 @@ export declare function resolveBuildCachePath(packageDir: string): string;
65
50
  * `readBuildDigest` returns.
66
51
  */
67
52
  export declare function resolveEmitDurationPath(packageDir: string): string;
68
- /**
69
- * Resolves the two scratch directories through which a build publishes: `staging`, which the emit is written to,
70
- * and `previous`, which the outgoing output is renamed aside to. Both are siblings of the emit directory, so a
71
- * rename between them never crosses a filesystem, and both are dot-prefixed so that a leftover stays out of the
72
- * globs that select sources.
73
- *
74
- * The names are fixed rather than unique because the build removes both before use. A unique name would need a
75
- * sweep to accomplish that removal, and a sweep cannot tell a directory orphaned by a killed run from one that a
76
- * concurrent build is still writing. Because a directory left behind by a failed build is cleared the same way,
77
- * the failure path does not need any cleanup of its own.
78
- */
79
- export declare function resolveScratchDirs(emitDir: string): ScratchDirs;
53
+ /** Selects the entry points that a build of `packageDir` compiles, as paths relative to `packageDir`. */
54
+ export declare function resolveEntryPoints(packageDir: string, options?: BuildOptions): Promise<string[]>;
80
55
  /**
81
56
  * Resolves the fingerprint of the nmr compiling `packageDir`, the `BuildToolchain` member that identifies nmr. It
82
57
  * is nmr's own build digest when one is on disk, which changes on a dev-loop edit that the version does not
@@ -3,17 +3,14 @@ import path from 'node:path';
3
3
  import { findPackageRoot, readCacheEntry, readGuardedCacheEntry, resolveCacheEntryPath, writeGuardedCacheEntry, } from '@williamthorsen/nmr-core';
4
4
  import { glob } from 'glob';
5
5
  import { readPackageJson } from "../helpers/package-json.js";
6
- export const DEFAULT_ENTRY_GLOBS = ['src/**/*.{ts,tsx}'];
7
- export const DEFAULT_IGNORE_PATTERNS = ['**/__fixtures__/**', '**/__mocks__/**', '**/__tests__/**', '**/test-utils/**'];
6
+ const DEFAULT_ENTRY_GLOBS = ['src/**/*.{ts,tsx}'];
7
+ const DEFAULT_IGNORE_PATTERNS = ['**/__fixtures__/**', '**/__mocks__/**', '**/__tests__/**', '**/test-utils/**'];
8
8
  export const DEFAULT_OUTDIR = 'dist/esm/';
9
9
  const BUILD_CACHE_TOOL = 'nmr-compile';
10
10
  const SELF_DIR = findPackageRoot(import.meta.url);
11
11
  export async function hasBuildOutput(packageDir, options = {}) {
12
12
  const outdir = options.outdir ?? DEFAULT_OUTDIR;
13
- const entryPoints = await glob(options.entryGlobs ?? DEFAULT_ENTRY_GLOBS, {
14
- cwd: packageDir,
15
- ignore: [...(options.ignorePatterns ?? DEFAULT_IGNORE_PATTERNS), ...(options.extraIgnorePatterns ?? [])],
16
- });
13
+ const entryPoints = await resolveEntryPoints(packageDir, options);
17
14
  return hasExpectedBuildOutput(packageDir, outdir, entryPoints);
18
15
  }
19
16
  export function hasExpectedBuildOutput(packageDir, outdir, entryPoints) {
@@ -49,13 +46,11 @@ export function resolveEmitDurationPath(packageDir) {
49
46
  extension: '.duration.json',
50
47
  });
51
48
  }
52
- export function resolveScratchDirs(emitDir) {
53
- const parent = path.dirname(emitDir);
54
- const name = path.basename(emitDir);
55
- return {
56
- previousDir: path.join(parent, `.${name}.previous`),
57
- stagingDir: path.join(parent, `.${name}.staging`),
58
- };
49
+ export async function resolveEntryPoints(packageDir, options = {}) {
50
+ return glob(options.entryGlobs ?? DEFAULT_ENTRY_GLOBS, {
51
+ cwd: packageDir,
52
+ ignore: [...(options.ignorePatterns ?? DEFAULT_IGNORE_PATTERNS), ...(options.extraIgnorePatterns ?? [])],
53
+ });
59
54
  }
60
55
  export async function resolveToolchainFingerprint(packageDir, selfDir = SELF_DIR) {
61
56
  if (!isSameDir(packageDir, selfDir)) {
@@ -0,0 +1,6 @@
1
+ import { type OutputStyle } from '@williamthorsen/nmr-core';
2
+ import type { BuildOutcome, BuildProgress } from './build.js';
3
+ /** Renders the line that closes a build: the skip, naming the saving when it is worth naming, or the emit. */
4
+ export declare function renderBuildOutcome(outcome: BuildOutcome, packageName: string, style: OutputStyle): string;
5
+ /** Renders the line that announces an emit, naming why the build is about to run. */
6
+ export declare function renderBuildProgress(progress: BuildProgress, packageName: string, style: OutputStyle): string;
@@ -0,0 +1,21 @@
1
+ import { formatGlyphLine, STATUS_GLYPHS } from '@williamthorsen/nmr-core';
2
+ import { NMR_GLYPHS } from "../glyphs.js";
3
+ import { formatSaving } from "../helpers/duration.js";
4
+ export function renderBuildOutcome(outcome, packageName, style) {
5
+ if (outcome.kind === 'unchanged') {
6
+ const saving = formatSaving(outcome.savedMs ?? 0);
7
+ const savingClause = saving === undefined ? '' : `, ${saving}`;
8
+ return `${STATUS_GLYPHS[style].skipped.text} ${packageName}: No changes detected. Skipping build${savingClause}.`;
9
+ }
10
+ return formatGlyphLine(NMR_GLYPHS, style, 'package', `${packageName}: ${describeEmit(outcome.fileCount, outcome.outdir)}`);
11
+ }
12
+ export function renderBuildProgress(progress, packageName, style) {
13
+ const message = progress.kind === 'output-missing' ? 'Build output is missing. Rebuilding.' : 'Changes detected.';
14
+ return formatGlyphLine(NMR_GLYPHS, style, 'package', `${packageName}: ${message}`);
15
+ }
16
+ function describeEmit(fileCount, outdir) {
17
+ if (fileCount === 0) {
18
+ return 'Did not emit any output.';
19
+ }
20
+ return `Compiled ${fileCount} files to ${outdir.replace(/\/+$/u, '')}.`;
21
+ }