@williamthorsen/nmr 0.33.0 → 0.35.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 (59) hide show
  1. package/.readyup/kits/default.js +121 -25
  2. package/.readyup/manifest.json +11 -8
  3. package/README.md +155 -18
  4. package/agents/guidance/rulebooks/nmr.md +1 -1
  5. package/dist/esm/UserError.d.ts +6 -0
  6. package/dist/esm/check-cache.d.ts +145 -0
  7. package/dist/esm/commands/build-output.d.ts +64 -0
  8. package/dist/esm/commands/build.d.ts +29 -1
  9. package/dist/esm/commands/build.js +25 -5
  10. package/dist/esm/commands/clean.d.ts +15 -0
  11. package/dist/esm/commands/ensure-prepublish-hooks.d.ts +11 -0
  12. package/dist/esm/commands/fmt.d.ts +32 -0
  13. package/dist/esm/commands/report-catalog.d.ts +10 -0
  14. package/dist/esm/commands/report-overrides.d.ts +6 -0
  15. package/dist/esm/commands/taze.d.ts +18 -0
  16. package/dist/esm/commands/taze.js +17 -1
  17. package/dist/esm/config.d.ts +25 -0
  18. package/dist/esm/context.d.ts +11 -0
  19. package/dist/esm/default-scripts.d.ts +15 -0
  20. package/dist/esm/defineConfig.d.ts +9 -0
  21. package/dist/esm/help.d.ts +13 -0
  22. package/dist/esm/helpers/clampToBytes.d.ts +12 -0
  23. package/dist/esm/helpers/createBoundedBuffer.d.ts +7 -0
  24. package/dist/esm/helpers/deriveExcerpt.d.ts +10 -0
  25. package/dist/esm/helpers/duration.d.ts +15 -0
  26. package/dist/esm/helpers/hook-name.d.ts +8 -0
  27. package/dist/esm/helpers/package-json.d.ts +25 -0
  28. package/dist/esm/helpers/readStringValues.d.ts +7 -0
  29. package/dist/esm/helpers/reportClosing.d.ts +15 -0
  30. package/dist/esm/helpers/transcript.d.ts +25 -0
  31. package/dist/esm/helpers/type-guards.d.ts +1 -0
  32. package/dist/esm/helpers/workspace-patterns.d.ts +7 -0
  33. package/dist/esm/prettier.d.ts +8 -0
  34. package/dist/esm/recording.d.ts +39 -1
  35. package/dist/esm/replay-assembly.d.ts +18 -2
  36. package/dist/esm/report-format.d.ts +33 -0
  37. package/dist/esm/reportCliFailure.d.ts +8 -0
  38. package/dist/esm/resolve-scripts.d.ts +10 -1
  39. package/dist/esm/resolver.d.ts +67 -2
  40. package/dist/esm/resolver.js +1 -1
  41. package/dist/esm/runCli.d.ts +17 -0
  42. package/dist/esm/runCli.js +1 -1
  43. package/dist/esm/runner.d.ts +52 -0
  44. package/dist/esm/scripts.d.ts +1 -0
  45. package/dist/esm/steps.d.ts +71 -0
  46. package/dist/esm/taze.d.ts +16 -0
  47. package/dist/esm/tests.d.ts +40 -0
  48. package/dist/esm/tests.js +25 -0
  49. package/dist/esm/tiers.d.ts +56 -1
  50. package/dist/esm/tiers.js +22 -9
  51. package/dist/esm/types.d.ts +27 -0
  52. package/dist/esm/verbosity.d.ts +37 -0
  53. package/dist/esm/verdict.d.ts +45 -0
  54. package/dist/esm/vitest-git-isolation.d.ts +1 -0
  55. package/dist/esm/vitest-git-isolation.js +4 -0
  56. package/dist/esm/vitest.d.ts +74 -2
  57. package/dist/esm/vitest.js +33 -2
  58. package/dist/esm/workspace.d.ts +18 -0
  59. package/package.json +21 -11
