@williamthorsen/nmr 0.38.0 → 0.40.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 (112) hide show
  1. package/.meta/changelog.json +212 -4
  2. package/.readyup/kits/default.js +124 -104
  3. package/.readyup/manifest.json +12 -7
  4. package/CHANGELOG.md +108 -0
  5. package/README.md +41 -47
  6. package/agents/README.md +8 -8
  7. package/agents/guidance/rulebooks/nmr.md +8 -8
  8. package/bin/ensure-prepublish-hooks.js +1 -1
  9. package/bin/nmr-clean.js +1 -1
  10. package/bin/nmr-compile.js +1 -1
  11. package/bin/nmr-fmt.js +1 -1
  12. package/bin/nmr-report-catalog.js +1 -1
  13. package/bin/nmr-report-overrides.js +1 -1
  14. package/bin/nmr-taze.js +1 -1
  15. package/bin/nmr.js +1 -1
  16. package/dist/esm/UserError.d.ts +1 -2
  17. package/dist/esm/check-cache.d.ts +61 -63
  18. package/dist/esm/check-cache.js +24 -24
  19. package/dist/esm/cli-ensure-prepublish-hooks.js +4 -4
  20. package/dist/esm/commands/build-output.d.ts +37 -38
  21. package/dist/esm/commands/build-output.js +9 -9
  22. package/dist/esm/commands/build.d.ts +13 -14
  23. package/dist/esm/commands/build.js +67 -65
  24. package/dist/esm/commands/clean.d.ts +6 -6
  25. package/dist/esm/commands/clean.js +11 -11
  26. package/dist/esm/commands/ensure-prepublish-hooks.d.ts +7 -7
  27. package/dist/esm/commands/ensure-prepublish-hooks.js +41 -41
  28. package/dist/esm/commands/fmt.d.ts +8 -8
  29. package/dist/esm/commands/fmt.js +33 -33
  30. package/dist/esm/commands/report-catalog.d.ts +5 -7
  31. package/dist/esm/commands/report-catalog.js +10 -8
  32. package/dist/esm/commands/report-overrides.d.ts +0 -2
  33. package/dist/esm/commands/report-overrides.js +8 -8
  34. package/dist/esm/commands/taze.d.ts +9 -8
  35. package/dist/esm/config.d.ts +5 -5
  36. package/dist/esm/config.js +27 -27
  37. package/dist/esm/context.d.ts +1 -2
  38. package/dist/esm/context.js +8 -8
  39. package/dist/esm/default-scripts.d.ts +5 -6
  40. package/dist/esm/defineConfig.d.ts +1 -1
  41. package/dist/esm/git-ignored-paths.d.ts +9 -0
  42. package/dist/esm/git-ignored-paths.js +10 -0
  43. package/dist/esm/glyphs.d.ts +3 -3
  44. package/dist/esm/help.d.ts +4 -4
  45. package/dist/esm/help.js +21 -21
  46. package/dist/esm/helpers/clampToBytes.d.ts +4 -5
  47. package/dist/esm/helpers/clampToBytes.js +3 -3
  48. package/dist/esm/helpers/config-path.d.ts +0 -2
  49. package/dist/esm/helpers/createBoundedBuffer.d.ts +3 -3
  50. package/dist/esm/helpers/createBoundedBuffer.js +2 -2
  51. package/dist/esm/helpers/deriveExcerpt.d.ts +4 -5
  52. package/dist/esm/helpers/duration.d.ts +10 -11
  53. package/dist/esm/helpers/duration.js +7 -7
  54. package/dist/esm/helpers/filter-selection.d.ts +19 -19
  55. package/dist/esm/helpers/filter-selection.js +12 -12
  56. package/dist/esm/helpers/findClosestName.d.ts +1 -1
  57. package/dist/esm/helpers/findClosestName.js +15 -15
  58. package/dist/esm/helpers/hook-name.d.ts +2 -5
  59. package/dist/esm/helpers/package-json.d.ts +11 -10
  60. package/dist/esm/helpers/package-json.js +26 -25
  61. package/dist/esm/helpers/readStringValues.d.ts +2 -3
  62. package/dist/esm/helpers/reportClosing.d.ts +8 -9
  63. package/dist/esm/helpers/transcript.d.ts +15 -15
  64. package/dist/esm/helpers/transcript.js +14 -14
  65. package/dist/esm/helpers/type-guards.d.ts +1 -0
  66. package/dist/esm/output-style.d.ts +12 -13
  67. package/dist/esm/prettier.d.ts +2 -2
  68. package/dist/esm/prettier.js +5 -5
  69. package/dist/esm/recording.d.ts +25 -20
  70. package/dist/esm/recording.js +28 -21
  71. package/dist/esm/replay-assembly.d.ts +10 -10
  72. package/dist/esm/report-format.d.ts +15 -20
  73. package/dist/esm/report-format.js +7 -7
  74. package/dist/esm/reportCliFailure.d.ts +2 -3
  75. package/dist/esm/resolve-scripts.d.ts +4 -5
  76. package/dist/esm/resolveBinStyles.d.ts +4 -5
  77. package/dist/esm/resolveBinStyles.js +3 -3
  78. package/dist/esm/resolver.d.ts +16 -20
  79. package/dist/esm/resolver.js +15 -15
  80. package/dist/esm/runCli.d.ts +6 -6
  81. package/dist/esm/runCli.js +261 -195
  82. package/dist/esm/runner.d.ts +20 -20
  83. package/dist/esm/runner.js +18 -18
  84. package/dist/esm/steps.d.ts +51 -38
  85. package/dist/esm/steps.js +32 -27
  86. package/dist/esm/taze.d.ts +1 -1
  87. package/dist/esm/tests.d.ts +23 -18
  88. package/dist/esm/tests.js +21 -9
  89. package/dist/esm/tiers.d.ts +28 -24
  90. package/dist/esm/tiers.js +13 -9
  91. package/dist/esm/types.d.ts +5 -5
  92. package/dist/esm/verbosity.d.ts +23 -21
  93. package/dist/esm/verbosity.js +7 -7
  94. package/dist/esm/verdict.d.ts +39 -32
  95. package/dist/esm/verdict.js +63 -50
  96. package/dist/esm/vitest-source-resolution.d.ts +11 -11
  97. package/dist/esm/vitest-source-resolution.js +49 -47
  98. package/dist/esm/vitest.d.ts +27 -26
  99. package/dist/esm/vitest.js +90 -33
  100. package/dist/esm/workspace.d.ts +6 -22
  101. package/dist/esm/workspace.js +9 -67
  102. package/docs/check-cache.md +57 -57
  103. package/docs/prettier.md +10 -10
  104. package/docs/reporting.md +48 -44
  105. package/docs/scripts.md +29 -29
  106. package/docs/upgrades.md +18 -18
  107. package/docs/utilities.md +13 -13
  108. package/docs/vitest.md +68 -66
  109. package/docs/workspace.md +27 -4
  110. package/package.json +6 -6
  111. package/dist/esm/helpers/workspace-patterns.d.ts +0 -8
  112. package/dist/esm/helpers/workspace-patterns.js +0 -29
