@williamthorsen/nmr 2.0.2 → 2.1.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 (73) hide show
  1. package/.meta/changelog.json +72 -0
  2. package/.readyup/kits/default.js +66 -35
  3. package/.readyup/manifest.json +38 -39
  4. package/CHANGELOG.md +25 -0
  5. package/README.md +7 -9
  6. package/dist/esm/assertResolvableCommandLists.d.ts +18 -0
  7. package/dist/esm/{check-cache/config-check-cache.js → assertResolvableCommandLists.js} +20 -12
  8. package/dist/esm/check-cache/check-cache-entries.js +1 -1
  9. package/dist/esm/check-cache/check-cache-gate.js +1 -1
  10. package/dist/esm/check-cache/check-cache-record.js +1 -1
  11. package/dist/esm/check-cache/check-cache.js +1 -1
  12. package/dist/esm/commands/build-output.d.ts +2 -1
  13. package/dist/esm/commands/build.js +1 -1
  14. package/dist/esm/commands/fmt.js +1 -1
  15. package/dist/esm/commands/taze.js +2 -2
  16. package/dist/esm/config-validation.js +25 -3
  17. package/dist/esm/config.js +1 -1
  18. package/dist/esm/crossing-diagnostics.js +1 -1
  19. package/dist/esm/defineConfig.d.ts +1 -1
  20. package/dist/esm/delegation/delegate-host.js +2 -2
  21. package/dist/esm/delegation/remote-probe.js +1 -1
  22. package/dist/esm/delegation/user-config.d.ts +2 -1
  23. package/dist/esm/delegation/user-config.js +1 -1
  24. package/dist/esm/{help.js → describeHelp.js} +1 -1
  25. package/dist/esm/fan-out.d.ts +7 -0
  26. package/dist/esm/fan-out.js +7 -2
  27. package/dist/esm/invocation.d.ts +1 -1
  28. package/dist/esm/machine-lock.d.ts +0 -4
  29. package/dist/esm/machine-lock.js +2 -2
  30. package/dist/esm/presentation.js +1 -1
  31. package/dist/esm/report-format.d.ts +2 -8
  32. package/dist/esm/report-format.js +8 -8
  33. package/dist/esm/resolve-scripts.js +1 -1
  34. package/dist/esm/route.js +1 -1
  35. package/dist/esm/run-chain.js +1 -1
  36. package/dist/esm/runCli.js +2 -2
  37. package/dist/esm/tiers.js +1 -1
  38. package/dist/esm/types.d.ts +12 -0
  39. package/dist/esm/verbosity.d.ts +2 -1
  40. package/dist/esm/verbosity.js +1 -1
  41. package/dist/esm/vitest.js +2 -2
  42. package/docs/scripts.md +12 -0
  43. package/package.json +8 -8
  44. package/dist/esm/check-cache/config-check-cache.d.ts +0 -17
  45. /package/dist/esm/check-cache/{replay-assembly.d.ts → assembleReplay.d.ts} +0 -0
  46. /package/dist/esm/check-cache/{replay-assembly.js → assembleReplay.js} +0 -0
  47. /package/dist/esm/check-cache/{check-cache-delegation.d.ts → recordDelegatedPass.d.ts} +0 -0
  48. /package/dist/esm/check-cache/{check-cache-delegation.js → recordDelegatedPass.js} +0 -0
  49. /package/dist/esm/commands/{load-package-maturity-period.d.ts → loadPackageMaturityPeriod.d.ts} +0 -0
  50. /package/dist/esm/commands/{load-package-maturity-period.js → loadPackageMaturityPeriod.js} +0 -0
  51. /package/dist/esm/commands/{taze-passes.d.ts → planTazePasses.d.ts} +0 -0
  52. /package/dist/esm/commands/{taze-passes.js → planTazePasses.js} +0 -0
  53. /package/dist/esm/commands/{format-targets.d.ts → resolveFormatTargets.d.ts} +0 -0
  54. /package/dist/esm/commands/{format-targets.js → resolveFormatTargets.js} +0 -0
  55. /package/dist/esm/commands/{tsconfig-chain.d.ts → resolveTsconfigChain.d.ts} +0 -0
  56. /package/dist/esm/commands/{tsconfig-chain.js → resolveTsconfigChain.js} +0 -0
  57. /package/dist/esm/delegation/{remote-snapshot.d.ts → createSnapshot.d.ts} +0 -0
  58. /package/dist/esm/delegation/{remote-snapshot.js → createSnapshot.js} +0 -0
  59. /package/dist/esm/delegation/{remote-probe-host.d.ts → probeHostLocks.d.ts} +0 -0
  60. /package/dist/esm/delegation/{remote-probe-host.js → probeHostLocks.js} +0 -0
  61. /package/dist/esm/delegation/{delegation-target.d.ts → resolveDelegationTarget.d.ts} +0 -0
  62. /package/dist/esm/delegation/{delegation-target.js → resolveDelegationTarget.js} +0 -0
  63. /package/dist/esm/{help.d.ts → describeHelp.d.ts} +0 -0
  64. /package/dist/esm/helpers/{hook-name.d.ts → isHookName.d.ts} +0 -0
  65. /package/dist/esm/helpers/{hook-name.js → isHookName.js} +0 -0
  66. /package/dist/esm/helpers/{type-guards.d.ts → isStringRecord.d.ts} +0 -0
  67. /package/dist/esm/helpers/{type-guards.js → isStringRecord.js} +0 -0
  68. /package/dist/esm/{git-ignored-paths.d.ts → listGitIgnoredPaths.d.ts} +0 -0
  69. /package/dist/esm/{git-ignored-paths.js → listGitIgnoredPaths.js} +0 -0
  70. /package/dist/esm/{bin-path.d.ts → prependBinDirs.d.ts} +0 -0
  71. /package/dist/esm/{bin-path.js → prependBinDirs.js} +0 -0
  72. /package/dist/esm/{workspace-globs.d.ts → toWorkspacePackageGlobs.d.ts} +0 -0
  73. /package/dist/esm/{workspace-globs.js → toWorkspacePackageGlobs.js} +0 -0