@@ -1,13 +1,33 @@
1
- /** @noformat — @generated. Do not edit. Compiled by rdy. */
1
+ /** @noformat -- @generated. Do not edit. Compiled by rdy. */
2
2
  /* eslint-disable */
3
- export const __readyupVersion = "0.30.0";
3
+ export const __readyupVersion = "0.35.0";
4
4
 
5
5
 
6
6
  // .readyup/kits/default.ts
7
- import { existsSync, globSync, readdirSync as readdirSync2, readFileSync } from "node:fs";
8
- import { basename, join, sep } from "node:path";
7
+ import { existsSync, globSync, readdirSync as readdirSync2 } from "node:fs";
8
+ import { basename, dirname, join, sep } from "node:path";
9
+
10
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.6.3/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/isError.js
11
+ function isError(error) {
12
+ return error instanceof Error || Object.prototype.toString.call(error) === "[object Error]";
13
+ }
14
+
15
+ // ../../node_modules/.pnpm/@williamthorsen+toolbelt.errors@0.6.3/node_modules/@williamthorsen/toolbelt.errors/dist/esm/4-release/describeError.js
16
+ function describeError(error) {
17
+ try {
18
+ if (isError(error) && typeof error.message === "string" && error.message !== "") {
19
+ return error.message;
20
+ }
21
+ return String(error);
22
+ } catch {
23
+ return "[unstringifiable value]";
24
+ }
25
+ }
26
+
27
+ // .readyup/kits/default.ts
9
28
  import { defineRdyKit } from "readyup";