@@ -64,7 +64,7 @@ var workspaceScripts = {
64
64
  "test:unit": "pnpm exec vitest --project unit",
65
65
  "test:watch": `pnpm exec vitest ${GATE_PROJECTS} --watch`,
66
66
  typecheck: "tsgo --noEmit",
67
- // The command is a string because neither half names an nmr command: both are binaries.
67
+ // The command is a string because neither half names an nmr command: Both are binaries.
68
68
  upgrade: "nmr-report-catalog && nmr-taze",
69
69
  "view-coverage": "open coverage/index.html"
70
70
  };
@@ -75,8 +75,8 @@ var rootScripts = {
75
75
  build: ["-R build"],
76
76
  check: [TYPECHECK_STEP, "fmt:check", "lint:check", "test"],
77
77
  "check:strict": [TYPECHECK_STEP, "fmt:check", "lint:strict", "test"],
78
- // Excludes the audit, which in CI has a workflow of its own. The build is what the narrowed check runs
79
- // against, so it declines the arguments rather than being narrowed by them.
78
+ // Excludes the audit, which in CI has a workflow of its own. The narrowed check runs against the build, so
79
+ // the build declines the arguments rather than being narrowed by them.
80
80
  ci: [{ run: "build", shouldDeclineArguments: true }, "check:strict"],
81
81
  clean: "nmr-clean",
82
82
  fix: ["lint", "fmt"],
@@ -86,8 +86,8 @@ var rootScripts = {
86
86
  lint: "eslint --fix .",
87
87
  "lint:check": "eslint .",
88
88
  "lint:strict": "strict-lint",
89
- // The audit costs seconds and `ci` costs minutes, so the cheap gate fails first. The audit reads the
90
- // dependency tree, which no argument narrowing the code under test says anything about.
89
+ // The audit takes seconds and `ci` takes minutes, so the cheap gate fails first. The audit reads the
90
+ // dependency tree, and an argument narrowing the code under test does not say anything about that tree.
91
91
  prepush: [{ run: "audit", shouldDeclineArguments: true }, "ci"],
92
92
  "report-overrides": "nmr-report-overrides",
93
93
  "root:check": [ROOT_TYPECHECK_STEP, "fmt:check", "root:lint:check", "root:test"],
@@ -99,7 +99,7 @@ var rootScripts = {
99
99
  "root:test:tool": "vitest --config ./vitest.root.config.ts --project tool",
100
100
  "root:test:unit": "vitest --config ./vitest.root.config.ts --project unit",
101
101
  "root:typecheck": "tsgo --noEmit",
102
- // Carries the override report for the same reason `upgrade` does: both end in the tool that rewrites a
102
+ // Includes the override report for the same reason `upgrade` does: Both end in the tool that rewrites a
103
103
  // `pnpm.overrides` block, so both need the reporter's rejection ahead of them.
104
104
  "root:upgrade": "nmr-report-overrides && nmr-taze",
105
105
  test: ["root:test", "-R test"],
@@ -109,9 +109,9 @@ var rootScripts = {
109
109
  "test:unit": ["root:test:unit", "-R test:unit"],
110
110
  "test:watch": `vitest ${GATE_PROJECTS} --watch`,
111
111
  // Neither step is narrowable, so `nmr typecheck <file>` is rejected rather than checking that file under
112
- // default options at the root and hunting for it in every package.
112
+ // default options at the root and searching for it in every package.
113
113
  typecheck: [ROOT_TYPECHECK_STEP, { run: "-R typecheck", shouldDeclineArguments: true }],
114
- // The command is a string because neither half names an nmr command: both are binaries, and a composite
114
+ // The command is a string because neither half names an nmr command: Both are binaries, and a composite
115
115
  // element can name only a command.
116
116
  upgrade: "nmr-report-overrides && nmr-taze --recursive"
117
117
  };
@@ -124,6 +124,20 @@ function getDefaultRootScripts() {
124
124
  // src/tiers.ts
125
125
  import { readdirSync } from "node:fs";
126
126
  import path from "node:path";
127
+
128
+ // src/git-ignored-paths.ts
129
+ import { spawnSync } from "node:child_process";
130
+ function listGitIgnoredPaths(dir) {
131
+ const result = spawnSync(
132
+ "git",
133
+ ["ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--directory", "--no-empty-directory"],
134
+ { cwd: dir, encoding: "utf8" }
135
+ );
136
+ if (result.error !== void 0 || result.status !== 0) return [];
137
+ return result.stdout.split("\0").filter((entry) => entry !== "").toSorted();
138
+ }
139
+
140
+ // src/tiers.ts
127
141
  var TEST_DIR = "__tests__";
128
142
  var TEST_EXTENSIONS = "{ts,tsx}";
129
143
  var TEST_GLOB_PREFIX = `**/${TEST_DIR}/**`;
@@ -144,26 +158,27 @@ function hasTierInfix(filePath) {
144
158
  }
145
159
  var TEST_COLLECTION_EXCLUDE = [".git", "coverage", "dist", "node_modules"];
146
160
  var TIER_NAMES = ["unit", "tool", "localhost", "remote"];
147
- function collectTestFiles(dir, relativeDir, inTestDir, context) {
161
+ function collectTestFiles(dir, relativeDir, isInTestDir, context) {
148
162
  const entries = readdirSync(dir, { withFileTypes: true });
149
163
  for (const entry of entries) {
150
164
  const relativePath = relativeDir === "" ? entry.name : `${relativeDir}/${entry.name}`;
151
165
  if (entry.isDirectory()) {
152
- if (context.pruned.has(entry.name)) continue;
153
- collectTestFiles(path.join(dir, entry.name), relativePath, inTestDir || entry.name === TEST_DIR, context);
154
- } else if (inTestDir !== context.misplaced && TEST_FILE_PATTERN.test(entry.name)) {
155
- context.found.push(relativePath);
166
+ if (context.pruned.has(entry.name) || context.ignoredPaths.has(`${relativePath}/`)) continue;
167
+ collectTestFiles(path.join(dir, entry.name), relativePath, isInTestDir || entry.name === TEST_DIR, context);
168
+ } else if (isInTestDir !== context.misplaced && TEST_FILE_PATTERN.test(entry.name) && !context.ignoredPaths.has(relativePath)) {
169
+ context.foundPaths.push(relativePath);
156
170
  }
157
171
  }
158
172
  }
159
- function walkTestFiles(rootDir, { exclude = [] }, misplaced) {
173
+ function walkTestFiles(rootDir, { excludedBasenames = [] }, misplaced) {
160
174
  const context = {
161
- found: [],
175
+ foundPaths: [],
176
+ ignoredPaths: new Set(listGitIgnoredPaths(rootDir)),
162
177
  misplaced,
163
- pruned: /* @__PURE__ */ new Set([...TEST_COLLECTION_EXCLUDE, ...exclude])
178
+ pruned: /* @__PURE__ */ new Set([...TEST_COLLECTION_EXCLUDE, ...excludedBasenames])
164
179
  };
165
180
  collectTestFiles(rootDir, "", false, context);
166
- return context.found.toSorted();
181
+ return context.foundPaths.toSorted();
167
182
  }
168
183
 
169
184
  // .readyup/kits/default.ts
@@ -210,21 +225,21 @@ var default_default = defineRdyKit({
210
225
  severity: "warn",
211
226
  quiet: true,
212
227
  check: toolVersionsHasNoPnpm,
213
- fix: "Remove pnpm from .tool-versions \u2014 manage via packageManager field and corepack"
228
+ fix: "Remove pnpm from .tool-versions, and manage it through the packageManager field"
214
229
  },
215
230
  {
216
231
  name: "no package.json declares a pnpm field",
217
232
  severity: "error",
218
233
  quiet: true,
219
234
  check: () => noPnpmFieldInPackageJson(),
220
- fix: "Move these settings into pnpm-workspace.yaml, quoting each version under `overrides`, or run `pnpx codemod run pnpm-v10-to-v11`. pnpm 11 reads no key from the `pnpm` field, so an override left there pins nothing while an upgrade run with `--write` goes on rewriting it"
235
+ fix: "Move these settings into pnpm-workspace.yaml, quoting each version under `overrides`, or run `pnpx codemod run pnpm-v10-to-v11`. pnpm 11 does not read any key from the `pnpm` field, so an override left there pins nothing while an upgrade run with `--write` goes on rewriting it"
221
236
  },
222
237
  {
223
238
  name: ".config/nmr.config.ts uses defineConfig",
224
239
  severity: "recommend",
225
240
  skip: () => !fileExists(".config/nmr.config.ts") ? "no nmr config file" : false,
226
241
  check: () => fileContains(".config/nmr.config.ts", /defineConfig/),
227
- fix: "Wrap your config export with defineConfig() from @williamthorsen/nmr/config for type safety"
242
+ fix: "Wrap the config export with defineConfig() from @williamthorsen/nmr/config for type safety"
228
243
  },
229
244
  // `error` because falling short produces wrong results rather than a failure. Names and fixes are
230
245
  // getters because their version constants are declared below the kit.
@@ -236,7 +251,7 @@ var default_default = defineRdyKit({
236
251
  skip: () => !hasDevDependency("eslint") ? "eslint not installed" : false,
237
252
  check: hasSupportedEslintVersion,
238
253
  get fix() {
239
- return `pnpm add --save-dev eslint@^${MIN_ESLINT_VERSION} \u2014 earlier releases resolve config from the working directory, so nmr's root lint and lint:check would apply the root config to every package`;
254
+ return `pnpm add --save-dev eslint@^${MIN_ESLINT_VERSION}. Earlier releases resolve config from the working directory, so nmr's root lint and lint:check would apply the root config to every package`;
240
255
  }
241
256
  },
242
257
  {
@@ -247,7 +262,7 @@ var default_default = defineRdyKit({
247
262
  skip: () => !hasDevDependency("@williamthorsen/strict-lint") ? "strict-lint not installed" : false,
248
263
  check: hasSupportedStrictLintVersion,
249
264
  get fix() {
250
- return `pnpm add --save-dev @williamthorsen/strict-lint@^${MIN_STRICT_LINT_VERSION} \u2014 earlier releases pin ESLint to one config and resolve ceilings from the working directory, so nmr's root lint:strict would report the wrong rules for every package`;
265
+ return `pnpm add --save-dev @williamthorsen/strict-lint@^${MIN_STRICT_LINT_VERSION}. Earlier releases pin ESLint to one config and resolve ceilings from the working directory, so nmr's root lint:strict would report the wrong rules for every package`;
251
266
  }
252
267
  },
253
268
  // -- Root script cleanup -------------------------------------------------
@@ -256,7 +271,7 @@ var default_default = defineRdyKit({
256
271
  severity: "warn",
257
272
  quiet: true,
258
273
  check: noRedundantRootScripts,
259
- fix: "Remove scripts from root package.json that nmr provides as built-in root scripts \u2014 invoke via nmr directly"
274
+ fix: "Remove scripts from root package.json that nmr provides as built-in root scripts, and invoke them through nmr directly"
260
275
  },
261
276
  // -- Git hooks -----------------------------------------------------------
262
277
  {
@@ -278,13 +293,13 @@ var default_default = defineRdyKit({
278
293
  name: "every bin target is a committed wrapper",
279
294
  severity: "error",
280
295
  check: () => everyBinTargetIsACommittedWrapper(),
281
- fix: `Point each listed entry at a committed wrapper under bin/ that loads the build output at runtime. pnpm links a workspace package's bins during the install's link phase, which runs before anything is built, so a target that is not committed does not exist when pnpm reaches for it \u2014 and pnpm never retries, leaving the link missing for the life of the node_modules tree`
296
+ fix: `Point each listed entry at a committed wrapper under bin/ that loads the build output at runtime. pnpm links a workspace package's bins during the install's link phase, which runs before anything is built, so a target that is not committed does not exist when pnpm tries to link it. pnpm never retries, which leaves the link missing for the life of the node_modules tree`
282
297
  },
283
298
  {
284
299
  name: "every bin wrapper's build-output target is covered by files",
285
300
  severity: "warn",
286
301
  check: () => everyBinWrapperTargetIsCoveredByFiles(),
287
- fix: "Add the build output directory to `files` in each listed package. npm and pnpm publish the bin target itself whatever `files` says, so the wrapper ships pointing at build output missing from the tarball"
302
+ fix: "Add the build output directory to `files` in each listed package. npm and pnpm publish the bin target itself whatever `files` says, so the wrapper is published pointing at build output missing from the tarball"
288
303
  },
289
304
  // -- Vitest projects -----------------------------------------------------
290
305
  {
@@ -298,7 +313,7 @@ var default_default = defineRdyKit({
298
313
  name: "every vitest.config builds on @williamthorsen/nmr/vitest",
299
314
  severity: "error",
300
315
  check: () => vitestConfigBuildsOnSharedConfig(),
301
- fix: "Replace each listed config with: import { defineVitestConfig } from '@williamthorsen/nmr/vitest'; export default defineVitestConfig(); -- a config that does not call the factory declares no projects, so every tier-selecting test command fails against it. Pass your own settings to the factory as layers to keep them"
316
+ fix: "Replace each listed config with: import { defineVitestConfig } from '@williamthorsen/nmr/vitest'; export default defineVitestConfig(); -- a config that does not call the factory does not declare any projects, so every tier-selecting test command fails against it. Pass the repo's own settings to the factory as layers to keep them"
302
317
  },
303
318
  {
304
319
  name: "vitest.root.config.ts builds on @williamthorsen/nmr/vitest",
@@ -310,27 +325,27 @@ var default_default = defineRdyKit({
310
325
  name: "every workspace with a Vite config has a Vitest config",
311
326
  severity: "error",
312
327
  check: () => everyViteConfigHasVitestConfig(),
313
- fix: "Add a vitest.config.ts calling defineVitestConfig() from @williamthorsen/nmr/vitest beside each listed vite.config -- Vitest stops its config search at the first directory holding either name, so the Vite config otherwise wins and the projects model is never reached"
328
+ fix: "Add a vitest.config.ts calling defineVitestConfig() from @williamthorsen/nmr/vitest beside each listed vite.config -- Vitest stops its config search at the first directory containing either name, so the Vite config otherwise wins and the projects model is never reached"
314
329
  },
315
330
  {
316
331
  name: "the test suite gates the test-file conventions",
317
332
  severity: "warn",
318
333
  check: () => testSuiteGatesTestFileConventions(),
319
- fix: "Add a test of the repo's own under a __tests__ directory that declares the check: import { checkTestFileConventions } from '@williamthorsen/nmr/tests'; checkTestFileConventions(); -- passing `exclude` the directory names that the repo passes to `testCollectionExclude`. Without it no test run reports an untiered or misplaced test file, and this kit reports them in its place against nmr's built-in exclusions alone"
334
+ fix: "Add a test of the repo's own under a __tests__ directory that declares the check: import { checkTestFileConventions } from '@williamthorsen/nmr/tests'; checkTestFileConventions(); -- passing `excludedBasenames` the directory names that the repo passes to `testCollectionExclude`. Without it, the repo's test run does not report an untiered or misplaced test file, and this kit reports them in its place against only nmr's built-in exclusions and the paths that git ignores"
320
335
  },
321
336
  {
322
337
  name: "every test file names its isolation tier",
323
338
  severity: "error",
324
339
  skip: () => describeConventionsGuardSkip(),
325
340
  check: () => everyTestFileNamesItsTier(),
326
- fix: `Rename each to <subject>[.<aspect>].<tier>.test.ts, naming one of ${TIER_NAMES.join(", ")}. Use tool for a test that reaches a program the environment supplies, which is where a retired .int. or .integration. file belongs. Only the segment before .test. selects a project, so an untiered file runs under the residual unit project and reports success`
341
+ fix: `Rename each to <subject>[.<aspect>].<tier>.test.ts, naming one of ${TIER_NAMES.join(", ")}. Use tool for a test that reaches a program supplied by the environment, which is the tier for a retired .int. or .integration. file. Only the segment before .test. selects a project, so an untiered file runs under the residual unit project and reports success`
327
342
  },
328
343
  {
329
- name: "every test file sits under a __tests__ directory",
344
+ name: "every test file is under a __tests__ directory",
330
345
  severity: "error",
331
346
  skip: () => describeConventionsGuardSkip(),
332
347
  check: () => everyTestFileSitsUnderTestsDir(),
333
- fix: "Move each into a __tests__ directory, the only place from which the shared Vitest config collects. No project collects a file outside one, so it runs nowhere and reports nothing. For a file that is not a test, declare checkTestFileConventions with its directory in `exclude` instead, which this check then defers to"
348
+ fix: "Move each into a __tests__ directory, the only place from which the shared Vitest config collects. Vitest does not collect a file outside one, so it runs nowhere and reports nothing. For a file that is not a test, declare checkTestFileConventions with its directory in `excludedBasenames` instead, which this check then defers to"
334
349
  },
335
350
  {
336
351
  name: "no package re-exports the ancestor Vitest config",
@@ -351,14 +366,14 @@ var default_default = defineRdyKit({
351
366
  name: "taze.config.ts builds on @williamthorsen/nmr/taze",
352
367
  severity: "warn",
353
368
  check: () => tazeConfigBuildsOnSharedConfig(),
354
- fix: "Replace the taze config with: import { defineConfig } from '@williamthorsen/nmr/taze'; export default defineConfig(); \u2014 nmr's upgrade policy reaches a repo only through this file, so without it `nmr upgrade` reports nothing where dependencies are pinned to exact versions"
369
+ fix: "Replace the taze config with: import { defineConfig } from '@williamthorsen/nmr/taze'; export default defineConfig(); -- nmr's upgrade policy applies to a repo only through this file, so without it `nmr upgrade` does not report a dependency pinned to an exact version"
355
370
  },
356
371
  {
357
372
  name: "taze config declares no option taze discards",
358
373
  severity: "warn",
359
374
  quiet: true,
360
375
  check: () => tazeConfigAvoidsClobberedOptions(),
361
- fix: "Set these through the upgrade script instead, as rootScripts: { upgrade: 'nmr-report-overrides && nmr-taze --recursive --request-timeout 90000' } in .config/nmr.config.ts, keeping the rest of the default script \u2014 taze's CLI writes a default for each of them over whatever the config file declares, so the value there never reaches taze (antfu-collective/taze#317). nmr already forwards a 30-second request timeout"
376
+ fix: "Set these through the upgrade script instead, as rootScripts: { upgrade: 'nmr-report-overrides && nmr-taze --recursive --request-timeout 90000' } in .config/nmr.config.ts, keeping the rest of the default script. taze's CLI writes a default for each of them over whatever the config file declares, so taze never uses the file's value (antfu-collective/taze#317). nmr already forwards a 30-second request timeout"
362
377
  },
363
378
  // -- Audit dependency --------------------------------------------------------
364
379
  {
@@ -373,7 +388,7 @@ var default_default = defineRdyKit({
373
388
  severity: "error",
374
389
  quiet: true,
375
390
  check: () => !fileExists("scripts/run-workspace-script.ts"),
376
- fix: "Delete scripts/run-workspace-script.ts \u2014 nmr replaces this custom script runner"
391
+ fix: "Delete scripts/run-workspace-script.ts, because nmr replaces this custom script runner"
377
392
  },
378
393
  {
379
394
  name: 'no workspace packages reference run-workspace-script or "pnpm run ws"',
@@ -424,7 +439,7 @@ function allWorkspacePackagesCanBuild() {
424
439
  const packagesDir = join(process.cwd(), "packages");
425
440
  if (!existsSync(packagesDir)) return true;
426
441
  const entries = readdirSync2(packagesDir, { withFileTypes: true });
427
- const failing = [];
442
+ const failingPackages = [];
428
443
  for (const entry of entries) {
429
444
  if (!entry.isDirectory()) continue;
430
445
  const pkgPath = `packages/${entry.name}/package.json`;
@@ -433,30 +448,33 @@ function allWorkspacePackagesCanBuild() {
433
448
  const hasBuildOverride = /"build"\s*:/.test(content);
434
449
  const hasDefaultBuildInputs = fileExists(`packages/${entry.name}/tsconfig.json`) && existsSync(join(packagesDir, entry.name, "src"));
435
450
  if (!hasBuildOverride && !hasDefaultBuildInputs) {
436
- failing.push(entry.name);
451
+ failingPackages.push(entry.name);
437
452
  }
438
453
  }
439
- if (failing.length === 0) return true;
454
+ if (failingPackages.length === 0) return true;
440
455
  return {
441
456
  ok: false,
442
- detail: `missing build override or tsconfig.json + src/: ${failing.join(", ")}`
457
+ detail: `missing build override or tsconfig.json + src/: ${failingPackages.join(", ")}`
443
458
  };
444
459
  }
445
460
  function checkNoMatchingFiles(patterns, cwd) {
446
- const found = findFiles(patterns, cwd);
447
- if (found.length === 0) return true;
448
- return { ok: false, detail: formatPaths(found) };
461
+ const foundFiles = findFiles(patterns, cwd);
462
+ if (foundFiles.length === 0) return true;
463
+ return { ok: false, detail: formatPaths(foundFiles) };
449
464
  }
450
465
  function checkRootVitestConfig(baseName, exportName, cwd) {
451
466
  const matches = findFiles([`${baseName}.${CONFIG_EXTENSIONS}`], cwd);
452
467
  if (matches.length === 0) {
453
468
  return { ok: false, detail: `${baseName}.ts is missing` };
454
469
  }
455
- const stale = matches.filter(
470
+ const staleConfigs = matches.filter(
456
471
  (relativePath) => !importsSharedExport(readFileIn(cwd, relativePath), exportName, SHARED_VITEST_MODULE)
457
472
  );
458
- if (stale.length === 0) return true;
459
- return { ok: false, detail: `does not import ${exportName} from ${SHARED_VITEST_MODULE}: ${stale.join(", ")}` };
473
+ if (staleConfigs.length === 0) return true;
474
+ return {
475
+ ok: false,
476
+ detail: `does not import ${exportName} from ${SHARED_VITEST_MODULE}: ${staleConfigs.join(", ")}`
477
+ };
460
478
  }
461
479
  function prettierConfigBuildsOnSharedConfig(cwd = process.cwd()) {
462
480
  const configs = findFiles(
@@ -467,20 +485,20 @@ function prettierConfigBuildsOnSharedConfig(cwd = process.cwd()) {
467
485
  if (configs.length === 0) {
468
486
  return { ok: false, detail: describeMissingPrettierConfig(cwd) };
469
487
  }
470
- const stale = configs.filter(
488
+ const staleConfigs = configs.filter(
471
489
  (relativePath) => !importsSharedExport(readFileIn(cwd, relativePath), "definePrettierConfig", SHARED_PRETTIER_MODULE)
472
490
  );
473
- if (stale.length === 0) return true;
491
+ if (staleConfigs.length === 0) return true;
474
492
  return {
475
493
  ok: false,
476
- detail: `does not import definePrettierConfig from ${SHARED_PRETTIER_MODULE}: ${stale.join(", ")}`
494
+ detail: `does not import definePrettierConfig from ${SHARED_PRETTIER_MODULE}: ${staleConfigs.join(", ")}`
477
495
  };
478
496
  }
479
497
  function describeMissingPrettierConfig(cwd) {
480
- const inert = findFiles(INERT_PRETTIER_CONFIGS, cwd);
481
- if (inert.length > 0) return `holds no code to call the factory: ${inert.join(", ")}`;
498
+ const inertConfigs = findFiles(INERT_PRETTIER_CONFIGS, cwd);
499
+ if (inertConfigs.length > 0) return `does not contain code that calls the factory: ${inertConfigs.join(", ")}`;
482
500
  if (hasPrettierConfigKey(cwd)) {
483
- return 'holds no code to call the factory: the "prettier" key in package.json';
501
+ return 'does not contain code that calls the factory: the "prettier" key in package.json';
484
502
  }
485
503
  return ".prettierrc.js is missing";
486
504
  }
@@ -488,8 +506,8 @@ function hasPrettierConfigKey(cwd) {
488
506
  const manifest = readFileIn(cwd, "package.json");
489
507
  if (manifest === void 0) return false;
490
508
  try {
491
- const parsed = JSON.parse(manifest);
492
- return isRecord(parsed) && parsed["prettier"] !== void 0;
509
+ const parsedManifest = JSON.parse(manifest);
510
+ return isRecord(parsedManifest) && parsedManifest["prettier"] !== void 0;
493
511
  } catch {
494
512
  return false;
495
513
  }
@@ -502,11 +520,11 @@ function discoverMemberWorkspaces() {
502
520
  }
503
521
  }
504
522
  async function everyBinTargetIsACommittedWrapper() {
505
- const discovery = discoverMemberWorkspaces();
506
- if (!discovery.ok) return discovery;
507
- const tracked = await listTrackedFiles();
508
- const trackedPaths = tracked === void 0 ? void 0 : new Set(tracked);
509
- const offenders = discovery.workspaces.flatMap(
523
+ const workspaceDiscovery = discoverMemberWorkspaces();
524
+ if (!workspaceDiscovery.ok) return workspaceDiscovery;
525
+ const trackedFiles = await listTrackedFiles();
526
+ const trackedPaths = trackedFiles === void 0 ? void 0 : new Set(trackedFiles);
527
+ const offenders = workspaceDiscovery.workspaces.flatMap(
510
528
  (workspace) => readBinEntries(workspace).flatMap((entry) => {
511
529
  const defect = describeBinTargetDefect(workspace, entry, trackedPaths);
512
530
  return defect === void 0 ? [] : [`${describeBinEntry(workspace, entry)} (${defect})`];
@@ -516,16 +534,18 @@ async function everyBinTargetIsACommittedWrapper() {
516
534
  return { ok: false, detail: formatPaths(offenders) };
517
535
  }
518
536
  function everyBinWrapperTargetIsCoveredByFiles() {
519
- const discovery = discoverMemberWorkspaces();
520
- if (!discovery.ok) return discovery;
537
+ const workspaceDiscovery = discoverMemberWorkspaces();
538
+ if (!workspaceDiscovery.ok) return workspaceDiscovery;
521
539
  const cwd = process.cwd();
522
- const offenders = discovery.workspaces.flatMap((workspace) => {
540
+ const offenders = workspaceDiscovery.workspaces.flatMap((workspace) => {
523
541
  const files = workspace.packageJson["files"];
524
542
  if (!Array.isArray(files)) return [];
525
- const published = new Set(files.flatMap((entry) => typeof entry === "string" ? [readFirstSegment(entry)] : []));
543
+ const publishedSegments = new Set(
544
+ files.flatMap((entry) => typeof entry === "string" ? [readFirstSegment(entry)] : [])
545
+ );
526
546
  return readBinEntries(workspace).flatMap((entry) => {
527
547
  const target = readWrapperTarget(cwd, workspace, entry);
528
- if (target === void 0 || published.has(readFirstSegment(target))) return [];
548
+ if (target === void 0 || publishedSegments.has(readFirstSegment(target))) return [];
529
549
  return [`${describeBinEntry(workspace, entry)} -> ${target}`];
530
550
  });
531
551
  });
@@ -566,25 +586,25 @@ function readWrapperTarget(cwd, workspace, entry) {
566
586
  return posix.normalize(posix.join(posix.dirname(entry.target), specifier));
567
587
  }
568
588
  function everyTestFileNamesItsTier(cwd = process.cwd()) {
569
- const untiered = findUntieredTestFiles(cwd);
570
- if (untiered.length === 0) return true;
571
- return { ok: false, detail: formatPaths(untiered) };
589
+ const untieredFiles = findUntieredTestFiles(cwd);
590
+ if (untieredFiles.length === 0) return true;
591
+ return { ok: false, detail: formatPaths(untieredFiles) };
572
592
  }
573
593
  function everyTestFileSitsUnderTestsDir(cwd = process.cwd()) {
574
- const misplaced = findMisplacedTestFiles(cwd);
575
- if (misplaced.length === 0) return true;
576
- return { ok: false, detail: formatPaths(misplaced) };
594
+ const misplacedFiles = findMisplacedTestFiles(cwd);
595
+ if (misplacedFiles.length === 0) return true;
596
+ return { ok: false, detail: formatPaths(misplacedFiles) };
577
597
  }
578
598
  function everyViteConfigHasVitestConfig() {
579
- const discovery = discoverMemberWorkspaces();
580
- if (!discovery.ok) return discovery;
581
- const unpaired = discovery.workspaces.flatMap((workspace) => {
599
+ const workspaceDiscovery = discoverMemberWorkspaces();
600
+ if (!workspaceDiscovery.ok) return workspaceDiscovery;
601
+ const unpairedConfigs = workspaceDiscovery.workspaces.flatMap((workspace) => {
582
602
  const viteConfigs = findWorkspaceConfigs(workspace, VITE_CONFIG_PATTERN);
583
603
  if (viteConfigs.length === 0) return [];
584
604
  return findWorkspaceConfigs(workspace, VITEST_CONFIG_PATTERN).length > 0 ? [] : viteConfigs;
585
605
  });
586
- if (unpaired.length === 0) return true;
587
- return { ok: false, detail: formatPaths(unpaired) };
606
+ if (unpairedConfigs.length === 0) return true;
607
+ return { ok: false, detail: formatPaths(unpairedConfigs) };
588
608
  }
589
609
  function findFiles(patterns, cwd) {
590
610
  return globSync(patterns, { cwd, exclude: (path2) => SCAN_EXCLUDE_DIRS.has(basename(path2)) }).map((path2) => path2.split(sep).join("/")).toSorted();
@@ -597,11 +617,11 @@ function formatPaths(paths) {
597
617
  ${paths.map((path2) => ` ${path2}`).join("\n")}`;
598
618
  }
599
619
  function getMinVersion() {
600
- const picked = { "version": "0.38.0" };
601
- if (typeof picked["version"] !== "string") {
620
+ const pickedFields = { "version": "0.40.0" };
621
+ if (typeof pickedFields["version"] !== "string") {
602
622
  throw new TypeError("nmr/package.json: 'version' must be a string");
603
623
  }
604
- return picked["version"];
624
+ return pickedFields["version"];
605
625
  }
606
626
  function hasSupportedEslintVersion() {
607
627
  return hasMinDevDependencyVersion("eslint", MIN_ESLINT_VERSION, {
@@ -630,13 +650,13 @@ function isReExportOnly(content) {
630
650
  return statements.length > 0 && statements.every((line) => RE_EXPORT_LINE_PATTERN.test(line));
631
651
  }
632
652
  function noPnpmFieldInPackageJson(cwd = process.cwd()) {
633
- const declaring = findFiles(["**/package.json"], cwd).flatMap((relativePath) => {
653
+ const declaringFiles = findFiles(["**/package.json"], cwd).flatMap((relativePath) => {
634
654
  const keys = readPnpmFieldKeys(readFileIn(cwd, relativePath));
635
655
  if (keys === void 0) return [];
636
656
  return [keys.length > 0 ? `${relativePath} (${keys.join(", ")})` : relativePath];
637
657
  });
638
- if (declaring.length === 0) return true;
639
- return { ok: false, detail: formatPaths(declaring) };
658
+ if (declaringFiles.length === 0) return true;
659
+ return { ok: false, detail: formatPaths(declaringFiles) };
640
660
  }
641
661
  function noReExportOnlyVitestConfigs(cwd = process.cwd()) {
642
662
  const nonRootConfigs = findFiles([`**/${VITEST_CONFIG_PATTERN}`], cwd).filter((path2) => path2.includes("/"));
@@ -650,11 +670,11 @@ function noRedundantRootScripts() {
650
670
  const scripts = pkg["scripts"];
651
671
  if (!isRecord(scripts)) return true;
652
672
  const builtInNames = Object.keys(getDefaultRootScripts());
653
- const redundant = Object.keys(scripts).filter((name) => builtInNames.includes(name));
654
- if (redundant.length === 0) return true;
673
+ const redundantNames = Object.keys(scripts).filter((name) => builtInNames.includes(name));
674
+ if (redundantNames.length === 0) return true;
655
675
  return {
656
676
  ok: false,
657
- detail: `redundant: ${redundant.join(", ")}`
677
+ detail: `redundant: ${redundantNames.join(", ")}`
658
678
  };
659
679
  }
660
680
  function noRetiredVitestConfigs(cwd = process.cwd()) {
@@ -665,14 +685,14 @@ function noRetiredVitestConfigs(cwd = process.cwd()) {
665
685
  }
666
686
  function noUnguardedLefthookInstall(cwd = process.cwd()) {
667
687
  const scripts = readRootScripts(cwd);
668
- const unguarded = INSTALL_LIFECYCLE_SCRIPTS.flatMap((name) => {
688
+ const unguardedScripts = INSTALL_LIFECYCLE_SCRIPTS.flatMap((name) => {
669
689
  const command = scripts[name];
670
690
  if (typeof command !== "string") return [];
671
691
  const isUnguarded = LEFTHOOK_INSTALL_PATTERN.test(command) && !LEFTHOOK_CHECK_INSTALL_PATTERN.test(command);
672
692
  return isUnguarded ? [`${name}: ${command}`] : [];
673
693
  });
674
- if (unguarded.length === 0) return true;
675
- return { ok: false, detail: formatPaths(unguarded) };
694
+ if (unguardedScripts.length === 0) return true;
695
+ return { ok: false, detail: formatPaths(unguardedScripts) };
676
696
  }
677
697
  function noWorkspaceRunScriptReferences() {
678
698
  const packagesDir = join(process.cwd(), "packages");
@@ -698,27 +718,27 @@ function readFileIn(cwd, relativePath) {
698
718
  }
699
719
  function readPnpmFieldKeys(content) {
700
720
  if (content === void 0) return void 0;
701
- let parsed;
721
+ let parsedManifest;
702
722
  try {
703
- parsed = JSON.parse(content);
723
+ parsedManifest = JSON.parse(content);
704
724
  } catch {
705
725
  return void 0;
706
726
  }
707
- if (!isRecord(parsed)) return void 0;
708
- const pnpm = parsed["pnpm"];
727
+ if (!isRecord(parsedManifest)) return void 0;
728
+ const pnpm = parsedManifest["pnpm"];
709
729
  return isRecord(pnpm) ? Object.keys(pnpm).toSorted() : void 0;
710
730
  }
711
731
  function readRootScripts(cwd) {
712
732
  const content = readFileIn(cwd, "package.json");
713
733
  if (content === void 0) return {};
714
- let parsed;
734
+ let parsedManifest;
715
735
  try {
716
- parsed = JSON.parse(content);
736
+ parsedManifest = JSON.parse(content);
717
737
  } catch {
718
738
  return {};
719
739
  }
720
- if (!isRecord(parsed)) return {};
721
- const scripts = parsed["scripts"];
740
+ if (!isRecord(parsedManifest)) return {};
741
+ const scripts = parsedManifest["scripts"];
722
742
  return isRecord(scripts) ? scripts : {};
723
743
  }
724
744
  function resolvesVersionViaWorkspace(range) {
@@ -729,11 +749,11 @@ function tazeConfigBuildsOnSharedConfig(cwd = process.cwd()) {
729
749
  if (configs.length === 0) {
730
750
  return { ok: false, detail: describeMissingTazeConfig(cwd) };
731
751
  }
732
- const stale = configs.filter(
752
+ const staleConfigs = configs.filter(
733
753
  (relativePath) => !importsSharedExport(readFileIn(cwd, relativePath), "defineConfig", SHARED_TAZE_MODULE)
734
754
  );
735
- if (stale.length === 0) return true;
736
- return { ok: false, detail: `does not import defineConfig from ${SHARED_TAZE_MODULE}: ${stale.join(", ")}` };
755
+ if (staleConfigs.length === 0) return true;
756
+ return { ok: false, detail: `does not import defineConfig from ${SHARED_TAZE_MODULE}: ${staleConfigs.join(", ")}` };
737
757
  }
738
758
  function tazeConfigAvoidsClobberedOptions(cwd = process.cwd()) {
739
759
  const configs = findFiles([`taze.config.${CONFIG_EXTENSIONS}`], cwd);
@@ -741,22 +761,22 @@ function tazeConfigAvoidsClobberedOptions(cwd = process.cwd()) {
741
761
  for (const relativePath of configs) {
742
762
  const content = readFileIn(cwd, relativePath);
743
763
  if (content === void 0) continue;
744
- const discarded = CLOBBERED_TAZE_OPTIONS.filter(({ pattern }) => pattern.test(content)).map(({ key }) => key);
745
- if (discarded.length > 0) findings.push(`${relativePath}: ${discarded.join(", ")}`);
764
+ const discardedKeys = CLOBBERED_TAZE_OPTIONS.filter(({ pattern }) => pattern.test(content)).map(({ key }) => key);
765
+ if (discardedKeys.length > 0) findings.push(`${relativePath}: ${discardedKeys.join(", ")}`);
746
766
  }
747
767
  if (findings.length === 0) return true;
748
768
  return { ok: false, detail: formatPaths(findings) };
749
769
  }
750
770
  function describeMissingTazeConfig(cwd) {
751
- const inert = findFiles(INERT_TAZE_CONFIGS, cwd);
752
- if (inert.length > 0) return `holds no code to call the factory: ${inert.join(", ")}`;
771
+ const inertConfigs = findFiles(INERT_TAZE_CONFIGS, cwd);
772
+ if (inertConfigs.length > 0) return `does not contain code that calls the factory: ${inertConfigs.join(", ")}`;
753
773
  return "taze.config.ts is missing";
754
774
  }
755
775
  function testSuiteGatesTestFileConventions(cwd = process.cwd()) {
756
776
  if (findConventionsGuard(cwd) !== void 0) return true;
757
777
  return {
758
778
  ok: false,
759
- detail: `no test file under __tests__ imports checkTestFileConventions from ${SHARED_TESTS_MODULE}`
779
+ detail: `the test files under __tests__ do not import checkTestFileConventions from ${SHARED_TESTS_MODULE}`
760
780
  };
761
781
  }
762
782
  function describeConventionsGuardSkip() {
@@ -777,9 +797,9 @@ function vitestConfigBuildsOnSharedConfig() {
777
797
  const cwd = process.cwd();
778
798
  const rootConfigs = findFiles([VITEST_CONFIG_PATTERN], cwd);
779
799
  if (rootConfigs.length === 0) return { ok: false, detail: "vitest.config.ts is missing" };
780
- const discovery = discoverMemberWorkspaces();
781
- if (!discovery.ok) return discovery;
782
- const workspaceConfigs = discovery.workspaces.flatMap((workspace) => findWorkspaceConfigs(workspace, VITEST_CONFIG_PATTERN)).filter((relativePath) => !isOwnedByReExportCheck(cwd, relativePath));
800
+ const workspaceDiscovery = discoverMemberWorkspaces();
801
+ if (!workspaceDiscovery.ok) return workspaceDiscovery;
802
+ const workspaceConfigs = workspaceDiscovery.workspaces.flatMap((workspace) => findWorkspaceConfigs(workspace, VITEST_CONFIG_PATTERN)).filter((relativePath) => !isOwnedByReExportCheck(cwd, relativePath));
783
803
  const stale = [...rootConfigs, ...workspaceConfigs].filter(
784
804
  (relativePath) => !importsSharedExport(readFileIn(cwd, relativePath), "defineVitestConfig", SHARED_VITEST_MODULE)
785
805
  );
@@ -11,12 +11,12 @@
11
11
  "esbuildVersion": "0.28.2",
12
12
  "inputs": [
13
13
  {
14
- "hash": "488459c0",
14
+ "hash": "5f7bd316",
15
15
  "kind": "module",
16
16
  "path": "kits/default.ts"
17
17
  },
18
18
  {
19
- "hash": "521b947a",
19
+ "hash": "9c1005ba",
20
20
  "kind": "inline",
21
21
  "path": "../package.json",
22
22
  "paths": [
@@ -24,17 +24,22 @@
24
24
  ]
25
25
  },
26
26
  {
27
- "hash": "06bffeed",
27
+ "hash": "9336f4c0",
28
28
  "kind": "module",
29
29
  "path": "../src/default-scripts.ts"
30
30
  },
31
31
  {
32
- "hash": "c53d0c51",
32
+ "hash": "d4828172",
33
+ "kind": "module",
34
+ "path": "../src/git-ignored-paths.ts"
35
+ },
36
+ {
37
+ "hash": "23646439",
33
38
  "kind": "module",
34
39
  "path": "../src/resolve-scripts.ts"
35
40
  },
36
41
  {
37
- "hash": "2dfde614",
42
+ "hash": "19dec003",
38
43
  "kind": "module",
39
44
  "path": "../src/tiers.ts"
40
45
  }
@@ -43,8 +48,8 @@
43
48
  "path": "kits/default.js",
44
49
  "readyupVersion": "0.38.0",
45
50
  "source": "kits/default.ts",
46
- "sourceHash": "488459c0",
47
- "targetHash": "cbef780a"
51
+ "sourceHash": "5f7bd316",
52
+ "targetHash": "da11ca1f"
48
53
  }
49
54
  ]
50
55
  }