vitest-auto-spy 5.53.0 → 5.55.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.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,33 @@ The latest released version here must always match the one published on
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [5.55.0] - 2026-09-30
14
+
15
+ ### Fixed
16
+
17
+ - **`perf-import` no longer names a barrel the spec cannot avoid.** A spec whose other imports —
18
+ usually its subject — already load the same barrel is skipped, since importing the module directly
19
+ would leave the graph as it is. So is a barrel that a non-wildcard tsconfig `paths` alias points at,
20
+ when the spec lives outside the barrel's directory: that is another package's public entry, and in
21
+ an Nx workspace `@nx/enforce-module-boundaries` rejects the relative import that would bypass it.
22
+ On a consumer Nx workspace this took the report from 7 barrel findings to 0.
23
+
24
+ ## [5.54.0] - 2026-09-30
25
+
26
+ ### Added
27
+
28
+ - **`perf --json <reports> --gate` re-measures its suspects without `--command`.** The perf reporter
29
+ now records the Vitest config file the suite ran with (`configFile`, relative to `root`), and the
30
+ confirmation pass re-runs each suspect with `vitest run --config <that file>` over the suspects
31
+ only: one run per config when the reports came from several (`vitest.config.ts` and
32
+ `libs/tooling/vitest.config.ts`, say), read back as one second reading. A single slow reading on a
33
+ loaded runner now comes back as not reproduced instead of needing `--no-confirm` to fail anything.
34
+ The re-run has coverage off (`--coverage.enabled=false`), even when the first reading had it on:
35
+ coverage thresholds fail a run of a few files, and instrumentation is the harness's cost rather
36
+ than the test's, so a body over budget only because of instrumentation is not reproduced.
37
+ `--command` still takes precedence, and a report without `configFile` is not re-measured, as
38
+ before. The perf report format is version 6; versions 1–6 are read.
39
+
13
40
  ## [5.53.0] - 2026-09-29
14
41
 
15
42
  ### Added
@@ -6657,7 +6684,9 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
6657
6684
  This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