10
29
  import {
30
+ discoverWorkspaces,
11
31
  fileContains,
12
32
  fileExists,
13
33
  hasDevDependency,
@@ -108,10 +128,8 @@ var TEST_EXTENSIONS = "{ts,tsx}";
108
128
  var TEST_GLOB_PREFIX = `**/${TEST_DIR}/**`;
109
129
  var TEST_FILE_PATTERN = /\.test\.tsx?$/;
110
130
  var ALL_TEST_PATTERNS = [`${TEST_GLOB_PREFIX}/*.test.${TEST_EXTENSIONS}`];
111
- function findTestFiles(rootDir) {
112
- const found = [];
113
- collectTestFiles(rootDir, "", false, found);
114
- return found.toSorted();
131
+ function findTestFiles(rootDir, options = {}) {
132
+ return walkTestFiles(rootDir, options, false);
115
133
  }
116
134
  function hasTierInfix(filePath) {
117
135
  const tiers = TIER_NAMES;
@@ -119,18 +137,27 @@ function hasTierInfix(filePath) {
119
137
  }
120
138
  var TEST_COLLECTION_EXCLUDE = [".git", "coverage", "dist", "node_modules"];
121
139
  var TIER_NAMES = ["unit", "tool", "localhost", "remote"];
122
- function collectTestFiles(dir, relativeDir, inTestDir, found) {
140
+ function collectTestFiles(dir, relativeDir, inTestDir, context) {
123
141
  const entries = readdirSync(dir, { withFileTypes: true });
124
142
  for (const entry of entries) {
125
143
  const relativePath = relativeDir === "" ? entry.name : `${relativeDir}/${entry.name}`;
126
144
  if (entry.isDirectory()) {
127
- if (TEST_COLLECTION_EXCLUDE.includes(entry.name)) continue;
128
- collectTestFiles(path.join(dir, entry.name), relativePath, inTestDir || entry.name === TEST_DIR, found);
129
- } else if (inTestDir && TEST_FILE_PATTERN.test(entry.name)) {
130
- found.push(relativePath);
145
+ if (context.pruned.has(entry.name)) continue;
146
+ collectTestFiles(path.join(dir, entry.name), relativePath, inTestDir || entry.name === TEST_DIR, context);
147
+ } else if (inTestDir !== context.misplaced && TEST_FILE_PATTERN.test(entry.name)) {
148
+ context.found.push(relativePath);
131
149
  }
132
150
  }
133
151
  }
152
+ function walkTestFiles(rootDir, { exclude = [] }, misplaced) {
153
+ const context = {
154
+ found: [],
155
+ misplaced,
156
+ pruned: /* @__PURE__ */ new Set([...TEST_COLLECTION_EXCLUDE, ...exclude])
157
+ };
158
+ collectTestFiles(rootDir, "", false, context);
159
+ return context.found.toSorted();
160
+ }
134
161
 
135
162
  // .readyup/kits/default.ts
136
163
  var default_default = defineRdyKit({
@@ -240,10 +267,10 @@ var default_default = defineRdyKit({
240
267
  fix: "Delete every vitest.standalone.config.* and vitest.integration.config.*. nmr's test scripts select Vitest projects instead of naming config files"
241
268
  },
242
269
  {
243
- name: "vitest.config.ts builds on @williamthorsen/nmr/vitest",
270
+ name: "every vitest.config builds on @williamthorsen/nmr/vitest",
244
271
  severity: "error",
245
272
  check: () => vitestConfigBuildsOnSharedConfig(),
246
- fix: "Replace vitest.config.ts with: import { defineVitestConfig } from '@williamthorsen/nmr/vitest'; export default defineVitestConfig();"
273
+ 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"
247
274
  },
248
275
  {
249
276
  name: "vitest.root.config.ts builds on @williamthorsen/nmr/vitest",
@@ -251,6 +278,12 @@ var default_default = defineRdyKit({
251
278
  check: () => vitestRootConfigBuildsOnSharedConfig(),
252
279
  fix: "Replace vitest.root.config.ts with: import { defineRootVitestConfig } from '@williamthorsen/nmr/vitest'; export default defineRootVitestConfig({ monorepoRoot: import.meta.dirname });"
253
280
  },
281
+ {
282
+ name: "every workspace with a Vite config has a Vitest config",
283
+ severity: "error",
284
+ check: () => everyViteConfigHasVitestConfig(),
285
+ 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"
286
+ },
254
287
  {
255
288
  name: "every test file names its isolation tier",
256
289
  severity: "error",
@@ -278,6 +311,13 @@ var default_default = defineRdyKit({
278
311
  check: () => tazeConfigBuildsOnSharedConfig(),
279
312
  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"
280
313
  },
314
+ {
315
+ name: "taze config declares no option taze discards",
316
+ severity: "warn",
317
+ quiet: true,
318
+ check: () => tazeConfigAvoidsClobberedOptions(),
319
+ 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"
320
+ },
281
321
  // -- Audit dependency --------------------------------------------------------
282
322
  {
283
323
  name: "v11y-check in devDependencies",
@@ -306,11 +346,20 @@ var default_default = defineRdyKit({
306
346
  });
307
347
  var SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([".git", "coverage", "dist", "node_modules"]);
308
348
  var CONFIG_EXTENSIONS = "{ts,mts,cts,js,mjs,cjs}";
349
+ var VITE_CONFIG_PATTERN = `vite.config.${CONFIG_EXTENSIONS}`;
350
+ var VITEST_CONFIG_PATTERN = `vitest.config.${CONFIG_EXTENSIONS}`;
309
351
  var SHARED_VITEST_MODULE = "@williamthorsen/nmr/vitest";
310
352
  var SHARED_PRETTIER_MODULE = "@williamthorsen/nmr/prettier";
311
353
  var INERT_PRETTIER_CONFIGS = [".prettierrc", ".prettierrc.{json,json5,yaml,yml,toml}"];
312
354
  var SHARED_TAZE_MODULE = "@williamthorsen/nmr/taze";
313
355
  var INERT_TAZE_CONFIGS = [".tazerc", ".tazerc.json", "taze.config.json"];
356
+ var CLOBBERED_TAZE_OPTIONS = [
357
+ { key: "concurrency", pattern: /\bconcurrency\s*:/ },
358
+ { key: "githubActions", pattern: /\bgithubActions\s*:\s*(?:false|\{)/ },
359
+ { key: "ignoreOtherWorkspaces", pattern: /\bignoreOtherWorkspaces\s*:\s*false/ },
360
+ { key: "nodeVersion", pattern: /\bnodeVersion\s*:\s*false/ },
361
+ { key: "requestTimeout", pattern: /\brequestTimeout\s*:/ }
362
+ ];
314
363
  var RE_EXPORT_LINE_PATTERN = /^export\s*(?:\{\s*default\s*}|\*)\s*from\s*['"]\.\.\/[^'"]*['"];?$/;
315
364
  var MIN_ESLINT_VERSION = "10.0.0";
316
365
  var MIN_STRICT_LINT_VERSION = "9.3.0";
@@ -389,20 +438,41 @@ function hasPrettierConfigKey(cwd) {
389
438
  return false;
390
439
  }
391
440
  }
441
+ function discoverMemberWorkspaces() {
442
+ try {
443
+ return { ok: true, workspaces: discoverWorkspaces({ filter: (workspace) => !workspace.isRoot }) };
444
+ } catch (error) {
445
+ return { ok: false, detail: `cannot enumerate workspaces: ${describeError(error)}` };
446
+ }
447
+ }
392
448
  function everyTestFileNamesItsTier(cwd = process.cwd()) {
393
449
  const untiered = findTestFiles(cwd).filter((path2) => !hasTierInfix(path2));
394
450
  if (untiered.length === 0) return true;
395
451
  return { ok: false, detail: formatPaths(untiered) };
396
452
  }
453
+ function everyViteConfigHasVitestConfig() {
454
+ const discovery = discoverMemberWorkspaces();
455
+ if (!discovery.ok) return discovery;
456
+ const unpaired = discovery.workspaces.flatMap((workspace) => {
457
+ const viteConfigs = findWorkspaceConfigs(workspace, VITE_CONFIG_PATTERN);
458
+ if (viteConfigs.length === 0) return [];
459
+ return findWorkspaceConfigs(workspace, VITEST_CONFIG_PATTERN).length > 0 ? [] : viteConfigs;
460
+ });
461
+ if (unpaired.length === 0) return true;
462
+ return { ok: false, detail: formatPaths(unpaired) };
463
+ }
397
464
  function findFiles(patterns, cwd) {
398
465
  return globSync(patterns, { cwd, exclude: (path2) => SCAN_EXCLUDE_DIRS.has(basename(path2)) }).map((path2) => path2.split(sep).join("/")).toSorted();
399
466
  }
467
+ function findWorkspaceConfigs(workspace, pattern) {
468
+ return findFiles([pattern], workspace.absolutePath).map((name) => `${workspace.dir}/${name}`);
469
+ }
400
470
  function formatPaths(paths) {
401
471
  return `${paths.length} found:
402
472
  ${paths.map((path2) => ` ${path2}`).join("\n")}`;
403
473
  }
404
474
  function getMinVersion() {
405
- const picked = { "version": "0.33.0" };
475
+ const picked = { "version": "0.35.0" };
406
476
  if (typeof picked["version"] !== "string") {
407
477
  throw new TypeError("nmr/package.json: 'version' must be a string");
408
478
  }
@@ -418,11 +488,17 @@ function hasSupportedStrictLintVersion() {
418
488
  exempt: resolvesVersionViaWorkspace
419
489
  });
420
490
  }
491
+ function hasViteConfigBeside(cwd, relativePath) {
492
+ return findFiles([VITE_CONFIG_PATTERN], join(cwd, dirname(relativePath))).length > 0;
493
+ }
421
494
  function importsSharedExport(content, exportName, moduleSpecifier) {
422
495
  if (content === void 0) return false;
423
496
  const pattern = new RegExp(String.raw`import\s*\{[^}]*\b${exportName}\b[^}]*\}\s*from\s*['"]${moduleSpecifier}['"]`);
424
497
  return pattern.test(content);
425
498
  }
499
+ function isOwnedByReExportCheck(cwd, relativePath) {
500
+ return isReExportOnly(readFileIn(cwd, relativePath)) && !hasViteConfigBeside(cwd, relativePath);
501
+ }
426
502
  function isReExportOnly(content) {
427
503
  if (content === void 0) return false;
428
504
  const statements = content.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "").split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
@@ -438,8 +514,8 @@ function noPnpmFieldInPackageJson(cwd = process.cwd()) {
438
514
  return { ok: false, detail: formatPaths(declaring) };
439
515
  }
440
516
  function noReExportOnlyVitestConfigs(cwd = process.cwd()) {
441
- const nonRootConfigs = findFiles([`**/vitest.config.${CONFIG_EXTENSIONS}`], cwd).filter((path2) => path2.includes("/"));
442
- const reExports = nonRootConfigs.filter((path2) => isReExportOnly(readFileIn(cwd, path2)));
517
+ const nonRootConfigs = findFiles([`**/${VITEST_CONFIG_PATTERN}`], cwd).filter((path2) => path2.includes("/"));
518
+ const reExports = nonRootConfigs.filter((path2) => isOwnedByReExportCheck(cwd, path2));
443
519
  if (reExports.length === 0) return true;
444
520
  return { ok: false, detail: formatPaths(reExports) };
445
521
  }
@@ -482,11 +558,7 @@ function noWorkspaceRunScriptReferences() {
482
558
  };
483
559
  }
484
560
  function readFileIn(cwd, relativePath) {
485
- try {
486
- return readFileSync(join(cwd, relativePath), "utf8");
487
- } catch {
488
- return void 0;
489
- }
561
+ return readFile(join(cwd, relativePath));
490
562
  }
491
563
  function readPnpmFieldKeys(content) {
492
564
  if (content === void 0) return void 0;
@@ -514,6 +586,18 @@ function tazeConfigBuildsOnSharedConfig(cwd = process.cwd()) {
514
586
  if (stale.length === 0) return true;
515
587
  return { ok: false, detail: `does not import defineConfig from ${SHARED_TAZE_MODULE}: ${stale.join(", ")}` };
516
588
  }
589
+ function tazeConfigAvoidsClobberedOptions(cwd = process.cwd()) {
590
+ const configs = findFiles([`taze.config.${CONFIG_EXTENSIONS}`], cwd);
591
+ const findings = [];
592
+ for (const relativePath of configs) {
593
+ const content = readFileIn(cwd, relativePath);
594
+ if (content === void 0) continue;
595
+ const discarded = CLOBBERED_TAZE_OPTIONS.filter(({ pattern }) => pattern.test(content)).map(({ key }) => key);
596
+ if (discarded.length > 0) findings.push(`${relativePath}: ${discarded.join(", ")}`);
597
+ }
598
+ if (findings.length === 0) return true;
599
+ return { ok: false, detail: formatPaths(findings) };
600
+ }
517
601
  function describeMissingTazeConfig(cwd) {
518
602
  const inert = findFiles(INERT_TAZE_CONFIGS, cwd);
519
603
  if (inert.length > 0) return `holds no code to call the factory: ${inert.join(", ")}`;
@@ -524,8 +608,18 @@ function toolVersionsHasNoPnpm() {
524
608
  if (content === void 0) return true;
525
609
  return !/^pnpm\s/m.test(content);
526
610
  }
527
- function vitestConfigBuildsOnSharedConfig(cwd = process.cwd()) {
528
- return checkRootVitestConfig("vitest.config", "defineVitestConfig", cwd);
611
+ function vitestConfigBuildsOnSharedConfig() {
612
+ const cwd = process.cwd();
613
+ const rootConfigs = findFiles([VITEST_CONFIG_PATTERN], cwd);
614
+ if (rootConfigs.length === 0) return { ok: false, detail: "vitest.config.ts is missing" };
615
+ const discovery = discoverMemberWorkspaces();
616
+ if (!discovery.ok) return discovery;
617
+ const workspaceConfigs = discovery.workspaces.flatMap((workspace) => findWorkspaceConfigs(workspace, VITEST_CONFIG_PATTERN)).filter((relativePath) => !isOwnedByReExportCheck(cwd, relativePath));
618
+ const stale = [...rootConfigs, ...workspaceConfigs].filter(
619
+ (relativePath) => !importsSharedExport(readFileIn(cwd, relativePath), "defineVitestConfig", SHARED_VITEST_MODULE)
620
+ );
621
+ if (stale.length === 0) return true;
622
+ return { ok: false, detail: formatPaths(stale) };
529
623
  }
530
624
  function vitestRootConfigBuildsOnSharedConfig(cwd = process.cwd()) {
531
625
  return checkRootVitestConfig("vitest.root.config", "defineRootVitestConfig", cwd);
@@ -533,12 +627,14 @@ function vitestRootConfigBuildsOnSharedConfig(cwd = process.cwd()) {
533
627
  export {
534
628
  default_default as default,
535
629
  everyTestFileNamesItsTier,
630
+ everyViteConfigHasVitestConfig,
536
631
  hasSupportedEslintVersion,
537
632
  hasSupportedStrictLintVersion,
538
633
  noPnpmFieldInPackageJson,
539
634
  noReExportOnlyVitestConfigs,
540
635
  noRetiredVitestConfigs,
541
636
  prettierConfigBuildsOnSharedConfig,
637
+ tazeConfigAvoidsClobberedOptions,
542
638
  tazeConfigBuildsOnSharedConfig,
543
639
  vitestConfigBuildsOnSharedConfig,
544
640
  vitestRootConfigBuildsOnSharedConfig
@@ -2,18 +2,21 @@
2
2
  "version": 1,
3
3
  "kits": [
4
4
  {
5
+ "bundledDependencies": {
6
+ "@williamthorsen/toolbelt.errors": "0.6.3"
7
+ },
5
8
  "checklists": [
6
9
  "nmr"
7
10
  ],
8
- "esbuildVersion": "0.28.1",
11
+ "esbuildVersion": "0.28.2",
9
12
  "inputs": [
10
13
  {
11
- "hash": "904ac753",
14
+ "hash": "b67d9796",
12
15
  "kind": "module",
13
16
  "path": "kits/default.ts"
14
17
  },
15
18
  {
16
- "hash": "1cd4c58d",
19
+ "hash": "7a897007",
17
20
  "kind": "inline",
18
21
  "path": "../package.json",
19
22
  "paths": [
@@ -26,22 +29,22 @@
26
29
  "path": "../src/default-scripts.ts"
27
30
  },
28
31
  {
29
- "hash": "b42c7523",
32
+ "hash": "c53d0c51",
30
33
  "kind": "module",
31
34
  "path": "../src/resolve-scripts.ts"
32
35
  },
33
36
  {
34
- "hash": "c2d21075",
37
+ "hash": "2dfde614",
35
38
  "kind": "module",
36
39
  "path": "../src/tiers.ts"
37
40
  }
38
41
  ],
39
42
  "name": "default",
40
43
  "path": "kits/default.js",
41
- "readyupVersion": "0.30.0",
44
+ "readyupVersion": "0.35.0",
42
45
  "source": "kits/default.ts",
43
- "sourceHash": "904ac753",
44
- "targetHash": "2b35e5fd"
46
+ "sourceHash": "b67d9796",
47
+ "targetHash": "3cd6b2d0"
45
48
  }
46
49
  ]
47
50
  }