vitest 4.0.0-beta.9 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE.md +86 -102
  2. package/browser/context.d.ts +7 -0
  3. package/browser/context.js +20 -0
  4. package/dist/browser.d.ts +24 -7
  5. package/dist/browser.js +15 -5
  6. package/dist/chunks/{base.CA5N8Af0.js → base.BYPMk0VN.js} +36 -36
  7. package/dist/chunks/{benchmark.CJUa-Hsa.js → benchmark.DHKMYAts.js} +2 -2
  8. package/dist/chunks/{browser.d.DtfyY9yS.d.ts → browser.d.B9iJzZyn.d.ts} +3 -3
  9. package/dist/chunks/{cac.Dt7e1TIu.js → cac.DrF4Gm0S.js} +47 -73
  10. package/dist/chunks/{cli-api.eAzsLIxz.js → cli-api.W2Q-JQoO.js} +1524 -296
  11. package/dist/chunks/{config.d.DacWrqWe.d.ts → config.d.u2CUDWwS.d.ts} +5 -19
  12. package/dist/chunks/{console.7h5kHUIf.js → console.CTJL2nuH.js} +4 -6
  13. package/dist/chunks/{coverage.CDRAMTt7.js → coverage.FU3w4IrQ.js} +125 -1108
  14. package/dist/chunks/{creator.KEg6n5IC.js → creator.DucAaYBz.js} +10 -37
  15. package/dist/chunks/{defaults.CXFFjsi8.js → defaults.BOqNVLsY.js} +0 -1
  16. package/dist/chunks/environment.d.CrsxCzP1.d.ts +29 -0
  17. package/dist/chunks/evaluatedModules.Dg1zASAC.js +17 -0
  18. package/dist/chunks/{global.d.K6uBQHzY.d.ts → global.d.BgJSTpgQ.d.ts} +2 -17
  19. package/dist/chunks/{globals.CJrTTbxC.js → globals.BGT_RUsD.js} +11 -7
  20. package/dist/chunks/{index.BjKEiSn0.js → index.BdSLhLDZ.js} +3 -3
  21. package/dist/chunks/{index.DfviD7lX.js → index.CbWINfS7.js} +49 -21
  22. package/dist/chunks/{index.BIP7prJq.js → index.CcRZ6fUh.js} +1493 -114
  23. package/dist/chunks/{index.X0nbfr6-.js → index.Dc3xnDvT.js} +48 -289
  24. package/dist/chunks/{index.C832ioot.js → index.RwjEGCQ0.js} +4 -4
  25. package/dist/chunks/init-forks.WglB-sfY.js +54 -0
  26. package/dist/chunks/init-threads.Czek6eA5.js +17 -0
  27. package/dist/chunks/init.94FWN9pW.js +213 -0
  28. package/dist/chunks/{inspector.CvQD-Nie.js → inspector.DLZxSeU3.js} +2 -6
  29. package/dist/chunks/{moduleRunner.d.DxTLreRD.d.ts → moduleRunner.d.YtNsMIoJ.d.ts} +9 -14
  30. package/dist/chunks/{node.CyipiPvJ.js → node.BwAWWjHZ.js} +3 -4
  31. package/dist/chunks/{plugin.d.CIk0YiKb.d.ts → plugin.d.DQU1R5px.d.ts} +1 -1
  32. package/dist/chunks/{reporters.d.DmP-iHLr.d.ts → reporters.d.BMKt7f6I.d.ts} +1064 -1021
  33. package/dist/chunks/{resolveSnapshotEnvironment.Bvv2zr69.js → resolveSnapshotEnvironment.DJJKMKxb.js} +7 -8
  34. package/dist/chunks/{rpc.BKr6mtxz.js → rpc.cD77ENhU.js} +13 -14
  35. package/dist/chunks/{setup-common.B7I37Tji.js → setup-common.DR1sucx6.js} +6 -6
  36. package/dist/chunks/{startModuleRunner.BDRvKSdz.js → startModuleRunner.iF1E9Bt4.js} +126 -110
  37. package/dist/chunks/{test.BAlBebnP.js → test.C3RPt8JR.js} +7 -7
  38. package/dist/chunks/{utils.D2R2NiOH.js → utils.CG9h5ccR.js} +2 -5
  39. package/dist/chunks/{vi.BB37KeLx.js → vi.BZvkKVkM.js} +61 -164
  40. package/dist/chunks/{vm.CjLTDaST.js → vm.CuMWYx_F.js} +20 -29
  41. package/dist/chunks/{worker.d.B2r4Ln6p.d.ts → worker.d.BFk-vvBU.d.ts} +42 -6
  42. package/dist/cli.js +12 -11
  43. package/dist/config.cjs +0 -1
  44. package/dist/config.d.ts +11 -13
  45. package/dist/config.js +1 -1
  46. package/dist/coverage.d.ts +7 -6
  47. package/dist/coverage.js +3 -14
  48. package/dist/environments.d.ts +3 -6
  49. package/dist/environments.js +1 -1
  50. package/dist/index.d.ts +20 -25
  51. package/dist/index.js +11 -7
  52. package/dist/module-evaluator.d.ts +5 -4
  53. package/dist/module-evaluator.js +11 -13
  54. package/dist/module-runner.js +5 -5
  55. package/dist/node.d.ts +82 -25
  56. package/dist/node.js +23 -20
  57. package/dist/reporters.d.ts +10 -9
  58. package/dist/reporters.js +12 -11
  59. package/dist/runners.d.ts +1 -1
  60. package/dist/runners.js +9 -7
  61. package/dist/snapshot.js +3 -3
  62. package/dist/suite.js +4 -3
  63. package/dist/worker.d.ts +26 -0
  64. package/dist/worker.js +45 -165
  65. package/dist/workers/forks.js +26 -43
  66. package/dist/workers/runVmTests.js +16 -12
  67. package/dist/workers/threads.js +26 -31
  68. package/dist/workers/vmForks.js +26 -39
  69. package/dist/workers/vmThreads.js +26 -29
  70. package/package.json +48 -32
  71. package/worker.d.ts +1 -0
  72. package/browser.d.ts +0 -1
  73. package/dist/chunks/environment.d.2fYMoz3o.d.ts +0 -66
  74. package/dist/chunks/moduleTransport.I-bgQy0S.js +0 -19
  75. package/dist/chunks/resolver.Bx6lE0iq.js +0 -119
  76. package/dist/chunks/typechecker.DB-fIMaH.js +0 -805
  77. package/dist/chunks/utils.C2YI6McM.js +0 -52
  78. package/dist/chunks/worker.d.DJ6qxO2w.d.ts +0 -8
  79. package/dist/workers.d.ts +0 -38
  80. package/dist/workers.js +0 -49
  81. package/execute.d.ts +0 -1
  82. package/utils.d.ts +0 -1
  83. package/workers.d.ts +0 -1