6658
6685
  [full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
6659
6686
 
6660
- [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.53.0...HEAD
6687
+ [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.55.0...HEAD
6688
+ [5.55.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.54.0...v5.55.0
6689
+ [5.54.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.53.0...v5.54.0
6661
6690
  [5.53.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.52.0...v5.53.0
6662
6691
  [5.52.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.51.0...v5.52.0
6663
6692
  [5.51.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.50.0...v5.51.0
@@ -254,8 +254,11 @@ test bodies, and the gate prints the slowest tests, the share in hooks against b
254
254
  time went in the spec, in your code and by package. The report ends in two tables of what is over
255
255
  budget and nothing else. And every candidate is re-measured on its own before it may fail anything: a
256
256
  file that is fast when it has the machine to itself is reported as _not reproduced_, an `info` rather
257
- than a finding. Without a way to re-measure, findings are warnings that fail nothing unless
258
- `--no-confirm` says one reading is enough. Exit `1` is "over budget", exit `2` is "there was nothing
257
+ than a finding. `--json` without `--command` re-measures too: the report records its Vitest config
258
+ file, and the gate runs `vitest run --config <it>` over the suspects, one run per config when the
259
+ reports came from several, with coverage off. Without a way to re-measure (a `--command` without
260
+ `{paths}`, a report from before the config was recorded), findings are warnings that fail nothing
261
+ unless `--no-confirm` says one reading is enough. Exit `1` is "over budget", exit `2` is "there was nothing
259
262
  to judge" — including a red or unfinished (`partial`) suite, which the gate will not judge at all, since a failed test is
260
263
  measured until its timeout and 30 s of timeout looks exactly like 30 s of slow code.
261
264
 
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { ownVersion, isDirectory, readProfile, formatFindings, outputWidth, painterFor, summarize, DOCTOR_CHECKS, writeTextFile, REPORT_SCHEMA, sortFindings, findingJson, tallyOf, ownPackageRoot, readTextFile, SCAN_CAP_ENV, scanCap, COVERAGE_MATCHING_DOCS, VITEST_5_PERF_DOCS, filterBySeverity, isSymlink, skillFrontmatter, toPosix, pathExists, NOTHING_TO_READ_DOCS, removeFile, parseJsonc, isRecord, extractSetupFiles, captures, gitignoreFilter, BARE_RUN_DOCS, MONOCHROME, findStringEnd, wrapText, listRepositoryFiles, isSkippedDirectory, versionFrom, stripColor, padStart, padEnd } from './cli-SELXGLDQ.js';
2
+ import { ownVersion, isDirectory, readProfile, formatFindings, outputWidth, painterFor, summarize, ownPackageRoot, DOCTOR_CHECKS, writeTextFile, REPORT_SCHEMA, sortFindings, findingJson, tallyOf, readTextFile, SCAN_CAP_ENV, scanCap, COVERAGE_MATCHING_DOCS, VITEST_5_PERF_DOCS, filterBySeverity, isSymlink, skillFrontmatter, toPosix, pathExists, NOTHING_TO_READ_DOCS, removeFile, parseJsonc, isRecord, extractSetupFiles, captures, gitignoreFilter, BARE_RUN_DOCS, MONOCHROME, findStringEnd, listRepositoryFiles, wrapText, isSkippedDirectory, versionFrom, stripColor, padStart, padEnd } from './cli-SELXGLDQ.js';
3
3
  import { resolve, join, posix, relative, isAbsolute, dirname } from 'node:path';
4
4
  import { createHash } from 'node:crypto';
5
5
  import { createRequire } from 'node:module';
@@ -1654,7 +1654,7 @@ function foreignPragmaPass() {
1654
1654
  }
1655
1655
 
1656
1656
  // src/cli/checks/export-map.generated.ts
1657
- var EXPORT_MAP_VERSION = "5.52.0";
1657
+ var EXPORT_MAP_VERSION = "5.54.0";
1658
1658
  var ENTRY_SPECIFIERS = "vitest-auto-spy vitest-auto-spy/bun vitest-auto-spy/bun-angular vitest-auto-spy/node vitest-auto-spy/rstest vitest-auto-spy/rxjs vitest-auto-spy/console vitest-auto-spy/dom-stubs vitest-auto-spy/diagnostics vitest-auto-spy/jasmine vitest-auto-spy/jasmine-compat vitest-auto-spy/observer-spy vitest-auto-spy/angular vitest-auto-spy/angular/diagnostics vitest-auto-spy/angular/doubles vitest-auto-spy/angular/matchers vitest-auto-spy/angular-http vitest-auto-spy/angular-router vitest-auto-spy/signal-forms vitest-auto-spy/nestjs vitest-auto-spy/react vitest-auto-spy/vue vitest-auto-spy/svelte vitest-auto-spy/setup vitest-auto-spy/zone vitest-auto-spy/eslint-plugin vitest-auto-spy/perf-reporter";
1659
1659
  var EXPORTED_BY = {
1660
1660
  AccessorImplementations: "0 1 2 3 4 12 20 21 22",
@@ -4614,8 +4614,6 @@ function runNgTest(options, tools, io) {
4614
4614
  }
4615
4615
  return tools.spawn({ command: process.execPath, args: run, cwd: options.cwd, env: {}, shell: false }).status;
4616
4616
  }
4617
-
4618
- // src/cli/checks/barrels.ts
4619
4617
  var BARREL_NAME = /(?:^|\/)(?:index|public[_-]api)\.[cm]?[jt]sx?$/;
4620
4618
  var RE_EXPORT = /\bexport\s+(?:type\s+)?(?:\*|{[^}]*})[^;]*?\bfrom\s*["'][^"']+["']/g;
4621
4619
  var OWN_DECLARATION = /(?:^|\n)\s*export\s+(?:abstract\s+)?(?:default|class|function|const|let|var|enum|namespace|interface|type\s+\w+\s*=)\b/;
@@ -4625,9 +4623,9 @@ function isBarrel(file, text) {
4625
4623
  }
4626
4624
  return (text.match(RE_EXPORT) ?? []).length >= 2;
4627
4625
  }
4628
- function reachOf(file, graph) {
4629
- const seen = /* @__PURE__ */ new Set([file]);
4630
- const order = [file];
4626
+ function reachableFrom(starts, graph) {
4627
+ const seen = new Set(starts);
4628
+ const order = [...starts];
4631
4629
  for (const current of order) {
4632
4630
  for (const edge of graph.imports.get(current) ?? []) {
4633
4631
  if (!seen.has(edge)) {
@@ -4636,9 +4634,33 @@ function reachOf(file, graph) {
4636
4634
  }
4637
4635
  }
4638
4636
  }
4639
- return seen.size - 1;
4637
+ return seen;
4638
+ }
4639
+ function reachOf(file, graph) {
4640
+ return reachableFrom([file], graph).size - 1;
4641
+ }
4642
+ function packageEntries(cwd, files) {
4643
+ const entries = /* @__PURE__ */ new Set();
4644
+ for (const alias of readCompilerPaths2(cwd).aliases) {
4645
+ for (const target of alias.wildcard ? [] : alias.targets) {
4646
+ const resolved = resolveRelative("package.json", `./${target}`, files);
4647
+ if (resolved !== void 0) {
4648
+ entries.add(resolved);
4649
+ }
4650
+ }
4651
+ }
4652
+ return entries;
4653
+ }
4654
+ function loadedAnyway(barrel, imported, graph) {
4655
+ return reachableFrom(
4656
+ imported.filter((other) => other !== barrel),
4657
+ graph
4658
+ ).has(barrel);
4659
+ }
4660
+ function outsidePackage(spec, barrel, entries) {
4661
+ return entries.has(barrel) && !spec.startsWith(`${posix.dirname(barrel)}/`);
4640
4662
  }
4641
- function findBarrelImports(graph) {
4663
+ function findBarrelImports(graph, entries = /* @__PURE__ */ new Set()) {
4642
4664
  const barrels = /* @__PURE__ */ new Map();
4643
4665
  const found = [];
4644
4666
  for (const [file, text] of graph.texts) {
@@ -4652,7 +4674,7 @@ function findBarrelImports(graph) {
4652
4674
  }
4653
4675
  for (const target of imported) {
4654
4676
  const reach = barrels.get(target);
4655
- if (reach !== void 0) {
4677
+ if (reach !== void 0 && !outsidePackage(spec, target, entries) && !loadedAnyway(target, imported, graph)) {
4656
4678
  found.push({ spec, barrel: target, reach });
4657
4679
  }
4658
4680
  }
@@ -4665,8 +4687,8 @@ var PERF_OUTPUT_ENV = "VITEST_AUTO_SPY_PERF_OUT";
4665
4687
  var PERF_REPORTER_ENV = "VITEST_AUTO_SPY_PERF_REPORTER";
4666
4688
  var PERF_PROFILE_ENV = "VITEST_AUTO_SPY_PERF_PROFILE";
4667
4689
  var PERF_ISOLATE_ENV = "VITEST_AUTO_SPY_PERF_ISOLATE";
4668
- var PERF_FORMAT_VERSION = 5;
4669
- var READABLE_VERSIONS = [1, 2, 3, 4, 5];
4690
+ var PERF_FORMAT_VERSION = 6;
4691
+ var READABLE_VERSIONS = [1, 2, 3, 4, 5, 6];
4670
4692
  var CASE_FLOOR_MS = 100;
4671
4693
  var CASES_PER_FILE = 5;
4672
4694
  var OPTIONAL_FILE_NUMBERS = ["workerId", "lane", "start", "fetch", "setupFetch", "retries", "autoSpy"];
@@ -4811,7 +4833,8 @@ function parsePerfRun(text) {
4811
4833
  ...parsed["partial"] === true ? { partial: true } : {},
4812
4834
  ...end === "failed" || end === "interrupted" || end === "passed" ? { end } : {},
4813
4835
  ...parsed["hung"] === true ? { hung: true } : {},
4814
- ...coverage === void 0 ? {} : { coverage }
4836
+ ...coverage === void 0 ? {} : { coverage },
4837
+ ...typeof parsed["configFile"] === "string" && parsed["configFile"] !== "" ? { configFile: parsed["configFile"] } : {}
4815
4838
  };
4816
4839
  }
4817
4840
  function sumOf(run, key) {
@@ -5385,7 +5408,8 @@ function finding3(candidate, severity, tail) {
5385
5408
  };
5386
5409
  }
5387
5410
  var UNCONFIRMED = [
5388
- "Measured once and never confirmed, so it fails nothing. Run the gate where the files can be re-measured,",
5411
+ "Measured once and never confirmed, so it fails nothing. Run the gate where the files can be re-measured \u2014",
5412
+ "a --command with {paths}, or a report whose perf reporter recorded the Vitest config \u2014",
5389
5413
  "or pass --no-confirm to gate on a single reading."
5390
5414
  ].join(" ");
5391
5415
  function rowOf(candidate, outcome, again) {
@@ -7070,9 +7094,9 @@ function perfMarkdown(document) {
7070
7094
 
7071
7095
  // src/cli/perf.ts
7072
7096
  var QUIET_MS = 5e3;
7073
- function barrelCandidates(graph) {
7097
+ function barrelCandidates(graph, entries) {
7074
7098
  const widest = /* @__PURE__ */ new Map();
7075
- for (const use of findBarrelImports(graph)) {
7099
+ for (const use of findBarrelImports(graph, entries)) {
7076
7100
  if (!widest.has(use.spec)) {
7077
7101
  widest.set(use.spec, use);
7078
7102
  }
@@ -7082,11 +7106,11 @@ function barrelCandidates(graph) {
7082
7106
  function importPhase(run) {
7083
7107
  return run.files.length > 0 && run.files.every((file) => file.fetch !== void 0) ? "Evaluating imported modules \u2014 the wait for their transforms is counted under `transform` \u2014" : "Importing modules";
7084
7108
  }
7085
- function importFindings(phases, graph, run, context) {
7109
+ function importFindings(phases, graph, profile, run, context) {
7086
7110
  if (shareOf(phases, "import") < DOMINATES || context.builder !== void 0) {
7087
7111
  return [];
7088
7112
  }
7089
- const ranked = barrelCandidates(graph);
7113
+ const ranked = barrelCandidates(graph, packageEntries(profile.cwd, new Set(profile.files)));
7090
7114
  if (ranked.length === 0) {
7091
7115
  return [];
7092
7116
  }
@@ -7127,7 +7151,7 @@ function analysePerf(run, profile, failOnFlaky = false, context = perfContext(pr
7127
7151
  ...environmentFindings(phases, profile, graph, measured),
7128
7152
  ...domEngineFindings(phases, graph, run, context),
7129
7153
  ...transformFindings(phases, graph, run, context),
7130
- ...importFindings(phases, graph, run, context),
7154
+ ...importFindings(phases, graph, profile, run, context),
7131
7155
  ...isolationFindings(phases, graph, profile, run, void 0, context),
7132
7156
  ...poolFindings(phases, graph, run, context),
7133
7157
  ...workerFindings(total, graph, run, void 0, context)
@@ -7450,6 +7474,11 @@ function reroot(path, from, to) {
7450
7474
  return rest.startsWith("/") || rest.startsWith("\\") ? `${to}${rest}` : path;
7451
7475
  }
7452
7476
  var END_ORDER = ["interrupted", "failed", "passed"];
7477
+ function sharedConfigFile(inputs) {
7478
+ const configFiles = new Set(inputs.map((input) => input.run.configFile));
7479
+ const [configFile] = configFiles;
7480
+ return { shared: configFiles.size <= 1, configFile };
7481
+ }
7453
7482
  function mergedRun(inputs) {
7454
7483
  const runs2 = inputs.map((input) => input.run);
7455
7484
  const vitest = runs2.find((run) => run.vitest !== void 0)?.vitest;
@@ -7457,6 +7486,7 @@ function mergedRun(inputs) {
7457
7486
  const startups = runs2.flatMap((run) => run.startup === void 0 ? [] : [run.startup]);
7458
7487
  const end = END_ORDER.find((reason) => runs2.some((run) => run.end === reason));
7459
7488
  const coverages = runs2.flatMap((run) => run.coverage === void 0 ? [] : [run.coverage]);
7489
+ const configFile = sharedConfigFile(inputs);
7460
7490
  const startup = startups.reduce((total, each) => ({ ms: total.ms + each.ms, workers: total.workers + each.workers }), {
7461
7491
  ms: 0,
7462
7492
  workers: 0
@@ -7474,7 +7504,8 @@ function mergedRun(inputs) {
7474
7504
  ...runs2.some((run) => run.partial === true) ? { partial: true } : {},
7475
7505
  ...end === void 0 ? {} : { end },
7476
7506
  ...runs2.some((run) => run.hung === true) ? { hung: true } : {},
7477
- ...coverages.length === 0 ? {} : { coverage: Math.max(...coverages) }
7507
+ ...coverages.length === 0 ? {} : { coverage: Math.max(...coverages) },
7508
+ ...configFile.shared && configFile.configFile !== void 0 ? { configFile: configFile.configFile } : {}
7478
7509
  };
7479
7510
  }
7480
7511
  function mergeRuns(inputs) {
@@ -7483,15 +7514,18 @@ function mergeRuns(inputs) {
7483
7514
  const carriedBy = /* @__PURE__ */ new Map();
7484
7515
  const empty = [];
7485
7516
  const offset = { workerId: 0, lane: 0 };
7517
+ const perFile = !sharedConfigFile(inputs).shared;
7486
7518
  for (const input of inputs) {
7487
7519
  const from = trimRoot(input.run.root);
7488
7520
  const shift = { ...offset };
7521
+ const configFile = perFile ? input.run.configFile : void 0;
7489
7522
  if (input.run.files.length === 0) {
7490
7523
  empty.push(input.path);
7491
7524
  }
7492
7525
  for (const file of input.run.files) {
7493
7526
  const path = reroot(file.file, from, root);
7494
- const moved = shifted(path === file.file ? file : { ...file, file: path }, shift, offset);
7527
+ const placed2 = { ...file, file: path, ...configFile === void 0 ? {} : { configFile } };
7528
+ const moved = shifted(placed2, shift, offset);
7495
7529
  const previous = kept.get(path);
7496
7530
  carriedBy.set(path, [...carriedBy.get(path) ?? [], input.path]);
7497
7531
  if (previous === void 0 || fileTotal(moved) > fileTotal(previous)) {
@@ -7726,13 +7760,22 @@ function vitestMajor3(cwd) {
7726
7760
  function versionFlags(cwd) {
7727
7761
  return (vitestMajor3(cwd) ?? 0) >= 5 ? ["--experimental.diagnostics=false"] : [];
7728
7762
  }
7763
+ function configArgs(config) {
7764
+ return config === void 0 ? [] : ["--config", config, "--coverage.enabled=false"];
7765
+ }
7729
7766
  function fromRun(options, spawn, packageRoot) {
7730
7767
  const entry = join(options.cwd, "node_modules", "vitest", "vitest.mjs");
7731
7768
  const reporter = reporterPath(packageRoot);
7732
- const mismatch = bareRunWouldMeasureSomethingElse(options.profile);
7769
+ const mismatch = options.config === void 0 ? bareRunWouldMeasureSomethingElse(options.profile) : void 0;
7733
7770
  if (mismatch !== void 0) {
7734
7771
  return failed(mismatch);
7735
7772
  }
7773
+ if (options.config !== void 0 && !pathExists(resolve(options.cwd, options.config))) {
7774
+ return failed(
7775
+ `The report was measured with the Vitest config ${options.config}, and there is no such file in ${options.cwd}. Run the gate in the checkout the report was written for, or pass --command.
7776
+ Docs: ${NOTHING_TO_READ_DOCS}`
7777
+ );
7778
+ }
7736
7779
  if (!pathExists(entry)) {
7737
7780
  return failed(
7738
7781
  `No Vitest is installed in ${options.cwd}, so there is nothing to run. Install vitest there, then measure again.
@@ -7749,7 +7792,16 @@ Docs: ${NOTHING_TO_READ_DOCS}`
7749
7792
  removeFile(target);
7750
7793
  const spawned = timed(spawn, {
7751
7794
  command: process.execPath,
7752
- args: [entry, "run", "--reporter=default", `--reporter=${reporter}`, "--logHeapUsage", ...versionFlags(options.cwd), ...options.paths],
7795
+ args: [
7796
+ entry,
7797
+ "run",
7798
+ ...configArgs(options.config),
7799
+ "--reporter=default",
7800
+ `--reporter=${reporter}`,
7801
+ "--logHeapUsage",
7802
+ ...versionFlags(options.cwd),
7803
+ ...options.paths
7804
+ ],
7753
7805
  cwd: options.cwd,
7754
7806
  env: runEnv(options, target, reporter),
7755
7807
  shell: false
@@ -7809,15 +7861,66 @@ function readPerfRun(options, spawn = spawnProcess, packageRoot = ownPackageRoot
7809
7861
  const source = options.json === void 0 ? fromCommand(options, command, spawn, packageRoot) : fromFile(options.json, options.cwd);
7810
7862
  return source.ok ? { ...source, command } : source;
7811
7863
  }
7812
- function perfRemeasure(options, spawn = spawnProcess, packageRoot = ownPackageRoot()) {
7864
+ function localRoot(root, cwd) {
7865
+ const here = relative(cwd, root);
7866
+ return root !== "" && !here.startsWith("..") && !isAbsolute(here) ? root : cwd;
7867
+ }
7868
+ function recordedConfigs(recorded, cwd) {
7869
+ const root = localRoot(recorded.root, cwd);
7870
+ const configs = /* @__PURE__ */ new Map();
7871
+ for (const [path, file] of measuredFiles(recorded, cwd)) {
7872
+ const configFile = file.configFile ?? recorded.configFile;
7873
+ if (configFile !== void 0) {
7874
+ configs.set(path, toPosix(relative(cwd, resolve(root, configFile))));
7875
+ }
7876
+ }
7877
+ return configs;
7878
+ }
7879
+ function mergeMeasured(measured) {
7880
+ return {
7881
+ ok: true,
7882
+ run: mergeRuns(measured.map((each, index) => ({ path: String(index), run: each.run }))).run,
7883
+ runFailed: measured.some((each) => each.runFailed),
7884
+ profiles: new Map(measured.flatMap((each) => [...new Map(each.profiles)]))
7885
+ };
7886
+ }
7887
+ function underRecordedConfigs(configs, remeasure) {
7888
+ return (paths) => {
7889
+ const groups = /* @__PURE__ */ new Map();
7890
+ for (const path of paths) {
7891
+ const config = configs.get(path);
7892
+ if (config !== void 0) {
7893
+ groups.set(config, [...groups.get(config) ?? [], path]);
7894
+ }
7895
+ }
7896
+ if (groups.size === 0) {
7897
+ return failed(
7898
+ `None of the files to re-measure is in a report that recorded its Vitest config. Pass --command to re-measure them.
7899
+ Docs: ${NOTHING_TO_READ_DOCS}`
7900
+ );
7901
+ }
7902
+ const measured = [];
7903
+ for (const [config, group] of groups) {
7904
+ const source = remeasure(group, config);
7905
+ if (!source.ok) {
7906
+ return source;
7907
+ }
7908
+ measured.push(source);
7909
+ }
7910
+ return mergeMeasured(measured);
7911
+ };
7912
+ }
7913
+ function perfRemeasure(options, spawn = spawnProcess, packageRoot = ownPackageRoot(), recorded) {
7813
7914
  if (options.command !== void 0 && !commandTakesPaths(options.command)) {
7814
7915
  return void 0;
7815
7916
  }
7917
+ const profileDir = join(options.cwd, "node_modules", ".cache", "vitest-auto-spy", `profiles-${process.pid}`);
7918
+ const scoped = { ...options, json: void 0, out: void 0, profileDir };
7816
7919
  if (options.command === void 0 && options.json !== void 0) {
7817
- return void 0;
7920
+ const configs = recorded === void 0 ? /* @__PURE__ */ new Map() : recordedConfigs(recorded, options.cwd);
7921
+ return configs.size === 0 ? void 0 : underRecordedConfigs(configs, (paths, config) => readPerfRun({ ...scoped, paths, config }, spawn, packageRoot));
7818
7922
  }
7819
- const profileDir = join(options.cwd, "node_modules", ".cache", "vitest-auto-spy", `profiles-${process.pid}`);
7820
- return (paths) => readPerfRun({ ...options, json: void 0, out: void 0, paths, profileDir }, spawn, packageRoot);
7923
+ return (paths) => readPerfRun({ ...scoped, paths }, spawn, packageRoot);
7821
7924
  }
7822
7925
  function perfAbIsolate(options, spawn, packageRoot = ownPackageRoot()) {
7823
7926
  if (options.command === void 0 && options.json !== void 0) {
@@ -7901,7 +8004,7 @@ function baselineRequest(args, cwd) {
7901
8004
  }
7902
8005
  };
7903
8006
  }
7904
- var PROFILE_DIR_UNUSED = "warning --profile-dir keeps the profiles the gate's confirmation pass records, and this run has no such pass: add --gate, without --no-confirm, on a source it can run again.\n";
8007
+ var PROFILE_DIR_UNUSED = "warning --profile-dir keeps the profiles the gate's confirmation pass records, and this run has no such pass: add --gate, without --no-confirm, on a source it can run again \u2014 a run, a --command with {paths}, or a report that recorded its Vitest config.\n";
7905
8008
  function perfCommand(cwd, argv, io) {
7906
8009
  const args = parseArgs(argv);
7907
8010
  const format = formatOf(args);
@@ -7918,7 +8021,13 @@ function perfCommand(cwd, argv, io) {
7918
8021
  ...keepProfiles === void 0 ? {} : { keepProfiles: resolve(cwd, keepProfiles) }
7919
8022
  };
7920
8023
  const trustSingle = flagEnabled(args, "no-confirm");
7921
- const gate = flagEnabled(args, "gate") ? { options: gateOptions(args), remeasure: trustSingle ? void 0 : perfRemeasure(options, spawn), trustSingle } : void 0;
8024
+ const handed = options.json === void 0 ? void 0 : readPerfRun(options, spawn);
8025
+ const recorded = handed?.ok === true ? handed.run : void 0;
8026
+ const gate = flagEnabled(args, "gate") ? {
8027
+ options: gateOptions(args),
8028
+ remeasure: trustSingle ? void 0 : perfRemeasure(options, spawn, ownPackageRoot(), recorded),
8029
+ trustSingle
8030
+ } : void 0;
7922
8031
  if (keepProfiles !== void 0 && gate?.remeasure === void 0) {
7923
8032
  io.err(PROFILE_DIR_UNUSED);
7924
8033
  }
@@ -7927,7 +8036,7 @@ function perfCommand(cwd, argv, io) {
7927
8036
  const minSeverity = minSeverityOf(args);
7928
8037
  const failOn = failOnOf(args);
7929
8038
  const codeQuality = flagValue(args, "code-quality");
7930
- return renderPerf(readPerfRun(options, spawn), profile, io, {
8039
+ return renderPerf(handed ?? readPerfRun(options, spawn), profile, io, {
7931
8040
  budgets: gateOptions(args),
7932
8041
  ...format === "json" || format === "markdown" ? { format } : {},
7933
8042
  ...gate === void 0 ? {} : { gate },
@@ -64,6 +64,8 @@ interface PerfFile {
64
64
  readonly setupFetch?: number;
65
65
  /** Failed attempts before a pass, summed over the file's tests. */
66
66
  readonly retries?: number;
67
+ /** Set only by `mergeRuns`, when the merged reports ran different configs: this file's, as `PerfRun.configFile`. */
68
+ readonly configFile?: string;
67
69
  }
68
70
  /** What the run was configured with, read from Vitest's resolved config rather than the config text. */
69
71
  interface PerfConfig {
@@ -108,6 +110,8 @@ interface PerfRun {
108
110
  readonly hung?: boolean;
109
111
  /** Milliseconds coverage took after the last file: generating the map, then writing the reports. */
110
112
  readonly coverage?: number;
113
+ /** The Vitest config file the run loaded, relative to `root` with `/` separators; absent without one. */
114
+ readonly configFile?: string;
111
115
  }
112
116
 
113
117
  interface PerfDiagnostic {
@@ -208,6 +212,12 @@ interface PerfVitest {
208
212
  readonly workersSpawned?: number;
209
213
  };
210
214
  readonly projects?: readonly PerfProject[];
215
+ /** Vite's resolved config holds the file Vitest loaded, on Vitest 4 and 5 alike; `configFile` is unset without one. */
216
+ readonly vite?: {
217
+ readonly config?: {
218
+ readonly configFile?: string | undefined;
219
+ };
220
+ };
211
221
  }
212
222
  declare class PerfReporter {
213
223
  #private;
@@ -1,4 +1,4 @@
1
- import { join, dirname, sep, basename } from 'node:path';
1
+ import { join, dirname, relative, sep, basename } from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { mkdirSync, writeFileSync, renameSync, rmSync, realpathSync, statSync } from 'node:fs';
4
4
 
@@ -39,7 +39,7 @@ var PERF_OUTPUT_ENV = "VITEST_AUTO_SPY_PERF_OUT";
39
39
  var PERF_PROFILE_ENV = "VITEST_AUTO_SPY_PERF_PROFILE";
40
40
  var PERF_ISOLATE_ENV = "VITEST_AUTO_SPY_PERF_ISOLATE";
41
41
  var AUTO_SPY_META_KEY = "autoSpyMs";
42
- var PERF_FORMAT_VERSION = 5;
42
+ var PERF_FORMAT_VERSION = 6;
43
43
  var CASE_FLOOR_MS = 100;
44
44
  var CASES_PER_FILE = 5;
45
45
 
@@ -208,6 +208,10 @@ function configOf(vitest) {
208
208
  ...provided === void 0 ? {} : { provided: Object.keys(provided).filter((key) => provided[key] === true) }
209
209
  };
210
210
  }
211
+ function configFileOf(vitest) {
212
+ const file = vitest.vite?.config?.configFile;
213
+ return file === void 0 ? void 0 : toPosix(relative(vitest.config.root, file));
214
+ }
211
215
  function startupOf(vitest) {
212
216
  const { startupTime, workersSpawned } = vitest.state;
213
217
  return startupTime === void 0 || workersSpawned === void 0 ? void 0 : { ms: startupTime, workers: workersSpawned };
@@ -232,9 +236,11 @@ var PerfReporter = class {
232
236
  #ended;
233
237
  #coverage;
234
238
  #hung = false;
239
+ #configFile;
235
240
  onInit(vitest) {
236
241
  this.#vitest = vitest;
237
242
  this.#start = Date.now();
243
+ this.#configFile = configFileOf(vitest);
238
244
  if (target() === void 0 && !profiling()) {
239
245
  return;
240
246
  }
@@ -349,7 +355,8 @@ var PerfReporter = class {
349
355
  ...partial ? { partial: true } : {},
350
356
  ...end === void 0 ? {} : { end },
351
357
  ...this.#hung ? { hung: true } : {},
352
- ...this.#coverage === void 0 ? {} : { coverage: this.#coverage }
358
+ ...this.#coverage === void 0 ? {} : { coverage: this.#coverage },
359
+ ...this.#configFile === void 0 ? {} : { configFile: this.#configFile }
353
360
  };
354
361
  }
355
362
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitest-auto-spy",
3
- "version": "5.53.0",
3
+ "version": "5.55.0",
4
4
  "description": "Auto-generate fully-typed test spies from a class — across Vitest, Bun, node:test and Rstest, with Angular/NestJS/React/Vue/Svelte recipes. Drop-in replacement for jest-auto-spies and jasmine-auto-spies.",
5
5
  "keywords": [
6
6
  "auto-mock",