@@ -1,4 +1,76 @@
1
1
  [
2
+ {
3
+ "version": "2.1.0",
4
+ "date": "2026-11-12",
5
+ "sections": [
6
+ {
7
+ "title": "🎉 Features",
8
+ "audience": "all",
9
+ "items": [
10
+ {
11
+ "description": "Adds the `fanOut.extraSortedCommands` root-config key, which lists commands besides `build` whose fan-out keeps the topological order, and nmr rejects a name in it that does not resolve to any command when it loads the config. (#1306)",
12
+ "hash": "01a2cf5ec7b3a64da280a9053c1d608e8594e3f8",
13
+ "entry": 2
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "title": "⚡ Performance",
19
+ "audience": "all",
20
+ "items": [
21
+ {
22
+ "description": "Drops pnpm's topological sort, which runs each package only after its workspace dependencies finish, from every `-R` and `-F` fan-out except one of `build`, so that `nmr test`, the `test:*` commands, and `nmr typecheck` start each package without waiting for its workspace dependencies. (#1306)",
23
+ "hash": "01a2cf5ec7b3a64da280a9053c1d608e8594e3f8",
24
+ "entry": 1
25
+ }
26
+ ]
27
+ },
28
+ {
29
+ "title": "♻️ Refactoring",
30
+ "audience": "dev",
31
+ "items": [
32
+ {
33
+ "description": "Renames each single-export module in `packages/nmr/src` after its main export, such as `bin-path.ts` to `prependBinDirs.ts`, and renames the test files that matched the old names. (#1303)",
34
+ "hash": "5e1b404c4aa76defaf9d3921b3b88d7266a57297",
35
+ "entry": 1
36
+ },
37
+ {
38
+ "description": "Stops exporting seven values from `nmr`'s source modules, among them `REPORT_FORMATS`, `COMMAND_VERBOSITIES`, and the machine lock's `POLL_INTERVAL_MS`, because only the module that defines each one reads it. (#1305)",
39
+ "hash": "fa7addca5299b46f5e8a40663cae8e8c56b4547d",
40
+ "entry": 1
41
+ }
42
+ ]
43
+ },
44
+ {
45
+ "title": "🧪 Tests",
46
+ "audience": "dev",
47
+ "items": [
48
+ {
49
+ "description": "Replaces the hand-copied git setup in the nmr tool tests and fixtures, and the local `initRepo` that shadowed the shared helper in `check-cache.tool.test.ts`, with calls to `test-utils/initRepo.ts`. (#1304)",
50
+ "hash": "20121aef0112f24b64880b68675d6cdcea6066a0",
51
+ "entry": 1
52
+ }
53
+ ]
54
+ }
55
+ ]
56
+ },
57
+ {
58
+ "version": "2.0.3",
59
+ "date": "2026-11-12",
60
+ "sections": [
61
+ {
62
+ "title": "⚙️ Tooling",
63
+ "audience": "dev",
64
+ "items": [
65
+ {
66
+ "description": "Drops `--conditions nmr-source` from the `prepare` scripts of `build-info`, `build-info-react`, and `change-grammar`, which pnpm now runs after nmr-core is built because each package depends on nmr-core through nmr. (#1302)",
67
+ "hash": "9316254f051da705a8d588fad273e3a837501594",
68
+ "entry": 2
69
+ }
70
+ ]
71
+ }
72
+ ]
73
+ },
2
74
  {
3
75
  "version": "2.0.2",
4
76
  "date": "2026-11-12",
@@ -1,6 +1,6 @@
1
1
  /** @noformat -- @generated. Do not edit. Compiled by rdy. */
2
2
  /* eslint-disable */
3
- export const __readyupVersion = "0.45.1";
3
+ export const __readyupVersion = "0.46.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.1/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/isError.js
7385
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.10.0/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.1/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/describeError.js
7390
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.10.0/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 !== "") {
@@ -7725,9 +7725,15 @@ function buildRootScripts(workspaceGlobs) {
7725
7725
  };
7726
7726
  }
7727
7727
 
7728
+ // src/toWorkspacePackageGlobs.ts
7729
+ import path from "node:path";
7730
+ function toWorkspacePackageGlobs(monorepoRoot, packageDirs) {
7731
+ return packageDirs.map((dir) => path.relative(monorepoRoot, dir).split(path.sep).join("/")).filter((relativeDir) => relativeDir !== "").map((relativeDir) => `${relativeDir}/**`).toSorted();
7732
+ }
7733
+
7728
7734
  // ../nmr-core/dist/esm/workspace/workspace.js
7729
7735
  import { existsSync, readFileSync } from "node:fs";
7730
- import path2 from "node:path";
7736
+ import path3 from "node:path";
7731
7737
  var import_yaml = __toESM(require_dist(), 1);
7732
7738
 
7733
7739
  // ../nmr-core/dist/esm/portable/isRecord.js
@@ -7737,7 +7743,7 @@ function isRecord2(value) {
7737
7743
 
7738
7744
  // ../nmr-core/dist/esm/workspace/workspace-patterns.js
7739
7745
  import { globSync as globSync2 } from "node:fs";
7740
- import path from "node:path";
7746
+ import path2 from "node:path";
7741
7747
  var MANIFEST_FILENAME = "package.json";
7742
7748
  var ALWAYS_EXCLUDED_PATTERNS = ["**/node_modules/**"];
7743
7749
  function matchPackageDirs(monorepoRoot, includedPatterns, excludedPatterns) {
@@ -7747,7 +7753,7 @@ function matchPackageDirs(monorepoRoot, includedPatterns, excludedPatterns) {
7747
7753
  followSymlinks: true
7748
7754
  };
7749
7755
  const matches = globSync2([...includedPatterns], options);
7750
- const dirs = matches.map((match) => path.dirname(path.resolve(monorepoRoot, match)));
7756
+ const dirs = matches.map((match) => path2.dirname(path2.resolve(monorepoRoot, match)));
7751
7757
  return [...new Set(dirs)].toSorted();
7752
7758
  }
7753
7759
  function resolvePackageDirs(monorepoRoot, patterns) {
@@ -7776,7 +7782,7 @@ function buildManifestPattern(pattern) {
7776
7782
  // ../nmr-core/dist/esm/workspace/workspace.js
7777
7783
  var WORKSPACE_MANIFEST_FILENAME = "pnpm-workspace.yaml";
7778
7784
  function isMonorepoRoot(dir) {
7779
- return existsSync(path2.join(dir, WORKSPACE_MANIFEST_FILENAME));
7785
+ return existsSync(path3.join(dir, WORKSPACE_MANIFEST_FILENAME));
7780
7786
  }
7781
7787
  function resolveWorkspace(monorepoRoot) {
7782
7788
  const manifestReadResult = readWorkspaceManifest(monorepoRoot);
@@ -7821,7 +7827,7 @@ function readDeclaredPackages(parsedManifest) {
7821
7827
  return packages.length === 0 ? { kind: "absent" } : { kind: "patterns", patterns: packages };
7822
7828
  }
7823
7829
  function readWorkspaceManifest(monorepoRoot) {
7824
- const workspaceManifestPath = path2.join(monorepoRoot, WORKSPACE_MANIFEST_FILENAME);
7830
+ const workspaceManifestPath = path3.join(monorepoRoot, WORKSPACE_MANIFEST_FILENAME);
7825
7831
  if (!existsSync(workspaceManifestPath)) {
7826
7832
  return { kind: "absent" };
7827
7833
  }
@@ -7881,12 +7887,6 @@ function getWorkspacePackageDirs(monorepoRoot) {
7881
7887
  return resolution.kind === "packages" ? resolution.packageDirs : [];
7882
7888
  }
7883
7889
 
7884
- // src/workspace-globs.ts
7885
- import path3 from "node:path";
7886
- function toWorkspacePackageGlobs(monorepoRoot, packageDirs) {
7887
- return packageDirs.map((dir) => path3.relative(monorepoRoot, dir).split(path3.sep).join("/")).filter((relativeDir) => relativeDir !== "").map((relativeDir) => `${relativeDir}/**`).toSorted();
7888
- }
7889
-
7890
7890
  // src/resolve-scripts.ts
7891
7891
  function getDefaultWorkspaceScripts() {
7892
7892
  return { ...workspaceScripts };
@@ -8065,7 +8065,7 @@ function readCell(row, index) {
8065
8065
  return value;
8066
8066
  }
8067
8067
 
8068
- // src/helpers/hook-name.ts
8068
+ // src/helpers/isHookName.ts
8069
8069
  function isHookName(key) {
8070
8070
  return key.length > 4 && key.endsWith(":pre") || key.length > 5 && key.endsWith(":post");
8071
8071
  }
@@ -8161,11 +8161,11 @@ function formatMalformedScript(dir, key, value) {
8161
8161
  return `${rejection} A step list belongs in \`${CONFIG_RELATIVE_PATH}\` under \`${field}\`.`;
8162
8162
  }
8163
8163
 
8164
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8164
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.2/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8165
8165
  import path6 from "node:path";
8166
8166
  import process2 from "node:process";
8167
8167
 
8168
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/UsageError.js
8168
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.2/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/UsageError.js
8169
8169
  var UsageError = class extends Error {
8170
8170
  constructor(message, options) {
8171
8171
  super(message, options);
@@ -8173,7 +8173,7 @@ var UsageError = class extends Error {
8173
8173
  }
8174
8174
  };
8175
8175
 
8176
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/ParseError.js
8176
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.2/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/ParseError.js
8177
8177
  var ParseError = class extends UsageError {
8178
8178
  kind;
8179
8179
  token;
@@ -8185,12 +8185,12 @@ var ParseError = class extends UsageError {
8185
8185
  }
8186
8186
  };
8187
8187
 
8188
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/resolveLongName.js
8188
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.2/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/resolveLongName.js
8189
8189
  function resolveLongName(key, definition) {
8190
8190
  return definition.long ?? key.replaceAll(/[A-Z]/g, (capital) => `-${capital.toLowerCase()}`);
8191
8191
  }
8192
8192
 
8193
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/tokenizeArgs.js
8193
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.2/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/tokenizeArgs.js
8194
8194
  import { parseArgs as nodeParseArgs } from "node:util";
8195
8195
  function tokenizeArgs(argv, schema, extraBooleans = []) {
8196
8196
  const nodeOptions = {};
@@ -8220,7 +8220,7 @@ function findFlagEntry(flagsByName, token) {
8220
8220
  return entry.definition.short === token.rawName.slice(1) ? entry : void 0;
8221
8221
  }
8222
8222
 
8223
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/validateSpec.js
8223
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.2/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/validateSpec.js
8224
8224
  var CAMEL_CASE_KEY = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]+)*$/;
8225
8225
  function validateSpec(spec) {
8226
8226
  validateFlags(spec.flags ?? {});
@@ -8301,7 +8301,7 @@ function validateOperands(operands) {
8301
8301
  }
8302
8302
  }
8303
8303
 
8304
- // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.1/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8304
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.cli@0.8.2/node_modules/@williamthorsen/toolbelt.cli/dist/esm/3-candidate/parseArgs.js
8305
8305
  function parseArgs(argv, spec, options = {}) {
8306
8306
  validateSpec(spec);
8307
8307
  return parseValidatedArgs(argv, spec, options);
@@ -8672,18 +8672,16 @@ function buildRootRegistry(config, monorepoRoot) {
8672
8672
  };
8673
8673
  }
8674
8674
 
8675
- // src/check-cache/config-check-cache.ts
8676
- function assertResolvableCheckCacheCommands(config, configPath, baseDir) {
8677
- const { checkCache } = config;
8678
- if (checkCache === void 0 || !isMonorepoRoot(baseDir)) {
8675
+ // src/assertResolvableCommandLists.ts
8676
+ function assertResolvableCommandLists(config, configPath, baseDir) {
8677
+ const entries = listCommandEntries(config);
8678
+ if (entries.length === 0 || !isMonorepoRoot(baseDir)) {
8679
8679
  return;
8680
8680
  }
8681
- const fields = ["excludeCommands", "extraCommands"];
8682
- const entries = fields.flatMap((field) => (checkCache[field] ?? []).map((command) => ({ command, field })));
8683
- const hook = entries.find(({ command }) => isHookName(command));
8681
+ const hook = entries.find(({ command, fieldPath }) => fieldPath.startsWith("checkCache.") && isHookName(command));
8684
8682
  if (hook !== void 0) {
8685
8683
  throw new UserError(
8686
- `Invalid nmr config at ${configPath}: \`checkCache.${hook.field}\` names the hook \`${hook.command}\`, which is never gated on its own: It runs as part of the chain of the command that it wraps.`
8684
+ `Invalid nmr config at ${configPath}: \`${hook.fieldPath}\` names the hook \`${hook.command}\`, which is never gated on its own: It runs as part of the chain of the command that it wraps.`
8687
8685
  );
8688
8686
  }
8689
8687
  const registeredNames = /* @__PURE__ */ new Set([
@@ -8701,9 +8699,21 @@ function assertResolvableCheckCacheCommands(config, configPath, baseDir) {
8701
8699
  }
8702
8700
  const closestName = findClosestName(unresolvableEntry.command, [...registeredNames, ...declaredNames]);
8703
8701
  throw new UserError(
8704
- `Invalid nmr config at ${configPath}: \`checkCache.${unresolvableEntry.field}\` does not name any command: \`${unresolvableEntry.command}\`.${closestName === void 0 ? "" : ` Did you mean \`${closestName}\`?`}`
8702
+ `Invalid nmr config at ${configPath}: \`${unresolvableEntry.fieldPath}\` does not name any command: \`${unresolvableEntry.command}\`.${closestName === void 0 ? "" : ` Did you mean \`${closestName}\`?`}`
8705
8703
  );
8706
8704
  }
8705
+ function listCommandEntries(config) {
8706
+ const checkCacheFields = ["excludeCommands", "extraCommands"];
8707
+ return [
8708
+ ...checkCacheFields.flatMap(
8709
+ (field) => (config.checkCache?.[field] ?? []).map((command) => ({ command, fieldPath: `checkCache.${field}` }))
8710
+ ),
8711
+ ...(config.fanOut?.extraSortedCommands ?? []).map((command) => ({
8712
+ command,
8713
+ fieldPath: "fanOut.extraSortedCommands"
8714
+ }))
8715
+ ];
8716
+ }
8707
8717
  function readDeclaredScriptNames(monorepoRoot) {
8708
8718
  let packageDirs;
8709
8719
  try {
@@ -8746,7 +8756,7 @@ function isCommandVerbosity(value) {
8746
8756
  var CONFIG_TIERS = {
8747
8757
  root: {
8748
8758
  label: "a monorepo-root config",
8749
- honoredKeys: ["checkCache", "delegate", "output", "rootScripts", "selfHostedRunner", "workspaceScripts"],
8759
+ honoredKeys: ["checkCache", "delegate", "fanOut", "output", "rootScripts", "selfHostedRunner", "workspaceScripts"],
8750
8760
  elsewhere: "the package's own config"
8751
8761
  },
8752
8762
  workspace: {
@@ -8758,6 +8768,7 @@ var CONFIG_TIERS = {
8758
8768
  var RECOGNIZED_KEYS = [...CONFIG_TIERS.root.honoredKeys, ...CONFIG_TIERS.workspace.honoredKeys];
8759
8769
  var RECOGNIZED_BUILD_KEYS = ["extraIgnorePatterns"];
8760
8770
  var RECOGNIZED_CHECK_CACHE_KEYS = ["enabled", "excludeCommands", "extraCommands"];
8771
+ var RECOGNIZED_FAN_OUT_KEYS = ["extraSortedCommands"];
8761
8772
  var RECOGNIZED_OUTPUT_KEYS = ["commandVerbosity", "extraAgentEnvVars"];
8762
8773
  var RECOGNIZED_SELF_HOSTED_RUNNER_KEYS = ["exempt"];
8763
8774
  var RECOGNIZED_STEP_KEYS = ["run", "shouldDeclineArguments", "shouldRunInBackground"];
@@ -8782,6 +8793,8 @@ function validateConfig(value, configPath, baseDir) {
8782
8793
  const checkCache = validateCheckCacheField(value, configPath);
8783
8794
  if (checkCache) config.checkCache = checkCache;
8784
8795
  if (value["delegate"] !== void 0) config.delegate = validateDelegateField(value["delegate"], configPath);
8796
+ const fanOut = validateFanOutField(value, configPath);
8797
+ if (fanOut) config.fanOut = fanOut;
8785
8798
  const output = validateOutputField(value, configPath);
8786
8799
  if (output) config.output = output;
8787
8800
  const workspaceScripts2 = validateScriptField(value, "workspaceScripts", configPath);
@@ -8790,7 +8803,7 @@ function validateConfig(value, configPath, baseDir) {
8790
8803
  if (rootScripts) config.rootScripts = rootScripts;
8791
8804
  const selfHostedRunner = validateSelfHostedRunnerField(value, configPath);
8792
8805
  if (selfHostedRunner) config.selfHostedRunner = selfHostedRunner;
8793
- assertResolvableCheckCacheCommands(config, configPath, baseDir);
8806
+ assertResolvableCommandLists(config, configPath, baseDir);
8794
8807
  return config;
8795
8808
  }
8796
8809
  function assertNoRetiredKeys(value, retiredKeys, configPath, prefix) {
@@ -8893,6 +8906,24 @@ function validateDelegateField(delegate, configPath) {
8893
8906
  `Invalid nmr config at ${configPath}: \`delegate\` accepts only \`false\`, which keeps the repo's checks on the machine that has the tree. Hosts and \`when\` belong in the user config, \`~/.config/nmr/config.ts\`.`
8894
8907
  );
8895
8908
  }
8909
+ function validateFanOutField(value, configPath) {
8910
+ const fanOut = value["fanOut"];
8911
+ if (fanOut === void 0) {
8912
+ return void 0;
8913
+ }
8914
+ if (!isRecord2(fanOut)) {
8915
+ throw new UserError(`Invalid nmr config at ${configPath}: \`fanOut\` must be an object`);
8916
+ }
8917
+ assertRecognizedKeys(fanOut, RECOGNIZED_FAN_OUT_KEYS, configPath, "fanOut.");
8918
+ const extraSortedCommands = fanOut["extraSortedCommands"];
8919
+ if (extraSortedCommands === void 0) {
8920
+ return {};
8921
+ }
8922
+ if (!isStringArray(extraSortedCommands)) {
8923
+ throw new UserError(`Invalid nmr config at ${configPath}: \`fanOut.extraSortedCommands\` must be a string[]`);
8924
+ }
8925
+ return { extraSortedCommands };
8926
+ }
8896
8927
  function validateOutputField(value, configPath) {
8897
8928
  const output = value["output"];
8898
8929
  if (output === void 0) {
@@ -9068,7 +9099,7 @@ function formatSelfReferenceRemedy(options) {
9068
9099
  import { readdirSync as readdirSync2 } from "node:fs";
9069
9100
  import path7 from "node:path";
9070
9101
 
9071
- // src/git-ignored-paths.ts
9102
+ // src/listGitIgnoredPaths.ts
9072
9103
  function listGitIgnoredPaths(dir) {
9073
9104
  const result = runGit(
9074
9105
  ["ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--directory", "--no-empty-directory"],
@@ -9631,7 +9662,7 @@ function hasSupportedStrictLintVersion() {
9631
9662
  });
9632
9663
  }
9633
9664
  function getMinVersion() {
9634
- const pickedFields = { "version": "2.0.2" };
9665
+ const pickedFields = { "version": "2.1.0" };
9635
9666
  if (typeof pickedFields["version"] !== "string") {
9636
9667
  throw new TypeError("nmr/package.json: 'version' must be a string");
9637
9668
  }
@@ -3,25 +3,24 @@
3
3
  "kits": [
4
4
  {
5
5
  "bundledDependencies": {
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
- "@williamthorsen/toolbelt.sets": "3.1.0",
16
- "@williamthorsen/toolbelt.strings": "8.2.2",
17
- "@williamthorsen/toolbelt.terminal": "0.3.0",
6
+ "@williamthorsen/toolbelt.arrays": "6.5.0",
7
+ "@williamthorsen/toolbelt.cli": "0.8.2",
8
+ "@williamthorsen/toolbelt.datetime": "4.1.3",
9
+ "@williamthorsen/toolbelt.errors": "0.10.0",
10
+ "@williamthorsen/toolbelt.filesystem": "0.10.2",
11
+ "@williamthorsen/toolbelt.guards": "4.2.2",
12
+ "@williamthorsen/toolbelt.numbers": "8.0.0",
13
+ "@williamthorsen/toolbelt.objects": "4.4.2",
14
+ "@williamthorsen/toolbelt.packaging": "0.7.2",
15
+ "@williamthorsen/toolbelt.sets": "3.1.1",
16
+ "@williamthorsen/toolbelt.strings": "8.2.3",
17
+ "@williamthorsen/toolbelt.terminal": "0.3.1",
18
18
  "ansi-regex": "6.6.0",
19
- "ansi-styles": "6.2.3",
20
- "cli-truncate": "6.1.1",
19
+ "ansi-styles": "6.2.3, 7.0.0",
20
+ "cli-truncate": "6.2.0",
21
21
  "diff": "9.0.0",
22
22
  "get-east-asian-width": "1.7.0",
23
- "is-fullwidth-code-point": "5.1.0",
24
- "slice-ansi": "9.0.1",
23
+ "slice-ansi": "10.0.1",
25
24
  "string-width": "8.3.0",
26
25
  "strip-ansi": "7.2.0",
27
26
  "wrap-ansi": "10.0.2",
@@ -32,7 +31,7 @@
32
31
  "checklist": "nmr",
33
32
  "checks": [
34
33
  "@williamthorsen/nmr in devDependencies",
35
- "@williamthorsen/nmr >= 2.0.2",
34
+ "@williamthorsen/nmr >= 2.1.0",
36
35
  "pnpm-workspace.yaml exists",
37
36
  "catalogued dependencies use catalog:",
38
37
  "package.json has packageManager field",
@@ -236,7 +235,7 @@
236
235
  "path": "lib/workspace-build.ts"
237
236
  },
238
237
  {
239
- "hash": "cf90a835",
238
+ "hash": "5277f03b",
240
239
  "kind": "inline",
241
240
  "path": "../package.json",
242
241
  "paths": [
@@ -244,9 +243,9 @@
244
243
  ]
245
244
  },
246
245
  {
247
- "hash": "a34d9027",
246
+ "hash": "9219d11c",
248
247
  "kind": "module",
249
- "path": "../src/check-cache/config-check-cache.ts"
248
+ "path": "../src/assertResolvableCommandLists.ts"
250
249
  },
251
250
  {
252
251
  "hash": "eb0306ed",
@@ -254,12 +253,12 @@
254
253
  "path": "../src/commands/ensure-prepublish-hooks.ts"
255
254
  },
256
255
  {
257
- "hash": "0555552a",
256
+ "hash": "8c3aa404",
258
257
  "kind": "module",
259
258
  "path": "../src/config-validation.ts"
260
259
  },
261
260
  {
262
- "hash": "f5e9e2fd",
261
+ "hash": "6df8ab2e",
263
262
  "kind": "module",
264
263
  "path": "../src/config.ts"
265
264
  },
@@ -268,11 +267,6 @@
268
267
  "kind": "module",
269
268
  "path": "../src/default-scripts.ts"
270
269
  },
271
- {
272
- "hash": "b58621ab",
273
- "kind": "module",
274
- "path": "../src/git-ignored-paths.ts"
275
- },
276
270
  {
277
271
  "hash": "ae257d50",
278
272
  "kind": "module",
@@ -289,14 +283,14 @@
289
283
  "path": "../src/helpers/findClosestName.ts"
290
284
  },
291
285
  {
292
- "hash": "3efe4131",
286
+ "hash": "d6455b2e",
293
287
  "kind": "module",
294
- "path": "../src/helpers/hook-name.ts"
288
+ "path": "../src/helpers/importConfigDefault.ts"
295
289
  },
296
290
  {
297
- "hash": "d6455b2e",
291
+ "hash": "3efe4131",
298
292
  "kind": "module",
299
- "path": "../src/helpers/importConfigDefault.ts"
293
+ "path": "../src/helpers/isHookName.ts"
300
294
  },
301
295
  {
302
296
  "hash": "62caa37e",
@@ -313,13 +307,18 @@
313
307
  "kind": "module",
314
308
  "path": "../src/helpers/reportClosing.ts"
315
309
  },
310
+ {
311
+ "hash": "b58621ab",
312
+ "kind": "module",
313
+ "path": "../src/listGitIgnoredPaths.ts"
314
+ },
316
315
  {
317
316
  "hash": "77f6bb50",
318
317
  "kind": "module",
319
318
  "path": "../src/nmr-flags.ts"
320
319
  },
321
320
  {
322
- "hash": "78ca6bd4",
321
+ "hash": "7e6066dd",
323
322
  "kind": "module",
324
323
  "path": "../src/resolve-scripts.ts"
325
324
  },
@@ -339,19 +338,19 @@
339
338
  "path": "../src/steps.ts"
340
339
  },
341
340
  {
342
- "hash": "d8368f1a",
341
+ "hash": "b82cfdf6",
343
342
  "kind": "module",
344
343
  "path": "../src/tiers.ts"
345
344
  },
346
345
  {
347
- "hash": "ecf8a873",
346
+ "hash": "f08b6ff3",
348
347
  "kind": "module",
349
- "path": "../src/verbosity.ts"
348
+ "path": "../src/toWorkspacePackageGlobs.ts"
350
349
  },
351
350
  {
352
- "hash": "f08b6ff3",
351
+ "hash": "cc34f13c",
353
352
  "kind": "module",
354
- "path": "../src/workspace-globs.ts"
353
+ "path": "../src/verbosity.ts"
355
354
  },
356
355
  {
357
356
  "hash": "770e4f3a",
@@ -361,10 +360,10 @@
361
360
  ],
362
361
  "name": "default",
363
362
  "path": "kits/default.js",
364
- "readyupVersion": "0.45.1",
363
+ "readyupVersion": "0.46.1",
365
364
  "source": "kits/default.ts",
366
365
  "sourceHash": "9fd61bf4",
367
- "targetHash": "9895b0df"
366
+ "targetHash": "70e833f9"
368
367
  }
369
368
  ]
370
369
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 2.1.0 — 2026-11-12
6
+
7
+ ### 🎉 Features
8
+
9
+ - Adds the `fanOut.extraSortedCommands` root-config key, which lists commands besides `build` whose fan-out keeps the topological order, and nmr rejects a name in it that does not resolve to any command when it loads the config. (#1306)
10
+
11
+ ### ⚡ Performance
12
+
13
+ - Drops pnpm's topological sort, which runs each package only after its workspace dependencies finish, from every `-R` and `-F` fan-out except one of `build`, so that `nmr test`, the `test:*` commands, and `nmr typecheck` start each package without waiting for its workspace dependencies. (#1306)
14
+
15
+ ### ♻️ Refactoring
16
+
17
+ - Renames each single-export module in `packages/nmr/src` after its main export, such as `bin-path.ts` to `prependBinDirs.ts`, and renames the test files that matched the old names. (#1303)
18
+ - Stops exporting seven values from `nmr`'s source modules, among them `REPORT_FORMATS`, `COMMAND_VERBOSITIES`, and the machine lock's `POLL_INTERVAL_MS`, because only the module that defines each one reads it. (#1305)
19
+
20
+ ### 🧪 Tests
21
+
22
+ - Replaces the hand-copied git setup in the nmr tool tests and fixtures, and the local `initRepo` that shadowed the shared helper in `check-cache.tool.test.ts`, with calls to `test-utils/initRepo.ts`. (#1304)
23
+
24
+ ## 2.0.3 — 2026-11-12
25
+
26
+ ### ⚙️ Tooling
27
+
28
+ - Drops `--conditions nmr-source` from the `prepare` scripts of `build-info`, `build-info-react`, and `change-grammar`, which pnpm now runs after nmr-core is built because each package depends on nmr-core through nmr. (#1302)
29
+
5
30
  ## 2.0.2 — 2026-11-12
6
31
 
7
32
  ### 🐛 Bug fixes
package/README.md CHANGED
@@ -5,17 +5,15 @@
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
7
  <!-- section:release-notes -->
8
- ## Release notes — v2.0.2 (2026-11-12)
8
+ ## Release notes — v2.1.0 (2026-11-12)
9
9
 
10
- ### 🐛 Bug fixes
10
+ ### 🎉 Features
11
11
 
12
- - Fixes the issue that `nmr-fmt` and `nmr-taze` exited with 1 when a signal ended Prettier or taze, and makes each exit with 128 plus the signal number, as the other commands that nmr runs already did. (#1281)
13
- - Fixes the issue that `nmr-fmt` formatted with nmr's own copy of Prettier when the repository's copy had a malformed or unreadable manifest, and makes `nmr-fmt` fail with an error that names that manifest instead. (#1282)
14
- - Fixes the issue that `nmr-vitest` reported a Vitest whose manifest is malformed or unreadable as not installed, and makes `nmr-vitest` name that manifest and the repair instead. (#1282)
15
- - Fixes the issue that `nmr-fmt` and `nmr-vitest` crashed on a Prettier or Vitest manifest that starts with a byte-order mark, which Node accepts. (#1282)
16
- - Fixes the issue that a `.config/nmr.config.ts` or `~/.config/nmr/config.ts` that failed to parse or threw while loading crashed `nmr` with an internal error that did not name the file, and makes `nmr` report an error that names the config and, for a syntax error, the file and line that Node reports, including those of a module that the config imports. (#1296)
17
- - Fixes the issue that `nmr` read any value of `NMR_DEBUG`, `NMR_NO_CACHE`, `NMR_NO_LOCK`, or `NMR_RUN_IF_PRESENT` other than `1`, such as `true`, as off without reporting it, by making `nmr` and `nmr-fmt` exit 2 with a message that names the variable and the values that it accepts. (#1297)
18
- - Fixes the issue that `nmr upgrade --write` rewrote the `nodejs` pin in `.tool-versions` to a release that asdf could not install yet, by limiting the check to releases that node-build defines and reporting a newer release without a definition as held back. (#1300)
12
+ - Adds the `fanOut.extraSortedCommands` root-config key, which lists commands besides `build` whose fan-out keeps the topological order, and nmr rejects a name in it that does not resolve to any command when it loads the config. (#1306)
13
+
14
+ ### ⚡ Performance
15
+
16
+ - Drops pnpm's topological sort, which runs each package only after its workspace dependencies finish, from every `-R` and `-F` fan-out except one of `build`, so that `nmr test`, the `test:*` commands, and `nmr typecheck` start each package without waiting for its workspace dependencies. (#1306)
19
17
  <!-- /section:release-notes -->
20
18
 
21
19
  ## Installation
@@ -0,0 +1,18 @@
1
+ import type { NmrConfig } from './types.js';
2
+ /**
3
+ * Rejects a name in a config list of command names (`checkCache.excludeCommands`, `checkCache.extraCommands`,
4
+ * `fanOut.extraSortedCommands`) that does not resolve to any command. Each list is read by name alone, so a
5
+ * misspelt entry is inert: The command that it meant to name goes on running as though the list did not mention
6
+ * it.
7
+ *
8
+ * Because the test is resolvability rather than membership of the set that a list adjusts, an entry written
9
+ * against a name that a later release moves into or out of the defaults remains valid.
10
+ *
11
+ * Reads nothing while every name resolves in the merged registries, and reads a package's own `scripts` only
12
+ * once a name is missing from them: Those are a resolution tier of their own; a name declared only there is
13
+ * valid.
14
+ *
15
+ * Does nothing outside a monorepo root, where these keys are ones that the tier does not honor. `assertTierKeys`
16
+ * reports that, and of the two messages it is the one that names the real mistake.
17
+ */
18
+ export declare function assertResolvableCommandLists(config: NmrConfig, configPath: string, baseDir: string): void;