@@ -1,18 +1,19 @@
1
- import fs, { promises, existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
2
- import { relative, resolve, dirname, extname, normalize, join, basename, isAbsolute } from 'pathe';
1
+ import fs, { promises as promises$1, existsSync, mkdirSync, readFileSync, statSync, readdirSync, writeFileSync } from 'node:fs';
2
+ import { relative, resolve, dirname, join, extname, normalize, basename, isAbsolute } from 'pathe';
3
3
  import { C as CoverageProviderMap } from './coverage.D_JHT54q.js';
4
4
  import path, { resolve as resolve$1 } from 'node:path';
5
- import { noop, createDefer, slash, highlight, toArray, cleanUrl, deepMerge, KNOWN_ASSET_RE, nanoid, deepClone, isPrimitive, notNullish } from '@vitest/utils';
6
- import { f as findUp, p as prompt } from './index.X0nbfr6-.js';
5
+ import { noop, createDefer, slash, isExternalUrl, unwrapId, nanoid, withTrailingSlash, cleanUrl, wrapId, toArray, deepMerge, deepClone, isPrimitive, notNullish } from '@vitest/utils/helpers';
6
+ import { a as any, p as prompt } from './index.Dc3xnDvT.js';
7
7
  import * as vite from 'vite';
8
- import { parseAst, searchForWorkspaceRoot, version, mergeConfig, createServer } from 'vite';
8
+ import { parseAst, fetchModule, version, searchForWorkspaceRoot, mergeConfig, createServer } from 'vite';
9
9
  import { A as API_PATH, c as configFiles, d as defaultBrowserPort, a as defaultPort } from './constants.D_Q9UYh-.js';
10
+ import * as nodeos from 'node:os';
10
11
  import nodeos__default, { tmpdir } from 'node:os';
11
- import { generateHash as generateHash$1, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, generateFileHash, limitConcurrency, createFileTask as createFileTask$1, hasFailed, getTasks, getTests } from '@vitest/runner/utils';
12
+ import { generateHash as generateHash$1, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, hasFailed, generateFileHash, limitConcurrency, createFileTask as createFileTask$1, getTasks, isTestCase } from '@vitest/runner/utils';
12
13
  import { SnapshotManager } from '@vitest/snapshot/manager';
13
- import { v as version$1 } from './cac.Dt7e1TIu.js';
14
+ import { v as version$1 } from './cac.DrF4Gm0S.js';
14
15
  import { c as createBirpc } from './index.Bgo3tNWt.js';
15
- import { p as parse, s as stringify, d as printError, f as formatProjectName, w as withLabel, e as errorBanner, h as divider, i as generateCodeFrame, R as ReportersMap, B as BlobReporter, r as readBlobs, H as HangingProcessReporter } from './index.BIP7prJq.js';
16
+ import { p as parse, d as stringify, e as TraceMap, o as originalPositionFor, h as ancestor, i as printError, f as formatProjectName, w as withLabel, j as errorBanner, k as divider, l as Typechecker, m as generateCodeFrame, n as createDefinesScript, R as ReportersMap, B as BlobReporter, r as readBlobs, q as convertTasksToEvents, H as HangingProcessReporter } from './index.CcRZ6fUh.js';
16
17
  import require$$0$3 from 'events';
17
18
  import require$$1$1 from 'https';
18
19
  import require$$2 from 'http';
@@ -25,29 +26,35 @@ import require$$0 from 'zlib';
25
26
  import require$$0$1 from 'buffer';
26
27
  import { g as getDefaultExportFromCjs } from './_commonjsHelpers.BFTU3MAI.js';
27
28
  import crypto, { createHash } from 'node:crypto';
28
- import { distDir, rootDir } from '../path.js';
29
- import { h as hash, d as createFetchModuleFunction, n as normalizeResolvedIdToUrl, R as RandomSequencer, i as isPackageExists, g as getFilePoolName, e as isBrowserEnabled, r as resolveConfig, f as groupBy, j as getCoverageProvider, k as createPool, w as wildcardPatternToRegExp, a as resolveApiServerConfig, s as stdout } from './coverage.CDRAMTt7.js';
30
- import { b as ancestor, c as createDefinesScript, d as convertTasksToEvents } from './typechecker.DB-fIMaH.js';
31
- import { TraceMap, originalPositionFor, parseErrorStacktrace } from '@vitest/utils/source-map';
29
+ import { rootDir, distDir } from '../path.js';
32
30
  import createDebug from 'debug';
31
+ import { h as hash, R as RandomSequencer, i as isPackageExists, c as isBrowserEnabled, r as resolveConfig, g as getCoverageProvider, a as resolveApiServerConfig } from './coverage.FU3w4IrQ.js';
32
+ import { readFile, writeFile, rename, stat, unlink, rm, mkdir, copyFile } from 'node:fs/promises';
33
+ import { builtinModules, createRequire, isBuiltin } from 'node:module';
33
34
  import { VitestModuleEvaluator } from '#module-evaluator';
34
35
  import { ModuleRunner } from 'vite/module-runner';
35
36
  import { Console } from 'node:console';
36
37
  import c from 'tinyrainbow';
37
- import { createRequire, builtinModules, isBuiltin } from 'node:module';
38
- import url, { pathToFileURL } from 'node:url';
38
+ import { highlight } from '@vitest/utils/highlight';
39
+ import url, { fileURLToPath, pathToFileURL } from 'node:url';
39
40
  import { i as isTTY, a as isWindows } from './env.D4Lgay0q.js';
40
- import { rm, mkdir, copyFile } from 'node:fs/promises';
41
+ import { isatty } from 'node:tty';
42
+ import EventEmitter$1, { EventEmitter } from 'node:events';
43
+ import { fork } from 'node:child_process';
44
+ import v8 from 'node:v8';
45
+ import { Worker } from 'node:worker_threads';
41
46
  import pm from 'picomatch';
42
47
  import { glob, isDynamicPattern } from 'tinyglobby';
43
48
  import MagicString from 'magic-string';
44
49
  import { hoistMocksPlugin, automockPlugin } from '@vitest/mocker/node';
45
- import { c as configDefaults } from './defaults.CXFFjsi8.js';
46
- import { f as findNearestPackageData } from './resolver.Bx6lE0iq.js';
50
+ import { c as configDefaults } from './defaults.BOqNVLsY.js';
51
+ import { KNOWN_ASSET_RE } from '@vitest/utils/constants';
52
+ import { findNearestPackageData } from '@vitest/utils/resolver';
47
53
  import * as esModuleLexer from 'es-module-lexer';
48
- import { a as BenchmarkReportsMap } from './index.BjKEiSn0.js';
54
+ import { a as BenchmarkReportsMap } from './index.BdSLhLDZ.js';
49
55
  import assert$1 from 'node:assert';
50
- import { serializeError as serializeError$1 } from '@vitest/utils/error';
56
+ import { serializeValue } from '@vitest/utils/serialize';
57
+ import { parseErrorStacktrace } from '@vitest/utils/source-map';
51
58
  import readline from 'node:readline';
52
59
  import { stripVTControlCharacters } from 'node:util';
53
60
 
@@ -5020,7 +5027,7 @@ var WebSocketServer = /*@__PURE__*/getDefaultExportFromCjs(websocketServerExport
5020
5027
  async function getModuleGraph(ctx, projectName, id, browser = false) {
5021
5028
  const graph = {}, externalized = /* @__PURE__ */ new Set(), inlined = /* @__PURE__ */ new Set(), project = ctx.getProjectByName(projectName);
5022
5029
  async function get(mod, seen = /* @__PURE__ */ new Map()) {
5023
- if (!mod || !mod.id || mod.id === "\0@vitest/browser/context") return;
5030
+ if (!mod || !mod.id || mod.id === "\0vitest/browser") return;
5024
5031
  if (seen.has(mod)) return seen.get(mod);
5025
5032
  let id = clearId(mod.id);
5026
5033
  seen.set(mod, id);
@@ -5081,8 +5088,8 @@ catch {}
5081
5088
  }
5082
5089
 
5083
5090
  function setup(ctx, _server) {
5084
- const wss = new WebSocketServer({ noServer: true }), clients = /* @__PURE__ */ new Map(), server = _server || ctx.vite;
5085
- server.httpServer?.on("upgrade", (request, socket, head) => {
5091
+ const wss = new WebSocketServer({ noServer: true }), clients = /* @__PURE__ */ new Map();
5092
+ (_server || ctx.vite).httpServer?.on("upgrade", (request, socket, head) => {
5086
5093
  if (!request.url) return;
5087
5094
  const { pathname } = new URL(request.url, "http://localhost");
5088
5095
  if (pathname === API_PATH) {
@@ -5107,11 +5114,11 @@ function setup(ctx, _server) {
5107
5114
  return ctx.state.getPaths();
5108
5115
  },
5109
5116
  async readTestFile(id) {
5110
- return !ctx.state.filesMap.has(id) || !existsSync(id) ? null : promises.readFile(id, "utf-8");
5117
+ return !ctx.state.filesMap.has(id) || !existsSync(id) ? null : promises$1.readFile(id, "utf-8");
5111
5118
  },
5112
5119
  async saveTestFile(id, content) {
5113
5120
  if (!ctx.state.filesMap.has(id) || !existsSync(id)) throw new Error(`Test file "${id}" was not registered, so it cannot be updated using the API.`);
5114
- return promises.writeFile(id, content, "utf-8");
5121
+ return promises$1.writeFile(id, content, "utf-8");
5115
5122
  },
5116
5123
  async rerun(files, resetTestNamePattern) {
5117
5124
  await ctx.rerunFiles(files, void 0, true, resetTestNamePattern);
@@ -5132,7 +5139,7 @@ function setup(ctx, _server) {
5132
5139
  const project = ctx.getProjectByName(projectName), result = browser ? await project.browser.vite.transformRequest(id) : await project.vite.transformRequest(id);
5133
5140
  if (result) {
5134
5141
  try {
5135
- result.source = result.source || await promises.readFile(id, "utf-8");
5142
+ result.source = result.source || await promises$1.readFile(id, "utf-8");
5136
5143
  } catch {}
5137
5144
  return result;
5138
5145
  }
@@ -5148,8 +5155,7 @@ function setup(ctx, _server) {
5148
5155
  return ctx.state.getUnhandledErrors();
5149
5156
  },
5150
5157
  async getTestFiles() {
5151
- const spec = await ctx.globTestSpecifications();
5152
- return spec.map((spec) => [
5158
+ return (await ctx.globTestSpecifications()).map((spec) => [
5153
5159
  {
5154
5160
  name: spec.project.config.name,
5155
5161
  root: spec.project.config.root
@@ -5229,16 +5235,39 @@ var setup$1 = /*#__PURE__*/Object.freeze({
5229
5235
  setup: setup
5230
5236
  });
5231
5237
 
5238
+ function groupBy(collection, iteratee) {
5239
+ return collection.reduce((acc, item) => {
5240
+ const key = iteratee(item);
5241
+ return acc[key] ||= [], acc[key].push(item), acc;
5242
+ }, {});
5243
+ }
5244
+ function stdout() {
5245
+ // @ts-expect-error Node.js maps process.stdout to console._stdout
5246
+ // eslint-disable-next-line no-console
5247
+ return console._stdout || process.stdout;
5248
+ }
5249
+ function escapeRegExp(s) {
5250
+ // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
5251
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5252
+ }
5253
+ function wildcardPatternToRegExp(pattern) {
5254
+ const negated = pattern[0] === "!";
5255
+ if (negated) pattern = pattern.slice(1);
5256
+ let regexp = `${pattern.split("*").map(escapeRegExp).join(".*")}$`;
5257
+ if (negated) regexp = `(?!${regexp})`;
5258
+ return new RegExp(`^${regexp}`, "i");
5259
+ }
5260
+
5232
5261
  function createDebugger(namespace) {
5233
5262
  const debug = createDebug(namespace);
5234
5263
  if (debug.enabled) return debug;
5235
5264
  }
5236
5265
 
5237
- const debug = createDebugger("vitest:ast-collect-info"), verbose = createDebugger("vitest:ast-collect-verbose");
5266
+ const debug$1 = createDebugger("vitest:ast-collect-info"), verbose = createDebugger("vitest:ast-collect-verbose");
5238
5267
  function astParseFile(filepath, code) {
5239
5268
  const ast = parseAst(code);
5240
5269
  if (verbose) verbose("Collecting", filepath, code);
5241
- else debug?.("Collecting", filepath);
5270
+ else debug$1?.("Collecting", filepath);
5242
5271
  const definitions = [], getName = (callee) => {
5243
5272
  if (!callee) return null;
5244
5273
  if (callee.type === "Identifier") return callee.name;
@@ -5302,7 +5331,7 @@ function astParseFile(filepath, code) {
5302
5331
  const property = callee.tag?.property?.name;
5303
5332
  isDynamicEach = property === "each" || property === "for";
5304
5333
  }
5305
- debug?.("Found", name, message, `(${mode})`), definitions.push({
5334
+ debug$1?.("Found", name, message, `(${mode})`), definitions.push({
5306
5335
  start,
5307
5336
  end,
5308
5337
  name: message,
@@ -5338,17 +5367,13 @@ function createFailedFileTask(project, filepath, error) {
5338
5367
  return file.file = file, file;
5339
5368
  }
5340
5369
  function serializeError(ctx, error) {
5341
- if ("errors" in error && "pluginCode" in error) {
5342
- const errors = error.errors.map((e) => {
5343
- return {
5344
- name: error.name,
5345
- message: e.text,
5346
- stack: e.location ? `${error.name}: ${e.text}\n at ${relative(ctx.config.root, e.location.file)}:${e.location.line}:${e.location.column}` : ""
5347
- };
5348
- });
5349
- return errors;
5350
- }
5351
- return [{
5370
+ return "errors" in error && "pluginCode" in error ? error.errors.map((e) => {
5371
+ return {
5372
+ name: error.name,
5373
+ message: e.text,
5374
+ stack: e.location ? `${error.name}: ${e.text}\n at ${relative(ctx.config.root, e.location.file)}:${e.location.line}:${e.location.column}` : ""
5375
+ };
5376
+ }) : [{
5352
5377
  name: error.name,
5353
5378
  stack: error.stack,
5354
5379
  message: error.message
@@ -5390,8 +5415,8 @@ function createFileTask(testFilepath, code, requestMap, options) {
5390
5415
  column: processedLocation.column
5391
5416
  });
5392
5417
  if (originalLocation.column != null) verbose?.(`Found location for`, definition.type, definition.name, `${processedLocation.line}:${processedLocation.column}`, "->", `${originalLocation.line}:${originalLocation.column}`), location = originalLocation;
5393
- else debug?.("Cannot find original location for", definition.type, definition.name, `${processedLocation.column}:${processedLocation.line}`);
5394
- } else debug?.("Cannot find original location for", definition.type, definition.name, `${definition.start}`);
5418
+ else debug$1?.("Cannot find original location for", definition.type, definition.name, `${processedLocation.column}:${processedLocation.line}`);
5419
+ } else debug$1?.("Cannot find original location for", definition.type, definition.name, `${definition.start}`);
5395
5420
  if (definition.type === "suite") {
5396
5421
  const task = {
5397
5422
  type: definition.type,
@@ -5446,7 +5471,7 @@ async function astCollectTests(project, filepath) {
5446
5471
  allowOnly: project.config.allowOnly,
5447
5472
  testNamePattern: project.config.testNamePattern,
5448
5473
  pool: project.browser ? "browser" : project.config.pool
5449
- }) : (debug?.("Cannot parse", testFilepath, "(vite didn't return anything)"), createFailedFileTask(project, filepath, /* @__PURE__ */ new Error(`Failed to parse ${testFilepath}. Vite didn't return anything.`)));
5474
+ }) : (debug$1?.("Cannot parse", testFilepath, "(vite didn't return anything)"), createFailedFileTask(project, filepath, /* @__PURE__ */ new Error(`Failed to parse ${testFilepath}. Vite didn't return anything.`)));
5450
5475
  }
5451
5476
  async function transformSSR(project, filepath) {
5452
5477
  const request = await project.vite.transformRequest(filepath, { ssr: false });
@@ -5611,18 +5636,200 @@ class VitestCache {
5611
5636
  }
5612
5637
  }
5613
5638
 
5639
+ const created = /* @__PURE__ */ new Set(), promises = /* @__PURE__ */ new Map();
5640
+ function createFetchModuleFunction(resolver, tmpDir = join(tmpdir(), nanoid()), dump) {
5641
+ return async (url, importer, environment, cacheFs, options) => {
5642
+ // We are copy pasting Vite's externalization logic from `fetchModule` because
5643
+ // we instead rely on our own `shouldExternalize` method because Vite
5644
+ // doesn't support `resolve.external` in non SSR environments (jsdom/happy-dom)
5645
+ if (url.startsWith("data:")) return {
5646
+ externalize: url,
5647
+ type: "builtin"
5648
+ };
5649
+ if (url === "/@vite/client" || url === "@vite/client")
5650
+ // this will be stubbed
5651
+ return {
5652
+ externalize: "/@vite/client",
5653
+ type: "module"
5654
+ };
5655
+ const isFileUrl = url.startsWith("file://");
5656
+ if (isExternalUrl(url) && !isFileUrl) return {
5657
+ externalize: url,
5658
+ type: "network"
5659
+ };
5660
+ // Vite does the same in `fetchModule`, but we want to externalize modules ourselves,
5661
+ // so we do this first to resolve the module and check its `id`. The next call of
5662
+ // `ensureEntryFromUrl` inside `fetchModule` is cached and should take no time
5663
+ // This also makes it so externalized modules are inside the module graph.
5664
+ const moduleGraphModule = await environment.moduleGraph.ensureEntryFromUrl(unwrapId(url)), cached = !!moduleGraphModule.transformResult;
5665
+ // if url is already cached, we can just confirm it's also cached on the server
5666
+ if (options?.cached && cached) return { cache: true };
5667
+ if (moduleGraphModule.id) {
5668
+ const externalize = await resolver.shouldExternalize(moduleGraphModule.id);
5669
+ if (externalize) return {
5670
+ externalize,
5671
+ type: "module"
5672
+ };
5673
+ }
5674
+ let moduleRunnerModule;
5675
+ if (dump?.dumpFolder && dump.readFromDump) {
5676
+ const path = resolve(dump?.dumpFolder, url.replace(/[^\w+]/g, "-"));
5677
+ if (existsSync(path)) {
5678
+ const code = await readFile(path, "utf-8"), matchIndex = code.lastIndexOf("\n//");
5679
+ if (matchIndex !== -1) {
5680
+ const { id, file } = JSON.parse(code.slice(matchIndex + 4));
5681
+ moduleRunnerModule = {
5682
+ code,
5683
+ id,
5684
+ url,
5685
+ file,
5686
+ invalidate: false
5687
+ };
5688
+ }
5689
+ }
5690
+ }
5691
+ if (!moduleRunnerModule) moduleRunnerModule = await fetchModule(environment, url, importer, {
5692
+ ...options,
5693
+ inlineSourceMap: false
5694
+ }).catch(handleRollupError);
5695
+ const result = processResultSource(environment, moduleRunnerModule);
5696
+ if (dump?.dumpFolder && "code" in result) {
5697
+ const path = resolve(dump?.dumpFolder, result.url.replace(/[^\w+]/g, "-"));
5698
+ await writeFile(path, `${result.code}\n// ${JSON.stringify({
5699
+ id: result.id,
5700
+ file: result.file
5701
+ })}`, "utf-8");
5702
+ }
5703
+ if (!cacheFs || !("code" in result)) return result;
5704
+ const code = result.code, transformResult = result.transformResult;
5705
+ if (!transformResult) throw new Error(`"transformResult" in not defined. This is a bug in Vitest.`);
5706
+ // to avoid serialising large chunks of code,
5707
+ // we store them in a tmp file and read in the test thread
5708
+ if ("_vitestTmp" in transformResult) return getCachedResult(result, Reflect.get(transformResult, "_vitestTmp"));
5709
+ const dir = join(tmpDir, environment.name), name = hash("sha1", result.id, "hex"), tmp = join(dir, name);
5710
+ if (!created.has(dir)) mkdirSync(dir, { recursive: true }), created.add(dir);
5711
+ return promises.has(tmp) ? (await promises.get(tmp), Reflect.set(transformResult, "_vitestTmp", tmp), getCachedResult(result, tmp)) : (promises.set(tmp, atomicWriteFile(tmp, code).catch(() => writeFile(tmp, code, "utf-8")).finally(() => promises.delete(tmp))), await promises.get(tmp), getCachedResult(result, tmp));
5712
+ };
5713
+ }
5714
+ let SOURCEMAPPING_URL = "sourceMa";
5715
+ SOURCEMAPPING_URL += "ppingURL";
5716
+ const MODULE_RUNNER_SOURCEMAPPING_SOURCE = "//# sourceMappingSource=vite-generated";
5717
+ function processResultSource(environment, result) {
5718
+ if (!("code" in result)) return result;
5719
+ const node = environment.moduleGraph.getModuleById(result.id);
5720
+ if (node?.transformResult)
5721
+ // this also overrides node.transformResult.code which is also what the module
5722
+ // runner does under the hood by default (we disable source maps inlining)
5723
+ inlineSourceMap(node.transformResult);
5724
+ return {
5725
+ ...result,
5726
+ code: node?.transformResult?.code || result.code,
5727
+ transformResult: node?.transformResult
5728
+ };
5729
+ }
5730
+ const OTHER_SOURCE_MAP_REGEXP = new RegExp(`//# ${SOURCEMAPPING_URL}=data:application/json[^,]+base64,([A-Za-z0-9+/=]+)$`, "gm");
5731
+ // we have to inline the source map ourselves, because
5732
+ // - we don't need //# sourceURL since we are running code in VM
5733
+ // - important in stack traces and the V8 coverage
5734
+ // - we need to inject an empty line for --inspect-brk
5735
+ function inlineSourceMap(result) {
5736
+ const map = result.map;
5737
+ let code = result.code;
5738
+ if (!map || !("version" in map) || code.includes(MODULE_RUNNER_SOURCEMAPPING_SOURCE)) return result;
5739
+ if (OTHER_SOURCE_MAP_REGEXP.lastIndex = 0, OTHER_SOURCE_MAP_REGEXP.test(code)) code = code.replace(OTHER_SOURCE_MAP_REGEXP, "");
5740
+ const sourceMap = { ...map };
5741
+ // If the first line is not present on source maps, add simple 1:1 mapping ([0,0,0,0], [1,0,0,0])
5742
+ // so that debuggers can be set to break on first line
5743
+ if (sourceMap.mappings[0] === ";") sourceMap.mappings = `AAAA,CAAA${sourceMap.mappings}`;
5744
+ return result.code = `${code.trimEnd()}\n${MODULE_RUNNER_SOURCEMAPPING_SOURCE}\n//# ${SOURCEMAPPING_URL}=${genSourceMapUrl(sourceMap)}\n`, result;
5745
+ }
5746
+ function genSourceMapUrl(map) {
5747
+ if (typeof map !== "string") map = JSON.stringify(map);
5748
+ return `data:application/json;base64,${Buffer.from(map).toString("base64")}`;
5749
+ }
5750
+ function getCachedResult(result, tmp) {
5751
+ return {
5752
+ cached: true,
5753
+ file: result.file,
5754
+ id: result.id,
5755
+ tmp,
5756
+ url: result.url,
5757
+ invalidate: result.invalidate
5758
+ };
5759
+ }
5760
+ // serialize rollup error on server to preserve details as a test error
5761
+ function handleRollupError(e) {
5762
+ throw e instanceof Error && ("plugin" in e || "frame" in e || "id" in e) ? {
5763
+ name: e.name,
5764
+ message: e.message,
5765
+ stack: e.stack,
5766
+ cause: e.cause,
5767
+ __vitest_rollup_error__: {
5768
+ plugin: e.plugin,
5769
+ id: e.id,
5770
+ loc: e.loc,
5771
+ frame: e.frame
5772
+ }
5773
+ } : e;
5774
+ }
5775
+ /**
5776
+ * Performs an atomic write operation using the write-then-rename pattern.
5777
+ *
5778
+ * Why we need this:
5779
+ * - Ensures file integrity by never leaving partially written files on disk
5780
+ * - Prevents other processes from reading incomplete data during writes
5781
+ * - Particularly important for test files where incomplete writes could cause test failures
5782
+ *
5783
+ * The implementation writes to a temporary file first, then renames it to the target path.
5784
+ * This rename operation is atomic on most filesystems (including POSIX-compliant ones),
5785
+ * guaranteeing that other processes will only ever see the complete file.
5786
+ *
5787
+ * Added in https://github.com/vitest-dev/vitest/pull/7531
5788
+ */
5789
+ async function atomicWriteFile(realFilePath, data) {
5790
+ const dir = dirname(realFilePath), tmpFilePath = join(dir, `.tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`);
5791
+ try {
5792
+ await writeFile(tmpFilePath, data, "utf-8"), await rename(tmpFilePath, realFilePath);
5793
+ } finally {
5794
+ try {
5795
+ if (await stat(tmpFilePath)) await unlink(tmpFilePath);
5796
+ } catch {}
5797
+ }
5798
+ }
5799
+
5800
+ // this is copy pasted from vite
5801
+ function normalizeResolvedIdToUrl(environment, resolvedId) {
5802
+ const root = environment.config.root, depsOptimizer = environment.depsOptimizer;
5803
+ let url;
5804
+ // normalize all imports into resolved URLs
5805
+ // e.g. `import 'foo'` -> `import '/@fs/.../node_modules/foo/index.js'`
5806
+ if (resolvedId.startsWith(withTrailingSlash(root)))
5807
+ // in root: infer short absolute path from root
5808
+ url = resolvedId.slice(root.length);
5809
+ else if (depsOptimizer?.isOptimizedDepFile(resolvedId) || resolvedId !== "/@react-refresh" && path.isAbsolute(resolvedId) && existsSync(cleanUrl(resolvedId)))
5810
+ // an optimized deps may not yet exists in the filesystem, or
5811
+ // a regular file exists but is out of root: rewrite to absolute /@fs/ paths
5812
+ url = path.posix.join("/@fs/", resolvedId);
5813
+ else url = resolvedId;
5814
+ // if the resolved id is not a valid browser import specifier,
5815
+ // prefix it to make it valid. We will strip this before feeding it
5816
+ // back into the transform pipeline
5817
+ if (url[0] !== "." && url[0] !== "/") url = wrapId(resolvedId);
5818
+ return url;
5819
+ }
5820
+
5821
+ const nodeBuiltins = builtinModules.filter((id) => !id.includes(":"));
5614
5822
  class ServerModuleRunner extends ModuleRunner {
5615
- constructor(environment, resolver, config) {
5616
- const fetchModule = createFetchModuleFunction(resolver, false);
5823
+ constructor(environment, fetcher, config) {
5617
5824
  super({
5618
5825
  hmr: false,
5619
- sourcemapInterceptor: "node",
5620
5826
  transport: { async invoke(event) {
5621
5827
  if (event.type !== "custom") throw new Error(`Vitest Module Runner doesn't support Vite HMR events.`);
5622
- const { data } = event.data;
5828
+ const { name, data } = event.data;
5829
+ if (name === "getBuiltins") return { result: [...nodeBuiltins, /^node:/] };
5830
+ if (name !== "fetchModule") return { error: /* @__PURE__ */ new Error(`Unknown method: ${name}. Expected "fetchModule".`) };
5623
5831
  try {
5624
- const result = await fetchModule(data[0], data[1], environment, data[2]);
5625
- return { result };
5832
+ return { result: await fetcher(data[0], data[1], environment, false, data[2]) };
5626
5833
  } catch (error) {
5627
5834
  return { error };
5628
5835
  }
@@ -5761,8 +5968,8 @@ class Logger {
5761
5968
  const projectsFilter = toArray(config.project);
5762
5969
  if (projectsFilter.length) this.console.error(c.dim("projects: ") + c.yellow(projectsFilter.join(comma)));
5763
5970
  this.ctx.projects.forEach((project) => {
5764
- const config = project.config, printConfig = !project.isRootProject() && project.name;
5765
- if (printConfig) this.console.error(`\n${formatProjectName(project)}\n`);
5971
+ const config = project.config;
5972
+ if (!project.isRootProject() && project.name) this.console.error(`\n${formatProjectName(project)}\n`);
5766
5973
  if (config.include) this.console.error(c.dim("include: ") + c.yellow(config.include.join(comma)));
5767
5974
  if (config.exclude) this.console.error(c.dim("exclude: ") + c.yellow(config.exclude.join(comma)));
5768
5975
  if (config.typecheck.enabled) this.console.error(c.dim("typecheck include: ") + c.yellow(config.typecheck.include.join(comma))), this.console.error(c.dim("typecheck exclude: ") + c.yellow(config.typecheck.exclude.join(comma)));
@@ -5787,7 +5994,7 @@ class Logger {
5787
5994
  if (!project.browser) return;
5788
5995
  const resolvedUrls = project.browser.vite.resolvedUrls, origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
5789
5996
  if (!origin) return;
5790
- const output = project.isRootProject() ? "" : formatProjectName(project), provider = project.browser.provider.name, providerString = provider === "preview" ? "" : ` by ${c.reset(c.bold(provider))}`;
5997
+ const output = project.isRootProject() ? "" : formatProjectName(project), provider = project.browser.provider?.name, providerString = provider === "preview" ? "" : ` by ${c.reset(c.bold(provider))}`;
5791
5998
  this.log(c.dim(`${output}Browser runner started${providerString} ${c.dim("at")} ${c.blue(new URL("/__vitest_test__/", origin))}\n`));
5792
5999
  }
5793
6000
  printUnhandledErrors(errors) {
@@ -5819,7 +6026,8 @@ This might cause false positive tests. Resolve unhandled errors to make sure you
5819
6026
  // Interrupted signals don't set exit code automatically.
5820
6027
  // Use same exit code as node: https://nodejs.org/api/process.html#signal-events
5821
6028
  if (cleanup(), process.exitCode === void 0) process.exitCode = exitCode !== void 0 ? 128 + exitCode : Number(signal);
5822
- process.exit();
6029
+ // Timeout to flush stderr
6030
+ setTimeout(() => process.exit(), 1);
5823
6031
  };
5824
6032
  process.once("SIGINT", onExit), process.once("SIGTERM", onExit), process.once("exit", onExit), this.ctx.onClose(() => {
5825
6033
  process.off("SIGINT", onExit), process.off("SIGTERM", onExit), process.off("exit", onExit), cleanup();
@@ -5853,7 +6061,7 @@ class VitestPackageInstaller {
5853
6061
  }
5854
6062
  if (/* @__PURE__ */ isPackageExists(dependency, { paths: [root, __dirname] })) return true;
5855
6063
  if (process.stderr.write(c.red(`${c.inverse(c.red(" MISSING DEPENDENCY "))} Cannot find dependency '${dependency}'\n\n`)), !isTTY) return false;
5856
- const prompts = await import('./index.X0nbfr6-.js').then(function (n) { return n.i; }), { install } = await prompts.default({
6064
+ const { install } = await (await import('./index.Dc3xnDvT.js').then(function (n) { return n.i; })).default({
5857
6065
  type: "confirm",
5858
6066
  name: "install",
5859
6067
  message: c.reset(`Do you want to install ${c.green(dependency)}?`)
@@ -5866,12 +6074,1057 @@ class VitestPackageInstaller {
5866
6074
  }
5867
6075
  }
5868
6076
 
6077
+ function getDefaultThreadsCount(config) {
6078
+ const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
6079
+ return config.watch ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1);
6080
+ }
6081
+ function getWorkerMemoryLimit(config) {
6082
+ return config.vmMemoryLimit ? config.vmMemoryLimit : 1 / (config.maxWorkers ?? getDefaultThreadsCount(config));
6083
+ }
6084
+ /**
6085
+ * Converts a string representing an amount of memory to bytes.
6086
+ *
6087
+ * @param input The value to convert to bytes.
6088
+ * @param percentageReference The reference value to use when a '%' value is supplied.
6089
+ */
6090
+ function stringToBytes(input, percentageReference) {
6091
+ if (input === null || input === void 0) return input;
6092
+ if (typeof input === "string") if (Number.isNaN(Number.parseFloat(input.slice(-1)))) {
6093
+ let [, numericString, trailingChars] = input.match(/(.*?)([^0-9.-]+)$/) || [];
6094
+ if (trailingChars && numericString) {
6095
+ const numericValue = Number.parseFloat(numericString);
6096
+ switch (trailingChars = trailingChars.toLowerCase(), trailingChars) {
6097
+ case "%":
6098
+ input = numericValue / 100;
6099
+ break;
6100
+ case "kb":
6101
+ case "k": return numericValue * 1e3;
6102
+ case "kib": return numericValue * 1024;
6103
+ case "mb":
6104
+ case "m": return numericValue * 1e3 * 1e3;
6105
+ case "mib": return numericValue * 1024 * 1024;
6106
+ case "gb":
6107
+ case "g": return numericValue * 1e3 * 1e3 * 1e3;
6108
+ case "gib": return numericValue * 1024 * 1024 * 1024;
6109
+ }
6110
+ }
6111
+ } else input = Number.parseFloat(input);
6112
+ if (typeof input === "number") if (input <= 1 && input > 0) {
6113
+ if (percentageReference) return Math.floor(input * percentageReference);
6114
+ throw new Error("For a percentage based memory limit a percentageReference must be supplied");
6115
+ } else if (input > 1) return Math.floor(input);
6116
+ else throw new Error("Unexpected numerical input for \"memoryLimit\"");
6117
+ return null;
6118
+ }
6119
+
6120
+ async function getSpecificationsEnvironments(specifications) {
6121
+ const environments = /* @__PURE__ */ new WeakMap(), cache = /* @__PURE__ */ new Map();
6122
+ return await Promise.all(specifications.map(async (spec) => {
6123
+ const { moduleId: filepath, project } = spec;
6124
+ // reuse if projects have the same test files
6125
+ let code = cache.get(filepath);
6126
+ if (!code) code = await promises$1.readFile(filepath, "utf-8"), cache.set(filepath, code);
6127
+ // 1. Check for control comments in the file
6128
+ let env = code.match(/@(?:vitest|jest)-environment\s+([\w-]+)\b/)?.[1];
6129
+ // 2. Fallback to global env
6130
+ env ||= project.config.environment || "node";
6131
+ let envOptionsJson = code.match(/@(?:vitest|jest)-environment-options\s+(.+)/)?.[1];
6132
+ if (envOptionsJson?.endsWith("*/"))
6133
+ // Trim closing Docblock characters the above regex might have captured
6134
+ envOptionsJson = envOptionsJson.slice(0, -2);
6135
+ const envOptions = JSON.parse(envOptionsJson || "null"), environment = {
6136
+ name: env,
6137
+ options: envOptions ? { [env === "happy-dom" ? "happyDOM" : env]: envOptions } : null
6138
+ };
6139
+ environments.set(spec, environment);
6140
+ })), environments;
6141
+ }
6142
+
6143
+ const debug = createDebugger("vitest:browser:pool");
6144
+ function createBrowserPool(vitest) {
6145
+ const providers = /* @__PURE__ */ new Set(), numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length, maxThreadsCount = Math.min(12, numCpus - 1), threadsCount = vitest.config.watch ? Math.max(Math.floor(maxThreadsCount / 2), 1) : Math.max(maxThreadsCount, 1), projectPools = /* @__PURE__ */ new WeakMap(), ensurePool = (project) => {
6146
+ if (projectPools.has(project)) return projectPools.get(project);
6147
+ debug?.("creating pool for project %s", project.name);
6148
+ const resolvedUrls = project.browser.vite.resolvedUrls, origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
6149
+ if (!origin) throw new Error(`Can't find browser origin URL for project "${project.name}"`);
6150
+ const pool = new BrowserPool(project, {
6151
+ maxWorkers: getThreadsCount(project),
6152
+ origin
6153
+ });
6154
+ return projectPools.set(project, pool), vitest.onCancel(() => {
6155
+ pool.cancel();
6156
+ }), pool;
6157
+ }, runWorkspaceTests = async (method, specs) => {
6158
+ const groupedFiles = /* @__PURE__ */ new Map();
6159
+ for (const { project, moduleId, testLines } of specs) {
6160
+ const files = groupedFiles.get(project) || [];
6161
+ files.push({
6162
+ filepath: moduleId,
6163
+ testLocations: testLines
6164
+ }), groupedFiles.set(project, files);
6165
+ }
6166
+ let isCancelled = false;
6167
+ vitest.onCancel(() => {
6168
+ isCancelled = true;
6169
+ });
6170
+ const initialisedPools = await Promise.all([...groupedFiles.entries()].map(async ([project, files]) => {
6171
+ if (await project._initBrowserProvider(), !project.browser) throw new TypeError(`The browser server was not initialized${project.name ? ` for the "${project.name}" project` : ""}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
6172
+ if (isCancelled) return;
6173
+ debug?.("provider is ready for %s project", project.name);
6174
+ const pool = ensurePool(project);
6175
+ return vitest.state.clearFiles(project, files.map((f) => f.filepath)), providers.add(project.browser.provider), {
6176
+ pool,
6177
+ provider: project.browser.provider,
6178
+ runTests: () => pool.runTests(method, files)
6179
+ };
6180
+ }));
6181
+ if (isCancelled) return;
6182
+ const parallelPools = [], nonParallelPools = [];
6183
+ for (const pool of initialisedPools) {
6184
+ if (!pool)
6185
+ // this means it was cancelled
6186
+ return;
6187
+ if (pool.provider.mocker && pool.provider.supportsParallelism) parallelPools.push(pool.runTests);
6188
+ else nonParallelPools.push(pool.runTests);
6189
+ }
6190
+ await Promise.all(parallelPools.map((runTests) => runTests()));
6191
+ for (const runTests of nonParallelPools) {
6192
+ if (isCancelled) return;
6193
+ await runTests();
6194
+ }
6195
+ };
6196
+ function getThreadsCount(project) {
6197
+ const config = project.config.browser;
6198
+ return !config.headless || !config.fileParallelism || !project.browser.provider.supportsParallelism ? 1 : project.config.maxWorkers ? project.config.maxWorkers : threadsCount;
6199
+ }
6200
+ return {
6201
+ name: "browser",
6202
+ async close() {
6203
+ await Promise.all([...providers].map((provider) => provider.close())), vitest._browserSessions.sessionIds.clear(), providers.clear(), vitest.projects.forEach((project) => {
6204
+ project.browser?.state.orchestrators.forEach((orchestrator) => {
6205
+ orchestrator.$close();
6206
+ });
6207
+ }), debug?.("browser pool closed all providers");
6208
+ },
6209
+ runTests: (files) => runWorkspaceTests("run", files),
6210
+ collectTests: (files) => runWorkspaceTests("collect", files)
6211
+ };
6212
+ }
6213
+ function escapePathToRegexp(path) {
6214
+ return path.replace(/[/\\.?*()^${}|[\]+]/g, "\\$&");
6215
+ }
6216
+ class BrowserPool {
6217
+ _queue = [];
6218
+ _promise;
6219
+ _providedContext;
6220
+ readySessions = /* @__PURE__ */ new Set();
6221
+ constructor(project, options) {
6222
+ this.project = project, this.options = options;
6223
+ }
6224
+ cancel() {
6225
+ this._queue = [];
6226
+ }
6227
+ reject(error) {
6228
+ this._promise?.reject(error), this._promise = void 0, this.cancel();
6229
+ }
6230
+ get orchestrators() {
6231
+ return this.project.browser.state.orchestrators;
6232
+ }
6233
+ async runTests(method, files) {
6234
+ if (this._promise ??= createDefer(), !files.length) return debug?.("no tests found, finishing test run immediately"), this._promise.resolve(), this._promise;
6235
+ if (this._providedContext = stringify(this.project.getProvidedContext()), this._queue.push(...files), this.readySessions.forEach((sessionId) => {
6236
+ if (this._queue.length) this.readySessions.delete(sessionId), this.runNextTest(method, sessionId);
6237
+ }), this.orchestrators.size >= this.options.maxWorkers) return debug?.("all orchestrators are ready, not creating more"), this._promise;
6238
+ // open the minimum amount of tabs
6239
+ // if there is only 1 file running, we don't need 8 tabs running
6240
+ const workerCount = Math.min(this.options.maxWorkers - this.orchestrators.size, files.length), promises = [];
6241
+ for (let i = 0; i < workerCount; i++) {
6242
+ const sessionId = crypto.randomUUID();
6243
+ this.project.vitest._browserSessions.sessionIds.add(sessionId);
6244
+ const project = this.project.name;
6245
+ debug?.("[%s] creating session for %s", sessionId, project);
6246
+ const page = this.openPage(sessionId).then(() => {
6247
+ // start running tests on the page when it's ready
6248
+ this.runNextTest(method, sessionId);
6249
+ });
6250
+ promises.push(page);
6251
+ }
6252
+ return await Promise.all(promises), debug?.("all sessions are created"), this._promise;
6253
+ }
6254
+ async openPage(sessionId) {
6255
+ const sessionPromise = this.project.vitest._browserSessions.createSession(sessionId, this.project, this), browser = this.project.browser, url = new URL("/__vitest_test__/", this.options.origin);
6256
+ url.searchParams.set("sessionId", sessionId);
6257
+ const pagePromise = browser.provider.openPage(sessionId, url.toString());
6258
+ await Promise.all([sessionPromise, pagePromise]);
6259
+ }
6260
+ getOrchestrator(sessionId) {
6261
+ const orchestrator = this.orchestrators.get(sessionId);
6262
+ if (!orchestrator) throw new Error(`Orchestrator not found for session ${sessionId}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
6263
+ return orchestrator;
6264
+ }
6265
+ finishSession(sessionId) {
6266
+ // the last worker finished running tests
6267
+ if (this.readySessions.add(sessionId), this.readySessions.size === this.orchestrators.size) this._promise?.resolve(), this._promise = void 0, debug?.("[%s] all tests finished running", sessionId);
6268
+ else debug?.(`did not finish sessions for ${sessionId}: |ready - %s| |overall - %s|`, [...this.readySessions].join(", "), [...this.orchestrators.keys()].join(", "));
6269
+ }
6270
+ runNextTest(method, sessionId) {
6271
+ const file = this._queue.shift();
6272
+ if (!file) {
6273
+ // we don't need to cleanup testers if isolation is enabled,
6274
+ // because cleanup is done at the end of every test
6275
+ if (debug?.("[%s] no more tests to run", sessionId), this.project.config.browser.isolate) {
6276
+ this.finishSession(sessionId);
6277
+ return;
6278
+ }
6279
+ this.getOrchestrator(sessionId).cleanupTesters().catch((error) => this.reject(error)).finally(() => this.finishSession(sessionId));
6280
+ return;
6281
+ }
6282
+ if (!this._promise) throw new Error(`Unexpected empty queue`);
6283
+ const orchestrator = this.getOrchestrator(sessionId);
6284
+ debug?.("[%s] run test %s", sessionId, file), this.setBreakpoint(sessionId, file.filepath).then(() => {
6285
+ // this starts running tests inside the orchestrator
6286
+ orchestrator.createTesters({
6287
+ method,
6288
+ files: [file],
6289
+ providedContext: this._providedContext || "[{}]"
6290
+ }).then(() => {
6291
+ debug?.("[%s] test %s finished running", sessionId, file), this.runNextTest(method, sessionId);
6292
+ }).catch((error) => {
6293
+ // if user cancels the test run manually, ignore the error and exit gracefully
6294
+ if (this.project.vitest.isCancelling && error instanceof Error && error.message.startsWith("Browser connection was closed while running tests")) {
6295
+ this.cancel(), this._promise?.resolve(), this._promise = void 0, debug?.("[%s] browser connection was closed", sessionId);
6296
+ return;
6297
+ }
6298
+ debug?.("[%s] error during %s test run: %s", sessionId, file, error), this.reject(error);
6299
+ });
6300
+ }).catch((err) => this.reject(err));
6301
+ }
6302
+ async setBreakpoint(sessionId, file) {
6303
+ if (!this.project.config.inspector.waitForDebugger) return;
6304
+ const provider = this.project.browser.provider, browser = this.project.config.browser.name;
6305
+ if (shouldIgnoreDebugger(provider.name, browser)) {
6306
+ debug?.("[$s] ignoring debugger in %s browser because it is not supported", sessionId, browser);
6307
+ return;
6308
+ }
6309
+ if (!provider.getCDPSession) throw new Error("Unable to set breakpoint, CDP not supported");
6310
+ debug?.("[%s] set breakpoint for %s", sessionId, file);
6311
+ const session = await provider.getCDPSession(sessionId);
6312
+ await session.send("Debugger.enable", {}), await session.send("Debugger.setBreakpointByUrl", {
6313
+ lineNumber: 0,
6314
+ urlRegex: escapePathToRegexp(file)
6315
+ });
6316
+ }
6317
+ }
6318
+ function shouldIgnoreDebugger(provider, browser) {
6319
+ return provider === "webdriverio" ? browser !== "chrome" && browser !== "edge" : browser !== "chromium";
6320
+ }
6321
+
6322
+ function createMethodsRPC(project, options = {}) {
6323
+ const vitest = project.vitest, cacheFs = options.cacheFs ?? false;
6324
+ if (project.vitest.state.metadata[project.name] ??= {
6325
+ externalized: {},
6326
+ duration: {},
6327
+ tmps: {}
6328
+ }, project.config.dumpDir && !existsSync(project.config.dumpDir)) mkdirSync(project.config.dumpDir, { recursive: true });
6329
+ return project.vitest.state.metadata[project.name].dumpDir = project.config.dumpDir, {
6330
+ async fetch(url, importer, environmentName, options) {
6331
+ const environment = project.vite.environments[environmentName];
6332
+ if (!environment) throw new Error(`The environment ${environmentName} was not defined in the Vite config.`);
6333
+ const start = performance.now();
6334
+ return await project._fetcher(url, importer, environment, cacheFs, options).then((result) => {
6335
+ const duration = performance.now() - start;
6336
+ project.vitest.state.transformTime += duration;
6337
+ const metadata = project.vitest.state.metadata[project.name];
6338
+ if ("externalize" in result) metadata.externalized[url] = result.externalize;
6339
+ if ("tmp" in result) metadata.tmps[url] = result.tmp;
6340
+ return metadata.duration[url] ??= [], metadata.duration[url].push(duration), result;
6341
+ });
6342
+ },
6343
+ async resolve(id, importer, environmentName) {
6344
+ const environment = project.vite.environments[environmentName];
6345
+ if (!environment) throw new Error(`The environment ${environmentName} was not defined in the Vite config.`);
6346
+ const resolved = await environment.pluginContainer.resolveId(id, importer);
6347
+ return resolved ? {
6348
+ file: cleanUrl(resolved.id),
6349
+ url: normalizeResolvedIdToUrl(environment, resolved.id),
6350
+ id: resolved.id
6351
+ } : null;
6352
+ },
6353
+ snapshotSaved(snapshot) {
6354
+ vitest.snapshot.add(snapshot);
6355
+ },
6356
+ resolveSnapshotPath(testPath) {
6357
+ return vitest.snapshot.resolvePath(testPath, { config: project.serializedConfig });
6358
+ },
6359
+ async transform(id) {
6360
+ const environment = project.vite.environments.__vitest_vm__;
6361
+ if (!environment) throw new Error(`The VM environment was not defined in the Vite config. This is a bug in Vitest. Please, open a new issue with reproduction.`);
6362
+ const url = normalizeResolvedIdToUrl(environment, fileURLToPath(id));
6363
+ return { code: (await environment.transformRequest(url).catch(handleRollupError))?.code };
6364
+ },
6365
+ async onQueued(file) {
6366
+ if (options.collect) vitest.state.collectFiles(project, [file]);
6367
+ else await vitest._testRun.enqueued(project, file);
6368
+ },
6369
+ async onCollected(files) {
6370
+ if (options.collect) vitest.state.collectFiles(project, files);
6371
+ else await vitest._testRun.collected(project, files);
6372
+ },
6373
+ onAfterSuiteRun(meta) {
6374
+ vitest.coverageProvider?.onAfterSuiteRun(meta);
6375
+ },
6376
+ async onTaskAnnotate(testId, annotation) {
6377
+ return vitest._testRun.annotate(testId, annotation);
6378
+ },
6379
+ async onTaskUpdate(packs, events) {
6380
+ if (options.collect) vitest.state.updateTasks(packs);
6381
+ else await vitest._testRun.updated(packs, events);
6382
+ },
6383
+ async onUserConsoleLog(log) {
6384
+ if (options.collect) vitest.state.updateUserLog(log);
6385
+ else await vitest._testRun.log(log);
6386
+ },
6387
+ onUnhandledError(err, type) {
6388
+ vitest.state.catchError(err, type);
6389
+ },
6390
+ onCancel(reason) {
6391
+ vitest.cancelCurrentRun(reason);
6392
+ },
6393
+ getCountOfFailedTests() {
6394
+ return vitest.state.getCountOfFailedTests();
6395
+ }
6396
+ };
6397
+ }
6398
+
6399
+ var RunnerState = /* @__PURE__ */ function(RunnerState) {
6400
+ return RunnerState["IDLE"] = "idle", RunnerState["STARTING"] = "starting", RunnerState["STARTED"] = "started", RunnerState["STOPPING"] = "stopping", RunnerState["STOPPED"] = "stopped", RunnerState;
6401
+ }(RunnerState || {});
6402
+ const START_TIMEOUT = 1e4, STOP_TIMEOUT = 1e4;
6403
+ /** @experimental */
6404
+ class PoolRunner {
6405
+ /** Exposed to test runner as `VITEST_POOL_ID`. Value is between 1-`maxWorkers`. */
6406
+ poolId = void 0;
6407
+ project;
6408
+ environment;
6409
+ _state = RunnerState.IDLE;
6410
+ _operationLock = null;
6411
+ _eventEmitter = new EventEmitter();
6412
+ _rpc;
6413
+ get isTerminated() {
6414
+ return this._state === RunnerState.STOPPED;
6415
+ }
6416
+ get isStarted() {
6417
+ return this._state === RunnerState.STARTED;
6418
+ }
6419
+ constructor(options, worker) {
6420
+ this.worker = worker, this.project = options.project, this.environment = options.environment, this._rpc = createBirpc(createMethodsRPC(this.project, {
6421
+ collect: options.method === "collect",
6422
+ cacheFs: worker.cacheFs
6423
+ }), {
6424
+ eventNames: ["onCancel"],
6425
+ post: (request) => this.postMessage(request),
6426
+ on: (callback) => this._eventEmitter.on("rpc", callback),
6427
+ timeout: -1
6428
+ }), this.project.vitest.onCancel((reason) => this._rpc.onCancel(reason));
6429
+ }
6430
+ postMessage(message) {
6431
+ // Only send messages when runner is active (not fully stopped)
6432
+ // Allow sending during STOPPING state for the 'stop' message itself
6433
+ if (this._state !== RunnerState.STOPPED) return this.worker.send(message);
6434
+ }
6435
+ async start() {
6436
+ // Wait for any ongoing operation to complete
6437
+ if (this._operationLock) await this._operationLock;
6438
+ if (!(this._state === RunnerState.STARTED || this._state === RunnerState.STARTING)) {
6439
+ if (this._state === RunnerState.STOPPED) throw new Error("[vitest-pool-runner]: Cannot start a stopped runner");
6440
+ // Create operation lock to prevent concurrent start/stop
6441
+ this._operationLock = createDefer();
6442
+ try {
6443
+ this._state = RunnerState.STARTING, await this.worker.start(), this.worker.on("error", this.emitWorkerError), this.worker.on("exit", this.emitUnexpectedExit), this.worker.on("message", this.emitWorkerMessage);
6444
+ const startPromise = this.withTimeout(this.waitForStart(), START_TIMEOUT);
6445
+ this.postMessage({
6446
+ type: "start",
6447
+ __vitest_worker_request__: true,
6448
+ options: { reportMemory: this.worker.reportMemory ?? false }
6449
+ }), await startPromise, this._state = RunnerState.STARTED;
6450
+ } catch (error) {
6451
+ throw this._state = RunnerState.IDLE, error;
6452
+ } finally {
6453
+ this._operationLock.resolve(), this._operationLock = null;
6454
+ }
6455
+ }
6456
+ }
6457
+ async stop() {
6458
+ // Wait for any ongoing operation to complete
6459
+ if (this._operationLock) await this._operationLock;
6460
+ if (!(this._state === RunnerState.STOPPED || this._state === RunnerState.STOPPING)) {
6461
+ if (this._state === RunnerState.IDLE) {
6462
+ this._state = RunnerState.STOPPED;
6463
+ return;
6464
+ }
6465
+ // Create operation lock to prevent concurrent start/stop
6466
+ this._operationLock = createDefer();
6467
+ try {
6468
+ this._state = RunnerState.STOPPING, this.worker.off("exit", this.emitUnexpectedExit), await this.withTimeout(new Promise((resolve) => {
6469
+ const onStop = (response) => {
6470
+ if (response.type === "stopped") {
6471
+ if (response.error) this.project.vitest.state.catchError(response.error, "Teardown Error");
6472
+ resolve(), this.off("message", onStop);
6473
+ }
6474
+ };
6475
+ this.on("message", onStop), this.postMessage({
6476
+ type: "stop",
6477
+ __vitest_worker_request__: true
6478
+ });
6479
+ }), STOP_TIMEOUT), this._eventEmitter.removeAllListeners(), this._rpc.$close(/* @__PURE__ */ new Error("[vitest-pool-runner]: Pending methods while closing rpc")), await this.worker.stop(), this._state = RunnerState.STOPPED;
6480
+ } catch (error) {
6481
+ throw this._state = RunnerState.STOPPED, error;
6482
+ } finally {
6483
+ this._operationLock.resolve(), this._operationLock = null;
6484
+ }
6485
+ }
6486
+ }
6487
+ on(event, callback) {
6488
+ this._eventEmitter.on(event, callback);
6489
+ }
6490
+ off(event, callback) {
6491
+ this._eventEmitter.off(event, callback);
6492
+ }
6493
+ emitWorkerError = (maybeError) => {
6494
+ const error = maybeError instanceof Error ? maybeError : new Error(String(maybeError));
6495
+ this._eventEmitter.emit("error", error);
6496
+ };
6497
+ emitWorkerMessage = (response) => {
6498
+ try {
6499
+ const message = this.worker.deserialize(response);
6500
+ if (typeof message === "object" && message != null && message.__vitest_worker_response__) this._eventEmitter.emit("message", message);
6501
+ else this._eventEmitter.emit("rpc", message);
6502
+ } catch (error) {
6503
+ this._eventEmitter.emit("error", error);
6504
+ }
6505
+ };
6506
+ emitUnexpectedExit = () => {
6507
+ const error = /* @__PURE__ */ new Error("Worker exited unexpectedly");
6508
+ this._eventEmitter.emit("error", error);
6509
+ };
6510
+ waitForStart() {
6511
+ return new Promise((resolve) => {
6512
+ const onStart = (message) => {
6513
+ if (message.type === "started") this.off("message", onStart), resolve();
6514
+ };
6515
+ this.on("message", onStart);
6516
+ });
6517
+ }
6518
+ withTimeout(promise, timeout) {
6519
+ return new Promise((resolve_, reject_) => {
6520
+ const timer = setTimeout(() => reject(/* @__PURE__ */ new Error("[vitest-pool-runner]: Timeout waiting for worker to respond")), timeout);
6521
+ function resolve(value) {
6522
+ clearTimeout(timer), resolve_(value);
6523
+ }
6524
+ function reject(error) {
6525
+ clearTimeout(timer), reject_(error);
6526
+ }
6527
+ promise.then(resolve, reject);
6528
+ });
6529
+ }
6530
+ }
6531
+
6532
+ const SIGKILL_TIMEOUT = 500;
6533
+ /** @experimental */
6534
+ class ForksPoolWorker {
6535
+ name = "forks";
6536
+ cacheFs = true;
6537
+ entrypoint;
6538
+ execArgv;
6539
+ env;
6540
+ _fork;
6541
+ constructor(options) {
6542
+ /** Loads {@link file://./../../../runtime/workers/forks.ts} */
6543
+ this.execArgv = options.execArgv, this.env = options.env, this.entrypoint = resolve$1(options.distPath, "workers/forks.js");
6544
+ }
6545
+ on(event, callback) {
6546
+ this.fork.on(event, callback);
6547
+ }
6548
+ off(event, callback) {
6549
+ this.fork.off(event, callback);
6550
+ }
6551
+ send(message) {
6552
+ if ("context" in message) message = {
6553
+ ...message,
6554
+ context: {
6555
+ ...message.context,
6556
+ config: wrapSerializableConfig(message.context.config)
6557
+ }
6558
+ };
6559
+ this.fork.send(v8.serialize(message));
6560
+ }
6561
+ async start() {
6562
+ this._fork ||= fork(this.entrypoint, [], {
6563
+ env: this.env,
6564
+ execArgv: this.execArgv
6565
+ });
6566
+ }
6567
+ async stop() {
6568
+ const fork = this.fork, waitForExit = new Promise((resolve) => {
6569
+ if (fork.exitCode != null) resolve();
6570
+ else fork.once("exit", resolve);
6571
+ }), sigkillTimeout = setTimeout(() => fork.kill("SIGKILL"), SIGKILL_TIMEOUT);
6572
+ fork.kill(), await waitForExit, clearTimeout(sigkillTimeout), this._fork = void 0;
6573
+ }
6574
+ deserialize(data) {
6575
+ try {
6576
+ return v8.deserialize(Buffer.from(data));
6577
+ } catch (error) {
6578
+ let stringified = "";
6579
+ try {
6580
+ stringified = `\nReceived value: ${JSON.stringify(data)}`;
6581
+ } catch {}
6582
+ throw new Error(`[vitest-pool]: Unexpected call to process.send(). Make sure your test cases are not interfering with process's channel.${stringified}`, { cause: error });
6583
+ }
6584
+ }
6585
+ get fork() {
6586
+ if (!this._fork) throw new Error(`The child process was torn down or never initialized. This is a bug in Vitest.`);
6587
+ return this._fork;
6588
+ }
6589
+ }
6590
+ /**
6591
+ * Prepares `SerializedConfig` for serialization, e.g. `node:v8.serialize`
6592
+ * - Unwrapping done in {@link file://./../../../runtime/workers/init-forks.ts}
6593
+ */
6594
+ function wrapSerializableConfig(config) {
6595
+ let testNamePattern = config.testNamePattern, defines = config.defines;
6596
+ // v8 serialize does not support regex
6597
+ if (testNamePattern && typeof testNamePattern !== "string") testNamePattern = `$$vitest:${testNamePattern.toString()}`;
6598
+ // v8 serialize drops properties with undefined value
6599
+ if (defines) defines = {
6600
+ keys: Object.keys(defines),
6601
+ original: defines
6602
+ };
6603
+ return {
6604
+ ...config,
6605
+ testNamePattern,
6606
+ defines
6607
+ };
6608
+ }
6609
+
6610
+ /** @experimental */
6611
+ class ThreadsPoolWorker {
6612
+ name = "threads";
6613
+ entrypoint;
6614
+ execArgv;
6615
+ env;
6616
+ _thread;
6617
+ constructor(options) {
6618
+ /** Loads {@link file://./../../../runtime/workers/threads.ts} */
6619
+ this.execArgv = options.execArgv, this.env = options.env, this.entrypoint = resolve$1(options.distPath, "workers/threads.js");
6620
+ }
6621
+ on(event, callback) {
6622
+ this.thread.on(event, callback);
6623
+ }
6624
+ off(event, callback) {
6625
+ this.thread.off(event, callback);
6626
+ }
6627
+ send(message) {
6628
+ this.thread.postMessage(message);
6629
+ }
6630
+ async start() {
6631
+ // This can be called multiple times if the runtime is shared.
6632
+ this._thread ||= new Worker(this.entrypoint, {
6633
+ env: this.env,
6634
+ execArgv: this.execArgv
6635
+ });
6636
+ }
6637
+ async stop() {
6638
+ await this.thread.terminate().then(() => {
6639
+ this._thread = void 0;
6640
+ });
6641
+ }
6642
+ deserialize(data) {
6643
+ return data;
6644
+ }
6645
+ get thread() {
6646
+ if (!this._thread) throw new Error(`The worker thread was torn down or never initialized. This is a bug in Vitest.`);
6647
+ return this._thread;
6648
+ }
6649
+ }
6650
+
6651
+ /** @experimental */
6652
+ class TypecheckPoolWorker {
6653
+ name = "typecheck";
6654
+ project;
6655
+ _eventEmitter = new EventEmitter$1();
6656
+ constructor(options) {
6657
+ this.project = options.project;
6658
+ }
6659
+ async start() {
6660
+ // noop, onMessage handles it
6661
+ }
6662
+ async stop() {
6663
+ // noop, onMessage handles it
6664
+ }
6665
+ canReuse() {
6666
+ return true;
6667
+ }
6668
+ send(message) {
6669
+ onMessage(message, this.project).then((response) => {
6670
+ if (response) this._eventEmitter.emit("message", response);
6671
+ });
6672
+ }
6673
+ on(event, callback) {
6674
+ this._eventEmitter.on(event, callback);
6675
+ }
6676
+ off(event, callback) {
6677
+ this._eventEmitter.on(event, callback);
6678
+ }
6679
+ deserialize(data) {
6680
+ return data;
6681
+ }
6682
+ }
6683
+ const __vitest_worker_response__ = true, runners = /* @__PURE__ */ new WeakMap();
6684
+ async function onMessage(message, project) {
6685
+ if (message?.__vitest_worker_request__ !== true) return;
6686
+ let runner = runners.get(project.vitest);
6687
+ if (!runner) runner = createRunner(project.vitest), runners.set(project.vitest, runner);
6688
+ let runPromise;
6689
+ switch (message.type) {
6690
+ case "start": return {
6691
+ type: "started",
6692
+ __vitest_worker_response__
6693
+ };
6694
+ case "run": return runPromise = runner.runTests(message.context.files, project).catch((error) => error), {
6695
+ type: "testfileFinished",
6696
+ error: await runPromise,
6697
+ __vitest_worker_response__
6698
+ };
6699
+ case "collect": return runPromise = runner.collectTests(message.context.files, project).catch((error) => error), {
6700
+ type: "testfileFinished",
6701
+ error: await runPromise,
6702
+ __vitest_worker_response__
6703
+ };
6704
+ case "stop": return await runPromise, await project.typechecker?.stop(), {
6705
+ type: "stopped",
6706
+ __vitest_worker_response__
6707
+ };
6708
+ }
6709
+ throw new Error(`Unexpected message ${JSON.stringify(message, null, 2)}`);
6710
+ }
6711
+ function createRunner(vitest) {
6712
+ const promisesMap = /* @__PURE__ */ new WeakMap(), rerunTriggered = /* @__PURE__ */ new WeakSet();
6713
+ async function onParseEnd(project, { files, sourceErrors }) {
6714
+ const checker = project.typechecker, { packs, events } = checker.getTestPacksAndEvents();
6715
+ if (await vitest._testRun.updated(packs, events), !project.config.typecheck.ignoreSourceErrors) sourceErrors.forEach((error) => vitest.state.catchError(error, "Unhandled Source Error"));
6716
+ if (!hasFailed(files) && !sourceErrors.length && checker.getExitCode()) {
6717
+ const error = new Error(checker.getOutput());
6718
+ error.stack = "", vitest.state.catchError(error, "Typecheck Error");
6719
+ }
6720
+ // triggered by TSC watcher, not Vitest watcher, so we need to emulate what Vitest does in this case
6721
+ if (promisesMap.get(project)?.resolve(), rerunTriggered.delete(project), vitest.config.watch && !vitest.runningPromise) {
6722
+ const modules = files.map((file) => vitest.state.getReportedEntity(file)).filter((e) => e?.type === "module"), state = vitest.isCancelling ? "interrupted" : modules.some((m) => !m.ok()) ? "failed" : "passed";
6723
+ await vitest.report("onTestRunEnd", modules, [], state), await vitest.report("onWatcherStart", files, [...project.config.typecheck.ignoreSourceErrors ? [] : sourceErrors, ...vitest.state.getUnhandledErrors()]);
6724
+ }
6725
+ }
6726
+ async function createWorkspaceTypechecker(project, files) {
6727
+ const checker = project.typechecker ?? new Typechecker(project);
6728
+ return project.typechecker ? checker : (project.typechecker = checker, checker.setFiles(files), checker.onParseStart(async () => {
6729
+ const files = checker.getTestFiles();
6730
+ for (const file of files) await vitest._testRun.enqueued(project, file);
6731
+ await vitest._testRun.collected(project, files);
6732
+ }), checker.onParseEnd((result) => onParseEnd(project, result)), checker.onWatcherRerun(async () => {
6733
+ if (rerunTriggered.add(project), !vitest.runningPromise) vitest.state.clearErrors(), await vitest.report("onWatcherRerun", files, "File change detected. Triggering rerun.");
6734
+ await checker.collectTests();
6735
+ const testFiles = checker.getTestFiles();
6736
+ for (const file of testFiles) await vitest._testRun.enqueued(project, file);
6737
+ await vitest._testRun.collected(project, testFiles);
6738
+ const { packs, events } = checker.getTestPacksAndEvents();
6739
+ await vitest._testRun.updated(packs, events);
6740
+ }), checker);
6741
+ }
6742
+ async function startTypechecker(project, files) {
6743
+ if (project.typechecker) return;
6744
+ const checker = await createWorkspaceTypechecker(project, files);
6745
+ await checker.collectTests(), await checker.start();
6746
+ }
6747
+ async function collectTests(specs, project) {
6748
+ const files = specs.map((spec) => spec.filepath), checker = await createWorkspaceTypechecker(project, files);
6749
+ checker.setFiles(files), await checker.collectTests();
6750
+ const testFiles = checker.getTestFiles();
6751
+ vitest.state.collectFiles(project, testFiles);
6752
+ }
6753
+ async function runTests(specs, project) {
6754
+ const promises = [], files = specs.map((spec) => spec.filepath), promise = createDefer(), triggered = await new Promise((resolve) => {
6755
+ const _i = setInterval(() => {
6756
+ if (!project.typechecker || rerunTriggered.has(project)) resolve(true), clearInterval(_i);
6757
+ });
6758
+ setTimeout(() => {
6759
+ resolve(false), clearInterval(_i);
6760
+ }, 500).unref();
6761
+ });
6762
+ if (project.typechecker && !triggered) {
6763
+ const testFiles = project.typechecker.getTestFiles();
6764
+ for (const file of testFiles) await vitest._testRun.enqueued(project, file);
6765
+ await vitest._testRun.collected(project, testFiles), await onParseEnd(project, project.typechecker.getResult());
6766
+ }
6767
+ promises.push(promise), promisesMap.set(project, promise), promises.push(startTypechecker(project, files)), await Promise.all(promises);
6768
+ }
6769
+ return {
6770
+ runTests,
6771
+ collectTests
6772
+ };
6773
+ }
6774
+
6775
+ /** @experimental */
6776
+ class VmForksPoolWorker extends ForksPoolWorker {
6777
+ name = "vmForks";
6778
+ reportMemory = true;
6779
+ entrypoint;
6780
+ constructor(options) {
6781
+ /** Loads {@link file://./../../../runtime/workers/vmForks.ts} */
6782
+ super(options), this.execArgv.push("--experimental-vm-modules"), this.entrypoint = resolve$1(options.distPath, "workers/vmForks.js");
6783
+ }
6784
+ }
6785
+
6786
+ /** @experimental */
6787
+ class VmThreadsPoolWorker extends ThreadsPoolWorker {
6788
+ name = "vmThreads";
6789
+ reportMemory = true;
6790
+ entrypoint;
6791
+ constructor(options) {
6792
+ /** Loads {@link file://./../../../runtime/workers/vmThreads.ts} */
6793
+ super(options), this.execArgv.push("--experimental-vm-modules"), this.entrypoint = resolve$1(options.distPath, "workers/vmThreads.js");
6794
+ }
6795
+ }
6796
+
6797
+ const WORKER_START_TIMEOUT = 5e3;
6798
+ class Pool {
6799
+ maxWorkers = 0;
6800
+ workerIds = /* @__PURE__ */ new Map();
6801
+ queue = [];
6802
+ activeTasks = [];
6803
+ sharedRunners = [];
6804
+ exitPromises = [];
6805
+ _isCancelling = false;
6806
+ constructor(options, logger) {
6807
+ this.options = options, this.logger = logger;
6808
+ }
6809
+ setMaxWorkers(maxWorkers) {
6810
+ this.maxWorkers = maxWorkers, this.workerIds = new Map(Array.from({ length: maxWorkers }).fill(0).map((_, i) => [i + 1, true]));
6811
+ }
6812
+ async run(task, method) {
6813
+ // Prevent new tasks from being queued during cancellation
6814
+ if (this._isCancelling) throw new Error("[vitest-pool]: Cannot run tasks while pool is cancelling");
6815
+ // Every runner related failure should make this promise reject so that it's picked by pool.
6816
+ // This resolver is used to make the error handling in recursive queue easier.
6817
+ const testFinish = withResolvers();
6818
+ this.queue.push({
6819
+ task,
6820
+ resolver: testFinish,
6821
+ method
6822
+ }), this.schedule(), await testFinish.promise;
6823
+ }
6824
+ async schedule() {
6825
+ if (this.queue.length === 0 || this.activeTasks.length >= this.maxWorkers) return;
6826
+ const { task, resolver, method } = this.queue.shift();
6827
+ try {
6828
+ let isMemoryLimitReached = false;
6829
+ const runner = this.getPoolRunner(task, method), activeTask = {
6830
+ task,
6831
+ resolver,
6832
+ method,
6833
+ cancelTask
6834
+ };
6835
+ this.activeTasks.push(activeTask), runner.on("error", (error) => {
6836
+ resolver.reject(new Error(`[vitest-pool]: Worker ${task.worker} emitted error.`, { cause: error }));
6837
+ });
6838
+ async function cancelTask() {
6839
+ await runner.stop(), resolver.reject(/* @__PURE__ */ new Error("Cancelled"));
6840
+ }
6841
+ const onFinished = (message) => {
6842
+ if (message?.__vitest_worker_response__ && message.type === "testfileFinished") {
6843
+ if (task.memoryLimit && message.usedMemory) isMemoryLimitReached = message.usedMemory >= task.memoryLimit;
6844
+ if (message.error) this.options.state.catchError(message.error, "Test Run Error");
6845
+ runner.off("message", onFinished), resolver.resolve();
6846
+ }
6847
+ };
6848
+ if (runner.on("message", onFinished), !runner.isStarted) {
6849
+ const id = setTimeout(() => resolver.reject(/* @__PURE__ */ new Error(`[vitest-pool]: Timeout starting ${task.worker} runner.`)), WORKER_START_TIMEOUT);
6850
+ await runner.start().finally(() => clearTimeout(id));
6851
+ }
6852
+ const poolId = runner.poolId ?? this.getWorkerId();
6853
+ runner.poolId = poolId, runner.postMessage({
6854
+ __vitest_worker_request__: true,
6855
+ type: method,
6856
+ context: task.context,
6857
+ poolId
6858
+ }), await resolver.promise;
6859
+ const index = this.activeTasks.indexOf(activeTask);
6860
+ if (index !== -1) this.activeTasks.splice(index, 1);
6861
+ if (!task.isolate && !isMemoryLimitReached && this.queue[0]?.task.isolate === false && isEqualRunner(runner, this.queue[0].task)) return this.sharedRunners.push(runner), this.schedule();
6862
+ // Runner terminations are started but not awaited until the end of full run.
6863
+ // Runner termination can also already start from task cancellation.
6864
+ if (!runner.isTerminated) {
6865
+ const id = setTimeout(() => this.logger.error(`[vitest-pool]: Timeout terminating ${task.worker} worker for test files ${formatFiles(task)}.`), this.options.teardownTimeout);
6866
+ this.exitPromises.push(runner.stop().then(() => clearTimeout(id)).catch((error) => this.logger.error(`[vitest-pool]: Failed to terminate ${task.worker} worker for test files ${formatFiles(task)}.`, error)));
6867
+ }
6868
+ this.freeWorkerId(poolId);
6869
+ }
6870
+ // This is mostly to avoid zombie workers when/if Vitest internals run into errors
6871
+ catch (error) {
6872
+ return resolver.reject(error);
6873
+ }
6874
+ return this.schedule();
6875
+ }
6876
+ async cancel() {
6877
+ // Set flag to prevent new tasks from being queued
6878
+ this._isCancelling = true;
6879
+ const pendingTasks = this.queue.splice(0);
6880
+ if (pendingTasks.length) {
6881
+ const error = /* @__PURE__ */ new Error("Cancelled");
6882
+ pendingTasks.forEach((task) => task.resolver.reject(error));
6883
+ }
6884
+ const activeTasks = this.activeTasks.splice(0);
6885
+ await Promise.all(activeTasks.map((task) => task.cancelTask()));
6886
+ const sharedRunners = this.sharedRunners.splice(0);
6887
+ // Reset flag after cancellation completes
6888
+ await Promise.all(sharedRunners.map((runner) => runner.stop())), await Promise.all(this.exitPromises.splice(0)), this.workerIds.forEach((_, id) => this.freeWorkerId(id)), this._isCancelling = false;
6889
+ }
6890
+ async close() {
6891
+ await this.cancel();
6892
+ }
6893
+ getPoolRunner(task, method) {
6894
+ if (task.isolate === false) {
6895
+ const index = this.sharedRunners.findIndex((runner) => isEqualRunner(runner, task));
6896
+ if (index !== -1) return this.sharedRunners.splice(index, 1)[0];
6897
+ }
6898
+ const options = {
6899
+ distPath: this.options.distPath,
6900
+ project: task.project,
6901
+ method,
6902
+ environment: task.context.environment.name,
6903
+ env: task.env,
6904
+ execArgv: task.execArgv
6905
+ };
6906
+ switch (task.worker) {
6907
+ case "forks": return new PoolRunner(options, new ForksPoolWorker(options));
6908
+ case "vmForks": return new PoolRunner(options, new VmForksPoolWorker(options));
6909
+ case "threads": return new PoolRunner(options, new ThreadsPoolWorker(options));
6910
+ case "vmThreads": return new PoolRunner(options, new VmThreadsPoolWorker(options));
6911
+ case "typescript": return new PoolRunner(options, new TypecheckPoolWorker(options));
6912
+ }
6913
+ const customPool = task.project.config.poolRunner;
6914
+ if (customPool != null && customPool.name === task.worker) return new PoolRunner(options, customPool.createPoolWorker(options));
6915
+ throw new Error(`Runner ${task.worker} is not supported. Test files: ${formatFiles(task)}.`);
6916
+ }
6917
+ getWorkerId() {
6918
+ let workerId = 0;
6919
+ return this.workerIds.forEach((state, id) => {
6920
+ if (state && !workerId) workerId = id, this.workerIds.set(id, false);
6921
+ }), workerId;
6922
+ }
6923
+ freeWorkerId(id) {
6924
+ this.workerIds.set(id, true);
6925
+ }
6926
+ }
6927
+ function withResolvers() {
6928
+ let resolve = () => {}, reject = (_error) => {};
6929
+ const promise = new Promise((res, rej) => {
6930
+ resolve = res, reject = rej;
6931
+ });
6932
+ return {
6933
+ resolve,
6934
+ reject,
6935
+ promise
6936
+ };
6937
+ }
6938
+ function formatFiles(task) {
6939
+ return task.context.files.map((file) => file.filepath).join(", ");
6940
+ }
6941
+ function isEqualRunner(runner, task) {
6942
+ if (task.isolate) throw new Error("Isolated tasks should not share runners");
6943
+ return runner.worker.name === task.worker && runner.project === task.project && runner.environment === task.context.environment.name && (!runner.worker.canReuse || runner.worker.canReuse(task));
6944
+ }
6945
+
6946
+ const suppressWarningsPath = resolve(rootDir, "./suppress-warnings.cjs");
6947
+ function getFilePoolName(project) {
6948
+ return project.config.browser.enabled ? "browser" : project.config.pool;
6949
+ }
6950
+ function createPool(ctx) {
6951
+ const pool = new Pool({
6952
+ distPath: ctx.distPath,
6953
+ teardownTimeout: ctx.config.teardownTimeout,
6954
+ state: ctx.state
6955
+ }, ctx.logger), options = resolveOptions(ctx), Sequencer = ctx.config.sequence.sequencer, sequencer = new Sequencer(ctx);
6956
+ let browserPool;
6957
+ async function executeTests(method, specs, invalidates) {
6958
+ if (ctx.onCancel(() => pool.cancel()), ctx.config.shard) {
6959
+ if (!ctx.config.passWithNoTests && ctx.config.shard.count > specs.length) throw new Error(`--shard <count> must be a smaller than count of test files. Resolved ${specs.length} test files for --shard=${ctx.config.shard.index}/${ctx.config.shard.count}.`);
6960
+ specs = await sequencer.shard(Array.from(specs));
6961
+ }
6962
+ const taskGroups = [];
6963
+ let workerId = 0;
6964
+ const sorted = await sequencer.sort(specs), environments = await getSpecificationsEnvironments(specs), groups = groupSpecs(sorted);
6965
+ for (const group of groups) {
6966
+ if (!group) continue;
6967
+ const taskGroup = [], browserSpecs = [];
6968
+ taskGroups.push({
6969
+ tasks: taskGroup,
6970
+ maxWorkers: group.maxWorkers,
6971
+ browserSpecs
6972
+ });
6973
+ for (const specs of group.specs) {
6974
+ const { project, pool } = specs[0];
6975
+ if (pool === "browser") {
6976
+ browserSpecs.push(...specs);
6977
+ continue;
6978
+ }
6979
+ const environment = environments.get(specs[0]);
6980
+ if (!environment) throw new Error(`Cannot find the environment. This is a bug in Vitest.`);
6981
+ taskGroup.push({
6982
+ context: {
6983
+ pool,
6984
+ config: project.serializedConfig,
6985
+ files: specs.map((spec) => ({
6986
+ filepath: spec.moduleId,
6987
+ testLocations: spec.testLines
6988
+ })),
6989
+ invalidates,
6990
+ environment,
6991
+ projectName: project.name,
6992
+ providedContext: project.getProvidedContext(),
6993
+ workerId: workerId++
6994
+ },
6995
+ project,
6996
+ env: {
6997
+ ...options.env,
6998
+ ...project.config.env
6999
+ },
7000
+ execArgv: [...options.execArgv, ...project.config.execArgv],
7001
+ worker: pool,
7002
+ isolate: project.config.isolate,
7003
+ memoryLimit: getMemoryLimit(ctx.config, pool) ?? null
7004
+ });
7005
+ }
7006
+ }
7007
+ const results = [];
7008
+ for (const { tasks, browserSpecs, maxWorkers } of taskGroups) {
7009
+ pool.setMaxWorkers(maxWorkers);
7010
+ const promises = tasks.map(async (task) => {
7011
+ if (ctx.isCancelling) return ctx.state.cancelFiles(task.context.files, task.project);
7012
+ try {
7013
+ await pool.run(task, method);
7014
+ } catch (error) {
7015
+ // Intentionally cancelled
7016
+ if (ctx.isCancelling && error instanceof Error && error.message === "Cancelled") ctx.state.cancelFiles(task.context.files, task.project);
7017
+ else throw error;
7018
+ }
7019
+ });
7020
+ if (browserSpecs.length) if (browserPool ??= createBrowserPool(ctx), method === "collect") promises.push(browserPool.collectTests(browserSpecs));
7021
+ else promises.push(browserPool.runTests(browserSpecs));
7022
+ const groupResults = await Promise.allSettled(promises);
7023
+ results.push(...groupResults);
7024
+ }
7025
+ const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
7026
+ if (errors.length > 0) throw new AggregateError(errors, "Errors occurred while running tests. For more information, see serialized error.");
7027
+ }
7028
+ return {
7029
+ name: "default",
7030
+ runTests: (files, invalidates) => executeTests("run", files, invalidates),
7031
+ collectTests: (files, invalidates) => executeTests("collect", files, invalidates),
7032
+ async close() {
7033
+ await Promise.all([pool.close(), browserPool?.close?.()]);
7034
+ }
7035
+ };
7036
+ }
7037
+ function resolveOptions(ctx) {
7038
+ // in addition to resolve.conditions Vite also adds production/development,
7039
+ // see: https://github.com/vitejs/vite/blob/af2aa09575229462635b7cbb6d248ca853057ba2/packages/vite/src/node/plugins/resolve.ts#L1056-L1080
7040
+ const viteMajor = Number(version.split(".")[0]), conditions = [...new Set(viteMajor >= 6 ? ctx.vite.config.ssr.resolve?.conditions ?? [] : [
7041
+ "production",
7042
+ "development",
7043
+ ...ctx.vite.config.resolve.conditions
7044
+ ])].filter((condition) => {
7045
+ return condition === "production" ? ctx.vite.config.isProduction : condition === "development" ? !ctx.vite.config.isProduction : true;
7046
+ }).map((condition) => {
7047
+ return viteMajor >= 6 && condition === "development|production" ? ctx.vite.config.isProduction ? "production" : "development" : condition;
7048
+ }).flatMap((c) => ["--conditions", c]), options = {
7049
+ execArgv: [
7050
+ ...process.execArgv.filter((execArg) => execArg.startsWith("--cpu-prof") || execArg.startsWith("--heap-prof") || execArg.startsWith("--diagnostic-dir")),
7051
+ ...conditions,
7052
+ "--experimental-import-meta-resolve",
7053
+ "--require",
7054
+ suppressWarningsPath
7055
+ ],
7056
+ env: {
7057
+ TEST: "true",
7058
+ VITEST: "true",
7059
+ NODE_ENV: process.env.NODE_ENV || "test",
7060
+ VITEST_MODE: ctx.config.watch ? "WATCH" : "RUN",
7061
+ FORCE_TTY: isatty(1) ? "true" : "",
7062
+ ...process.env,
7063
+ ...ctx.config.env
7064
+ }
7065
+ };
7066
+ // env are case-insensitive on Windows, but spawned processes don't support it
7067
+ if (isWindows) for (const name in options.env) options.env[name.toUpperCase()] = options.env[name];
7068
+ return options;
7069
+ }
7070
+ function resolveMaxWorkers(project) {
7071
+ if (project.config.maxWorkers) return project.config.maxWorkers;
7072
+ if (project.vitest.config.maxWorkers) return project.vitest.config.maxWorkers;
7073
+ const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
7074
+ return project.vitest.config.watch ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1);
7075
+ }
7076
+ function getMemoryLimit(config, pool) {
7077
+ if (pool !== "vmForks" && pool !== "vmThreads") return null;
7078
+ const memory = nodeos.totalmem(), limit = getWorkerMemoryLimit(config);
7079
+ // just ignore "memoryLimit" value because we cannot detect memory limit
7080
+ return typeof memory === "number" ? stringToBytes(limit, config.watch ? memory / 2 : memory) : typeof limit === "number" && limit > 1 || typeof limit === "string" && limit.at(-1) !== "%" ? stringToBytes(limit) : null;
7081
+ }
7082
+ function groupSpecs(specs) {
7083
+ const groups = [], sequential = {
7084
+ specs: [],
7085
+ maxWorkers: 1
7086
+ }, typechecks = {};
7087
+ specs.forEach((spec) => {
7088
+ if (spec.pool === "typescript") {
7089
+ typechecks[spec.project.name] ||= [], typechecks[spec.project.name].push(spec);
7090
+ return;
7091
+ }
7092
+ const order = spec.project.config.sequence.groupOrder;
7093
+ // Files that have disabled parallelism and default groupId are set into their own group
7094
+ if (order === 0 && spec.project.config.fileParallelism === false) return sequential.specs.push([spec]);
7095
+ const maxWorkers = resolveMaxWorkers(spec.project);
7096
+ // Multiple projects with different maxWorkers but same groupId
7097
+ if (groups[order] ||= {
7098
+ specs: [],
7099
+ maxWorkers
7100
+ }, groups[order].maxWorkers !== maxWorkers) {
7101
+ const last = groups[order].specs.at(-1)?.at(-1)?.project.name;
7102
+ throw new Error(`Projects "${last}" and "${spec.project.name}" have different 'maxWorkers' but same 'sequence.groupId'.\nProvide unique 'sequence.groupId' for them.`);
7103
+ }
7104
+ groups[order].specs.push([spec]);
7105
+ });
7106
+ let order = Math.max(0, ...groups.keys()) + 1;
7107
+ for (const projectName in typechecks) {
7108
+ const maxWorkers = resolveMaxWorkers(typechecks[projectName][0].project), previous = groups[order - 1];
7109
+ if (previous && previous.typecheck && maxWorkers !== previous.maxWorkers) order += 1;
7110
+ groups[order] ||= {
7111
+ specs: [],
7112
+ maxWorkers,
7113
+ typecheck: true
7114
+ }, groups[order].specs.push(typechecks[projectName]);
7115
+ }
7116
+ if (sequential.specs.length) groups.push(sequential);
7117
+ return groups;
7118
+ }
7119
+
5869
7120
  function serializeConfig(project) {
5870
- const { config, globalConfig } = project, viteConfig = project._vite?.config, optimizer = config.deps?.optimizer || {}, poolOptions = config.poolOptions, isolate = viteConfig?.test?.isolate;
7121
+ const { config, globalConfig } = project, viteConfig = project._vite?.config, optimizer = config.deps?.optimizer || {};
5871
7122
  return {
5872
7123
  environmentOptions: config.environmentOptions,
5873
7124
  mode: config.mode,
5874
7125
  isolate: config.isolate,
7126
+ fileParallelism: config.fileParallelism,
7127
+ maxWorkers: config.maxWorkers,
5875
7128
  base: config.base,
5876
7129
  logHeapUsage: config.logHeapUsage,
5877
7130
  runner: config.runner,
@@ -5911,18 +7164,6 @@ function serializeConfig(project) {
5911
7164
  };
5912
7165
  })(config.coverage),
5913
7166
  fakeTimers: config.fakeTimers,
5914
- poolOptions: {
5915
- forks: {
5916
- singleFork: poolOptions?.forks?.singleFork ?? globalConfig.poolOptions?.forks?.singleFork ?? false,
5917
- isolate: poolOptions?.forks?.isolate ?? isolate ?? globalConfig.poolOptions?.forks?.isolate ?? true
5918
- },
5919
- threads: {
5920
- singleThread: poolOptions?.threads?.singleThread ?? globalConfig.poolOptions?.threads?.singleThread ?? false,
5921
- isolate: poolOptions?.threads?.isolate ?? isolate ?? globalConfig.poolOptions?.threads?.isolate ?? true
5922
- },
5923
- vmThreads: { singleThread: poolOptions?.vmThreads?.singleThread ?? globalConfig.poolOptions?.vmThreads?.singleThread ?? false },
5924
- vmForks: { singleFork: poolOptions?.vmForks?.singleFork ?? globalConfig.poolOptions?.vmForks?.singleFork ?? false }
5925
- },
5926
7167
  deps: {
5927
7168
  web: config.deps.web || {},
5928
7169
  optimizer: Object.entries(optimizer).reduce((acc, [name, option]) => {
@@ -5954,6 +7195,7 @@ function serializeConfig(project) {
5954
7195
  ...config.env
5955
7196
  },
5956
7197
  browser: ((browser) => {
7198
+ const provider = project.browser?.provider;
5957
7199
  return {
5958
7200
  name: browser.name,
5959
7201
  headless: browser.headless,
@@ -5963,8 +7205,9 @@ function serializeConfig(project) {
5963
7205
  viewport: browser.viewport,
5964
7206
  screenshotFailures: browser.screenshotFailures,
5965
7207
  locators: { testIdAttribute: browser.locators.testIdAttribute },
5966
- providerOptions: browser.provider === "playwright" ? { actionTimeout: browser.providerOptions?.context?.actionTimeout } : {},
5967
- trackUnhandledErrors: browser.trackUnhandledErrors ?? true
7208
+ providerOptions: provider?.name === "playwright" ? { actionTimeout: provider?.options?.actionTimeout } : {},
7209
+ trackUnhandledErrors: browser.trackUnhandledErrors ?? true,
7210
+ trace: browser.trace.mode
5968
7211
  };
5969
7212
  })(config.browser),
5970
7213
  standalone: config.standalone,
@@ -6500,7 +7743,7 @@ function MetaEnvReplacerPlugin() {
6500
7743
  transform(code, id) {
6501
7744
  if (!/\bimport\.meta\.env\b/.test(code)) return null;
6502
7745
  let s = null;
6503
- const cleanCode = stripLiteral(code), envs = cleanCode.matchAll(/\bimport\.meta\.env\b/g);
7746
+ const envs = stripLiteral(code).matchAll(/\bimport\.meta\.env\b/g);
6504
7747
  for (const env of envs) {
6505
7748
  s ||= new MagicString(code);
6506
7749
  const startIndex = env.index, endIndex = startIndex + env[0].length;
@@ -6632,9 +7875,7 @@ function isInline(id) {
6632
7875
  return cssInlineRE.test(id);
6633
7876
  }
6634
7877
  function getCSSModuleProxyReturn(strategy, filename) {
6635
- if (strategy === "non-scoped") return "style";
6636
- const hash = generateCssFilenameHash(filename);
6637
- return `\`_\${style}_${hash}\``;
7878
+ return strategy === "non-scoped" ? "style" : `\`_\${style}_${generateCssFilenameHash(filename)}\``;
6638
7879
  }
6639
7880
  function CSSEnablerPlugin(ctx) {
6640
7881
  const shouldProcessCSS = (id) => {
@@ -6656,12 +7897,12 @@ function CSSEnablerPlugin(ctx) {
6656
7897
  // return proxy for css modules, so that imported module has names:
6657
7898
  // styles.foo returns a "foo" instead of "undefined"
6658
7899
  // we don't use code content to generate hash for "scoped", because it's empty
6659
- const scopeStrategy = typeof ctx.config.css !== "boolean" && ctx.config.css.modules?.classNameStrategy || "stable", proxyReturn = getCSSModuleProxyReturn(scopeStrategy, relative(ctx.config.root, id)), code = `export default new Proxy(Object.create(null), {
7900
+ const scopeStrategy = typeof ctx.config.css !== "boolean" && ctx.config.css.modules?.classNameStrategy || "stable";
7901
+ return { code: `export default new Proxy(Object.create(null), {
6660
7902
  get(_, style) {
6661
- return ${proxyReturn};
7903
+ return ${getCSSModuleProxyReturn(scopeStrategy, relative(ctx.config.root, id))};
6662
7904
  },
6663
- })`;
6664
- return { code };
7905
+ })` };
6665
7906
  }
6666
7907
  return { code: "export default \"\"" };
6667
7908
  }
@@ -6776,12 +8017,7 @@ function getDefaultResolveOptions() {
6776
8017
  };
6777
8018
  }
6778
8019
  function getDefaultServerConditions() {
6779
- const viteMajor = Number(version.split(".")[0]);
6780
- if (viteMajor >= 6) {
6781
- const conditions = vite.defaultServerConditions;
6782
- return conditions.filter((c) => c !== "module");
6783
- }
6784
- return ["node"];
8020
+ return Number(version.split(".")[0]) >= 6 ? vite.defaultServerConditions.filter((c) => c !== "module") : ["node"];
6785
8021
  }
6786
8022
 
6787
8023
  function ModuleRunnerTransform() {
@@ -6838,16 +8074,17 @@ function VitestProjectResolver(ctx) {
6838
8074
  const plugin = {
6839
8075
  name: "vitest:resolve-root",
6840
8076
  enforce: "pre",
6841
- async resolveId(id, _, { ssr }) {
6842
- if (id === "vitest" || id.startsWith("@vitest/") || id.startsWith("vitest/")) {
6843
- // always redirect the request to the root vitest plugin since
6844
- // it will be the one used to run Vitest
6845
- const resolved = await ctx.vite.pluginContainer.resolveId(id, void 0, {
6846
- skip: new Set([plugin]),
6847
- ssr
6848
- });
6849
- return resolved;
8077
+ config: {
8078
+ order: "post",
8079
+ handler() {
8080
+ return { base: "/" };
6850
8081
  }
8082
+ },
8083
+ async resolveId(id, _, { ssr }) {
8084
+ if (id === "vitest" || id.startsWith("@vitest/") || id.startsWith("vitest/")) return await ctx.vite.pluginContainer.resolveId(id, void 0, {
8085
+ skip: new Set([plugin]),
8086
+ ssr
8087
+ });
6851
8088
  }
6852
8089
  };
6853
8090
  return plugin;
@@ -6856,6 +8093,12 @@ function VitestCoreResolver(ctx) {
6856
8093
  return {
6857
8094
  name: "vitest:resolve-core",
6858
8095
  enforce: "pre",
8096
+ config: {
8097
+ order: "post",
8098
+ handler() {
8099
+ return { base: "/" };
8100
+ }
8101
+ },
6859
8102
  async resolveId(id) {
6860
8103
  if (id === "vitest") return resolve(distDir, "index.js");
6861
8104
  if (id.startsWith("@vitest/") || id.startsWith("vitest/"))
@@ -6882,24 +8125,22 @@ function WorkspaceVitestPlugin(project, options) {
6882
8125
  if (existsSync(pkgJsonPath)) name = JSON.parse(readFileSync(pkgJsonPath, "utf-8")).name;
6883
8126
  if (typeof name !== "string" || !name) name = basename(dir);
6884
8127
  } else name = options.workspacePath.toString();
6885
- const isUserBrowserEnabled = viteConfig.test?.browser?.enabled, isBrowserEnabled = isUserBrowserEnabled ?? (viteConfig.test?.browser && project.vitest._cliOptions.browser?.enabled), workspaceNames = [name], browser = viteConfig.test.browser || {};
8128
+ const isBrowserEnabled = viteConfig.test?.browser?.enabled ?? (viteConfig.test?.browser && project.vitest._cliOptions.browser?.enabled), workspaceNames = [name], browser = viteConfig.test.browser || {};
6886
8129
  if (isBrowserEnabled && browser.name && !browser.instances?.length)
6887
8130
  // vitest injects `instances` in this case later on
6888
8131
  workspaceNames.push(name ? `${name} (${browser.name})` : browser.name);
6889
- viteConfig.test?.browser?.instances?.forEach((instance) => {
6890
- if (instance.name ??= name ? `${name} (${instance.browser})` : instance.browser, isBrowserEnabled) workspaceNames.push(instance.name);
6891
- });
6892
- const filters = project.vitest.config.project;
6893
8132
  // if there is `--project=...` filter, check if any of the potential projects match
6894
8133
  // if projects don't match, we ignore the test project altogether
6895
8134
  // if some of them match, they will later be filtered again by `resolveWorkspace`
6896
- if (filters.length) {
6897
- const hasProject = workspaceNames.some((name) => {
8135
+ if (viteConfig.test?.browser?.instances?.forEach((instance) => {
8136
+ if (instance.name ??= name ? `${name} (${instance.browser})` : instance.browser, isBrowserEnabled) workspaceNames.push(instance.name);
8137
+ }), project.vitest.config.project.length) {
8138
+ if (!workspaceNames.some((name) => {
6898
8139
  return project.vitest.matchesProjectFilter(name);
6899
- });
6900
- if (!hasProject) throw new VitestFilteredOutProjectError();
8140
+ })) throw new VitestFilteredOutProjectError();
6901
8141
  }
6902
8142
  return {
8143
+ base: "/",
6903
8144
  environments: { __vitest__: { dev: {} } },
6904
8145
  test: { name: {
6905
8146
  label: name,
@@ -7038,13 +8279,11 @@ async function isValidNodeImport(id) {
7038
8279
  const extension = extname(id);
7039
8280
  if (BUILTIN_EXTENSIONS.has(extension)) return true;
7040
8281
  if (extension !== ".js") return false;
7041
- id = id.replace("file:///", "");
7042
- const package_ = findNearestPackageData(dirname(id));
7043
- if (package_.type === "module") return true;
8282
+ if (id = id.replace("file:///", ""), findNearestPackageData(dirname(id)).type === "module") return true;
7044
8283
  if (/\.(?:\w+-)?esm?(?:-\w+)?\.js$|\/esm?\//.test(id)) return false;
7045
8284
  try {
7046
8285
  await esModuleLexer.init;
7047
- const code = await promises.readFile(id, "utf8"), [, , , hasModuleSyntax] = esModuleLexer.parse(code);
8286
+ const code = await promises$1.readFile(id, "utf8"), [, , , hasModuleSyntax] = esModuleLexer.parse(code);
7048
8287
  return !hasModuleSyntax;
7049
8288
  } catch {
7050
8289
  return false;
@@ -7064,8 +8303,8 @@ async function _shouldExternalize(id, options) {
7064
8303
  // Unless the user explicitly opted to inline them, externalize Vite deps.
7065
8304
  // They are too big to inline by default.
7066
8305
  if (matchExternalizePattern(id, moduleDirectories, options?.external) || options?.cacheDir && id.includes(options.cacheDir)) return id;
7067
- const isLibraryModule = moduleDirectories.some((dir) => id.includes(dir)), guessCJS = isLibraryModule && options?.fallbackCJS;
7068
- return id = guessCJS ? guessCJSversion(id) || id : id, matchExternalizePattern(id, moduleDirectories, defaultInline) ? false : matchExternalizePattern(id, moduleDirectories, depsExternal) || isLibraryModule && await isValidNodeImport(id) ? id : false;
8306
+ const isLibraryModule = moduleDirectories.some((dir) => id.includes(dir));
8307
+ return id = isLibraryModule && options?.fallbackCJS ? guessCJSversion(id) || id : id, matchExternalizePattern(id, moduleDirectories, defaultInline) ? false : matchExternalizePattern(id, moduleDirectories, depsExternal) || isLibraryModule && await isValidNodeImport(id) ? id : false;
7069
8308
  }
7070
8309
  function matchExternalizePattern(id, moduleDirectories, patterns) {
7071
8310
  if (patterns == null) return false;
@@ -7107,7 +8346,7 @@ class TestSpecification {
7107
8346
  */
7108
8347
  get testModule() {
7109
8348
  const task = this.project.vitest.state.idMap.get(this.taskId);
7110
- return task ? this.project.vitest.state.getReportedEntity(task) : void 0;
8349
+ if (task) return this.project.vitest.state.getReportedEntity(task);
7111
8350
  }
7112
8351
  toJSON() {
7113
8352
  return [
@@ -7138,7 +8377,6 @@ async function createViteServer(inlineConfig) {
7138
8377
  class TestProject {
7139
8378
  /**
7140
8379
  * The global Vitest instance.
7141
- * @experimental The public Vitest API is experimental and does not follow semver.
7142
8380
  */
7143
8381
  vitest;
7144
8382
  /**
@@ -7152,12 +8390,13 @@ class TestProject {
7152
8390
  /**
7153
8391
  * Temporary directory for the project. This is unique for each project. Vitest stores transformed content here.
7154
8392
  */
7155
- tmpDir = join(tmpdir(), nanoid());
8393
+ tmpDir;
7156
8394
  /** @internal */ typechecker;
7157
8395
  /** @internal */ _config;
7158
8396
  /** @internal */ _vite;
7159
8397
  /** @internal */ _hash;
7160
8398
  /** @internal */ _resolver;
8399
+ /** @internal */ _fetcher;
7161
8400
  /** @internal */ _serializedDefines;
7162
8401
  /** @inetrnal */ testFilesList = null;
7163
8402
  runner;
@@ -7165,8 +8404,8 @@ class TestProject {
7165
8404
  typecheckFilesList = null;
7166
8405
  _globalSetups;
7167
8406
  _provided = {};
7168
- constructor(vitest, options) {
7169
- this.options = options, this.vitest = vitest, this.globalConfig = vitest.config;
8407
+ constructor(vitest, options, tmpDir) {
8408
+ this.options = options, this.vitest = vitest, this.globalConfig = vitest.config, this.tmpDir = tmpDir || join(tmpdir(), nanoid());
7170
8409
  }
7171
8410
  /**
7172
8411
  * The unique hash of this project. This value is consistent between the reruns.
@@ -7301,7 +8540,7 @@ class TestProject {
7301
8540
  const files = await this.globFiles(includeSource, exclude, cwd);
7302
8541
  await Promise.all(files.map(async (file) => {
7303
8542
  try {
7304
- const code = await promises.readFile(file, "utf-8");
8543
+ const code = await promises$1.readFile(file, "utf-8");
7305
8544
  if (this.isInSourceTestCode(code)) testFiles.push(file);
7306
8545
  } catch {
7307
8546
  return null;
@@ -7336,16 +8575,15 @@ class TestProject {
7336
8575
  }
7337
8576
  /** @internal */
7338
8577
  async globFiles(include, exclude, cwd) {
7339
- const globOptions = {
8578
+ // keep the slashes consistent with Vite
8579
+ // we are not using the pathe here because it normalizes the drive letter on Windows
8580
+ // and we want to keep it the same as working dir
8581
+ return (await glob(include, {
7340
8582
  dot: true,
7341
8583
  cwd,
7342
8584
  ignore: exclude,
7343
8585
  expandDirectories: false
7344
- }, files = await glob(include, globOptions);
7345
- // keep the slashes consistent with Vite
7346
- // we are not using the pathe here because it normalizes the drive letter on Windows
7347
- // and we want to keep it the same as working dir
7348
- return files.map((file) => slash(path.resolve(cwd, file)));
8586
+ })).map((file) => slash(path.resolve(cwd, file)));
7349
8587
  }
7350
8588
  /**
7351
8589
  * Test if a file matches the test globs. This does the actual glob matching if the test is not cached, unlike `isCachedTestFile`.
@@ -7380,28 +8618,22 @@ class TestProject {
7380
8618
  /** @internal */
7381
8619
  _parent;
7382
8620
  /** @internal */
7383
- _initParentBrowser = deduped(async () => {
8621
+ _initParentBrowser = deduped(async (childProject) => {
7384
8622
  if (!this.isBrowserEnabled() || this._parentBrowser) return;
7385
- await this.vitest.packageInstaller.ensureInstalled("@vitest/browser", this.config.root, this.vitest.version);
7386
- const { createBrowserServer, distRoot } = await import('@vitest/browser');
7387
- let cacheDir;
7388
- const browser = await createBrowserServer(this, this.vite.config.configFile, [
7389
- {
7390
- name: "vitest:browser-cacheDir",
7391
- configResolved(config) {
7392
- cacheDir = config.cacheDir;
7393
- }
7394
- },
7395
- ...MocksPlugins({ filter(id) {
7396
- return !(id.includes(distRoot) || id.includes(cacheDir));
7397
- } }),
7398
- MetaEnvReplacerPlugin()
7399
- ], [CoverageTransform(this.vitest)]);
8623
+ const provider = this.config.browser.provider || childProject.config.browser.provider;
8624
+ if (provider == null) throw new Error(`Proider was not specified in the "browser.provider" setting. Please, pass down playwright(), webdriverio() or preview() from "@vitest/browser-playwright", "@vitest/browser-webdriverio" or "@vitest/browser-preview" package respectively.`);
8625
+ if (typeof provider.serverFactory !== "function") throw new TypeError(`The browser provider options do not return a "serverFactory" function. Are you using the latest "@vitest/browser-${provider.name}" package?`);
8626
+ const browser = await provider.serverFactory({
8627
+ project: this,
8628
+ mocksPlugins: (options) => MocksPlugins(options),
8629
+ metaEnvReplacer: () => MetaEnvReplacerPlugin(),
8630
+ coveragePlugin: () => CoverageTransform(this.vitest)
8631
+ });
7400
8632
  if (this._parentBrowser = browser, this.config.browser.ui) setup(this.vitest, browser.vite);
7401
8633
  });
7402
8634
  /** @internal */
7403
8635
  _initBrowserServer = deduped(async () => {
7404
- if (await this._parent?._initParentBrowser(), !this.browser && this._parent?._parentBrowser) this.browser = this._parent._parentBrowser.spawn(this), await this.vitest.report("onBrowserInit", this);
8636
+ if (await this._parent?._initParentBrowser(this), !this.browser && this._parent?._parentBrowser) this.browser = this._parent._parentBrowser.spawn(this), await this.vitest.report("onBrowserInit", this);
7405
8637
  });
7406
8638
  /**
7407
8639
  * Closes the project and all associated resources. This can only be called once; the closing promise is cached until the server restarts.
@@ -7414,6 +8646,8 @@ class TestProject {
7414
8646
  this.browser?.close(),
7415
8647
  this.clearTmpDir()
7416
8648
  ].filter(Boolean)).then(() => {
8649
+ if (!this.runner.isClosed()) return this.runner.close();
8650
+ }).then(() => {
7417
8651
  this._provided = {}, this._vite = void 0;
7418
8652
  });
7419
8653
  return this.closingPromise;
@@ -7438,9 +8672,12 @@ class TestProject {
7438
8672
  // type is very strict here, so we cast it to any
7439
8673
  this.provide(providedKey, this.config.provide[providedKey]);
7440
8674
  }
7441
- this.closingPromise = void 0, this._resolver = new VitestResolver(server.config.cacheDir, this._config), this._vite = server, this._serializedDefines = createDefinesScript(server.config.define);
8675
+ this.closingPromise = void 0, this._resolver = new VitestResolver(server.config.cacheDir, this._config), this._vite = server, this._serializedDefines = createDefinesScript(server.config.define), this._fetcher = createFetchModuleFunction(this._resolver, this.tmpDir, {
8676
+ dumpFolder: this.config.dumpDir,
8677
+ readFromDump: this.config.server.debug?.load ?? process.env.VITEST_DEBUG_LOAD_DUMP != null
8678
+ });
7442
8679
  const environment = server.environments.__vitest__;
7443
- this.runner = new ServerModuleRunner(environment, this._resolver, this._config);
8680
+ this.runner = new ServerModuleRunner(environment, this._fetcher, this._config);
7444
8681
  }
7445
8682
  _serializeOverriddenConfig() {
7446
8683
  // TODO: serialize the config _once_ or when needed
@@ -7469,13 +8706,13 @@ class TestProject {
7469
8706
  }
7470
8707
  /** @internal */
7471
8708
  static _createBasicProject(vitest) {
7472
- const project = new TestProject(vitest);
7473
- return project.runner = vitest.runner, project._vite = vitest.vite, project._config = vitest.config, project._resolver = vitest._resolver, project._serializedDefines = createDefinesScript(vitest.vite.config.define), project._setHash(), project._provideObject(vitest.config.provide), project;
8709
+ const project = new TestProject(vitest, void 0, vitest._tmpDir);
8710
+ return project.runner = vitest.runner, project._vite = vitest.vite, project._config = vitest.config, project._resolver = vitest._resolver, project._fetcher = vitest._fetcher, project._serializedDefines = createDefinesScript(vitest.vite.config.define), project._setHash(), project._provideObject(vitest.config.provide), project;
7474
8711
  }
7475
8712
  /** @internal */
7476
8713
  static _cloneBrowserProject(parent, config) {
7477
- const clone = new TestProject(parent.vitest);
7478
- return clone.runner = parent.runner, clone._vite = parent._vite, clone._resolver = parent._resolver, clone._config = config, clone._setHash(), clone._parent = parent, clone._serializedDefines = parent._serializedDefines, clone._provideObject(config.provide), clone;
8714
+ const clone = new TestProject(parent.vitest, void 0, parent.tmpDir);
8715
+ return clone.runner = parent.runner, clone._vite = parent._vite, clone._resolver = parent._resolver, clone._fetcher = parent._fetcher, clone._config = config, clone._setHash(), clone._parent = parent, clone._serializedDefines = parent._serializedDefines, clone._provideObject(config.provide), clone;
7479
8716
  }
7480
8717
  }
7481
8718
  function deduped(cb) {
@@ -7510,8 +8747,13 @@ function generateHash(str) {
7510
8747
  return `${hash}`;
7511
8748
  }
7512
8749
 
8750
+ // vitest.config.*
8751
+ // vite.config.*
8752
+ // vitest.unit.config.*
8753
+ // vite.unit.config.*
8754
+ const CONFIG_REGEXP = /^vite(?:st)?(?:\.\w+)?\.config\./;
7513
8755
  async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projectsDefinition, names) {
7514
- const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition), overridesOptions = [
8756
+ const { configFiles, projectConfigs, nonConfigDirectories } = await resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDefinition), cliOverrides = [
7515
8757
  "logHeapUsage",
7516
8758
  "allowOnly",
7517
8759
  "sequence",
@@ -7530,7 +8772,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
7530
8772
  "inspect",
7531
8773
  "inspectBrk",
7532
8774
  "fileParallelism"
7533
- ], cliOverrides = overridesOptions.reduce((acc, name) => {
8775
+ ].reduce((acc, name) => {
7534
8776
  if (name in cliOptions) acc[name] = cliOptions[name];
7535
8777
  return acc;
7536
8778
  }, {}), projectPromises = [], fileProjects = [...configFiles, ...nonConfigDirectories], concurrent = limitConcurrency(nodeos__default.availableParallelism?.() || nodeos__default.cpus().length || 5);
@@ -7598,19 +8840,13 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
7598
8840
  }
7599
8841
  async function resolveBrowserProjects(vitest, names, resolvedProjects) {
7600
8842
  const removeProjects = /* @__PURE__ */ new Set();
7601
- resolvedProjects.forEach((project) => {
8843
+ return resolvedProjects.forEach((project) => {
7602
8844
  if (!project.config.browser.enabled) return;
7603
- const instances = project.config.browser.instances || [], browser = project.config.browser.name;
7604
- if (instances.length === 0 && browser) instances.push({
7605
- browser,
7606
- name: project.name ? `${project.name} (${browser})` : browser
7607
- }), vitest.logger.warn(withLabel("yellow", "Vitest", [
7608
- `No browser "instances" were defined`,
7609
- project.name ? ` for the "${project.name}" project. ` : ". ",
7610
- `Running tests in "${project.config.browser.name}" browser. `,
7611
- "The \"browser.name\" field is deprecated since Vitest 3. ",
7612
- "Read more: https://vitest.dev/guide/browser/config#browser-instances"
7613
- ].filter(Boolean).join("")));
8845
+ const instances = project.config.browser.instances || [];
8846
+ if (instances.length === 0) {
8847
+ removeProjects.add(project);
8848
+ return;
8849
+ }
7614
8850
  const originalName = project.config.name, filteredInstances = vitest.matchesProjectFilter(originalName) ? instances : instances.filter((instance) => {
7615
8851
  const newName = instance.name;
7616
8852
  return vitest.matchesProjectFilter(newName);
@@ -7620,7 +8856,6 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
7620
8856
  removeProjects.add(project);
7621
8857
  return;
7622
8858
  }
7623
- if (project.config.browser.providerOptions) vitest.logger.warn(withLabel("yellow", "Vitest", `"providerOptions"${originalName ? ` in "${originalName}" project` : ""} is ignored because it's overridden by the configs. To hide this warning, remove the "providerOptions" property from the browser configuration.`));
7624
8859
  filteredInstances.forEach((config, index) => {
7625
8860
  const browser = config.browser;
7626
8861
  if (!browser) {
@@ -7629,6 +8864,7 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
7629
8864
  }
7630
8865
  const name = config.name;
7631
8866
  if (name == null) throw new Error(`The browser configuration must have a "name" property. This is a bug in Vitest. Please, open a new issue with reproduction`);
8867
+ if (config.provider?.name != null && project.config.browser.provider?.name != null && config.provider?.name !== project.config.browser.provider?.name) throw new Error(`The instance cannot have a different provider from its parent. The "${name}" instance specifies "${config.provider?.name}" provider, but its parent has a "${project.config.browser.provider?.name}" provider.`);
7632
8868
  if (names.has(name)) throw new Error([
7633
8869
  `Cannot define a nested project for a ${browser} browser. The project name "${name}" was already defined. `,
7634
8870
  "If you have multiple instances for the same browser, make sure to define a custom \"name\". ",
@@ -7640,29 +8876,10 @@ async function resolveBrowserProjects(vitest, names, resolvedProjects) {
7640
8876
  const clone = TestProject._cloneBrowserProject(project, clonedConfig);
7641
8877
  resolvedProjects.push(clone);
7642
8878
  }), removeProjects.add(project);
7643
- }), resolvedProjects = resolvedProjects.filter((project) => !removeProjects.has(project));
7644
- const headedBrowserProjects = resolvedProjects.filter((project) => {
7645
- return project.config.browser.enabled && !project.config.browser.headless;
7646
- });
7647
- if (headedBrowserProjects.length > 1) {
7648
- const message = [`Found multiple projects that run browser tests in headed mode: "${headedBrowserProjects.map((p) => p.name).join("\", \"")}".`, ` Vitest cannot run multiple headed browsers at the same time.`].join("");
7649
- if (!isTTY) throw new Error(`${message} Please, filter projects with --browser=name or --project=name flag or run tests with "headless: true" option.`);
7650
- const prompts = await import('./index.X0nbfr6-.js').then(function (n) { return n.i; }), { projectName } = await prompts.default({
7651
- type: "select",
7652
- name: "projectName",
7653
- choices: headedBrowserProjects.map((project) => ({
7654
- title: project.name,
7655
- value: project.name
7656
- })),
7657
- message: `${message} Select a single project to run or cancel and run tests with "headless: true" option. Note that you can also start tests with --browser=name or --project=name flag.`
7658
- });
7659
- if (!projectName) throw new Error("The test run was aborted.");
7660
- return resolvedProjects.filter((project) => project.name === projectName);
7661
- }
7662
- return resolvedProjects;
8879
+ }), resolvedProjects.filter((project) => !removeProjects.has(project));
7663
8880
  }
7664
8881
  function cloneConfig(project, { browser,...config }) {
7665
- const { locators, viewport, testerHtmlPath, headless, screenshotDirectory, screenshotFailures, browser: _browser, name,...overrideConfig } = config, currentConfig = project.config.browser, clonedConfig = deepClone(project.config);
8882
+ const { locators, viewport, testerHtmlPath, headless, screenshotDirectory, screenshotFailures, browser: _browser, name, provider,...overrideConfig } = config, currentConfig = project.config.browser, clonedConfig = deepClone(project.config);
7666
8883
  return mergeConfig({
7667
8884
  ...clonedConfig,
7668
8885
  browser: {
@@ -7673,9 +8890,9 @@ function cloneConfig(project, { browser,...config }) {
7673
8890
  screenshotDirectory: screenshotDirectory ?? currentConfig.screenshotDirectory,
7674
8891
  screenshotFailures: screenshotFailures ?? currentConfig.screenshotFailures,
7675
8892
  headless: headless ?? currentConfig.headless,
8893
+ provider: provider ?? currentConfig.provider,
7676
8894
  name: browser,
7677
- providerOptions: config,
7678
- instances: void 0
8895
+ instances: []
7679
8896
  },
7680
8897
  include: overrideConfig.include && overrideConfig.include.length > 0 ? [] : clonedConfig.include,
7681
8898
  exclude: overrideConfig.exclude && overrideConfig.exclude.length > 0 ? [] : clonedConfig.exclude,
@@ -7695,14 +8912,17 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
7695
8912
  const note = "Projects definition";
7696
8913
  throw new Error(`${note} references a non-existing file or a directory: ${file}`);
7697
8914
  }
7698
- const stats = await promises.stat(file);
8915
+ const stats = statSync(file);
7699
8916
  // user can specify a config file directly
7700
- if (stats.isFile()) projectsConfigFiles.push(file);
7701
- else if (stats.isDirectory()) {
7702
- const configFile = await resolveDirectoryConfig(file);
8917
+ if (stats.isFile()) {
8918
+ const name = basename(file);
8919
+ if (!CONFIG_REGEXP.test(name)) throw new Error(`The file "${relative(vitest.config.root, file)}" must start with "vitest.config"/"vite.config" or match the pattern "(vitest|vite).*.config.*" to be a valid project config.`);
8920
+ projectsConfigFiles.push(file);
8921
+ } else if (stats.isDirectory()) {
8922
+ const configFile = resolveDirectoryConfig(file);
7703
8923
  if (configFile) projectsConfigFiles.push(configFile);
7704
8924
  else {
7705
- const directory = file[file.length - 1] === "/" ? file : `${file}/`;
8925
+ const directory = file.at(-1) === "/" ? file : `${file}/`;
7706
8926
  nonConfigProjectDirectories.push(directory);
7707
8927
  }
7708
8928
  } else
@@ -7728,16 +8948,20 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
7728
8948
  "**/*.timestamp-*",
7729
8949
  "**/.DS_Store"
7730
8950
  ]
7731
- }, projectsFs = await glob(projectsGlobMatches, globOptions);
7732
- await Promise.all(projectsFs.map(async (path) => {
8951
+ };
8952
+ (await glob(projectsGlobMatches, globOptions)).forEach((path) => {
7733
8953
  // directories are allowed with a glob like `packages/*`
7734
8954
  // in this case every directory is treated as a project
7735
8955
  if (path.endsWith("/")) {
7736
- const configFile = await resolveDirectoryConfig(path);
8956
+ const configFile = resolveDirectoryConfig(path);
7737
8957
  if (configFile) projectsConfigFiles.push(configFile);
7738
8958
  else nonConfigProjectDirectories.push(path);
7739
- } else projectsConfigFiles.push(path);
7740
- }));
8959
+ } else {
8960
+ const name = basename(path);
8961
+ if (!CONFIG_REGEXP.test(name)) throw new Error(`The projects glob matched a file "${relative(vitest.config.root, path)}", but it should also either start with "vitest.config"/"vite.config" or match the pattern "(vitest|vite).*.config.*".`);
8962
+ projectsConfigFiles.push(path);
8963
+ }
8964
+ });
7741
8965
  }
7742
8966
  const projectConfigFiles = Array.from(new Set(projectsConfigFiles));
7743
8967
  return {
@@ -7746,16 +8970,13 @@ async function resolveTestProjectConfigs(vitest, workspaceConfigPath, projectsDe
7746
8970
  configFiles: projectConfigFiles
7747
8971
  };
7748
8972
  }
7749
- async function resolveDirectoryConfig(directory) {
7750
- const files = new Set(await promises.readdir(directory)), configFile = configFiles.find((file) => files.has(file));
8973
+ function resolveDirectoryConfig(directory) {
8974
+ const files = new Set(readdirSync(directory)), configFile = configFiles.find((file) => files.has(file));
7751
8975
  return configFile ? resolve(directory, configFile) : null;
7752
8976
  }
7753
8977
  function getDefaultTestProject(vitest) {
7754
8978
  const filter = vitest.config.project, project = vitest._ensureRootProject();
7755
- if (!filter.length) return project;
7756
- // check for the project name and browser names
7757
- const hasProjects = getPotentialProjectNames(project).some((p) => vitest.matchesProjectFilter(p));
7758
- return hasProjects ? project : null;
8979
+ return !filter.length || getPotentialProjectNames(project).some((p) => vitest.matchesProjectFilter(p)) ? project : null;
7759
8980
  }
7760
8981
  function getPotentialProjectNames(project) {
7761
8982
  const names = [project.name];
@@ -7778,17 +8999,11 @@ function createReporters(reporterReferences, ctx) {
7778
8999
  const runner = ctx.runner, promisedReporters = reporterReferences.map(async (referenceOrInstance) => {
7779
9000
  if (Array.isArray(referenceOrInstance)) {
7780
9001
  const [reporterName, reporterOptions] = referenceOrInstance;
7781
- if (reporterName === "html") {
7782
- await ctx.packageInstaller.ensureInstalled("@vitest/ui", ctx.config.root, ctx.version);
7783
- const CustomReporter = await loadCustomReporterModule("@vitest/ui/reporter", runner);
7784
- return new CustomReporter(reporterOptions);
7785
- } else if (reporterName in ReportersMap) {
9002
+ if (reporterName === "html") return await ctx.packageInstaller.ensureInstalled("@vitest/ui", ctx.config.root, ctx.version), new (await (loadCustomReporterModule("@vitest/ui/reporter", runner)))(reporterOptions);
9003
+ if (reporterName in ReportersMap) {
7786
9004
  const BuiltinReporter = ReportersMap[reporterName];
7787
9005
  return new BuiltinReporter(reporterOptions);
7788
- } else {
7789
- const CustomReporter = await loadCustomReporterModule(reporterName, runner);
7790
- return new CustomReporter(reporterOptions);
7791
- }
9006
+ } else return new (await (loadCustomReporterModule(reporterName, runner)))(reporterOptions);
7792
9007
  }
7793
9008
  return referenceOrInstance;
7794
9009
  });
@@ -7799,10 +9014,7 @@ function createBenchmarkReporters(reporterReferences, runner) {
7799
9014
  if (typeof referenceOrInstance === "string") if (referenceOrInstance in BenchmarkReportsMap) {
7800
9015
  const BuiltinReporter = BenchmarkReportsMap[referenceOrInstance];
7801
9016
  return new BuiltinReporter();
7802
- } else {
7803
- const CustomReporter = await loadCustomReporterModule(referenceOrInstance, runner);
7804
- return new CustomReporter();
7805
- }
9017
+ } else return new (await (loadCustomReporterModule(referenceOrInstance, runner)))();
7806
9018
  return referenceOrInstance;
7807
9019
  });
7808
9020
  return Promise.all(promisedReporters);
@@ -7820,11 +9032,11 @@ function parseFilter(filter) {
7820
9032
  return { filename: filter };
7821
9033
  }
7822
9034
  function groupFilters(filters) {
7823
- const groupedFilters_ = groupBy(filters, (f) => f.filename), groupedFilters = Object.fromEntries(Object.entries(groupedFilters_).map((entry) => {
9035
+ const groupedFilters_ = groupBy(filters, (f) => f.filename);
9036
+ return Object.fromEntries(Object.entries(groupedFilters_).map((entry) => {
7824
9037
  const [filename, filters] = entry, testLocations = filters.map((f) => f.lineNumber);
7825
9038
  return [filename, testLocations.filter((l) => l !== void 0)];
7826
9039
  }));
7827
- return groupedFilters;
7828
9040
  }
7829
9041
 
7830
9042
  class VitestSpecifications {
@@ -7885,7 +9097,7 @@ class VitestSpecifications {
7885
9097
  }
7886
9098
  async filterTestsBySource(specs) {
7887
9099
  if (this.vitest.config.changed && !this.vitest.config.related) {
7888
- const { VitestGit } = await import('./git.BFNcloKD.js'), vitestGit = new VitestGit(this.vitest.config.root), related = await vitestGit.findChangedFiles({ changedSince: this.vitest.config.changed });
9100
+ const { VitestGit } = await import('./git.BFNcloKD.js'), related = await new VitestGit(this.vitest.config.root).findChangedFiles({ changedSince: this.vitest.config.changed });
7889
9101
  if (!related) throw process.exitCode = 1, new GitNotFoundError();
7890
9102
  this.vitest.config.related = Array.from(new Set(related));
7891
9103
  }
@@ -7909,7 +9121,7 @@ class VitestSpecifications {
7909
9121
  const addImports = async (project, filepath) => {
7910
9122
  if (deps.has(filepath)) return;
7911
9123
  deps.add(filepath);
7912
- const mod = project.vite.environments.ssr.moduleGraph.getModuleById(filepath), transformed = mod?.transformResult || await project.vite.environments.ssr.transformRequest(filepath);
9124
+ const transformed = project.vite.environments.ssr.moduleGraph.getModuleById(filepath)?.transformResult || await project.vite.environments.ssr.transformRequest(filepath);
7913
9125
  if (!transformed) return;
7914
9126
  const dependencies = [...transformed.deps || [], ...transformed.dynamicDeps || []];
7915
9127
  await Promise.all(dependencies.map(async (dep) => {
@@ -8047,10 +9259,10 @@ class TestCase extends ReportedTaskImplementation {
8047
9259
  diagnostic() {
8048
9260
  const result = this.task.result;
8049
9261
  // startTime should always be available if the test has properly finished
8050
- if (!result || !result.startTime) return void 0;
8051
- const duration = result.duration || 0, slow = duration > this.project.globalConfig.slowTestThreshold;
9262
+ if (!result || !result.startTime) return;
9263
+ const duration = result.duration || 0;
8052
9264
  return {
8053
- slow,
9265
+ slow: duration > this.project.globalConfig.slowTestThreshold,
8054
9266
  heap: result.heap,
8055
9267
  duration,
8056
9268
  startTime: result.startTime,
@@ -8189,16 +9401,19 @@ class TestModule extends SuiteImplementation {
8189
9401
  * This value corresponds to the ID in the Vite's module graph.
8190
9402
  */
8191
9403
  moduleId;
9404
+ /**
9405
+ * Module id relative to the project. This is the same as `task.name`.
9406
+ */
9407
+ relativeModuleId;
8192
9408
  /** @internal */
8193
9409
  constructor(task, project) {
8194
- super(task, project), this.moduleId = task.filepath;
9410
+ super(task, project), this.moduleId = task.filepath, this.relativeModuleId = task.name;
8195
9411
  }
8196
9412
  /**
8197
9413
  * Checks the running state of the test file.
8198
9414
  */
8199
9415
  state() {
8200
- const state = this.task.result?.state;
8201
- return state === "queued" ? "queued" : getSuiteState(this.task);
9416
+ return this.task.result?.state === "queued" ? "queued" : getSuiteState(this.task);
8202
9417
  }
8203
9418
  /**
8204
9419
  * Useful information about the module like duration, memory usage, etc.
@@ -8257,10 +9472,10 @@ class StateManager {
8257
9472
  idMap = /* @__PURE__ */ new Map();
8258
9473
  taskFileMap = /* @__PURE__ */ new WeakMap();
8259
9474
  errorsSet = /* @__PURE__ */ new Set();
8260
- processTimeoutCauses = /* @__PURE__ */ new Set();
8261
9475
  reportedTasksMap = /* @__PURE__ */ new WeakMap();
8262
9476
  blobs;
8263
9477
  transformTime = 0;
9478
+ metadata = {};
8264
9479
  onUnhandledError;
8265
9480
  /** @internal */
8266
9481
  _data = {
@@ -8291,12 +9506,6 @@ class StateManager {
8291
9506
  getUnhandledErrors() {
8292
9507
  return Array.from(this.errorsSet.values());
8293
9508
  }
8294
- addProcessTimeoutCause(cause) {
8295
- this.processTimeoutCauses.add(cause);
8296
- }
8297
- getProcessTimeoutCauses() {
8298
- return Array.from(this.processTimeoutCauses.values());
8299
- }
8300
9509
  getPaths() {
8301
9510
  return Array.from(this.pathsSet);
8302
9511
  }
@@ -8357,6 +9566,10 @@ class StateManager {
8357
9566
  getReportedEntity(task) {
8358
9567
  return this.reportedTasksMap.get(task);
8359
9568
  }
9569
+ getReportedEntityById(taskId) {
9570
+ const task = this.idMap.get(taskId);
9571
+ return task ? this.reportedTasksMap.get(task) : void 0;
9572
+ }
8360
9573
  updateTasks(packs) {
8361
9574
  for (const [id, result, meta] of packs) {
8362
9575
  const task = this.idMap.get(id);
@@ -8377,7 +9590,12 @@ class StateManager {
8377
9590
  return Array.from(this.idMap.values()).filter((t) => t.result?.state === "fail").length;
8378
9591
  }
8379
9592
  cancelFiles(files, project) {
8380
- this.collectFiles(project, files.map((filepath) => createFileTask$1(filepath, project.config.root, project.config.name)));
9593
+ // if we don't filter existing modules, they will be overriden by `collectFiles`
9594
+ const nonRegisteredFiles = files.filter(({ filepath }) => {
9595
+ const relativePath = relative(project.config.root, filepath), id = generateFileHash(relativePath, project.name);
9596
+ return !this.idMap.has(id);
9597
+ });
9598
+ this.collectFiles(project, nonRegisteredFiles.map((file) => createFileTask$1(file.filepath, project.config.root, project.config.name)));
8381
9599
  }
8382
9600
  }
8383
9601
 
@@ -8862,7 +10080,7 @@ class TestRun {
8862
10080
  async updated(update, events) {
8863
10081
  this.syncUpdateStacks(update), this.vitest.state.updateTasks(update);
8864
10082
  for (const [id, event, data] of events) await this.reportEvent(id, event, data).catch((error) => {
8865
- this.vitest.state.catchError(serializeError$1(error), "Unhandled Reporter Error");
10083
+ this.vitest.state.catchError(serializeValue(error), "Unhandled Reporter Error");
8866
10084
  });
8867
10085
  // TODO: what is the order or reports here?
8868
10086
  // "onTaskUpdate" in parallel with others or before all or after all?
@@ -8874,11 +10092,20 @@ class TestRun {
8874
10092
  // specification won't have the File task if they were filtered by the --shard command
8875
10093
  const modules = specifications.map((spec) => spec.testModule).filter((s) => s != null), state = this.vitest.isCancelling ? "interrupted" : this.hasFailed(modules) ? "failed" : "passed";
8876
10094
  if (state !== "passed") process.exitCode = 1;
8877
- await this.vitest.report("onTestRunEnd", modules, [...errors], state);
10095
+ for (const project in await this.vitest.report("onTestRunEnd", modules, [...errors], state), this.vitest.state.metadata) {
10096
+ const meta = this.vitest.state.metadata[project];
10097
+ if (!meta?.dumpDir) continue;
10098
+ const path = resolve(meta.dumpDir, "vitest-metadata.json");
10099
+ meta.outline = {
10100
+ externalized: Object.keys(meta.externalized).length,
10101
+ inlined: Object.keys(meta.tmps).length
10102
+ }, await writeFile(path, JSON.stringify(meta, null, 2), "utf-8"), this.vitest.logger.log(`Metadata written to ${path}`);
10103
+ }
8878
10104
  }
8879
10105
  hasFailed(modules) {
8880
10106
  return modules.length ? modules.some((m) => !m.ok()) : !this.vitest.config.passWithNoTests;
8881
10107
  }
10108
+ // make sure the error always has a "stacks" property
8882
10109
  syncUpdateStacks(update) {
8883
10110
  update.forEach(([taskId, result]) => {
8884
10111
  const task = this.vitest.state.idMap.get(taskId), isBrowser = task && task.file.pool === "browser";
@@ -8929,9 +10156,8 @@ class TestRun {
8929
10156
  const path = attachment.path;
8930
10157
  if (path && !path.startsWith("http://") && !path.startsWith("https://")) {
8931
10158
  const currentPath = resolve(project.config.root, path), hash = createHash("sha1").update(currentPath).digest("hex"), newPath = resolve(project.config.attachmentsDir, `${sanitizeFilePath(annotation.message)}-${hash}${extname(currentPath)}`);
8932
- await mkdir(dirname(newPath), { recursive: true }), await copyFile(currentPath, newPath), attachment.path = newPath;
8933
- const contentType = attachment.contentType ?? mime.getType(basename(currentPath));
8934
- attachment.contentType = contentType || void 0;
10159
+ if (!existsSync(project.config.attachmentsDir)) await mkdir(project.config.attachmentsDir, { recursive: true });
10160
+ await copyFile(currentPath, newPath), attachment.path = newPath, attachment.contentType = (attachment.contentType ?? mime.getType(basename(currentPath))) || void 0;
8935
10161
  }
8936
10162
  return attachment;
8937
10163
  }
@@ -8991,21 +10217,14 @@ class VitestWatcher {
8991
10217
  }), triggered;
8992
10218
  }
8993
10219
  onFileChange = (id) => {
8994
- id = slash(id), this.vitest.logger.clearHighlightCache(id), this.vitest.invalidateFile(id);
8995
- const testFiles = this.getTestFilesFromWatcherTrigger(id);
8996
- if (testFiles) this.scheduleRerun(id);
8997
- else {
8998
- const needsRerun = this.handleFileChanged(id);
8999
- if (needsRerun) this.scheduleRerun(id);
9000
- }
10220
+ if (id = slash(id), this.vitest.logger.clearHighlightCache(id), this.vitest.invalidateFile(id), this.getTestFilesFromWatcherTrigger(id)) this.scheduleRerun(id);
10221
+ else if (this.handleFileChanged(id)) this.scheduleRerun(id);
9001
10222
  };
9002
10223
  onFileDelete = (id) => {
9003
10224
  if (id = slash(id), this.vitest.logger.clearHighlightCache(id), this.invalidates.add(id), this.vitest.state.filesMap.has(id)) this.vitest.projects.forEach((project) => project._removeCachedTestFile(id)), this.vitest.state.filesMap.delete(id), this.vitest.cache.results.removeFromCache(id), this.vitest.cache.stats.removeStats(id), this.changedTests.delete(id), this.vitest.report("onTestRemoved", id);
9004
10225
  };
9005
10226
  onFileCreate = (id) => {
9006
- id = slash(id), this.vitest.invalidateFile(id);
9007
- const testFiles = this.getTestFilesFromWatcherTrigger(id);
9008
- if (testFiles) {
10227
+ if (id = slash(id), this.vitest.invalidateFile(id), this.getTestFilesFromWatcherTrigger(id)) {
9009
10228
  this.scheduleRerun(id);
9010
10229
  return;
9011
10230
  }
@@ -9014,11 +10233,7 @@ class VitestWatcher {
9014
10233
  if (this.vitest.projects.forEach((project) => {
9015
10234
  if (project.matchesTestGlob(id, () => fileContent ??= readFileSync(id, "utf-8"))) matchingProjects.push(project);
9016
10235
  }), matchingProjects.length > 0) this.changedTests.add(id), this.scheduleRerun(id);
9017
- else {
9018
- // it's possible that file was already there but watcher triggered "add" event instead
9019
- const needsRerun = this.handleFileChanged(id);
9020
- if (needsRerun) this.scheduleRerun(id);
9021
- }
10236
+ else if (this.handleFileChanged(id)) this.scheduleRerun(id);
9022
10237
  };
9023
10238
  handleSetupFile(filepath) {
9024
10239
  let isSetupFile = false;
@@ -9038,8 +10253,7 @@ class VitestWatcher {
9038
10253
  if (pm.isMatch(filepath, this.vitest.config.forceRerunTriggers)) return this.vitest.state.getFilepaths().forEach((file) => this.changedTests.add(file)), true;
9039
10254
  if (this.handleSetupFile(filepath)) return true;
9040
10255
  const projects = this.vitest.projects.filter((project) => {
9041
- const moduleGraph = project.browser?.vite.moduleGraph || project.vite.moduleGraph;
9042
- return moduleGraph.getModulesByFile(filepath)?.size;
10256
+ return (project.browser?.vite.moduleGraph || project.vite.moduleGraph).getModulesByFile(filepath)?.size;
9043
10257
  });
9044
10258
  if (!projects.length) return this.vitest.state.filesMap.has(filepath) || this.vitest.projects.some((project) => project._isCachedTestFile(filepath)) ? (this.changedTests.add(filepath), true) : false;
9045
10259
  const files = [];
@@ -9053,9 +10267,7 @@ class VitestWatcher {
9053
10267
  }
9054
10268
  let rerun = false;
9055
10269
  for (const mod of mods) mod.importers.forEach((i) => {
9056
- if (!i.file) return;
9057
- const needsRerun = this.handleFileChanged(i.file);
9058
- if (needsRerun) rerun = true;
10270
+ if (i.file && this.handleFileChanged(i.file)) rerun = true;
9059
10271
  });
9060
10272
  if (rerun) files.push(filepath);
9061
10273
  }
@@ -9115,6 +10327,8 @@ class Vitest {
9115
10327
  /** @internal */ runner;
9116
10328
  /** @internal */ _testRun = void 0;
9117
10329
  /** @internal */ _resolver;
10330
+ /** @internal */ _fetcher;
10331
+ /** @internal */ _tmpDir = join(tmpdir(), nanoid());
9118
10332
  isFirstRun = true;
9119
10333
  restartsCount = 0;
9120
10334
  specifications;
@@ -9170,18 +10384,19 @@ class Vitest {
9170
10384
  this.watcher.unregisterWatcher(), clearTimeout(this._rerunTimer), this.restartsCount += 1, this.pool?.close?.(), this.pool = void 0, this.closingPromise = void 0, this.projects = [], this.runningPromise = void 0, this.coreWorkspaceProject = void 0, this.specifications.clearCache(), this._coverageProvider = void 0, this._onUserTestsRerun = [], this._vite = server;
9171
10385
  const resolved = resolveConfig(this, options, server.config);
9172
10386
  if (this._config = resolved, this._state = new StateManager({ onUnhandledError: resolved.onUnhandledError }), this._cache = new VitestCache(this.version), this._snapshot = new SnapshotManager({ ...resolved.snapshotOptions }), this._testRun = new TestRun(this), this.config.watch) this.watcher.registerWatcher();
9173
- this._resolver = new VitestResolver(server.config.cacheDir, resolved);
10387
+ this._resolver = new VitestResolver(server.config.cacheDir, resolved), this._fetcher = createFetchModuleFunction(this._resolver, this._tmpDir, {
10388
+ dumpFolder: this.config.dumpDir,
10389
+ readFromDump: this.config.server.debug?.load ?? process.env.VITEST_DEBUG_LOAD_DUMP != null
10390
+ });
9174
10391
  const environment = server.environments.__vitest__;
9175
- if (this.runner = new ServerModuleRunner(environment, this._resolver, resolved), this.config.watch) {
10392
+ if (this.runner = new ServerModuleRunner(environment, this._fetcher, resolved), this.config.watch) {
9176
10393
  // hijack server restart
9177
10394
  const serverRestart = server.restart;
9178
10395
  // since we set `server.hmr: false`, Vite does not auto restart itself
9179
10396
  server.restart = async (...args) => {
9180
10397
  await Promise.all(this._onRestartListeners.map((fn) => fn())), this.report("onServerRestart"), await this.close(), await serverRestart(...args);
9181
10398
  }, server.watcher.on("change", async (file) => {
9182
- file = normalize(file);
9183
- const isConfig = file === server.config.configFile || this.projects.some((p) => p.vite.config.configFile === file);
9184
- if (isConfig) await Promise.all(this._onRestartListeners.map((fn) => fn("config"))), this.report("onServerRestart", "config"), await this.close(), await serverRestart();
10399
+ if (file = normalize(file), file === server.config.configFile || this.projects.some((p) => p.vite.config.configFile === file)) await Promise.all(this._onRestartListeners.map((fn) => fn("config"))), this.report("onServerRestart", "config"), await this.close(), await serverRestart();
9185
10400
  });
9186
10401
  }
9187
10402
  this.cache.results.setConfig(resolved.root, resolved.cache);
@@ -9190,19 +10405,22 @@ class Vitest {
9190
10405
  } catch {}
9191
10406
  const projects = await this.resolveProjects(this._cliOptions);
9192
10407
  if (this.projects = projects, await Promise.all(projects.flatMap((project) => {
9193
- const hooks = project.vite.config.getSortedPluginHooks("configureVitest");
9194
- return hooks.map((hook) => hook({
10408
+ return project.vite.config.getSortedPluginHooks("configureVitest").map((hook) => hook({
9195
10409
  project,
9196
10410
  vitest: this,
9197
10411
  injectTestProjects: this.injectTestProject
9198
10412
  }));
9199
10413
  })), this._cliOptions.browser?.enabled) {
9200
- const browserProjects = this.projects.filter((p) => p.config.browser.enabled);
9201
- if (!browserProjects.length) throw new Error(`Vitest received --browser flag, but no project had a browser configuration.`);
10414
+ if (!this.projects.filter((p) => p.config.browser.enabled).length) throw new Error(`Vitest received --browser flag, but no project had a browser configuration.`);
9202
10415
  }
9203
10416
  if (!this.projects.length) {
9204
10417
  const filter = toArray(resolved.project).join("\", \"");
9205
- throw filter ? new Error(`No projects matched the filter "${filter}".`) : new Error(`Vitest wasn't able to resolve any project.`);
10418
+ if (filter) throw new Error(`No projects matched the filter "${filter}".`);
10419
+ {
10420
+ let error = `Vitest wasn't able to resolve any project.`;
10421
+ if (this.config.browser.enabled && !this.config.browser.instances?.length) error += ` Please, check that you specified the "browser.instances" option.`;
10422
+ throw new Error(error);
10423
+ }
9206
10424
  }
9207
10425
  if (!this.coreWorkspaceProject) this.coreWorkspaceProject = TestProject._createBasicProject(this);
9208
10426
  if (this.config.testNamePattern) this.configOverride.testNamePattern = this.config.testNamePattern;
@@ -9317,7 +10535,7 @@ class Vitest {
9317
10535
  }, await this.report("onInit", this);
9318
10536
  const specifications = [];
9319
10537
  for (const file of files) {
9320
- const project = this.getProjectByName(file.projectName || ""), specification = project.createSpecification(file.filepath, void 0, file.pool);
10538
+ const specification = this.getProjectByName(file.projectName || "").createSpecification(file.filepath, void 0, file.pool);
9321
10539
  specifications.push(specification);
9322
10540
  }
9323
10541
  await this._testRun.start(specifications).catch(noop);
@@ -9327,6 +10545,12 @@ class Vitest {
9327
10545
  unhandledErrors: this.state.getUnhandledErrors()
9328
10546
  };
9329
10547
  }
10548
+ /**
10549
+ * Returns the seed, if tests are running in a random order.
10550
+ */
10551
+ getSeed() {
10552
+ return this.config.sequence.seed ?? null;
10553
+ }
9330
10554
  /** @internal */
9331
10555
  async _reportFileTask(file) {
9332
10556
  const project = this.getProjectByName(file.projectName || "");
@@ -9650,10 +10874,8 @@ class Vitest {
9650
10874
  */
9651
10875
  invalidateFile(filepath) {
9652
10876
  this.projects.forEach(({ vite, browser }) => {
9653
- const environments = [...Object.values(vite.environments), ...Object.values(browser?.vite.environments || {})];
9654
- environments.forEach(({ moduleGraph }) => {
9655
- const modules = moduleGraph.getModulesByFile(filepath);
9656
- modules?.forEach((module) => moduleGraph.invalidateModule(module));
10877
+ [...Object.values(vite.environments), ...Object.values(browser?.vite.environments || {})].forEach(({ moduleGraph }) => {
10878
+ moduleGraph.getModulesByFile(filepath)?.forEach((module) => moduleGraph.invalidateModule(module));
9657
10879
  });
9658
10880
  });
9659
10881
  }
@@ -9703,7 +10925,7 @@ class Vitest {
9703
10925
  async exit(force = false) {
9704
10926
  if (setTimeout(() => {
9705
10927
  this.report("onProcessTimeout").then(() => {
9706
- if (console.warn(`close timed out after ${this.config.teardownTimeout}ms`), this.state.getProcessTimeoutCauses().forEach((cause) => console.warn(cause)), !this.pool) {
10928
+ if (console.warn(`close timed out after ${this.config.teardownTimeout}ms`), !this.pool) {
9707
10929
  const runningServers = [this._vite, ...this.projects.map((p) => p._vite)].filter(Boolean).length;
9708
10930
  if (runningServers === 1) console.warn("Tests closed successfully but something prevents Vite server from exiting");
9709
10931
  else if (runningServers > 1) console.warn(`Tests closed successfully but something prevents ${runningServers} Vite servers from exiting`);
@@ -9776,8 +10998,7 @@ class Vitest {
9776
10998
  matchesProjectFilter(name) {
9777
10999
  const projects = this._config?.project || this._cliOptions?.project;
9778
11000
  return !projects || !projects.length ? true : toArray(projects).some((project) => {
9779
- const regexp = wildcardPatternToRegExp(project);
9780
- return regexp.test(name);
11001
+ return wildcardPatternToRegExp(project).test(name);
9781
11002
  });
9782
11003
  }
9783
11004
  }
@@ -9806,15 +11027,12 @@ async function VitestPlugin(options = {}, vitest = new Vitest("test", deepClone(
9806
11027
  // however to allow vitest plugins to modify vitest config values
9807
11028
  // this is repeated in configResolved where the config is final
9808
11029
  const testConfig = deepMerge({}, configDefaults, removeUndefinedValues(viteConfig.test ?? {}), options);
9809
- testConfig.api = resolveApiServerConfig(testConfig, defaultPort);
9810
- // store defines for globalThis to make them
9811
- // reassignable when running in worker in src/runtime/setup.ts
9812
- const defines = deleteDefineConfig(viteConfig);
9813
- options.defines = defines;
11030
+ testConfig.api = resolveApiServerConfig(testConfig, defaultPort), options.defines = deleteDefineConfig(viteConfig);
9814
11031
  let open = false;
9815
11032
  if (testConfig.ui && testConfig.open) open = testConfig.uiBase ?? "/__vitest__/";
9816
11033
  const resolveOptions = getDefaultResolveOptions();
9817
11034
  let config = {
11035
+ base: "/",
9818
11036
  root: viteConfig.test?.root || options.root,
9819
11037
  define: { "process.env.NODE_ENV": "process.env.NODE_ENV" },
9820
11038
  resolve: {
@@ -9838,10 +11056,6 @@ async function VitestPlugin(options = {}, vitest = new Vitest("test", deepClone(
9838
11056
  __vitest__: { dev: {} }
9839
11057
  },
9840
11058
  test: {
9841
- poolOptions: {
9842
- threads: { isolate: options.poolOptions?.threads?.isolate ?? options.isolate ?? testConfig.poolOptions?.threads?.isolate ?? viteConfig.test?.isolate },
9843
- forks: { isolate: options.poolOptions?.forks?.isolate ?? options.isolate ?? testConfig.poolOptions?.forks?.isolate ?? viteConfig.test?.isolate }
9844
- },
9845
11059
  root: testConfig.root ?? viteConfig.test?.root,
9846
11060
  deps: testConfig.deps ?? viteConfig.test?.deps
9847
11061
  }
@@ -9925,7 +11139,7 @@ function removeUndefinedValues(obj) {
9925
11139
  }
9926
11140
 
9927
11141
  async function createVitest(mode, options, viteOverrides = {}, vitestOptions = {}) {
9928
- const ctx = new Vitest(mode, deepClone(options), vitestOptions), root = slash(resolve$1(options.root || process.cwd())), configPath = options.config === false ? false : options.config ? resolve$1(root, options.config) : await findUp(configFiles, { cwd: root });
11142
+ const ctx = new Vitest(mode, deepClone(options), vitestOptions), root = slash(resolve$1(options.root || process.cwd())), configPath = options.config === false ? false : options.config ? resolve$1(root, options.config) : any(configFiles, { cwd: root });
9929
11143
  options.config = configPath;
9930
11144
  const { browser: _removeBrowser,...restOptions } = options, config = {
9931
11145
  configFile: configPath,
@@ -9977,9 +11191,11 @@ class WatchFilter {
9977
11191
  this.write(`${ESC}1G${ESC}0J`), onSubmit(void 0);
9978
11192
  return;
9979
11193
  case key?.name === "enter":
9980
- case key?.name === "return":
9981
- onSubmit(this.results[this.selectionIndex] || this.currentKeyword || ""), this.currentKeyword = void 0;
11194
+ case key?.name === "return": {
11195
+ const selection = this.results[this.selectionIndex], result = typeof selection === "string" ? selection : selection?.key;
11196
+ onSubmit(result || this.currentKeyword || ""), this.currentKeyword = void 0;
9982
11197
  break;
11198
+ }
9983
11199
  case key?.name === "up":
9984
11200
  if (this.selectionIndex && this.selectionIndex > 0) this.selectionIndex--;
9985
11201
  else this.selectionIndex = -1;
@@ -10041,7 +11257,7 @@ ${c.dim(` ...and ${remainingResultCount} more ${remainingResultCount === 1 ? "
10041
11257
  this.stdout.write(data);
10042
11258
  }
10043
11259
  getLastResults() {
10044
- return this.results;
11260
+ return this.results.map((r) => typeof r === "string" ? r : r.toString());
10045
11261
  }
10046
11262
  }
10047
11263
 
@@ -10067,6 +11283,26 @@ ${c.bold(" Watch Usage")}
10067
11283
  ${keys.map((i) => c.dim(" press ") + c.reset([i[0]].flat().map(c.bold).join(", ")) + c.dim(` to ${i[1]}`)).join("\n")}
10068
11284
  `);
10069
11285
  }
11286
+ function* traverseFilteredTestNames(parentName, filter, t) {
11287
+ if (isTestCase(t)) {
11288
+ if (t.name.match(filter)) {
11289
+ const displayName = `${parentName} > ${t.name}`;
11290
+ yield {
11291
+ key: t.name,
11292
+ toString: () => displayName
11293
+ };
11294
+ }
11295
+ } else {
11296
+ parentName = parentName.length ? `${parentName} > ${t.name}` : t.name;
11297
+ for (const task of t.tasks) yield* traverseFilteredTestNames(parentName, filter, task);
11298
+ }
11299
+ }
11300
+ function* getFilteredTestNames(pattern, suite) {
11301
+ try {
11302
+ const reg = new RegExp(pattern), files = /* @__PURE__ */ new Set();
11303
+ for (const file of suite) if (!files.has(file.name)) files.add(file.name), yield* traverseFilteredTestNames("", reg, file);
11304
+ } catch {}
11305
+ }
10070
11306
  function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10071
11307
  let latestFilename = "";
10072
11308
  async function _keypressHandler(str, key) {
@@ -10116,15 +11352,8 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10116
11352
  }
10117
11353
  async function inputNamePattern() {
10118
11354
  off();
10119
- const watchFilter = new WatchFilter("Input test name pattern (RegExp)", stdin, stdout), filter = await watchFilter.filter((str) => {
10120
- const files = ctx.state.getFiles(), tests = getTests(files);
10121
- try {
10122
- const reg = new RegExp(str);
10123
- return tests.map((test) => test.name).filter((testName) => testName.match(reg));
10124
- } catch {
10125
- // `new RegExp` may throw error when input is invalid regexp
10126
- return [];
10127
- }
11355
+ const filter = await new WatchFilter("Input test name pattern (RegExp)", stdin, stdout).filter((str) => {
11356
+ return [...getFilteredTestNames(str, ctx.state.getFiles())];
10128
11357
  });
10129
11358
  if (on(), typeof filter === "undefined") return;
10130
11359
  const files = ctx.state.getFilepaths(), cliFiles = ctx.config.standalone && !files.length ? await ctx._globTestFilepaths() : void 0;
@@ -10143,8 +11372,7 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
10143
11372
  async function inputFilePattern() {
10144
11373
  off();
10145
11374
  const watchFilter = new WatchFilter("Input filename pattern", stdin, stdout), filter = await watchFilter.filter(async (str) => {
10146
- const specifications = await ctx.globTestSpecifications([str]);
10147
- return specifications.map((specification) => relative(ctx.config.root, specification.moduleId)).filter((file, index, all) => all.indexOf(file) === index);
11375
+ return (await ctx.globTestSpecifications([str])).map((specification) => relative(ctx.config.root, specification.moduleId)).filter((file, index, all) => all.indexOf(file) === index);
10148
11376
  });
10149
11377
  if (on(), typeof filter === "undefined") return;
10150
11378
  latestFilename = filter?.trim() || "";
@@ -10296,4 +11524,4 @@ var cliApi = /*#__PURE__*/Object.freeze({
10296
11524
  startVitest: startVitest
10297
11525
  });
10298
11526
 
10299
- export { FilesNotFoundError as F, GitNotFoundError as G, Vitest as V, VitestPlugin as a, VitestPackageInstaller as b, createVitest as c, experimental_getRunnerTask as d, escapeTestName as e, registerConsoleShortcuts as f, createViteLogger as g, createDebugger as h, isValidApiRequest as i, cliApi as j, resolveFsAllow as r, startVitest as s };
11527
+ export { FilesNotFoundError as F, GitNotFoundError as G, ThreadsPoolWorker as T, Vitest as V, VitestPlugin as a, VitestPackageInstaller as b, createVitest as c, createMethodsRPC as d, escapeTestName as e, ForksPoolWorker as f, getFilePoolName as g, TypecheckPoolWorker as h, isValidApiRequest as i, VmForksPoolWorker as j, VmThreadsPoolWorker as k, experimental_getRunnerTask as l, registerConsoleShortcuts as m, createViteLogger as n, createDebugger as o, cliApi as p, resolveFsAllow as r, startVitest as s };