| import { runInThisContext } from 'node:vm'; | ||
| import * as spyModule from '@vitest/spy'; | ||
| import { r as resolveTestRunner, a as resolveSnapshotEnvironment, d as detectAsyncLeaks, s as setupChaiConfig } from './index.DXx9Dtk7.js'; | ||
| import { l as loadEnvironment, e as emitModuleRunner, a as listenForErrors } from './init.k9zZ9sLh.js'; | ||
| import { N as NativeModuleRunner } from './nativeModuleRunner.BIakptoF.js'; | ||
| import { T as Traces } from './traces.DT5aQ62U.js'; | ||
| import { V as VitestEvaluatedModules } from './evaluatedModules.Dg1zASAC.js'; | ||
| import { s as startVitestModuleRunner, c as createNodeImportMeta } from './startVitestModuleRunner.DB-7oCpn.js'; | ||
| import { performance as performance$1 } from 'node:perf_hooks'; | ||
| import { startTests, collectTests } from '@vitest/runner'; | ||
| import { s as setupCommonEnv, b as startCoverageInsideWorker, c as stopCoverageInsideWorker } from './setup-common.DYx3LtFI.js'; | ||
| import { g as globalExpect, v as vi } from './test.DNmyFkvJ.js'; | ||
| import { c as closeInspector } from './inspector.CvyFGlXm.js'; | ||
| import { createRequire } from 'node:module'; | ||
| import timers from 'node:timers'; | ||
| import timersPromises from 'node:timers/promises'; | ||
| import util from 'node:util'; | ||
| import { KNOWN_ASSET_TYPES } from '@vitest/utils/constants'; | ||
| import { i as index } from './index.DdgEv5B1.js'; | ||
| import { g as getWorkerState, r as resetModules, p as provideWorkerState, a as getSafeWorkerState } from './utils.BX5Fg8C4.js'; | ||
| // this should only be used in Node | ||
| let globalSetup = false; | ||
| async function setupGlobalEnv(config, environment) { | ||
| await setupCommonEnv(config); | ||
| Object.defineProperty(globalThis, "__vitest_index__", { | ||
| value: index, | ||
| enumerable: false | ||
| }); | ||
| globalExpect.setState({ environment: environment.name }); | ||
| if (globalSetup) return; | ||
| globalSetup = true; | ||
| if ((environment.viteEnvironment || environment.name) === "client") { | ||
| const _require = createRequire(import.meta.url); | ||
| // always mock "required" `css` files, because we cannot process them | ||
| _require.extensions[".css"] = resolveCss; | ||
| _require.extensions[".scss"] = resolveCss; | ||
| _require.extensions[".sass"] = resolveCss; | ||
| _require.extensions[".less"] = resolveCss; | ||
| // since we are using Vite, we can assume how these will be resolved | ||
| KNOWN_ASSET_TYPES.forEach((type) => { | ||
| _require.extensions[`.${type}`] = resolveAsset; | ||
| }); | ||
| process.env.SSR = ""; | ||
| } else process.env.SSR = "1"; | ||
| // @ts-expect-error not typed global for patched timers | ||
| globalThis.__vitest_required__ = { | ||
| util, | ||
| timers, | ||
| timersPromises | ||
| }; | ||
| if (!config.disableConsoleIntercept) await setupConsoleLogSpy(); | ||
| } | ||
| function resolveCss(mod) { | ||
| mod.exports = ""; | ||
| } | ||
| function resolveAsset(mod, url) { | ||
| mod.exports = url; | ||
| } | ||
| async function setupConsoleLogSpy() { | ||
| const { createCustomConsole } = await import('./console.3WNpx0tS.js'); | ||
| globalThis.console = createCustomConsole(); | ||
| } | ||
| // browser shouldn't call this! | ||
| async function run(method, files, config, moduleRunner, environment, traces) { | ||
| const workerState = getWorkerState(); | ||
| const [testRunner] = await Promise.all([ | ||
| traces.$("vitest.runtime.runner", () => resolveTestRunner(config, moduleRunner, traces)), | ||
| traces.$("vitest.runtime.global_env", () => setupGlobalEnv(config, environment)), | ||
| traces.$("vitest.runtime.coverage.start", () => startCoverageInsideWorker(config.coverage, moduleRunner, { isolate: config.isolate })), | ||
| traces.$("vitest.runtime.snapshot.environment", async () => { | ||
| if (!workerState.config.snapshotOptions.snapshotEnvironment) workerState.config.snapshotOptions.snapshotEnvironment = await resolveSnapshotEnvironment(config, moduleRunner); | ||
| }) | ||
| ]); | ||
| workerState.onCancel((reason) => { | ||
| closeInspector(config); | ||
| testRunner.cancel?.(reason); | ||
| }); | ||
| workerState.durations.prepare = performance$1.now() - workerState.durations.prepare; | ||
| await traces.$(`vitest.test.runner.${method}`, async () => { | ||
| for (const file of files) { | ||
| if (config.isolate) { | ||
| moduleRunner.mocker?.reset(); | ||
| resetModules(workerState.evaluatedModules, true); | ||
| } | ||
| workerState.filepath = file.filepath; | ||
| if (method === "run") { | ||
| const collectAsyncLeaks = config.detectAsyncLeaks ? detectAsyncLeaks(file.filepath, workerState.ctx.projectName) : void 0; | ||
| await traces.$(`vitest.test.runner.${method}.module`, { attributes: { "code.file.path": file.filepath } }, () => startTests([file], testRunner)); | ||
| const leaks = await collectAsyncLeaks?.(); | ||
| if (leaks?.length) workerState.rpc.onAsyncLeaks(leaks); | ||
| } else await traces.$(`vitest.test.runner.${method}.module`, { attributes: { "code.file.path": file.filepath } }, () => collectTests([file], testRunner)); | ||
| // reset after tests, because user might call `vi.setConfig` in setupFile | ||
| vi.resetConfig(); | ||
| // mocks should not affect different files | ||
| vi.restoreAllMocks(); | ||
| } | ||
| }); | ||
| await traces.$("vitest.runtime.coverage.stop", () => stopCoverageInsideWorker(config.coverage, moduleRunner, { isolate: config.isolate })); | ||
| } | ||
| let _moduleRunner; | ||
| const evaluatedModules = new VitestEvaluatedModules(); | ||
| const moduleExecutionInfo = /* @__PURE__ */ new Map(); | ||
| async function startModuleRunner(options) { | ||
| if (_moduleRunner) return _moduleRunner; | ||
| process.exit = (code = process.exitCode || 0) => { | ||
| throw new Error(`process.exit unexpectedly called with "${code}"`); | ||
| }; | ||
| const state = () => getSafeWorkerState() || options.state; | ||
| listenForErrors(state); | ||
| if (options.state.config.experimental.viteModuleRunner === false) { | ||
| const root = options.state.config.root; | ||
| let mocker; | ||
| if (options.state.config.experimental.nodeLoader !== false) { | ||
| // this additionally imports acorn/magic-string | ||
| const { NativeModuleMocker } = await import('./nativeModuleMocker.BkNfQMkH.js'); | ||
| mocker = new NativeModuleMocker({ | ||
| async resolveId(id, importer) { | ||
| // TODO: use import.meta.resolve instead | ||
| return state().rpc.resolve(id, importer, "__vitest__"); | ||
| }, | ||
| root, | ||
| moduleDirectories: state().config.deps.moduleDirectories || ["/node_modules/"], | ||
| traces: options.traces || new Traces({ enabled: false }), | ||
| getCurrentTestFilepath() { | ||
| return state().filepath; | ||
| }, | ||
| spyModule | ||
| }); | ||
| } | ||
| _moduleRunner = new NativeModuleRunner(root, mocker); | ||
| return _moduleRunner; | ||
| } | ||
| _moduleRunner = startVitestModuleRunner(options); | ||
| return _moduleRunner; | ||
| } | ||
| let _currentEnvironment; | ||
| let _environmentTime; | ||
| /** @experimental */ | ||
| async function setupBaseEnvironment(context) { | ||
| if (context.config.experimental.viteModuleRunner === false) { | ||
| const { setupNodeLoaderHooks } = await import('./native.DPzPHdi5.js'); | ||
| await setupNodeLoaderHooks(context); | ||
| } | ||
| const startTime = performance.now(); | ||
| const { environment: { name: environmentName, options: environmentOptions }, rpc, config } = context; | ||
| // we could load @vite/env, but it would take ~8ms, while this takes ~0,02ms | ||
| if (context.config.serializedDefines) try { | ||
| runInThisContext(`(() =>{\n${context.config.serializedDefines}})()`, { | ||
| lineOffset: 1, | ||
| filename: "virtual:load-defines.js" | ||
| }); | ||
| } catch (error) { | ||
| throw new Error(`Failed to load custom "defines": ${error.message}`); | ||
| } | ||
| const otel = context.traces; | ||
| const { environment, loader } = await loadEnvironment(environmentName, config.root, rpc, otel, context.config.experimental.viteModuleRunner); | ||
| _currentEnvironment = environment; | ||
| const env = await otel.$("vitest.runtime.environment.setup", { attributes: { | ||
| "vitest.environment": environment.name, | ||
| "vitest.environment.vite_environment": environment.viteEnvironment || environment.name | ||
| } }, () => environment.setup(globalThis, environmentOptions || config.environmentOptions || {})); | ||
| _environmentTime = performance.now() - startTime; | ||
| if (config.chaiConfig) setupChaiConfig(config.chaiConfig); | ||
| return async () => { | ||
| await otel.$("vitest.runtime.environment.teardown", () => env.teardown(globalThis)); | ||
| await loader?.close(); | ||
| }; | ||
| } | ||
| /** @experimental */ | ||
| async function runBaseTests(method, state, traces) { | ||
| const { ctx } = state; | ||
| state.environment = _currentEnvironment; | ||
| state.durations.environment = _environmentTime; | ||
| // state has new context, but we want to reuse existing ones | ||
| state.evaluatedModules = evaluatedModules; | ||
| state.moduleExecutionInfo = moduleExecutionInfo; | ||
| provideWorkerState(globalThis, state); | ||
| if (ctx.invalidates) ctx.invalidates.forEach((filepath) => { | ||
| (state.evaluatedModules.fileToModulesMap.get(filepath) || []).forEach((module) => { | ||
| state.evaluatedModules.invalidateModule(module); | ||
| }); | ||
| }); | ||
| ctx.files.forEach((i) => { | ||
| const filepath = i.filepath; | ||
| (state.evaluatedModules.fileToModulesMap.get(filepath) || []).forEach((module) => { | ||
| state.evaluatedModules.invalidateModule(module); | ||
| }); | ||
| }); | ||
| const moduleRunner = await startModuleRunner({ | ||
| state, | ||
| evaluatedModules: state.evaluatedModules, | ||
| spyModule, | ||
| createImportMeta: createNodeImportMeta, | ||
| traces | ||
| }); | ||
| emitModuleRunner(moduleRunner); | ||
| await run(method, ctx.files, ctx.config, moduleRunner, _currentEnvironment, traces); | ||
| } | ||
| export { runBaseTests as r, setupBaseEnvironment as s }; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| import { i as init } from './init.k9zZ9sLh.js'; | ||
| if (!process.send) throw new Error("Expected worker to be run in node:child_process"); | ||
| // Store globals in case tests overwrite them | ||
| const processExit = process.exit.bind(process); | ||
| const processSend = process.send.bind(process); | ||
| const processOn = process.on.bind(process); | ||
| const processOff = process.off.bind(process); | ||
| const processRemoveAllListeners = process.removeAllListeners.bind(process); | ||
| // Work-around for nodejs/node#55094 | ||
| if (process.execArgv.some((execArg) => execArg.startsWith("--prof") || execArg.startsWith("--cpu-prof") || execArg.startsWith("--heap-prof") || execArg.startsWith("--diagnostic-dir"))) processOn("SIGTERM", () => processExit()); | ||
| processOn("error", onError); | ||
| function workerInit(options) { | ||
| const { runTests } = options; | ||
| init({ | ||
| post: (v) => processSend(v), | ||
| on: (cb) => processOn("message", cb), | ||
| off: (cb) => processOff("message", cb), | ||
| teardown: () => { | ||
| processRemoveAllListeners("message"); | ||
| processOff("error", onError); | ||
| }, | ||
| runTests: (state, traces) => executeTests("run", state, traces), | ||
| collectTests: (state, traces) => executeTests("collect", state, traces), | ||
| setup: options.setup | ||
| }); | ||
| async function executeTests(method, state, traces) { | ||
| try { | ||
| await runTests(method, state, traces); | ||
| } finally { | ||
| process.exit = processExit; | ||
| } | ||
| } | ||
| } | ||
| // Prevent leaving worker in loops where it tries to send message to closed main | ||
| // thread, errors, and tries to send the error. | ||
| function onError(error) { | ||
| if (error?.code === "ERR_IPC_CHANNEL_CLOSED" || error?.code === "EPIPE") processExit(1); | ||
| } | ||
| export { workerInit as w }; |
| import { isMainThread, parentPort } from 'node:worker_threads'; | ||
| import { i as init } from './init.k9zZ9sLh.js'; | ||
| if (isMainThread || !parentPort) throw new Error("Expected worker to be run in node:worker_threads"); | ||
| function workerInit(options) { | ||
| const { runTests } = options; | ||
| init({ | ||
| post: (response) => parentPort.postMessage(response), | ||
| on: (callback) => parentPort.on("message", callback), | ||
| off: (callback) => parentPort.off("message", callback), | ||
| teardown: () => parentPort.removeAllListeners("message"), | ||
| runTests: async (state, traces) => runTests("run", state, traces), | ||
| collectTests: async (state, traces) => runTests("collect", state, traces), | ||
| setup: options.setup | ||
| }); | ||
| } | ||
| export { workerInit as w }; |
| import { readFileSync } from 'node:fs'; | ||
| import { isBuiltin } from 'node:module'; | ||
| import { pathToFileURL } from 'node:url'; | ||
| import { resolve } from 'pathe'; | ||
| import { ModuleRunner, EvaluatedModules } from 'vite/module-runner'; | ||
| import { b as VitestTransport } from './startVitestModuleRunner.DB-7oCpn.js'; | ||
| import { e as environments } from './index.DC7d2Pf8.js'; | ||
| import { serializeValue } from '@vitest/utils/serialize'; | ||
| import { serializeError } from '@vitest/utils/error'; | ||
| import { disableDefaultColors } from 'tinyrainbow'; | ||
| import { T as Traces } from './traces.DT5aQ62U.js'; | ||
| import { o as onCancel, a as rpcDone, c as createRuntimeRpc } from './rpc.MzXet3jl.js'; | ||
| import { createStackString, parseStacktrace } from '@vitest/utils/source-map'; | ||
| import { s as setupInspect } from './inspector.CvyFGlXm.js'; | ||
| import { V as VitestEvaluatedModules } from './evaluatedModules.Dg1zASAC.js'; | ||
| import { E as EnvironmentTeardownError } from './utils.BX5Fg8C4.js'; | ||
| function isBuiltinEnvironment(env) { | ||
| return env in environments; | ||
| } | ||
| const isWindows = process.platform === "win32"; | ||
| const _loaders = /* @__PURE__ */ new Map(); | ||
| function createEnvironmentLoader(root, rpc) { | ||
| const cachedLoader = _loaders.get(root); | ||
| if (!cachedLoader || cachedLoader.isClosed()) { | ||
| _loaders.delete(root); | ||
| const moduleRunner = new ModuleRunner({ | ||
| hmr: false, | ||
| sourcemapInterceptor: "prepareStackTrace", | ||
| transport: new VitestTransport({ | ||
| async fetchModule(id, importer, options) { | ||
| const result = await rpc.fetch(id, importer, "__vitest__", options); | ||
| if ("cached" in result) return { | ||
| code: readFileSync(result.tmp, "utf-8"), | ||
| ...result | ||
| }; | ||
| if (isWindows && "externalize" in result) | ||
| // TODO: vitest returns paths for external modules, but Vite returns file:// | ||
| // https://github.com/vitejs/vite/pull/20449 | ||
| result.externalize = isBuiltin(id) || /^(?:node:|data:|http:|https:|file:)/.test(id) ? result.externalize : pathToFileURL(result.externalize).toString(); | ||
| return result; | ||
| }, | ||
| async resolveId(id, importer) { | ||
| return rpc.resolve(id, importer, "__vitest__"); | ||
| } | ||
| }, new EvaluatedModules(), /* @__PURE__ */ new WeakMap()) | ||
| }); | ||
| _loaders.set(root, moduleRunner); | ||
| } | ||
| return _loaders.get(root); | ||
| } | ||
| async function loadNativeEnvironment(name, root, traces) { | ||
| const packageId = name[0] === "." || name[0] === "/" ? pathToFileURL(resolve(root, name)).toString() : import.meta.resolve(`vitest-environment-${name}`, pathToFileURL(root).toString()); | ||
| return resolveEnvironmentFromModule(name, packageId, await traces.$("vitest.runtime.environment.import", () => import(packageId))); | ||
| } | ||
| function resolveEnvironmentFromModule(name, packageId, pkg) { | ||
| if (!pkg || !pkg.default || typeof pkg.default !== "object") throw new TypeError(`Environment "${name}" is not a valid environment. Path "${packageId}" should export default object with a "setup" or/and "setupVM" method.`); | ||
| const environment = pkg.default; | ||
| if (environment.transformMode != null && environment.transformMode !== "web" && environment.transformMode !== "ssr") throw new TypeError(`Environment "${name}" is not a valid environment. Path "${packageId}" should export default object with a "transformMode" method equal to "ssr" or "web", received "${environment.transformMode}".`); | ||
| if (environment.transformMode) { | ||
| console.warn(`The Vitest environment ${environment.name} defines the "transformMode". This options was deprecated in Vitest 4 and will be removed in the next major version. Please, use "viteEnvironment" instead.`); | ||
| // keep for backwards compat | ||
| environment.viteEnvironment ??= environment.transformMode === "ssr" ? "ssr" : "client"; | ||
| } | ||
| return environment; | ||
| } | ||
| async function loadEnvironment(name, root, rpc, traces, viteModuleRunner) { | ||
| if (isBuiltinEnvironment(name)) return { environment: environments[name] }; | ||
| if (!viteModuleRunner) return { environment: await loadNativeEnvironment(name, root, traces) }; | ||
| const loader = createEnvironmentLoader(root, rpc); | ||
| const packageId = name[0] === "." || name[0] === "/" ? resolve(root, name) : (await traces.$("vitest.runtime.environment.resolve", () => rpc.resolve(`vitest-environment-${name}`, void 0, "__vitest__")))?.id ?? resolve(root, name); | ||
| return { | ||
| environment: resolveEnvironmentFromModule(name, packageId, await traces.$("vitest.runtime.environment.import", () => loader.import(packageId))), | ||
| loader | ||
| }; | ||
| } | ||
| const cleanupListeners = /* @__PURE__ */ new Set(); | ||
| const moduleRunnerListeners = /* @__PURE__ */ new Set(); | ||
| function onCleanup(cb) { | ||
| cleanupListeners.add(cb); | ||
| } | ||
| async function cleanup() { | ||
| await Promise.all([...cleanupListeners].map((l) => l())); | ||
| } | ||
| function onModuleRunner(cb) { | ||
| moduleRunnerListeners.add(cb); | ||
| } | ||
| function emitModuleRunner(moduleRunner) { | ||
| moduleRunnerListeners.forEach((l) => l(moduleRunner)); | ||
| } | ||
| // Store globals in case tests overwrite them | ||
| const processListeners = process.listeners.bind(process); | ||
| const processOn = process.on.bind(process); | ||
| const processOff = process.off.bind(process); | ||
| const dispose = []; | ||
| function listenForErrors(state) { | ||
| dispose.forEach((fn) => fn()); | ||
| dispose.length = 0; | ||
| function catchError(err, type, event) { | ||
| const worker = state(); | ||
| // if there is another listener, assume that it's handled by user code | ||
| // one is Vitest's own listener | ||
| if (processListeners(event).length > 1) return; | ||
| const error = serializeValue(err); | ||
| if (typeof error === "object" && error != null) { | ||
| error.VITEST_TEST_NAME = worker.current?.type === "test" ? worker.current.name : void 0; | ||
| if (worker.filepath) error.VITEST_TEST_PATH = worker.filepath; | ||
| } | ||
| state().rpc.onUnhandledError(error, type); | ||
| } | ||
| const uncaughtException = (e) => catchError(e, "Uncaught Exception", "uncaughtException"); | ||
| const unhandledRejection = (e) => catchError(e, "Unhandled Rejection", "unhandledRejection"); | ||
| processOn("uncaughtException", uncaughtException); | ||
| processOn("unhandledRejection", unhandledRejection); | ||
| dispose.push(() => { | ||
| processOff("uncaughtException", uncaughtException); | ||
| processOff("unhandledRejection", unhandledRejection); | ||
| }); | ||
| } | ||
| const resolvingModules = /* @__PURE__ */ new Set(); | ||
| async function execute(method, ctx, worker, traces) { | ||
| const prepareStart = performance.now(); | ||
| const cleanups = [setupInspect(ctx)]; | ||
| // RPC is used to communicate between worker (be it a thread worker or child process or a custom implementation) and the main thread | ||
| const rpc = ctx.rpc; | ||
| try { | ||
| // do not close the RPC channel so that we can get the error messages sent to the main thread | ||
| cleanups.push(async () => { | ||
| await Promise.all(rpc.$rejectPendingCalls(({ method, reject }) => { | ||
| reject(new EnvironmentTeardownError(`[vitest-worker]: Closing rpc while "${method}" was pending`)); | ||
| })); | ||
| }); | ||
| const state = { | ||
| ctx, | ||
| evaluatedModules: new VitestEvaluatedModules(), | ||
| resolvingModules, | ||
| moduleExecutionInfo: /* @__PURE__ */ new Map(), | ||
| config: ctx.config, | ||
| environment: null, | ||
| durations: { | ||
| environment: 0, | ||
| prepare: prepareStart | ||
| }, | ||
| rpc, | ||
| onCancel, | ||
| onCleanup: onCleanup, | ||
| providedContext: ctx.providedContext, | ||
| onFilterStackTrace(stack) { | ||
| return createStackString(parseStacktrace(stack)); | ||
| }, | ||
| metaEnv: createImportMetaEnvProxy() | ||
| }; | ||
| const methodName = method === "collect" ? "collectTests" : "runTests"; | ||
| if (!worker[methodName] || typeof worker[methodName] !== "function") throw new TypeError(`Test worker should expose "runTests" method. Received "${typeof worker.runTests}".`); | ||
| await worker[methodName](state, traces); | ||
| } finally { | ||
| await rpcDone().catch(() => {}); | ||
| await Promise.all(cleanups.map((fn) => fn())).catch(() => {}); | ||
| } | ||
| } | ||
| function run(ctx, worker, traces) { | ||
| return execute("run", ctx, worker, traces); | ||
| } | ||
| function collect(ctx, worker, traces) { | ||
| return execute("collect", ctx, worker, traces); | ||
| } | ||
| async function teardown() { | ||
| await cleanup(); | ||
| } | ||
| const env = process.env; | ||
| function createImportMetaEnvProxy() { | ||
| // packages/vitest/src/node/plugins/index.ts:146 | ||
| const booleanKeys = [ | ||
| "DEV", | ||
| "PROD", | ||
| "SSR" | ||
| ]; | ||
| return new Proxy(env, { | ||
| get(_, key) { | ||
| if (typeof key !== "string") return; | ||
| if (booleanKeys.includes(key)) return !!process.env[key]; | ||
| return process.env[key]; | ||
| }, | ||
| set(_, key, value) { | ||
| if (typeof key !== "string") return true; | ||
| if (booleanKeys.includes(key)) process.env[key] = value ? "1" : ""; | ||
| else process.env[key] = value; | ||
| return true; | ||
| } | ||
| }); | ||
| } | ||
| const __vitest_worker_response__ = true; | ||
| const memoryUsage = process.memoryUsage.bind(process); | ||
| let reportMemory = false; | ||
| let traces; | ||
| /** @experimental */ | ||
| function init(worker) { | ||
| worker.on(onMessage); | ||
| if (worker.onModuleRunner) onModuleRunner(worker.onModuleRunner); | ||
| let runPromise; | ||
| let isRunning = false; | ||
| let workerTeardown; | ||
| let setupContext; | ||
| function send(response) { | ||
| worker.post(worker.serialize ? worker.serialize(response) : response); | ||
| } | ||
| async function onMessage(rawMessage) { | ||
| const message = worker.deserialize ? worker.deserialize(rawMessage) : rawMessage; | ||
| if (message?.__vitest_worker_request__ !== true) return; | ||
| switch (message.type) { | ||
| case "start": { | ||
| process.env.VITEST_POOL_ID = String(message.poolId); | ||
| process.env.VITEST_WORKER_ID = String(message.workerId); | ||
| reportMemory = message.options.reportMemory; | ||
| if (message.context.config.isAgent) disableDefaultColors(); | ||
| traces ??= await new Traces({ | ||
| enabled: message.traces.enabled, | ||
| sdkPath: message.traces.sdkPath | ||
| }).waitInit(); | ||
| const { environment, config, pool } = message.context; | ||
| const context = traces.getContextFromCarrier(message.traces.otelCarrier); | ||
| // record telemetry as part of "start" | ||
| traces.recordInitSpan(context); | ||
| try { | ||
| setupContext = { | ||
| environment, | ||
| config, | ||
| pool, | ||
| rpc: createRuntimeRpc(worker), | ||
| projectName: config.name || "", | ||
| traces | ||
| }; | ||
| workerTeardown = await traces.$("vitest.runtime.setup", { context }, () => worker.setup?.(setupContext)); | ||
| send({ | ||
| type: "started", | ||
| __vitest_worker_response__ | ||
| }); | ||
| } catch (error) { | ||
| send({ | ||
| type: "started", | ||
| __vitest_worker_response__, | ||
| error: serializeError(error) | ||
| }); | ||
| } | ||
| break; | ||
| } | ||
| case "run": | ||
| // Prevent concurrent execution if worker is already running | ||
| if (isRunning) { | ||
| send({ | ||
| type: "testfileFinished", | ||
| __vitest_worker_response__, | ||
| error: serializeError(/* @__PURE__ */ new Error("[vitest-worker]: Worker is already running tests")) | ||
| }); | ||
| return; | ||
| } | ||
| try { | ||
| process.env.VITEST_WORKER_ID = String(message.context.workerId); | ||
| } catch (error) { | ||
| return send({ | ||
| type: "testfileFinished", | ||
| __vitest_worker_response__, | ||
| error: serializeError(error), | ||
| usedMemory: reportMemory ? memoryUsage().heapUsed : void 0 | ||
| }); | ||
| } | ||
| isRunning = true; | ||
| try { | ||
| const tracesContext = traces.getContextFromCarrier(message.otelCarrier); | ||
| runPromise = traces.$("vitest.runtime.run", { | ||
| context: tracesContext, | ||
| attributes: { | ||
| "vitest.worker.specifications": traces.isEnabled() ? getFilesWithLocations(message.context.files) : [], | ||
| "vitest.worker.id": message.context.workerId | ||
| } | ||
| }, () => run({ | ||
| ...setupContext, | ||
| ...message.context | ||
| }, worker, traces).catch((error) => serializeError(error))); | ||
| send({ | ||
| type: "testfileFinished", | ||
| __vitest_worker_response__, | ||
| error: await runPromise, | ||
| usedMemory: reportMemory ? memoryUsage().heapUsed : void 0 | ||
| }); | ||
| } finally { | ||
| runPromise = void 0; | ||
| isRunning = false; | ||
| } | ||
| break; | ||
| case "collect": | ||
| // Prevent concurrent execution if worker is already running | ||
| if (isRunning) { | ||
| send({ | ||
| type: "testfileFinished", | ||
| __vitest_worker_response__, | ||
| error: serializeError(/* @__PURE__ */ new Error("[vitest-worker]: Worker is already running tests")) | ||
| }); | ||
| return; | ||
| } | ||
| try { | ||
| process.env.VITEST_WORKER_ID = String(message.context.workerId); | ||
| } catch (error) { | ||
| return send({ | ||
| type: "testfileFinished", | ||
| __vitest_worker_response__, | ||
| error: serializeError(error), | ||
| usedMemory: reportMemory ? memoryUsage().heapUsed : void 0 | ||
| }); | ||
| } | ||
| isRunning = true; | ||
| try { | ||
| const tracesContext = traces.getContextFromCarrier(message.otelCarrier); | ||
| runPromise = traces.$("vitest.runtime.collect", { | ||
| context: tracesContext, | ||
| attributes: { | ||
| "vitest.worker.specifications": traces.isEnabled() ? getFilesWithLocations(message.context.files) : [], | ||
| "vitest.worker.id": message.context.workerId | ||
| } | ||
| }, () => collect({ | ||
| ...setupContext, | ||
| ...message.context | ||
| }, worker, traces).catch((error) => serializeError(error))); | ||
| send({ | ||
| type: "testfileFinished", | ||
| __vitest_worker_response__, | ||
| error: await runPromise, | ||
| usedMemory: reportMemory ? memoryUsage().heapUsed : void 0 | ||
| }); | ||
| } finally { | ||
| runPromise = void 0; | ||
| isRunning = false; | ||
| } | ||
| break; | ||
| case "stop": | ||
| await runPromise; | ||
| try { | ||
| const context = traces.getContextFromCarrier(message.otelCarrier); | ||
| const error = await traces.$("vitest.runtime.teardown", { context }, async () => { | ||
| const error = await teardown().catch((error) => serializeError(error)); | ||
| await workerTeardown?.(); | ||
| return error; | ||
| }); | ||
| await traces.finish(); | ||
| send({ | ||
| type: "stopped", | ||
| error, | ||
| __vitest_worker_response__ | ||
| }); | ||
| } catch (error) { | ||
| send({ | ||
| type: "stopped", | ||
| error: serializeError(error), | ||
| __vitest_worker_response__ | ||
| }); | ||
| } | ||
| worker.teardown?.(); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| function getFilesWithLocations(files) { | ||
| return files.flatMap((file) => { | ||
| if (!file.testLocations) return file.filepath; | ||
| return file.testLocations.map((location) => { | ||
| return `${file}:${location}`; | ||
| }); | ||
| }); | ||
| } | ||
| export { listenForErrors as a, emitModuleRunner as e, init as i, loadEnvironment as l }; |
| import module$1, { isBuiltin } from 'node:module'; | ||
| import { fileURLToPath, pathToFileURL } from 'node:url'; | ||
| import { automockModule, createManualModuleSource, collectModuleExports } from '@vitest/mocker/transforms'; | ||
| import { cleanUrl, createDefer } from '@vitest/utils/helpers'; | ||
| import { p as parse } from './acorn.B2iPLyUM.js'; | ||
| import { isAbsolute } from 'pathe'; | ||
| import { t as toBuiltin } from './modules.BJuCwlRJ.js'; | ||
| import { B as BareModuleMocker, n as normalizeModuleId } from './startVitestModuleRunner.DB-7oCpn.js'; | ||
| import 'node:fs'; | ||
| import './utils.BX5Fg8C4.js'; | ||
| import '@vitest/utils/timers'; | ||
| import '../path.js'; | ||
| import 'node:path'; | ||
| import '../module-evaluator.js'; | ||
| import 'node:vm'; | ||
| import 'vite/module-runner'; | ||
| import './traces.DT5aQ62U.js'; | ||
| import '@vitest/mocker'; | ||
| import '@vitest/mocker/redirect'; | ||
| class NativeModuleMocker extends BareModuleMocker { | ||
| wrapDynamicImport(moduleFactory) { | ||
| if (typeof moduleFactory === "function") return new Promise((resolve, reject) => { | ||
| this.resolveMocks().finally(() => { | ||
| moduleFactory().then(resolve, reject); | ||
| }); | ||
| }); | ||
| return moduleFactory; | ||
| } | ||
| resolveMockedModule(url, parentURL) { | ||
| // don't mock modules inside of packages because there is | ||
| // a high chance that it uses `require` which is not mockable | ||
| // because we use top-level await in "manual" mocks. | ||
| // for the sake of consistency we don't support mocking anything at all | ||
| if (parentURL.includes("/node_modules/")) return; | ||
| const moduleId = normalizeModuleId(url.startsWith("file://") ? fileURLToPath(url) : url); | ||
| const mockedModule = this.getDependencyMock(moduleId); | ||
| if (!mockedModule) return; | ||
| if (mockedModule.type === "redirect") return { | ||
| url: pathToFileURL(mockedModule.redirect).toString(), | ||
| shortCircuit: true | ||
| }; | ||
| if (mockedModule.type === "automock" || mockedModule.type === "autospy") return { | ||
| url: injectQuery(url, parentURL, `mock=${mockedModule.type}`), | ||
| shortCircuit: true | ||
| }; | ||
| if (mockedModule.type === "manual") return { | ||
| url: injectQuery(url, parentURL, "mock=manual"), | ||
| shortCircuit: true | ||
| }; | ||
| } | ||
| loadAutomock(url, result) { | ||
| const moduleId = cleanUrl(normalizeModuleId(url.startsWith("file://") ? fileURLToPath(url) : url)); | ||
| let source; | ||
| if (isBuiltin(moduleId)) { | ||
| const builtinModule = getBuiltinModule(moduleId); | ||
| const exports$1 = Object.keys(builtinModule); | ||
| source = ` | ||
| import * as builtinModule from '${toBuiltin(moduleId)}?mock=actual' | ||
| ${exports$1.map((key, index) => { | ||
| return ` | ||
| const __${index} = builtinModule["${key}"] | ||
| export { __${index} as "${key}" } | ||
| `; | ||
| }).join("")}`; | ||
| } else source = result.source?.toString(); | ||
| if (source == null) return; | ||
| const mockType = url.includes("mock=automock") ? "automock" : "autospy"; | ||
| const transformedCode = transformCode(source, result.format || "module", moduleId); | ||
| try { | ||
| const ms = automockModule(transformedCode, mockType, (code) => parse(code, { | ||
| sourceType: "module", | ||
| ecmaVersion: "latest" | ||
| }), { id: moduleId }); | ||
| return { | ||
| format: "module", | ||
| source: `${ms.toString()}\n//# sourceMappingURL=${genSourceMapUrl(ms.generateMap({ | ||
| hires: "boundary", | ||
| source: moduleId | ||
| }))}`, | ||
| shortCircuit: true | ||
| }; | ||
| } catch (cause) { | ||
| throw new Error(`Cannot automock '${url}' because it failed to parse.`, { cause }); | ||
| } | ||
| } | ||
| loadManualMock(url, result) { | ||
| const moduleId = cleanUrl(normalizeModuleId(url.startsWith("file://") ? fileURLToPath(url) : url)); | ||
| // should not be possible | ||
| if (this.getDependencyMock(moduleId)?.type !== "manual") { | ||
| console.warn(`Vitest detected unregistered manual mock ${moduleId}. This is a bug in Vitest. Please, open a new issue with reproduction.`); | ||
| return; | ||
| } | ||
| if (isBuiltin(moduleId)) { | ||
| const builtinModule = getBuiltinModule(toBuiltin(moduleId)); | ||
| return { | ||
| format: "module", | ||
| source: createManualModuleSource(moduleId, Object.keys(builtinModule)), | ||
| shortCircuit: true | ||
| }; | ||
| } | ||
| if (!result.source) return; | ||
| const transformedCode = transformCode(result.source.toString(), result.format || "module", moduleId); | ||
| if (transformedCode == null) return; | ||
| const format = result.format?.startsWith("module") ? "module" : "commonjs"; | ||
| try { | ||
| return { | ||
| format: "module", | ||
| source: createManualModuleSource(moduleId, collectModuleExports(moduleId, transformedCode, format)), | ||
| shortCircuit: true | ||
| }; | ||
| } catch (cause) { | ||
| throw new Error(`Failed to mock '${url}'. See the cause for more information.`, { cause }); | ||
| } | ||
| } | ||
| processedModules = /* @__PURE__ */ new Map(); | ||
| checkCircularManualMock(url) { | ||
| const id = cleanUrl(normalizeModuleId(url.startsWith("file://") ? fileURLToPath(url) : url)); | ||
| this.processedModules.set(id, (this.processedModules.get(id) ?? 0) + 1); | ||
| // the module is mocked and requested a second time, let's resolve | ||
| // the factory function that will redefine the exports later | ||
| if (this.originalModulePromises.has(id)) { | ||
| const factoryPromise = this.factoryPromises.get(id); | ||
| this.originalModulePromises.get(id)?.resolve({ __factoryPromise: factoryPromise }); | ||
| } | ||
| } | ||
| originalModulePromises = /* @__PURE__ */ new Map(); | ||
| factoryPromises = /* @__PURE__ */ new Map(); | ||
| // potential performance improvement: | ||
| // store by URL, not ids, no need to call url.*to* methods and normalizeModuleId | ||
| getFactoryModule(id) { | ||
| const mock = this.getMockerRegistry().getById(id); | ||
| if (!mock || mock.type !== "manual") throw new Error(`Mock ${id} wasn't registered. This is probably a Vitest error. Please, open a new issue with reproduction.`); | ||
| const mockResult = mock.resolve(); | ||
| if (mockResult instanceof Promise) { | ||
| // to avoid circular dependency, we resolve this function as {__factoryPromise} in `checkCircularManualMock` | ||
| // when it's requested the second time. then the exports are exposed as `undefined`, | ||
| // but later redefined when the promise is actually resolved | ||
| const promise = createDefer(); | ||
| promise.finally(() => { | ||
| this.originalModulePromises.delete(id); | ||
| }); | ||
| mockResult.then(promise.resolve, promise.reject).finally(() => { | ||
| this.factoryPromises.delete(id); | ||
| }); | ||
| this.factoryPromises.set(id, mockResult); | ||
| this.originalModulePromises.set(id, promise); | ||
| // Node.js on windows processes all the files first, and then runs them | ||
| // unlike Node.js logic on Mac and Unix where it also runs the code while evaluating | ||
| // So on Linux/Mac this `if` won't be hit because `checkCircularManualMock` will resolve it | ||
| // And on Windows, the `checkCircularManualMock` will never have `originalModulePromises` | ||
| // because `getFactoryModule` is not called until the evaluation phase | ||
| // But if we track how many times the module was transformed, | ||
| // we can deduce when to return `__factoryPromise` to support circular modules | ||
| if ((this.processedModules.get(id) ?? 0) > 1) { | ||
| this.processedModules.set(id, (this.processedModules.get(id) ?? 1) - 1); | ||
| promise.resolve({ __factoryPromise: mockResult }); | ||
| } | ||
| return promise; | ||
| } | ||
| return mockResult; | ||
| } | ||
| importActual(rawId, importer) { | ||
| const resolvedId = import.meta.resolve(rawId, pathToFileURL(importer).toString()); | ||
| const url = new URL(resolvedId); | ||
| url.searchParams.set("mock", "actual"); | ||
| return import(url.toString()); | ||
| } | ||
| importMock(rawId, importer) { | ||
| const resolvedId = import.meta.resolve(rawId, pathToFileURL(importer).toString()); | ||
| // file is already mocked | ||
| if (resolvedId.includes("mock=")) return import(resolvedId); | ||
| const filename = fileURLToPath(resolvedId); | ||
| const external = !isAbsolute(filename) || this.isModuleDirectory(resolvedId) ? normalizeModuleId(rawId) : null; | ||
| // file is not mocked, automock or redirect it | ||
| const redirect = this.findMockRedirect(filename, external); | ||
| if (redirect) return import(pathToFileURL(redirect).toString()); | ||
| const url = new URL(resolvedId); | ||
| url.searchParams.set("mock", "automock"); | ||
| return import(url.toString()); | ||
| } | ||
| } | ||
| const replacePercentageRE = /%/g; | ||
| function injectQuery(url, importer, queryToInject) { | ||
| const { search, hash } = new URL(url.replace(replacePercentageRE, "%25"), importer); | ||
| return `${cleanUrl(url)}?${queryToInject}${search ? `&${search.slice(1)}` : ""}${hash ?? ""}`; | ||
| } | ||
| let __require; | ||
| function getBuiltinModule(moduleId) { | ||
| __require ??= module$1.createRequire(import.meta.url); | ||
| return __require(`${moduleId}?mock=actual`); | ||
| } | ||
| function genSourceMapUrl(map) { | ||
| if (typeof map !== "string") map = JSON.stringify(map); | ||
| return `data:application/json;base64,${Buffer.from(map).toString("base64")}`; | ||
| } | ||
| function transformCode(code, format, filename) { | ||
| if (format.includes("typescript")) { | ||
| if (!module$1.stripTypeScriptTypes) throw new Error(`Cannot parse '${filename}' because "module.stripTypeScriptTypes" is not supported. Module mocking requires Node.js 22.15 or higher. This is NOT a bug of Vitest.`); | ||
| return module$1.stripTypeScriptTypes(code); | ||
| } | ||
| return code; | ||
| } | ||
| export { NativeModuleMocker }; |
| import fs from 'node:fs'; | ||
| import { splitFileAndPostfix as splitFileAndPostfix$1, isBareImport } from '@vitest/utils/helpers'; | ||
| import { i as isBuiltin, a as isBrowserExternal, t as toBuiltin } from './modules.BJuCwlRJ.js'; | ||
| import { E as EnvironmentTeardownError, a as getSafeWorkerState } from './utils.BX5Fg8C4.js'; | ||
| import { pathToFileURL } from 'node:url'; | ||
| import { normalize, join } from 'pathe'; | ||
| import { distDir } from '../path.js'; | ||
| import { VitestModuleEvaluator, unwrapId } from '../module-evaluator.js'; | ||
| import { isAbsolute, resolve } from 'node:path'; | ||
| import vm from 'node:vm'; | ||
| import { MockerRegistry, mockObject, RedirectedModule, AutomockedModule } from '@vitest/mocker'; | ||
| import { findMockRedirect } from '@vitest/mocker/redirect'; | ||
| import * as viteModuleRunner from 'vite/module-runner'; | ||
| import { T as Traces } from './traces.DT5aQ62U.js'; | ||
| class BareModuleMocker { | ||
| static pendingIds = []; | ||
| spyModule; | ||
| primitives; | ||
| registries = /* @__PURE__ */ new Map(); | ||
| mockContext = { callstack: null }; | ||
| _otel; | ||
| constructor(options) { | ||
| this.options = options; | ||
| this._otel = options.traces; | ||
| this.primitives = { | ||
| Object, | ||
| Error, | ||
| Function, | ||
| RegExp, | ||
| Symbol: globalThis.Symbol, | ||
| Array, | ||
| Map | ||
| }; | ||
| if (options.spyModule) this.spyModule = options.spyModule; | ||
| } | ||
| get root() { | ||
| return this.options.root; | ||
| } | ||
| get moduleDirectories() { | ||
| return this.options.moduleDirectories || []; | ||
| } | ||
| getMockerRegistry() { | ||
| const suite = this.getSuiteFilepath(); | ||
| if (!this.registries.has(suite)) this.registries.set(suite, new MockerRegistry()); | ||
| return this.registries.get(suite); | ||
| } | ||
| reset() { | ||
| this.registries.clear(); | ||
| } | ||
| invalidateModuleById(_id) { | ||
| // implemented by mockers that control the module runner | ||
| } | ||
| isModuleDirectory(path) { | ||
| return this.moduleDirectories.some((dir) => path.includes(dir)); | ||
| } | ||
| getSuiteFilepath() { | ||
| return this.options.getCurrentTestFilepath() || "global"; | ||
| } | ||
| createError(message, codeFrame) { | ||
| const Error = this.primitives.Error; | ||
| const error = new Error(message); | ||
| Object.assign(error, { codeFrame }); | ||
| return error; | ||
| } | ||
| async resolveId(rawId, importer) { | ||
| return this._otel.$("vitest.mocker.resolve_id", { attributes: { | ||
| "vitest.module.raw_id": rawId, | ||
| "vitest.module.importer": rawId | ||
| } }, async (span) => { | ||
| const result = await this.options.resolveId(rawId, importer); | ||
| if (!result) { | ||
| span.addEvent("could not resolve id, fallback to unresolved values"); | ||
| const id = normalizeModuleId(rawId); | ||
| span.setAttributes({ | ||
| "vitest.module.id": id, | ||
| "vitest.module.url": rawId, | ||
| "vitest.module.external": id, | ||
| "vitest.module.fallback": true | ||
| }); | ||
| return { | ||
| id, | ||
| url: rawId, | ||
| external: id | ||
| }; | ||
| } | ||
| // external is node_module or unresolved module | ||
| // for example, some people mock "vscode" and don't have it installed | ||
| const external = !isAbsolute(result.file) || this.isModuleDirectory(result.file) ? normalizeModuleId(rawId) : null; | ||
| const id = normalizeModuleId(result.id); | ||
| span.setAttributes({ | ||
| "vitest.module.id": id, | ||
| "vitest.module.url": result.url, | ||
| "vitest.module.external": external ?? false | ||
| }); | ||
| return { | ||
| ...result, | ||
| id, | ||
| external | ||
| }; | ||
| }); | ||
| } | ||
| async resolveMocks() { | ||
| if (!BareModuleMocker.pendingIds.length) return; | ||
| const resolveMock = async (mock) => { | ||
| const { id, url, external } = await this.resolveId(mock.id, mock.importer); | ||
| if (mock.action === "unmock") this.unmockPath(id); | ||
| if (mock.action === "mock") this.mockPath(mock.id, id, url, external, mock.type, mock.factory); | ||
| }; | ||
| // group consecutive mocks of the same action type together, | ||
| // resolve in parallel inside each group, but run groups sequentially | ||
| // to preserve mock/unmock ordering | ||
| const groups = groupByConsecutiveAction(BareModuleMocker.pendingIds); | ||
| for (const group of groups) await Promise.all(group.map(resolveMock)); | ||
| BareModuleMocker.pendingIds = []; | ||
| } | ||
| // public method to avoid circular dependency | ||
| getMockContext() { | ||
| return this.mockContext; | ||
| } | ||
| // path used to store mocked dependencies | ||
| getMockPath(dep) { | ||
| return `mock:${dep}`; | ||
| } | ||
| getDependencyMock(id) { | ||
| return this.getMockerRegistry().getById(fixLeadingSlashes(id)); | ||
| } | ||
| getDependencyMockByUrl(url) { | ||
| return this.getMockerRegistry().get(url); | ||
| } | ||
| findMockRedirect(mockPath, external) { | ||
| return findMockRedirect(this.root, mockPath, external); | ||
| } | ||
| mockObject(object, mockExportsOrModuleType, moduleType) { | ||
| let mockExports; | ||
| if (mockExportsOrModuleType === "automock" || mockExportsOrModuleType === "autospy") { | ||
| moduleType = mockExportsOrModuleType; | ||
| mockExports = void 0; | ||
| } else mockExports = mockExportsOrModuleType; | ||
| moduleType ??= "automock"; | ||
| const createMockInstance = this.spyModule?.createMockInstance; | ||
| if (!createMockInstance) throw this.createError("[vitest] `spyModule` is not defined. This is a Vitest error. Please open a new issue with reproduction."); | ||
| return mockObject({ | ||
| globalConstructors: this.primitives, | ||
| createMockInstance, | ||
| type: moduleType | ||
| }, object, mockExports); | ||
| } | ||
| unmockPath(id) { | ||
| this.getMockerRegistry().deleteById(id); | ||
| this.invalidateModuleById(id); | ||
| } | ||
| mockPath(originalId, id, url, external, mockType, factory) { | ||
| const registry = this.getMockerRegistry(); | ||
| if (mockType === "manual") registry.register("manual", originalId, id, url, factory); | ||
| else if (mockType === "autospy") registry.register("autospy", originalId, id, url); | ||
| else { | ||
| const redirect = this.findMockRedirect(id, external); | ||
| if (redirect) registry.register("redirect", originalId, id, url, redirect); | ||
| else registry.register("automock", originalId, id, url); | ||
| } | ||
| // every time the mock is registered, we remove the previous one from the cache | ||
| this.invalidateModuleById(id); | ||
| } | ||
| async importActual(_rawId, _importer, _callstack) { | ||
| throw new Error(`importActual is not implemented`); | ||
| } | ||
| async importMock(_rawId, _importer, _callstack) { | ||
| throw new Error(`importMock is not implemented`); | ||
| } | ||
| queueMock(id, importer, factoryOrOptions) { | ||
| const mockType = getMockType(factoryOrOptions); | ||
| BareModuleMocker.pendingIds.push({ | ||
| action: "mock", | ||
| id, | ||
| importer, | ||
| factory: typeof factoryOrOptions === "function" ? factoryOrOptions : void 0, | ||
| type: mockType | ||
| }); | ||
| } | ||
| queueUnmock(id, importer) { | ||
| BareModuleMocker.pendingIds.push({ | ||
| action: "unmock", | ||
| id, | ||
| importer | ||
| }); | ||
| } | ||
| } | ||
| function getMockType(factoryOrOptions) { | ||
| if (!factoryOrOptions) return "automock"; | ||
| if (typeof factoryOrOptions === "function") return "manual"; | ||
| return factoryOrOptions.spy ? "autospy" : "automock"; | ||
| } | ||
| // unique id that is not available as "$bare_import" like "test" | ||
| // https://nodejs.org/api/modules.html#built-in-modules-with-mandatory-node-prefix | ||
| const prefixedBuiltins = new Set([ | ||
| "node:sea", | ||
| "node:sqlite", | ||
| "node:test", | ||
| "node:test/reporters" | ||
| ]); | ||
| const isWindows$1 = process.platform === "win32"; | ||
| // transform file url to id | ||
| // virtual:custom -> virtual:custom | ||
| // \0custom -> \0custom | ||
| // /root/id -> /id | ||
| // /root/id.js -> /id.js | ||
| // C:/root/id.js -> /id.js | ||
| // C:\root\id.js -> /id.js | ||
| // TODO: expose this in vite/module-runner | ||
| function normalizeModuleId(file) { | ||
| if (prefixedBuiltins.has(file)) return file; | ||
| // if it's not in the root, keep it as a path, not a URL | ||
| return slash(file).replace(/^\/@fs\//, isWindows$1 ? "" : "/").replace(/^node:/, "").replace(/^\/+/, "/").replace(/^file:\//, "/"); | ||
| } | ||
| const windowsSlashRE = /\\/g; | ||
| function slash(p) { | ||
| return p.replace(windowsSlashRE, "/"); | ||
| } | ||
| function groupByConsecutiveAction(mocks) { | ||
| const groups = []; | ||
| for (const mock of mocks) { | ||
| const last = groups.at(-1); | ||
| if (last?.[0].action === mock.action) last.push(mock); | ||
| else groups.push([mock]); | ||
| } | ||
| return groups; | ||
| } | ||
| const multipleSlashRe = /^\/+/; | ||
| // module-runner incorrectly replaces file:///path with `///path` | ||
| function fixLeadingSlashes(id) { | ||
| if (id.startsWith("//")) return id.replace(multipleSlashRe, "/"); | ||
| return id; | ||
| } | ||
| // copied from vite | ||
| // https://github.com/vitejs/vite/blob/4417b4f305623b2850bd6ae6553834c017694672/packages/vite/src/shared/utils.ts | ||
| // https://github.com/vitejs/vite/blob/4417b4f305623b2850bd6ae6553834c017694672/packages/vite/src/node/utils.ts | ||
| const postfixRE = /[?#].*$/; | ||
| const trailingSeparatorRE = /[?&]$/; | ||
| function cleanUrl(url) { | ||
| return url.replace(postfixRE, ""); | ||
| } | ||
| function splitFileAndPostfix(path) { | ||
| const file = cleanUrl(path); | ||
| return { | ||
| file, | ||
| postfix: path.slice(file.length) | ||
| }; | ||
| } | ||
| function injectQuery(url, queryToInject) { | ||
| const { file, postfix } = splitFileAndPostfix(url); | ||
| return `${file}?${queryToInject}${postfix[0] === "?" ? `&${postfix.slice(1)}` : postfix}`; | ||
| } | ||
| function removeQuery(url, queryToRemove) { | ||
| return url.replace(new RegExp(`([?&])${queryToRemove}(?:&|$)`), "$1").replace(trailingSeparatorRE, ""); | ||
| } | ||
| const spyModulePath = resolve(distDir, "spy.js"); | ||
| class VitestMocker extends BareModuleMocker { | ||
| filterPublicKeys; | ||
| constructor(moduleRunner, options) { | ||
| super(options); | ||
| this.moduleRunner = moduleRunner; | ||
| this.options = options; | ||
| const context = this.options.context; | ||
| if (context) this.primitives = vm.runInContext("({ Object, Error, Function, RegExp, Symbol, Array, Map })", context); | ||
| const Symbol = this.primitives.Symbol; | ||
| this.filterPublicKeys = [ | ||
| "__esModule", | ||
| Symbol.asyncIterator, | ||
| Symbol.hasInstance, | ||
| Symbol.isConcatSpreadable, | ||
| Symbol.iterator, | ||
| Symbol.match, | ||
| Symbol.matchAll, | ||
| Symbol.replace, | ||
| Symbol.search, | ||
| Symbol.split, | ||
| Symbol.species, | ||
| Symbol.toPrimitive, | ||
| Symbol.toStringTag, | ||
| Symbol.unscopables | ||
| ]; | ||
| } | ||
| get evaluatedModules() { | ||
| return this.moduleRunner.evaluatedModules; | ||
| } | ||
| async initializeSpyModule() { | ||
| if (this.spyModule) return; | ||
| this.spyModule = await this.moduleRunner.import(spyModulePath); | ||
| } | ||
| reset() { | ||
| this.registries.clear(); | ||
| } | ||
| invalidateModuleById(id) { | ||
| const mockId = this.getMockPath(id); | ||
| const node = this.evaluatedModules.getModuleById(mockId); | ||
| if (node) { | ||
| this.evaluatedModules.invalidateModule(node); | ||
| node.mockedExports = void 0; | ||
| } | ||
| } | ||
| ensureModule(id, url) { | ||
| const node = this.evaluatedModules.ensureModule(id, url); | ||
| // TODO | ||
| node.meta = { | ||
| id, | ||
| url, | ||
| code: "", | ||
| file: null, | ||
| invalidate: false | ||
| }; | ||
| return node; | ||
| } | ||
| async callFunctionMock(id, url, mock) { | ||
| const node = this.ensureModule(id, url); | ||
| if (node.exports) return node.exports; | ||
| const exports$1 = await mock.resolve(); | ||
| const moduleExports = new Proxy(exports$1, { get: (target, prop) => { | ||
| const val = target[prop]; | ||
| // 'then' can exist on non-Promise objects, need nested instanceof check for logic to work | ||
| if (prop === "then") { | ||
| if (target instanceof Promise) return target.then.bind(target); | ||
| } else if (!(prop in target)) { | ||
| if (this.filterPublicKeys.includes(prop)) return; | ||
| throw this.createError(`[vitest] No "${String(prop)}" export is defined on the "${mock.raw}" mock. Did you forget to return it from "vi.mock"? | ||
| If you need to partially mock a module, you can use "importOriginal" helper inside: | ||
| `, `vi.mock(import("${mock.raw}"), async (importOriginal) => { | ||
| const actual = await importOriginal() | ||
| return { | ||
| ...actual, | ||
| // your mocked methods | ||
| } | ||
| })`); | ||
| } | ||
| return val; | ||
| } }); | ||
| node.exports = moduleExports; | ||
| return moduleExports; | ||
| } | ||
| async importActual(rawId, importer, callstack) { | ||
| const { url } = await this.resolveId(rawId, importer); | ||
| const actualUrl = injectQuery(url, "_vitest_original"); | ||
| const node = await this.moduleRunner.fetchModule(actualUrl, importer); | ||
| return await this.moduleRunner.cachedRequest(node.url, node, callstack || [importer], void 0, true); | ||
| } | ||
| async importMock(rawId, importer) { | ||
| const { id, url, external } = await this.resolveId(rawId, importer); | ||
| let mock = this.getDependencyMock(id); | ||
| if (!mock) { | ||
| const redirect = this.findMockRedirect(id, external); | ||
| if (redirect) mock = new RedirectedModule(rawId, id, rawId, redirect); | ||
| else mock = new AutomockedModule(rawId, id, rawId); | ||
| } | ||
| if (mock.type === "automock" || mock.type === "autospy") { | ||
| const node = await this.moduleRunner.fetchModule(url, importer); | ||
| const mod = await this.moduleRunner.cachedRequest(url, node, [importer], void 0, true); | ||
| const Object = this.primitives.Object; | ||
| return this.mockObject(mod, Object.create(Object.prototype), mock.type); | ||
| } | ||
| if (mock.type === "manual") return this.callFunctionMock(id, url, mock); | ||
| const node = await this.moduleRunner.fetchModule(mock.redirect); | ||
| return this.moduleRunner.cachedRequest(mock.redirect, node, [importer], void 0, true); | ||
| } | ||
| async requestWithMockedModule(url, evaluatedNode, callstack, mock) { | ||
| return this._otel.$("vitest.mocker.evaluate", async (span) => { | ||
| const mockId = this.getMockPath(evaluatedNode.id); | ||
| span.setAttributes({ | ||
| "vitest.module.id": mockId, | ||
| "vitest.mock.type": mock.type, | ||
| "vitest.mock.id": mock.id, | ||
| "vitest.mock.url": mock.url, | ||
| "vitest.mock.raw": mock.raw | ||
| }); | ||
| if (mock.type === "automock" || mock.type === "autospy") { | ||
| const cache = this.evaluatedModules.getModuleById(mockId); | ||
| if (cache && cache.mockedExports) return cache.mockedExports; | ||
| const Object = this.primitives.Object; | ||
| // we have to define a separate object that will copy all properties into itself | ||
| // and can't just use the same `exports` define automatically by Vite before the evaluator | ||
| const exports$1 = Object.create(null); | ||
| Object.defineProperty(exports$1, Symbol.toStringTag, { | ||
| value: "Module", | ||
| configurable: true, | ||
| writable: true | ||
| }); | ||
| const node = this.ensureModule(mockId, this.getMockPath(evaluatedNode.url)); | ||
| node.meta = evaluatedNode.meta; | ||
| node.file = evaluatedNode.file; | ||
| node.mockedExports = exports$1; | ||
| const mod = await this.moduleRunner.cachedRequest(url, node, callstack, void 0, true); | ||
| this.mockObject(mod, exports$1, mock.type); | ||
| return exports$1; | ||
| } | ||
| if (mock.type === "manual" && !callstack.includes(mockId) && !callstack.includes(url)) try { | ||
| callstack.push(mockId); | ||
| // this will not work if user does Promise.all(import(), import()) | ||
| // we can also use AsyncLocalStorage to store callstack, but this won't work in the browser | ||
| // maybe we should improve mock API in the future? | ||
| this.mockContext.callstack = callstack; | ||
| return await this.callFunctionMock(mockId, this.getMockPath(url), mock); | ||
| } finally { | ||
| this.mockContext.callstack = null; | ||
| const indexMock = callstack.indexOf(mockId); | ||
| callstack.splice(indexMock, 1); | ||
| } | ||
| else if (mock.type === "redirect" && !callstack.includes(mock.redirect)) { | ||
| span.setAttribute("vitest.mock.redirect", mock.redirect); | ||
| return mock.redirect; | ||
| } | ||
| }); | ||
| } | ||
| async mockedRequest(url, evaluatedNode, callstack) { | ||
| const mock = this.getDependencyMock(evaluatedNode.id); | ||
| if (!mock) return; | ||
| return this.requestWithMockedModule(url, evaluatedNode, callstack, mock); | ||
| } | ||
| } | ||
| class VitestTransport { | ||
| constructor(options, evaluatedModules, callstacks) { | ||
| this.options = options; | ||
| this.evaluatedModules = evaluatedModules; | ||
| this.callstacks = callstacks; | ||
| } | ||
| async invoke(event) { | ||
| if (event.type !== "custom") return { error: /* @__PURE__ */ new Error(`Vitest Module Runner doesn't support Vite HMR events.`) }; | ||
| if (event.event !== "vite:invoke") return { error: /* @__PURE__ */ new Error(`Vitest Module Runner doesn't support ${event.event} event.`) }; | ||
| const { name, data } = event.data; | ||
| if (name === "getBuiltins") | ||
| // we return an empty array here to avoid client-side builtin check, | ||
| // as we need builtins to go through `fetchModule` | ||
| return { result: [] }; | ||
| if (name !== "fetchModule") return { error: /* @__PURE__ */ new Error(`Unknown method: ${name}. Expected "fetchModule".`) }; | ||
| try { | ||
| return { result: await this.options.fetchModule(...data) }; | ||
| } catch (cause) { | ||
| if (cause instanceof EnvironmentTeardownError) { | ||
| const [id, importer] = data; | ||
| let message = `Cannot load '${id}'${importer ? ` imported from ${importer}` : ""} after the environment was torn down. This is not a bug in Vitest.`; | ||
| const moduleNode = importer ? this.evaluatedModules.getModuleById(importer) : void 0; | ||
| const callstack = moduleNode ? this.callstacks.get(moduleNode) : void 0; | ||
| if (callstack) message += ` The last recorded callstack:\n- ${[ | ||
| ...callstack, | ||
| importer, | ||
| id | ||
| ].reverse().join("\n- ")}`; | ||
| const error = new EnvironmentTeardownError(message); | ||
| if (cause.stack) error.stack = cause.stack.replace(cause.message, error.message); | ||
| return { error }; | ||
| } | ||
| return { error: cause }; | ||
| } | ||
| } | ||
| } | ||
| const createNodeImportMeta = (modulePath) => { | ||
| if (!viteModuleRunner.createDefaultImportMeta) throw new Error(`createNodeImportMeta is not supported in this version of Vite.`); | ||
| const defaultMeta = viteModuleRunner.createDefaultImportMeta(modulePath); | ||
| const href = defaultMeta.url; | ||
| const importMetaResolver = createImportMetaResolver(); | ||
| return { | ||
| ...defaultMeta, | ||
| main: false, | ||
| resolve(id, parent) { | ||
| return (importMetaResolver ?? defaultMeta.resolve)(id, parent ?? href); | ||
| } | ||
| }; | ||
| }; | ||
| function createImportMetaResolver() { | ||
| if (!import.meta.resolve) return; | ||
| return (specifier, importer) => import.meta.resolve(specifier, importer); | ||
| } | ||
| // @ts-expect-error overriding private method | ||
| class VitestModuleRunner extends viteModuleRunner.ModuleRunner { | ||
| mocker; | ||
| moduleExecutionInfo; | ||
| _otel; | ||
| _callstacks; | ||
| constructor(vitestOptions) { | ||
| const options = vitestOptions; | ||
| const evaluatedModules = options.evaluatedModules; | ||
| const callstacks = /* @__PURE__ */ new WeakMap(); | ||
| const transport = new VitestTransport(options.transport, evaluatedModules, callstacks); | ||
| super({ | ||
| transport, | ||
| hmr: false, | ||
| evaluatedModules, | ||
| sourcemapInterceptor: "prepareStackTrace", | ||
| createImportMeta: vitestOptions.createImportMeta | ||
| }, options.evaluator); | ||
| this.vitestOptions = vitestOptions; | ||
| this._callstacks = callstacks; | ||
| this._otel = vitestOptions.traces || new Traces({ enabled: false }); | ||
| this.moduleExecutionInfo = options.getWorkerState().moduleExecutionInfo; | ||
| this.mocker = options.mocker || new VitestMocker(this, { | ||
| spyModule: options.spyModule, | ||
| context: options.vm?.context, | ||
| traces: this._otel, | ||
| resolveId: options.transport.resolveId, | ||
| get root() { | ||
| return options.getWorkerState().config.root; | ||
| }, | ||
| get moduleDirectories() { | ||
| return options.getWorkerState().config.deps.moduleDirectories || []; | ||
| }, | ||
| getCurrentTestFilepath() { | ||
| return options.getWorkerState().filepath; | ||
| } | ||
| }); | ||
| if (options.vm) options.vm.context.__vitest_mocker__ = this.mocker; | ||
| else Object.defineProperty(globalThis, "__vitest_mocker__", { | ||
| configurable: true, | ||
| writable: true, | ||
| value: this.mocker | ||
| }); | ||
| } | ||
| /** | ||
| * Vite checks that the module has exports emulating the Node.js behaviour, | ||
| * but Vitest is more relaxed. | ||
| * | ||
| * We should keep the Vite behavior when there is a `strict` flag. | ||
| * @internal | ||
| */ | ||
| processImport(exports$1) { | ||
| return exports$1; | ||
| } | ||
| async import(rawId) { | ||
| const resolved = await this._otel.$("vitest.module.resolve_id", { attributes: { "vitest.module.raw_id": rawId } }, async (span) => { | ||
| const result = await this.vitestOptions.transport.resolveId(rawId); | ||
| if (result) span.setAttributes({ | ||
| "vitest.module.url": result.url, | ||
| "vitest.module.file": result.file, | ||
| "vitest.module.id": result.id | ||
| }); | ||
| return result; | ||
| }); | ||
| return super.import(resolved ? resolved.url : rawId); | ||
| } | ||
| async fetchModule(url, importer) { | ||
| return await this.cachedModule(url, importer); | ||
| } | ||
| _cachedRequest(url, module, callstack = [], metadata) { | ||
| // @ts-expect-error "cachedRequest" is private | ||
| return super.cachedRequest(url, module, callstack, metadata); | ||
| } | ||
| /** | ||
| * @internal | ||
| */ | ||
| async cachedRequest(url, mod, callstack = [], metadata, ignoreMock = false) { | ||
| // Track for a better error message if dynamic import is not resolved properly | ||
| this._callstacks.set(mod, callstack); | ||
| if (ignoreMock) return this._cachedRequest(url, mod, callstack, metadata); | ||
| let mocked; | ||
| if (mod.meta && "mockedModule" in mod.meta) { | ||
| const mockedModule = mod.meta.mockedModule; | ||
| const mockId = this.mocker.getMockPath(mod.id); | ||
| // bypass mock and force "importActual" behavior when: | ||
| // - mock was removed by doUnmock (stale mockedModule in meta) | ||
| // - self-import: mock factory/file is importing the module it's mocking | ||
| const isStale = !this.mocker.getDependencyMock(mod.id); | ||
| const isSelfImport = callstack.includes(mockId) || callstack.includes(url) || "redirect" in mockedModule && callstack.includes(mockedModule.redirect); | ||
| if (isStale || isSelfImport) { | ||
| const node = await this.fetchModule(injectQuery(url, "_vitest_original")); | ||
| return this._cachedRequest(node.url, node, callstack, metadata); | ||
| } | ||
| mocked = await this.mocker.requestWithMockedModule(url, mod, callstack, mockedModule); | ||
| } else mocked = await this.mocker.mockedRequest(url, mod, callstack); | ||
| if (typeof mocked === "string") { | ||
| const node = await this.fetchModule(mocked); | ||
| return this._cachedRequest(mocked, node, callstack, metadata); | ||
| } | ||
| if (mocked != null && typeof mocked === "object") return mocked; | ||
| return this._cachedRequest(url, mod, callstack, metadata); | ||
| } | ||
| /** @internal */ | ||
| _invalidateSubTreeById(ids, invalidated = /* @__PURE__ */ new Set()) { | ||
| for (const id of ids) { | ||
| if (invalidated.has(id)) continue; | ||
| const node = this.evaluatedModules.getModuleById(id); | ||
| if (!node) continue; | ||
| invalidated.add(id); | ||
| const subIds = Array.from(this.evaluatedModules.idToModuleMap).filter(([, mod]) => mod.importers.has(id)).map(([key]) => key); | ||
| if (subIds.length) this._invalidateSubTreeById(subIds, invalidated); | ||
| this.evaluatedModules.invalidateModule(node); | ||
| } | ||
| } | ||
| } | ||
| const bareVitestRegexp = /^@?vitest(?:\/|$)/; | ||
| const normalizedDistDir = normalize(distDir); | ||
| const relativeIds = {}; | ||
| const externalizeMap = /* @__PURE__ */ new Map(); | ||
| // all Vitest imports always need to be externalized | ||
| function getCachedVitestImport(id, state) { | ||
| if (id.startsWith("/@fs/") || id.startsWith("\\@fs\\")) id = id.slice(process.platform === "win32" ? 5 : 4); | ||
| if (externalizeMap.has(id)) return { | ||
| externalize: externalizeMap.get(id), | ||
| type: "module" | ||
| }; | ||
| // always externalize Vitest because we import from there before running tests | ||
| // so we already have it cached by Node.js | ||
| const root = state().config.root; | ||
| const relativeRoot = relativeIds[root] ?? (relativeIds[root] = normalizedDistDir.slice(root.length)); | ||
| if (id.includes(distDir) || id.includes(normalizedDistDir)) { | ||
| const { file, postfix } = splitFileAndPostfix$1(id); | ||
| const externalize = id.startsWith("file://") ? id : `${pathToFileURL(file)}${postfix}`; | ||
| externalizeMap.set(id, externalize); | ||
| return { | ||
| externalize, | ||
| type: "module" | ||
| }; | ||
| } | ||
| if (relativeRoot && relativeRoot !== "/" && id.startsWith(relativeRoot)) { | ||
| const { file, postfix } = splitFileAndPostfix$1(id); | ||
| const externalize = `${pathToFileURL(join(root, file))}${postfix}`; | ||
| externalizeMap.set(id, externalize); | ||
| return { | ||
| externalize, | ||
| type: "module" | ||
| }; | ||
| } | ||
| if (bareVitestRegexp.test(id)) { | ||
| externalizeMap.set(id, id); | ||
| return { | ||
| externalize: id, | ||
| type: "module" | ||
| }; | ||
| } | ||
| return null; | ||
| } | ||
| const { readFileSync } = fs; | ||
| const VITEST_VM_CONTEXT_SYMBOL = "__vitest_vm_context__"; | ||
| const cwd = process.cwd(); | ||
| const isWindows = process.platform === "win32"; | ||
| function startVitestModuleRunner(options) { | ||
| const traces = options.traces; | ||
| const state = () => getSafeWorkerState() || options.state; | ||
| const rpc = () => state().rpc; | ||
| const environment = () => { | ||
| const environment = state().environment; | ||
| return environment.viteEnvironment || environment.name; | ||
| }; | ||
| const vm = options.context && options.externalModulesExecutor ? { | ||
| context: options.context, | ||
| externalModulesExecutor: options.externalModulesExecutor | ||
| } : void 0; | ||
| const evaluator = options.evaluator || new VitestModuleEvaluator(vm, { | ||
| traces, | ||
| evaluatedModules: options.evaluatedModules, | ||
| get moduleExecutionInfo() { | ||
| return state().moduleExecutionInfo; | ||
| }, | ||
| get interopDefault() { | ||
| return state().config.deps.interopDefault; | ||
| }, | ||
| getCurrentTestFilepath: () => state().filepath | ||
| }); | ||
| const moduleRunner = new VitestModuleRunner({ | ||
| spyModule: options.spyModule, | ||
| evaluatedModules: options.evaluatedModules, | ||
| evaluator, | ||
| traces, | ||
| mocker: options.mocker, | ||
| transport: { | ||
| async fetchModule(id, importer, options) { | ||
| const resolvingModules = state().resolvingModules; | ||
| if (isWindows) { | ||
| if (id[1] === ":") { | ||
| // The drive letter is different for whatever reason, we need to normalize it to CWD | ||
| if (id[0] !== cwd[0] && id[0].toUpperCase() === cwd[0].toUpperCase()) id = (cwd[0].toUpperCase() === cwd[0] ? id[0].toUpperCase() : id[0].toLowerCase()) + id.slice(1); | ||
| // always mark absolute windows paths, otherwise Vite will externalize it | ||
| id = `/@id/${id}`; | ||
| } | ||
| } | ||
| const vitest = getCachedVitestImport(id, state); | ||
| if (vitest) return vitest; | ||
| // strip _vitest_original query added by importActual so that | ||
| // the plugin pipeline sees the original import id (e.g. virtual modules's load hook) | ||
| const isImportActual = id.includes("_vitest_original"); | ||
| if (isImportActual) id = removeQuery(id, "_vitest_original"); | ||
| const rawId = unwrapId(id); | ||
| resolvingModules.add(rawId); | ||
| try { | ||
| if (VitestMocker.pendingIds.length) await moduleRunner.mocker.resolveMocks(); | ||
| if (!isImportActual) { | ||
| const resolvedMock = moduleRunner.mocker.getDependencyMockByUrl(id); | ||
| if (resolvedMock?.type === "manual" || resolvedMock?.type === "redirect") return { | ||
| code: "", | ||
| file: null, | ||
| id: resolvedMock.id, | ||
| url: resolvedMock.url, | ||
| invalidate: false, | ||
| mockedModule: resolvedMock | ||
| }; | ||
| } | ||
| if (isBuiltin(rawId)) return { | ||
| externalize: rawId, | ||
| type: "builtin" | ||
| }; | ||
| if (isBrowserExternal(rawId)) return { | ||
| externalize: toBuiltin(rawId), | ||
| type: "builtin" | ||
| }; | ||
| // if module is invalidated, the worker will be recreated, | ||
| // so cached is always true in a single worker | ||
| if (!isImportActual && options?.cached) return { cache: true }; | ||
| const otelCarrier = traces?.getContextCarrier(); | ||
| const result = await rpc().fetch(id, importer, environment(), options, otelCarrier); | ||
| if ("cached" in result) return { | ||
| code: readFileSync(result.tmp, "utf-8"), | ||
| ...result | ||
| }; | ||
| return result; | ||
| } catch (cause) { | ||
| // rethrow vite error if it cannot load the module because it's not resolved | ||
| if (typeof cause === "object" && cause != null && cause.code === "ERR_LOAD_URL" || typeof cause?.message === "string" && cause.message.includes("Failed to load url") || typeof cause?.message === "string" && cause.message.startsWith("Cannot find module '")) { | ||
| const error = new Error(`Cannot find ${isBareImport(id) ? "package" : "module"} '${id}'${importer ? ` imported from ${importer}` : ""}`, { cause }); | ||
| error.code = "ERR_MODULE_NOT_FOUND"; | ||
| throw error; | ||
| } | ||
| throw cause; | ||
| } finally { | ||
| resolvingModules.delete(rawId); | ||
| } | ||
| }, | ||
| resolveId(id, importer) { | ||
| return rpc().resolve(id, importer, environment()); | ||
| } | ||
| }, | ||
| getWorkerState: state, | ||
| vm, | ||
| createImportMeta: options.createImportMeta | ||
| }); | ||
| return moduleRunner; | ||
| } | ||
| export { BareModuleMocker as B, VITEST_VM_CONTEXT_SYMBOL as V, VitestModuleRunner as a, VitestTransport as b, createNodeImportMeta as c, normalizeModuleId as n, startVitestModuleRunner as s }; |
| import { fileURLToPath, pathToFileURL } from 'node:url'; | ||
| import vm, { isContext, runInContext } from 'node:vm'; | ||
| import { dirname, basename, extname, normalize, resolve } from 'pathe'; | ||
| import { l as loadEnvironment, a as listenForErrors, e as emitModuleRunner } from './init.k9zZ9sLh.js'; | ||
| import { distDir } from '../path.js'; | ||
| import { createCustomConsole } from './console.3WNpx0tS.js'; | ||
| import fs from 'node:fs'; | ||
| import { createRequire, Module, isBuiltin } from 'node:module'; | ||
| import { toArray, splitFileAndPostfix, isBareImport } from '@vitest/utils/helpers'; | ||
| import { findNearestPackageData } from '@vitest/utils/resolver'; | ||
| import { dirname as dirname$1 } from 'node:path'; | ||
| import { CSS_LANGS_RE, KNOWN_ASSET_RE } from '@vitest/utils/constants'; | ||
| import { getDefaultRequestStubs } from '../module-evaluator.js'; | ||
| import { s as startVitestModuleRunner, V as VITEST_VM_CONTEXT_SYMBOL, c as createNodeImportMeta } from './startVitestModuleRunner.DB-7oCpn.js'; | ||
| import { p as provideWorkerState } from './utils.BX5Fg8C4.js'; | ||
| function interopCommonJsModule(interopDefault, mod) { | ||
| if (isPrimitive(mod) || Array.isArray(mod) || mod instanceof Promise) return { | ||
| keys: [], | ||
| moduleExports: {}, | ||
| defaultExport: mod | ||
| }; | ||
| if (interopDefault !== false && "__esModule" in mod && !isPrimitive(mod.default)) { | ||
| const defaultKets = Object.keys(mod.default); | ||
| const moduleKeys = Object.keys(mod); | ||
| const allKeys = new Set([...defaultKets, ...moduleKeys]); | ||
| allKeys.delete("default"); | ||
| return { | ||
| keys: Array.from(allKeys), | ||
| moduleExports: new Proxy(mod, { get(mod, prop) { | ||
| return mod[prop] ?? mod.default?.[prop]; | ||
| } }), | ||
| defaultExport: mod | ||
| }; | ||
| } | ||
| return { | ||
| keys: Object.keys(mod).filter((key) => key !== "default"), | ||
| moduleExports: mod, | ||
| defaultExport: mod | ||
| }; | ||
| } | ||
| function isPrimitive(obj) { | ||
| return !(obj != null && (typeof obj === "object" || typeof obj === "function")); | ||
| } | ||
| const SyntheticModule = vm.SyntheticModule; | ||
| const SourceTextModule = vm.SourceTextModule; | ||
| const _require = createRequire(import.meta.url); | ||
| const requiresCache = /* @__PURE__ */ new WeakMap(); | ||
| class CommonjsExecutor { | ||
| context; | ||
| requireCache = /* @__PURE__ */ new Map(); | ||
| publicRequireCache = this.createProxyCache(); | ||
| moduleCache = /* @__PURE__ */ new Map(); | ||
| builtinCache = Object.create(null); | ||
| extensions = Object.create(null); | ||
| fs; | ||
| Module; | ||
| interopDefault; | ||
| constructor(options) { | ||
| this.context = options.context; | ||
| this.fs = options.fileMap; | ||
| this.interopDefault = options.interopDefault; | ||
| const primitives = vm.runInContext("({ Object, Array, Error })", this.context); | ||
| // eslint-disable-next-line ts/no-this-alias | ||
| const executor = this; | ||
| this.Module = class Module$1 { | ||
| exports; | ||
| isPreloading = false; | ||
| id; | ||
| filename; | ||
| loaded; | ||
| parent; | ||
| children = []; | ||
| path; | ||
| paths = []; | ||
| constructor(id = "", parent) { | ||
| this.exports = primitives.Object.create(Object.prototype); | ||
| // in our case the path should always be resolved already | ||
| this.path = dirname(id); | ||
| this.id = id; | ||
| this.filename = id; | ||
| this.loaded = false; | ||
| this.parent = parent; | ||
| } | ||
| get require() { | ||
| const require = requiresCache.get(this); | ||
| if (require) return require; | ||
| const _require = Module$1.createRequire(this.id); | ||
| requiresCache.set(this, _require); | ||
| return _require; | ||
| } | ||
| static getSourceMapsSupport = () => ({ | ||
| enabled: false, | ||
| nodeModules: false, | ||
| generatedCode: false | ||
| }); | ||
| static setSourceMapsSupport = () => { | ||
| // noop | ||
| }; | ||
| static register = () => { | ||
| throw new Error(`[vitest] "register" is not available when running in Vitest.`); | ||
| }; | ||
| static registerHooks = () => { | ||
| throw new Error(`[vitest] "registerHooks" is not available when running in Vitest.`); | ||
| }; | ||
| _compile(code, filename) { | ||
| const cjsModule = Module$1.wrap(code); | ||
| const script = new vm.Script(cjsModule, { | ||
| filename, | ||
| importModuleDynamically: options.importModuleDynamically | ||
| }); | ||
| // @ts-expect-error mark script with current identifier | ||
| script.identifier = filename; | ||
| const fn = script.runInContext(executor.context); | ||
| const __dirname = dirname(filename); | ||
| executor.requireCache.set(filename, this); | ||
| try { | ||
| fn(this.exports, this.require, this, filename, __dirname); | ||
| return this.exports; | ||
| } finally { | ||
| this.loaded = true; | ||
| } | ||
| } | ||
| // exposed for external use, Node.js does the opposite | ||
| static _load = (request, parent, _isMain) => { | ||
| return Module$1.createRequire(parent?.filename ?? request)(request); | ||
| }; | ||
| static wrap = (script) => { | ||
| return Module$1.wrapper[0] + script + Module$1.wrapper[1]; | ||
| }; | ||
| static wrapper = new primitives.Array("(function (exports, require, module, __filename, __dirname) { ", "\n});"); | ||
| static builtinModules = Module.builtinModules; | ||
| static findSourceMap = Module.findSourceMap; | ||
| static SourceMap = Module.SourceMap; | ||
| static syncBuiltinESMExports = Module.syncBuiltinESMExports; | ||
| static _cache = executor.publicRequireCache; | ||
| static _extensions = executor.extensions; | ||
| static createRequire = (filename) => { | ||
| return executor.createRequire(filename); | ||
| }; | ||
| static runMain = () => { | ||
| throw new primitives.Error("[vitest] \"runMain\" is not implemented."); | ||
| }; | ||
| // @ts-expect-error not typed | ||
| static _resolveFilename = Module._resolveFilename; | ||
| // @ts-expect-error not typed | ||
| static _findPath = Module._findPath; | ||
| // @ts-expect-error not typed | ||
| static _initPaths = Module._initPaths; | ||
| // @ts-expect-error not typed | ||
| static _preloadModules = Module._preloadModules; | ||
| // @ts-expect-error not typed | ||
| static _resolveLookupPaths = Module._resolveLookupPaths; | ||
| // @ts-expect-error not typed | ||
| static globalPaths = Module.globalPaths; | ||
| static isBuiltin = Module.isBuiltin; | ||
| static constants = Module.constants; | ||
| static enableCompileCache = Module.enableCompileCache; | ||
| static getCompileCacheDir = Module.getCompileCacheDir; | ||
| static flushCompileCache = Module.flushCompileCache; | ||
| static stripTypeScriptTypes = Module.stripTypeScriptTypes; | ||
| static findPackageJSON = Module.findPackageJSON; | ||
| static Module = Module$1; | ||
| }; | ||
| this.extensions[".js"] = this.requireJs; | ||
| this.extensions[".json"] = this.requireJson; | ||
| } | ||
| requireJs = (m, filename) => { | ||
| const content = this.fs.readFile(filename); | ||
| m._compile(content, filename); | ||
| }; | ||
| requireJson = (m, filename) => { | ||
| const code = this.fs.readFile(filename); | ||
| m.exports = JSON.parse(code); | ||
| }; | ||
| static cjsConditions; | ||
| static getCjsConditions() { | ||
| if (!CommonjsExecutor.cjsConditions) CommonjsExecutor.cjsConditions = parseCjsConditions(process.execArgv, process.env.NODE_OPTIONS); | ||
| return CommonjsExecutor.cjsConditions; | ||
| } | ||
| createRequire = (filename) => { | ||
| const _require = createRequire(filename); | ||
| const resolve = (id, options) => { | ||
| return _require.resolve(id, { | ||
| ...options, | ||
| conditions: CommonjsExecutor.getCjsConditions() | ||
| }); | ||
| }; | ||
| const require = ((id) => { | ||
| const resolved = resolve(id); | ||
| if (extname(resolved) === ".node" || isBuiltin(resolved)) return this.requireCoreModule(resolved); | ||
| const module = new this.Module(resolved); | ||
| return this.loadCommonJSModule(module, resolved); | ||
| }); | ||
| require.resolve = resolve; | ||
| require.resolve.paths = _require.resolve.paths; | ||
| Object.defineProperty(require, "extensions", { | ||
| get: () => this.extensions, | ||
| set: () => {}, | ||
| configurable: true | ||
| }); | ||
| require.main = void 0; | ||
| require.cache = this.publicRequireCache; | ||
| return require; | ||
| }; | ||
| createProxyCache() { | ||
| return new Proxy(Object.create(null), { | ||
| defineProperty: () => true, | ||
| deleteProperty: () => true, | ||
| set: () => true, | ||
| get: (_, key) => this.requireCache.get(key), | ||
| has: (_, key) => this.requireCache.has(key), | ||
| ownKeys: () => Array.from(this.requireCache.keys()), | ||
| getOwnPropertyDescriptor() { | ||
| return { | ||
| configurable: true, | ||
| enumerable: true | ||
| }; | ||
| } | ||
| }); | ||
| } | ||
| // very naive implementation for Node.js require | ||
| loadCommonJSModule(module, filename) { | ||
| const cached = this.requireCache.get(filename); | ||
| if (cached) return cached.exports; | ||
| const extension = this.findLongestRegisteredExtension(filename); | ||
| (this.extensions[extension] || this.extensions[".js"])(module, filename); | ||
| return module.exports; | ||
| } | ||
| findLongestRegisteredExtension(filename) { | ||
| const name = basename(filename); | ||
| let currentExtension; | ||
| let index; | ||
| let startIndex = 0; | ||
| // eslint-disable-next-line no-cond-assign | ||
| while ((index = name.indexOf(".", startIndex)) !== -1) { | ||
| startIndex = index + 1; | ||
| if (index === 0) continue; | ||
| currentExtension = name.slice(index); | ||
| if (this.extensions[currentExtension]) return currentExtension; | ||
| } | ||
| return ".js"; | ||
| } | ||
| getCoreSyntheticModule(identifier) { | ||
| if (this.moduleCache.has(identifier)) return this.moduleCache.get(identifier); | ||
| const exports$1 = this.require(identifier); | ||
| const keys = Object.keys(exports$1); | ||
| const module = new SyntheticModule([...keys, "default"], () => { | ||
| for (const key of keys) module.setExport(key, exports$1[key]); | ||
| module.setExport("default", exports$1); | ||
| }, { | ||
| context: this.context, | ||
| identifier | ||
| }); | ||
| this.moduleCache.set(identifier, module); | ||
| return module; | ||
| } | ||
| getCjsSyntheticModule(path, identifier) { | ||
| if (this.moduleCache.has(identifier)) return this.moduleCache.get(identifier); | ||
| const exports$1 = this.require(path); | ||
| // TODO: technically module should be parsed to find static exports, implement for strict mode in #2854 | ||
| const { keys, moduleExports, defaultExport } = interopCommonJsModule(this.interopDefault, exports$1); | ||
| const module = new SyntheticModule([...keys, "default"], function() { | ||
| for (const key of keys) this.setExport(key, moduleExports[key]); | ||
| this.setExport("default", defaultExport); | ||
| }, { | ||
| context: this.context, | ||
| identifier | ||
| }); | ||
| this.moduleCache.set(identifier, module); | ||
| return module; | ||
| } | ||
| // TODO: use this in strict mode, when available in #2854 | ||
| // private _getNamedCjsExports(path: string): Set<string> { | ||
| // const cachedNamedExports = this.cjsNamedExportsMap.get(path) | ||
| // if (cachedNamedExports) { | ||
| // return cachedNamedExports | ||
| // } | ||
| // if (extname(path) === '.node') { | ||
| // const moduleExports = this.require(path) | ||
| // const namedExports = new Set(Object.keys(moduleExports)) | ||
| // this.cjsNamedExportsMap.set(path, namedExports) | ||
| // return namedExports | ||
| // } | ||
| // const code = this.fs.readFile(path) | ||
| // const { exports, reexports } = parseCjs(code, path) | ||
| // const namedExports = new Set(exports) | ||
| // this.cjsNamedExportsMap.set(path, namedExports) | ||
| // for (const reexport of reexports) { | ||
| // if (isNodeBuiltin(reexport)) { | ||
| // const exports = this.require(reexport) | ||
| // if (exports !== null && typeof exports === 'object') { | ||
| // for (const e of Object.keys(exports)) { | ||
| // namedExports.add(e) | ||
| // } | ||
| // } | ||
| // } | ||
| // else { | ||
| // const require = this.createRequire(path) | ||
| // const resolved = require.resolve(reexport) | ||
| // const exports = this._getNamedCjsExports(resolved) | ||
| // for (const e of exports) { | ||
| // namedExports.add(e) | ||
| // } | ||
| // } | ||
| // } | ||
| // return namedExports | ||
| // } | ||
| require(identifier) { | ||
| if (extname(identifier) === ".node" || isBuiltin(identifier)) return this.requireCoreModule(identifier); | ||
| const module = new this.Module(identifier); | ||
| return this.loadCommonJSModule(module, identifier); | ||
| } | ||
| requireCoreModule(identifier) { | ||
| const normalized = identifier.replace(/^node:/, ""); | ||
| if (this.builtinCache[normalized]) return this.builtinCache[normalized].exports; | ||
| const moduleExports = _require(identifier); | ||
| if (identifier === "node:module" || identifier === "module") { | ||
| const module = new this.Module("/module.js"); | ||
| module.exports = this.Module; | ||
| this.builtinCache[normalized] = module; | ||
| return module.exports; | ||
| } | ||
| this.builtinCache[normalized] = _require.cache[normalized]; | ||
| // TODO: should we wrap module to rethrow context errors? | ||
| return moduleExports; | ||
| } | ||
| } | ||
| // The "module-sync" exports condition (added in Node 22.12/20.19 when | ||
| // require(esm) was unflagged) can resolve to ESM files that our CJS | ||
| // vm.Script executor cannot handle. We exclude it by passing explicit | ||
| // CJS conditions to require.resolve (Node 22.12+). | ||
| // Must be a Set because Node's internal resolver calls conditions.has(). | ||
| // User-specified --conditions/-C flags are respected, except module-sync. | ||
| function parseCjsConditions(execArgv, nodeOptions) { | ||
| const conditions = [ | ||
| "node", | ||
| "require", | ||
| "node-addons" | ||
| ]; | ||
| const args = [...execArgv, ...nodeOptions?.split(/\s+/) ?? []]; | ||
| for (let i = 0; i < args.length; i++) { | ||
| const arg = args[i]; | ||
| const eqMatch = arg.match(/^(?:--conditions|-C)=(.+)$/); | ||
| if (eqMatch) conditions.push(eqMatch[1]); | ||
| else if ((arg === "--conditions" || arg === "-C") && i + 1 < args.length) conditions.push(args[++i]); | ||
| } | ||
| return new Set(conditions.filter((c) => c !== "module-sync")); | ||
| } | ||
| const dataURIRegex = /^data:(?<mime>text\/javascript|application\/json|application\/wasm)(?:;(?<encoding>charset=utf-8|base64))?,(?<code>.*)$/; | ||
| class EsmExecutor { | ||
| moduleCache = /* @__PURE__ */ new Map(); | ||
| esmLinkMap = /* @__PURE__ */ new WeakMap(); | ||
| context; | ||
| #httpIp = IPnumber("127.0.0.0"); | ||
| constructor(executor, options) { | ||
| this.executor = executor; | ||
| this.context = options.context; | ||
| } | ||
| async evaluateModule(m) { | ||
| if (m.status === "unlinked") this.esmLinkMap.set(m, m.link((identifier, referencer) => this.executor.resolveModule(identifier, referencer.identifier))); | ||
| await this.esmLinkMap.get(m); | ||
| if (m.status === "linked") await m.evaluate(); | ||
| return m; | ||
| } | ||
| async createEsModule(fileURL, getCode) { | ||
| const cached = this.moduleCache.get(fileURL); | ||
| if (cached) return cached; | ||
| const promise = this.loadEsModule(fileURL, getCode); | ||
| this.moduleCache.set(fileURL, promise); | ||
| return promise; | ||
| } | ||
| async loadEsModule(fileURL, getCode) { | ||
| const code = await getCode(); | ||
| // TODO: should not be allowed in strict mode, implement in #2854 | ||
| if (fileURL.endsWith(".json")) { | ||
| const m = new SyntheticModule(["default"], function() { | ||
| const result = JSON.parse(code); | ||
| this.setExport("default", result); | ||
| }); | ||
| this.moduleCache.set(fileURL, m); | ||
| return m; | ||
| } | ||
| const m = new SourceTextModule(code, { | ||
| identifier: fileURL, | ||
| context: this.context, | ||
| importModuleDynamically: this.executor.importModuleDynamically, | ||
| initializeImportMeta: (meta, mod) => { | ||
| meta.url = mod.identifier; | ||
| if (mod.identifier.startsWith("file:")) { | ||
| const filename = fileURLToPath(mod.identifier); | ||
| meta.filename = filename; | ||
| meta.dirname = dirname$1(filename); | ||
| } | ||
| meta.resolve = (specifier, importer) => { | ||
| return this.executor.resolve(specifier, importer != null ? importer.toString() : mod.identifier); | ||
| }; | ||
| } | ||
| }); | ||
| this.moduleCache.set(fileURL, m); | ||
| return m; | ||
| } | ||
| async createWebAssemblyModule(fileUrl, getCode) { | ||
| const cached = this.moduleCache.get(fileUrl); | ||
| if (cached) return cached; | ||
| const m = this.loadWebAssemblyModule(getCode(), fileUrl); | ||
| this.moduleCache.set(fileUrl, m); | ||
| return m; | ||
| } | ||
| async createNetworkModule(fileUrl) { | ||
| // https://nodejs.org/api/esm.html#https-and-http-imports | ||
| if (fileUrl.startsWith("http:")) { | ||
| const url = new URL(fileUrl); | ||
| if (url.hostname !== "localhost" && url.hostname !== "::1" && (IPnumber(url.hostname) & IPmask(8)) !== this.#httpIp) throw new Error( | ||
| // we don't know the importer, so it's undefined (the same happens in --pool=threads) | ||
| `import of '${fileUrl}' by undefined is not supported: http can only be used to load local resources (use https instead).` | ||
| ); | ||
| } | ||
| return this.createEsModule(fileUrl, () => fetch(fileUrl).then((r) => r.text())); | ||
| } | ||
| async loadWebAssemblyModule(source, identifier) { | ||
| const cached = this.moduleCache.get(identifier); | ||
| if (cached) return cached; | ||
| const wasmModule = await WebAssembly.compile(source); | ||
| const exports$1 = WebAssembly.Module.exports(wasmModule); | ||
| const imports = WebAssembly.Module.imports(wasmModule); | ||
| const moduleLookup = {}; | ||
| for (const { module } of imports) if (moduleLookup[module] === void 0) moduleLookup[module] = await this.executor.resolveModule(module, identifier); | ||
| const evaluateModule = (module) => this.evaluateModule(module); | ||
| return new SyntheticModule(exports$1.map(({ name }) => name), async function() { | ||
| const importsObject = {}; | ||
| for (const { module, name } of imports) { | ||
| if (!importsObject[module]) importsObject[module] = {}; | ||
| await evaluateModule(moduleLookup[module]); | ||
| importsObject[module][name] = moduleLookup[module].namespace[name]; | ||
| } | ||
| const wasmInstance = new WebAssembly.Instance(wasmModule, importsObject); | ||
| for (const { name } of exports$1) this.setExport(name, wasmInstance.exports[name]); | ||
| }, { | ||
| context: this.context, | ||
| identifier | ||
| }); | ||
| } | ||
| cacheModule(identifier, module) { | ||
| this.moduleCache.set(identifier, module); | ||
| } | ||
| resolveCachedModule(identifier) { | ||
| return this.moduleCache.get(identifier); | ||
| } | ||
| async createDataModule(identifier) { | ||
| const cached = this.moduleCache.get(identifier); | ||
| if (cached) return cached; | ||
| const match = identifier.match(dataURIRegex); | ||
| if (!match || !match.groups) throw new Error("Invalid data URI"); | ||
| const mime = match.groups.mime; | ||
| const encoding = match.groups.encoding; | ||
| if (mime === "application/wasm") { | ||
| if (!encoding) throw new Error("Missing data URI encoding"); | ||
| if (encoding !== "base64") throw new Error(`Invalid data URI encoding: ${encoding}`); | ||
| const module = this.loadWebAssemblyModule(Buffer.from(match.groups.code, "base64"), identifier); | ||
| this.moduleCache.set(identifier, module); | ||
| return module; | ||
| } | ||
| let code = match.groups.code; | ||
| if (!encoding || encoding === "charset=utf-8") code = decodeURIComponent(code); | ||
| else if (encoding === "base64") code = Buffer.from(code, "base64").toString(); | ||
| else throw new Error(`Invalid data URI encoding: ${encoding}`); | ||
| if (mime === "application/json") { | ||
| const module = new SyntheticModule(["default"], function() { | ||
| const obj = JSON.parse(code); | ||
| this.setExport("default", obj); | ||
| }, { | ||
| context: this.context, | ||
| identifier | ||
| }); | ||
| this.moduleCache.set(identifier, module); | ||
| return module; | ||
| } | ||
| return this.createEsModule(identifier, () => code); | ||
| } | ||
| } | ||
| function IPnumber(address) { | ||
| const ip = address.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/); | ||
| if (ip) return (+ip[1] << 24) + (+ip[2] << 16) + (+ip[3] << 8) + +ip[4]; | ||
| throw new Error(`Expected IP address, received ${address}`); | ||
| } | ||
| function IPmask(maskSize) { | ||
| return -1 << 32 - maskSize; | ||
| } | ||
| const CLIENT_ID = "/@vite/client"; | ||
| const CLIENT_FILE = pathToFileURL(CLIENT_ID).href; | ||
| class ViteExecutor { | ||
| esm; | ||
| constructor(options) { | ||
| this.options = options; | ||
| this.esm = options.esmExecutor; | ||
| } | ||
| resolve = (identifier) => { | ||
| if (identifier === CLIENT_ID) return identifier; | ||
| }; | ||
| get workerState() { | ||
| return this.options.context.__vitest_worker__; | ||
| } | ||
| async createViteModule(fileUrl) { | ||
| if (fileUrl === CLIENT_FILE || fileUrl === CLIENT_ID) return this.createViteClientModule(); | ||
| const cached = this.esm.resolveCachedModule(fileUrl); | ||
| if (cached) return cached; | ||
| return this.esm.createEsModule(fileUrl, async () => { | ||
| try { | ||
| const result = await this.options.transform(fileUrl); | ||
| if (result.code) return result.code; | ||
| } catch (cause) { | ||
| // rethrow vite error if it cannot load the module because it's not resolved | ||
| if (typeof cause === "object" && cause.code === "ERR_LOAD_URL" || typeof cause?.message === "string" && cause.message.includes("Failed to load url")) { | ||
| const error = new Error(`Cannot find module '${fileUrl}'`, { cause }); | ||
| error.code = "ERR_MODULE_NOT_FOUND"; | ||
| throw error; | ||
| } | ||
| } | ||
| throw new Error(`[vitest] Failed to transform ${fileUrl}. Does the file exist?`); | ||
| }); | ||
| } | ||
| createViteClientModule() { | ||
| const identifier = CLIENT_ID; | ||
| const cached = this.esm.resolveCachedModule(identifier); | ||
| if (cached) return cached; | ||
| const stub = this.options.viteClientModule; | ||
| const moduleKeys = Object.keys(stub); | ||
| const module = new SyntheticModule(moduleKeys, function() { | ||
| moduleKeys.forEach((key) => { | ||
| this.setExport(key, stub[key]); | ||
| }); | ||
| }, { | ||
| context: this.options.context, | ||
| identifier | ||
| }); | ||
| this.esm.cacheModule(identifier, module); | ||
| return module; | ||
| } | ||
| canResolve = (fileUrl) => { | ||
| if (fileUrl === CLIENT_FILE) return true; | ||
| const config = this.workerState.config.deps?.web || {}; | ||
| const [modulePath] = fileUrl.split("?"); | ||
| if (config.transformCss && CSS_LANGS_RE.test(modulePath)) return true; | ||
| if (config.transformAssets && KNOWN_ASSET_RE.test(modulePath)) return true; | ||
| if (toArray(config.transformGlobPattern).some((pattern) => pattern.test(modulePath))) return true; | ||
| return false; | ||
| }; | ||
| } | ||
| const { existsSync } = fs; | ||
| // always defined when we use vm pool | ||
| const nativeResolve = import.meta.resolve; | ||
| // TODO: improve Node.js strict mode support in #2854 | ||
| class ExternalModulesExecutor { | ||
| cjs; | ||
| esm; | ||
| vite; | ||
| context; | ||
| fs; | ||
| resolvers = []; | ||
| #networkSupported = null; | ||
| constructor(options) { | ||
| this.options = options; | ||
| this.context = options.context; | ||
| this.fs = options.fileMap; | ||
| this.esm = new EsmExecutor(this, { context: this.context }); | ||
| this.cjs = new CommonjsExecutor({ | ||
| context: this.context, | ||
| importModuleDynamically: this.importModuleDynamically, | ||
| fileMap: options.fileMap, | ||
| interopDefault: options.interopDefault | ||
| }); | ||
| this.vite = new ViteExecutor({ | ||
| esmExecutor: this.esm, | ||
| context: this.context, | ||
| transform: options.transform, | ||
| viteClientModule: options.viteClientModule | ||
| }); | ||
| this.resolvers = [this.vite.resolve]; | ||
| } | ||
| async import(identifier) { | ||
| const module = await this.createModule(identifier); | ||
| await this.esm.evaluateModule(module); | ||
| return module.namespace; | ||
| } | ||
| require(identifier) { | ||
| return this.cjs.require(identifier); | ||
| } | ||
| createRequire(identifier) { | ||
| return this.cjs.createRequire(identifier); | ||
| } | ||
| // dynamic import can be used in both ESM and CJS, so we have it in the executor | ||
| importModuleDynamically = async (specifier, referencer) => { | ||
| const module = await this.resolveModule(specifier, referencer.identifier); | ||
| return await this.esm.evaluateModule(module); | ||
| }; | ||
| resolveModule = async (specifier, referencer) => { | ||
| let identifier = this.resolve(specifier, referencer); | ||
| if (identifier instanceof Promise) identifier = await identifier; | ||
| return await this.createModule(identifier); | ||
| }; | ||
| resolve(specifier, parent) { | ||
| for (const resolver of this.resolvers) { | ||
| const id = resolver(specifier, parent); | ||
| if (id) return id; | ||
| } | ||
| // import.meta.resolve can be asynchronous in older +18 Node versions | ||
| return nativeResolve(specifier, parent); | ||
| } | ||
| getModuleInformation(identifier) { | ||
| if (identifier.startsWith("data:")) return { | ||
| type: "data", | ||
| url: identifier, | ||
| path: identifier | ||
| }; | ||
| const { file, postfix } = splitFileAndPostfix(identifier); | ||
| const extension = extname(file); | ||
| if (extension === ".node" || isBuiltin(identifier)) return { | ||
| type: "builtin", | ||
| url: identifier, | ||
| path: identifier | ||
| }; | ||
| if (this.isNetworkSupported && (identifier.startsWith("http:") || identifier.startsWith("https:"))) return { | ||
| type: "network", | ||
| url: identifier, | ||
| path: identifier | ||
| }; | ||
| const isFileUrl = identifier.startsWith("file://"); | ||
| const pathUrl = isFileUrl ? fileURLToPath(file) : file; | ||
| const fileUrl = isFileUrl ? identifier : `${pathToFileURL(file)}${postfix}`; | ||
| let type; | ||
| if (this.vite.canResolve(fileUrl)) type = "vite"; | ||
| else if (extension === ".mjs") type = "module"; | ||
| else if (extension === ".cjs") type = "commonjs"; | ||
| else if (extension === ".wasm") | ||
| // still experimental on NodeJS --experimental-wasm-modules | ||
| // cf. ESM_FILE_FORMAT(url) in https://nodejs.org/docs/latest-v20.x/api/esm.html#resolution-algorithm | ||
| type = "wasm"; | ||
| else type = findNearestPackageData(normalize(pathUrl)).type === "module" ? "module" : "commonjs"; | ||
| return { | ||
| type, | ||
| path: pathUrl, | ||
| url: fileUrl | ||
| }; | ||
| } | ||
| createModule(identifier) { | ||
| const { type, url, path } = this.getModuleInformation(identifier); | ||
| // create ERR_MODULE_NOT_FOUND on our own since latest NodeJS's import.meta.resolve doesn't throw on non-existing namespace or path | ||
| // https://github.com/nodejs/node/pull/49038 | ||
| if ((type === "module" || type === "commonjs" || type === "wasm") && !existsSync(path)) { | ||
| const error = /* @__PURE__ */ new Error(`Cannot find ${isBareImport(path) ? "package" : "module"} '${path}'`); | ||
| error.code = "ERR_MODULE_NOT_FOUND"; | ||
| throw error; | ||
| } | ||
| switch (type) { | ||
| case "data": return this.esm.createDataModule(identifier); | ||
| case "builtin": return this.cjs.getCoreSyntheticModule(identifier); | ||
| case "vite": return this.vite.createViteModule(url); | ||
| case "wasm": return this.esm.createWebAssemblyModule(url, () => this.fs.readBuffer(path)); | ||
| case "module": return this.esm.createEsModule(url, () => this.fs.readFileAsync(path)); | ||
| case "commonjs": return this.cjs.getCjsSyntheticModule(path, identifier); | ||
| case "network": return this.esm.createNetworkModule(url); | ||
| default: return type; | ||
| } | ||
| } | ||
| get isNetworkSupported() { | ||
| if (this.#networkSupported == null) if (process.execArgv.includes("--experimental-network-imports")) this.#networkSupported = true; | ||
| else if (process.env.NODE_OPTIONS?.includes("--experimental-network-imports")) this.#networkSupported = true; | ||
| else this.#networkSupported = false; | ||
| return this.#networkSupported; | ||
| } | ||
| } | ||
| const { promises, readFileSync } = fs; | ||
| class FileMap { | ||
| fsCache = /* @__PURE__ */ new Map(); | ||
| fsBufferCache = /* @__PURE__ */ new Map(); | ||
| async readFileAsync(path) { | ||
| const cached = this.fsCache.get(path); | ||
| if (cached != null) return cached; | ||
| const source = await promises.readFile(path, "utf-8"); | ||
| this.fsCache.set(path, source); | ||
| return source; | ||
| } | ||
| readFile(path) { | ||
| const cached = this.fsCache.get(path); | ||
| if (cached != null) return cached; | ||
| const source = readFileSync(path, "utf-8"); | ||
| this.fsCache.set(path, source); | ||
| return source; | ||
| } | ||
| readBuffer(path) { | ||
| const cached = this.fsBufferCache.get(path); | ||
| if (cached != null) return cached; | ||
| const buffer = readFileSync(path); | ||
| this.fsBufferCache.set(path, buffer); | ||
| return buffer; | ||
| } | ||
| } | ||
| const entryFile = pathToFileURL(resolve(distDir, "workers/runVmTests.js")).href; | ||
| const fileMap = new FileMap(); | ||
| const packageCache = /* @__PURE__ */ new Map(); | ||
| async function runVmTests(method, state, traces) { | ||
| const { ctx, rpc } = state; | ||
| const beforeEnvironmentTime = performance.now(); | ||
| const { environment } = await loadEnvironment(ctx.environment.name, ctx.config.root, rpc, traces, true); | ||
| state.environment = environment; | ||
| if (!environment.setupVM) { | ||
| const envName = ctx.environment.name; | ||
| const packageId = envName[0] === "." ? envName : `vitest-environment-${envName}`; | ||
| throw new TypeError(`Environment "${ctx.environment.name}" is not a valid environment. Path "${packageId}" doesn't support vm environment because it doesn't provide "setupVM" method.`); | ||
| } | ||
| const vm = await traces.$("vitest.runtime.environment.setup", { attributes: { | ||
| "vitest.environment": environment.name, | ||
| "vitest.environment.vite_environment": environment.viteEnvironment || environment.name | ||
| } }, () => environment.setupVM(ctx.environment.options || ctx.config.environmentOptions || {})); | ||
| state.durations.environment = performance.now() - beforeEnvironmentTime; | ||
| process.env.VITEST_VM_POOL = "1"; | ||
| if (!vm.getVmContext) throw new TypeError(`Environment ${environment.name} doesn't provide "getVmContext" method. It should return a context created by "vm.createContext" method.`); | ||
| const context = vm.getVmContext(); | ||
| if (!isContext(context)) throw new TypeError(`Environment ${environment.name} doesn't provide a valid context. It should be created by "vm.createContext" method.`); | ||
| provideWorkerState(context, state); | ||
| // this is unfortunately needed for our own dependencies | ||
| // we need to find a way to not rely on this by default | ||
| // because browser doesn't provide these globals | ||
| context.process = process; | ||
| context.global = context; | ||
| context.console = state.config.disableConsoleIntercept ? console : createCustomConsole(state); | ||
| // TODO: don't hardcode setImmediate in fake timers defaults | ||
| context.setImmediate = setImmediate; | ||
| context.clearImmediate = clearImmediate; | ||
| const stubs = getDefaultRequestStubs(context); | ||
| const externalModulesExecutor = new ExternalModulesExecutor({ | ||
| context, | ||
| fileMap, | ||
| packageCache, | ||
| transform: rpc.transform, | ||
| viteClientModule: stubs["/@vite/client"] | ||
| }); | ||
| process.exit = (code = process.exitCode || 0) => { | ||
| throw new Error(`process.exit unexpectedly called with "${code}"`); | ||
| }; | ||
| listenForErrors(() => state); | ||
| const moduleRunner = startVitestModuleRunner({ | ||
| context, | ||
| evaluatedModules: state.evaluatedModules, | ||
| state, | ||
| externalModulesExecutor, | ||
| createImportMeta: createNodeImportMeta, | ||
| traces | ||
| }); | ||
| emitModuleRunner(moduleRunner); | ||
| Object.defineProperty(context, VITEST_VM_CONTEXT_SYMBOL, { | ||
| value: { | ||
| context, | ||
| externalModulesExecutor | ||
| }, | ||
| configurable: true, | ||
| enumerable: false, | ||
| writable: false | ||
| }); | ||
| context.__vitest_mocker__ = moduleRunner.mocker; | ||
| if (ctx.config.serializedDefines) try { | ||
| runInContext(ctx.config.serializedDefines, context, { filename: "virtual:load-defines.js" }); | ||
| } catch (error) { | ||
| throw new Error(`Failed to load custom "defines": ${error.message}`); | ||
| } | ||
| await moduleRunner.mocker.initializeSpyModule(); | ||
| const { run } = await moduleRunner.import(entryFile); | ||
| try { | ||
| await run(method, ctx.files, ctx.config, moduleRunner, traces); | ||
| } finally { | ||
| await traces.$("vitest.runtime.environment.teardown", () => vm.teardown?.()); | ||
| } | ||
| } | ||
| function setupVmWorker(context) { | ||
| if (context.config.experimental.viteModuleRunner === false) throw new Error(`Pool "${context.pool}" cannot run with "experimental.viteModuleRunner: false". Please, use "threads" or "forks" instead.`); | ||
| } | ||
| export { runVmTests as r, setupVmWorker as s }; |
+1
-1
@@ -1,2 +0,2 @@ | ||
| import { c as createCLI } from './chunks/cac.D3xHeqeL.js'; | ||
| import { c as createCLI } from './chunks/cac.DdICfEr1.js'; | ||
| import '@vitest/utils/helpers'; | ||
@@ -3,0 +3,0 @@ import 'events'; |
+3
-3
| import * as vite from 'vite'; | ||
| import { resolveConfig as resolveConfig$1, mergeConfig } from 'vite'; | ||
| export { esbuildVersion, isCSSRequest, isFileLoadingAllowed, parseAst, parseAstAsync, rollupVersion, version as viteVersion } from 'vite'; | ||
| import { V as Vitest, a as VitestPlugin } from './chunks/cli-api.24X8XwN1.js'; | ||
| export { F as ForksPoolWorker, G as GitNotFoundError, b as TestsNotFoundError, T as ThreadsPoolWorker, c as TypecheckPoolWorker, d as VitestPackageInstaller, e as VmForksPoolWorker, f as VmThreadsPoolWorker, g as createDebugger, h as createMethodsRPC, i as createViteLogger, j as createVitest, k as escapeTestName, l as experimental_getRunnerTask, m as getFilePoolName, n as isFileServingAllowed, o as isValidApiRequest, r as registerConsoleShortcuts, p as resolveFsAllow, s as startVitest } from './chunks/cli-api.24X8XwN1.js'; | ||
| export { p as parseCLI } from './chunks/cac.D3xHeqeL.js'; | ||
| import { V as Vitest, a as VitestPlugin } from './chunks/cli-api.BK8pd4xc.js'; | ||
| export { F as ForksPoolWorker, G as GitNotFoundError, b as TestsNotFoundError, T as ThreadsPoolWorker, c as TypecheckPoolWorker, d as VitestPackageInstaller, e as VmForksPoolWorker, f as VmThreadsPoolWorker, g as createDebugger, h as createMethodsRPC, i as createViteLogger, j as createVitest, k as escapeTestName, l as experimental_getRunnerTask, m as getFilePoolName, n as isFileServingAllowed, o as isValidApiRequest, r as registerConsoleShortcuts, p as resolveFsAllow, s as startVitest } from './chunks/cli-api.BK8pd4xc.js'; | ||
| export { p as parseCLI } from './chunks/cac.DdICfEr1.js'; | ||
| import { r as resolveConfig$2 } from './chunks/coverage.DM_a_rWm.js'; | ||
@@ -8,0 +8,0 @@ export { B as BaseCoverageProvider, a as BaseSequencer, b as resolveApiServerConfig } from './chunks/coverage.DM_a_rWm.js'; |
+1
-1
| import { VitestModuleEvaluator } from './module-evaluator.js'; | ||
| import { V as VITEST_VM_CONTEXT_SYMBOL, s as startVitestModuleRunner, a as VitestModuleRunner } from './chunks/startVitestModuleRunner.CCBQZ9DQ.js'; | ||
| import { V as VITEST_VM_CONTEXT_SYMBOL, s as startVitestModuleRunner, a as VitestModuleRunner } from './chunks/startVitestModuleRunner.DB-7oCpn.js'; | ||
| import { g as getWorkerState } from './chunks/utils.BX5Fg8C4.js'; | ||
@@ -4,0 +4,0 @@ export { e as builtinEnvironments, p as populateGlobal } from './chunks/index.DC7d2Pf8.js'; |
+3
-3
@@ -1,3 +0,3 @@ | ||
| export { r as runBaseTests, s as setupEnvironment } from './chunks/base.BE3cXZj3.js'; | ||
| export { i as init } from './chunks/init.d4hAcNdp.js'; | ||
| export { r as runBaseTests, s as setupEnvironment } from './chunks/base.B6Opl8PE.js'; | ||
| export { i as init } from './chunks/init.k9zZ9sLh.js'; | ||
| import 'node:vm'; | ||
@@ -39,3 +39,3 @@ import '@vitest/spy'; | ||
| import './chunks/evaluatedModules.Dg1zASAC.js'; | ||
| import './chunks/startVitestModuleRunner.CCBQZ9DQ.js'; | ||
| import './chunks/startVitestModuleRunner.DB-7oCpn.js'; | ||
| import './chunks/modules.BJuCwlRJ.js'; | ||
@@ -42,0 +42,0 @@ import './path.js'; |
@@ -1,3 +0,3 @@ | ||
| import { r as runBaseTests, s as setupBaseEnvironment } from '../chunks/base.BE3cXZj3.js'; | ||
| import { w as workerInit } from '../chunks/init-forks.B4VBaxRV.js'; | ||
| import { r as runBaseTests, s as setupBaseEnvironment } from '../chunks/base.B6Opl8PE.js'; | ||
| import { w as workerInit } from '../chunks/init-forks.H5ZuobOQ.js'; | ||
| import 'node:vm'; | ||
@@ -25,3 +25,3 @@ import '@vitest/spy'; | ||
| import '../chunks/_commonjsHelpers.D26ty3Ew.js'; | ||
| import '../chunks/init.d4hAcNdp.js'; | ||
| import '../chunks/init.k9zZ9sLh.js'; | ||
| import 'node:fs'; | ||
@@ -31,3 +31,3 @@ import 'node:module'; | ||
| import 'vite/module-runner'; | ||
| import '../chunks/startVitestModuleRunner.CCBQZ9DQ.js'; | ||
| import '../chunks/startVitestModuleRunner.DB-7oCpn.js'; | ||
| import '../chunks/modules.BJuCwlRJ.js'; | ||
@@ -34,0 +34,0 @@ import '../path.js'; |
@@ -1,3 +0,3 @@ | ||
| import { s as setupBaseEnvironment, r as runBaseTests } from '../chunks/base.BE3cXZj3.js'; | ||
| import { w as workerInit } from '../chunks/init-threads.C_ClEpAf.js'; | ||
| import { s as setupBaseEnvironment, r as runBaseTests } from '../chunks/base.B6Opl8PE.js'; | ||
| import { w as workerInit } from '../chunks/init-threads.6kl1khcL.js'; | ||
| import 'node:vm'; | ||
@@ -25,3 +25,3 @@ import '@vitest/spy'; | ||
| import '../chunks/_commonjsHelpers.D26ty3Ew.js'; | ||
| import '../chunks/init.d4hAcNdp.js'; | ||
| import '../chunks/init.k9zZ9sLh.js'; | ||
| import 'node:fs'; | ||
@@ -31,3 +31,3 @@ import 'node:module'; | ||
| import 'vite/module-runner'; | ||
| import '../chunks/startVitestModuleRunner.CCBQZ9DQ.js'; | ||
| import '../chunks/startVitestModuleRunner.DB-7oCpn.js'; | ||
| import '../chunks/modules.BJuCwlRJ.js'; | ||
@@ -34,0 +34,0 @@ import '../path.js'; |
@@ -1,4 +0,4 @@ | ||
| import { w as workerInit } from '../chunks/init-forks.B4VBaxRV.js'; | ||
| import { r as runVmTests, s as setupVmWorker } from '../chunks/vm.B2aHeaNw.js'; | ||
| import '../chunks/init.d4hAcNdp.js'; | ||
| import { w as workerInit } from '../chunks/init-forks.H5ZuobOQ.js'; | ||
| import { r as runVmTests, s as setupVmWorker } from '../chunks/vm.CXMd5FHa.js'; | ||
| import '../chunks/init.k9zZ9sLh.js'; | ||
| import 'node:fs'; | ||
@@ -9,3 +9,3 @@ import 'node:module'; | ||
| import 'vite/module-runner'; | ||
| import '../chunks/startVitestModuleRunner.CCBQZ9DQ.js'; | ||
| import '../chunks/startVitestModuleRunner.DB-7oCpn.js'; | ||
| import '@vitest/utils/helpers'; | ||
@@ -12,0 +12,0 @@ import '../chunks/modules.BJuCwlRJ.js'; |
@@ -1,5 +0,5 @@ | ||
| import { w as workerInit } from '../chunks/init-threads.C_ClEpAf.js'; | ||
| import { s as setupVmWorker, r as runVmTests } from '../chunks/vm.B2aHeaNw.js'; | ||
| import { w as workerInit } from '../chunks/init-threads.6kl1khcL.js'; | ||
| import { s as setupVmWorker, r as runVmTests } from '../chunks/vm.CXMd5FHa.js'; | ||
| import 'node:worker_threads'; | ||
| import '../chunks/init.d4hAcNdp.js'; | ||
| import '../chunks/init.k9zZ9sLh.js'; | ||
| import 'node:fs'; | ||
@@ -10,3 +10,3 @@ import 'node:module'; | ||
| import 'vite/module-runner'; | ||
| import '../chunks/startVitestModuleRunner.CCBQZ9DQ.js'; | ||
| import '../chunks/startVitestModuleRunner.DB-7oCpn.js'; | ||
| import '@vitest/utils/helpers'; | ||
@@ -13,0 +13,0 @@ import '../chunks/modules.BJuCwlRJ.js'; |
+14
-14
| { | ||
| "name": "vitest", | ||
| "type": "module", | ||
| "version": "4.1.9", | ||
| "version": "4.1.10", | ||
| "description": "Next generation testing framework powered by Vite", | ||
@@ -138,8 +138,8 @@ "author": "Anthony Fu <anthonyfu117@hotmail.com>", | ||
| "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", | ||
| "@vitest/browser-playwright": "4.1.9", | ||
| "@vitest/browser-preview": "4.1.9", | ||
| "@vitest/browser-webdriverio": "4.1.9", | ||
| "@vitest/coverage-istanbul": "4.1.9", | ||
| "@vitest/coverage-v8": "4.1.9", | ||
| "@vitest/ui": "4.1.9" | ||
| "@vitest/browser-preview": "4.1.10", | ||
| "@vitest/coverage-istanbul": "4.1.10", | ||
| "@vitest/browser-playwright": "4.1.10", | ||
| "@vitest/browser-webdriverio": "4.1.10", | ||
| "@vitest/coverage-v8": "4.1.10", | ||
| "@vitest/ui": "4.1.10" | ||
| }, | ||
@@ -198,9 +198,9 @@ "peerDependenciesMeta": { | ||
| "why-is-node-running": "^2.3.0", | ||
| "@vitest/mocker": "4.1.9", | ||
| "@vitest/expect": "4.1.9", | ||
| "@vitest/pretty-format": "4.1.9", | ||
| "@vitest/runner": "4.1.9", | ||
| "@vitest/spy": "4.1.9", | ||
| "@vitest/snapshot": "4.1.9", | ||
| "@vitest/utils": "4.1.9" | ||
| "@vitest/expect": "4.1.10", | ||
| "@vitest/mocker": "4.1.10", | ||
| "@vitest/pretty-format": "4.1.10", | ||
| "@vitest/runner": "4.1.10", | ||
| "@vitest/spy": "4.1.10", | ||
| "@vitest/snapshot": "4.1.10", | ||
| "@vitest/utils": "4.1.10" | ||
| }, | ||
@@ -207,0 +207,0 @@ "devDependencies": { |
| import { runInThisContext } from 'node:vm'; | ||
| import * as spyModule from '@vitest/spy'; | ||
| import { r as resolveTestRunner, a as resolveSnapshotEnvironment, d as detectAsyncLeaks, s as setupChaiConfig } from './index.DXx9Dtk7.js'; | ||
| import { l as loadEnvironment, e as emitModuleRunner, a as listenForErrors } from './init.d4hAcNdp.js'; | ||
| import { N as NativeModuleRunner } from './nativeModuleRunner.BIakptoF.js'; | ||
| import { T as Traces } from './traces.DT5aQ62U.js'; | ||
| import { V as VitestEvaluatedModules } from './evaluatedModules.Dg1zASAC.js'; | ||
| import { s as startVitestModuleRunner, c as createNodeImportMeta } from './startVitestModuleRunner.CCBQZ9DQ.js'; | ||
| import { performance as performance$1 } from 'node:perf_hooks'; | ||
| import { startTests, collectTests } from '@vitest/runner'; | ||
| import { s as setupCommonEnv, b as startCoverageInsideWorker, c as stopCoverageInsideWorker } from './setup-common.DYx3LtFI.js'; | ||
| import { g as globalExpect, v as vi } from './test.DNmyFkvJ.js'; | ||
| import { c as closeInspector } from './inspector.CvyFGlXm.js'; | ||
| import { createRequire } from 'node:module'; | ||
| import timers from 'node:timers'; | ||
| import timersPromises from 'node:timers/promises'; | ||
| import util from 'node:util'; | ||
| import { KNOWN_ASSET_TYPES } from '@vitest/utils/constants'; | ||
| import { i as index } from './index.DdgEv5B1.js'; | ||
| import { g as getWorkerState, r as resetModules, p as provideWorkerState, a as getSafeWorkerState } from './utils.BX5Fg8C4.js'; | ||
| // this should only be used in Node | ||
| let globalSetup = false; | ||
| async function setupGlobalEnv(config, environment) { | ||
| await setupCommonEnv(config); | ||
| Object.defineProperty(globalThis, "__vitest_index__", { | ||
| value: index, | ||
| enumerable: false | ||
| }); | ||
| globalExpect.setState({ environment: environment.name }); | ||
| if (globalSetup) return; | ||
| globalSetup = true; | ||
| if ((environment.viteEnvironment || environment.name) === "client") { | ||
| const _require = createRequire(import.meta.url); | ||
| // always mock "required" `css` files, because we cannot process them | ||
| _require.extensions[".css"] = resolveCss; | ||
| _require.extensions[".scss"] = resolveCss; | ||
| _require.extensions[".sass"] = resolveCss; | ||
| _require.extensions[".less"] = resolveCss; | ||
| // since we are using Vite, we can assume how these will be resolved | ||
| KNOWN_ASSET_TYPES.forEach((type) => { | ||
| _require.extensions[`.${type}`] = resolveAsset; | ||
| }); | ||
| process.env.SSR = ""; | ||
| } else process.env.SSR = "1"; | ||
| // @ts-expect-error not typed global for patched timers | ||
| globalThis.__vitest_required__ = { | ||
| util, | ||
| timers, | ||
| timersPromises | ||
| }; | ||
| if (!config.disableConsoleIntercept) await setupConsoleLogSpy(); | ||
| } | ||
| function resolveCss(mod) { | ||
| mod.exports = ""; | ||
| } | ||
| function resolveAsset(mod, url) { | ||
| mod.exports = url; | ||
| } | ||
| async function setupConsoleLogSpy() { | ||
| const { createCustomConsole } = await import('./console.3WNpx0tS.js'); | ||
| globalThis.console = createCustomConsole(); | ||
| } | ||
| // browser shouldn't call this! | ||
| async function run(method, files, config, moduleRunner, environment, traces) { | ||
| const workerState = getWorkerState(); | ||
| const [testRunner] = await Promise.all([ | ||
| traces.$("vitest.runtime.runner", () => resolveTestRunner(config, moduleRunner, traces)), | ||
| traces.$("vitest.runtime.global_env", () => setupGlobalEnv(config, environment)), | ||
| traces.$("vitest.runtime.coverage.start", () => startCoverageInsideWorker(config.coverage, moduleRunner, { isolate: config.isolate })), | ||
| traces.$("vitest.runtime.snapshot.environment", async () => { | ||
| if (!workerState.config.snapshotOptions.snapshotEnvironment) workerState.config.snapshotOptions.snapshotEnvironment = await resolveSnapshotEnvironment(config, moduleRunner); | ||
| }) | ||
| ]); | ||
| workerState.onCancel((reason) => { | ||
| closeInspector(config); | ||
| testRunner.cancel?.(reason); | ||
| }); | ||
| workerState.durations.prepare = performance$1.now() - workerState.durations.prepare; | ||
| await traces.$(`vitest.test.runner.${method}`, async () => { | ||
| for (const file of files) { | ||
| if (config.isolate) { | ||
| moduleRunner.mocker?.reset(); | ||
| resetModules(workerState.evaluatedModules, true); | ||
| } | ||
| workerState.filepath = file.filepath; | ||
| if (method === "run") { | ||
| const collectAsyncLeaks = config.detectAsyncLeaks ? detectAsyncLeaks(file.filepath, workerState.ctx.projectName) : void 0; | ||
| await traces.$(`vitest.test.runner.${method}.module`, { attributes: { "code.file.path": file.filepath } }, () => startTests([file], testRunner)); | ||
| const leaks = await collectAsyncLeaks?.(); | ||
| if (leaks?.length) workerState.rpc.onAsyncLeaks(leaks); | ||
| } else await traces.$(`vitest.test.runner.${method}.module`, { attributes: { "code.file.path": file.filepath } }, () => collectTests([file], testRunner)); | ||
| // reset after tests, because user might call `vi.setConfig` in setupFile | ||
| vi.resetConfig(); | ||
| // mocks should not affect different files | ||
| vi.restoreAllMocks(); | ||
| } | ||
| }); | ||
| await traces.$("vitest.runtime.coverage.stop", () => stopCoverageInsideWorker(config.coverage, moduleRunner, { isolate: config.isolate })); | ||
| } | ||
| let _moduleRunner; | ||
| const evaluatedModules = new VitestEvaluatedModules(); | ||
| const moduleExecutionInfo = /* @__PURE__ */ new Map(); | ||
| async function startModuleRunner(options) { | ||
| if (_moduleRunner) return _moduleRunner; | ||
| process.exit = (code = process.exitCode || 0) => { | ||
| throw new Error(`process.exit unexpectedly called with "${code}"`); | ||
| }; | ||
| const state = () => getSafeWorkerState() || options.state; | ||
| listenForErrors(state); | ||
| if (options.state.config.experimental.viteModuleRunner === false) { | ||
| const root = options.state.config.root; | ||
| let mocker; | ||
| if (options.state.config.experimental.nodeLoader !== false) { | ||
| // this additionally imports acorn/magic-string | ||
| const { NativeModuleMocker } = await import('./nativeModuleMocker.BLzfffNb.js'); | ||
| mocker = new NativeModuleMocker({ | ||
| async resolveId(id, importer) { | ||
| // TODO: use import.meta.resolve instead | ||
| return state().rpc.resolve(id, importer, "__vitest__"); | ||
| }, | ||
| root, | ||
| moduleDirectories: state().config.deps.moduleDirectories || ["/node_modules/"], | ||
| traces: options.traces || new Traces({ enabled: false }), | ||
| getCurrentTestFilepath() { | ||
| return state().filepath; | ||
| }, | ||
| spyModule | ||
| }); | ||
| } | ||
| _moduleRunner = new NativeModuleRunner(root, mocker); | ||
| return _moduleRunner; | ||
| } | ||
| _moduleRunner = startVitestModuleRunner(options); | ||
| return _moduleRunner; | ||
| } | ||
| let _currentEnvironment; | ||
| let _environmentTime; | ||
| /** @experimental */ | ||
| async function setupBaseEnvironment(context) { | ||
| if (context.config.experimental.viteModuleRunner === false) { | ||
| const { setupNodeLoaderHooks } = await import('./native.DPzPHdi5.js'); | ||
| await setupNodeLoaderHooks(context); | ||
| } | ||
| const startTime = performance.now(); | ||
| const { environment: { name: environmentName, options: environmentOptions }, rpc, config } = context; | ||
| // we could load @vite/env, but it would take ~8ms, while this takes ~0,02ms | ||
| if (context.config.serializedDefines) try { | ||
| runInThisContext(`(() =>{\n${context.config.serializedDefines}})()`, { | ||
| lineOffset: 1, | ||
| filename: "virtual:load-defines.js" | ||
| }); | ||
| } catch (error) { | ||
| throw new Error(`Failed to load custom "defines": ${error.message}`); | ||
| } | ||
| const otel = context.traces; | ||
| const { environment, loader } = await loadEnvironment(environmentName, config.root, rpc, otel, context.config.experimental.viteModuleRunner); | ||
| _currentEnvironment = environment; | ||
| const env = await otel.$("vitest.runtime.environment.setup", { attributes: { | ||
| "vitest.environment": environment.name, | ||
| "vitest.environment.vite_environment": environment.viteEnvironment || environment.name | ||
| } }, () => environment.setup(globalThis, environmentOptions || config.environmentOptions || {})); | ||
| _environmentTime = performance.now() - startTime; | ||
| if (config.chaiConfig) setupChaiConfig(config.chaiConfig); | ||
| return async () => { | ||
| await otel.$("vitest.runtime.environment.teardown", () => env.teardown(globalThis)); | ||
| await loader?.close(); | ||
| }; | ||
| } | ||
| /** @experimental */ | ||
| async function runBaseTests(method, state, traces) { | ||
| const { ctx } = state; | ||
| state.environment = _currentEnvironment; | ||
| state.durations.environment = _environmentTime; | ||
| // state has new context, but we want to reuse existing ones | ||
| state.evaluatedModules = evaluatedModules; | ||
| state.moduleExecutionInfo = moduleExecutionInfo; | ||
| provideWorkerState(globalThis, state); | ||
| if (ctx.invalidates) ctx.invalidates.forEach((filepath) => { | ||
| (state.evaluatedModules.fileToModulesMap.get(filepath) || []).forEach((module) => { | ||
| state.evaluatedModules.invalidateModule(module); | ||
| }); | ||
| }); | ||
| ctx.files.forEach((i) => { | ||
| const filepath = i.filepath; | ||
| (state.evaluatedModules.fileToModulesMap.get(filepath) || []).forEach((module) => { | ||
| state.evaluatedModules.invalidateModule(module); | ||
| }); | ||
| }); | ||
| const moduleRunner = await startModuleRunner({ | ||
| state, | ||
| evaluatedModules: state.evaluatedModules, | ||
| spyModule, | ||
| createImportMeta: createNodeImportMeta, | ||
| traces | ||
| }); | ||
| emitModuleRunner(moduleRunner); | ||
| await run(method, ctx.files, ctx.config, moduleRunner, _currentEnvironment, traces); | ||
| } | ||
| export { runBaseTests as r, setupBaseEnvironment as s }; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| import { i as init } from './init.d4hAcNdp.js'; | ||
| if (!process.send) throw new Error("Expected worker to be run in node:child_process"); | ||
| // Store globals in case tests overwrite them | ||
| const processExit = process.exit.bind(process); | ||
| const processSend = process.send.bind(process); | ||
| const processOn = process.on.bind(process); | ||
| const processOff = process.off.bind(process); | ||
| const processRemoveAllListeners = process.removeAllListeners.bind(process); | ||
| // Work-around for nodejs/node#55094 | ||
| if (process.execArgv.some((execArg) => execArg.startsWith("--prof") || execArg.startsWith("--cpu-prof") || execArg.startsWith("--heap-prof") || execArg.startsWith("--diagnostic-dir"))) processOn("SIGTERM", () => processExit()); | ||
| processOn("error", onError); | ||
| function workerInit(options) { | ||
| const { runTests } = options; | ||
| init({ | ||
| post: (v) => processSend(v), | ||
| on: (cb) => processOn("message", cb), | ||
| off: (cb) => processOff("message", cb), | ||
| teardown: () => { | ||
| processRemoveAllListeners("message"); | ||
| processOff("error", onError); | ||
| }, | ||
| runTests: (state, traces) => executeTests("run", state, traces), | ||
| collectTests: (state, traces) => executeTests("collect", state, traces), | ||
| setup: options.setup | ||
| }); | ||
| async function executeTests(method, state, traces) { | ||
| try { | ||
| await runTests(method, state, traces); | ||
| } finally { | ||
| process.exit = processExit; | ||
| } | ||
| } | ||
| } | ||
| // Prevent leaving worker in loops where it tries to send message to closed main | ||
| // thread, errors, and tries to send the error. | ||
| function onError(error) { | ||
| if (error?.code === "ERR_IPC_CHANNEL_CLOSED" || error?.code === "EPIPE") processExit(1); | ||
| } | ||
| export { workerInit as w }; |
| import { isMainThread, parentPort } from 'node:worker_threads'; | ||
| import { i as init } from './init.d4hAcNdp.js'; | ||
| if (isMainThread || !parentPort) throw new Error("Expected worker to be run in node:worker_threads"); | ||
| function workerInit(options) { | ||
| const { runTests } = options; | ||
| init({ | ||
| post: (response) => parentPort.postMessage(response), | ||
| on: (callback) => parentPort.on("message", callback), | ||
| off: (callback) => parentPort.off("message", callback), | ||
| teardown: () => parentPort.removeAllListeners("message"), | ||
| runTests: async (state, traces) => runTests("run", state, traces), | ||
| collectTests: async (state, traces) => runTests("collect", state, traces), | ||
| setup: options.setup | ||
| }); | ||
| } | ||
| export { workerInit as w }; |
| import { readFileSync } from 'node:fs'; | ||
| import { isBuiltin } from 'node:module'; | ||
| import { pathToFileURL } from 'node:url'; | ||
| import { resolve } from 'pathe'; | ||
| import { ModuleRunner, EvaluatedModules } from 'vite/module-runner'; | ||
| import { b as VitestTransport } from './startVitestModuleRunner.CCBQZ9DQ.js'; | ||
| import { e as environments } from './index.DC7d2Pf8.js'; | ||
| import { serializeValue } from '@vitest/utils/serialize'; | ||
| import { serializeError } from '@vitest/utils/error'; | ||
| import { disableDefaultColors } from 'tinyrainbow'; | ||
| import { T as Traces } from './traces.DT5aQ62U.js'; | ||
| import { o as onCancel, a as rpcDone, c as createRuntimeRpc } from './rpc.MzXet3jl.js'; | ||
| import { createStackString, parseStacktrace } from '@vitest/utils/source-map'; | ||
| import { s as setupInspect } from './inspector.CvyFGlXm.js'; | ||
| import { V as VitestEvaluatedModules } from './evaluatedModules.Dg1zASAC.js'; | ||
| import { E as EnvironmentTeardownError } from './utils.BX5Fg8C4.js'; | ||
| function isBuiltinEnvironment(env) { | ||
| return env in environments; | ||
| } | ||
| const isWindows = process.platform === "win32"; | ||
| const _loaders = /* @__PURE__ */ new Map(); | ||
| function createEnvironmentLoader(root, rpc) { | ||
| const cachedLoader = _loaders.get(root); | ||
| if (!cachedLoader || cachedLoader.isClosed()) { | ||
| _loaders.delete(root); | ||
| const moduleRunner = new ModuleRunner({ | ||
| hmr: false, | ||
| sourcemapInterceptor: "prepareStackTrace", | ||
| transport: new VitestTransport({ | ||
| async fetchModule(id, importer, options) { | ||
| const result = await rpc.fetch(id, importer, "__vitest__", options); | ||
| if ("cached" in result) return { | ||
| code: readFileSync(result.tmp, "utf-8"), | ||
| ...result | ||
| }; | ||
| if (isWindows && "externalize" in result) | ||
| // TODO: vitest returns paths for external modules, but Vite returns file:// | ||
| // https://github.com/vitejs/vite/pull/20449 | ||
| result.externalize = isBuiltin(id) || /^(?:node:|data:|http:|https:|file:)/.test(id) ? result.externalize : pathToFileURL(result.externalize).toString(); | ||
| return result; | ||
| }, | ||
| async resolveId(id, importer) { | ||
| return rpc.resolve(id, importer, "__vitest__"); | ||
| } | ||
| }, new EvaluatedModules(), /* @__PURE__ */ new WeakMap()) | ||
| }); | ||
| _loaders.set(root, moduleRunner); | ||
| } | ||
| return _loaders.get(root); | ||
| } | ||
| async function loadNativeEnvironment(name, root, traces) { | ||
| const packageId = name[0] === "." || name[0] === "/" ? pathToFileURL(resolve(root, name)).toString() : import.meta.resolve(`vitest-environment-${name}`, pathToFileURL(root).toString()); | ||
| return resolveEnvironmentFromModule(name, packageId, await traces.$("vitest.runtime.environment.import", () => import(packageId))); | ||
| } | ||
| function resolveEnvironmentFromModule(name, packageId, pkg) { | ||
| if (!pkg || !pkg.default || typeof pkg.default !== "object") throw new TypeError(`Environment "${name}" is not a valid environment. Path "${packageId}" should export default object with a "setup" or/and "setupVM" method.`); | ||
| const environment = pkg.default; | ||
| if (environment.transformMode != null && environment.transformMode !== "web" && environment.transformMode !== "ssr") throw new TypeError(`Environment "${name}" is not a valid environment. Path "${packageId}" should export default object with a "transformMode" method equal to "ssr" or "web", received "${environment.transformMode}".`); | ||
| if (environment.transformMode) { | ||
| console.warn(`The Vitest environment ${environment.name} defines the "transformMode". This options was deprecated in Vitest 4 and will be removed in the next major version. Please, use "viteEnvironment" instead.`); | ||
| // keep for backwards compat | ||
| environment.viteEnvironment ??= environment.transformMode === "ssr" ? "ssr" : "client"; | ||
| } | ||
| return environment; | ||
| } | ||
| async function loadEnvironment(name, root, rpc, traces, viteModuleRunner) { | ||
| if (isBuiltinEnvironment(name)) return { environment: environments[name] }; | ||
| if (!viteModuleRunner) return { environment: await loadNativeEnvironment(name, root, traces) }; | ||
| const loader = createEnvironmentLoader(root, rpc); | ||
| const packageId = name[0] === "." || name[0] === "/" ? resolve(root, name) : (await traces.$("vitest.runtime.environment.resolve", () => rpc.resolve(`vitest-environment-${name}`, void 0, "__vitest__")))?.id ?? resolve(root, name); | ||
| return { | ||
| environment: resolveEnvironmentFromModule(name, packageId, await traces.$("vitest.runtime.environment.import", () => loader.import(packageId))), | ||
| loader | ||
| }; | ||
| } | ||
| const cleanupListeners = /* @__PURE__ */ new Set(); | ||
| const moduleRunnerListeners = /* @__PURE__ */ new Set(); | ||
| function onCleanup(cb) { | ||
| cleanupListeners.add(cb); | ||
| } | ||
| async function cleanup() { | ||
| await Promise.all([...cleanupListeners].map((l) => l())); | ||
| } | ||
| function onModuleRunner(cb) { | ||
| moduleRunnerListeners.add(cb); | ||
| } | ||
| function emitModuleRunner(moduleRunner) { | ||
| moduleRunnerListeners.forEach((l) => l(moduleRunner)); | ||
| } | ||
| // Store globals in case tests overwrite them | ||
| const processListeners = process.listeners.bind(process); | ||
| const processOn = process.on.bind(process); | ||
| const processOff = process.off.bind(process); | ||
| const dispose = []; | ||
| function listenForErrors(state) { | ||
| dispose.forEach((fn) => fn()); | ||
| dispose.length = 0; | ||
| function catchError(err, type, event) { | ||
| const worker = state(); | ||
| // if there is another listener, assume that it's handled by user code | ||
| // one is Vitest's own listener | ||
| if (processListeners(event).length > 1) return; | ||
| const error = serializeValue(err); | ||
| if (typeof error === "object" && error != null) { | ||
| error.VITEST_TEST_NAME = worker.current?.type === "test" ? worker.current.name : void 0; | ||
| if (worker.filepath) error.VITEST_TEST_PATH = worker.filepath; | ||
| } | ||
| state().rpc.onUnhandledError(error, type); | ||
| } | ||
| const uncaughtException = (e) => catchError(e, "Uncaught Exception", "uncaughtException"); | ||
| const unhandledRejection = (e) => catchError(e, "Unhandled Rejection", "unhandledRejection"); | ||
| processOn("uncaughtException", uncaughtException); | ||
| processOn("unhandledRejection", unhandledRejection); | ||
| dispose.push(() => { | ||
| processOff("uncaughtException", uncaughtException); | ||
| processOff("unhandledRejection", unhandledRejection); | ||
| }); | ||
| } | ||
| const resolvingModules = /* @__PURE__ */ new Set(); | ||
| async function execute(method, ctx, worker, traces) { | ||
| const prepareStart = performance.now(); | ||
| const cleanups = [setupInspect(ctx)]; | ||
| // RPC is used to communicate between worker (be it a thread worker or child process or a custom implementation) and the main thread | ||
| const rpc = ctx.rpc; | ||
| try { | ||
| // do not close the RPC channel so that we can get the error messages sent to the main thread | ||
| cleanups.push(async () => { | ||
| await Promise.all(rpc.$rejectPendingCalls(({ method, reject }) => { | ||
| reject(new EnvironmentTeardownError(`[vitest-worker]: Closing rpc while "${method}" was pending`)); | ||
| })); | ||
| }); | ||
| const state = { | ||
| ctx, | ||
| evaluatedModules: new VitestEvaluatedModules(), | ||
| resolvingModules, | ||
| moduleExecutionInfo: /* @__PURE__ */ new Map(), | ||
| config: ctx.config, | ||
| environment: null, | ||
| durations: { | ||
| environment: 0, | ||
| prepare: prepareStart | ||
| }, | ||
| rpc, | ||
| onCancel, | ||
| onCleanup: onCleanup, | ||
| providedContext: ctx.providedContext, | ||
| onFilterStackTrace(stack) { | ||
| return createStackString(parseStacktrace(stack)); | ||
| }, | ||
| metaEnv: createImportMetaEnvProxy() | ||
| }; | ||
| const methodName = method === "collect" ? "collectTests" : "runTests"; | ||
| if (!worker[methodName] || typeof worker[methodName] !== "function") throw new TypeError(`Test worker should expose "runTests" method. Received "${typeof worker.runTests}".`); | ||
| await worker[methodName](state, traces); | ||
| } finally { | ||
| await rpcDone().catch(() => {}); | ||
| await Promise.all(cleanups.map((fn) => fn())).catch(() => {}); | ||
| } | ||
| } | ||
| function run(ctx, worker, traces) { | ||
| return execute("run", ctx, worker, traces); | ||
| } | ||
| function collect(ctx, worker, traces) { | ||
| return execute("collect", ctx, worker, traces); | ||
| } | ||
| async function teardown() { | ||
| await cleanup(); | ||
| } | ||
| const env = process.env; | ||
| function createImportMetaEnvProxy() { | ||
| // packages/vitest/src/node/plugins/index.ts:146 | ||
| const booleanKeys = [ | ||
| "DEV", | ||
| "PROD", | ||
| "SSR" | ||
| ]; | ||
| return new Proxy(env, { | ||
| get(_, key) { | ||
| if (typeof key !== "string") return; | ||
| if (booleanKeys.includes(key)) return !!process.env[key]; | ||
| return process.env[key]; | ||
| }, | ||
| set(_, key, value) { | ||
| if (typeof key !== "string") return true; | ||
| if (booleanKeys.includes(key)) process.env[key] = value ? "1" : ""; | ||
| else process.env[key] = value; | ||
| return true; | ||
| } | ||
| }); | ||
| } | ||
| const __vitest_worker_response__ = true; | ||
| const memoryUsage = process.memoryUsage.bind(process); | ||
| let reportMemory = false; | ||
| let traces; | ||
| /** @experimental */ | ||
| function init(worker) { | ||
| worker.on(onMessage); | ||
| if (worker.onModuleRunner) onModuleRunner(worker.onModuleRunner); | ||
| let runPromise; | ||
| let isRunning = false; | ||
| let workerTeardown; | ||
| let setupContext; | ||
| function send(response) { | ||
| worker.post(worker.serialize ? worker.serialize(response) : response); | ||
| } | ||
| async function onMessage(rawMessage) { | ||
| const message = worker.deserialize ? worker.deserialize(rawMessage) : rawMessage; | ||
| if (message?.__vitest_worker_request__ !== true) return; | ||
| switch (message.type) { | ||
| case "start": { | ||
| process.env.VITEST_POOL_ID = String(message.poolId); | ||
| process.env.VITEST_WORKER_ID = String(message.workerId); | ||
| reportMemory = message.options.reportMemory; | ||
| if (message.context.config.isAgent) disableDefaultColors(); | ||
| traces ??= await new Traces({ | ||
| enabled: message.traces.enabled, | ||
| sdkPath: message.traces.sdkPath | ||
| }).waitInit(); | ||
| const { environment, config, pool } = message.context; | ||
| const context = traces.getContextFromCarrier(message.traces.otelCarrier); | ||
| // record telemetry as part of "start" | ||
| traces.recordInitSpan(context); | ||
| try { | ||
| setupContext = { | ||
| environment, | ||
| config, | ||
| pool, | ||
| rpc: createRuntimeRpc(worker), | ||
| projectName: config.name || "", | ||
| traces | ||
| }; | ||
| workerTeardown = await traces.$("vitest.runtime.setup", { context }, () => worker.setup?.(setupContext)); | ||
| send({ | ||
| type: "started", | ||
| __vitest_worker_response__ | ||
| }); | ||
| } catch (error) { | ||
| send({ | ||
| type: "started", | ||
| __vitest_worker_response__, | ||
| error: serializeError(error) | ||
| }); | ||
| } | ||
| break; | ||
| } | ||
| case "run": | ||
| // Prevent concurrent execution if worker is already running | ||
| if (isRunning) { | ||
| send({ | ||
| type: "testfileFinished", | ||
| __vitest_worker_response__, | ||
| error: serializeError(/* @__PURE__ */ new Error("[vitest-worker]: Worker is already running tests")) | ||
| }); | ||
| return; | ||
| } | ||
| try { | ||
| process.env.VITEST_WORKER_ID = String(message.context.workerId); | ||
| } catch (error) { | ||
| return send({ | ||
| type: "testfileFinished", | ||
| __vitest_worker_response__, | ||
| error: serializeError(error), | ||
| usedMemory: reportMemory ? memoryUsage().heapUsed : void 0 | ||
| }); | ||
| } | ||
| isRunning = true; | ||
| try { | ||
| const tracesContext = traces.getContextFromCarrier(message.otelCarrier); | ||
| runPromise = traces.$("vitest.runtime.run", { | ||
| context: tracesContext, | ||
| attributes: { | ||
| "vitest.worker.specifications": traces.isEnabled() ? getFilesWithLocations(message.context.files) : [], | ||
| "vitest.worker.id": message.context.workerId | ||
| } | ||
| }, () => run({ | ||
| ...setupContext, | ||
| ...message.context | ||
| }, worker, traces).catch((error) => serializeError(error))); | ||
| send({ | ||
| type: "testfileFinished", | ||
| __vitest_worker_response__, | ||
| error: await runPromise, | ||
| usedMemory: reportMemory ? memoryUsage().heapUsed : void 0 | ||
| }); | ||
| } finally { | ||
| runPromise = void 0; | ||
| isRunning = false; | ||
| } | ||
| break; | ||
| case "collect": | ||
| // Prevent concurrent execution if worker is already running | ||
| if (isRunning) { | ||
| send({ | ||
| type: "testfileFinished", | ||
| __vitest_worker_response__, | ||
| error: serializeError(/* @__PURE__ */ new Error("[vitest-worker]: Worker is already running tests")) | ||
| }); | ||
| return; | ||
| } | ||
| try { | ||
| process.env.VITEST_WORKER_ID = String(message.context.workerId); | ||
| } catch (error) { | ||
| return send({ | ||
| type: "testfileFinished", | ||
| __vitest_worker_response__, | ||
| error: serializeError(error), | ||
| usedMemory: reportMemory ? memoryUsage().heapUsed : void 0 | ||
| }); | ||
| } | ||
| isRunning = true; | ||
| try { | ||
| const tracesContext = traces.getContextFromCarrier(message.otelCarrier); | ||
| runPromise = traces.$("vitest.runtime.collect", { | ||
| context: tracesContext, | ||
| attributes: { | ||
| "vitest.worker.specifications": traces.isEnabled() ? getFilesWithLocations(message.context.files) : [], | ||
| "vitest.worker.id": message.context.workerId | ||
| } | ||
| }, () => collect({ | ||
| ...setupContext, | ||
| ...message.context | ||
| }, worker, traces).catch((error) => serializeError(error))); | ||
| send({ | ||
| type: "testfileFinished", | ||
| __vitest_worker_response__, | ||
| error: await runPromise, | ||
| usedMemory: reportMemory ? memoryUsage().heapUsed : void 0 | ||
| }); | ||
| } finally { | ||
| runPromise = void 0; | ||
| isRunning = false; | ||
| } | ||
| break; | ||
| case "stop": | ||
| await runPromise; | ||
| try { | ||
| const context = traces.getContextFromCarrier(message.otelCarrier); | ||
| const error = await traces.$("vitest.runtime.teardown", { context }, async () => { | ||
| const error = await teardown().catch((error) => serializeError(error)); | ||
| await workerTeardown?.(); | ||
| return error; | ||
| }); | ||
| await traces.finish(); | ||
| send({ | ||
| type: "stopped", | ||
| error, | ||
| __vitest_worker_response__ | ||
| }); | ||
| } catch (error) { | ||
| send({ | ||
| type: "stopped", | ||
| error: serializeError(error), | ||
| __vitest_worker_response__ | ||
| }); | ||
| } | ||
| worker.teardown?.(); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| function getFilesWithLocations(files) { | ||
| return files.flatMap((file) => { | ||
| if (!file.testLocations) return file.filepath; | ||
| return file.testLocations.map((location) => { | ||
| return `${file}:${location}`; | ||
| }); | ||
| }); | ||
| } | ||
| export { listenForErrors as a, emitModuleRunner as e, init as i, loadEnvironment as l }; |
| import module$1, { isBuiltin } from 'node:module'; | ||
| import { fileURLToPath, pathToFileURL } from 'node:url'; | ||
| import { automockModule, createManualModuleSource, collectModuleExports } from '@vitest/mocker/transforms'; | ||
| import { cleanUrl, createDefer } from '@vitest/utils/helpers'; | ||
| import { p as parse } from './acorn.B2iPLyUM.js'; | ||
| import { isAbsolute } from 'pathe'; | ||
| import { t as toBuiltin } from './modules.BJuCwlRJ.js'; | ||
| import { B as BareModuleMocker, n as normalizeModuleId } from './startVitestModuleRunner.CCBQZ9DQ.js'; | ||
| import 'node:fs'; | ||
| import './utils.BX5Fg8C4.js'; | ||
| import '@vitest/utils/timers'; | ||
| import '../path.js'; | ||
| import 'node:path'; | ||
| import '../module-evaluator.js'; | ||
| import 'node:vm'; | ||
| import 'vite/module-runner'; | ||
| import './traces.DT5aQ62U.js'; | ||
| import '@vitest/mocker'; | ||
| import '@vitest/mocker/redirect'; | ||
| class NativeModuleMocker extends BareModuleMocker { | ||
| wrapDynamicImport(moduleFactory) { | ||
| if (typeof moduleFactory === "function") return new Promise((resolve, reject) => { | ||
| this.resolveMocks().finally(() => { | ||
| moduleFactory().then(resolve, reject); | ||
| }); | ||
| }); | ||
| return moduleFactory; | ||
| } | ||
| resolveMockedModule(url, parentURL) { | ||
| // don't mock modules inside of packages because there is | ||
| // a high chance that it uses `require` which is not mockable | ||
| // because we use top-level await in "manual" mocks. | ||
| // for the sake of consistency we don't support mocking anything at all | ||
| if (parentURL.includes("/node_modules/")) return; | ||
| const moduleId = normalizeModuleId(url.startsWith("file://") ? fileURLToPath(url) : url); | ||
| const mockedModule = this.getDependencyMock(moduleId); | ||
| if (!mockedModule) return; | ||
| if (mockedModule.type === "redirect") return { | ||
| url: pathToFileURL(mockedModule.redirect).toString(), | ||
| shortCircuit: true | ||
| }; | ||
| if (mockedModule.type === "automock" || mockedModule.type === "autospy") return { | ||
| url: injectQuery(url, parentURL, `mock=${mockedModule.type}`), | ||
| shortCircuit: true | ||
| }; | ||
| if (mockedModule.type === "manual") return { | ||
| url: injectQuery(url, parentURL, "mock=manual"), | ||
| shortCircuit: true | ||
| }; | ||
| } | ||
| loadAutomock(url, result) { | ||
| const moduleId = cleanUrl(normalizeModuleId(url.startsWith("file://") ? fileURLToPath(url) : url)); | ||
| let source; | ||
| if (isBuiltin(moduleId)) { | ||
| const builtinModule = getBuiltinModule(moduleId); | ||
| const exports$1 = Object.keys(builtinModule); | ||
| source = ` | ||
| import * as builtinModule from '${toBuiltin(moduleId)}?mock=actual' | ||
| ${exports$1.map((key, index) => { | ||
| return ` | ||
| const __${index} = builtinModule["${key}"] | ||
| export { __${index} as "${key}" } | ||
| `; | ||
| }).join("")}`; | ||
| } else source = result.source?.toString(); | ||
| if (source == null) return; | ||
| const mockType = url.includes("mock=automock") ? "automock" : "autospy"; | ||
| const transformedCode = transformCode(source, result.format || "module", moduleId); | ||
| try { | ||
| const ms = automockModule(transformedCode, mockType, (code) => parse(code, { | ||
| sourceType: "module", | ||
| ecmaVersion: "latest" | ||
| }), { id: moduleId }); | ||
| return { | ||
| format: "module", | ||
| source: `${ms.toString()}\n//# sourceMappingURL=${genSourceMapUrl(ms.generateMap({ | ||
| hires: "boundary", | ||
| source: moduleId | ||
| }))}`, | ||
| shortCircuit: true | ||
| }; | ||
| } catch (cause) { | ||
| throw new Error(`Cannot automock '${url}' because it failed to parse.`, { cause }); | ||
| } | ||
| } | ||
| loadManualMock(url, result) { | ||
| const moduleId = cleanUrl(normalizeModuleId(url.startsWith("file://") ? fileURLToPath(url) : url)); | ||
| // should not be possible | ||
| if (this.getDependencyMock(moduleId)?.type !== "manual") { | ||
| console.warn(`Vitest detected unregistered manual mock ${moduleId}. This is a bug in Vitest. Please, open a new issue with reproduction.`); | ||
| return; | ||
| } | ||
| if (isBuiltin(moduleId)) { | ||
| const builtinModule = getBuiltinModule(toBuiltin(moduleId)); | ||
| return { | ||
| format: "module", | ||
| source: createManualModuleSource(moduleId, Object.keys(builtinModule)), | ||
| shortCircuit: true | ||
| }; | ||
| } | ||
| if (!result.source) return; | ||
| const transformedCode = transformCode(result.source.toString(), result.format || "module", moduleId); | ||
| if (transformedCode == null) return; | ||
| const format = result.format?.startsWith("module") ? "module" : "commonjs"; | ||
| try { | ||
| return { | ||
| format: "module", | ||
| source: createManualModuleSource(moduleId, collectModuleExports(moduleId, transformedCode, format)), | ||
| shortCircuit: true | ||
| }; | ||
| } catch (cause) { | ||
| throw new Error(`Failed to mock '${url}'. See the cause for more information.`, { cause }); | ||
| } | ||
| } | ||
| processedModules = /* @__PURE__ */ new Map(); | ||
| checkCircularManualMock(url) { | ||
| const id = cleanUrl(normalizeModuleId(url.startsWith("file://") ? fileURLToPath(url) : url)); | ||
| this.processedModules.set(id, (this.processedModules.get(id) ?? 0) + 1); | ||
| // the module is mocked and requested a second time, let's resolve | ||
| // the factory function that will redefine the exports later | ||
| if (this.originalModulePromises.has(id)) { | ||
| const factoryPromise = this.factoryPromises.get(id); | ||
| this.originalModulePromises.get(id)?.resolve({ __factoryPromise: factoryPromise }); | ||
| } | ||
| } | ||
| originalModulePromises = /* @__PURE__ */ new Map(); | ||
| factoryPromises = /* @__PURE__ */ new Map(); | ||
| // potential performance improvement: | ||
| // store by URL, not ids, no need to call url.*to* methods and normalizeModuleId | ||
| getFactoryModule(id) { | ||
| const mock = this.getMockerRegistry().getById(id); | ||
| if (!mock || mock.type !== "manual") throw new Error(`Mock ${id} wasn't registered. This is probably a Vitest error. Please, open a new issue with reproduction.`); | ||
| const mockResult = mock.resolve(); | ||
| if (mockResult instanceof Promise) { | ||
| // to avoid circular dependency, we resolve this function as {__factoryPromise} in `checkCircularManualMock` | ||
| // when it's requested the second time. then the exports are exposed as `undefined`, | ||
| // but later redefined when the promise is actually resolved | ||
| const promise = createDefer(); | ||
| promise.finally(() => { | ||
| this.originalModulePromises.delete(id); | ||
| }); | ||
| mockResult.then(promise.resolve, promise.reject).finally(() => { | ||
| this.factoryPromises.delete(id); | ||
| }); | ||
| this.factoryPromises.set(id, mockResult); | ||
| this.originalModulePromises.set(id, promise); | ||
| // Node.js on windows processes all the files first, and then runs them | ||
| // unlike Node.js logic on Mac and Unix where it also runs the code while evaluating | ||
| // So on Linux/Mac this `if` won't be hit because `checkCircularManualMock` will resolve it | ||
| // And on Windows, the `checkCircularManualMock` will never have `originalModulePromises` | ||
| // because `getFactoryModule` is not called until the evaluation phase | ||
| // But if we track how many times the module was transformed, | ||
| // we can deduce when to return `__factoryPromise` to support circular modules | ||
| if ((this.processedModules.get(id) ?? 0) > 1) { | ||
| this.processedModules.set(id, (this.processedModules.get(id) ?? 1) - 1); | ||
| promise.resolve({ __factoryPromise: mockResult }); | ||
| } | ||
| return promise; | ||
| } | ||
| return mockResult; | ||
| } | ||
| importActual(rawId, importer) { | ||
| const resolvedId = import.meta.resolve(rawId, pathToFileURL(importer).toString()); | ||
| const url = new URL(resolvedId); | ||
| url.searchParams.set("mock", "actual"); | ||
| return import(url.toString()); | ||
| } | ||
| importMock(rawId, importer) { | ||
| const resolvedId = import.meta.resolve(rawId, pathToFileURL(importer).toString()); | ||
| // file is already mocked | ||
| if (resolvedId.includes("mock=")) return import(resolvedId); | ||
| const filename = fileURLToPath(resolvedId); | ||
| const external = !isAbsolute(filename) || this.isModuleDirectory(resolvedId) ? normalizeModuleId(rawId) : null; | ||
| // file is not mocked, automock or redirect it | ||
| const redirect = this.findMockRedirect(filename, external); | ||
| if (redirect) return import(pathToFileURL(redirect).toString()); | ||
| const url = new URL(resolvedId); | ||
| url.searchParams.set("mock", "automock"); | ||
| return import(url.toString()); | ||
| } | ||
| } | ||
| const replacePercentageRE = /%/g; | ||
| function injectQuery(url, importer, queryToInject) { | ||
| const { search, hash } = new URL(url.replace(replacePercentageRE, "%25"), importer); | ||
| return `${cleanUrl(url)}?${queryToInject}${search ? `&${search.slice(1)}` : ""}${hash ?? ""}`; | ||
| } | ||
| let __require; | ||
| function getBuiltinModule(moduleId) { | ||
| __require ??= module$1.createRequire(import.meta.url); | ||
| return __require(`${moduleId}?mock=actual`); | ||
| } | ||
| function genSourceMapUrl(map) { | ||
| if (typeof map !== "string") map = JSON.stringify(map); | ||
| return `data:application/json;base64,${Buffer.from(map).toString("base64")}`; | ||
| } | ||
| function transformCode(code, format, filename) { | ||
| if (format.includes("typescript")) { | ||
| if (!module$1.stripTypeScriptTypes) throw new Error(`Cannot parse '${filename}' because "module.stripTypeScriptTypes" is not supported. Module mocking requires Node.js 22.15 or higher. This is NOT a bug of Vitest.`); | ||
| return module$1.stripTypeScriptTypes(code); | ||
| } | ||
| return code; | ||
| } | ||
| export { NativeModuleMocker }; |
| import fs from 'node:fs'; | ||
| import { isBareImport } from '@vitest/utils/helpers'; | ||
| import { i as isBuiltin, a as isBrowserExternal, t as toBuiltin } from './modules.BJuCwlRJ.js'; | ||
| import { E as EnvironmentTeardownError, a as getSafeWorkerState } from './utils.BX5Fg8C4.js'; | ||
| import { pathToFileURL } from 'node:url'; | ||
| import { normalize, join } from 'pathe'; | ||
| import { distDir } from '../path.js'; | ||
| import { VitestModuleEvaluator, unwrapId } from '../module-evaluator.js'; | ||
| import { isAbsolute, resolve } from 'node:path'; | ||
| import vm from 'node:vm'; | ||
| import { MockerRegistry, mockObject, RedirectedModule, AutomockedModule } from '@vitest/mocker'; | ||
| import { findMockRedirect } from '@vitest/mocker/redirect'; | ||
| import * as viteModuleRunner from 'vite/module-runner'; | ||
| import { T as Traces } from './traces.DT5aQ62U.js'; | ||
| class BareModuleMocker { | ||
| static pendingIds = []; | ||
| spyModule; | ||
| primitives; | ||
| registries = /* @__PURE__ */ new Map(); | ||
| mockContext = { callstack: null }; | ||
| _otel; | ||
| constructor(options) { | ||
| this.options = options; | ||
| this._otel = options.traces; | ||
| this.primitives = { | ||
| Object, | ||
| Error, | ||
| Function, | ||
| RegExp, | ||
| Symbol: globalThis.Symbol, | ||
| Array, | ||
| Map | ||
| }; | ||
| if (options.spyModule) this.spyModule = options.spyModule; | ||
| } | ||
| get root() { | ||
| return this.options.root; | ||
| } | ||
| get moduleDirectories() { | ||
| return this.options.moduleDirectories || []; | ||
| } | ||
| getMockerRegistry() { | ||
| const suite = this.getSuiteFilepath(); | ||
| if (!this.registries.has(suite)) this.registries.set(suite, new MockerRegistry()); | ||
| return this.registries.get(suite); | ||
| } | ||
| reset() { | ||
| this.registries.clear(); | ||
| } | ||
| invalidateModuleById(_id) { | ||
| // implemented by mockers that control the module runner | ||
| } | ||
| isModuleDirectory(path) { | ||
| return this.moduleDirectories.some((dir) => path.includes(dir)); | ||
| } | ||
| getSuiteFilepath() { | ||
| return this.options.getCurrentTestFilepath() || "global"; | ||
| } | ||
| createError(message, codeFrame) { | ||
| const Error = this.primitives.Error; | ||
| const error = new Error(message); | ||
| Object.assign(error, { codeFrame }); | ||
| return error; | ||
| } | ||
| async resolveId(rawId, importer) { | ||
| return this._otel.$("vitest.mocker.resolve_id", { attributes: { | ||
| "vitest.module.raw_id": rawId, | ||
| "vitest.module.importer": rawId | ||
| } }, async (span) => { | ||
| const result = await this.options.resolveId(rawId, importer); | ||
| if (!result) { | ||
| span.addEvent("could not resolve id, fallback to unresolved values"); | ||
| const id = normalizeModuleId(rawId); | ||
| span.setAttributes({ | ||
| "vitest.module.id": id, | ||
| "vitest.module.url": rawId, | ||
| "vitest.module.external": id, | ||
| "vitest.module.fallback": true | ||
| }); | ||
| return { | ||
| id, | ||
| url: rawId, | ||
| external: id | ||
| }; | ||
| } | ||
| // external is node_module or unresolved module | ||
| // for example, some people mock "vscode" and don't have it installed | ||
| const external = !isAbsolute(result.file) || this.isModuleDirectory(result.file) ? normalizeModuleId(rawId) : null; | ||
| const id = normalizeModuleId(result.id); | ||
| span.setAttributes({ | ||
| "vitest.module.id": id, | ||
| "vitest.module.url": result.url, | ||
| "vitest.module.external": external ?? false | ||
| }); | ||
| return { | ||
| ...result, | ||
| id, | ||
| external | ||
| }; | ||
| }); | ||
| } | ||
| async resolveMocks() { | ||
| if (!BareModuleMocker.pendingIds.length) return; | ||
| const resolveMock = async (mock) => { | ||
| const { id, url, external } = await this.resolveId(mock.id, mock.importer); | ||
| if (mock.action === "unmock") this.unmockPath(id); | ||
| if (mock.action === "mock") this.mockPath(mock.id, id, url, external, mock.type, mock.factory); | ||
| }; | ||
| // group consecutive mocks of the same action type together, | ||
| // resolve in parallel inside each group, but run groups sequentially | ||
| // to preserve mock/unmock ordering | ||
| const groups = groupByConsecutiveAction(BareModuleMocker.pendingIds); | ||
| for (const group of groups) await Promise.all(group.map(resolveMock)); | ||
| BareModuleMocker.pendingIds = []; | ||
| } | ||
| // public method to avoid circular dependency | ||
| getMockContext() { | ||
| return this.mockContext; | ||
| } | ||
| // path used to store mocked dependencies | ||
| getMockPath(dep) { | ||
| return `mock:${dep}`; | ||
| } | ||
| getDependencyMock(id) { | ||
| return this.getMockerRegistry().getById(fixLeadingSlashes(id)); | ||
| } | ||
| getDependencyMockByUrl(url) { | ||
| return this.getMockerRegistry().get(url); | ||
| } | ||
| findMockRedirect(mockPath, external) { | ||
| return findMockRedirect(this.root, mockPath, external); | ||
| } | ||
| mockObject(object, mockExportsOrModuleType, moduleType) { | ||
| let mockExports; | ||
| if (mockExportsOrModuleType === "automock" || mockExportsOrModuleType === "autospy") { | ||
| moduleType = mockExportsOrModuleType; | ||
| mockExports = void 0; | ||
| } else mockExports = mockExportsOrModuleType; | ||
| moduleType ??= "automock"; | ||
| const createMockInstance = this.spyModule?.createMockInstance; | ||
| if (!createMockInstance) throw this.createError("[vitest] `spyModule` is not defined. This is a Vitest error. Please open a new issue with reproduction."); | ||
| return mockObject({ | ||
| globalConstructors: this.primitives, | ||
| createMockInstance, | ||
| type: moduleType | ||
| }, object, mockExports); | ||
| } | ||
| unmockPath(id) { | ||
| this.getMockerRegistry().deleteById(id); | ||
| this.invalidateModuleById(id); | ||
| } | ||
| mockPath(originalId, id, url, external, mockType, factory) { | ||
| const registry = this.getMockerRegistry(); | ||
| if (mockType === "manual") registry.register("manual", originalId, id, url, factory); | ||
| else if (mockType === "autospy") registry.register("autospy", originalId, id, url); | ||
| else { | ||
| const redirect = this.findMockRedirect(id, external); | ||
| if (redirect) registry.register("redirect", originalId, id, url, redirect); | ||
| else registry.register("automock", originalId, id, url); | ||
| } | ||
| // every time the mock is registered, we remove the previous one from the cache | ||
| this.invalidateModuleById(id); | ||
| } | ||
| async importActual(_rawId, _importer, _callstack) { | ||
| throw new Error(`importActual is not implemented`); | ||
| } | ||
| async importMock(_rawId, _importer, _callstack) { | ||
| throw new Error(`importMock is not implemented`); | ||
| } | ||
| queueMock(id, importer, factoryOrOptions) { | ||
| const mockType = getMockType(factoryOrOptions); | ||
| BareModuleMocker.pendingIds.push({ | ||
| action: "mock", | ||
| id, | ||
| importer, | ||
| factory: typeof factoryOrOptions === "function" ? factoryOrOptions : void 0, | ||
| type: mockType | ||
| }); | ||
| } | ||
| queueUnmock(id, importer) { | ||
| BareModuleMocker.pendingIds.push({ | ||
| action: "unmock", | ||
| id, | ||
| importer | ||
| }); | ||
| } | ||
| } | ||
| function getMockType(factoryOrOptions) { | ||
| if (!factoryOrOptions) return "automock"; | ||
| if (typeof factoryOrOptions === "function") return "manual"; | ||
| return factoryOrOptions.spy ? "autospy" : "automock"; | ||
| } | ||
| // unique id that is not available as "$bare_import" like "test" | ||
| // https://nodejs.org/api/modules.html#built-in-modules-with-mandatory-node-prefix | ||
| const prefixedBuiltins = new Set([ | ||
| "node:sea", | ||
| "node:sqlite", | ||
| "node:test", | ||
| "node:test/reporters" | ||
| ]); | ||
| const isWindows$1 = process.platform === "win32"; | ||
| // transform file url to id | ||
| // virtual:custom -> virtual:custom | ||
| // \0custom -> \0custom | ||
| // /root/id -> /id | ||
| // /root/id.js -> /id.js | ||
| // C:/root/id.js -> /id.js | ||
| // C:\root\id.js -> /id.js | ||
| // TODO: expose this in vite/module-runner | ||
| function normalizeModuleId(file) { | ||
| if (prefixedBuiltins.has(file)) return file; | ||
| // if it's not in the root, keep it as a path, not a URL | ||
| return slash(file).replace(/^\/@fs\//, isWindows$1 ? "" : "/").replace(/^node:/, "").replace(/^\/+/, "/").replace(/^file:\//, "/"); | ||
| } | ||
| const windowsSlashRE = /\\/g; | ||
| function slash(p) { | ||
| return p.replace(windowsSlashRE, "/"); | ||
| } | ||
| function groupByConsecutiveAction(mocks) { | ||
| const groups = []; | ||
| for (const mock of mocks) { | ||
| const last = groups.at(-1); | ||
| if (last?.[0].action === mock.action) last.push(mock); | ||
| else groups.push([mock]); | ||
| } | ||
| return groups; | ||
| } | ||
| const multipleSlashRe = /^\/+/; | ||
| // module-runner incorrectly replaces file:///path with `///path` | ||
| function fixLeadingSlashes(id) { | ||
| if (id.startsWith("//")) return id.replace(multipleSlashRe, "/"); | ||
| return id; | ||
| } | ||
| // copied from vite | ||
| // https://github.com/vitejs/vite/blob/4417b4f305623b2850bd6ae6553834c017694672/packages/vite/src/shared/utils.ts | ||
| // https://github.com/vitejs/vite/blob/4417b4f305623b2850bd6ae6553834c017694672/packages/vite/src/node/utils.ts | ||
| const postfixRE = /[?#].*$/; | ||
| const trailingSeparatorRE = /[?&]$/; | ||
| function cleanUrl(url) { | ||
| return url.replace(postfixRE, ""); | ||
| } | ||
| function splitFileAndPostfix(path) { | ||
| const file = cleanUrl(path); | ||
| return { | ||
| file, | ||
| postfix: path.slice(file.length) | ||
| }; | ||
| } | ||
| function injectQuery(url, queryToInject) { | ||
| const { file, postfix } = splitFileAndPostfix(url); | ||
| return `${file}?${queryToInject}${postfix[0] === "?" ? `&${postfix.slice(1)}` : postfix}`; | ||
| } | ||
| function removeQuery(url, queryToRemove) { | ||
| return url.replace(new RegExp(`([?&])${queryToRemove}(?:&|$)`), "$1").replace(trailingSeparatorRE, ""); | ||
| } | ||
| const spyModulePath = resolve(distDir, "spy.js"); | ||
| class VitestMocker extends BareModuleMocker { | ||
| filterPublicKeys; | ||
| constructor(moduleRunner, options) { | ||
| super(options); | ||
| this.moduleRunner = moduleRunner; | ||
| this.options = options; | ||
| const context = this.options.context; | ||
| if (context) this.primitives = vm.runInContext("({ Object, Error, Function, RegExp, Symbol, Array, Map })", context); | ||
| const Symbol = this.primitives.Symbol; | ||
| this.filterPublicKeys = [ | ||
| "__esModule", | ||
| Symbol.asyncIterator, | ||
| Symbol.hasInstance, | ||
| Symbol.isConcatSpreadable, | ||
| Symbol.iterator, | ||
| Symbol.match, | ||
| Symbol.matchAll, | ||
| Symbol.replace, | ||
| Symbol.search, | ||
| Symbol.split, | ||
| Symbol.species, | ||
| Symbol.toPrimitive, | ||
| Symbol.toStringTag, | ||
| Symbol.unscopables | ||
| ]; | ||
| } | ||
| get evaluatedModules() { | ||
| return this.moduleRunner.evaluatedModules; | ||
| } | ||
| async initializeSpyModule() { | ||
| if (this.spyModule) return; | ||
| this.spyModule = await this.moduleRunner.import(spyModulePath); | ||
| } | ||
| reset() { | ||
| this.registries.clear(); | ||
| } | ||
| invalidateModuleById(id) { | ||
| const mockId = this.getMockPath(id); | ||
| const node = this.evaluatedModules.getModuleById(mockId); | ||
| if (node) { | ||
| this.evaluatedModules.invalidateModule(node); | ||
| node.mockedExports = void 0; | ||
| } | ||
| } | ||
| ensureModule(id, url) { | ||
| const node = this.evaluatedModules.ensureModule(id, url); | ||
| // TODO | ||
| node.meta = { | ||
| id, | ||
| url, | ||
| code: "", | ||
| file: null, | ||
| invalidate: false | ||
| }; | ||
| return node; | ||
| } | ||
| async callFunctionMock(id, url, mock) { | ||
| const node = this.ensureModule(id, url); | ||
| if (node.exports) return node.exports; | ||
| const exports$1 = await mock.resolve(); | ||
| const moduleExports = new Proxy(exports$1, { get: (target, prop) => { | ||
| const val = target[prop]; | ||
| // 'then' can exist on non-Promise objects, need nested instanceof check for logic to work | ||
| if (prop === "then") { | ||
| if (target instanceof Promise) return target.then.bind(target); | ||
| } else if (!(prop in target)) { | ||
| if (this.filterPublicKeys.includes(prop)) return; | ||
| throw this.createError(`[vitest] No "${String(prop)}" export is defined on the "${mock.raw}" mock. Did you forget to return it from "vi.mock"? | ||
| If you need to partially mock a module, you can use "importOriginal" helper inside: | ||
| `, `vi.mock(import("${mock.raw}"), async (importOriginal) => { | ||
| const actual = await importOriginal() | ||
| return { | ||
| ...actual, | ||
| // your mocked methods | ||
| } | ||
| })`); | ||
| } | ||
| return val; | ||
| } }); | ||
| node.exports = moduleExports; | ||
| return moduleExports; | ||
| } | ||
| async importActual(rawId, importer, callstack) { | ||
| const { url } = await this.resolveId(rawId, importer); | ||
| const actualUrl = injectQuery(url, "_vitest_original"); | ||
| const node = await this.moduleRunner.fetchModule(actualUrl, importer); | ||
| return await this.moduleRunner.cachedRequest(node.url, node, callstack || [importer], void 0, true); | ||
| } | ||
| async importMock(rawId, importer) { | ||
| const { id, url, external } = await this.resolveId(rawId, importer); | ||
| let mock = this.getDependencyMock(id); | ||
| if (!mock) { | ||
| const redirect = this.findMockRedirect(id, external); | ||
| if (redirect) mock = new RedirectedModule(rawId, id, rawId, redirect); | ||
| else mock = new AutomockedModule(rawId, id, rawId); | ||
| } | ||
| if (mock.type === "automock" || mock.type === "autospy") { | ||
| const node = await this.moduleRunner.fetchModule(url, importer); | ||
| const mod = await this.moduleRunner.cachedRequest(url, node, [importer], void 0, true); | ||
| const Object = this.primitives.Object; | ||
| return this.mockObject(mod, Object.create(Object.prototype), mock.type); | ||
| } | ||
| if (mock.type === "manual") return this.callFunctionMock(id, url, mock); | ||
| const node = await this.moduleRunner.fetchModule(mock.redirect); | ||
| return this.moduleRunner.cachedRequest(mock.redirect, node, [importer], void 0, true); | ||
| } | ||
| async requestWithMockedModule(url, evaluatedNode, callstack, mock) { | ||
| return this._otel.$("vitest.mocker.evaluate", async (span) => { | ||
| const mockId = this.getMockPath(evaluatedNode.id); | ||
| span.setAttributes({ | ||
| "vitest.module.id": mockId, | ||
| "vitest.mock.type": mock.type, | ||
| "vitest.mock.id": mock.id, | ||
| "vitest.mock.url": mock.url, | ||
| "vitest.mock.raw": mock.raw | ||
| }); | ||
| if (mock.type === "automock" || mock.type === "autospy") { | ||
| const cache = this.evaluatedModules.getModuleById(mockId); | ||
| if (cache && cache.mockedExports) return cache.mockedExports; | ||
| const Object = this.primitives.Object; | ||
| // we have to define a separate object that will copy all properties into itself | ||
| // and can't just use the same `exports` define automatically by Vite before the evaluator | ||
| const exports$1 = Object.create(null); | ||
| Object.defineProperty(exports$1, Symbol.toStringTag, { | ||
| value: "Module", | ||
| configurable: true, | ||
| writable: true | ||
| }); | ||
| const node = this.ensureModule(mockId, this.getMockPath(evaluatedNode.url)); | ||
| node.meta = evaluatedNode.meta; | ||
| node.file = evaluatedNode.file; | ||
| node.mockedExports = exports$1; | ||
| const mod = await this.moduleRunner.cachedRequest(url, node, callstack, void 0, true); | ||
| this.mockObject(mod, exports$1, mock.type); | ||
| return exports$1; | ||
| } | ||
| if (mock.type === "manual" && !callstack.includes(mockId) && !callstack.includes(url)) try { | ||
| callstack.push(mockId); | ||
| // this will not work if user does Promise.all(import(), import()) | ||
| // we can also use AsyncLocalStorage to store callstack, but this won't work in the browser | ||
| // maybe we should improve mock API in the future? | ||
| this.mockContext.callstack = callstack; | ||
| return await this.callFunctionMock(mockId, this.getMockPath(url), mock); | ||
| } finally { | ||
| this.mockContext.callstack = null; | ||
| const indexMock = callstack.indexOf(mockId); | ||
| callstack.splice(indexMock, 1); | ||
| } | ||
| else if (mock.type === "redirect" && !callstack.includes(mock.redirect)) { | ||
| span.setAttribute("vitest.mock.redirect", mock.redirect); | ||
| return mock.redirect; | ||
| } | ||
| }); | ||
| } | ||
| async mockedRequest(url, evaluatedNode, callstack) { | ||
| const mock = this.getDependencyMock(evaluatedNode.id); | ||
| if (!mock) return; | ||
| return this.requestWithMockedModule(url, evaluatedNode, callstack, mock); | ||
| } | ||
| } | ||
| class VitestTransport { | ||
| constructor(options, evaluatedModules, callstacks) { | ||
| this.options = options; | ||
| this.evaluatedModules = evaluatedModules; | ||
| this.callstacks = callstacks; | ||
| } | ||
| async invoke(event) { | ||
| if (event.type !== "custom") return { error: /* @__PURE__ */ new Error(`Vitest Module Runner doesn't support Vite HMR events.`) }; | ||
| if (event.event !== "vite:invoke") return { error: /* @__PURE__ */ new Error(`Vitest Module Runner doesn't support ${event.event} event.`) }; | ||
| const { name, data } = event.data; | ||
| if (name === "getBuiltins") | ||
| // we return an empty array here to avoid client-side builtin check, | ||
| // as we need builtins to go through `fetchModule` | ||
| return { result: [] }; | ||
| if (name !== "fetchModule") return { error: /* @__PURE__ */ new Error(`Unknown method: ${name}. Expected "fetchModule".`) }; | ||
| try { | ||
| return { result: await this.options.fetchModule(...data) }; | ||
| } catch (cause) { | ||
| if (cause instanceof EnvironmentTeardownError) { | ||
| const [id, importer] = data; | ||
| let message = `Cannot load '${id}'${importer ? ` imported from ${importer}` : ""} after the environment was torn down. This is not a bug in Vitest.`; | ||
| const moduleNode = importer ? this.evaluatedModules.getModuleById(importer) : void 0; | ||
| const callstack = moduleNode ? this.callstacks.get(moduleNode) : void 0; | ||
| if (callstack) message += ` The last recorded callstack:\n- ${[ | ||
| ...callstack, | ||
| importer, | ||
| id | ||
| ].reverse().join("\n- ")}`; | ||
| const error = new EnvironmentTeardownError(message); | ||
| if (cause.stack) error.stack = cause.stack.replace(cause.message, error.message); | ||
| return { error }; | ||
| } | ||
| return { error: cause }; | ||
| } | ||
| } | ||
| } | ||
| const createNodeImportMeta = (modulePath) => { | ||
| if (!viteModuleRunner.createDefaultImportMeta) throw new Error(`createNodeImportMeta is not supported in this version of Vite.`); | ||
| const defaultMeta = viteModuleRunner.createDefaultImportMeta(modulePath); | ||
| const href = defaultMeta.url; | ||
| const importMetaResolver = createImportMetaResolver(); | ||
| return { | ||
| ...defaultMeta, | ||
| main: false, | ||
| resolve(id, parent) { | ||
| return (importMetaResolver ?? defaultMeta.resolve)(id, parent ?? href); | ||
| } | ||
| }; | ||
| }; | ||
| function createImportMetaResolver() { | ||
| if (!import.meta.resolve) return; | ||
| return (specifier, importer) => import.meta.resolve(specifier, importer); | ||
| } | ||
| // @ts-expect-error overriding private method | ||
| class VitestModuleRunner extends viteModuleRunner.ModuleRunner { | ||
| mocker; | ||
| moduleExecutionInfo; | ||
| _otel; | ||
| _callstacks; | ||
| constructor(vitestOptions) { | ||
| const options = vitestOptions; | ||
| const evaluatedModules = options.evaluatedModules; | ||
| const callstacks = /* @__PURE__ */ new WeakMap(); | ||
| const transport = new VitestTransport(options.transport, evaluatedModules, callstacks); | ||
| super({ | ||
| transport, | ||
| hmr: false, | ||
| evaluatedModules, | ||
| sourcemapInterceptor: "prepareStackTrace", | ||
| createImportMeta: vitestOptions.createImportMeta | ||
| }, options.evaluator); | ||
| this.vitestOptions = vitestOptions; | ||
| this._callstacks = callstacks; | ||
| this._otel = vitestOptions.traces || new Traces({ enabled: false }); | ||
| this.moduleExecutionInfo = options.getWorkerState().moduleExecutionInfo; | ||
| this.mocker = options.mocker || new VitestMocker(this, { | ||
| spyModule: options.spyModule, | ||
| context: options.vm?.context, | ||
| traces: this._otel, | ||
| resolveId: options.transport.resolveId, | ||
| get root() { | ||
| return options.getWorkerState().config.root; | ||
| }, | ||
| get moduleDirectories() { | ||
| return options.getWorkerState().config.deps.moduleDirectories || []; | ||
| }, | ||
| getCurrentTestFilepath() { | ||
| return options.getWorkerState().filepath; | ||
| } | ||
| }); | ||
| if (options.vm) options.vm.context.__vitest_mocker__ = this.mocker; | ||
| else Object.defineProperty(globalThis, "__vitest_mocker__", { | ||
| configurable: true, | ||
| writable: true, | ||
| value: this.mocker | ||
| }); | ||
| } | ||
| /** | ||
| * Vite checks that the module has exports emulating the Node.js behaviour, | ||
| * but Vitest is more relaxed. | ||
| * | ||
| * We should keep the Vite behavior when there is a `strict` flag. | ||
| * @internal | ||
| */ | ||
| processImport(exports$1) { | ||
| return exports$1; | ||
| } | ||
| async import(rawId) { | ||
| const resolved = await this._otel.$("vitest.module.resolve_id", { attributes: { "vitest.module.raw_id": rawId } }, async (span) => { | ||
| const result = await this.vitestOptions.transport.resolveId(rawId); | ||
| if (result) span.setAttributes({ | ||
| "vitest.module.url": result.url, | ||
| "vitest.module.file": result.file, | ||
| "vitest.module.id": result.id | ||
| }); | ||
| return result; | ||
| }); | ||
| return super.import(resolved ? resolved.url : rawId); | ||
| } | ||
| async fetchModule(url, importer) { | ||
| return await this.cachedModule(url, importer); | ||
| } | ||
| _cachedRequest(url, module, callstack = [], metadata) { | ||
| // @ts-expect-error "cachedRequest" is private | ||
| return super.cachedRequest(url, module, callstack, metadata); | ||
| } | ||
| /** | ||
| * @internal | ||
| */ | ||
| async cachedRequest(url, mod, callstack = [], metadata, ignoreMock = false) { | ||
| // Track for a better error message if dynamic import is not resolved properly | ||
| this._callstacks.set(mod, callstack); | ||
| if (ignoreMock) return this._cachedRequest(url, mod, callstack, metadata); | ||
| let mocked; | ||
| if (mod.meta && "mockedModule" in mod.meta) { | ||
| const mockedModule = mod.meta.mockedModule; | ||
| const mockId = this.mocker.getMockPath(mod.id); | ||
| // bypass mock and force "importActual" behavior when: | ||
| // - mock was removed by doUnmock (stale mockedModule in meta) | ||
| // - self-import: mock factory/file is importing the module it's mocking | ||
| const isStale = !this.mocker.getDependencyMock(mod.id); | ||
| const isSelfImport = callstack.includes(mockId) || callstack.includes(url) || "redirect" in mockedModule && callstack.includes(mockedModule.redirect); | ||
| if (isStale || isSelfImport) { | ||
| const node = await this.fetchModule(injectQuery(url, "_vitest_original")); | ||
| return this._cachedRequest(node.url, node, callstack, metadata); | ||
| } | ||
| mocked = await this.mocker.requestWithMockedModule(url, mod, callstack, mockedModule); | ||
| } else mocked = await this.mocker.mockedRequest(url, mod, callstack); | ||
| if (typeof mocked === "string") { | ||
| const node = await this.fetchModule(mocked); | ||
| return this._cachedRequest(mocked, node, callstack, metadata); | ||
| } | ||
| if (mocked != null && typeof mocked === "object") return mocked; | ||
| return this._cachedRequest(url, mod, callstack, metadata); | ||
| } | ||
| /** @internal */ | ||
| _invalidateSubTreeById(ids, invalidated = /* @__PURE__ */ new Set()) { | ||
| for (const id of ids) { | ||
| if (invalidated.has(id)) continue; | ||
| const node = this.evaluatedModules.getModuleById(id); | ||
| if (!node) continue; | ||
| invalidated.add(id); | ||
| const subIds = Array.from(this.evaluatedModules.idToModuleMap).filter(([, mod]) => mod.importers.has(id)).map(([key]) => key); | ||
| if (subIds.length) this._invalidateSubTreeById(subIds, invalidated); | ||
| this.evaluatedModules.invalidateModule(node); | ||
| } | ||
| } | ||
| } | ||
| const bareVitestRegexp = /^@?vitest(?:\/|$)/; | ||
| const normalizedDistDir = normalize(distDir); | ||
| const relativeIds = {}; | ||
| const externalizeMap = /* @__PURE__ */ new Map(); | ||
| // all Vitest imports always need to be externalized | ||
| function getCachedVitestImport(id, state) { | ||
| if (id.startsWith("/@fs/") || id.startsWith("\\@fs\\")) id = id.slice(process.platform === "win32" ? 5 : 4); | ||
| if (externalizeMap.has(id)) return { | ||
| externalize: externalizeMap.get(id), | ||
| type: "module" | ||
| }; | ||
| // always externalize Vitest because we import from there before running tests | ||
| // so we already have it cached by Node.js | ||
| const root = state().config.root; | ||
| const relativeRoot = relativeIds[root] ?? (relativeIds[root] = normalizedDistDir.slice(root.length)); | ||
| if (id.includes(distDir) || id.includes(normalizedDistDir)) { | ||
| const externalize = id.startsWith("file://") ? id : pathToFileURL(id).toString(); | ||
| externalizeMap.set(id, externalize); | ||
| return { | ||
| externalize, | ||
| type: "module" | ||
| }; | ||
| } | ||
| if (relativeRoot && relativeRoot !== "/" && id.startsWith(relativeRoot)) { | ||
| const externalize = pathToFileURL(join(root, id)).toString(); | ||
| externalizeMap.set(id, externalize); | ||
| return { | ||
| externalize, | ||
| type: "module" | ||
| }; | ||
| } | ||
| if (bareVitestRegexp.test(id)) { | ||
| externalizeMap.set(id, id); | ||
| return { | ||
| externalize: id, | ||
| type: "module" | ||
| }; | ||
| } | ||
| return null; | ||
| } | ||
| const { readFileSync } = fs; | ||
| const VITEST_VM_CONTEXT_SYMBOL = "__vitest_vm_context__"; | ||
| const cwd = process.cwd(); | ||
| const isWindows = process.platform === "win32"; | ||
| function startVitestModuleRunner(options) { | ||
| const traces = options.traces; | ||
| const state = () => getSafeWorkerState() || options.state; | ||
| const rpc = () => state().rpc; | ||
| const environment = () => { | ||
| const environment = state().environment; | ||
| return environment.viteEnvironment || environment.name; | ||
| }; | ||
| const vm = options.context && options.externalModulesExecutor ? { | ||
| context: options.context, | ||
| externalModulesExecutor: options.externalModulesExecutor | ||
| } : void 0; | ||
| const evaluator = options.evaluator || new VitestModuleEvaluator(vm, { | ||
| traces, | ||
| evaluatedModules: options.evaluatedModules, | ||
| get moduleExecutionInfo() { | ||
| return state().moduleExecutionInfo; | ||
| }, | ||
| get interopDefault() { | ||
| return state().config.deps.interopDefault; | ||
| }, | ||
| getCurrentTestFilepath: () => state().filepath | ||
| }); | ||
| const moduleRunner = new VitestModuleRunner({ | ||
| spyModule: options.spyModule, | ||
| evaluatedModules: options.evaluatedModules, | ||
| evaluator, | ||
| traces, | ||
| mocker: options.mocker, | ||
| transport: { | ||
| async fetchModule(id, importer, options) { | ||
| const resolvingModules = state().resolvingModules; | ||
| if (isWindows) { | ||
| if (id[1] === ":") { | ||
| // The drive letter is different for whatever reason, we need to normalize it to CWD | ||
| if (id[0] !== cwd[0] && id[0].toUpperCase() === cwd[0].toUpperCase()) id = (cwd[0].toUpperCase() === cwd[0] ? id[0].toUpperCase() : id[0].toLowerCase()) + id.slice(1); | ||
| // always mark absolute windows paths, otherwise Vite will externalize it | ||
| id = `/@id/${id}`; | ||
| } | ||
| } | ||
| const vitest = getCachedVitestImport(id, state); | ||
| if (vitest) return vitest; | ||
| // strip _vitest_original query added by importActual so that | ||
| // the plugin pipeline sees the original import id (e.g. virtual modules's load hook) | ||
| const isImportActual = id.includes("_vitest_original"); | ||
| if (isImportActual) id = removeQuery(id, "_vitest_original"); | ||
| const rawId = unwrapId(id); | ||
| resolvingModules.add(rawId); | ||
| try { | ||
| if (VitestMocker.pendingIds.length) await moduleRunner.mocker.resolveMocks(); | ||
| if (!isImportActual) { | ||
| const resolvedMock = moduleRunner.mocker.getDependencyMockByUrl(id); | ||
| if (resolvedMock?.type === "manual" || resolvedMock?.type === "redirect") return { | ||
| code: "", | ||
| file: null, | ||
| id: resolvedMock.id, | ||
| url: resolvedMock.url, | ||
| invalidate: false, | ||
| mockedModule: resolvedMock | ||
| }; | ||
| } | ||
| if (isBuiltin(rawId)) return { | ||
| externalize: rawId, | ||
| type: "builtin" | ||
| }; | ||
| if (isBrowserExternal(rawId)) return { | ||
| externalize: toBuiltin(rawId), | ||
| type: "builtin" | ||
| }; | ||
| // if module is invalidated, the worker will be recreated, | ||
| // so cached is always true in a single worker | ||
| if (!isImportActual && options?.cached) return { cache: true }; | ||
| const otelCarrier = traces?.getContextCarrier(); | ||
| const result = await rpc().fetch(id, importer, environment(), options, otelCarrier); | ||
| if ("cached" in result) return { | ||
| code: readFileSync(result.tmp, "utf-8"), | ||
| ...result | ||
| }; | ||
| return result; | ||
| } catch (cause) { | ||
| // rethrow vite error if it cannot load the module because it's not resolved | ||
| if (typeof cause === "object" && cause != null && cause.code === "ERR_LOAD_URL" || typeof cause?.message === "string" && cause.message.includes("Failed to load url") || typeof cause?.message === "string" && cause.message.startsWith("Cannot find module '")) { | ||
| const error = new Error(`Cannot find ${isBareImport(id) ? "package" : "module"} '${id}'${importer ? ` imported from ${importer}` : ""}`, { cause }); | ||
| error.code = "ERR_MODULE_NOT_FOUND"; | ||
| throw error; | ||
| } | ||
| throw cause; | ||
| } finally { | ||
| resolvingModules.delete(rawId); | ||
| } | ||
| }, | ||
| resolveId(id, importer) { | ||
| return rpc().resolve(id, importer, environment()); | ||
| } | ||
| }, | ||
| getWorkerState: state, | ||
| vm, | ||
| createImportMeta: options.createImportMeta | ||
| }); | ||
| return moduleRunner; | ||
| } | ||
| export { BareModuleMocker as B, VITEST_VM_CONTEXT_SYMBOL as V, VitestModuleRunner as a, VitestTransport as b, createNodeImportMeta as c, normalizeModuleId as n, startVitestModuleRunner as s }; |
| import { fileURLToPath, pathToFileURL } from 'node:url'; | ||
| import vm, { isContext, runInContext } from 'node:vm'; | ||
| import { dirname, basename, extname, normalize, resolve } from 'pathe'; | ||
| import { l as loadEnvironment, a as listenForErrors, e as emitModuleRunner } from './init.d4hAcNdp.js'; | ||
| import { distDir } from '../path.js'; | ||
| import { createCustomConsole } from './console.3WNpx0tS.js'; | ||
| import fs from 'node:fs'; | ||
| import { createRequire, Module, isBuiltin } from 'node:module'; | ||
| import { toArray, splitFileAndPostfix, isBareImport } from '@vitest/utils/helpers'; | ||
| import { findNearestPackageData } from '@vitest/utils/resolver'; | ||
| import { dirname as dirname$1 } from 'node:path'; | ||
| import { CSS_LANGS_RE, KNOWN_ASSET_RE } from '@vitest/utils/constants'; | ||
| import { getDefaultRequestStubs } from '../module-evaluator.js'; | ||
| import { s as startVitestModuleRunner, V as VITEST_VM_CONTEXT_SYMBOL, c as createNodeImportMeta } from './startVitestModuleRunner.CCBQZ9DQ.js'; | ||
| import { p as provideWorkerState } from './utils.BX5Fg8C4.js'; | ||
| function interopCommonJsModule(interopDefault, mod) { | ||
| if (isPrimitive(mod) || Array.isArray(mod) || mod instanceof Promise) return { | ||
| keys: [], | ||
| moduleExports: {}, | ||
| defaultExport: mod | ||
| }; | ||
| if (interopDefault !== false && "__esModule" in mod && !isPrimitive(mod.default)) { | ||
| const defaultKets = Object.keys(mod.default); | ||
| const moduleKeys = Object.keys(mod); | ||
| const allKeys = new Set([...defaultKets, ...moduleKeys]); | ||
| allKeys.delete("default"); | ||
| return { | ||
| keys: Array.from(allKeys), | ||
| moduleExports: new Proxy(mod, { get(mod, prop) { | ||
| return mod[prop] ?? mod.default?.[prop]; | ||
| } }), | ||
| defaultExport: mod | ||
| }; | ||
| } | ||
| return { | ||
| keys: Object.keys(mod).filter((key) => key !== "default"), | ||
| moduleExports: mod, | ||
| defaultExport: mod | ||
| }; | ||
| } | ||
| function isPrimitive(obj) { | ||
| return !(obj != null && (typeof obj === "object" || typeof obj === "function")); | ||
| } | ||
| const SyntheticModule = vm.SyntheticModule; | ||
| const SourceTextModule = vm.SourceTextModule; | ||
| const _require = createRequire(import.meta.url); | ||
| const requiresCache = /* @__PURE__ */ new WeakMap(); | ||
| class CommonjsExecutor { | ||
| context; | ||
| requireCache = /* @__PURE__ */ new Map(); | ||
| publicRequireCache = this.createProxyCache(); | ||
| moduleCache = /* @__PURE__ */ new Map(); | ||
| builtinCache = Object.create(null); | ||
| extensions = Object.create(null); | ||
| fs; | ||
| Module; | ||
| interopDefault; | ||
| constructor(options) { | ||
| this.context = options.context; | ||
| this.fs = options.fileMap; | ||
| this.interopDefault = options.interopDefault; | ||
| const primitives = vm.runInContext("({ Object, Array, Error })", this.context); | ||
| // eslint-disable-next-line ts/no-this-alias | ||
| const executor = this; | ||
| this.Module = class Module$1 { | ||
| exports; | ||
| isPreloading = false; | ||
| id; | ||
| filename; | ||
| loaded; | ||
| parent; | ||
| children = []; | ||
| path; | ||
| paths = []; | ||
| constructor(id = "", parent) { | ||
| this.exports = primitives.Object.create(Object.prototype); | ||
| // in our case the path should always be resolved already | ||
| this.path = dirname(id); | ||
| this.id = id; | ||
| this.filename = id; | ||
| this.loaded = false; | ||
| this.parent = parent; | ||
| } | ||
| get require() { | ||
| const require = requiresCache.get(this); | ||
| if (require) return require; | ||
| const _require = Module$1.createRequire(this.id); | ||
| requiresCache.set(this, _require); | ||
| return _require; | ||
| } | ||
| static getSourceMapsSupport = () => ({ | ||
| enabled: false, | ||
| nodeModules: false, | ||
| generatedCode: false | ||
| }); | ||
| static setSourceMapsSupport = () => { | ||
| // noop | ||
| }; | ||
| static register = () => { | ||
| throw new Error(`[vitest] "register" is not available when running in Vitest.`); | ||
| }; | ||
| static registerHooks = () => { | ||
| throw new Error(`[vitest] "registerHooks" is not available when running in Vitest.`); | ||
| }; | ||
| _compile(code, filename) { | ||
| const cjsModule = Module$1.wrap(code); | ||
| const script = new vm.Script(cjsModule, { | ||
| filename, | ||
| importModuleDynamically: options.importModuleDynamically | ||
| }); | ||
| // @ts-expect-error mark script with current identifier | ||
| script.identifier = filename; | ||
| const fn = script.runInContext(executor.context); | ||
| const __dirname = dirname(filename); | ||
| executor.requireCache.set(filename, this); | ||
| try { | ||
| fn(this.exports, this.require, this, filename, __dirname); | ||
| return this.exports; | ||
| } finally { | ||
| this.loaded = true; | ||
| } | ||
| } | ||
| // exposed for external use, Node.js does the opposite | ||
| static _load = (request, parent, _isMain) => { | ||
| return Module$1.createRequire(parent?.filename ?? request)(request); | ||
| }; | ||
| static wrap = (script) => { | ||
| return Module$1.wrapper[0] + script + Module$1.wrapper[1]; | ||
| }; | ||
| static wrapper = new primitives.Array("(function (exports, require, module, __filename, __dirname) { ", "\n});"); | ||
| static builtinModules = Module.builtinModules; | ||
| static findSourceMap = Module.findSourceMap; | ||
| static SourceMap = Module.SourceMap; | ||
| static syncBuiltinESMExports = Module.syncBuiltinESMExports; | ||
| static _cache = executor.publicRequireCache; | ||
| static _extensions = executor.extensions; | ||
| static createRequire = (filename) => { | ||
| return executor.createRequire(filename); | ||
| }; | ||
| static runMain = () => { | ||
| throw new primitives.Error("[vitest] \"runMain\" is not implemented."); | ||
| }; | ||
| // @ts-expect-error not typed | ||
| static _resolveFilename = Module._resolveFilename; | ||
| // @ts-expect-error not typed | ||
| static _findPath = Module._findPath; | ||
| // @ts-expect-error not typed | ||
| static _initPaths = Module._initPaths; | ||
| // @ts-expect-error not typed | ||
| static _preloadModules = Module._preloadModules; | ||
| // @ts-expect-error not typed | ||
| static _resolveLookupPaths = Module._resolveLookupPaths; | ||
| // @ts-expect-error not typed | ||
| static globalPaths = Module.globalPaths; | ||
| static isBuiltin = Module.isBuiltin; | ||
| static constants = Module.constants; | ||
| static enableCompileCache = Module.enableCompileCache; | ||
| static getCompileCacheDir = Module.getCompileCacheDir; | ||
| static flushCompileCache = Module.flushCompileCache; | ||
| static stripTypeScriptTypes = Module.stripTypeScriptTypes; | ||
| static findPackageJSON = Module.findPackageJSON; | ||
| static Module = Module$1; | ||
| }; | ||
| this.extensions[".js"] = this.requireJs; | ||
| this.extensions[".json"] = this.requireJson; | ||
| } | ||
| requireJs = (m, filename) => { | ||
| const content = this.fs.readFile(filename); | ||
| m._compile(content, filename); | ||
| }; | ||
| requireJson = (m, filename) => { | ||
| const code = this.fs.readFile(filename); | ||
| m.exports = JSON.parse(code); | ||
| }; | ||
| static cjsConditions; | ||
| static getCjsConditions() { | ||
| if (!CommonjsExecutor.cjsConditions) CommonjsExecutor.cjsConditions = parseCjsConditions(process.execArgv, process.env.NODE_OPTIONS); | ||
| return CommonjsExecutor.cjsConditions; | ||
| } | ||
| createRequire = (filename) => { | ||
| const _require = createRequire(filename); | ||
| const resolve = (id, options) => { | ||
| return _require.resolve(id, { | ||
| ...options, | ||
| conditions: CommonjsExecutor.getCjsConditions() | ||
| }); | ||
| }; | ||
| const require = ((id) => { | ||
| const resolved = resolve(id); | ||
| if (extname(resolved) === ".node" || isBuiltin(resolved)) return this.requireCoreModule(resolved); | ||
| const module = new this.Module(resolved); | ||
| return this.loadCommonJSModule(module, resolved); | ||
| }); | ||
| require.resolve = resolve; | ||
| require.resolve.paths = _require.resolve.paths; | ||
| Object.defineProperty(require, "extensions", { | ||
| get: () => this.extensions, | ||
| set: () => {}, | ||
| configurable: true | ||
| }); | ||
| require.main = void 0; | ||
| require.cache = this.publicRequireCache; | ||
| return require; | ||
| }; | ||
| createProxyCache() { | ||
| return new Proxy(Object.create(null), { | ||
| defineProperty: () => true, | ||
| deleteProperty: () => true, | ||
| set: () => true, | ||
| get: (_, key) => this.requireCache.get(key), | ||
| has: (_, key) => this.requireCache.has(key), | ||
| ownKeys: () => Array.from(this.requireCache.keys()), | ||
| getOwnPropertyDescriptor() { | ||
| return { | ||
| configurable: true, | ||
| enumerable: true | ||
| }; | ||
| } | ||
| }); | ||
| } | ||
| // very naive implementation for Node.js require | ||
| loadCommonJSModule(module, filename) { | ||
| const cached = this.requireCache.get(filename); | ||
| if (cached) return cached.exports; | ||
| const extension = this.findLongestRegisteredExtension(filename); | ||
| (this.extensions[extension] || this.extensions[".js"])(module, filename); | ||
| return module.exports; | ||
| } | ||
| findLongestRegisteredExtension(filename) { | ||
| const name = basename(filename); | ||
| let currentExtension; | ||
| let index; | ||
| let startIndex = 0; | ||
| // eslint-disable-next-line no-cond-assign | ||
| while ((index = name.indexOf(".", startIndex)) !== -1) { | ||
| startIndex = index + 1; | ||
| if (index === 0) continue; | ||
| currentExtension = name.slice(index); | ||
| if (this.extensions[currentExtension]) return currentExtension; | ||
| } | ||
| return ".js"; | ||
| } | ||
| getCoreSyntheticModule(identifier) { | ||
| if (this.moduleCache.has(identifier)) return this.moduleCache.get(identifier); | ||
| const exports$1 = this.require(identifier); | ||
| const keys = Object.keys(exports$1); | ||
| const module = new SyntheticModule([...keys, "default"], () => { | ||
| for (const key of keys) module.setExport(key, exports$1[key]); | ||
| module.setExport("default", exports$1); | ||
| }, { | ||
| context: this.context, | ||
| identifier | ||
| }); | ||
| this.moduleCache.set(identifier, module); | ||
| return module; | ||
| } | ||
| getCjsSyntheticModule(path, identifier) { | ||
| if (this.moduleCache.has(identifier)) return this.moduleCache.get(identifier); | ||
| const exports$1 = this.require(path); | ||
| // TODO: technically module should be parsed to find static exports, implement for strict mode in #2854 | ||
| const { keys, moduleExports, defaultExport } = interopCommonJsModule(this.interopDefault, exports$1); | ||
| const module = new SyntheticModule([...keys, "default"], function() { | ||
| for (const key of keys) this.setExport(key, moduleExports[key]); | ||
| this.setExport("default", defaultExport); | ||
| }, { | ||
| context: this.context, | ||
| identifier | ||
| }); | ||
| this.moduleCache.set(identifier, module); | ||
| return module; | ||
| } | ||
| // TODO: use this in strict mode, when available in #2854 | ||
| // private _getNamedCjsExports(path: string): Set<string> { | ||
| // const cachedNamedExports = this.cjsNamedExportsMap.get(path) | ||
| // if (cachedNamedExports) { | ||
| // return cachedNamedExports | ||
| // } | ||
| // if (extname(path) === '.node') { | ||
| // const moduleExports = this.require(path) | ||
| // const namedExports = new Set(Object.keys(moduleExports)) | ||
| // this.cjsNamedExportsMap.set(path, namedExports) | ||
| // return namedExports | ||
| // } | ||
| // const code = this.fs.readFile(path) | ||
| // const { exports, reexports } = parseCjs(code, path) | ||
| // const namedExports = new Set(exports) | ||
| // this.cjsNamedExportsMap.set(path, namedExports) | ||
| // for (const reexport of reexports) { | ||
| // if (isNodeBuiltin(reexport)) { | ||
| // const exports = this.require(reexport) | ||
| // if (exports !== null && typeof exports === 'object') { | ||
| // for (const e of Object.keys(exports)) { | ||
| // namedExports.add(e) | ||
| // } | ||
| // } | ||
| // } | ||
| // else { | ||
| // const require = this.createRequire(path) | ||
| // const resolved = require.resolve(reexport) | ||
| // const exports = this._getNamedCjsExports(resolved) | ||
| // for (const e of exports) { | ||
| // namedExports.add(e) | ||
| // } | ||
| // } | ||
| // } | ||
| // return namedExports | ||
| // } | ||
| require(identifier) { | ||
| if (extname(identifier) === ".node" || isBuiltin(identifier)) return this.requireCoreModule(identifier); | ||
| const module = new this.Module(identifier); | ||
| return this.loadCommonJSModule(module, identifier); | ||
| } | ||
| requireCoreModule(identifier) { | ||
| const normalized = identifier.replace(/^node:/, ""); | ||
| if (this.builtinCache[normalized]) return this.builtinCache[normalized].exports; | ||
| const moduleExports = _require(identifier); | ||
| if (identifier === "node:module" || identifier === "module") { | ||
| const module = new this.Module("/module.js"); | ||
| module.exports = this.Module; | ||
| this.builtinCache[normalized] = module; | ||
| return module.exports; | ||
| } | ||
| this.builtinCache[normalized] = _require.cache[normalized]; | ||
| // TODO: should we wrap module to rethrow context errors? | ||
| return moduleExports; | ||
| } | ||
| } | ||
| // The "module-sync" exports condition (added in Node 22.12/20.19 when | ||
| // require(esm) was unflagged) can resolve to ESM files that our CJS | ||
| // vm.Script executor cannot handle. We exclude it by passing explicit | ||
| // CJS conditions to require.resolve (Node 22.12+). | ||
| // Must be a Set because Node's internal resolver calls conditions.has(). | ||
| // User-specified --conditions/-C flags are respected, except module-sync. | ||
| function parseCjsConditions(execArgv, nodeOptions) { | ||
| const conditions = [ | ||
| "node", | ||
| "require", | ||
| "node-addons" | ||
| ]; | ||
| const args = [...execArgv, ...nodeOptions?.split(/\s+/) ?? []]; | ||
| for (let i = 0; i < args.length; i++) { | ||
| const arg = args[i]; | ||
| const eqMatch = arg.match(/^(?:--conditions|-C)=(.+)$/); | ||
| if (eqMatch) conditions.push(eqMatch[1]); | ||
| else if ((arg === "--conditions" || arg === "-C") && i + 1 < args.length) conditions.push(args[++i]); | ||
| } | ||
| return new Set(conditions.filter((c) => c !== "module-sync")); | ||
| } | ||
| const dataURIRegex = /^data:(?<mime>text\/javascript|application\/json|application\/wasm)(?:;(?<encoding>charset=utf-8|base64))?,(?<code>.*)$/; | ||
| class EsmExecutor { | ||
| moduleCache = /* @__PURE__ */ new Map(); | ||
| esmLinkMap = /* @__PURE__ */ new WeakMap(); | ||
| context; | ||
| #httpIp = IPnumber("127.0.0.0"); | ||
| constructor(executor, options) { | ||
| this.executor = executor; | ||
| this.context = options.context; | ||
| } | ||
| async evaluateModule(m) { | ||
| if (m.status === "unlinked") this.esmLinkMap.set(m, m.link((identifier, referencer) => this.executor.resolveModule(identifier, referencer.identifier))); | ||
| await this.esmLinkMap.get(m); | ||
| if (m.status === "linked") await m.evaluate(); | ||
| return m; | ||
| } | ||
| async createEsModule(fileURL, getCode) { | ||
| const cached = this.moduleCache.get(fileURL); | ||
| if (cached) return cached; | ||
| const promise = this.loadEsModule(fileURL, getCode); | ||
| this.moduleCache.set(fileURL, promise); | ||
| return promise; | ||
| } | ||
| async loadEsModule(fileURL, getCode) { | ||
| const code = await getCode(); | ||
| // TODO: should not be allowed in strict mode, implement in #2854 | ||
| if (fileURL.endsWith(".json")) { | ||
| const m = new SyntheticModule(["default"], function() { | ||
| const result = JSON.parse(code); | ||
| this.setExport("default", result); | ||
| }); | ||
| this.moduleCache.set(fileURL, m); | ||
| return m; | ||
| } | ||
| const m = new SourceTextModule(code, { | ||
| identifier: fileURL, | ||
| context: this.context, | ||
| importModuleDynamically: this.executor.importModuleDynamically, | ||
| initializeImportMeta: (meta, mod) => { | ||
| meta.url = mod.identifier; | ||
| if (mod.identifier.startsWith("file:")) { | ||
| const filename = fileURLToPath(mod.identifier); | ||
| meta.filename = filename; | ||
| meta.dirname = dirname$1(filename); | ||
| } | ||
| meta.resolve = (specifier, importer) => { | ||
| return this.executor.resolve(specifier, importer != null ? importer.toString() : mod.identifier); | ||
| }; | ||
| } | ||
| }); | ||
| this.moduleCache.set(fileURL, m); | ||
| return m; | ||
| } | ||
| async createWebAssemblyModule(fileUrl, getCode) { | ||
| const cached = this.moduleCache.get(fileUrl); | ||
| if (cached) return cached; | ||
| const m = this.loadWebAssemblyModule(getCode(), fileUrl); | ||
| this.moduleCache.set(fileUrl, m); | ||
| return m; | ||
| } | ||
| async createNetworkModule(fileUrl) { | ||
| // https://nodejs.org/api/esm.html#https-and-http-imports | ||
| if (fileUrl.startsWith("http:")) { | ||
| const url = new URL(fileUrl); | ||
| if (url.hostname !== "localhost" && url.hostname !== "::1" && (IPnumber(url.hostname) & IPmask(8)) !== this.#httpIp) throw new Error( | ||
| // we don't know the importer, so it's undefined (the same happens in --pool=threads) | ||
| `import of '${fileUrl}' by undefined is not supported: http can only be used to load local resources (use https instead).` | ||
| ); | ||
| } | ||
| return this.createEsModule(fileUrl, () => fetch(fileUrl).then((r) => r.text())); | ||
| } | ||
| async loadWebAssemblyModule(source, identifier) { | ||
| const cached = this.moduleCache.get(identifier); | ||
| if (cached) return cached; | ||
| const wasmModule = await WebAssembly.compile(source); | ||
| const exports$1 = WebAssembly.Module.exports(wasmModule); | ||
| const imports = WebAssembly.Module.imports(wasmModule); | ||
| const moduleLookup = {}; | ||
| for (const { module } of imports) if (moduleLookup[module] === void 0) moduleLookup[module] = await this.executor.resolveModule(module, identifier); | ||
| const evaluateModule = (module) => this.evaluateModule(module); | ||
| return new SyntheticModule(exports$1.map(({ name }) => name), async function() { | ||
| const importsObject = {}; | ||
| for (const { module, name } of imports) { | ||
| if (!importsObject[module]) importsObject[module] = {}; | ||
| await evaluateModule(moduleLookup[module]); | ||
| importsObject[module][name] = moduleLookup[module].namespace[name]; | ||
| } | ||
| const wasmInstance = new WebAssembly.Instance(wasmModule, importsObject); | ||
| for (const { name } of exports$1) this.setExport(name, wasmInstance.exports[name]); | ||
| }, { | ||
| context: this.context, | ||
| identifier | ||
| }); | ||
| } | ||
| cacheModule(identifier, module) { | ||
| this.moduleCache.set(identifier, module); | ||
| } | ||
| resolveCachedModule(identifier) { | ||
| return this.moduleCache.get(identifier); | ||
| } | ||
| async createDataModule(identifier) { | ||
| const cached = this.moduleCache.get(identifier); | ||
| if (cached) return cached; | ||
| const match = identifier.match(dataURIRegex); | ||
| if (!match || !match.groups) throw new Error("Invalid data URI"); | ||
| const mime = match.groups.mime; | ||
| const encoding = match.groups.encoding; | ||
| if (mime === "application/wasm") { | ||
| if (!encoding) throw new Error("Missing data URI encoding"); | ||
| if (encoding !== "base64") throw new Error(`Invalid data URI encoding: ${encoding}`); | ||
| const module = this.loadWebAssemblyModule(Buffer.from(match.groups.code, "base64"), identifier); | ||
| this.moduleCache.set(identifier, module); | ||
| return module; | ||
| } | ||
| let code = match.groups.code; | ||
| if (!encoding || encoding === "charset=utf-8") code = decodeURIComponent(code); | ||
| else if (encoding === "base64") code = Buffer.from(code, "base64").toString(); | ||
| else throw new Error(`Invalid data URI encoding: ${encoding}`); | ||
| if (mime === "application/json") { | ||
| const module = new SyntheticModule(["default"], function() { | ||
| const obj = JSON.parse(code); | ||
| this.setExport("default", obj); | ||
| }, { | ||
| context: this.context, | ||
| identifier | ||
| }); | ||
| this.moduleCache.set(identifier, module); | ||
| return module; | ||
| } | ||
| return this.createEsModule(identifier, () => code); | ||
| } | ||
| } | ||
| function IPnumber(address) { | ||
| const ip = address.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/); | ||
| if (ip) return (+ip[1] << 24) + (+ip[2] << 16) + (+ip[3] << 8) + +ip[4]; | ||
| throw new Error(`Expected IP address, received ${address}`); | ||
| } | ||
| function IPmask(maskSize) { | ||
| return -1 << 32 - maskSize; | ||
| } | ||
| const CLIENT_ID = "/@vite/client"; | ||
| const CLIENT_FILE = pathToFileURL(CLIENT_ID).href; | ||
| class ViteExecutor { | ||
| esm; | ||
| constructor(options) { | ||
| this.options = options; | ||
| this.esm = options.esmExecutor; | ||
| } | ||
| resolve = (identifier) => { | ||
| if (identifier === CLIENT_ID) return identifier; | ||
| }; | ||
| get workerState() { | ||
| return this.options.context.__vitest_worker__; | ||
| } | ||
| async createViteModule(fileUrl) { | ||
| if (fileUrl === CLIENT_FILE || fileUrl === CLIENT_ID) return this.createViteClientModule(); | ||
| const cached = this.esm.resolveCachedModule(fileUrl); | ||
| if (cached) return cached; | ||
| return this.esm.createEsModule(fileUrl, async () => { | ||
| try { | ||
| const result = await this.options.transform(fileUrl); | ||
| if (result.code) return result.code; | ||
| } catch (cause) { | ||
| // rethrow vite error if it cannot load the module because it's not resolved | ||
| if (typeof cause === "object" && cause.code === "ERR_LOAD_URL" || typeof cause?.message === "string" && cause.message.includes("Failed to load url")) { | ||
| const error = new Error(`Cannot find module '${fileUrl}'`, { cause }); | ||
| error.code = "ERR_MODULE_NOT_FOUND"; | ||
| throw error; | ||
| } | ||
| } | ||
| throw new Error(`[vitest] Failed to transform ${fileUrl}. Does the file exist?`); | ||
| }); | ||
| } | ||
| createViteClientModule() { | ||
| const identifier = CLIENT_ID; | ||
| const cached = this.esm.resolveCachedModule(identifier); | ||
| if (cached) return cached; | ||
| const stub = this.options.viteClientModule; | ||
| const moduleKeys = Object.keys(stub); | ||
| const module = new SyntheticModule(moduleKeys, function() { | ||
| moduleKeys.forEach((key) => { | ||
| this.setExport(key, stub[key]); | ||
| }); | ||
| }, { | ||
| context: this.options.context, | ||
| identifier | ||
| }); | ||
| this.esm.cacheModule(identifier, module); | ||
| return module; | ||
| } | ||
| canResolve = (fileUrl) => { | ||
| if (fileUrl === CLIENT_FILE) return true; | ||
| const config = this.workerState.config.deps?.web || {}; | ||
| const [modulePath] = fileUrl.split("?"); | ||
| if (config.transformCss && CSS_LANGS_RE.test(modulePath)) return true; | ||
| if (config.transformAssets && KNOWN_ASSET_RE.test(modulePath)) return true; | ||
| if (toArray(config.transformGlobPattern).some((pattern) => pattern.test(modulePath))) return true; | ||
| return false; | ||
| }; | ||
| } | ||
| const { existsSync } = fs; | ||
| // always defined when we use vm pool | ||
| const nativeResolve = import.meta.resolve; | ||
| // TODO: improve Node.js strict mode support in #2854 | ||
| class ExternalModulesExecutor { | ||
| cjs; | ||
| esm; | ||
| vite; | ||
| context; | ||
| fs; | ||
| resolvers = []; | ||
| #networkSupported = null; | ||
| constructor(options) { | ||
| this.options = options; | ||
| this.context = options.context; | ||
| this.fs = options.fileMap; | ||
| this.esm = new EsmExecutor(this, { context: this.context }); | ||
| this.cjs = new CommonjsExecutor({ | ||
| context: this.context, | ||
| importModuleDynamically: this.importModuleDynamically, | ||
| fileMap: options.fileMap, | ||
| interopDefault: options.interopDefault | ||
| }); | ||
| this.vite = new ViteExecutor({ | ||
| esmExecutor: this.esm, | ||
| context: this.context, | ||
| transform: options.transform, | ||
| viteClientModule: options.viteClientModule | ||
| }); | ||
| this.resolvers = [this.vite.resolve]; | ||
| } | ||
| async import(identifier) { | ||
| const module = await this.createModule(identifier); | ||
| await this.esm.evaluateModule(module); | ||
| return module.namespace; | ||
| } | ||
| require(identifier) { | ||
| return this.cjs.require(identifier); | ||
| } | ||
| createRequire(identifier) { | ||
| return this.cjs.createRequire(identifier); | ||
| } | ||
| // dynamic import can be used in both ESM and CJS, so we have it in the executor | ||
| importModuleDynamically = async (specifier, referencer) => { | ||
| const module = await this.resolveModule(specifier, referencer.identifier); | ||
| return await this.esm.evaluateModule(module); | ||
| }; | ||
| resolveModule = async (specifier, referencer) => { | ||
| let identifier = this.resolve(specifier, referencer); | ||
| if (identifier instanceof Promise) identifier = await identifier; | ||
| return await this.createModule(identifier); | ||
| }; | ||
| resolve(specifier, parent) { | ||
| for (const resolver of this.resolvers) { | ||
| const id = resolver(specifier, parent); | ||
| if (id) return id; | ||
| } | ||
| // import.meta.resolve can be asynchronous in older +18 Node versions | ||
| return nativeResolve(specifier, parent); | ||
| } | ||
| getModuleInformation(identifier) { | ||
| if (identifier.startsWith("data:")) return { | ||
| type: "data", | ||
| url: identifier, | ||
| path: identifier | ||
| }; | ||
| const { file, postfix } = splitFileAndPostfix(identifier); | ||
| const extension = extname(file); | ||
| if (extension === ".node" || isBuiltin(identifier)) return { | ||
| type: "builtin", | ||
| url: identifier, | ||
| path: identifier | ||
| }; | ||
| if (this.isNetworkSupported && (identifier.startsWith("http:") || identifier.startsWith("https:"))) return { | ||
| type: "network", | ||
| url: identifier, | ||
| path: identifier | ||
| }; | ||
| const isFileUrl = identifier.startsWith("file://"); | ||
| const pathUrl = isFileUrl ? fileURLToPath(file) : file; | ||
| const fileUrl = isFileUrl ? identifier : `${pathToFileURL(file)}${postfix}`; | ||
| let type; | ||
| if (this.vite.canResolve(fileUrl)) type = "vite"; | ||
| else if (extension === ".mjs") type = "module"; | ||
| else if (extension === ".cjs") type = "commonjs"; | ||
| else if (extension === ".wasm") | ||
| // still experimental on NodeJS --experimental-wasm-modules | ||
| // cf. ESM_FILE_FORMAT(url) in https://nodejs.org/docs/latest-v20.x/api/esm.html#resolution-algorithm | ||
| type = "wasm"; | ||
| else type = findNearestPackageData(normalize(pathUrl)).type === "module" ? "module" : "commonjs"; | ||
| return { | ||
| type, | ||
| path: pathUrl, | ||
| url: fileUrl | ||
| }; | ||
| } | ||
| createModule(identifier) { | ||
| const { type, url, path } = this.getModuleInformation(identifier); | ||
| // create ERR_MODULE_NOT_FOUND on our own since latest NodeJS's import.meta.resolve doesn't throw on non-existing namespace or path | ||
| // https://github.com/nodejs/node/pull/49038 | ||
| if ((type === "module" || type === "commonjs" || type === "wasm") && !existsSync(path)) { | ||
| const error = /* @__PURE__ */ new Error(`Cannot find ${isBareImport(path) ? "package" : "module"} '${path}'`); | ||
| error.code = "ERR_MODULE_NOT_FOUND"; | ||
| throw error; | ||
| } | ||
| switch (type) { | ||
| case "data": return this.esm.createDataModule(identifier); | ||
| case "builtin": return this.cjs.getCoreSyntheticModule(identifier); | ||
| case "vite": return this.vite.createViteModule(url); | ||
| case "wasm": return this.esm.createWebAssemblyModule(url, () => this.fs.readBuffer(path)); | ||
| case "module": return this.esm.createEsModule(url, () => this.fs.readFileAsync(path)); | ||
| case "commonjs": return this.cjs.getCjsSyntheticModule(path, identifier); | ||
| case "network": return this.esm.createNetworkModule(url); | ||
| default: return type; | ||
| } | ||
| } | ||
| get isNetworkSupported() { | ||
| if (this.#networkSupported == null) if (process.execArgv.includes("--experimental-network-imports")) this.#networkSupported = true; | ||
| else if (process.env.NODE_OPTIONS?.includes("--experimental-network-imports")) this.#networkSupported = true; | ||
| else this.#networkSupported = false; | ||
| return this.#networkSupported; | ||
| } | ||
| } | ||
| const { promises, readFileSync } = fs; | ||
| class FileMap { | ||
| fsCache = /* @__PURE__ */ new Map(); | ||
| fsBufferCache = /* @__PURE__ */ new Map(); | ||
| async readFileAsync(path) { | ||
| const cached = this.fsCache.get(path); | ||
| if (cached != null) return cached; | ||
| const source = await promises.readFile(path, "utf-8"); | ||
| this.fsCache.set(path, source); | ||
| return source; | ||
| } | ||
| readFile(path) { | ||
| const cached = this.fsCache.get(path); | ||
| if (cached != null) return cached; | ||
| const source = readFileSync(path, "utf-8"); | ||
| this.fsCache.set(path, source); | ||
| return source; | ||
| } | ||
| readBuffer(path) { | ||
| const cached = this.fsBufferCache.get(path); | ||
| if (cached != null) return cached; | ||
| const buffer = readFileSync(path); | ||
| this.fsBufferCache.set(path, buffer); | ||
| return buffer; | ||
| } | ||
| } | ||
| const entryFile = pathToFileURL(resolve(distDir, "workers/runVmTests.js")).href; | ||
| const fileMap = new FileMap(); | ||
| const packageCache = /* @__PURE__ */ new Map(); | ||
| async function runVmTests(method, state, traces) { | ||
| const { ctx, rpc } = state; | ||
| const beforeEnvironmentTime = performance.now(); | ||
| const { environment } = await loadEnvironment(ctx.environment.name, ctx.config.root, rpc, traces, true); | ||
| state.environment = environment; | ||
| if (!environment.setupVM) { | ||
| const envName = ctx.environment.name; | ||
| const packageId = envName[0] === "." ? envName : `vitest-environment-${envName}`; | ||
| throw new TypeError(`Environment "${ctx.environment.name}" is not a valid environment. Path "${packageId}" doesn't support vm environment because it doesn't provide "setupVM" method.`); | ||
| } | ||
| const vm = await traces.$("vitest.runtime.environment.setup", { attributes: { | ||
| "vitest.environment": environment.name, | ||
| "vitest.environment.vite_environment": environment.viteEnvironment || environment.name | ||
| } }, () => environment.setupVM(ctx.environment.options || ctx.config.environmentOptions || {})); | ||
| state.durations.environment = performance.now() - beforeEnvironmentTime; | ||
| process.env.VITEST_VM_POOL = "1"; | ||
| if (!vm.getVmContext) throw new TypeError(`Environment ${environment.name} doesn't provide "getVmContext" method. It should return a context created by "vm.createContext" method.`); | ||
| const context = vm.getVmContext(); | ||
| if (!isContext(context)) throw new TypeError(`Environment ${environment.name} doesn't provide a valid context. It should be created by "vm.createContext" method.`); | ||
| provideWorkerState(context, state); | ||
| // this is unfortunately needed for our own dependencies | ||
| // we need to find a way to not rely on this by default | ||
| // because browser doesn't provide these globals | ||
| context.process = process; | ||
| context.global = context; | ||
| context.console = state.config.disableConsoleIntercept ? console : createCustomConsole(state); | ||
| // TODO: don't hardcode setImmediate in fake timers defaults | ||
| context.setImmediate = setImmediate; | ||
| context.clearImmediate = clearImmediate; | ||
| const stubs = getDefaultRequestStubs(context); | ||
| const externalModulesExecutor = new ExternalModulesExecutor({ | ||
| context, | ||
| fileMap, | ||
| packageCache, | ||
| transform: rpc.transform, | ||
| viteClientModule: stubs["/@vite/client"] | ||
| }); | ||
| process.exit = (code = process.exitCode || 0) => { | ||
| throw new Error(`process.exit unexpectedly called with "${code}"`); | ||
| }; | ||
| listenForErrors(() => state); | ||
| const moduleRunner = startVitestModuleRunner({ | ||
| context, | ||
| evaluatedModules: state.evaluatedModules, | ||
| state, | ||
| externalModulesExecutor, | ||
| createImportMeta: createNodeImportMeta, | ||
| traces | ||
| }); | ||
| emitModuleRunner(moduleRunner); | ||
| Object.defineProperty(context, VITEST_VM_CONTEXT_SYMBOL, { | ||
| value: { | ||
| context, | ||
| externalModulesExecutor | ||
| }, | ||
| configurable: true, | ||
| enumerable: false, | ||
| writable: false | ||
| }); | ||
| context.__vitest_mocker__ = moduleRunner.mocker; | ||
| if (ctx.config.serializedDefines) try { | ||
| runInContext(ctx.config.serializedDefines, context, { filename: "virtual:load-defines.js" }); | ||
| } catch (error) { | ||
| throw new Error(`Failed to load custom "defines": ${error.message}`); | ||
| } | ||
| await moduleRunner.mocker.initializeSpyModule(); | ||
| const { run } = await moduleRunner.import(entryFile); | ||
| try { | ||
| await run(method, ctx.files, ctx.config, moduleRunner, traces); | ||
| } finally { | ||
| await traces.$("vitest.runtime.environment.teardown", () => vm.teardown?.()); | ||
| } | ||
| } | ||
| function setupVmWorker(context) { | ||
| if (context.config.experimental.viteModuleRunner === false) throw new Error(`Pool "${context.pool}" cannot run with "experimental.viteModuleRunner: false". Please, use "threads" or "forks" instead.`); | ||
| } | ||
| export { runVmTests as r, setupVmWorker as s }; |
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
1905633
0.01%50890
0+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated
Updated
Updated
Updated