Initial commit

This commit is contained in:
mats committed 2026-09-22 08:41:20 +02:00
commit 89053153cc
8152 files changed
+2086461

No files matched your search

File diff suppressed because it is too large. Load diff
+211
View File
@@ -0,0 +1,211 @@
import { runInThisContext } from 'node:vm';
import { s as spyModule } from './spy.DPLvp2J7.js';
import { r as resolveTestRunner, a as resolveSnapshotEnvironment, d as detectAsyncLeaks, s as setupChaiConfig } from './index.5J3Pr46F.js';
import { l as loadEnvironment, e as emitModuleRunner, a as listenForErrors } from './init.BS957yFf.js';
import { Traces } from '../traces.js';
import { V as VitestEvaluatedModules } from './rpc.DWKX1ZbI.js';
import { s as startVitestModuleRunner, c as createNodeImportMeta } from './index.1_nbEjJY.js';
import { performance as performance$1 } from 'node:perf_hooks';
import { s as startCoverageInsideWorker, a as stopCoverageInsideWorker } from './coverage.BtDxDqSU.js';
import { i as index, g as globalExpect, v as vi } from './index.OVGXnVRj.js';
import { c as closeInspector } from './inspector.CvyFGlXm.js';
import { s as startTests, p as publicCollect } from './run.CQOUYP-x.js';
import { createRequire } from 'node:module';
import timers from 'node:timers';
import timersPromises from 'node:timers/promises';
import util from 'node:util';
import { K as KNOWN_ASSET_TYPES } from './pathe.M-eThtNZ.Clq7AXhv.js';
import { s as setupCommonEnv, b as setupEnv } from './setup-common.D5-2VgqG.js';
import { g as getWorkerState, r as resetModules, p as provideWorkerState, a as getSafeWorkerState } from './utils.DYj33du9.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.CHi0lDgQ.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);
})
]);
const offCancel = workerState.onCancel((reason) => {
closeInspector(config);
testRunner.cancel?.(reason);
});
workerState.durations.prepare = performance$1.now() - workerState.durations.prepare;
try {
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 } }, () => publicCollect([file], testRunner));
// reset after tests, because user might call `vi.setConfig` in setupFile
vi.resetConfig();
// mocks should not affect different files
vi.restoreAllMocks();
}
});
} finally {
offCancel();
}
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;
const state = () => getSafeWorkerState() || options.state;
process.exit = (code = process.exitCode || 0) => {
const filepath = state().filepath;
throw new Error(`process.exit unexpectedly called with "${code}"${filepath ? ` (test file: ${filepath})` : ""}`);
};
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.E7jPi2fl.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
});
}
// imported lazily (pulls in `local-pkg`) so it stays out of the default
// worker startup graph; only the `viteModuleRunner: false` path needs it
const { NativeModuleRunner } = await import('./nativeModuleRunner.BOvBCgI_.js').then(function (n) { return n.n; });
_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.cLbCmZDO.js');
await setupNodeLoaderHooks(context);
}
const startTime = performance.now();
const { environment: { name: environmentName, options: environmentOptions }, rpc, config } = context;
setupEnv(config.env, context.metaEnv);
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);
// we could load @vite/env, but it would take ~8ms, while this takes ~0,02ms
if (state.config.serializedDefines) try {
runInThisContext(`(() =>{\n${state.config.serializedDefines}})()`, {
lineOffset: 1,
filename: "virtual:load-defines.js"
});
} catch (error) {
throw new Error(`Failed to load custom "defines": ${error.message}`);
}
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 };
+64
View File
@@ -0,0 +1,64 @@
import { F as FileSpecification } from './config.d.CU_b-wJj.js';
import { O as OTELCarrier } from './rpc.d.DA9Utv4e.js';
import { T as TestExecutionMethod } from './worker.d.MLmnzOJE.js';
interface ModuleDefinitionLocation {
line: number;
column: number;
}
interface SourceModuleLocations {
modules: ModuleDefinitionDiagnostic[];
untracked: ModuleDefinitionDiagnostic[];
}
interface ModuleDefinitionDiagnostic {
start: ModuleDefinitionLocation;
end: ModuleDefinitionLocation;
startIndex: number;
endIndex: number;
rawUrl: string;
resolvedUrl: string;
resolvedId: string;
}
interface ModuleDefinitionDurationsDiagnostic extends ModuleDefinitionDiagnostic {
selfTime: number;
totalTime: number;
transformTime?: number;
external?: boolean;
importer?: string;
}
interface UntrackedModuleDefinitionDiagnostic {
url: string;
resolvedId: string;
resolvedUrl: string;
selfTime: number;
totalTime: number;
transformTime?: number;
external?: boolean;
importer?: string;
}
interface SourceModuleDiagnostic {
modules: ModuleDefinitionDurationsDiagnostic[];
untrackedModules: UntrackedModuleDefinitionDiagnostic[];
}
type SerializedTestSpecification = [project: {
name: string | undefined;
root: string;
}, file: string, options: {
pool: string;
testLines?: number[] | undefined;
testIds?: string[] | undefined;
testNamePattern?: RegExp | undefined;
testTagsFilter?: string[] | undefined;
}];
interface BrowserTesterOptions {
method: TestExecutionMethod;
files: FileSpecification[];
providedContext: string;
otelCarrier?: OTELCarrier;
concurrencyId: number;
workerId: number;
}
export type { BrowserTesterOptions as B, ModuleDefinitionDurationsDiagnostic as M, SerializedTestSpecification as S, UntrackedModuleDefinitionDiagnostic as U, ModuleDefinitionDiagnostic as a, ModuleDefinitionLocation as b, SourceModuleDiagnostic as c, SourceModuleLocations as d };
File diff suppressed because it is too large. Load diff
+528
View File
@@ -0,0 +1,528 @@
import { mkdirSync, writeFileSync } from 'node:fs';
import { C as CoverageProviderMap } from './coverage.BcXK1DsQ.js';
import { L as Logger, k as VitestPackageInstaller, P as PluginHarness, w as resolveConfig, V as Vitest, A as stdout, y as prompt, g as FilesNotFoundError, G as GitNotFoundError, I as IncludeTaskLocationDisabledError, C as RangeLocationFilterProvidedError, E as LocationFilterFileNotFoundError } from './index.B89dZ0-N.js';
import readline from 'node:readline';
import { y } from './tinyrainbow.Ht9iggcq.js';
import { i as isWindows } from './env.DzFJjrmK.js';
import { stripVTControlCharacters } from 'node:util';
import { e as createDefer, a as relative, r as resolve, d as dirname, i as isAbsolute } from './pathe.M-eThtNZ.Clq7AXhv.js';
async function createVitest(modeOrOptions, optionsOrViteOverrides = {}, viteOverridesOrVitestOptions = {}, maybeVitestOptions = {}) {
let options;
let viteOverrides;
let vitestOptions;
if (typeof modeOrOptions === "string") {
options = optionsOrViteOverrides;
viteOverrides = viteOverridesOrVitestOptions;
vitestOptions = maybeVitestOptions;
} else {
options = modeOrOptions;
viteOverrides = optionsOrViteOverrides;
vitestOptions = viteOverridesOrVitestOptions;
}
const logger = new Logger(vitestOptions.stdout, vitestOptions.stderr);
const packageInstaller = vitestOptions.packageInstaller ?? new VitestPackageInstaller();
const pluginHarness = new PluginHarness(logger, packageInstaller);
const config = await resolveConfig(options, viteOverrides, pluginHarness);
const vitest = new Vitest(pluginHarness, config);
try {
await vitest._start(config);
if (vitest.config.api.port && vitest.config.ui && vitest.config.open) vitest.vite.openBrowser();
return vitest;
}
// Vitest can fail at any point during setup or inside a custom plugin.
// Make sure everything is properly closed (like the logger).
catch (error) {
await vitest.close();
throw error;
}
}
const MAX_RESULT_COUNT = 10;
const SELECTION_MAX_INDEX = 7;
const ESC = "\x1B[";
class WatchFilter {
filterRL;
currentKeyword = void 0;
message;
results = [];
selectionIndex = -1;
onKeyPress;
stdin;
stdout;
constructor(message, stdin = process.stdin, stdout$1 = stdout()) {
this.message = message;
this.stdin = stdin;
this.stdout = stdout$1;
this.filterRL = readline.createInterface({
input: this.stdin,
escapeCodeTimeout: 50
});
readline.emitKeypressEvents(this.stdin, this.filterRL);
if (this.stdin.isTTY) this.stdin.setRawMode(true);
}
async filter(filterFunc) {
this.write(this.promptLine());
const resultPromise = createDefer();
this.onKeyPress = this.filterHandler(filterFunc, (result) => {
resultPromise.resolve(result);
});
this.stdin.on("keypress", this.onKeyPress);
try {
return await resultPromise;
} finally {
this.close();
}
}
filterHandler(filterFunc, onSubmit) {
return async (str, key) => {
switch (true) {
case key.sequence === "":
if (this.currentKeyword && this.currentKeyword?.length > 1) this.currentKeyword = this.currentKeyword?.slice(0, -1);
else this.currentKeyword = void 0;
break;
case key?.ctrl && key?.name === "c":
case key?.name === "escape":
this.write(`${ESC}1G${ESC}0J`);
onSubmit(void 0);
return;
case key?.name === "enter":
case key?.name === "return": {
const selection = this.results[this.selectionIndex];
onSubmit((typeof selection === "string" ? selection : selection?.key) || this.currentKeyword || "");
this.currentKeyword = void 0;
break;
}
case key?.name === "up":
if (this.selectionIndex && this.selectionIndex > 0) this.selectionIndex--;
else this.selectionIndex = -1;
break;
case key?.name === "down":
if (this.selectionIndex < this.results.length - 1) this.selectionIndex++;
else if (this.selectionIndex >= this.results.length - 1) this.selectionIndex = this.results.length - 1;
break;
case !key?.ctrl && !key?.meta: if (this.currentKeyword === void 0) this.currentKeyword = str;
else this.currentKeyword += str || "";
}
if (this.currentKeyword) this.results = await filterFunc(this.currentKeyword);
this.render();
};
}
render() {
let printStr = this.promptLine();
if (!this.currentKeyword) printStr += "\nPlease input filter pattern";
else if (this.currentKeyword && this.results.length === 0) printStr += "\nPattern matches no results";
else {
const resultCountLine = this.results.length === 1 ? `Pattern matches ${this.results.length} result` : `Pattern matches ${this.results.length} results`;
let resultBody = "";
if (this.results.length > MAX_RESULT_COUNT) {
const offset = this.selectionIndex > SELECTION_MAX_INDEX ? this.selectionIndex - SELECTION_MAX_INDEX : 0;
const displayResults = this.results.slice(offset, MAX_RESULT_COUNT + offset);
const remainingResultCount = this.results.length - offset - displayResults.length;
resultBody = `${displayResults.map((result, index) => index + offset === this.selectionIndex ? y.green(` › ${result}`) : y.dim(` › ${result}`)).join("\n")}`;
if (remainingResultCount > 0) resultBody += `
${y.dim(` ...and ${remainingResultCount} more ${remainingResultCount === 1 ? "result" : "results"}`)}`;
} else resultBody = this.results.map((result, index) => index === this.selectionIndex ? y.green(` › ${result}`) : y.dim(` › ${result}`)).join("\n");
printStr += `\n${resultCountLine}\n${resultBody}`;
}
this.eraseAndPrint(printStr);
this.restoreCursor();
}
keywordOffset() {
return `? ${this.message} › `.length + 1;
}
promptLine() {
return `${y.cyan("?")} ${y.bold(this.message)} › ${this.currentKeyword || ""}`;
}
eraseAndPrint(str) {
let rows = 0;
const lines = str.split(/\r?\n/);
for (const line of lines) {
const columns = "columns" in this.stdout ? this.stdout.columns : 80;
// We have to take care of screen width in case of long lines
rows += 1 + Math.floor(Math.max(stripVTControlCharacters(line).length - 1, 0) / columns);
}
this.write(`${ESC}1G`);
this.write(`${ESC}J`);
this.write(str);
this.write(`${ESC}${rows - 1}A`);
}
close() {
this.filterRL.close();
if (this.onKeyPress) this.stdin.removeListener("keypress", this.onKeyPress);
if (this.stdin.isTTY) this.stdin.setRawMode(false);
}
restoreCursor() {
const cursorPos = this.keywordOffset() + (this.currentKeyword?.length || 0);
this.write(`${ESC}${cursorPos}G`);
}
write(data) {
this.stdout.write(data);
}
getLastResults() {
return this.results.map((r) => typeof r === "string" ? r : r.toString());
}
}
const keys = [
[["a", "return"], "rerun all tests"],
["r", "rerun current pattern tests"],
["f", "rerun only failed tests"],
["u", "update snapshot"],
["p", "filter by a filename"],
["t", "filter by a test name regex pattern"],
["w", "filter by a project name"],
["q", "quit"]
];
const cancelKeys = [
"space",
"c",
"h",
...keys.map((key) => key[0]).flat()
];
function printShortcutsHelp() {
stdout().write(`
${y.bold(" Watch Usage")}
${keys.map((i) => y.dim(" press ") + y.reset([i[0]].flat().map(y.bold).join(", ")) + y.dim(` to ${i[1]}`)).join("\n")}
`);
}
function* traverseFilteredTestNames(parentName, filter, t) {
if (t.type === "test") {
if (t.name.match(filter)) {
const displayName = `${parentName} > ${t.name}`;
yield {
key: t.name,
toString: () => displayName
};
}
} else {
parentName = parentName.length ? `${parentName} > ${t.name}` : t.name;
for (const task of t.tasks) yield* traverseFilteredTestNames(parentName, filter, task);
}
}
function* getFilteredTestNames(pattern, suite) {
try {
const reg = new RegExp(pattern);
// TODO: we cannot run tests per workspace yet: filtering files
const files = /* @__PURE__ */ new Set();
for (const file of suite) if (!files.has(file.name)) {
files.add(file.name);
yield* traverseFilteredTestNames("", reg, file);
}
} catch {}
}
function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
let latestFilename = "";
async function _keypressHandler(str, key) {
// Cancel run and exit when ctrl-c or esc is pressed.
// If cancelling takes long and key is pressed multiple times, exit forcefully.
if (str === "" || str === "\x1B" || key && key.ctrl && key.name === "c") {
if (!ctx.isCancelling) {
ctx.logger.log(y.red("Cancelling test run. Press CTRL+c again to exit forcefully.\n"));
process.exitCode = 130;
// Unregister raw mode so that second CTRL+c is handled by Node.js as SIGINT
off();
await ctx.cancelCurrentRun("keyboard-input");
}
return ctx.exit(true);
}
// window not support suspend
if (!isWindows && key && key.ctrl && key.name === "z") {
process.kill(process.ppid, "SIGTSTP");
process.kill(process.pid, "SIGTSTP");
return;
}
const name = key?.name;
if (ctx.runningPromise) {
if (cancelKeys.includes(name)) await ctx.cancelCurrentRun("keyboard-input");
return;
}
// quit
if (name === "q") return ctx.exit(true);
// help
if (name === "h") return printShortcutsHelp();
// update snapshot
if (name === "u") return ctx.updateSnapshot();
// rerun all tests
if (name === "a" || name === "return") {
const files = await ctx._globTestFilepaths();
return ctx.changeNamePattern("", files, "rerun all tests");
}
// rerun current pattern tests
if (name === "r") return ctx.rerunFiles();
// rerun only failed tests
if (name === "f") return ctx.rerunFailed();
// change project filter
if (name === "w") return inputProjectName();
// change testNamePattern
if (name === "t") return inputNamePattern();
// change fileNamePattern
if (name === "p") return inputFilePattern();
}
async function keypressHandler(str, key) {
await _keypressHandler(str, key);
}
async function inputNamePattern() {
off();
const filter = await new WatchFilter("Input test name pattern (RegExp)", stdin, stdout).filter((str) => {
return [...getFilteredTestNames(str, ctx.state.getFiles())];
});
on();
if (typeof filter === "undefined") return;
const files = ctx.state.getFilepaths();
// if running in standalone mode, Vitest instance doesn't know about any test file
const cliFiles = ctx.config.standalone && !files.length ? await ctx._globTestFilepaths() : void 0;
await ctx.changeNamePattern(filter?.trim() || "", cliFiles, "change pattern");
}
async function inputProjectName() {
off();
const { filter = "" } = await prompt([{
name: "filter",
type: "text",
message: "Input a single project name",
initial: ctx.config.project[0] || ""
}]);
on();
await ctx.changeProjectName(filter.trim());
}
async function inputFilePattern() {
off();
const watchFilter = new WatchFilter("Input filename pattern", stdin, stdout);
const filter = await watchFilter.filter(async (str) => {
return (await ctx.globTestSpecifications([str])).map((specification) => relative(ctx.config.root, specification.moduleId)).filter((file, index, all) => all.indexOf(file) === index);
});
on();
if (typeof filter === "undefined") return;
latestFilename = filter?.trim() || "";
const lastResults = watchFilter.getLastResults();
await ctx.changeFilenamePattern(latestFilename, filter && lastResults.length ? lastResults.map((i) => resolve(ctx.config.root, i)) : void 0);
}
let rl;
function on() {
off();
rl = readline.createInterface({
input: stdin,
escapeCodeTimeout: 50
});
readline.emitKeypressEvents(stdin, rl);
if (stdin.isTTY) stdin.setRawMode(true);
stdin.on("keypress", keypressHandler);
}
function off() {
rl?.close();
rl = void 0;
stdin.removeListener("keypress", keypressHandler);
if (stdin.isTTY) stdin.setRawMode(false);
}
on();
return function cleanup() {
off();
};
}
async function startVitest(modeOrCliFilters, cliFiltersOrOptions, optionsOrViteOverrides, viteOverridesOrVitestOptions, maybeVitestOptions) {
let cliFilters;
let options;
let viteOverrides;
let vitestOptions;
if (typeof modeOrCliFilters === "string") {
cliFilters = cliFiltersOrOptions ?? [];
options = optionsOrViteOverrides ?? {};
viteOverrides = viteOverridesOrVitestOptions;
vitestOptions = maybeVitestOptions;
} else {
cliFilters = modeOrCliFilters ?? [];
options = cliFiltersOrOptions ?? {};
viteOverrides = optionsOrViteOverrides;
vitestOptions = viteOverridesOrVitestOptions;
}
const root = resolve(options.root || process.cwd());
const ctx = await prepareVitest(options, viteOverrides, vitestOptions, cliFilters);
if (ctx._coverageOptions.enabled) {
const provider = ctx._coverageOptions.provider || "v8";
const requiredPackages = CoverageProviderMap[provider];
if (requiredPackages) {
if (!await ctx.packageInstaller.ensureInstalled(requiredPackages, root, ctx.version)) {
process.exitCode = 1;
return ctx;
}
}
}
const stdin = vitestOptions?.stdin || process.stdin;
const stdout = vitestOptions?.stdout || process.stdout;
let stdinCleanup;
if (stdin.isTTY && ctx.config.watch) stdinCleanup = registerConsoleShortcuts(ctx, stdin, stdout);
ctx.onAfterSetServer(async () => {
if (ctx.closingPromise) return;
try {
if (ctx.config.standalone) await ctx.standalone();
else await ctx.start(cliFilters);
} catch (error) {
reportStartError(ctx, error);
}
});
try {
if (ctx.config.listTags) await ctx.listTags();
else if (ctx.config.clearCache) await ctx.clearCache();
else if (ctx.config.mergeReports) await ctx.mergeReports();
else if (ctx.config.standalone) await ctx.standalone();
else await ctx.start(cliFilters);
return ctx;
} catch (e) {
reportStartError(ctx, e);
return ctx;
} finally {
if (!ctx?.shouldKeepServer()) {
stdinCleanup?.();
await ctx.close();
}
}
}
function reportStartError(ctx, error) {
if (error instanceof FilesNotFoundError) return;
if (error instanceof GitNotFoundError) {
ctx.logger.error(error.message);
return;
}
if (error instanceof IncludeTaskLocationDisabledError || error instanceof RangeLocationFilterProvidedError || error instanceof LocationFilterFileNotFoundError) {
ctx.logger.printError(error, { verbose: false });
return;
}
process.exitCode = 1;
ctx.logger.printError(error, {
fullStack: true,
type: "Unhandled Error"
});
ctx.logger.error("\n\n");
}
async function prepareVitest(modeOrOptions, optionsOrViteOverrides, viteOverridesOrVitestOptions, vitestOptionsOrCliFilters, maybeCliFilters) {
let options;
let viteOverrides;
let vitestOptions;
let cliFilters;
if (typeof modeOrOptions === "string") {
options = optionsOrViteOverrides ?? {};
viteOverrides = viteOverridesOrVitestOptions;
vitestOptions = vitestOptionsOrCliFilters;
cliFilters = maybeCliFilters;
} else {
options = modeOrOptions ?? {};
viteOverrides = optionsOrViteOverrides;
vitestOptions = viteOverridesOrVitestOptions;
cliFilters = vitestOptionsOrCliFilters;
}
process.env.TEST = "true";
process.env.VITEST = "true";
process.env.NODE_ENV ??= "test";
if (options.run) options.watch = false;
if (options.standalone && (cliFilters?.length || 0) > 0) options.standalone = false;
// this shouldn't affect _application root_ that can be changed inside config
const root = resolve(options.root || process.cwd());
const ctx = await createVitest(options, viteOverrides, vitestOptions);
const environmentPackage = getEnvPackageName(ctx.config.environment);
if (environmentPackage && !await ctx.packageInstaller.ensureInstalled(environmentPackage, root)) {
process.exitCode = 1;
return ctx;
}
return ctx;
}
function processCollected(ctx, files, options) {
let errorsPrinted = false;
forEachSuite(files, (suite) => {
suite.errors().forEach((error) => {
errorsPrinted = true;
ctx.logger.printError(error, { project: suite.project });
});
});
if (errorsPrinted) return;
if (typeof options.json !== "undefined") return processJsonOutput(files, options);
return formatCollectedAsString(files).forEach((test) => console.log(test));
}
function outputFileList(files, options) {
if (typeof options.json !== "undefined") return outputJsonFileList(files, options);
formatFilesAsString(files, options).map((file) => console.log(file));
}
function outputJsonFileList(files, options) {
if (typeof options.json === "boolean") return console.log(JSON.stringify(formatFilesAsJSON(files), null, 2));
if (typeof options.json === "string") {
const jsonPath = resolve(options.root || process.cwd(), options.json);
mkdirSync(dirname(jsonPath), { recursive: true });
writeFileSync(jsonPath, JSON.stringify(formatFilesAsJSON(files), null, 2));
}
}
function formatFilesAsJSON(files) {
return files.map((file) => {
const result = { file: file.moduleId };
if (file.project.name) result.projectName = file.project.name;
return result;
});
}
function formatFilesAsString(files, options) {
return files.map((file) => {
let name = relative(options.root || process.cwd(), file.moduleId);
if (file.project.name) name = `[${file.project.name}] ${name}`;
return name;
});
}
function processJsonOutput(files, options) {
if (typeof options.json === "boolean") return console.log(JSON.stringify(formatCollectedAsJSON(files), null, 2));
if (typeof options.json === "string") {
const jsonPath = resolve(options.root || process.cwd(), options.json);
mkdirSync(dirname(jsonPath), { recursive: true });
writeFileSync(jsonPath, JSON.stringify(formatCollectedAsJSON(files), null, 2));
}
}
function forEachSuite(modules, callback) {
modules.forEach((testModule) => {
callback(testModule);
for (const suite of testModule.children.allSuites()) callback(suite);
});
}
function formatCollectedAsJSON(files) {
const results = [];
files.forEach((file) => {
for (const test of file.children.allTests()) {
if (test.result().state === "skipped") continue;
const result = {
name: test.fullName,
file: test.module.moduleId
};
if (test.project.name) result.projectName = test.project.name;
if (test.location) result.location = test.location;
results.push(result);
}
});
return results;
}
function formatCollectedAsString(testModules) {
const results = [];
testModules.forEach((testModule) => {
for (const test of testModule.children.allTests()) {
if (test.result().state === "skipped") continue;
const fullName = `${test.module.task.name} > ${test.fullName}`;
results.push((test.project.name ? `[${test.project.name}] ` : "") + fullName);
}
});
return results;
}
const envPackageNames = {
"jsdom": "jsdom",
"happy-dom": "happy-dom",
"edge-runtime": "@edge-runtime/vm"
};
function getEnvPackageName(env) {
if (env === "node") return null;
if (env in envPackageNames) return envPackageNames[env];
if (env[0] === "." || isAbsolute(env)) return null;
return `vitest-environment-${env}`;
}
var cliApi = /*#__PURE__*/Object.freeze({
__proto__: null,
outputFileList: outputFileList,
prepareVitest: prepareVitest,
processCollected: processCollected,
startVitest: startVitest
});
export { cliApi as a, createVitest as c, registerConsoleShortcuts as r, startVitest as s };
File diff suppressed because it is too large. Load diff
+149
View File
@@ -0,0 +1,149 @@
import { Console } from 'node:console';
import { relative } from 'node:path';
import { Writable } from 'node:stream';
import { a as getSafeTimers } from './source-map.Dx3YPjIe.js';
import { y } from './tinyrainbow.Ht9iggcq.js';
import { g as getWorkerState } from './utils.DYj33du9.js';
import './pathe.M-eThtNZ.Clq7AXhv.js';
const RealDate = globalThis.Date;
const UNKNOWN_TEST_ID = "__vitest__unknown_test__";
function getTaskIdByStack(root) {
const stack = (/* @__PURE__ */ new Error("STACK_TRACE_ERROR")).stack?.split("\n");
if (!stack) return UNKNOWN_TEST_ID;
const index = stack.findIndex((line) => line.includes("at Console.value"));
const line = index === -1 ? null : stack[index + 2];
if (!line) return UNKNOWN_TEST_ID;
const filepath = line.match(/at\s(.*)\s?/)?.[1];
if (filepath) return relative(root, filepath);
return UNKNOWN_TEST_ID;
}
function createCustomConsole(defaultState) {
const stdoutBuffer = /* @__PURE__ */ new Map();
const stderrBuffer = /* @__PURE__ */ new Map();
const timers = /* @__PURE__ */ new Map();
const { queueMicrotask } = getSafeTimers();
function queueCancelableMicrotask(callback) {
let canceled = false;
queueMicrotask(() => {
if (!canceled) callback();
});
return () => {
canceled = true;
};
}
const state = () => defaultState || getWorkerState();
// group sync console.log calls with micro task
function schedule(taskId) {
const timer = timers.get(taskId);
const { stdoutTime, stderrTime } = timer;
timer.cancel?.();
timer.cancel = queueCancelableMicrotask(() => {
if (stderrTime < stdoutTime) {
sendStderr(taskId);
sendStdout(taskId);
} else {
sendStdout(taskId);
sendStderr(taskId);
}
});
}
function sendStdout(taskId) {
sendBuffer("stdout", taskId);
}
function sendStderr(taskId) {
sendBuffer("stderr", taskId);
}
function sendBuffer(type, taskId) {
const buffers = type === "stdout" ? stdoutBuffer : stderrBuffer;
const buffer = buffers.get(taskId);
if (!buffer) return;
if (state().config.printConsoleTrace) buffer.forEach(([buffer, origin]) => {
sendLog(type, taskId, String(buffer), buffer.length, origin);
});
else sendLog(type, taskId, buffer.map((i) => String(i[0])).join(""), buffer.length);
const timer = timers.get(taskId);
buffers.delete(taskId);
if (type === "stderr") timer.stderrTime = 0;
else timer.stdoutTime = 0;
}
function sendLog(type, taskId, content, size, origin) {
const timer = timers.get(taskId);
const time = type === "stderr" ? timer.stderrTime : timer.stdoutTime;
state().rpc.onUserConsoleLog({
type,
content: content || "<empty line>",
taskId,
time: time || RealDate.now(),
size,
origin
});
}
const stdout = new Writable({ write(data, encoding, callback) {
const s = state();
const id = s?.current?.id || s?.current?.suite?.id || s.current?.file.id || getTaskIdByStack(s.config.root);
let timer = timers.get(id);
if (timer) timer.stdoutTime = timer.stdoutTime || RealDate.now();
else {
timer = {
stdoutTime: RealDate.now(),
stderrTime: 0
};
timers.set(id, timer);
}
let buffer = stdoutBuffer.get(id);
if (!buffer) {
buffer = [];
stdoutBuffer.set(id, buffer);
}
if (state().config.printConsoleTrace) {
const limit = Error.stackTraceLimit;
Error.stackTraceLimit = limit + 6;
const trace = (/* @__PURE__ */ new Error("STACK_TRACE")).stack?.split("\n").slice(7).join("\n");
Error.stackTraceLimit = limit;
buffer.push([data, trace]);
} else buffer.push([data, void 0]);
schedule(id);
callback();
} });
const stderr = new Writable({ write(data, encoding, callback) {
const s = state();
const id = s?.current?.id || s?.current?.suite?.id || s.current?.file.id || getTaskIdByStack(s.config.root);
let timer = timers.get(id);
if (timer) timer.stderrTime = timer.stderrTime || RealDate.now();
else {
timer = {
stderrTime: RealDate.now(),
stdoutTime: 0
};
timers.set(id, timer);
}
let buffer = stderrBuffer.get(id);
if (!buffer) {
buffer = [];
stderrBuffer.set(id, buffer);
}
if (state().config.printConsoleTrace) {
const limit = Error.stackTraceLimit;
Error.stackTraceLimit = limit + 6;
const stack = (/* @__PURE__ */ new Error("STACK_TRACE")).stack?.split("\n");
Error.stackTraceLimit = limit;
if (stack?.some((line) => line.includes("at Console.trace"))) buffer.push([data, void 0]);
else {
const trace = stack?.slice(7).join("\n");
Error.stackTraceLimit = limit;
buffer.push([data, trace]);
}
} else buffer.push([data, void 0]);
schedule(id);
callback();
} });
return new Console({
stdout,
stderr,
colorMode: y.isColorSupported,
groupIndentation: 2
});
}
export { createCustomConsole };
+38
View File
@@ -0,0 +1,38 @@
// if changed, update also jsdocs and docs
const defaultPort = 51204;
const defaultBrowserPort = 63315;
const defaultInspectPort = 9229;
const API_PATH = "/__vitest_api__";
const CONFIG_NAMES = ["vitest.config", "vite.config"];
const CONFIG_EXTENSIONS = [
".ts",
".mts",
".cts",
".js",
".mjs",
".cjs"
];
const configFiles = CONFIG_NAMES.flatMap((name) => CONFIG_EXTENSIONS.map((ext) => name + ext));
const globalApis = [
"suite",
"test",
"describe",
"it",
"chai",
"expect",
"assert",
"expectTypeOf",
"assertType",
"vitest",
"vi",
"beforeAll",
"afterAll",
"beforeEach",
"afterEach",
"onTestFinished",
"onTestFailed",
"aroundEach",
"aroundAll"
];
export { API_PATH as A, defaultPort as a, defaultInspectPort as b, configFiles as c, defaultBrowserPort as d, globalApis as g };
+33
View File
@@ -0,0 +1,33 @@
import { r as resolve } from './pathe.M-eThtNZ.Clq7AXhv.js';
function getCoverageFilesDirectory(reportsDirectory, shard) {
return resolve(reportsDirectory, `.tmp${shard ? `-${shard.index}-${shard.count}` : ""}`);
}
const CoverageProviderMap = {
v8: "@vitest/coverage-v8",
istanbul: "@vitest/coverage-istanbul"
};
async function resolveCoverageProviderModule(options, loader) {
if (!options?.enabled || !options.provider) return null;
const provider = options.provider;
if (provider === "v8" || provider === "istanbul") {
let builtInModule = CoverageProviderMap[provider];
if (loader.isBrowser) builtInModule += "/browser";
const { default: coverageModule } = loader.isBrowser ? await loader.import(builtInModule) : await import(
/* @vite-ignore */
builtInModule
);
if (!coverageModule) throw new Error(`Failed to load ${CoverageProviderMap[provider]}. Default export is missing.`);
return coverageModule;
}
let customProviderModule;
try {
customProviderModule = await loader.import(options.customProviderModule);
} catch (error) {
throw new Error(`Failed to load custom CoverageProviderModule from ${options.customProviderModule}`, { cause: error });
}
if (customProviderModule.default == null) throw new Error(`Custom CoverageProviderModule loaded from ${options.customProviderModule} was not the default export`);
return customProviderModule.default;
}
export { CoverageProviderMap as C, getCoverageFilesDirectory as g, resolveCoverageProviderModule as r };
+26
View File
@@ -0,0 +1,26 @@
import { r as resolveCoverageProviderModule } from './coverage.BcXK1DsQ.js';
async function startCoverageInsideWorker(options, loader, runtimeOptions) {
const coverageModule = await resolveCoverageProviderModule(options, loader);
if (coverageModule) return coverageModule.startCoverage?.({
...runtimeOptions,
autoAttachSubprocess: options.autoAttachSubprocess,
reportsDirectory: options.reportsDirectory
});
return null;
}
async function takeCoverageInsideWorker(options, loader) {
const coverageModule = await resolveCoverageProviderModule(options, loader);
if (coverageModule) return coverageModule.takeCoverage?.({
moduleExecutionInfo: loader.moduleExecutionInfo,
coverageFilesDirectory: options.coverageFilesDirectory
});
return null;
}
async function stopCoverageInsideWorker(options, loader, runtimeOptions) {
const coverageModule = await resolveCoverageProviderModule(options, loader);
if (coverageModule) return coverageModule.stopCoverage?.(runtimeOptions);
return null;
}
export { stopCoverageInsideWorker as a, startCoverageInsideWorker as s, takeCoverageInsideWorker as t };
+751
View File
@@ -0,0 +1,751 @@
import { existsSync, writeFileSync, readFileSync } from 'node:fs';
import { mkdir, writeFile } from 'node:fs/promises';
import { resolve, dirname, relative } from 'node:path';
import { detectPackageManager, installPackage } from './index.DXQx-kDM.js';
import { y as prompt, z as findConfigFile } from './index.B89dZ0-N.js';
import { x } from 'tinyexec';
import { y } from './tinyrainbow.Ht9iggcq.js';
import 'node:process';
import 'node:os';
import './pathe.M-eThtNZ.Clq7AXhv.js';
import './source-map.Dx3YPjIe.js';
import './cac.D805sv8h.js';
import 'events';
import './env.DzFJjrmK.js';
import 'std-env';
import './constants.-juJ8b_4.js';
import 'node:assert';
import 'node:perf_hooks';
import './index.DmDMHCg8.js';
import 'https';
import 'http';
import 'net';
import 'tls';
import 'crypto';
import 'stream';
import 'url';
import 'zlib';
import 'buffer';
import 'util';
import 'vite';
import 'node:crypto';
import '../path.js';
import 'node:url';
import './run.CQOUYP-x.js';
import './index.M2dsQ_UQ.js';
import './display.pkpxlVcY.js';
import '../task-utils.js';
import './nativeModuleRunner.BOvBCgI_.js';
import 'node:module';
import 'node:v8';
import 'node:util';
import 'vite/module-runner';
import '../traces.js';
import 'obug';
import 'tinyglobby';
import './defaults.D2ip7f-X.js';
import 'magic-string';
import '@vitest/mocker/node';
import 'node:console';
import 'node:stream';
import './offset.Dy-5Fdfn.js';
import './utils.CtqmNjXD.js';
import '#module-evaluator';
import 'picomatch';
import './coverage.BcXK1DsQ.js';
import './resolver.CBCHRZ7j.js';
import 'es-module-lexer';
import 'node:tty';
import 'node:events';
import './modules.BJuCwlRJ.js';
import 'node:child_process';
import 'node:worker_threads';
import 'readline';
const jsxExample = {
name: "HelloWorld.jsx",
js: `
export default function HelloWorld({ name }) {
return (
<div>
<h1>Hello {name}!</h1>
</div>
)
}
`,
ts: `
export default function HelloWorld({ name }: { name: string }) {
return (
<div>
<h1>Hello {name}!</h1>
</div>
)
}
`,
test: `
import { expect, test } from 'vitest'
import { render } from '@testing-library/jsx'
import HelloWorld from './HelloWorld.<EXT>x'
test('renders name', async () => {
const { getByText } = await render(<HelloWorld name="Vitest" />)
await expect.element(getByText('Hello Vitest!')).toBeInTheDocument()
})
`
};
const vueExample = {
name: "HelloWorld.vue",
js: `
<script setup>
defineProps({
name: String
})
<\/script>
<template>
<div>
<h1>Hello {{ name }}!</h1>
</div>
</template>
`,
ts: `
<script setup lang="ts">
defineProps<{
name: string
}>()
<\/script>
<template>
<div>
<h1>Hello {{ name }}!</h1>
</div>
</template>
`,
test: `
import { expect, test } from 'vitest'
import { render } from 'vitest-browser-vue'
import HelloWorld from './HelloWorld.vue'
test('renders name', async () => {
const { getByText } = await render(HelloWorld, {
props: { name: 'Vitest' },
})
await expect.element(getByText('Hello Vitest!')).toBeInTheDocument()
})
`
};
const svelteExample = {
name: "HelloWorld.svelte",
js: `
<script>
export let name
<\/script>
<h1>Hello {name}!</h1>
`,
ts: `
<script lang="ts">
export let name: string
<\/script>
<h1>Hello {name}!</h1>
`,
test: `
import { expect, test } from 'vitest'
import { render } from 'vitest-browser-svelte'
import HelloWorld from './HelloWorld.svelte'
test('renders name', async () => {
const { getByText } = await render(HelloWorld, { name: 'Vitest' })
await expect.element(getByText('Hello Vitest!')).toBeInTheDocument()
})
`
};
const markoExample = {
name: "HelloWorld.marko",
js: `
class {
onCreate() {
this.state = { name: null }
}
}
<h1>Hello \${state.name}!</h1>
`,
ts: `
export interface Input {
name: string
}
<h1>Hello \${input.name}!</h1>
`,
test: `
import { expect, test } from 'vitest'
import { render } from '@marko/testing-library'
import HelloWorld from './HelloWorld.svelte'
test('renders name', async () => {
const { getByText } = await render(HelloWorld, { name: 'Vitest' })
const element = getByText('Hello Vitest!')
expect(element).toBeInTheDocument()
})
`
};
const litExample = {
name: "HelloWorld.js",
js: `
import { html, LitElement } from 'lit'
export class HelloWorld extends LitElement {
static properties = {
name: { type: String },
}
constructor() {
super()
this.name = 'World'
}
render() {
return html\`<h1>Hello \${this.name}!</h1>\`
}
}
customElements.define('hello-world', HelloWorld)
`,
ts: `
import { html, LitElement } from 'lit'
import { customElement, property } from 'lit/decorators.js'
@customElement('hello-world')
export class HelloWorld extends LitElement {
@property({ type: String })
name = 'World'
render() {
return html\`<h1>Hello \${this.name}!</h1>\`
}
}
declare global {
interface HTMLElementTagNameMap {
'hello-world': HelloWorld
}
}
`,
test: `
import { expect, test } from 'vitest'
import { render } from 'vitest-browser-lit'
import { html } from 'lit'
import './HelloWorld.js'
test('renders name', async () => {
const screen = render(html\`<hello-world name="Vitest"></hello-world>\`)
const element = screen.getByText('Hello Vitest!')
await expect.element(element).toBeInTheDocument()
})
`
};
const qwikExample = {
name: "HelloWorld.jsx",
js: `
import { component$ } from '@builder.io/qwik'
export default component$(({ name }) => {
return (
<div>
<h1>Hello {name}!</h1>
</div>
)
})
`,
ts: `
import { component$ } from '@builder.io/qwik'
export default component$(({ name }: { name: string }) => {
return (
<div>
<h1>Hello {name}!</h1>
</div>
)
})
`,
test: `
import { expect, test } from 'vitest'
import { render } from 'vitest-browser-qwik'
import HelloWorld from './HelloWorld.tsx'
test('renders name', async () => {
const { getByText } = render(<HelloWorld name="Vitest" />)
await expect.element(getByText('Hello Vitest!')).toBeInTheDocument()
})
`
};
const preactExample = {
name: "HelloWorld.jsx",
js: `
export default function HelloWorld({ name }) {
return (
<div>
<h1>Hello {name}!</h1>
</div>
)
}
`,
ts: `
export default function HelloWorld({ name }: { name: string }) {
return (
<div>
<h1>Hello {name}!</h1>
</div>
)
}
`,
test: `
import { expect, test } from 'vitest'
import { render } from 'vitest-browser-preact'
import HelloWorld from './HelloWorld.<EXT>x'
test('renders name', async () => {
const { getByText } = render(<HelloWorld name="Vitest" />)
await expect.element(getByText('Hello Vitest!')).toBeInTheDocument()
})
`
};
const vanillaExample = {
name: "HelloWorld.js",
js: `
export default function HelloWorld({ name }) {
const parent = document.createElement('div')
const h1 = document.createElement('h1')
h1.textContent = 'Hello ' + name + '!'
parent.appendChild(h1)
return parent
}
`,
ts: `
export default function HelloWorld({ name }: { name: string }): HTMLDivElement {
const parent = document.createElement('div')
const h1 = document.createElement('h1')
h1.textContent = 'Hello ' + name + '!'
parent.appendChild(h1)
return parent
}
`,
test: `
import { expect, test } from 'vitest'
import { getByText } from '@testing-library/dom'
import HelloWorld from './HelloWorld.js'
test('renders name', () => {
const parent = HelloWorld({ name: 'Vitest' })
document.body.appendChild(parent)
const element = getByText(parent, 'Hello Vitest!')
expect(element).toBeInTheDocument()
})
`
};
function getExampleTest(framework) {
switch (framework) {
case "solid": return {
...jsxExample,
test: jsxExample.test.replace("@testing-library/jsx", `@testing-library/${framework}`)
};
case "preact": return preactExample;
case "react": return {
...jsxExample,
test: jsxExample.test.replace("@testing-library/jsx", `vitest-browser-${framework}`)
};
case "vue": return vueExample;
case "svelte": return svelteExample;
case "lit": return litExample;
case "marko": return markoExample;
case "qwik": return qwikExample;
default: return vanillaExample;
}
}
async function generateExampleFiles(framework, lang) {
const example = getExampleTest(framework);
let fileName = example.name;
const folder = resolve(process.cwd(), "vitest-example");
const fileContent = example[lang];
if (!existsSync(folder)) await mkdir(folder, { recursive: true });
const isJSX = fileName.endsWith(".jsx");
if (isJSX && lang === "ts") fileName = fileName.replace(".jsx", ".tsx");
else if (fileName.endsWith(".js") && lang === "ts") fileName = fileName.replace(".js", ".ts");
example.test = example.test.replace("<EXT>", lang);
const filePath = resolve(folder, fileName);
const testPath = resolve(folder, `HelloWorld.test.${isJSX ? `${lang}x` : lang}`);
writeFileSync(filePath, fileContent.trimStart(), "utf-8");
writeFileSync(testPath, example.test.trimStart(), "utf-8");
return testPath;
}
// eslint-disable-next-line no-console
const log = console.log;
function getProviderOptions() {
return Object.entries({
playwright: "Playwright relies on Chrome DevTools protocol. Read more: https://playwright.dev",
webdriverio: "WebdriverIO uses WebDriver protocol. Read more: https://webdriver.io",
preview: "Preview is useful to quickly run your tests in the browser, but not suitable for CI."
}).map(([provider, description]) => {
return {
title: provider,
description,
value: provider
};
});
}
function getBrowserNames(provider) {
switch (provider) {
case "webdriverio": return [
"chrome",
"firefox",
"edge",
"safari"
];
case "playwright": return [
"chromium",
"firefox",
"webkit"
];
case "preview": return [
"chrome",
"firefox",
"safari"
];
}
}
function getFramework() {
return [
{
title: "vanilla",
value: "vanilla",
description: "No framework, just plain JavaScript or TypeScript."
},
{
title: "vue",
value: "vue",
description: "\"The Progressive JavaScript Framework\""
},
{
title: "svelte",
value: "svelte",
description: "\"Svelte: cybernetically enhanced web apps\""
},
{
title: "react",
value: "react",
description: "\"The library for web and native user interfaces\""
},
{
title: "lit",
value: "lit",
description: "\"A simple library for building fast, lightweight web components.\""
},
{
title: "preact",
value: "preact",
description: "\"Fast 3kB alternative to React with the same modern API\""
},
{
title: "solid",
value: "solid",
description: "\"Simple and performant reactivity for building user interfaces\""
},
{
title: "marko",
value: "marko",
description: "\"A declarative, HTML-based language that makes building web apps fun\""
},
{
title: "qwik",
value: "qwik",
description: "\"Instantly interactive web apps at scale\""
}
];
}
function getFrameworkTestPackage(framework) {
switch (framework) {
case "vanilla": return null;
case "vue": return "vitest-browser-vue";
case "svelte": return "vitest-browser-svelte";
case "react": return "vitest-browser-react";
case "lit": return "vitest-browser-lit";
case "preact": return "vitest-browser-preact";
case "solid": return "@solidjs/testing-library";
case "marko": return "@marko/testing-library";
case "qwik": return "vitest-browser-qwik";
}
throw new Error(`Unsupported framework: ${framework}`);
}
function getFrameworkPluginPackage(framework) {
switch (framework) {
case "vue": return "@vitejs/plugin-vue";
case "svelte": return "@sveltejs/vite-plugin-svelte";
case "react": return "@vitejs/plugin-react";
case "preact": return "@preact/preset-vite";
case "solid": return "vite-plugin-solid";
case "marko": return "@marko/vite";
case "qwik": return "@builder.io/qwik/optimizer";
}
return null;
}
function getLanguageOptions() {
return [{
title: "TypeScript",
description: "Use TypeScript.",
value: "ts"
}, {
title: "JavaScript",
description: "Use plain JavaScript.",
value: "js"
}];
}
async function installPackages(pkgManager, packages) {
if (!packages.length) {
log(y.green("✔"), y.bold("All packages are already installed."));
return;
}
log(y.cyan("◼"), y.bold("Installing packages..."));
log(y.cyan("◼"), packages.join(", "));
log();
await installPackage(packages, {
dev: true,
packageManager: pkgManager ?? void 0
});
}
function readPkgJson(path) {
if (!existsSync(path)) return null;
const content = readFileSync(path, "utf-8");
return JSON.parse(content);
}
function getPossibleDefaults(dependencies) {
return {
lang: "ts",
provider: getPossibleProvider(dependencies),
framework: getPossibleFramework(dependencies)
};
}
function getPossibleFramework(dependencies) {
if (dependencies.vue || dependencies["vue-tsc"] || dependencies["@vue/reactivity"]) return "vue";
if (dependencies.react || dependencies["react-dom"]) return "react";
if (dependencies.svelte || dependencies["@sveltejs/kit"]) return "svelte";
if (dependencies.lit || dependencies["lit-html"]) return "lit";
if (dependencies.preact) return "preact";
if (dependencies["solid-js"] || dependencies["@solidjs/start"]) return "solid";
if (dependencies.marko) return "marko";
if (dependencies["@builder.io/qwik"] || dependencies["@qwik.dev/core"]) return "qwik";
return "vanilla";
}
function getPossibleProvider(dependencies) {
if (dependencies.webdriverio || dependencies["@wdio/cli"] || dependencies["@wdio/config"]) return "webdriverio";
// playwright is the default recommendation
return "playwright";
}
function getProviderDocsLink(provider) {
switch (provider) {
case "playwright": return "https://vitest.dev/config/browser/playwright";
case "webdriverio": return "https://vitest.dev/config/browser/webdriverio";
}
}
function sort(choices, value) {
const index = choices.findIndex((i) => i.value === value);
if (index === -1) return choices;
return [choices.splice(index, 1)[0], ...choices];
}
function fail() {
process.exitCode = 1;
}
function getFrameworkImportInfo(framework) {
switch (framework) {
case "svelte": return {
importName: "svelte",
isNamedExport: true
};
case "qwik": return {
importName: "qwikVite",
isNamedExport: true
};
default: return {
importName: framework,
isNamedExport: false
};
}
}
async function generateFrameworkConfigFile(options) {
const { importName, isNamedExport } = getFrameworkImportInfo(options.framework);
const frameworkImport = isNamedExport ? `import { ${importName} } from '${options.frameworkPlugin}'` : `import ${importName} from '${options.frameworkPlugin}'`;
const configContent = [
`import { defineConfig } from 'vitest/config'`,
`import { ${options.provider} } from '@vitest/browser-${options.provider}'`,
options.frameworkPlugin ? frameworkImport : null,
``,
"export default defineConfig({",
options.frameworkPlugin ? ` plugins: [${importName}()],` : null,
` test: {`,
` browser: {`,
` enabled: true,`,
` provider: ${options.provider}(),`,
options.provider !== "preview" && ` // ${getProviderDocsLink(options.provider)}`,
` instances: [`,
...options.browsers.map((browser) => ` { browser: '${browser}' },`),
` ],`,
` },`,
` },`,
`})`,
""
].filter((t) => typeof t === "string").join("\n");
await writeFile(options.configPath, configContent);
}
async function updatePkgJsonScripts(pkgJsonPath, vitestScript) {
if (!existsSync(pkgJsonPath)) await writeFile(pkgJsonPath, `${JSON.stringify({ scripts: { "test:browser": vitestScript } }, null, 2)}\n`, "utf-8");
else {
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
pkg.scripts = pkg.scripts || {};
pkg.scripts["test:browser"] = vitestScript;
await writeFile(pkgJsonPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf-8");
}
log(y.green("✔"), "Added \"test:browser\" script to your package.json.");
}
function getRunScript(pkgManager) {
switch (pkgManager) {
case "yarn@berry":
case "yarn": return "yarn test:browser";
case "pnpm@6":
case "pnpm": return "pnpm test:browser";
case "bun": return "bun test:browser";
default: return "npm run test:browser";
}
}
function getPlaywrightRunArgs(pkgManager) {
switch (pkgManager) {
case "yarn@berry":
case "yarn": return ["yarn", "exec"];
case "pnpm@6":
case "pnpm": return ["pnpx"];
case "bun": return ["bunx"];
default: return ["npx"];
}
}
async function create() {
log(y.cyan("◼"), "This utility will help you set up a browser testing environment.\n");
const pkgJsonPath = resolve(process.cwd(), "package.json");
const pkg = readPkgJson(pkgJsonPath) || {};
const dependencies = {
...pkg.dependencies,
...pkg.devDependencies
};
const defaults = getPossibleDefaults(dependencies);
const { lang } = await prompt({
type: "select",
name: "lang",
message: "Choose a language for your tests",
choices: sort(getLanguageOptions(), defaults?.lang)
});
if (!lang) return fail();
const { provider } = await prompt({
type: "select",
name: "provider",
message: "Choose a browser provider. Vitest will use its API to control the testing environment",
choices: sort(getProviderOptions(), defaults?.provider)
});
if (!provider) return fail();
const { browsers } = await prompt({
type: "multiselect",
name: "browsers",
message: "Choose a browser",
choices: getBrowserNames(provider).map((browser) => ({
title: browser,
value: browser
}))
});
if (!provider) return fail();
const { framework } = await prompt({
type: "select",
name: "framework",
message: "Choose your framework",
choices: sort(getFramework(), defaults?.framework)
});
if (!framework) return fail();
let installPlaywright = false;
if (provider === "playwright") ({installPlaywright} = await prompt({
type: "confirm",
name: "installPlaywright",
message: `Install Playwright browsers (can be done manually via 'pnpm exec playwright install')?`
}));
if (installPlaywright == null) return fail();
const dependenciesToInstall = [`@vitest/browser-${provider}`];
const frameworkPackage = getFrameworkTestPackage(framework);
if (frameworkPackage) dependenciesToInstall.push(frameworkPackage);
const frameworkPlugin = getFrameworkPluginPackage(framework);
if (frameworkPlugin) dependenciesToInstall.push(frameworkPlugin);
const pkgManager = await detectPackageManager();
log();
await installPackages(pkgManager, dependenciesToInstall.filter((pkg) => !dependencies[pkg]));
const rootConfig = findConfigFile(process.cwd());
let scriptCommand = "vitest";
log();
if (rootConfig) {
const configPath = resolve(dirname(rootConfig), `vitest.browser.config.${lang}`);
scriptCommand = `vitest --config=${relative(process.cwd(), configPath)}`;
await generateFrameworkConfigFile({
configPath,
framework,
frameworkPlugin,
provider,
browsers
});
log(
y.green("✔"),
"Created a new config file for browser tests:",
y.bold(relative(process.cwd(), configPath)),
// TODO: Can we modify the config ourselves?
"\nSince you already have a Vitest config file, it is recommended to copy the contents of the new file ",
"into your existing config located at ",
y.bold(relative(process.cwd(), rootConfig))
);
} else {
const configPath = resolve(process.cwd(), `vitest.config.${lang}`);
await generateFrameworkConfigFile({
configPath,
framework,
frameworkPlugin,
provider,
browsers
});
log(y.green("✔"), "Created a config file for browser tests:", y.bold(relative(process.cwd(), configPath)));
}
log();
await updatePkgJsonScripts(pkgJsonPath, scriptCommand);
if (installPlaywright) {
log();
const [command, ...args] = getPlaywrightRunArgs(pkgManager);
const allArgs = [
...args,
"playwright",
"install",
"--with-deps"
];
log(y.cyan("◼"), `Installing Playwright dependencies with \`${y.bold(command)} ${y.bold(allArgs.join(" "))}\`...`);
log();
await x(command, allArgs, { nodeOptions: { stdio: [
"pipe",
"inherit",
"inherit"
] } });
}
log();
const exampleTestFile = await generateExampleFiles(framework, lang);
log(y.green("✔"), "Created example test file in", y.bold(relative(process.cwd(), exampleTestFile)));
log(y.dim(" You can safely delete this file once you have written your own tests."));
log();
log(y.cyan("◼"), "All done! Run your tests with", y.bold(getRunScript(pkgManager)));
}
export { create };
+90
View File
@@ -0,0 +1,90 @@
import os__default from 'node:os';
import './env.DzFJjrmK.js';
import { isCI, isAgent } from 'std-env';
const defaultInclude = ["**/*.{test,spec}.?(c|m)[jt]s?(x)"];
const defaultExclude = ["**/node_modules/**", "**/.git/**"];
const benchmarkConfigDefaults = {
enabled: false,
include: ["**/*.{bench,benchmark}.?(c|m)[jt]s?(x)"],
exclude: defaultExclude,
includeSource: [],
retainSamples: false,
suppressExportGetterWarnings: false,
// Populated automatically when Vitest clones the parent project; the default
// here applies to the (unused) raw config that's never run as a benchmark.
projectName: ""
};
// These are the generic defaults for coverage. Providers may also set some provider specific defaults.
const coverageConfigDefaults = {
provider: "v8",
enabled: false,
clean: true,
cleanOnRerun: true,
reportsDirectory: "./coverage",
exclude: [],
reportOnFailure: false,
reporter: [
"text",
"html",
"clover",
"json"
],
allowExternal: false,
excludeAfterRemap: false,
processingConcurrency: Math.min(20, os__default.availableParallelism?.() ?? os__default.cpus().length),
ignoreClassMethods: [],
skipFull: false,
watermarks: {
statements: [50, 80],
functions: [50, 80],
branches: [50, 80],
lines: [50, 80]
},
autoAttachSubprocess: false
};
const fakeTimersDefaults = {
loopLimit: 1e4,
shouldClearNativeTimers: true
};
const configDefaults = Object.freeze({
allowOnly: !isCI,
isolate: true,
watch: !isCI && process.stdin.isTTY && !isAgent,
globals: false,
injectCjsGlobals: true,
environment: "node",
clearMocks: true,
restoreMocks: false,
mockReset: false,
unstubGlobals: false,
unstubEnvs: false,
include: defaultInclude,
exclude: defaultExclude,
teardownTimeout: 1e4,
forceRerunTriggers: ["**/package.json", "**/{vitest,vite}.config.*"],
update: false,
reporters: [isAgent ? "minimal" : "default", ...process.env.GITHUB_ACTIONS === "true" ? ["github-actions"] : []],
silent: false,
hideSkippedTests: false,
api: false,
ui: false,
uiBase: "/__vitest__/",
open: !isCI,
css: { include: [] },
coverage: coverageConfigDefaults,
fakeTimers: fakeTimersDefaults,
maxConcurrency: 5,
dangerouslyIgnoreUnhandledErrors: false,
typecheck: {
checker: "tsc",
include: ["**/*.{test,spec}-d.?(c|m)[jt]s?(x)"],
exclude: defaultExclude
},
slowTestThreshold: 300,
taskTitleValueFormatTruncate: 40,
disableConsoleIntercept: false,
detectAsyncLeaks: false
});
export { coverageConfigDefaults as a, defaultInclude as b, configDefaults as c, defaultExclude as d, benchmarkConfigDefaults as e };
+194
View File
@@ -0,0 +1,194 @@
import { f as format$1, p as plugins, c as createDOMElementFilter } from './index.M2dsQ_UQ.js';
const { AsymmetricMatcher, DOMCollection, DOMElement, Immutable, ReactElement, ReactTestComponent } = plugins;
const PLUGINS = [
ReactTestComponent,
ReactElement,
DOMElement,
DOMCollection,
Immutable,
AsymmetricMatcher
];
function stringify(object, maxDepth = 10, { maxLength, filterNode, ...options } = {}) {
const MAX_LENGTH = maxLength ?? 1e4;
let result;
// Convert string selector to filter function
const filterFn = typeof filterNode === "string" ? createNodeFilterFromSelector(filterNode) : filterNode;
const plugins = filterFn ? [
ReactTestComponent,
ReactElement,
createDOMElementFilter(filterFn),
DOMCollection,
Immutable,
AsymmetricMatcher
] : PLUGINS;
try {
result = format$1(object, {
maxDepth,
escapeString: false,
// min: true,
plugins,
...options
});
} catch {
result = format$1(object, {
callToJSON: false,
maxDepth,
escapeString: false,
// min: true,
plugins,
...options
});
}
// Prevents infinite loop https://github.com/vitest-dev/vitest/issues/7249
return result.length >= MAX_LENGTH && maxDepth > 1 ? stringify(object, Math.floor(Math.min(maxDepth, Number.MAX_SAFE_INTEGER) / 2), {
maxLength,
filterNode,
...options
}) : result;
}
function createNodeFilterFromSelector(selector) {
const ELEMENT_NODE = 1;
const COMMENT_NODE = 8;
return (node) => {
// Filter out comments
if (node.nodeType === COMMENT_NODE) return false;
// Filter out elements matching the selector
if (node.nodeType === ELEMENT_NODE && node.matches) try {
return !node.matches(selector);
} catch {
return true;
}
return true;
};
}
const formatRegExp = /%[sdjifoOc%]/g;
function format(args, options = {}) {
const formatArg = (item) => inspect(item, options);
if (typeof args[0] !== "string") {
const objects = [];
for (let i = 0; i < args.length; i++) objects.push(formatArg(args[i]));
return objects.join(" ");
}
const len = args.length;
let i = 1;
const template = args[0];
let str = String(template).replace(formatRegExp, (x) => {
if (x === "%%") return "%";
if (i >= len) return x;
switch (x) {
case "%s": {
const value = args[i++];
if (typeof value === "bigint") return `${value.toString()}n`;
if (typeof value === "number" && value === 0 && 1 / value < 0) return "-0";
if (typeof value === "object" && value !== null) {
if (typeof value.toString === "function" && value.toString !== Object.prototype.toString) return value.toString();
return formatArg(value);
}
return String(value);
}
case "%d": {
const value = args[i++];
if (typeof value === "bigint") return `${value.toString()}n`;
if (typeof value === "symbol") return "NaN";
return Number(value).toString();
}
case "%i": {
const value = args[i++];
if (typeof value === "bigint") return `${value.toString()}n`;
return Number.parseInt(String(value)).toString();
}
case "%f": return Number.parseFloat(String(args[i++])).toString();
case "%o":
case "%O": return formatArg(args[i++]);
case "%c":
i++;
return "";
case "%j": try {
return JSON.stringify(args[i++]);
} catch (err) {
const m = err.message;
if (m.includes("circular structure") || m.includes("cyclic structures") || m.includes("cyclic object")) return "[Circular]";
throw err;
}
default: return x;
}
});
for (let x = args[i]; i < len; x = args[++i]) if (x === null || typeof x !== "object") str += ` ${typeof x === "symbol" ? x.toString() : x}`;
else str += ` ${formatArg(x)}`;
return str;
}
function inspect(obj, options) {
const { truncate, multiline, ...stringifyOptions } = options ?? {};
const prettyFormatOptions = {
singleQuote: true,
quoteKeys: false,
min: true,
spacingInner: " ",
spacingOuter: " ",
printBasicPrototype: false,
compareKeys: null,
...multiline ? {
min: false,
spacingInner: void 0,
spacingOuter: void 0
} : {}
};
const threshold = truncate ?? 0;
const formatted = stringify(obj, void 0, {
...prettyFormatOptions,
...stringifyOptions,
maxLength: threshold || void 0
});
if (threshold === 0 || formatted.length <= threshold) return formatted;
// if stringify's adaptive maxDepth (down to 1) fails to truncate enough,
// - for known types (e.g. string, object, array, etc), apply best effort truncation.
// - for other values, fallback to maxDepth = 0 which should can show minimal output.
const type = Object.prototype.toString.call(obj);
if (typeof obj === "string") {
let end = threshold - 1;
if (end > 0 && isHighSurrogate(formatted[end - 1])) end = end - 1;
return `'${formatted.slice(1, end)}…'`;
}
if (type === "[object Array]" || type === "[object Object]" || type === "[object Set]" || type === "[object Map]") return stringifyByMaxWidth(obj, threshold, {
...prettyFormatOptions,
...stringifyOptions,
maxDepth: 1
});
return stringify(obj, void 0, {
...prettyFormatOptions,
...stringifyOptions,
maxDepth: 0
});
}
function truncateString(string, maxLength) {
if (string.length <= maxLength) return string;
let end = maxLength - 1;
if (isHighSurrogate(string[end - 1])) end = end - 1;
return `${string.slice(0, end)}…`;
}
function stringifyByMaxWidth(object, threshold, options) {
function evaluate(x) {
return stringify(object, void 0, {
...options,
maxWidth: x
});
}
return evaluate(binarySearch(0, threshold, (x) => evaluate(x).length <= threshold));
}
// find max(x \in [x, y) | f(x) = true)
// if f(x0) is false, then returns x0.
function binarySearch(x0, x1, f) {
while (x0 + 1 < x1) {
const x = Math.floor((x0 + x1) / 2);
if (f(x)) x0 = x;
else x1 = x;
}
return x0;
}
// https://github.com/chaijs/loupe/pull/79
function isHighSurrogate(char) {
return char >= "\ud800" && char <= "\udbff";
}
export { formatRegExp as a, format as f, inspect as i, stringify as s, truncateString as t };
+465
View File
@@ -0,0 +1,465 @@
import { spawn } from 'node:child_process';
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
import { availableParallelism, tmpdir } from 'node:os';
import { join } from 'node:path';
import { performance } from 'node:perf_hooks';
import { pathToFileURL } from 'node:url';
import { t as toArray, r as resolve } from './pathe.M-eThtNZ.Clq7AXhv.js';
import { y } from './tinyrainbow.Ht9iggcq.js';
import { distDir } from '../path.js';
const DOM_ENVIRONMENTS = /* @__PURE__ */ new Set(["jsdom", "happy-dom"]);
/**
* Builds the list of configurations worth measuring for this config. The
* candidates are pruned by what the config already uses - there is no point
* in measuring `threads` when the config already runs in `threads`.
*/
function resolveDoctorCandidates(projects, options = {}) {
const candidates = [];
// `pool`, `environment` and the fs module cache don't reach the browser
// runtime, so those candidates are driven by the node-side projects only;
// isolation applies to browser projects as well
const testProjects = projects.filter((project) => !project.browser);
const pools = new Set(testProjects.map((project) => project.pool));
const usesVmPool = pools.has("vmThreads") || pools.has("vmForks");
const runsDom = testProjects.some((project) => DOM_ENVIRONMENTS.has(project.environment));
const isolates = projects.some((project) => project.isolate && (project.browser || project.pool !== "vmThreads" && project.pool !== "vmForks"));
if (pools.has("forks")) candidates.push({
id: "threads",
title: `pool: 'threads'`,
overrides: { pool: "threads" },
configLines: [`pool: 'threads'`],
preservesIsolation: true
});
if (runsDom && !pools.has("vmThreads")) candidates.push({
id: "vmThreads",
title: `pool: 'vmThreads'`,
overrides: { pool: "vmThreads" },
configLines: [`pool: 'vmThreads'`],
preservesIsolation: true
});
// vmForks trades vmThreads' worker threads for child processes: each child
// gets its own heap and GC, which can beat vmThreads on GC-heavy suites, and
// it is the vm option for suites that cannot run in worker threads
if (runsDom && !pools.has("vmForks")) candidates.push({
id: "vmForks",
title: `pool: 'vmForks'`,
overrides: { pool: "vmForks" },
configLines: [`pool: 'vmForks'`],
preservesIsolation: true
});
// jsdom -> happy-dom is the only swap with a speed upside; it is applied per
// project, so projects running other environments keep them
if (options.happyDomAvailable && testProjects.some((project) => project.environment === "jsdom")) candidates.push({
id: "happy-dom",
title: `environment: 'happy-dom'`,
overrides: {},
envSwap: {
from: "jsdom",
to: "happy-dom"
},
configLines: [`environment: 'happy-dom'`],
preservesIsolation: true
});
if (isolates) candidates.push({
id: "no-isolate",
title: "isolate: false",
overrides: { isolate: false },
configLines: ["isolate: false"],
preservesIsolation: false,
validateIsolation: true
});
if (usesVmPool)
// the honest competitor of a vm pool: reused workers with shared state
candidates.push({
id: "threads-no-isolate",
title: `pool: 'threads' + isolate: false`,
overrides: {
pool: "threads",
isolate: false
},
configLines: [`pool: 'threads'`, "isolate: false"],
preservesIsolation: false,
validateIsolation: true
});
if (testProjects.length > 0 && testProjects.every((project) => !project.fsModuleCache))
// an untimed priming run populates the cache first: the candidate measures
// what repeated runs pay, which is what doctor compares everywhere else
candidates.push({
id: "fs-cache",
title: "fsModuleCache: true",
overrides: { fsModuleCache: true },
configLines: ["fsModuleCache: true"],
preservesIsolation: true,
primeRuns: 1
});
return candidates;
}
function log(...args) {
console.log(...args);
}
async function doctor(cliFilters, options) {
const { prepareVitest } = await import('./cli-api.LUtK11-x.js').then(function (n) { return n.a; });
log();
log(y.inverse(y.bold(y.blue(" DOCTOR "))), "resolving the current configuration...");
const ctx = await prepareVitest({
...options,
watch: false,
run: true
}, void 0, void 0, cliFilters);
const projects = ctx.projects.map((project) => ({
name: project.name,
pool: project.config.pool,
environment: project.config.environment,
isolate: project.config.isolate,
browser: project.config.browser.enabled,
fsModuleCache: project.config.fsModuleCache === true
}));
const fileCount = (await ctx.getRelevantTestSpecifications(cliFilters)).length;
const configuredMaxWorkers = ctx.config.maxWorkers;
const effectiveMaxWorkers = typeof configuredMaxWorkers === "number" && configuredMaxWorkers > 0 ? configuredMaxWorkers : Math.max(1, availableParallelism() - 1);
await ctx.close();
// the environments import 'happy-dom' relative to the vitest package (it is
// a peer dependency), so resolving from here mirrors what a run would do
let happyDomAvailable = false;
try {
import.meta.resolve("happy-dom");
happyDomAvailable = true;
} catch {}
const candidates = resolveDoctorCandidates(projects, { happyDomAvailable });
const baselineTitle = `baseline (${describeProjects(projects)})`;
// every measured run goes through one generated runner script: candidates
// may need per-project overrides (the happy-dom swap), which the CLI cannot
// express, and routing the baseline through the same entry keeps the
// process cost identical across measurements
const runnerDir = mkdtempSync(join(tmpdir(), "vitest-doctor-"));
const runnerPath = join(runnerDir, "runner.mjs");
writeFileSync(runnerPath, createRunnerScript());
process.once("exit", () => rmSync(runnerDir, {
recursive: true,
force: true
}));
const childProjects = toArray(options.project).map(String);
const childOptions = {
watch: false,
...options.root ? { root: String(options.root) } : {},
...options.config ? { config: String(options.config) } : {},
...childProjects.length ? { project: childProjects } : {}
};
// make sure an interrupted doctor doesn't leave a test suite running
let activeChild;
const killActiveChild = () => {
activeChild?.kill("SIGKILL");
process.exit(130);
};
process.once("SIGINT", killActiveChild);
process.once("SIGTERM", killActiveChild);
const runVitest = (run, timeoutMs) => {
return new Promise((resolve) => {
const start = performance.now();
const payload = JSON.stringify({
filters: cliFilters,
options: {
...childOptions,
...run.overrides
},
envSwap: run.envSwap
});
const child = spawn(process.execPath, [runnerPath, payload], {
env: {
...process.env,
NO_COLOR: "1"
},
stdio: [
"ignore",
"ignore",
"pipe"
]
});
activeChild = child;
let timedOut = false;
const timer = timeoutMs ? setTimeout(() => {
timedOut = true;
child.kill("SIGKILL");
}, timeoutMs) : void 0;
let stderr = "";
child.stderr.on("data", (chunk) => {
stderr = (stderr + String(chunk)).slice(-4e3);
});
child.on("close", (code) => {
if (timer) clearTimeout(timer);
activeChild = void 0;
if (timedOut) stderr += `\n(the run was killed after exceeding ${Math.round((timeoutMs || 0) / 1e3)}s - several times the baseline duration)`;
resolve({
wall: performance.now() - start,
ok: code === 0 && !timedOut,
stderr
});
});
});
};
log();
if (candidates.length === 0) log("The configuration already uses the fastest setup Vitest knows how to compare - measuring it for reference.");
else {
log("Measuring alternative configurations by running the test suite under each of them.");
log(y.dim("Close other heavy programs - the comparison is only as good as the machine is quiet."));
}
log();
// The first baseline run also warms up caches for every following run, so
// candidates are compared in a warm steady state.
process.stdout.write(y.dim(` measuring ${baselineTitle}...`));
const firstRun = await runVitest({});
if (!firstRun.ok) {
process.stdout.write("\n");
log(y.red("The test suite fails with the current configuration. Fix the failures first - doctor needs a green suite to compare configurations."));
if (firstRun.stderr.trim()) log(y.dim(firstRun.stderr.trim()));
process.exitCode = 1;
return;
}
// scale repetitions to the suite duration: short suites are noisy and can
// afford more runs, long suites cannot
const reps = firstRun.wall < 1e4 ? 3 : firstRun.wall < 6e4 ? 2 : 1;
// the first run is cold and mostly warms up caches: always take at least one
// more baseline run so the reported baseline is a warm one, like the
// candidates that run after it
const baselineRuns = Math.max(reps, 2);
const baselineWalls = [firstRun.wall];
for (let i = 1; i < baselineRuns; i++) {
const run = await runVitest({});
if (!run.ok) {
process.stdout.write("\n");
log(y.red(`The test suite passed once but failed on repetition ${i + 1} with the same configuration - doctor needs a stable green suite to compare configurations.`));
if (run.stderr.trim()) log(y.dim(run.stderr.trim()));
process.exitCode = 1;
return;
}
baselineWalls.push(run.wall);
}
const baselineWall = Math.min(...baselineWalls);
process.stdout.write(` ${formatSeconds(baselineWall)}\n`);
// a candidate that takes several times the baseline is a regression, not a
// recommendation - don't let it stall doctor indefinitely
const candidateTimeout = Math.max(6e4, baselineWall * 4);
const measured = [];
for (const candidate of candidates) {
process.stdout.write(y.dim(` measuring ${candidate.title}...`));
let wall = Number.POSITIVE_INFINITY;
let ok = true;
let stderr = "";
const candidateRun = {
overrides: candidate.overrides,
envSwap: candidate.envSwap
};
for (let i = 0; i < (candidate.primeRuns ?? 0) && ok; i++) {
const run = await runVitest(candidateRun, candidateTimeout);
ok = run.ok;
stderr = run.stderr;
}
for (let i = 0; i < reps && ok; i++) {
const run = await runVitest(candidateRun, candidateTimeout);
wall = Math.min(wall, run.wall);
ok = run.ok;
stderr = run.stderr;
}
let isolationVerdict;
if (ok && candidate.validateIsolation) {
// tests can pass under shared state by accident of ordering: shuffle the
// file order twice to catch the common cross-file couplings; the explicit
// distinct seeds guarantee two different orders even when the user pinned
// `sequence.seed` in their config
isolationVerdict = "passed";
for (let i = 0; i < 2; i++) {
const run = await runVitest({
overrides: {
...candidate.overrides,
sequence: {
shuffle: { files: true },
seed: 271828 + i
}
},
envSwap: candidate.envSwap
}, candidateTimeout);
if (!run.ok) {
isolationVerdict = "failed";
stderr = run.stderr;
break;
}
}
}
measured.push({
candidate,
wall,
ok,
stderr,
isolationVerdict
});
process.stdout.write(ok ? ` ${formatSeconds(wall)}\n` : ` ${y.red("failed")}\n`);
}
const viable = measured.filter((result) => result.ok && result.isolationVerdict !== "failed" && result.wall < baselineWall * .9);
// among candidates close to the fastest, prefer the one that keeps per-file
// isolation - equal speed with stronger guarantees wins
const fastest = viable.length > 0 ? viable.reduce((a, b) => b.wall < a.wall ? b : a) : void 0;
const preferred = fastest ? viable.filter((result) => result.wall <= fastest.wall * 1.05).sort((a, b) => Number(b.candidate.preservesIsolation) - Number(a.candidate.preservesIsolation))[0] : void 0;
// Past a certain worker count the single main-thread Vite server becomes the
// bottleneck, so FEWER workers can be faster. Greedily descend by halving on
// top of the winning configuration, keeping a step only when it is a real
// (>= 5%) improvement.
const workerStack = preferred ? {
overrides: preferred.candidate.overrides,
envSwap: preferred.candidate.envSwap
} : {};
const workerSuffix = preferred ? ` (with ${preferred.candidate.title})` : "";
const workerProbes = [];
let bestWorkers;
{
let currentBest = preferred ? preferred.wall : baselineWall;
let probe = Math.floor(effectiveMaxWorkers / 2);
while (probe >= 2 && probe < fileCount) {
process.stdout.write(y.dim(` measuring maxWorkers: ${probe}${workerSuffix}...`));
let wall = Number.POSITIVE_INFINITY;
let ok = true;
for (let i = 0; i < reps && ok; i++) {
const run = await runVitest({
overrides: {
...workerStack.overrides,
maxWorkers: probe
},
envSwap: workerStack.envSwap
}, candidateTimeout);
wall = Math.min(wall, run.wall);
ok = run.ok;
}
process.stdout.write(ok ? ` ${formatSeconds(wall)}\n` : ` ${y.red("failed")}\n`);
if (!ok) break;
workerProbes.push({
workers: probe,
wall
});
if (wall >= currentBest * .95) break;
currentBest = wall;
bestWorkers = {
workers: probe,
wall
};
probe = Math.floor(probe / 2);
}
}
log();
log(y.bold("Results") + y.dim(` (min of ${reps} run${reps === 1 ? "" : "s"} each)`));
log();
const rowTitles = [...measured.map((m) => m.candidate.title), ...workerProbes.map((p) => `maxWorkers: ${p.workers}${workerSuffix}`)];
const width = Math.max(baselineTitle.length, ...rowTitles.map((title) => title.length)) + 2;
log(` ${baselineTitle.padEnd(width)}${formatSeconds(baselineWall)}`);
for (const result of measured) {
const title = result.candidate.title.padEnd(width);
if (!result.ok) log(` ${title}${y.red("failed")}`);
else if (result.isolationVerdict === "failed") log(` ${title}${formatSeconds(result.wall)} ${y.red("(fails with a shuffled file order - tests depend on isolation)")}`);
else log(` ${title}${formatSeconds(result.wall)} ${formatDelta(result.wall, baselineWall)}`);
}
for (const probeResult of workerProbes) log(` ${`maxWorkers: ${probeResult.workers}${workerSuffix}`.padEnd(width)}${formatSeconds(probeResult.wall)} ${formatDelta(probeResult.wall, baselineWall)}`);
// failing candidates are as informative as fast ones: show what broke,
// so "vmThreads is not an option for this suite" comes with the reason
for (const result of measured) if (!result.ok) logFailureExcerpt(`${result.candidate.title} failed with:`, result.stderr);
else if (result.isolationVerdict === "failed") logFailureExcerpt(`${result.candidate.title} failed with a shuffled file order:`, result.stderr);
log();
if (!preferred && !bestWorkers) {
if (candidates.length === 0) log(`${y.bold("Recommendation:")} keep the current configuration (${describeProjects(projects)}) - it measured ${y.yellow(formatSeconds(baselineWall))} and Vitest has no faster candidate to suggest for it.`);
else log(`${y.bold("Recommendation:")} keep the current configuration (${describeProjects(projects)}) - no measured candidate was more than 10% faster.`);
return;
}
const finalWall = bestWorkers ? bestWorkers.wall : preferred.wall;
const recommendationTitle = [preferred?.candidate.title, bestWorkers ? `maxWorkers: ${bestWorkers.workers}` : void 0].filter(Boolean).join(" + ");
const configLines = [...preferred ? preferred.candidate.configLines : [], ...bestWorkers ? [`maxWorkers: ${bestWorkers.workers}`] : []];
log(`${y.bold("Recommendation:")} ${y.yellow(recommendationTitle)} ${formatDelta(finalWall, baselineWall)}`);
log();
log(y.dim(" // vitest.config.ts"));
log(y.dim(` import { defineConfig } from 'vitest/config'`));
log();
log(y.dim(" export default defineConfig({"));
log(y.dim(" test: {"));
for (const [index, line] of configLines.entries()) log(` ${line},${index === configLines.length - 1 ? y.dim(` // measured ${Math.round((finalWall - baselineWall) / baselineWall * 100)}% on this suite`) : ""}`);
log(y.dim(" },"));
log(y.dim(" })"));
log();
if (preferred) for (const note of candidateNotes(preferred)) log(y.dim(` ${note}`));
if (bestWorkers) {
log(y.dim(` Fewer workers can be faster because every worker funnels transforms through the`));
log(y.dim(` single main-thread Vite server; a lower count also reduces memory pressure.`));
}
if (projects.length > 1) {
const note = preferred?.candidate.envSwap ? `Doctor swapped the environment of every project running ${preferred.candidate.envSwap.from}; other projects were left unchanged.` : "Doctor overrides options for all projects at once; apply the change per project if they need different settings.";
log(y.dim(` ${note}`));
}
log(y.dim(" Trade-offs of every option: https://vitest.dev/guide/improving-performance"));
}
/**
* The measured runs execute this script instead of the CLI binary: it accepts
* the overrides as JSON and can apply per-project changes (the environment
* swap) that CLI flags cannot express. `createVitest` attaches the projects,
* so the swap mutates each resolved project config before the run starts.
*/
function createRunnerScript() {
const nodeEntry = pathToFileURL(resolve(distDir, "node.js")).href;
return `import { createVitest } from ${JSON.stringify(nodeEntry)}
const { filters, options, envSwap } = JSON.parse(process.argv[2])
const ctx = await createVitest(options)
if (envSwap) {
for (const project of ctx.projects) {
if (!project.config.browser.enabled && project.config.environment === envSwap.from) {
project.config.environment = envSwap.to
}
}
}
try {
await ctx.start(filters)
}
catch (error) {
console.error(error?.stack || String(error))
process.exitCode = 1
}
await ctx.close()
process.exit(process.exitCode || 0)
`;
}
function logFailureExcerpt(title, stderr) {
log();
log(` ${y.red(title)}`);
if (!stderr.trim()) {
log(y.dim(" (the run produced no error output - rerun with the same options to inspect it)"));
return;
}
const lines = stderr.trim().split("\n");
const excerpt = lines.slice(-15);
if (lines.length > excerpt.length) log(y.dim(` … (last ${excerpt.length} lines)`));
for (const line of excerpt) log(y.dim(` ${line}`));
}
function candidateNotes(result) {
switch (result.candidate.id) {
case "vmThreads":
case "vmForks": return [`vm pools keep per-file isolation but run test code in a VM context: cross-realm`, `instanceof edge cases and higher memory usage are possible (see vmMemoryLimit).`];
case "no-isolate":
case "threads-no-isolate": return [`The suite passed twice with a shuffled file order under shared state, so it is`, `likely - but not guaranteed - that no test depends on isolation.`];
case "happy-dom": return [
`The swap was applied only to projects running jsdom; other projects kept their`,
`environment. happy-dom implements the DOM differently than jsdom: the suite`,
`passed under it, but double-check tests that depend on layout, navigation or`,
`other DOM edge cases.`
];
case "fs-cache": return [`The fs module cache persists transformed modules on disk: repeated runs skip`, `the transforms, the first run after a file change still pays them.`];
default: return [];
}
}
function describeProjects(projects) {
return `pool: ${[...new Set(projects.map((project) => project.browser ? "browser" : project.pool))].join(", ")} · isolate: ${projects.some((project) => project.isolate)}`;
}
function formatSeconds(time) {
return `${(time / 1e3).toFixed(2)}s`;
}
function formatDelta(wall, baseline) {
const delta = Math.round((wall - baseline) / baseline * 100);
if (delta === 0) return y.dim("(±0%)");
return delta < 0 ? y.green(`(${delta}%)`) : y.yellow(`(+${delta}%)`);
}
export { doctor, resolveDoctorCandidates };
+9
View File
@@ -0,0 +1,9 @@
import { isCI } from 'std-env';
const isNode = typeof process < "u" && typeof process.stdout < "u" && !process.versions?.deno && !globalThis.window;
const isDeno = typeof process < "u" && typeof process.stdout < "u" && process.versions?.deno !== void 0;
const isWindows = (isNode || isDeno) && process.platform === "win32";
const isTTY = (isNode || isDeno) && process.stdout?.isTTY && !isCI;
const isForceColor = () => "FORCE_COLOR" in process.env;
export { isForceColor as a, isTTY as b, isWindows as i };
+37
View File
@@ -0,0 +1,37 @@
import { A as Awaitable } from './config.d.CU_b-wJj.js';
interface EnvironmentReturn {
teardown: (global: any) => Awaitable<void>;
}
interface VmEnvironmentReturn {
getVmContext: () => {
[key: string]: any;
};
teardown: () => Awaitable<void>;
}
interface Environment {
name: string;
/**
* @deprecated use `viteEnvironment` instead. Uses `name` by default
*/
transformMode?: "web" | "ssr";
/**
* Environment initiated by the Vite server. It is usually available
* as `vite.server.environments.${name}`.
*
* By default, fallbacks to `name`.
*/
viteEnvironment?: "client" | "ssr" | ({} & string);
/**
* Let the server transform the test file's import graph while `setupVM`
* runs. Only worth it when `setupVM` is slow (jsdom, happy-dom): otherwise
* the transforms compete with the worker's own module requests.
*
* @default `true`
*/
prewarmModules?: boolean;
setupVM?: (options: Record<string, any>) => Awaitable<VmEnvironmentReturn>;
setup: (global: any, options: Record<string, any>) => Awaitable<EnvironmentReturn>;
}
export type { Environment as E, VmEnvironmentReturn as V, EnvironmentReturn as a };
@@ -0,0 +1,7 @@
import { EvaluatedModules } from 'vite/module-runner';
declare class VitestEvaluatedModules extends EvaluatedModules {
getModuleSourceMapById(id: string): any;
}
export { VitestEvaluatedModules as V };
+28
View File
@@ -0,0 +1,28 @@
import { g as globalApis } from './constants.-juJ8b_4.js';
import { i as index } from './index.OVGXnVRj.js';
import './run.CQOUYP-x.js';
import './index.M2dsQ_UQ.js';
import './tinyrainbow.Ht9iggcq.js';
import './display.pkpxlVcY.js';
import './pathe.M-eThtNZ.Clq7AXhv.js';
import './source-map.Dx3YPjIe.js';
import '../task-utils.js';
import './utils.DYj33du9.js';
import './spy.DPLvp2J7.js';
import 'chai';
import './plugins.Cigb0uSy.js';
import './offset.Dy-5Fdfn.js';
import './rpc.DWKX1ZbI.js';
import 'vite/module-runner';
import './index.DmDMHCg8.js';
import 'tinybench';
import 'expect-type';
function registerApiGlobally() {
globalApis.forEach((api) => {
// @ts-expect-error I know what I am doing :P
globalThis[api] = index[api];
});
}
export { registerApiGlobally };
File diff suppressed because it is too large. Load diff
+196
View File
@@ -0,0 +1,196 @@
import * as chai from 'chai';
import { createHook } from 'node:async_hooks';
import { t as takeCoverageInsideWorker } from './coverage.BtDxDqSU.js';
import { r as rpc } from './rpc.DWKX1ZbI.js';
import { l as loadDiffConfig, a as loadSnapshotSerializers } from './setup-common.D5-2VgqG.js';
import { g as getWorkerState } from './utils.DYj33du9.js';
import { T as TestRunner } from './index.OVGXnVRj.js';
function setupChaiConfig(config) {
Object.assign(chai.config, config);
}
async function resolveSnapshotEnvironment(config, moduleRunner) {
if (!config.snapshotEnvironment) {
const { VitestNodeSnapshotEnvironment } = await import('./node.Es9ukMGX.js');
return new VitestNodeSnapshotEnvironment();
}
const mod = await moduleRunner.import(config.snapshotEnvironment);
if (typeof mod.default !== "object" || !mod.default) throw new Error("Snapshot environment module must have a default export object with a shape of `SnapshotEnvironment`");
return mod.default;
}
const IGNORED_TYPES = /* @__PURE__ */ new Set([
"DNSCHANNEL",
"ELDHISTOGRAM",
"PerformanceObserver",
"RANDOMBYTESREQUEST",
"SIGNREQUEST",
"STREAM_END_OF_STREAM",
"TCPWRAP",
"TIMERWRAP",
"TLSWRAP",
"ZLIB"
]);
function detectAsyncLeaks(testFile, projectName) {
const resources = /* @__PURE__ */ new Map();
const hook = createHook({
init(asyncId, type, triggerAsyncId, resource) {
if (IGNORED_TYPES.has(type)) return;
let stack = "";
const limit = Error.stackTraceLimit;
// VitestModuleEvaluator's async wrapper of node:vm causes out-of-bound stack traces, simply skip it.
// Crash fixed in https://github.com/vitejs/vite/pull/21585
try {
Error.stackTraceLimit = 100;
stack = (/* @__PURE__ */ new Error("VITEST_DETECT_ASYNC_LEAKS")).stack || "";
} catch {
return;
} finally {
Error.stackTraceLimit = limit;
}
if (!stack.includes(testFile)) {
const trigger = resources.get(triggerAsyncId);
if (!trigger) return;
stack = trigger.stack;
}
let isActive = isActiveDefault;
if ("hasRef" in resource) {
const ref = new WeakRef(resource);
isActive = () => ref.deref()?.hasRef() ?? false;
}
resources.set(asyncId, {
type,
stack,
projectName,
filename: testFile,
isActive
});
},
destroy(asyncId) {
if (resources.get(asyncId)?.type !== "PROMISE") resources.delete(asyncId);
},
promiseResolve(asyncId) {
resources.delete(asyncId);
}
});
hook.enable();
return async function collect() {
await new Promise((resolve) => setImmediate(resolve));
hook.disable();
const leaks = [];
for (const resource of resources.values()) if (resource.isActive()) leaks.push({
stack: resource.stack,
type: resource.type,
filename: resource.filename,
projectName: resource.projectName
});
resources.clear();
return leaks;
};
}
function isActiveDefault() {
return true;
}
async function getTestRunnerConstructor(config, moduleRunner) {
if (!config.runner) return TestRunner;
const mod = await moduleRunner.import(config.runner);
if (!mod.default && typeof mod.default !== "function") throw new Error(`Runner must export a default function, but got ${typeof mod.default} imported from ${config.runner}`);
return mod.default;
}
async function resolveTestRunner(config, moduleRunner, traces) {
const testRunner = new (await (getTestRunnerConstructor(config, moduleRunner)))(config);
// inject private executor to every runner
Object.defineProperty(testRunner, "moduleRunner", {
value: moduleRunner,
enumerable: false,
configurable: false
});
if (!testRunner.config) testRunner.config = config;
if (!testRunner.importFile) throw new Error("Runner must implement \"importFile\" method.");
if ("__setTraces" in testRunner) testRunner.__setTraces(traces);
const [diffOptions] = await Promise.all([loadDiffConfig(config, moduleRunner), loadSnapshotSerializers(config, moduleRunner)]);
testRunner.config._diffOptions = diffOptions;
// patch some methods, so custom runners don't need to call RPC
const originalOnTaskUpdate = testRunner.onTaskUpdate;
testRunner.onTaskUpdate = async (task, events) => {
const p = rpc().onTaskUpdate(task, events);
await originalOnTaskUpdate?.call(testRunner, task, events);
return p;
};
// patch some methods, so custom runners don't need to call RPC
const originalOnTestAnnotate = testRunner.onTestAnnotate;
testRunner.onTestAnnotate = async (test, annotation) => {
const p = rpc().onTaskArtifactRecord(test.id, {
type: "internal:annotation",
location: annotation.location,
annotation
});
const overriddenResult = await originalOnTestAnnotate?.call(testRunner, test, annotation);
const vitestResult = await p;
return overriddenResult || vitestResult.annotation;
};
const originalOnTestArtifactRecord = testRunner.onTestArtifactRecord;
testRunner.onTestArtifactRecord = async (test, artifact) => {
const p = rpc().onTaskArtifactRecord(test.id, artifact);
const overriddenResult = await originalOnTestArtifactRecord?.call(testRunner, test, artifact);
const vitestResult = await p;
return overriddenResult || vitestResult;
};
const originalOnCollectStart = testRunner.onCollectStart;
testRunner.onCollectStart = async (file) => {
await rpc().onQueued(file);
await originalOnCollectStart?.call(testRunner, file);
};
const originalOnCollected = testRunner.onCollected;
testRunner.onCollected = async (files) => {
const state = getWorkerState();
files.forEach((file) => {
file.prepareDuration = state.durations.prepare;
file.environmentLoad = state.durations.environment;
// should be collected only for a single test file in a batch
state.durations.prepare = 0;
state.durations.environment = 0;
});
// Strip function conditions from retry config before sending via RPC
// Functions cannot be cloned by structured clone algorithm
const sanitizeRetryConditions = (task) => {
if (task.retry && typeof task.retry === "object" && typeof task.retry.condition === "function")
// Remove function condition - it can't be serialized
task.retry = {
...task.retry,
condition: void 0
};
if (task.tasks) task.tasks.forEach(sanitizeRetryConditions);
};
files.forEach(sanitizeRetryConditions);
rpc().onCollected(files);
await originalOnCollected?.call(testRunner, files);
};
const originalOnAfterRun = testRunner.onAfterRunFiles;
testRunner.onAfterRunFiles = async (files) => {
const state = getWorkerState();
const coverage = await takeCoverageInsideWorker(config.coverage, moduleRunner);
if (coverage) rpc().onAfterSuiteRun({
coverage,
testFiles: files.map((file) => file.name).sort(),
environment: state.environment.viteEnvironment || state.environment.name,
projectName: state.ctx.projectName
});
await originalOnAfterRun?.call(testRunner, files);
};
const originalOnAfterRunTask = testRunner.onAfterRunTask;
testRunner.onAfterRunTask = async (test) => {
if (config.bail && test.result?.state === "fail") {
if (1 + await rpc().getCountOfFailedTests() >= config.bail) {
rpc().onCancel("test-failure");
testRunner.cancel?.("test-failure");
}
}
await originalOnAfterRunTask?.call(testRunner, test);
};
return testRunner;
}
export { resolveSnapshotEnvironment as a, detectAsyncLeaks as d, resolveTestRunner as r, setupChaiConfig as s };
File diff suppressed because it is too large. Load diff
+234
View File
@@ -0,0 +1,234 @@
import process from 'node:process';
import fs from 'node:fs/promises';
import path, { resolve } from 'node:path';
import { existsSync } from 'node:fs';
import { x } from 'tinyexec';
const AGENTS = [
"npm",
"yarn",
"yarn@berry",
"pnpm",
"pnpm@6",
"pnpm-rush",
"bun",
"deno",
"nub",
"aube"
];
const LOCKS = {
"aube-lock.yaml": "aube",
"aube-workspace.yaml": "aube",
"bun.lock": "bun",
"bun.lockb": "bun",
"deno.lock": "deno",
"nub.lock": "nub",
"pnpm-lock.yaml": "pnpm",
"pnpm-workspace.yaml": "pnpm",
"yarn.lock": "yarn",
"package-lock.json": "npm",
"npm-shrinkwrap.json": "npm"
};
const INSTALL_METADATA = {
"node_modules/.aube/": "aube",
"node_modules/.deno/": "deno",
"node_modules/.pnpm/": "pnpm",
"node_modules/.yarn-state.yml": "yarn",
// yarn v2+ (node-modules)
"node_modules/.yarn_integrity": "yarn",
// yarn v1
"node_modules/.package-lock.json": "npm",
".pnp.cjs": "yarn",
// yarn v3+ (pnp)
".pnp.js": "yarn",
// yarn v2 (pnp)
"bun.lock": "bun",
"bun.lockb": "bun"
};
async function pathExists(path2, type) {
try {
const stat = await fs.stat(path2);
return type === "file" ? stat.isFile() : stat.isDirectory();
} catch {
return false;
}
}
function* lookup(cwd = process.cwd()) {
let directory = path.resolve(cwd);
const { root } = path.parse(directory);
while (directory && directory !== root) {
yield directory;
directory = path.dirname(directory);
}
}
async function parsePackageJson(filepath, options) {
if (!filepath || !await pathExists(filepath, "file"))
return null;
return await handlePackageManager(filepath, options);
}
async function detect(options = {}) {
const {
cwd,
strategies = ["lockfile", "packageManager-field", "devEngines-field"]
} = options;
let stopDir;
if (typeof options.stopDir === "string") {
const resolved = path.resolve(options.stopDir);
stopDir = (dir) => dir === resolved;
} else {
stopDir = options.stopDir;
}
for (const directory of lookup(cwd)) {
for (const strategy of strategies) {
switch (strategy) {
case "lockfile": {
if (await pathExists(path.join(directory, "rush.json"), "file")) {
return { name: "pnpm", agent: "pnpm-rush" };
}
for (const lock of Object.keys(LOCKS)) {
if (await pathExists(path.join(directory, lock), "file")) {
const name = LOCKS[lock];
const result = await parsePackageJson(path.join(directory, "package.json"), options);
if (result)
return result;
else
return { name, agent: name };
}
}
break;
}
case "packageManager-field":
case "devEngines-field": {
const result = await parsePackageJson(path.join(directory, "package.json"), options);
if (result)
return result;
break;
}
case "install-metadata": {
for (const metadata of Object.keys(INSTALL_METADATA)) {
const fileOrDir = metadata.endsWith("/") ? "dir" : "file";
if (await pathExists(path.join(directory, metadata), fileOrDir)) {
const name = INSTALL_METADATA[metadata];
const agent = name === "yarn" ? isMetadataYarnClassic(metadata) ? "yarn" : "yarn@berry" : name;
return { name, agent };
}
}
break;
}
}
}
if (stopDir?.(directory))
break;
}
return null;
}
function getNameAndVer(pkg) {
const handelVer = (version) => version?.match(/\d+(\.\d+){0,2}/)?.[0] ?? version;
if (typeof pkg.packageManager === "string") {
const [name, ver] = pkg.packageManager.replace(/^\^/, "").split("@");
return { name, ver: handelVer(ver) };
}
if (typeof pkg.devEngines?.packageManager?.name === "string") {
return {
name: pkg.devEngines.packageManager.name,
ver: handelVer(pkg.devEngines.packageManager.version)
};
}
return void 0;
}
async function handlePackageManager(filepath, options) {
try {
const content = await fs.readFile(filepath, "utf8");
const pkg = options.packageJsonParser ? await options.packageJsonParser(content, filepath) : JSON.parse(content);
let agent;
const nameAndVer = getNameAndVer(pkg);
if (nameAndVer) {
const name = nameAndVer.name;
const ver = nameAndVer.ver;
let version = ver;
if (name === "yarn" && ver && Number.parseInt(ver) > 1) {
agent = "yarn@berry";
version = "berry";
return { name, agent, version };
} else if (name === "pnpm" && ver && Number.parseInt(ver) < 7) {
agent = "pnpm@6";
return { name, agent, version };
} else if (AGENTS.includes(name)) {
agent = name;
return { name, agent, version };
} else {
return options.onUnknown?.(pkg.packageManager) ?? null;
}
}
} catch {
}
return null;
}
function isMetadataYarnClassic(metadataPath) {
return metadataPath.endsWith(".yarn_integrity");
}
//#region src/detect.ts
async function detectPackageManager(cwd = process.cwd()) {
return (await detect({
cwd,
onUnknown(packageManager) {
console.warn("[@antfu/install-pkg] Unknown packageManager:", packageManager);
}
}))?.agent || null;
}
//#endregion
//#region src/install.ts
async function installPackage(names, options = {}) {
const detectedAgent = options.packageManager || await detectPackageManager(options.cwd) || "npm";
const [agent] = detectedAgent.split("@");
if (!Array.isArray(names)) names = [names];
const args = (typeof options.additionalArgs === "function" ? options.additionalArgs(agent, detectedAgent) : options.additionalArgs) || [];
if (options.preferOffline) if (detectedAgent === "yarn@berry") args.unshift("--cached");
else args.unshift("--prefer-offline");
if (agent === "pnpm") {
args.unshift(
/**
* Prevent pnpm from removing installed devDeps while `NODE_ENV` is `production`
* @see https://pnpm.io/cli/install#--prod--p
*/
"--prod=false"
);
if (existsSync(resolve(options.cwd ?? process.cwd(), "pnpm-workspace.yaml"))) args.unshift("-w");
}
return x(agent, [
agent === "yarn" ? "add" : "install",
options.dev ? "-D" : "",
...args,
...names
].filter(Boolean), {
nodeOptions: {
stdio: options.silent ? "ignore" : "inherit",
cwd: options.cwd
},
throwOnError: true
});
}
//#endregion
//#region src/uninstall.ts
async function uninstallPackage(names, options = {}) {
const [agent] = (options.packageManager || await detectPackageManager(options.cwd) || "npm").split("@");
if (!Array.isArray(names)) names = [names];
const args = options.additionalArgs || [];
if (agent === "pnpm" && existsSync(resolve(options.cwd ?? process.cwd(), "pnpm-workspace.yaml"))) args.unshift("-w");
return x(agent, [
agent === "yarn" ? "remove" : "uninstall",
options.dev ? "-D" : "",
...args,
...names
].filter(Boolean), {
nodeOptions: {
stdio: options.silent ? "ignore" : "inherit",
cwd: options.cwd
},
throwOnError: true
});
}
export { detectPackageManager, installPackage, uninstallPackage };
+200
View File
@@ -0,0 +1,200 @@
//#region src/utils.ts
function createPromiseWithResolvers() {
let resolve;
let reject;
return {
promise: new Promise((res, rej) => {
resolve = res;
reject = rej;
}),
resolve,
reject
};
}
const random = Math.random.bind(Math);
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
function nanoid(size = 21) {
let id = "";
let i = size;
while (i--) id += urlAlphabet[random() * 64 | 0];
return id;
}
//#endregion
//#region src/main.ts
const DEFAULT_TIMEOUT = 6e4;
const defaultSerialize = (i) => i;
const defaultDeserialize = defaultSerialize;
const { clearTimeout, setTimeout } = globalThis;
function createBirpc($functions, options) {
const { post, on, off = () => {}, eventNames = [], serialize = defaultSerialize, deserialize = defaultDeserialize, resolver, bind = "rpc", timeout = DEFAULT_TIMEOUT, proxify = true } = options;
let $closed = false;
const _rpcPromiseMap = /* @__PURE__ */ new Map();
let _promiseInit;
let rpc;
async function _call(method, args, event, optional) {
if ($closed) throw new Error(`[birpc] rpc is closed, cannot call "${method}"`);
const req = {
m: method,
a: args,
t: "q"
};
if (optional) req.o = true;
const send = async (_req) => post(serialize(_req));
if (event) {
await send(req);
return;
}
if (_promiseInit) try {
await _promiseInit;
} finally {
_promiseInit = void 0;
}
let { promise, resolve, reject } = createPromiseWithResolvers();
const id = nanoid();
req.i = id;
let timeoutId;
async function handler(newReq = req) {
if (timeout >= 0) {
timeoutId = setTimeout(() => {
try {
if (options.onTimeoutError?.call(rpc, method, args) !== true) throw new Error(`[birpc] timeout on calling "${method}"`);
} catch (e) {
reject(e);
}
_rpcPromiseMap.delete(id);
}, timeout);
if (typeof timeoutId === "object") timeoutId = timeoutId.unref?.();
}
_rpcPromiseMap.set(id, {
resolve,
reject,
timeoutId,
method
});
await send(newReq);
return promise;
}
try {
if (options.onRequest) await options.onRequest.call(rpc, req, handler, resolve);
else await handler();
} catch (e) {
if (options.onGeneralError?.call(rpc, e) !== true) throw e;
return;
} finally {
clearTimeout(timeoutId);
_rpcPromiseMap.delete(id);
}
return promise;
}
const builtinMethods = {
$call: (method, ...args) => _call(method, args, false),
$callOptional: (method, ...args) => _call(method, args, false, true),
$callEvent: (method, ...args) => _call(method, args, true),
$callRaw: (options) => _call(options.method, options.args, options.event, options.optional),
$rejectPendingCalls,
get $closed() {
return $closed;
},
get $meta() {
return options.meta;
},
$close,
$functions
};
if (proxify) rpc = new Proxy({}, { get(_, method) {
if (Object.hasOwn(builtinMethods, method)) return builtinMethods[method];
if (method === "then" && !eventNames.includes("then") && !("then" in $functions)) return void 0;
const sendEvent = (...args) => _call(method, args, true);
if (eventNames.includes(method)) {
sendEvent.asEvent = sendEvent;
return sendEvent;
}
const sendCall = (...args) => _call(method, args, false);
sendCall.asEvent = sendEvent;
return sendCall;
} });
else rpc = builtinMethods;
function $close(customError) {
$closed = true;
_rpcPromiseMap.forEach(({ reject, method }) => {
const error = /* @__PURE__ */ new Error(`[birpc] rpc is closed, cannot call "${method}"`);
if (customError) {
customError.cause ??= error;
return reject(customError);
}
reject(error);
});
_rpcPromiseMap.clear();
off(onMessage);
}
function $rejectPendingCalls(handler) {
const handlerResults = Array.from(_rpcPromiseMap.values()).map(({ method, reject }) => {
if (!handler) return reject(/* @__PURE__ */ new Error(`[birpc]: rejected pending call "${method}".`));
return handler({
method,
reject
});
});
_rpcPromiseMap.clear();
return handlerResults;
}
async function onMessage(data, ...extra) {
let msg;
try {
msg = deserialize(data);
} catch (e) {
if (options.onGeneralError?.call(rpc, e) !== true) throw e;
return;
}
if (msg.t === "q") {
const { m: method, a: args, o: optional } = msg;
let result, error;
let fn = await (resolver ? resolver.call(rpc, method, $functions[method]) : $functions[method]);
if (optional) fn ||= () => void 0;
if (!fn) error = /* @__PURE__ */ new Error(`[birpc] function "${method}" not found`);
else try {
result = await fn.apply(bind === "rpc" ? rpc : $functions, args);
} catch (e) {
error = e;
}
if (msg.i) {
if (error && options.onFunctionError) {
if (options.onFunctionError.call(rpc, error, method, args) === true) return;
}
if (!error) try {
await post(serialize({
t: "s",
i: msg.i,
r: result
}), ...extra);
return;
} catch (e) {
error = e;
if (options.onGeneralError?.call(rpc, e, method, args) !== true) throw e;
}
try {
await post(serialize({
t: "s",
i: msg.i,
e: error
}), ...extra);
} catch (e) {
if (options.onGeneralError?.call(rpc, e, method, args) !== true) throw e;
}
}
} else {
const { i: ack, r: result, e: error } = msg;
const promise = _rpcPromiseMap.get(ack);
if (promise) {
clearTimeout(promise.timeoutId);
if (error) promise.reject(error);
else promise.resolve(result);
}
_rpcPromiseMap.delete(ack);
}
}
_promiseInit = on(onMessage);
return rpc;
}
export { createBirpc as c };
+944
View File
@@ -0,0 +1,944 @@
import { y } from './tinyrainbow.Ht9iggcq.js';
function _mergeNamespaces$1(n, m) {
m.forEach(function(e) {
e && typeof e !== "string" && !Array.isArray(e) && Object.keys(e).forEach(function(k) {
if (k !== "default" && !(k in n)) {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function() {
return e[k];
}
});
}
});
});
return Object.freeze(n);
}
function getKeysOfEnumerableProperties(object, compareKeys) {
const rawKeys = Object.keys(object);
const keys = compareKeys === null ? rawKeys : rawKeys.sort(compareKeys);
if (Object.getOwnPropertySymbols) {
for (const symbol of Object.getOwnPropertySymbols(object)) if (Object.getOwnPropertyDescriptor(object, symbol).enumerable) keys.push(symbol);
}
return keys;
}
/**
* Return entries (for example, of a map)
* with spacing, indentation, and comma
* without surrounding punctuation (for example, braces)
*/
function printIteratorEntries(iterator, config, indentation, depth, refs, printer, separator = ": ", length) {
let result = "";
let width = 0;
let current = iterator.next();
if (!current.done) {
result += config.spacingOuter;
const indentationNext = indentation + config.indent;
while (!current.done) {
result += indentationNext;
if (width++ === config.maxWidth) {
result += typeof length === "number" ? `…(${length - width + 1})` : "…";
break;
}
const name = printer(current.value[0], config, indentationNext, depth, refs);
const value = printer(current.value[1], config, indentationNext, depth, refs);
result += name + separator + value;
current = iterator.next();
if (!current.done) result += `,${config.spacingInner}`;
else if (!config.min) result += ",";
}
result += config.spacingOuter + indentation;
}
return result;
}
/**
* Return values (for example, of a set)
* with spacing, indentation, and comma
* without surrounding punctuation (braces or brackets)
*/
function printIteratorValues(iterator, config, indentation, depth, refs, printer, length) {
let result = "";
let width = 0;
let current = iterator.next();
if (!current.done) {
result += config.spacingOuter;
const indentationNext = indentation + config.indent;
while (!current.done) {
result += indentationNext;
if (width++ === config.maxWidth) {
result += typeof length === "number" ? `…(${length - width + 1})` : "…";
break;
}
result += printer(current.value, config, indentationNext, depth, refs);
current = iterator.next();
if (!current.done) result += `,${config.spacingInner}`;
else if (!config.min) result += ",";
}
result += config.spacingOuter + indentation;
}
return result;
}
/**
* Return items (for example, of an array)
* with spacing, indentation, and comma
* without surrounding punctuation (for example, brackets)
*/
function printListItems(list, config, indentation, depth, refs, printer) {
let result = "";
list = list instanceof ArrayBuffer ? new DataView(list) : list;
const isDataView = (l) => l instanceof DataView;
const length = isDataView(list) ? list.byteLength : list.length;
if (length > 0) {
result += config.spacingOuter;
const indentationNext = indentation + config.indent;
for (let i = 0; i < length; i++) {
result += indentationNext;
if (i === config.maxWidth) {
result += `…(${length - i})`;
break;
}
if (isDataView(list) || i in list) result += printer(isDataView(list) ? list.getInt8(i) : list[i], config, indentationNext, depth, refs);
if (i < length - 1) result += `,${config.spacingInner}`;
else if (!config.min) result += ",";
}
result += config.spacingOuter + indentation;
}
return result;
}
/**
* Return properties of an object
* with spacing, indentation, and comma
* without surrounding punctuation (for example, braces)
*/
function printObjectProperties(val, config, indentation, depth, refs, printer, compareKeysOverride = config.compareKeys) {
let result = "";
const keys = getKeysOfEnumerableProperties(val, compareKeysOverride);
if (keys.length > 0) {
result += config.spacingOuter;
const indentationNext = indentation + config.indent;
for (let i = 0; i < keys.length; i++) {
result += indentationNext;
if (i === config.maxWidth) {
result += `…(${keys.length - i})`;
break;
}
const key = keys[i];
const name = !config.quoteKeys && isUnquotableKey(key) ? key : printer(key, config, indentationNext, depth, refs);
const value = printer(val[key], config, indentationNext, depth, refs);
result += `${name}: ${value}`;
if (i < keys.length - 1) result += `,${config.spacingInner}`;
else if (!config.min) result += ",";
}
result += config.spacingOuter + indentation;
}
return result;
}
// https://github.com/nodejs/node/blob/61102cdbb3d59155ad5bb4fc9419627a31e63f7a/lib/internal/util/inspect.js#L249
// /^[a-zA-Z_][a-zA-Z_0-9]*$/
const keyStrRegExp = /^[a-z_]\w*$/i;
function isUnquotableKey(key) {
return typeof key === "string" && key !== "__proto__" && keyStrRegExp.test(key);
}
const asymmetricMatcher = typeof Symbol === "function" && Symbol.for ? Symbol.for("jest.asymmetricMatcher") : 1267621;
const SPACE$2 = " ";
const serialize$5 = (val, config, indentation, depth, refs, printer) => {
const stringedValue = val.toString();
if (stringedValue === "ArrayContaining" || stringedValue === "ArrayNotContaining") {
if (++depth > config.maxDepth) return `[${stringedValue}]`;
return `${stringedValue + SPACE$2}[${printListItems(val.sample, config, indentation, depth, refs, printer)}]`;
}
if (stringedValue === "ObjectContaining" || stringedValue === "ObjectNotContaining") {
if (++depth > config.maxDepth) return `[${stringedValue}]`;
return `${stringedValue + SPACE$2}{${printObjectProperties(val.sample, config, indentation, depth, refs, printer)}}`;
}
if (stringedValue === "StringMatching" || stringedValue === "StringNotMatching") return stringedValue + SPACE$2 + printer(val.sample, config, indentation, depth, refs);
if (stringedValue === "StringContaining" || stringedValue === "StringNotContaining") return stringedValue + SPACE$2 + printer(val.sample, config, indentation, depth, refs);
if (typeof val.toAsymmetricMatcher !== "function") throw new TypeError(`Asymmetric matcher ${val.constructor.name} does not implement toAsymmetricMatcher()`);
return val.toAsymmetricMatcher();
};
const test$5 = (val) => val && val.$$typeof === asymmetricMatcher;
const plugin$5 = {
serialize: serialize$5,
test: test$5
};
const SPACE$1 = " ";
const OBJECT_NAMES = /* @__PURE__ */ new Set(["DOMStringMap", "NamedNodeMap"]);
const ARRAY_REGEXP = /^(?:HTML\w*Collection|NodeList)$/;
function testName(name) {
return OBJECT_NAMES.has(name) || ARRAY_REGEXP.test(name);
}
const test$4 = (val) => val && val.constructor && !!val.constructor.name && testName(val.constructor.name);
function isNamedNodeMap(collection) {
return collection.constructor.name === "NamedNodeMap";
}
const serialize$4 = (collection, config, indentation, depth, refs, printer) => {
const name = collection.constructor.name;
if (++depth > config.maxDepth) return `[${name}]`;
return (config.min ? "" : name + SPACE$1) + (OBJECT_NAMES.has(name) ? `{${printObjectProperties(isNamedNodeMap(collection) ? [...collection].reduce((props, attribute) => {
props[attribute.name] = attribute.value;
return props;
}, {}) : { ...collection }, config, indentation, depth, refs, printer)}}` : `[${printListItems([...collection], config, indentation, depth, refs, printer)}]`);
};
const plugin$4 = {
serialize: serialize$4,
test: test$4
};
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
function escapeHTML(str) {
return str.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
}
// Return empty string if keys is empty.
function printProps(keys, props, config, indentation, depth, refs, printer) {
const indentationNext = indentation + config.indent;
const colors = config.colors;
return keys.map((key) => {
const value = props[key];
// hidden injected value that should not be printed
if (typeof value === "string" && value[0] === "_" && value.startsWith("__vitest_") && /__vitest_\d+__/.test(value)) return "";
let printed = printer(value, config, indentationNext, depth, refs);
if (typeof value !== "string") {
if (printed.includes("\n")) printed = config.spacingOuter + indentationNext + printed + config.spacingOuter + indentation;
printed = `{${printed}}`;
}
return `${config.spacingInner + indentation + colors.prop.open + key + colors.prop.close}=${colors.value.open}${printed}${colors.value.close}`;
}).join("");
}
// Return empty string if children is empty.
function printChildren(children, config, indentation, depth, refs, printer) {
return children.map((child) => config.spacingOuter + indentation + (typeof child === "string" ? printText(child, config) : printer(child, config, indentation, depth, refs))).join("");
}
function printShadowRoot(children, config, indentation, depth, refs, printer) {
if (config.printShadowRoot === false) return "";
return [`${config.spacingOuter + indentation}#shadow-root`, printChildren(children, config, indentation + config.indent, depth, refs, printer)].join("");
}
function printText(text, config) {
const contentColor = config.colors.content;
return contentColor.open + escapeHTML(text) + contentColor.close;
}
function printComment(comment, config) {
const commentColor = config.colors.comment;
return `${commentColor.open}<!--${escapeHTML(comment)}-->${commentColor.close}`;
}
// Separate the functions to format props, children, and element,
// so a plugin could override a particular function, if needed.
// Too bad, so sad: the traditional (but unnecessary) space
// in a self-closing tagColor requires a second test of printedProps.
function printElement(type, printedProps, printedChildren, config, indentation) {
const tagColor = config.colors.tag;
return `${tagColor.open}<${type}${printedProps && tagColor.close + printedProps + config.spacingOuter + indentation + tagColor.open}${printedChildren ? `>${tagColor.close}${printedChildren}${config.spacingOuter}${indentation}${tagColor.open}</${type}` : `${printedProps && !config.min ? "" : " "}/`}>${tagColor.close}`;
}
function printElementAsLeaf(type, config) {
const tagColor = config.colors.tag;
return `${tagColor.open}<${type}${tagColor.close} …${tagColor.open} />${tagColor.close}`;
}
const ELEMENT_NODE = 1;
const TEXT_NODE = 3;
const COMMENT_NODE = 8;
const FRAGMENT_NODE = 11;
const ELEMENT_REGEXP = /^(?:(?:HTML|SVG)\w*)?Element$/;
function testHasAttribute(val) {
try {
return typeof val.hasAttribute === "function" && val.hasAttribute("is");
} catch {
return false;
}
}
function testNode(val) {
const constructorName = val.constructor.name;
const { nodeType, tagName } = val;
const isCustomElement = typeof tagName === "string" && tagName.includes("-") || testHasAttribute(val);
return nodeType === ELEMENT_NODE && (ELEMENT_REGEXP.test(constructorName) || isCustomElement) || nodeType === TEXT_NODE && constructorName === "Text" || nodeType === COMMENT_NODE && constructorName === "Comment" || nodeType === FRAGMENT_NODE && constructorName === "DocumentFragment";
}
const test$3 = (val) => val?.constructor?.name && testNode(val);
function nodeIsText(node) {
return node.nodeType === TEXT_NODE;
}
function nodeIsComment(node) {
return node.nodeType === COMMENT_NODE;
}
function nodeIsFragment(node) {
return node.nodeType === FRAGMENT_NODE;
}
function filterChildren(children, filterNode) {
// Filter out text nodes that only contain whitespace to prevent empty lines
// This is done regardless of whether a filterNode is provided
let filtered = children.filter((node) => {
// Filter out text nodes that are only whitespace
if (node.nodeType === TEXT_NODE)
// Keep text nodes that have non-whitespace content
return (node.data || "").trim().length > 0;
return true;
});
// Apply additional user-provided filter if specified
if (filterNode) filtered = filtered.filter(filterNode);
return filtered;
}
function serializeDOM(node, config, indentation, depth, refs, printer, filterNode) {
if (nodeIsText(node)) return printText(node.data, config);
if (nodeIsComment(node)) return printComment(node.data, config);
const type = nodeIsFragment(node) ? "DocumentFragment" : node.tagName.toLowerCase();
if (++depth > config.maxDepth) return printElementAsLeaf(type, config);
const children = Array.prototype.slice.call(node.childNodes || node.children);
const shadowChildren = nodeIsFragment(node) || !node.shadowRoot ? [] : Array.prototype.slice.call(node.shadowRoot.children);
const resolvedChildren = filterNode ? filterChildren(children, filterNode) : children;
const resolvedShadowChildren = filterNode ? filterChildren(shadowChildren, filterNode) : shadowChildren;
return printElement(type, printProps(nodeIsFragment(node) ? [] : Array.from(node.attributes, (attr) => attr.name).sort(), nodeIsFragment(node) ? {} : [...node.attributes].reduce((props, attribute) => {
props[attribute.name] = attribute.value;
return props;
}, {}), config, indentation + config.indent, depth, refs, printer), (resolvedShadowChildren.length > 0 ? printShadowRoot(resolvedShadowChildren, config, indentation + config.indent, depth, refs, printer) : "") + printChildren(resolvedChildren, config, indentation + config.indent, depth, refs, printer), config, indentation);
}
const serialize$3 = (node, config, indentation, depth, refs, printer) => serializeDOM(node, config, indentation, depth, refs, printer);
function createDOMElementFilter(filterNode) {
return {
test: test$3,
serialize: (node, config, indentation, depth, refs, printer) => serializeDOM(node, config, indentation, depth, refs, printer, filterNode)
};
}
const plugin$3 = {
serialize: serialize$3,
test: test$3
};
// SENTINEL constants are from https://github.com/immutable-js/immutable-js
const IS_ITERABLE_SENTINEL = "@@__IMMUTABLE_ITERABLE__@@";
const IS_LIST_SENTINEL = "@@__IMMUTABLE_LIST__@@";
const IS_KEYED_SENTINEL = "@@__IMMUTABLE_KEYED__@@";
const IS_MAP_SENTINEL = "@@__IMMUTABLE_MAP__@@";
const IS_ORDERED_SENTINEL = "@@__IMMUTABLE_ORDERED__@@";
const IS_RECORD_SENTINEL = "@@__IMMUTABLE_RECORD__@@";
const IS_SEQ_SENTINEL = "@@__IMMUTABLE_SEQ__@@";
const IS_SET_SENTINEL = "@@__IMMUTABLE_SET__@@";
const IS_STACK_SENTINEL = "@@__IMMUTABLE_STACK__@@";
const getImmutableName = (name) => `Immutable.${name}`;
const printAsLeaf = (name) => `[${name}]`;
const SPACE = " ";
const LAZY = "…";
function printImmutableEntries(val, config, indentation, depth, refs, printer, type) {
return ++depth > config.maxDepth ? printAsLeaf(getImmutableName(type)) : `${getImmutableName(type) + SPACE}{${printIteratorEntries(val.entries(), config, indentation, depth, refs, printer)}}`;
}
// Record has an entries method because it is a collection in immutable v3.
// Return an iterator for Immutable Record from version v3 or v4.
function getRecordEntries(val) {
let i = 0;
return { next() {
if (i < val._keys.length) {
const key = val._keys[i++];
return {
done: false,
value: [key, val.get(key)]
};
}
return {
done: true,
value: void 0
};
} };
}
function printImmutableRecord(val, config, indentation, depth, refs, printer) {
// _name property is defined only for an Immutable Record instance
// which was constructed with a second optional descriptive name arg
const name = getImmutableName(val._name || "Record");
return ++depth > config.maxDepth ? printAsLeaf(name) : `${name + SPACE}{${printIteratorEntries(getRecordEntries(val), config, indentation, depth, refs, printer)}}`;
}
function printImmutableSeq(val, config, indentation, depth, refs, printer) {
const name = getImmutableName("Seq");
if (++depth > config.maxDepth) return printAsLeaf(name);
if (val[IS_KEYED_SENTINEL]) return `${name + SPACE}{${
// from Immutable collection of entries or from ECMAScript object
val._iter || val._object ? printIteratorEntries(val.entries(), config, indentation, depth, refs, printer) : LAZY}}`;
return `${name + SPACE}[${val._iter || val._array || val._collection || val._iterable ? printIteratorValues(val.values(), config, indentation, depth, refs, printer) : LAZY}]`;
}
function printImmutableValues(val, config, indentation, depth, refs, printer, type) {
return ++depth > config.maxDepth ? printAsLeaf(getImmutableName(type)) : `${getImmutableName(type) + SPACE}[${printIteratorValues(val.values(), config, indentation, depth, refs, printer)}]`;
}
const serialize$2 = (val, config, indentation, depth, refs, printer) => {
if (val[IS_MAP_SENTINEL]) return printImmutableEntries(val, config, indentation, depth, refs, printer, val[IS_ORDERED_SENTINEL] ? "OrderedMap" : "Map");
if (val[IS_LIST_SENTINEL]) return printImmutableValues(val, config, indentation, depth, refs, printer, "List");
if (val[IS_SET_SENTINEL]) return printImmutableValues(val, config, indentation, depth, refs, printer, val[IS_ORDERED_SENTINEL] ? "OrderedSet" : "Set");
if (val[IS_STACK_SENTINEL]) return printImmutableValues(val, config, indentation, depth, refs, printer, "Stack");
if (val[IS_SEQ_SENTINEL]) return printImmutableSeq(val, config, indentation, depth, refs, printer);
// For compatibility with immutable v3 and v4, let record be the default.
return printImmutableRecord(val, config, indentation, depth, refs, printer);
};
// Explicitly comparing sentinel properties to true avoids false positive
// when mock identity-obj-proxy returns the key as the value for any key.
const test$2 = (val) => val && (val[IS_ITERABLE_SENTINEL] === true || val[IS_RECORD_SENTINEL] === true);
const plugin$2 = {
serialize: serialize$2,
test: test$2
};
function getDefaultExportFromCjs(x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
}
var reactIs$1 = { exports: {} };
var reactIs_production = {};
/**
* @license React
* react-is.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var hasRequiredReactIs_production;
function requireReactIs_production() {
if (hasRequiredReactIs_production) return reactIs_production;
hasRequiredReactIs_production = 1;
var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"), REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_VIEW_TRANSITION_TYPE = Symbol.for("react.view_transition"), REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference");
function typeOf(object) {
if ("object" === typeof object && null !== object) {
var $$typeof = object.$$typeof;
switch ($$typeof) {
case REACT_ELEMENT_TYPE: switch (object = object.type, object) {
case REACT_FRAGMENT_TYPE:
case REACT_PROFILER_TYPE:
case REACT_STRICT_MODE_TYPE:
case REACT_SUSPENSE_TYPE:
case REACT_SUSPENSE_LIST_TYPE:
case REACT_VIEW_TRANSITION_TYPE: return object;
default: switch (object = object && object.$$typeof, object) {
case REACT_CONTEXT_TYPE:
case REACT_FORWARD_REF_TYPE:
case REACT_LAZY_TYPE:
case REACT_MEMO_TYPE: return object;
case REACT_CONSUMER_TYPE: return object;
default: return $$typeof;
}
}
case REACT_PORTAL_TYPE: return $$typeof;
}
}
}
reactIs_production.ContextConsumer = REACT_CONSUMER_TYPE;
reactIs_production.ContextProvider = REACT_CONTEXT_TYPE;
reactIs_production.Element = REACT_ELEMENT_TYPE;
reactIs_production.ForwardRef = REACT_FORWARD_REF_TYPE;
reactIs_production.Fragment = REACT_FRAGMENT_TYPE;
reactIs_production.Lazy = REACT_LAZY_TYPE;
reactIs_production.Memo = REACT_MEMO_TYPE;
reactIs_production.Portal = REACT_PORTAL_TYPE;
reactIs_production.Profiler = REACT_PROFILER_TYPE;
reactIs_production.StrictMode = REACT_STRICT_MODE_TYPE;
reactIs_production.Suspense = REACT_SUSPENSE_TYPE;
reactIs_production.SuspenseList = REACT_SUSPENSE_LIST_TYPE;
reactIs_production.isContextConsumer = function(object) {
return typeOf(object) === REACT_CONSUMER_TYPE;
};
reactIs_production.isContextProvider = function(object) {
return typeOf(object) === REACT_CONTEXT_TYPE;
};
reactIs_production.isElement = function(object) {
return "object" === typeof object && null !== object && object.$$typeof === REACT_ELEMENT_TYPE;
};
reactIs_production.isForwardRef = function(object) {
return typeOf(object) === REACT_FORWARD_REF_TYPE;
};
reactIs_production.isFragment = function(object) {
return typeOf(object) === REACT_FRAGMENT_TYPE;
};
reactIs_production.isLazy = function(object) {
return typeOf(object) === REACT_LAZY_TYPE;
};
reactIs_production.isMemo = function(object) {
return typeOf(object) === REACT_MEMO_TYPE;
};
reactIs_production.isPortal = function(object) {
return typeOf(object) === REACT_PORTAL_TYPE;
};
reactIs_production.isProfiler = function(object) {
return typeOf(object) === REACT_PROFILER_TYPE;
};
reactIs_production.isStrictMode = function(object) {
return typeOf(object) === REACT_STRICT_MODE_TYPE;
};
reactIs_production.isSuspense = function(object) {
return typeOf(object) === REACT_SUSPENSE_TYPE;
};
reactIs_production.isSuspenseList = function(object) {
return typeOf(object) === REACT_SUSPENSE_LIST_TYPE;
};
reactIs_production.isValidElementType = function(type) {
return "string" === typeof type || "function" === typeof type || type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || "object" === typeof type && null !== type && (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_CONSUMER_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || type.$$typeof === REACT_CLIENT_REFERENCE || void 0 !== type.getModuleId) ? true : false;
};
reactIs_production.typeOf = typeOf;
return reactIs_production;
}
var hasRequiredReactIs$1;
function requireReactIs$1() {
if (hasRequiredReactIs$1) return reactIs$1.exports;
hasRequiredReactIs$1 = 1;
reactIs$1.exports = requireReactIs_production();
return reactIs$1.exports;
}
var reactIsExports$1 = requireReactIs$1();
var ReactIs19 = /*#__PURE__*/ _mergeNamespaces$1({
__proto__: null,
default: /* @__PURE__ */ getDefaultExportFromCjs(reactIsExports$1)
}, [reactIsExports$1]);
var reactIs = { exports: {} };
var reactIs_production_min = {};
/**
* @license React
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var hasRequiredReactIs_production_min;
function requireReactIs_production_min() {
if (hasRequiredReactIs_production_min) return reactIs_production_min;
hasRequiredReactIs_production_min = 1;
var b = Symbol.for("react.element"), c = Symbol.for("react.portal"), d = Symbol.for("react.fragment"), e = Symbol.for("react.strict_mode"), f = Symbol.for("react.profiler"), g = Symbol.for("react.provider"), h = Symbol.for("react.context"), k = Symbol.for("react.server_context"), l = Symbol.for("react.forward_ref"), m = Symbol.for("react.suspense"), n = Symbol.for("react.suspense_list"), p = Symbol.for("react.memo"), q = Symbol.for("react.lazy"), t = Symbol.for("react.offscreen"), u = Symbol.for("react.module.reference");
function v(a) {
if ("object" === typeof a && null !== a) {
var r = a.$$typeof;
switch (r) {
case b: switch (a = a.type, a) {
case d:
case f:
case e:
case m:
case n: return a;
default: switch (a = a && a.$$typeof, a) {
case k:
case h:
case l:
case q:
case p:
case g: return a;
default: return r;
}
}
case c: return r;
}
}
}
reactIs_production_min.ContextConsumer = h;
reactIs_production_min.ContextProvider = g;
reactIs_production_min.Element = b;
reactIs_production_min.ForwardRef = l;
reactIs_production_min.Fragment = d;
reactIs_production_min.Lazy = q;
reactIs_production_min.Memo = p;
reactIs_production_min.Portal = c;
reactIs_production_min.Profiler = f;
reactIs_production_min.StrictMode = e;
reactIs_production_min.Suspense = m;
reactIs_production_min.SuspenseList = n;
reactIs_production_min.isAsyncMode = function() {
return false;
};
reactIs_production_min.isConcurrentMode = function() {
return false;
};
reactIs_production_min.isContextConsumer = function(a) {
return v(a) === h;
};
reactIs_production_min.isContextProvider = function(a) {
return v(a) === g;
};
reactIs_production_min.isElement = function(a) {
return "object" === typeof a && null !== a && a.$$typeof === b;
};
reactIs_production_min.isForwardRef = function(a) {
return v(a) === l;
};
reactIs_production_min.isFragment = function(a) {
return v(a) === d;
};
reactIs_production_min.isLazy = function(a) {
return v(a) === q;
};
reactIs_production_min.isMemo = function(a) {
return v(a) === p;
};
reactIs_production_min.isPortal = function(a) {
return v(a) === c;
};
reactIs_production_min.isProfiler = function(a) {
return v(a) === f;
};
reactIs_production_min.isStrictMode = function(a) {
return v(a) === e;
};
reactIs_production_min.isSuspense = function(a) {
return v(a) === m;
};
reactIs_production_min.isSuspenseList = function(a) {
return v(a) === n;
};
reactIs_production_min.isValidElementType = function(a) {
return "string" === typeof a || "function" === typeof a || a === d || a === f || a === e || a === m || a === n || a === t || "object" === typeof a && null !== a && (a.$$typeof === q || a.$$typeof === p || a.$$typeof === g || a.$$typeof === h || a.$$typeof === l || a.$$typeof === u || void 0 !== a.getModuleId) ? true : false;
};
reactIs_production_min.typeOf = v;
return reactIs_production_min;
}
var hasRequiredReactIs;
function requireReactIs() {
if (hasRequiredReactIs) return reactIs.exports;
hasRequiredReactIs = 1;
reactIs.exports = requireReactIs_production_min();
return reactIs.exports;
}
var reactIsExports = requireReactIs();
var ReactIs18 = /*#__PURE__*/ _mergeNamespaces$1({
__proto__: null,
default: /* @__PURE__ */ getDefaultExportFromCjs(reactIsExports)
}, [reactIsExports]);
const ReactIs = Object.fromEntries([
"isAsyncMode",
"isConcurrentMode",
"isContextConsumer",
"isContextProvider",
"isElement",
"isForwardRef",
"isFragment",
"isLazy",
"isMemo",
"isPortal",
"isProfiler",
"isStrictMode",
"isSuspense",
"isSuspenseList",
"isValidElementType"
].map((m) => [m, (v) => ReactIs18[m](v) || ReactIs19[m](v)]));
// Given element.props.children, or subtree during recursive traversal,
// return flattened array of children.
function getChildren(arg, children = []) {
if (Array.isArray(arg)) for (const item of arg) getChildren(item, children);
else if (arg != null && arg !== false && arg !== "") children.push(arg);
return children;
}
function getType(element) {
const type = element.type;
if (typeof type === "string") return type;
if (typeof type === "function") return type.displayName || type.name || "Unknown";
if (ReactIs.isFragment(element)) return "React.Fragment";
if (ReactIs.isSuspense(element)) return "React.Suspense";
if (typeof type === "object" && type !== null) {
if (ReactIs.isContextProvider(element)) return "Context.Provider";
if (ReactIs.isContextConsumer(element)) return "Context.Consumer";
if (ReactIs.isForwardRef(element)) {
if (type.displayName) return type.displayName;
const functionName = type.render.displayName || type.render.name || "";
return functionName === "" ? "ForwardRef" : `ForwardRef(${functionName})`;
}
if (ReactIs.isMemo(element)) {
const functionName = type.displayName || type.type.displayName || type.type.name || "";
return functionName === "" ? "Memo" : `Memo(${functionName})`;
}
}
return "UNDEFINED";
}
function getPropKeys$1(element) {
const { props } = element;
return Object.keys(props).filter((key) => key !== "children" && props[key] !== void 0).sort();
}
const serialize$1 = (element, config, indentation, depth, refs, printer) => ++depth > config.maxDepth ? printElementAsLeaf(getType(element), config) : printElement(getType(element), printProps(getPropKeys$1(element), element.props, config, indentation + config.indent, depth, refs, printer), printChildren(getChildren(element.props.children), config, indentation + config.indent, depth, refs, printer), config, indentation);
const test$1 = (val) => val != null && ReactIs.isElement(val);
const plugin$1 = {
serialize: serialize$1,
test: test$1
};
const testSymbol = typeof Symbol === "function" && Symbol.for ? Symbol.for("react.test.json") : 245830487;
function getPropKeys(object) {
const { props } = object;
return props ? Object.keys(props).filter((key) => props[key] !== void 0).sort() : [];
}
const serialize = (object, config, indentation, depth, refs, printer) => ++depth > config.maxDepth ? printElementAsLeaf(object.type, config) : printElement(object.type, object.props ? printProps(getPropKeys(object), object.props, config, indentation + config.indent, depth, refs, printer) : "", object.children ? printChildren(object.children, config, indentation + config.indent, depth, refs, printer) : "", config, indentation);
const test = (val) => val && val.$$typeof === testSymbol;
const plugin = {
serialize,
test
};
const toString = Object.prototype.toString;
const toISOString = Date.prototype.toISOString;
const errorToString = Error.prototype.toString;
const regExpToString = RegExp.prototype.toString;
/**
* Explicitly comparing typeof constructor to function avoids undefined as name
* when mock identity-obj-proxy returns the key as the value for any key.
*/
function getConstructorName(val) {
return typeof val.constructor === "function" && val.constructor.name || "Object";
}
/** Is val is equal to global window object? Works even if it does not exist :) */
function isWindow(val) {
return typeof window !== "undefined" && val === window;
}
// eslint-disable-next-line regexp/no-super-linear-backtracking
const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/;
const NEWLINE_REGEXP = /\n/g;
class PrettyFormatPluginError extends Error {
constructor(message, stack) {
super(message);
this.stack = stack;
this.name = this.constructor.name;
}
}
function isToStringedArrayType(toStringed) {
return toStringed === "[object Array]" || toStringed === "[object ArrayBuffer]" || toStringed === "[object DataView]" || toStringed === "[object Float32Array]" || toStringed === "[object Float64Array]" || toStringed === "[object Int8Array]" || toStringed === "[object Int16Array]" || toStringed === "[object Int32Array]" || toStringed === "[object Uint8Array]" || toStringed === "[object Uint8ClampedArray]" || toStringed === "[object Uint16Array]" || toStringed === "[object Uint32Array]";
}
function printNumber(val) {
return Object.is(val, -0) ? "-0" : String(val);
}
function printBigInt(val) {
return String(`${val}n`);
}
function printFunction(val, printFunctionName) {
if (!printFunctionName) return "[Function]";
return `[Function ${val.name || "anonymous"}]`;
}
function printSymbol(val) {
return String(val).replace(SYMBOL_REGEXP, "Symbol($1)");
}
function printError(val) {
return `[${errorToString.call(val)}]`;
}
/**
* The first port of call for printing an object, handles most of the
* data-types in JS.
*/
function printBasicValue(val, printFunctionName, escapeRegex, escapeString, singleQuote) {
if (val === true || val === false) return `${val}`;
if (val === void 0) return "undefined";
if (val === null) return "null";
const typeOf = typeof val;
if (typeOf === "number") return printNumber(val);
if (typeOf === "bigint") return printBigInt(val);
if (typeOf === "string") {
const q = singleQuote ? "'" : "\"";
if (escapeString) {
// escape quote in each case, e.g.
// it's me -> 'it\'s me'
// say "hi" -> "say \"hi\""
const escapePattern = singleQuote ? /['\\]/g : /["\\]/g;
return `${q}${val.replaceAll(escapePattern, "\\$&")}${q}`;
}
return `${q}${val}${q}`;
}
if (typeOf === "function") return printFunction(val, printFunctionName);
if (typeOf === "symbol") return printSymbol(val);
const toStringed = toString.call(val);
if (toStringed === "[object WeakMap]") return "WeakMap {}";
if (toStringed === "[object WeakSet]") return "WeakSet {}";
if (toStringed === "[object Function]" || toStringed === "[object GeneratorFunction]") return printFunction(val, printFunctionName);
if (toStringed === "[object Symbol]") return printSymbol(val);
if (toStringed === "[object Date]") return Number.isNaN(+val) ? "Date { NaN }" : toISOString.call(val);
if (toStringed === "[object Error]") return printError(val);
if (toStringed === "[object RegExp]") {
if (escapeRegex)
// https://github.com/benjamingr/RegExp.escape/blob/main/polyfill.js
return regExpToString.call(val).replaceAll(/[$()*+.?[\\\]^{|}]/g, "\\$&");
return regExpToString.call(val);
}
if (val instanceof Error) return printError(val);
return null;
}
/**
* Handles more complex objects ( such as objects with circular references.
* maps and sets etc )
*/
function printComplexValue(val, config, indentation, depth, refs, hasCalledToJSON) {
if (refs.includes(val)) return "[Circular]";
refs = [...refs];
refs.push(val);
const hitMaxDepth = ++depth > config.maxDepth;
const min = config.min;
if (config.callToJSON && !hitMaxDepth && val.toJSON && typeof val.toJSON === "function" && !hasCalledToJSON) return printer(val.toJSON(), config, indentation, depth, refs, true);
const toStringed = toString.call(val);
if (toStringed === "[object Arguments]") return hitMaxDepth ? "[Arguments]" : `${min ? "" : "Arguments "}[${printListItems(val, config, indentation, depth, refs, printer)}]`;
if (isToStringedArrayType(toStringed)) return hitMaxDepth ? `[${val.constructor.name}]` : `${!config.printBasicPrototype && val.constructor.name === "Array" ? "" : `${val.constructor.name} `}[${printListItems(val, config, indentation, depth, refs, printer)}]`;
if (toStringed === "[object Map]") return hitMaxDepth ? "[Map]" : `Map {${printIteratorEntries(val.entries(), config, indentation, depth, refs, printer, " => ", val.size)}}`;
if (toStringed === "[object Set]") return hitMaxDepth ? "[Set]" : `Set {${printIteratorValues(val.values(), config, indentation, depth, refs, printer, val.size)}}`;
// Avoid failure to serialize global window object in jsdom test environment.
// For example, not even relevant if window is prop of React element.
return hitMaxDepth || isWindow(val) ? `[${getConstructorName(val)}]` : `${!config.printBasicPrototype && getConstructorName(val) === "Object" ? "" : `${getConstructorName(val)} `}{${printObjectProperties(val, config, indentation, depth, refs, printer)}}`;
}
const ErrorPlugin = {
test: (val) => val && val instanceof Error,
serialize(val, config, indentation, depth, refs, printer) {
if (refs.includes(val)) return "[Circular]";
refs = [...refs, val];
const hitMaxDepth = ++depth > config.maxDepth;
const { message, cause, ...rest } = val;
const entries = {
message,
...typeof cause !== "undefined" ? { cause } : {},
...val instanceof AggregateError ? { errors: val.errors } : {},
...rest
};
const name = val.name !== "Error" ? val.name : getConstructorName(val);
return hitMaxDepth ? `[${name}]` : `${name} {${printObjectProperties(entries, config, indentation, depth, refs, printer, null)}}`;
}
};
function isNewPlugin(plugin) {
return plugin.serialize != null;
}
function printPlugin(plugin, val, config, indentation, depth, refs) {
let printed;
try {
printed = isNewPlugin(plugin) ? plugin.serialize(val, config, indentation, depth, refs, printer) : plugin.print(val, (valChild) => printer(valChild, config, indentation, depth, refs), (str) => {
const indentationNext = indentation + config.indent;
return indentationNext + str.replaceAll(NEWLINE_REGEXP, `\n${indentationNext}`);
}, {
edgeSpacing: config.spacingOuter,
min: config.min,
spacing: config.spacingInner
}, config.colors);
} catch (error) {
throw new PrettyFormatPluginError(error.message, error.stack);
}
if (typeof printed !== "string") throw new TypeError(`pretty-format: Plugin must return type "string" but instead returned "${typeof printed}".`);
return printed;
}
function findPlugin(plugins, val) {
for (const plugin of plugins) try {
if (plugin.test(val)) return plugin;
} catch (error) {
throw new PrettyFormatPluginError(error.message, error.stack);
}
return null;
}
function printer(val, config, indentation, depth, refs, hasCalledToJSON) {
let result;
const plugin = findPlugin(config.plugins, val);
if (plugin !== null) result = printPlugin(plugin, val, config, indentation, depth, refs);
else {
const basicResult = printBasicValue(val, config.printFunctionName, config.escapeRegex, config.escapeString, config.singleQuote);
if (basicResult !== null) result = basicResult;
else result = printComplexValue(val, config, indentation, depth, refs, hasCalledToJSON);
}
// Per-depth output budget (inspired by Node's util.inspect).
// Each depth level tracks output independently, so nested results
// don't inflate a single counter (which would undercount by ~Nx for
// N levels of nesting). Nodes at the same depth produce disjoint spans
// in the output string, so each bucket accurately reflects output at
// that level. Total output is bounded by maxDepth × maxOutputLength.
config._outputLengthPerDepth[depth] ??= 0;
config._outputLengthPerDepth[depth] += result.length;
if (config._outputLengthPerDepth[depth] > config.maxOutputLength) config.maxDepth = 0;
return result;
}
const DEFAULT_THEME = {
comment: "gray",
content: "reset",
prop: "yellow",
tag: "cyan",
value: "green"
};
const DEFAULT_THEME_KEYS = Object.keys(DEFAULT_THEME);
const DEFAULT_OPTIONS = {
callToJSON: true,
compareKeys: void 0,
escapeRegex: false,
escapeString: true,
highlight: false,
indent: 2,
maxDepth: Number.POSITIVE_INFINITY,
// Practical default hard-limit to avoid too long string being generated
// (Node's limit is buffer.constants.MAX_STRING_LENGTH ~ 512MB)
maxOutputLength: 1e6,
maxWidth: Number.POSITIVE_INFINITY,
min: false,
plugins: [],
printBasicPrototype: true,
printFunctionName: true,
printShadowRoot: true,
theme: DEFAULT_THEME,
singleQuote: false,
quoteKeys: true,
spacingInner: "\n",
spacingOuter: "\n"
};
function validateOptions(options) {
for (const key of Object.keys(options)) if (!Object.hasOwn(DEFAULT_OPTIONS, key)) throw new Error(`pretty-format: Unknown option "${key}".`);
if (options.min && options.indent !== void 0 && options.indent !== 0) throw new Error("pretty-format: Options \"min\" and \"indent\" cannot be used together.");
}
function getColorsHighlight() {
return DEFAULT_THEME_KEYS.reduce((colors, key) => {
const value = DEFAULT_THEME[key];
const color = value && y[value];
if (color && typeof color.close === "string" && typeof color.open === "string") colors[key] = color;
else throw new Error(`pretty-format: Option "theme" has a key "${key}" whose value "${value}" is undefined in ansi-styles.`);
return colors;
}, Object.create(null));
}
function getColorsEmpty() {
return DEFAULT_THEME_KEYS.reduce((colors, key) => {
colors[key] = {
close: "",
open: ""
};
return colors;
}, Object.create(null));
}
function getPrintFunctionName(options) {
return options?.printFunctionName ?? DEFAULT_OPTIONS.printFunctionName;
}
function getEscapeRegex(options) {
return options?.escapeRegex ?? DEFAULT_OPTIONS.escapeRegex;
}
function getEscapeString(options) {
return options?.escapeString ?? DEFAULT_OPTIONS.escapeString;
}
function getConfig(options) {
return {
callToJSON: options?.callToJSON ?? DEFAULT_OPTIONS.callToJSON,
colors: options?.highlight ? getColorsHighlight() : getColorsEmpty(),
compareKeys: typeof options?.compareKeys === "function" || options?.compareKeys === null ? options.compareKeys : DEFAULT_OPTIONS.compareKeys,
escapeRegex: getEscapeRegex(options),
escapeString: getEscapeString(options),
indent: options?.min ? "" : createIndent(options?.indent ?? DEFAULT_OPTIONS.indent),
maxDepth: options?.maxDepth ?? DEFAULT_OPTIONS.maxDepth,
maxWidth: options?.maxWidth ?? DEFAULT_OPTIONS.maxWidth,
min: options?.min ?? DEFAULT_OPTIONS.min,
plugins: options?.plugins ?? DEFAULT_OPTIONS.plugins,
printBasicPrototype: options?.printBasicPrototype ?? !options?.min,
printFunctionName: getPrintFunctionName(options),
printShadowRoot: options?.printShadowRoot ?? true,
spacingInner: options?.spacingInner ?? (options?.min ? " " : "\n"),
spacingOuter: options?.spacingOuter ?? (options?.min ? "" : "\n"),
singleQuote: options?.singleQuote ?? DEFAULT_OPTIONS.singleQuote,
quoteKeys: options?.quoteKeys ?? DEFAULT_OPTIONS.quoteKeys,
maxOutputLength: options?.maxOutputLength ?? DEFAULT_OPTIONS.maxOutputLength,
_outputLengthPerDepth: []
};
}
function createIndent(indent) {
return Array.from({ length: indent + 1 }).join(" ");
}
/**
* Returns a presentation string of your `val` object
* @param val any potential JavaScript object
* @param options Custom settings
*/
function format(val, options) {
if (options) validateOptions(options);
const config = getConfig(options);
const plugin = findPlugin(config.plugins, val);
if (plugin !== null) return printPlugin(plugin, val, config, "", 0, []);
const basicResult = printBasicValue(val, config.printFunctionName, config.escapeRegex, config.escapeString, config.singleQuote);
if (basicResult !== null) return basicResult;
return printComplexValue(val, config, "", 0, []);
}
const plugins = {
AsymmetricMatcher: plugin$5,
DOMCollection: plugin$4,
DOMElement: plugin$3,
Immutable: plugin$2,
ReactElement: plugin$1,
ReactTestComponent: plugin,
Error: ErrorPlugin
};
export { createDOMElementFilter as c, format as f, plugins as p };
File diff suppressed because it is too large. Load diff
+38
View File
@@ -0,0 +1,38 @@
import { i as init } from './init.BS957yFf.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);
// the guard installed by the test runner stays active between test
// files: with `isolate: false` a late process.exit would kill the
// other files sharing this process
process.exit = processExit;
},
runTests: (state, traces) => runTests("run", state, traces),
collectTests: (state, traces) => runTests("collect", state, traces),
setup: options.setup
});
}
// 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 };
+18
View File
@@ -0,0 +1,18 @@
import { isMainThread, parentPort } from 'node:worker_threads';
import { i as init } from './init.BS957yFf.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 };
+450
View File
@@ -0,0 +1,450 @@
import { readFileSync } from 'node:fs';
import module$1, { isBuiltin } from 'node:module';
import { pathToFileURL } from 'node:url';
import { EvaluatedModules, ModuleRunner } from 'vite/module-runner';
import { e as environments, b as VitestTransport } from './index.1_nbEjJY.js';
import { r as resolve } from './pathe.M-eThtNZ.Clq7AXhv.js';
import { b as serializeValue, c as createStackString, p as parseStacktrace } from './source-map.Dx3YPjIe.js';
import './index.M2dsQ_UQ.js';
import { m } from './tinyrainbow.Ht9iggcq.js';
import { Traces } from '../traces.js';
import { o as onCancel, V as VitestEvaluatedModules, a as rpcDone, c as createRuntimeRpc } from './rpc.DWKX1ZbI.js';
import { s as setupInspect } from './inspector.CvyFGlXm.js';
import { E as EnvironmentTeardownError } from './utils.DYj33du9.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 evaluatedModules = new EvaluatedModules();
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__");
}
}, 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(Array.from(cleanupListeners, (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);
});
}
class GetterTracker {
static EXPORTS_MAX_INVOCATIONS = 1e6;
invocations = /* @__PURE__ */ new Map();
excessiveInvocations = /* @__PURE__ */ new Map();
createTracker(moduleId, defineExport) {
return (name, getter) => {
const key = `${moduleId}:${name}`;
defineExport(name, () => {
const count = (this.invocations.get(key) || 0) + 1;
this.invocations.set(key, count);
if (count > GetterTracker.EXPORTS_MAX_INVOCATIONS && !this.excessiveInvocations.has(key)) this.excessiveInvocations.set(key, {
moduleId,
exportName: name
});
return getter();
});
};
}
resetInvocations() {
this.invocations.clear();
this.excessiveInvocations.clear();
}
getExcessiveInvocations() {
return [...this.excessiveInvocations.values()];
}
}
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,
// here we create a new one, workers can reassign this if they need to keep it non-isolated
evaluatedModules: new VitestEvaluatedModules(),
resolvingModules,
moduleExecutionInfo: /* @__PURE__ */ new Map(),
config: ctx.config,
// this is set later by vm or base
environment: null,
durations: {
environment: 0,
prepare: prepareStart,
fetch: 0
},
rpc,
onCancel,
onCleanup: onCleanup,
providedContext: ctx.providedContext,
onFilterStackTrace(stack) {
return createStackString(parseStacktrace(stack));
},
metaEnv: ctx.metaEnv,
getterTracker: ctx.config.benchmark.enabled && !ctx.config.benchmark.suppressExportGetterWarnings ? new GetterTracker() : void 0
};
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();
}
// default import: `flushCompileCache` only exists since Node 22.10, a named
// import would fail to link on older versions
function createImportMetaEnvProxy() {
const booleanKeys = [
"DEV",
"PROD",
"SSR"
];
return new Proxy(process.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 importMetaEnvProxy = createImportMetaEnvProxy();
const __vitest_worker_response__ = true;
const memoryUsage = process.memoryUsage.bind(process);
let reportMemory = false;
const streams = [{ write: process.stdout.write.bind(process.stdout) }, { write: process.stderr.write.bind(process.stderr) }];
// In worker threads stdio is proxied to the parent over a MessagePort with a
// backpressure protocol: a chunk stays buffered inside the worker until the
// parent acks the previous one. The pool starts `runner.stop()` as soon as it
// receives `testfileFinished`, and `thread.terminate()` halts the worker before
// buffered chunks are ever posted, losing output. An empty write's callback
// only fires after every previously buffered chunk has been acked, so awaiting
// it before signaling completion guarantees the output reached the parent.
// A cheap no-op for forks, where stdio goes through OS pipes.
function flushStdio() {
const flush = (stream) => new Promise((resolve) => {
try {
stream.write("", () => resolve(void 0));
} catch {
resolve(void 0);
}
});
return Promise.all(streams.map((stream) => flush(stream)));
}
let traces;
/** @experimental */
function init(worker) {
let runPromise;
let isRunning = false;
let workerTeardown;
let setupContext;
let poolId;
worker.on(onMessage);
if (worker.onModuleRunner) onModuleRunner(worker.onModuleRunner);
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;
poolId = message.poolId;
if (message.context.config.disableColors) m();
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),
metaEnv: importMetaEnvProxy,
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: serializeValue(error)
});
}
break;
}
case "run":
// Prevent concurrent execution if worker is already running
if (isRunning) {
send({
type: "testfileFinished",
__vitest_worker_response__,
error: serializeValue(/* @__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: serializeValue(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,
concurrencyId: poolId
}, worker, traces).catch((error) => serializeValue(error)));
const error = await runPromise;
await flushStdio();
send({
type: "testfileFinished",
__vitest_worker_response__,
error,
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: serializeValue(/* @__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: serializeValue(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,
concurrencyId: poolId
}, worker, traces).catch((error) => serializeValue(error)));
const error = await runPromise;
await flushStdio();
send({
type: "testfileFinished",
__vitest_worker_response__,
error,
usedMemory: reportMemory ? memoryUsage().heapUsed : void 0
});
} finally {
runPromise = void 0;
isRunning = false;
}
break;
case "stop": {
await runPromise;
// Persist this worker's compile cache before the parent tears the
// worker down — forks are SIGTERM'd and never reach Node's exit-time
// flush, so without this the cache stays write-only for them. Runs
// even when teardown throws (the compiled modules are still worth
// persisting). A no-op when the cache is disabled or was fully loaded
// from disk, and cheap (~tens of ms) otherwise, so every worker can
// afford it.
const persistCompileCache = () => {
try {
module$1.flushCompileCache?.();
} catch {}
};
try {
const context = traces.getContextFromCarrier(message.otelCarrier);
const error = await traces.$("vitest.runtime.teardown", { context }, async () => {
const error = await teardown().catch((error) => serializeValue(error));
await workerTeardown?.();
return error;
});
await traces.finish();
persistCompileCache();
await flushStdio();
send({
type: "stopped",
error,
__vitest_worker_response__
});
} catch (error) {
persistCompileCache();
await flushStdio();
send({
type: "stopped",
error: serializeValue(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 };
+53
View File
@@ -0,0 +1,53 @@
import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
const __require = createRequire(import.meta.url);
let inspector;
let session;
/**
* Enables debugging inside `worker_threads` and `child_process`.
* Should be called as early as possible when worker/process has been set up.
*/
function setupInspect(ctx) {
const config = ctx.config;
const isEnabled = config.inspector.enabled;
if (isEnabled) {
inspector = __require("node:inspector");
if (!(inspector.url() !== void 0)) {
inspector.open(config.inspector.port, config.inspector.host, config.inspector.waitForDebugger);
if (config.inspectBrk) {
const firstTestFile = typeof ctx.files[0] === "string" ? ctx.files[0] : ctx.files[0].filepath;
// Stop at first test file
if (firstTestFile) {
session = new inspector.Session();
session.connect();
session.post("Debugger.enable");
session.post("Debugger.setBreakpointByUrl", {
lineNumber: 0,
url: pathToFileURL(firstTestFile)
});
}
}
}
}
const keepOpen = shouldKeepOpen(config);
return function cleanup() {
if (isEnabled && !keepOpen && inspector) {
inspector.close();
session?.disconnect();
}
};
}
function closeInspector(config) {
const keepOpen = shouldKeepOpen(config);
if (inspector && !keepOpen) {
inspector.close();
session?.disconnect();
}
}
function shouldKeepOpen(config) {
// In watch mode the inspector can persist re-runs if isolation is disabled and a single worker is used
return config.watch && config.isolate === false && config.maxWorkers === 1;
}
export { closeInspector as c, setupInspect as s };
+36
View File
@@ -0,0 +1,36 @@
import { builtinModules } from 'node:module';
// copied from vite
// https://github.com/vitejs/vite/blob/814120f2ad387ca3d1e16c7dd403b04ca4b97f75/packages/vite/src/node/utils.ts#L106
// Supported by Node, Deno, Bun
const NODE_BUILTIN_NAMESPACE = "node:";
// Supported by Deno
const NPM_BUILTIN_NAMESPACE = "npm:";
// Supported by Bun
const BUN_BUILTIN_NAMESPACE = "bun:";
// Some runtimes like Bun injects namespaced modules here, which is not a node builtin
const nodeBuiltins = builtinModules.filter((id) => !id.includes(":"));
const { bun: isBun, deno: isDeno } = process.versions;
// TODO: Use `isBuiltin` from `node:module`, but Deno doesn't support it
function isBuiltin(id) {
if (isDeno && id.startsWith(NPM_BUILTIN_NAMESPACE)) return true;
if (isBun && id.startsWith(BUN_BUILTIN_NAMESPACE)) return true;
return isNodeBuiltin(id);
}
function isNodeBuiltin(id) {
if (id.startsWith(NODE_BUILTIN_NAMESPACE)) return true;
return nodeBuiltins.includes(id);
}
const browserExternalId = "__vite-browser-external";
const browserExternalLength = 24;
function isBrowserExternal(id) {
return id.startsWith(browserExternalId);
}
function toBuiltin(id) {
if (id.startsWith(browserExternalId)) id = id.slice(browserExternalLength);
if (id.startsWith(NPM_BUILTIN_NAMESPACE) || id.startsWith(BUN_BUILTIN_NAMESPACE) || id.startsWith(NODE_BUILTIN_NAMESPACE)) return id;
if (isDeno || isBun) return id;
return `node:${id}`;
}
export { isBrowserExternal as a, isBuiltin as i, toBuiltin as t };
+147
View File
@@ -0,0 +1,147 @@
import module$1, { isBuiltin } from 'node:module';
import { fileURLToPath } from 'node:url';
import { MessageChannel } from 'node:worker_threads';
import { initSyntaxLexers, hoistMocks } from '@vitest/mocker/transforms';
import { c as cleanUrl, r as resolve } from './pathe.M-eThtNZ.Clq7AXhv.js';
import { p as parse } from './acorn.C1kjbUFw.js';
import MagicString from 'magic-string';
import { y } from './tinyrainbow.Ht9iggcq.js';
import { distDir } from '../path.js';
import { t as toBuiltin } from './modules.BJuCwlRJ.js';
import 'node:path';
const NOW_LENGTH = Date.now().toString().length;
const REGEXP_VITEST = new RegExp(`%3Fvitest=\\d{${NOW_LENGTH}}`);
const REGEXP_MOCK_ACTUAL = /\?mock=actual/;
async function setupNodeLoaderHooks(worker) {
if (module$1.setSourceMapsSupport) module$1.setSourceMapsSupport(true);
else if (process.setSourceMapsEnabled) process.setSourceMapsEnabled(true);
if (worker.config.experimental.nodeLoader !== false) await initSyntaxLexers();
if (typeof module$1.registerHooks === "function") module$1.registerHooks({
resolve(specifier, context, nextResolve) {
if (specifier.includes("mock=actual")) {
// url is already resolved by `importActual`
const moduleId = specifier.replace(REGEXP_MOCK_ACTUAL, "");
const builtin = isBuiltin(moduleId);
return {
url: builtin ? toBuiltin(moduleId) : moduleId,
format: builtin ? "builtin" : void 0,
shortCircuit: true
};
}
const isVitest = specifier.includes("%3Fvitest=");
const result = nextResolve(isVitest ? specifier.replace(REGEXP_VITEST, "") : specifier, context);
// avoid tracking /node_modules/ module graph for performance reasons
if (context.parentURL && result.url && !result.url.includes("/node_modules/")) worker.rpc.ensureModuleGraphEntry(result.url, context.parentURL).catch(() => {
// ignore errors
});
// this is require for in-source tests to be invalidated if
// one of the files already imported it in --maxWorkers=1 --no-isolate
if (isVitest) result.url = `${result.url}?vitest=${Date.now()}`;
if (worker.config.experimental.nodeLoader === false || !context.parentURL || result.url.includes(distDir) || context.parentURL?.toString().includes(distDir)) return result;
const mockedResult = getNativeMocker()?.resolveMockedModule(result.url, context.parentURL);
if (mockedResult != null) return mockedResult;
return result;
},
load: worker.config.experimental.nodeLoader === false ? void 0 : createLoadHook()
});
else if (module$1.register) {
if (worker.config.experimental.nodeLoader !== false) console.warn(`${y.bgYellow(" WARNING ")} "module.registerHooks" is not supported in Node.js ${process.version}. This means that some features like module mocking or in-source testing are not supported. Upgrade your Node.js version to at least 22.15 or disable "experimental.nodeLoader" flag manually.\n`);
const { port1, port2 } = new MessageChannel();
port1.unref();
port2.unref();
port1.on("message", (data) => {
if (!data || typeof data !== "object") return;
switch (data.event) {
case "register-module-graph-entry": {
const { url, parentURL } = data;
worker.rpc.ensureModuleGraphEntry(url, parentURL);
return;
}
default: console.error("Unknown message event:", data.event);
}
});
/** Registers {@link file://./../nodejsWorkerLoader.ts} */
module$1.register("#nodejs-worker-loader", {
parentURL: import.meta.url,
data: { port: port2 },
transferList: [port2]
});
} else if (!process.versions.deno && !process.versions.bun) console.warn("\"module.registerHooks\" and \"module.register\" are not supported. Some Vitest features may not work. Please, use Node.js 18.19.0 or higher.");
}
function replaceInSourceMarker(url, source, ms) {
const re = /import\.meta\.vitest/g;
let match;
let overridden = false;
// eslint-disable-next-line no-cond-assign
while (match = re.exec(source)) {
const { index, "0": code } = match;
overridden = true;
// should it support process.vitest for CJS modules?
ms().overwrite(index, index + code.length, "IMPORT_META_TEST()");
}
if (overridden) {
const filename = resolve(fileURLToPath(url));
// appending instead of prepending because functions are hoisted and we don't change the offset
ms().append(`;\nfunction IMPORT_META_TEST() { return typeof __vitest_worker__ !== 'undefined' && __vitest_worker__.filepath === "${filename.replace(/"/g, "\\\"")}" ? __vitest_index__ : undefined; }`);
}
}
const ignoreFormats = /* @__PURE__ */ new Set([
"addon",
"builtin",
"wasm"
]);
function createLoadHook(_worker) {
return (url, context, nextLoad) => {
const result = url.includes("mock=") && isBuiltin(cleanUrl(url)) ? { format: "commonjs" } : nextLoad(url, context);
if (result.format && ignoreFormats.has(result.format) || url.includes(distDir)) return result;
const mocker = getNativeMocker();
mocker?.checkCircularManualMock(url);
if (url.includes("mock=automock") || url.includes("mock=autospy")) {
const automockedResult = mocker?.loadAutomock(url, result);
if (automockedResult != null) return automockedResult;
return result;
}
if (url.includes("mock=manual")) {
const mockedResult = mocker?.loadManualMock(url, result);
if (mockedResult != null) return mockedResult;
return result;
}
// ignore non-vitest modules for performance reasons,
// vi.hoisted and vi.mock won't work outside of test files or setup files
if (!result.source || !url.includes("vitest=")) return result;
const filename = url.startsWith("file://") ? fileURLToPath(url) : url;
const source = result.source.toString();
const transformedCode = result.format?.includes("typescript") ? module$1.stripTypeScriptTypes(source) : source;
let _ms;
const ms = () => _ms || (_ms = new MagicString(source));
if (source.includes("import.meta.vitest")) replaceInSourceMarker(url, source, ms);
hoistMocks(transformedCode, filename, (code) => parse(code, {
ecmaVersion: "latest",
sourceType: result.format === "module" || result.format === "module-typescript" || result.format === "typescript" ? "module" : "script"
}), {
magicString: ms,
globalThisAccessor: "\"__vitest_mocker__\""
});
let code;
if (_ms) code = `${_ms.toString()}\n//# sourceMappingURL=${genSourceMapUrl(_ms.generateMap({
hires: "boundary",
source: filename
}))}`;
else code = source;
return {
format: result.format,
shortCircuit: true,
source: code
};
};
}
function genSourceMapUrl(map) {
if (typeof map !== "string") map = JSON.stringify(map);
return `data:application/json;base64,${Buffer.from(map).toString("base64")}`;
}
function getNativeMocker() {
return typeof __vitest_mocker__ !== "undefined" ? __vitest_mocker__ : void 0;
}
export { setupNodeLoaderHooks };
+213
View File
@@ -0,0 +1,213 @@
import module$1, { isBuiltin } from 'node:module';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { automockModule, createManualModuleSource, collectModuleExports } from '@vitest/mocker/transforms';
import { c as cleanUrl, e as createDefer, i as isAbsolute } from './pathe.M-eThtNZ.Clq7AXhv.js';
import { p as parse } from './acorn.C1kjbUFw.js';
import { t as toBuiltin } from './modules.BJuCwlRJ.js';
import { B as BareModuleMocker, n as normalizeModuleId } from './index.1_nbEjJY.js';
import 'node:fs';
import './utils.DYj33du9.js';
import '../path.js';
import 'node:path';
import '../module-evaluator.js';
import 'node:vm';
import 'vite/module-runner';
import '../traces.js';
import '@vitest/mocker';
import '@vitest/mocker/redirect';
import 'node:console';
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 filename = url.startsWith("file://") ? fileURLToPath(url) : url;
const moduleId = normalizeModuleId(filename);
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 filename = url.startsWith("file://") ? fileURLToPath(url) : url;
const moduleId = cleanUrl(normalizeModuleId(filename));
let source;
if (isBuiltin(moduleId)) {
const builtinModule = getBuiltinModule(moduleId);
const exports = Object.keys(builtinModule);
source = `
import * as builtinModule from '${toBuiltin(moduleId)}?mock=actual'
${exports.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 filename = url.startsWith("file://") ? fileURLToPath(url) : url;
const moduleId = cleanUrl(normalizeModuleId(filename));
// 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));
const exports = Object.keys(builtinModule);
return {
format: "module",
source: createManualModuleSource(moduleId, exports),
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 {
// we parse the module with es/cjs-module-lexer to find the original exports -- we assume the same ones are returned from the factory
// injecting new keys is not supported (and should not be advised anyway)
const exports = collectModuleExports(moduleId, transformedCode, format);
return {
format: "module",
source: createManualModuleSource(moduleId, exports),
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 filename = url.startsWith("file://") ? fileURLToPath(url) : url;
const id = cleanUrl(normalizeModuleId(filename));
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 };
File diff suppressed because it is too large. Load diff
+47
View File
@@ -0,0 +1,47 @@
import { g as getWorkerState } from './utils.DYj33du9.js';
import { promises, existsSync } from 'node:fs';
import { i as isAbsolute, r as resolve, d as dirname, j as join, b as basename } from './pathe.M-eThtNZ.Clq7AXhv.js';
class NodeSnapshotEnvironment {
options;
constructor(options = {}) {
this.options = options;
}
getVersion() {
return "1";
}
getHeader() {
return `// Snapshot v${this.getVersion()}`;
}
async resolveRawPath(testPath, rawPath) {
return isAbsolute(rawPath) ? rawPath : resolve(dirname(testPath), rawPath);
}
async resolvePath(filepath) {
return join(join(dirname(filepath), this.options.snapshotsDirName ?? "__snapshots__"), `${basename(filepath)}.snap`);
}
async prepareDirectory(dirPath) {
await promises.mkdir(dirPath, { recursive: true });
}
async saveSnapshotFile(filepath, snapshot) {
await promises.mkdir(dirname(filepath), { recursive: true });
await promises.writeFile(filepath, snapshot, "utf-8");
}
async readSnapshotFile(filepath) {
if (!existsSync(filepath)) return null;
return promises.readFile(filepath, "utf-8");
}
async removeSnapshotFile(filepath) {
if (existsSync(filepath)) await promises.unlink(filepath);
}
}
class VitestNodeSnapshotEnvironment extends NodeSnapshotEnvironment {
getHeader() {
return `// Vitest Snapshot v${this.getVersion()}, https://vitest.dev/guide/snapshot.html`;
}
resolvePath(filepath) {
return getWorkerState().rpc.resolveSnapshotPath(filepath);
}
}
export { VitestNodeSnapshotEnvironment };
+24
View File
@@ -0,0 +1,24 @@
const lineSplitRE = /\r?\n/;
function positionToOffset(source, lineNumber, columnNumber) {
const lines = source.split(lineSplitRE);
const nl = /\r\n/.test(source) ? 2 : 1;
let start = 0;
if (lineNumber > lines.length) return source.length;
for (let i = 0; i < lineNumber - 1; i++) start += lines[i].length + nl;
return start + columnNumber;
}
function offsetToLineNumber(source, offset) {
if (offset > source.length) throw new Error(`offset is longer than source length! offset ${offset} > length ${source.length}`);
const lines = source.split(lineSplitRE);
const nl = /\r\n/.test(source) ? 2 : 1;
let counted = 0;
let line = 0;
for (; line < lines.length; line++) {
const lineLength = lines[line].length + nl;
if (counted + lineLength >= offset) break;
counted += lineLength;
}
return line + 1;
}
export { lineSplitRE as l, offsetToLineNumber as o, positionToOffset as p };
+489
View File
@@ -0,0 +1,489 @@
// TODO: this is all copy pasted from Vite - can they expose a module that exports only constants?
const KNOWN_ASSET_TYPES = [
"apng",
"bmp",
"png",
"jpe?g",
"jfif",
"pjpeg",
"pjp",
"gif",
"svg",
"ico",
"webp",
"avif",
"mp4",
"webm",
"ogg",
"mp3",
"wav",
"flac",
"aac",
"woff2?",
"eot",
"ttf",
"otf",
"webmanifest",
"pdf",
"txt"
];
const KNOWN_ASSET_RE = new RegExp(`\\.(${KNOWN_ASSET_TYPES.join("|")})$`);
const CSS_LANGS_RE = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
/**
* Prefix for resolved Ids that are not valid browser import specifiers
*/
const VALID_ID_PREFIX = `/@id/`;
/**
* Plugins that use 'virtual modules' (e.g. for helper functions), prefix the
* module ID with `\0`, a convention from the rollup ecosystem.
* This prevents other plugins from trying to process the id (like node resolution),
* and core features like sourcemaps can use this info to differentiate between
* virtual modules and regular files.
* `\0` is not a permitted char in import URLs so we have to replace them during
* import analysis. The id will be decoded back before entering the plugins pipeline.
* These encoded virtual ids are also prefixed by the VALID_ID_PREFIX, so virtual
* modules in the browser end up encoded as `/@id/__x00__{id}`
*/
const NULL_BYTE_PLACEHOLDER = `__x00__`;
/**
* Get original stacktrace without source map support the most performant way.
* - Create only 1 stack frame.
* - Rewrite prepareStackTrace to bypass "support-stack-trace" (usually takes ~250ms).
*/
function createSimpleStackTrace(options) {
const { message = "$$stack trace error", stackTraceLimit = 1 } = options || {};
const limit = Error.stackTraceLimit;
const prepareStackTrace = Error.prepareStackTrace;
Error.stackTraceLimit = stackTraceLimit;
Error.prepareStackTrace = (e) => e.stack;
const stackTrace = new Error(message).stack || "";
Error.prepareStackTrace = prepareStackTrace;
Error.stackTraceLimit = limit;
return stackTrace;
}
function notNullish(v) {
return v != null;
}
function assertTypes(value, name, types) {
const receivedType = typeof value;
if (!types.includes(receivedType)) throw new TypeError(`${name} value must be ${types.join(" or ")}, received "${receivedType}"`);
}
function isPrimitive(value) {
return value === null || typeof value !== "function" && typeof value !== "object";
}
function slash(path) {
return path.replace(/\\/g, "/");
}
const postfixRE = /[?#].*$/;
function cleanUrl(url) {
return url.replace(postfixRE, "");
}
function splitFileAndPostfix(path) {
const file = cleanUrl(path);
return {
file,
postfix: path.slice(file.length)
};
}
const externalRE = /^(?:[a-z]+:)?\/\//;
const isExternalUrl = (url) => externalRE.test(url);
/**
* Prepend `/@id/` and replace null byte so the id is URL-safe.
* This is prepended to resolved ids that are not valid browser
* import specifiers by the importAnalysis plugin.
*/
function wrapId(id) {
return id.startsWith(VALID_ID_PREFIX) ? id : VALID_ID_PREFIX + id.replace("\0", NULL_BYTE_PLACEHOLDER);
}
/**
* Undo {@link wrapId}'s `/@id/` and null byte replacements.
*/
function unwrapId(id) {
return id.startsWith(VALID_ID_PREFIX) ? id.slice(VALID_ID_PREFIX.length).replace(NULL_BYTE_PLACEHOLDER, "\0") : id;
}
function withTrailingSlash(path) {
if (path.at(-1) !== "/") return `${path}/`;
return path;
}
function filterOutComments(s) {
const result = [];
let commentState = "none";
for (let i = 0; i < s.length; ++i) if (commentState === "singleline") {
if (s[i] === "\n") commentState = "none";
} else if (commentState === "multiline") {
if (s[i - 1] === "*" && s[i] === "/") commentState = "none";
} else if (commentState === "none") if (s[i] === "/" && s[i + 1] === "/") commentState = "singleline";
else if (s[i] === "/" && s[i + 1] === "*") {
commentState = "multiline";
i += 2;
} else result.push(s[i]);
return result.join("");
}
const bareImportRE = /^(?![a-z]:)[\w@](?!.*:\/\/)/i;
function isBareImport(id) {
return bareImportRE.test(id);
}
function toArray(array) {
array ??= [];
if (Array.isArray(array)) return array;
return [array];
}
function isObject(item) {
return item != null && typeof item === "object" && !Array.isArray(item);
}
function isFinalObj(obj) {
return obj === Object.prototype || obj === Function.prototype || obj === RegExp.prototype;
}
function getType(value) {
return Object.prototype.toString.apply(value).slice(8, -1);
}
function collectOwnProperties(obj, collector) {
const collect = typeof collector === "function" ? collector : (key) => collector.add(key);
Object.getOwnPropertyNames(obj).forEach(collect);
Object.getOwnPropertySymbols(obj).forEach(collect);
}
function getOwnProperties(obj) {
const ownProps = /* @__PURE__ */ new Set();
if (isFinalObj(obj)) return [];
collectOwnProperties(obj, ownProps);
return Array.from(ownProps);
}
const defaultCloneOptions = { forceWritable: false };
function deepClone(val, options = defaultCloneOptions) {
return clone(val, /* @__PURE__ */ new WeakMap(), options);
}
function clone(val, seen, options = defaultCloneOptions) {
let k, out;
if (seen.has(val)) return seen.get(val);
if (Array.isArray(val)) {
out = Array.from({ length: k = val.length });
seen.set(val, out);
while (k--) out[k] = clone(val[k], seen, options);
return out;
}
if (Object.prototype.toString.call(val) === "[object Object]") {
out = Object.create(Object.getPrototypeOf(val));
seen.set(val, out);
// we don't need properties from prototype
const props = getOwnProperties(val);
for (const k of props) {
const descriptor = Object.getOwnPropertyDescriptor(val, k);
if (!descriptor) continue;
const cloned = clone(val[k], seen, options);
if (options.forceWritable) Object.defineProperty(out, k, {
enumerable: descriptor.enumerable,
configurable: true,
writable: true,
value: cloned
});
else if ("get" in descriptor) Object.defineProperty(out, k, {
...descriptor,
get() {
return cloned;
}
});
else Object.defineProperty(out, k, {
...descriptor,
value: cloned
});
}
return out;
}
return val;
}
function noop() {}
function objectAttr(source, path, defaultValue = void 0) {
// a[3].b -> a.3.b
const paths = path.replace(/\[(\d+)\]/g, ".$1").split(".");
let result = source;
for (const p of paths) {
result = new Object(result)[p];
if (result === void 0) return defaultValue;
}
return result;
}
function createDefer() {
let resolve = null;
let reject = null;
const p = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
p.resolve = resolve;
p.reject = reject;
return p;
}
/**
* If code starts with a function call, will return its last index, respecting arguments.
* This will return 25 - last ending character of toMatch ")"
* Also works with callbacks
* ```
* toMatch({ test: '123' });
* toBeAliased('123')
* ```
*/
function getCallLastIndex(code) {
let charIndex = -1;
let inString = null;
let startedBracers = 0;
let endedBracers = 0;
let beforeChar = null;
while (charIndex <= code.length) {
beforeChar = code[charIndex];
charIndex++;
const char = code[charIndex];
if ((char === "\"" || char === "'" || char === "`") && beforeChar !== "\\") {
if (inString === char) inString = null;
else if (!inString) inString = char;
}
if (!inString) {
if (char === "(") startedBracers++;
if (char === ")") endedBracers++;
}
if (startedBracers && endedBracers && startedBracers === endedBracers) return charIndex;
}
return null;
}
function isNegativeNaN(val) {
if (!Number.isNaN(val)) return false;
const f64 = /* @__PURE__ */ new Float64Array(1);
f64[0] = val;
return new Uint32Array(f64.buffer)[1] >>> 31 === 1;
}
function toString(v) {
return Object.prototype.toString.call(v);
}
function isPlainObject(val) {
return toString(val) === "[object Object]" && (!val.constructor || val.constructor.name === "Object");
}
function isMergeableObject(item) {
return isPlainObject(item) && !Array.isArray(item);
}
function ordinal(i) {
const j = i % 10;
const k = i % 100;
if (j === 1 && k !== 11) return `${i}st`;
if (j === 2 && k !== 12) return `${i}nd`;
if (j === 3 && k !== 13) return `${i}rd`;
return `${i}th`;
}
/**
* Deep merge :P
*
* Will merge objects only if they are plain
*
* Do not merge types - it is very expensive and usually it's better to case a type here
*/
function deepMerge(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (source === void 0) return target;
if (isMergeableObject(target) && isMergeableObject(source)) Object.keys(source).forEach((key) => {
const _source = source;
if (isMergeableObject(_source[key])) {
if (!target[key]) target[key] = {};
deepMerge(target[key], _source[key]);
} else target[key] = _source[key];
});
return deepMerge(target, ...sources);
}
function unique(array) {
return Array.from(new Set(array));
}
function sanitizeFilePath(s) {
// eslint-disable-next-line no-control-regex
return s.replace(/[\x00-\x2C\x2E\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, "-");
}
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
function normalizeWindowsPath(input = "") {
if (!input) {
return input;
}
return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
}
const _UNC_REGEX = /^[/\\]{2}/;
const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
const _DRIVE_LETTER_RE = /^[A-Za-z]:$/;
const _ROOT_FOLDER_RE = /^\/([A-Za-z]:)?$/;
const _EXTNAME_RE = /.(\.[^./]+|\.)$/;
const normalize = function(path) {
if (path.length === 0) {
return ".";
}
path = normalizeWindowsPath(path);
const isUNCPath = path.match(_UNC_REGEX);
const isPathAbsolute = isAbsolute(path);
const trailingSeparator = path[path.length - 1] === "/";
path = normalizeString(path, !isPathAbsolute);
if (path.length === 0) {
if (isPathAbsolute) {
return "/";
}
return trailingSeparator ? "./" : ".";
}
if (trailingSeparator) {
path += "/";
}
if (_DRIVE_LETTER_RE.test(path)) {
path += "/";
}
if (isUNCPath) {
if (!isPathAbsolute) {
return `//./${path}`;
}
return `//${path}`;
}
return isPathAbsolute && !isAbsolute(path) ? `/${path}` : path;
};
const join = function(...segments) {
let path = "";
for (const seg of segments) {
if (!seg) {
continue;
}
if (path.length > 0) {
const pathTrailing = path[path.length - 1] === "/";
const segLeading = seg[0] === "/";
const both = pathTrailing && segLeading;
if (both) {
path += seg.slice(1);
} else {
path += pathTrailing || segLeading ? seg : `/${seg}`;
}
} else {
path += seg;
}
}
return normalize(path);
};
function cwd() {
if (typeof process !== "undefined" && typeof process.cwd === "function") {
return process.cwd().replace(/\\/g, "/");
}
return "/";
}
const resolve = function(...arguments_) {
arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument));
let resolvedPath = "";
let resolvedAbsolute = false;
for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) {
const path = index >= 0 ? arguments_[index] : cwd();
if (!path || path.length === 0) {
continue;
}
resolvedPath = `${path}/${resolvedPath}`;
resolvedAbsolute = isAbsolute(path);
}
resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
if (resolvedAbsolute && !isAbsolute(resolvedPath)) {
return `/${resolvedPath}`;
}
return resolvedPath.length > 0 ? resolvedPath : ".";
};
function normalizeString(path, allowAboveRoot) {
let res = "";
let lastSegmentLength = 0;
let lastSlash = -1;
let dots = 0;
let char = null;
for (let index = 0; index <= path.length; ++index) {
if (index < path.length) {
char = path[index];
} else if (char === "/") {
break;
} else {
char = "/";
}
if (char === "/") {
if (lastSlash === index - 1 || dots === 1) ; else if (dots === 2) {
if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") {
if (res.length > 2) {
const lastSlashIndex = res.lastIndexOf("/");
if (lastSlashIndex === -1) {
res = "";
lastSegmentLength = 0;
} else {
res = res.slice(0, lastSlashIndex);
lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
}
lastSlash = index;
dots = 0;
continue;
} else if (res.length > 0) {
res = "";
lastSegmentLength = 0;
lastSlash = index;
dots = 0;
continue;
}
}
if (allowAboveRoot) {
res += res.length > 0 ? "/.." : "..";
lastSegmentLength = 2;
}
} else {
if (res.length > 0) {
res += `/${path.slice(lastSlash + 1, index)}`;
} else {
res = path.slice(lastSlash + 1, index);
}
lastSegmentLength = index - lastSlash - 1;
}
lastSlash = index;
dots = 0;
} else if (char === "." && dots !== -1) {
++dots;
} else {
dots = -1;
}
}
return res;
}
const isAbsolute = function(p) {
return _IS_ABSOLUTE_RE.test(p);
};
const extname = function(p) {
if (p === "..") return "";
const match = _EXTNAME_RE.exec(normalizeWindowsPath(p));
return match && match[1] || "";
};
const relative = function(from, to) {
const _from = resolve(from).replace(_ROOT_FOLDER_RE, "$1").split("/");
const _to = resolve(to).replace(_ROOT_FOLDER_RE, "$1").split("/");
if (_to[0][1] === ":" && _from[0][1] === ":" && _from[0] !== _to[0]) {
return _to.join("/");
}
const _fromCopy = [..._from];
for (const segment of _fromCopy) {
if (_to[0] !== segment) {
break;
}
_from.shift();
_to.shift();
}
return [..._from.map(() => ".."), ..._to].join("/");
};
const dirname = function(p) {
const segments = normalizeWindowsPath(p).replace(/\/$/, "").split("/").slice(0, -1);
if (segments.length === 1 && _DRIVE_LETTER_RE.test(segments[0])) {
segments[0] += "/";
}
return segments.join("/") || (isAbsolute(p) ? "/" : ".");
};
const basename = function(p, extension) {
const segments = normalizeWindowsPath(p).split("/");
let lastSegment = "";
for (let i = segments.length - 1; i >= 0; i--) {
const val = segments[i];
if (val) {
lastSegment = val;
break;
}
}
return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
};
export { objectAttr as A, getCallLastIndex as B, CSS_LANGS_RE as C, noop as D, createSimpleStackTrace as E, withTrailingSlash as F, wrapId as G, deepMerge as H, isExternalUrl as I, unwrapId as J, KNOWN_ASSET_TYPES as K, sanitizeFilePath as L, relative as a, basename as b, cleanUrl as c, dirname as d, createDefer as e, extname as f, getType as g, KNOWN_ASSET_RE as h, isAbsolute as i, join as j, splitFileAndPostfix as k, isBareImport as l, isPrimitive as m, normalize as n, notNullish as o, deepClone as p, getOwnProperties as q, resolve as r, slash as s, toArray as t, isObject as u, filterOutComments as v, ordinal as w, assertTypes as x, isNegativeNaN as y, unique as z };
File diff suppressed because it is too large. Load diff
+37
View File
@@ -0,0 +1,37 @@
import { p as plugins } from './index.M2dsQ_UQ.js';
const serialize = (val, config, indentation, depth, refs, printer) => {
// Serialize a non-default name, even if config.printFunctionName is false.
const name = val.getMockName();
const nameString = name === "vi.fn()" ? "" : ` ${name}`;
let callsString = "";
if (val.mock.calls.length !== 0) {
const indentationNext = indentation + config.indent;
callsString = ` {${config.spacingOuter}${indentationNext}"calls": ${printer(val.mock.calls, config, indentationNext, depth, refs)}${config.min ? ", " : ","}${config.spacingOuter}${indentationNext}"results": ${printer(val.mock.results, config, indentationNext, depth, refs)}${config.min ? "" : ","}${config.spacingOuter}${indentation}}`;
}
return `[MockFunction${nameString}]${callsString}`;
};
const test = (val) => val && !!val._isMockFunction;
const plugin = {
serialize,
test
};
const { DOMCollection, DOMElement, Immutable, ReactElement, ReactTestComponent, AsymmetricMatcher } = plugins;
let PLUGINS = [
ReactTestComponent,
ReactElement,
DOMElement,
DOMCollection,
Immutable,
AsymmetricMatcher,
plugin
];
function addSerializer(plugin) {
PLUGINS = [plugin].concat(PLUGINS);
}
function getSerializers() {
return PLUGINS;
}
export { addSerializer as a, getSerializers as g };
+48
View File
@@ -0,0 +1,48 @@
import fs__default from 'node:fs';
import { b as basename, j as join, d as dirname } from './pathe.M-eThtNZ.Clq7AXhv.js';
const packageScopeTypeCache = /* @__PURE__ */ new Map();
// mirrors LOOKUP_PACKAGE_SCOPE from the ESM resolution algorithm:
// the lookup stops at the first package.json and never crosses
// the "node_modules" boundary, so typeless dependencies don't
// inherit the `type` field of the user's project
function lookupPackageScopeType(directory) {
const visited = [];
let result = "none";
let current = directory;
while (current) {
const cached = packageScopeTypeCache.get(current);
if (cached) {
result = cached;
break;
}
if (basename(current) === "node_modules") break;
visited.push(current);
const packageJsonPath = join(current, "package.json");
if (tryStatSync(packageJsonPath)?.isFile()) {
try {
const packageJson = JSON.parse(stripBomTag(fs__default.readFileSync(packageJsonPath, "utf8")));
if (packageJson.type === "module") result = "esm";
else if (packageJson.type === "commonjs") result = "cjs";
} catch {}
break;
}
const parent = dirname(current);
if (parent === current) break;
current = parent;
}
visited.forEach((dir) => packageScopeTypeCache.set(dir, result));
return result;
}
function stripBomTag(content) {
if (content.charCodeAt(0) === 65279) return content.slice(1);
return content;
}
function tryStatSync(file) {
try {
// The "throwIfNoEntry" is a performance optimization for cases where the file does not exist
return fs__default.statSync(file, { throwIfNoEntry: false });
} catch {}
}
export { lookupPackageScopeType as l };
+91
View File
@@ -0,0 +1,91 @@
import { EvaluatedModules } from 'vite/module-runner';
import { d as dirname, r as resolve } from './pathe.M-eThtNZ.Clq7AXhv.js';
import { a as getSafeTimers } from './source-map.Dx3YPjIe.js';
import { c as createBirpc } from './index.DmDMHCg8.js';
import { g as getWorkerState } from './utils.DYj33du9.js';
// TODO: this is not needed in Vite 7.2+
class VitestEvaluatedModules extends EvaluatedModules {
getModuleSourceMapById(id) {
const map = super.getModuleSourceMapById(id);
if (map != null && !("_patched" in map)) {
map._patched = true;
const dir = dirname(map.url);
map.resolvedSources = (map.map.sources || []).map((s) => resolve(dir, s || ""));
}
return map;
}
}
const { get } = Reflect;
function withSafeTimers(fn) {
const { setTimeout, clearTimeout, nextTick, setImmediate, clearImmediate } = getSafeTimers();
const currentSetTimeout = globalThis.setTimeout;
const currentClearTimeout = globalThis.clearTimeout;
const currentSetImmediate = globalThis.setImmediate;
const currentClearImmediate = globalThis.clearImmediate;
const currentNextTick = globalThis.process?.nextTick;
try {
globalThis.setTimeout = setTimeout;
globalThis.clearTimeout = clearTimeout;
if (setImmediate) globalThis.setImmediate = setImmediate;
if (clearImmediate) globalThis.clearImmediate = clearImmediate;
if (globalThis.process && nextTick) globalThis.process.nextTick = nextTick;
return fn();
} finally {
globalThis.setTimeout = currentSetTimeout;
globalThis.clearTimeout = currentClearTimeout;
globalThis.setImmediate = currentSetImmediate;
globalThis.clearImmediate = currentClearImmediate;
if (globalThis.process && nextTick) nextTick(() => {
globalThis.process.nextTick = currentNextTick;
});
}
}
const promises = /* @__PURE__ */ new Set();
async function rpcDone() {
if (!promises.size) return;
const awaitable = Array.from(promises);
return Promise.all(awaitable);
}
const onCancelCallbacks = [];
function onCancel(callback) {
onCancelCallbacks.push(callback);
return () => {
const index = onCancelCallbacks.indexOf(callback);
if (index !== -1) onCancelCallbacks.splice(index, 1);
};
}
function createRuntimeRpc(options) {
return createSafeRpc(createBirpc({ async onCancel(reason) {
await Promise.all(onCancelCallbacks.map((fn) => fn(reason)));
} }, {
eventNames: ["onCancel"],
timeout: -1,
...options
}));
}
function createSafeRpc(rpc) {
return new Proxy(rpc, { get(target, p, handler) {
// keep $rejectPendingCalls as sync function
if (p === "$rejectPendingCalls") return rpc.$rejectPendingCalls;
const sendCall = get(target, p, handler);
const safeSendCall = (...args) => withSafeTimers(async () => {
const result = sendCall(...args);
promises.add(result);
try {
return await result;
} finally {
promises.delete(result);
}
});
safeSendCall.asEvent = sendCall.asEvent;
return safeSendCall;
} });
}
function rpc() {
const { rpc } = getWorkerState();
return rpc;
}
export { VitestEvaluatedModules as V, rpcDone as a, createRuntimeRpc as c, onCancel as o, rpc as r };
+74
View File
@@ -0,0 +1,74 @@
import { bt as FetchCachedFileSystemResult, bu as ResolveFunctionResult, U as UserConsoleLog, bl as AsyncLeak, a as File, a2 as AfterSuiteRunMeta, b0 as TestBenchmark, h as TestArtifact, j as TaskResultPack, k as TaskEventPack, Y as CancelReason, aM as SnapshotResult, a5 as BaselineData } from './config.d.CU_b-wJj.js';
import { FetchFunctionOptions, FetchResult } from 'vite/module-runner';
interface OTELCarrier {
traceparent?: string;
tracestate?: string;
}
interface TracesOptions {
enabled: boolean;
watchMode?: boolean;
sdkPath?: string;
tracerName?: string;
}
declare class Traces {
#private;
constructor(options: TracesOptions);
isEnabled(): boolean;
}
declare class GetterTracker {
static EXPORTS_MAX_INVOCATIONS: number;
private invocations;
private excessiveInvocations;
createTracker(moduleId: string, defineExport: (name: string, getter: () => unknown) => void): (name: string, getter: () => unknown) => void;
resetInvocations(): void;
getExcessiveInvocations(): GetterTrackerExport[];
}
interface GetterTrackerExport {
moduleId: string;
exportName: string;
}
interface RuntimeRPC {
fetch: (id: string, importer: string | undefined, environment: string, options?: FetchFunctionOptions, otelCarrier?: OTELCarrier) => Promise<FetchResult | FetchCachedFileSystemResult>;
resolve: (id: string, importer: string | undefined, environment: string) => Promise<ResolveFunctionResult | null>;
/**
* Returns the modules of the given test files' import graphs that the server
* has already processed, so a fresh worker can load them from disk without
* paying a `fetch` round-trip per module.
*/
fetchWarmModules: (environment: string, files: string[]) => Promise<Record<string, FetchResult | FetchCachedFileSystemResult>>;
/**
* Transforms the import graphs of the given test files ahead of the
* worker's own fetches. Fired by vm pool workers before their environment
* setup, so the server transforms modules while the worker is busy
* importing its environment package.
*/
prewarmModuleGraph: (environment: string, files: string[]) => Promise<void>;
transform: (id: string) => Promise<{
code?: string;
}>;
onUserConsoleLog: (log: UserConsoleLog) => void;
onUnhandledError: (err: unknown, type: string) => void;
onAsyncLeaks: (leak: AsyncLeak[]) => void;
onQueued: (file: File) => void;
onCollected: (files: File[]) => Promise<void>;
onAfterSuiteRun: (meta: AfterSuiteRunMeta) => void;
onTestBenchmark: (testId: string, bench: TestBenchmark) => void;
onTaskArtifactRecord: <Artifact extends TestArtifact>(testId: string, artifact: Artifact) => Promise<Artifact>;
onTaskUpdate: (pack: TaskResultPack[], events: TaskEventPack[]) => Promise<void>;
onCancel: (reason: CancelReason) => void;
getCountOfFailedTests: () => number;
snapshotSaved: (snapshot: SnapshotResult) => void;
resolveSnapshotPath: (testPath: string) => string;
readBenchmarkResult: (relativePath: string) => Promise<BaselineData | null>;
writeBenchmarkResult: (relativePath: string, data: BaselineData) => Promise<void>;
ensureModuleGraphEntry: (id: string, importer: string) => void;
}
interface RunnerRPC {
onCancel: (reason: CancelReason) => void;
}
export { GetterTracker as G, Traces as T };
export type { OTELCarrier as O, RunnerRPC as R, RuntimeRPC as a };
File diff suppressed because it is too large. Load diff
+36
View File
@@ -0,0 +1,36 @@
import { s as setSafeTimers } from './source-map.Dx3YPjIe.js';
import { a as addSerializer } from './plugins.Cigb0uSy.js';
let globalSetup = false;
async function setupCommonEnv(config) {
setupDefines(config);
if (globalSetup) return;
globalSetup = true;
setSafeTimers();
if (config.globals) (await import('./globals.DQ4rmEUL.js')).registerApiGlobally();
}
function setupDefines(config) {
for (const key in config.defines) globalThis[key] = config.defines[key];
}
function setupEnv(env, metaEnv) {
for (const key in env) metaEnv[key] = env[key];
}
async function loadDiffConfig(config, moduleRunner) {
if (typeof config.diff === "object") return config.diff;
if (typeof config.diff !== "string") return;
const diffModule = await moduleRunner.import(config.diff);
if (diffModule && typeof diffModule.default === "object" && diffModule.default != null) return diffModule.default;
else throw new Error(`invalid diff config file ${config.diff}. Must have a default export with config object`);
}
async function loadSnapshotSerializers(config, moduleRunner) {
const files = config.snapshotSerializers;
(await Promise.all(files.map(async (file) => {
const mo = await moduleRunner.import(file);
if (!mo || typeof mo.default !== "object" || mo.default === null) throw new Error(`invalid snapshot serializer file ${file}. Must export a default object`);
const config = mo.default;
if (typeof config.test !== "function" || typeof config.serialize !== "function" && typeof config.print !== "function") throw new TypeError(`invalid snapshot serializer in ${file}. Must have a 'test' method along with either a 'serialize' or 'print' method.`);
return config;
}))).forEach((serializer) => addSerializer(serializer));
}
export { loadSnapshotSerializers as a, setupEnv as b, loadDiffConfig as l, setupCommonEnv as s };
File diff suppressed because it is too large. Load diff
+88
View File
@@ -0,0 +1,88 @@
import { T as TestError, P as ParsedStack } from './config.d.CU_b-wJj.js';
interface RuntimeCoverageModuleLoader {
import: (id: string) => Promise<{
default: RuntimeCoverageProviderModule;
}>;
isBrowser?: boolean;
moduleExecutionInfo?: Map<string, {
startOffset: number;
}>;
}
interface RuntimeCoverageProviderModule {
/**
* Factory for creating a new coverage provider
*/
getProvider: () => any;
/**
* Executed before tests are run in the worker thread.
*/
startCoverage?: (runtimeOptions: {
isolate: boolean;
/** @internal */
autoAttachSubprocess: boolean;
/** @internal */
reportsDirectory: string;
}) => unknown | Promise<unknown>;
/**
* Executed on after each run in the worker thread. Possible to return a payload passed to the provider
*/
takeCoverage?: (runtimeOptions?: {
moduleExecutionInfo?: Map<string, {
startOffset: number;
}>;
coverageFilesDirectory: string;
}) => unknown | Promise<unknown>;
/**
* Executed after all tests have been run in the worker thread.
*/
stopCoverage?: (runtimeOptions: {
isolate: boolean;
}) => unknown | Promise<unknown>;
}
type OriginalMapping = {
source: string | null;
line: number;
column: number;
name: string | null;
};
interface StackTraceParserOptions {
ignoreStackEntries?: (RegExp | string)[];
getSourceMap?: (file: string) => unknown;
getUrlId?: (id: string) => string;
frameFilter?: (error: TestError, frame: ParsedStack) => boolean | void;
}
interface SourceMapLike {
version: number;
mappings?: string;
names?: string[];
sources?: string[];
sourcesContent?: string[];
sourceRoot?: string;
}
interface Needle {
line: number;
column: number;
}
declare class DecodedMap {
map: SourceMapLike;
_encoded: string;
_decoded: undefined | number[][][];
_decodedMemo: Stats;
url: string;
version: number;
names: string[];
resolvedSources: string[];
constructor(map: SourceMapLike, from: string);
}
interface Stats {
lastKey: number;
lastNeedle: number;
lastIndex: number;
}
declare function getOriginalPosition(map: DecodedMap, needle: Needle): OriginalMapping | null;
export { DecodedMap as D, getOriginalPosition as g };
export type { RuntimeCoverageModuleLoader as R, StackTraceParserOptions as S, RuntimeCoverageProviderModule as a };
+477
View File
@@ -0,0 +1,477 @@
function isMockFunction(fn) {
return typeof fn === "function" && "_isMockFunction" in fn && fn._isMockFunction === true;
}
const MOCK_RESTORE = /* @__PURE__ */ new Set();
// Jest keeps the state in a separate WeakMap which is good for memory,
// but it makes the state slower to access and return different values
// if you stored it before calling `mockClear` where it will be recreated
const DIRTY_MOCK_STATES = /* @__PURE__ */ new Set();
const REGISTERED_MOCKS = /* @__PURE__ */ new Set();
const MOCK_FINALIZER = new FinalizationRegistry((ref) => {
REGISTERED_MOCKS.delete(ref);
});
const MOCK_CONFIGS = /* @__PURE__ */ new WeakMap();
const MOCKS_BY_STATE = /* @__PURE__ */ new WeakMap();
function createMockInstance(options = {}) {
const { originalImplementation, restore, mockImplementation, resetToMockImplementation, resetToMockName } = options;
if (restore) MOCK_RESTORE.add(restore);
const config = getDefaultConfig(originalImplementation);
const state = getDefaultState();
const mock = createMock({
config,
state,
...options
});
const mockLength = (mockImplementation || originalImplementation)?.length ?? 0;
Object.defineProperty(mock, "length", {
writable: true,
enumerable: false,
value: mockLength,
configurable: true
});
// inherit the default name so it appears in snapshots and logs
// this is used by `vi.spyOn()` for better debugging.
// when `vi.fn()` is called, we just use the default string
if (resetToMockName) config.mockName = mock.name || "vi.fn()";
MOCK_CONFIGS.set(mock, config);
MOCKS_BY_STATE.set(state, mock);
const ref = new WeakRef(mock);
REGISTERED_MOCKS.add(ref);
MOCK_FINALIZER.register(mock, ref);
mock._isMockFunction = true;
mock.getMockImplementation = () => {
// Jest only returns `config.mockImplementation` here,
// but we think it makes sense to return what the next function will be called
return config.onceMockImplementations[0] || config.mockImplementation;
};
// keep the prototype chain in sync with the implementation the next
// construction will use, so it is correct before any `new` call.
// automocked classes are skipped: their methods are pre-mocked
// on `mock.prototype`
const updateMockPrototype = () => {
if (!options.prototypeMembers?.length) reparentMockPrototype(mock, config.onceMockImplementations[0] || config.mockImplementation || originalImplementation);
};
Object.defineProperty(mock, "mock", {
configurable: false,
enumerable: true,
writable: false,
value: state
});
mock.mockImplementation = function mockImplementation(implementation) {
config.mockImplementation = implementation;
updateMockPrototype();
return mock;
};
mock.mockImplementationOnce = function mockImplementationOnce(implementation) {
config.onceMockImplementations.push(implementation);
updateMockPrototype();
return mock;
};
mock.withImplementation = function withImplementation(implementation, callback) {
const previousImplementation = config.mockImplementation;
const previousOnceImplementations = config.onceMockImplementations;
const reset = () => {
config.mockImplementation = previousImplementation;
config.onceMockImplementations = previousOnceImplementations;
updateMockPrototype();
};
config.mockImplementation = implementation;
config.onceMockImplementations = [];
updateMockPrototype();
const returnValue = callback();
if (typeof returnValue === "object" && typeof returnValue?.then === "function") return returnValue.then(() => {
reset();
return mock;
});
else reset();
return mock;
};
mock.mockReturnThis = function mockReturnThis() {
return mock.mockImplementation(function() {
return this;
});
};
mock.mockReturnValue = function mockReturnValue(value) {
return mock.mockImplementation(function() {
if (new.target) throwConstructorError("mockReturnValue");
return value;
});
};
mock.mockReturnValueOnce = function mockReturnValueOnce(value) {
return mock.mockImplementationOnce(function() {
if (new.target) throwConstructorError("mockReturnValueOnce");
return value;
});
};
mock.mockThrow = function mockThrow(value) {
// eslint-disable-next-line prefer-arrow-callback
return mock.mockImplementation(function() {
throw value;
});
};
mock.mockThrowOnce = function mockThrowOnce(value) {
// eslint-disable-next-line prefer-arrow-callback
return mock.mockImplementationOnce(function() {
throw value;
});
};
mock.mockResolvedValue = function mockResolvedValue(value) {
return mock.mockImplementation(function() {
if (new.target) throwConstructorError("mockResolvedValue");
return Promise.resolve(value);
});
};
mock.mockResolvedValueOnce = function mockResolvedValueOnce(value) {
return mock.mockImplementationOnce(function() {
if (new.target) throwConstructorError("mockResolvedValueOnce");
return Promise.resolve(value);
});
};
mock.mockRejectedValue = function mockRejectedValue(value) {
return mock.mockImplementation(function() {
if (new.target) throwConstructorError("mockRejectedValue");
return Promise.reject(value);
});
};
mock.mockRejectedValueOnce = function mockRejectedValueOnce(value) {
return mock.mockImplementationOnce(function() {
if (new.target) throwConstructorError("mockRejectedValueOnce");
return Promise.reject(value);
});
};
mock.mockClear = function mockClear() {
state.calls = [];
state.contexts = [];
state.instances = [];
state.invocationCallOrder = [];
state.results = [];
state.settledResults = [];
DIRTY_MOCK_STATES.delete(mock);
return mock;
};
mock.mockReset = function mockReset() {
mock.mockClear();
config.mockImplementation = resetToMockImplementation ? mockImplementation : void 0;
config.mockName = resetToMockName ? mock.name || "vi.fn()" : "vi.fn()";
config.onceMockImplementations = [];
updateMockPrototype();
return mock;
};
mock.mockRestore = function mockRestore() {
mock.mockReset();
return restore?.();
};
mock.mockName = function mockName(name) {
if (typeof name === "string") config.mockName = name;
return mock;
};
mock.getMockName = function getMockName() {
return config.mockName || "vi.fn()";
};
if (Symbol.dispose) mock[Symbol.dispose] = () => mock.mockRestore();
if (mockImplementation) mock.mockImplementation(mockImplementation);
else
// vi.spyOn() has no mock implementation, chain the original one
updateMockPrototype();
return mock;
}
function fn(originalImplementation) {
// if the function is already a mock, just return the same function,
// similarly to how vi.spyOn() works
if (originalImplementation != null && isMockFunction(originalImplementation)) return originalImplementation;
return createMockInstance({
// we pass this down so getMockImplementation() always returns the value
mockImplementation: originalImplementation,
// special case so that .mockReset() resets the value to
// the the originalImplementation instead of () => undefined
resetToMockImplementation: true
});
}
function spyOn(object, key, accessor) {
assert(object != null, "The vi.spyOn() function could not find an object to spy upon. The first argument must be defined.");
assert(typeof object === "object" || typeof object === "function", "Vitest cannot spy on a primitive value.");
const [originalDescriptorObject, originalDescriptor] = getDescriptor(object, key) || [];
assert(originalDescriptor || key in object, `The property "${String(key)}" is not defined on the ${typeof object}.`);
let accessType = accessor || "value";
let ssr = false;
// vite ssr support - actual function is stored inside a getter
if (accessType === "value" && originalDescriptor && originalDescriptor.value == null && originalDescriptor.get) {
accessType = "get";
ssr = true;
}
let original;
if (originalDescriptor) {
original = originalDescriptor[accessType];
// weird Proxy edge case where descriptor's value is undefined,
// but there's still a value on the object when called
// https://github.com/vitest-dev/vitest/issues/9439
if (original == null && accessType === "value") original = object[key];
} else if (accessType !== "value") original = () => object[key];
else original = object[key];
const originalImplementation = ssr && original ? original() : original;
const originalType = typeof originalImplementation;
assert(
// allow only functions
originalType === "function" || accessType !== "value" && original == null,
`vi.spyOn() can only spy on a function. Received ${originalType}.`
);
if (isMockFunction(originalImplementation)) return originalImplementation;
const reassign = (cb) => {
const { value, ...desc } = originalDescriptor || {
configurable: true,
writable: true
};
if (accessType !== "value") delete desc.writable;
desc[accessType] = cb;
Object.defineProperty(object, key, desc);
};
const restore = () => {
// if method is defined on the prototype, we can just remove it from
// the current object instead of redefining a copy of it
if (originalDescriptorObject !== object) Reflect.deleteProperty(object, key);
else if (originalDescriptor && !original) Object.defineProperty(object, key, originalDescriptor);
else reassign(original);
};
const mock = createMockInstance({
restore,
originalImplementation,
resetToMockName: true
});
try {
reassign(ssr ? () => mock : mock);
} catch (error) {
if (error instanceof TypeError && Symbol.toStringTag && object[Symbol.toStringTag] === "Module" && (error.message.includes("Cannot redefine property") || error.message.includes("Cannot replace module namespace") || error.message.includes("can't redefine non-configurable property"))) throw new TypeError(`Cannot spy on export "${String(key)}". Module namespace is not configurable in ESM. See: https://vitest.dev/guide/mocking/modules#mocking-a-module`, { cause: error });
throw error;
}
return mock;
}
function getDescriptor(obj, method) {
const objDescriptor = Object.getOwnPropertyDescriptor(obj, method);
if (objDescriptor) return [obj, objDescriptor];
let currentProto = Object.getPrototypeOf(obj);
while (currentProto !== null) {
const descriptor = Object.getOwnPropertyDescriptor(currentProto, method);
if (descriptor) return [currentProto, descriptor];
currentProto = Object.getPrototypeOf(currentProto);
}
}
function assert(condition, message) {
if (!condition) throw new Error(message);
}
let invocationCallCounter = 1;
function createMock({ state, config, name: mockName, prototypeState, prototypeConfig, keepMembersImplementation, mockImplementation, prototypeMembers = [] }) {
const original = config.mockOriginal;
const pseudoOriginal = mockImplementation;
const name = mockName || original?.name || "Mock";
const noopImplementation = function() {};
const prototypeMock = prototypeState && MOCKS_BY_STATE.get(prototypeState);
const namedObject = {
// to keep the name of the function intact
[name]: (function(...args) {
DIRTY_MOCK_STATES.add(namedObject[name]);
if (prototypeMock) DIRTY_MOCK_STATES.add(prototypeMock);
registerCalls(args, state, prototypeState);
registerInvocationOrder(invocationCallCounter++, state, prototypeState);
const result = {
type: "incomplete",
value: void 0
};
const settledResult = {
type: "incomplete",
value: void 0
};
registerResult(result, state, prototypeState);
registerSettledResult(settledResult, state, prototypeState);
const context = new.target ? void 0 : this;
const [instanceIndex, instancePrototypeIndex] = registerInstance(context, state, prototypeState);
const [contextIndex, contextPrototypeIndex] = registerContext(context, state, prototypeState);
const implementation = config.onceMockImplementations.shift() || config.mockImplementation || prototypeConfig?.onceMockImplementations.shift() || prototypeConfig?.mockImplementation || original || noopImplementation;
let returnValue;
let thrownValue;
let didThrow = false;
try {
if (new.target) {
// the prototype chain is already prepared when the implementation
// is registered, but a consumed `mockImplementationOnce` can change
// which implementation this construction uses
if (prototypeMembers.length === 0)
// eslint-disable-next-line ts/no-use-before-define
reparentMockPrototype(mock, implementation === noopImplementation ? void 0 : implementation);
returnValue = Reflect.construct(implementation, args, new.target);
// jest calls this before the implementation, but we have to resolve this _after_
// because we cannot do it before the `Reflect.construct` called the custom implementation.
// fortunately, the constructor is always an empty function because `prototypeMethods`
// are only used by the automocker, so this doesn't matter
for (const prop of prototypeMembers) {
const prototypeMock = returnValue[prop];
// the method was overridden because of inheritance, ignore it
// eslint-disable-next-line ts/no-use-before-define
if (prototypeMock !== mock.prototype[prop]) continue;
const isMock = isMockFunction(prototypeMock);
const prototypeState = isMock ? prototypeMock.mock : void 0;
const prototypeConfig = isMock ? MOCK_CONFIGS.get(prototypeMock) : void 0;
returnValue[prop] = createMockInstance({
originalImplementation: keepMembersImplementation ? prototypeConfig?.mockOriginal : void 0,
prototypeState,
prototypeConfig,
keepMembersImplementation
});
}
} else returnValue = implementation.apply(this, args);
} catch (error) {
thrownValue = error;
didThrow = true;
if (error instanceof TypeError && error.message.includes("is not a constructor")) console.warn(`[vitest] The ${namedObject[name].getMockName()} mock did not use 'function' or 'class' in its implementation, see https://vitest.dev/api/vi#vi-spyon for examples.`);
throw error;
} finally {
if (didThrow) {
result.type = "throw";
result.value = thrownValue;
settledResult.type = "rejected";
settledResult.value = thrownValue;
} else {
result.type = "return";
result.value = returnValue;
if (new.target) {
state.contexts[contextIndex - 1] = returnValue;
state.instances[instanceIndex - 1] = returnValue;
if (contextPrototypeIndex != null && prototypeState) prototypeState.contexts[contextPrototypeIndex - 1] = returnValue;
if (instancePrototypeIndex != null && prototypeState) prototypeState.instances[instancePrototypeIndex - 1] = returnValue;
}
if (returnValue instanceof Promise) returnValue.then((settledValue) => {
settledResult.type = "fulfilled";
settledResult.value = settledValue;
}, (rejectedValue) => {
settledResult.type = "rejected";
settledResult.value = rejectedValue;
});
else {
settledResult.type = "fulfilled";
settledResult.value = returnValue;
}
}
}
return returnValue;
}) };
const mock = namedObject[name];
const copyPropertiesFrom = original || pseudoOriginal;
if (copyPropertiesFrom) copyOriginalStaticProperties(mock, copyPropertiesFrom);
return mock;
}
// puts the implementation's prototype behind `mock.prototype` so instances
// see prototype methods both during and after construction, while properties
// assigned on `mock.prototype` still shadow them
function reparentMockPrototype(mock, implementation) {
const mockPrototype = mock.prototype;
if (mockPrototype == null) return;
// an implementation without a usable prototype (reset mock, arrow or bound
// function) reverts the chain to `Object.prototype`, the parent every mock
// is created with
const parent = implementation?.prototype ?? Object.prototype;
if (Object.getPrototypeOf(mockPrototype) !== parent) Object.setPrototypeOf(mockPrototype, parent);
}
function registerCalls(args, state, prototypeState) {
state.calls.push(args);
prototypeState?.calls.push(args);
}
function registerInvocationOrder(order, state, prototypeState) {
state.invocationCallOrder.push(order);
prototypeState?.invocationCallOrder.push(order);
}
function registerResult(result, state, prototypeState) {
state.results.push(result);
prototypeState?.results.push(result);
}
function registerSettledResult(result, state, prototypeState) {
state.settledResults.push(result);
prototypeState?.settledResults.push(result);
}
function registerInstance(instance, state, prototypeState) {
return [state.instances.push(instance), prototypeState?.instances.push(instance)];
}
function registerContext(context, state, prototypeState) {
return [state.contexts.push(context), prototypeState?.contexts.push(context)];
}
function copyOriginalStaticProperties(mock, original) {
const { properties, descriptors } = getAllProperties(original);
for (const key of properties) {
const descriptor = descriptors[key];
if (getDescriptor(mock, key)) continue;
Object.defineProperty(mock, key, descriptor);
}
}
const ignoreProperties = /* @__PURE__ */ new Set([
"length",
"name",
"prototype",
Symbol.for("nodejs.util.promisify.custom")
]);
function getAllProperties(original) {
const properties = /* @__PURE__ */ new Set();
const descriptors = {};
while (original && original !== Object.prototype && original !== Function.prototype) {
const ownProperties = [...Object.getOwnPropertyNames(original), ...Object.getOwnPropertySymbols(original)];
for (const prop of ownProperties) {
if (descriptors[prop] || ignoreProperties.has(prop)) continue;
properties.add(prop);
descriptors[prop] = Object.getOwnPropertyDescriptor(original, prop);
}
original = Object.getPrototypeOf(original);
}
return {
properties,
descriptors
};
}
function getDefaultConfig(original) {
return {
mockImplementation: void 0,
mockOriginal: original,
mockName: "vi.fn()",
onceMockImplementations: []
};
}
function getDefaultState() {
const state = {
calls: [],
contexts: [],
instances: [],
invocationCallOrder: [],
settledResults: [],
results: [],
get lastCall() {
return state.calls.at(-1);
}
};
return state;
}
function restoreAllMocks() {
for (const restore of MOCK_RESTORE) restore();
MOCK_RESTORE.clear();
}
function clearAllMocks() {
for (const mock of DIRTY_MOCK_STATES) mock.mockClear();
}
function resetAllMocks() {
for (const ref of REGISTERED_MOCKS) {
const mock = ref.deref();
if (mock) mock.mockReset();
else REGISTERED_MOCKS.delete(ref);
}
}
function throwConstructorError(shorthand) {
throw new TypeError(`Cannot use \`${shorthand}\` when called with \`new\`. Use \`mockImplementation\` with a \`class\` keyword instead. See https://vitest.dev/api/mock#class-support for more information.`);
}
var spyModule = /*#__PURE__*/Object.freeze({
__proto__: null,
clearAllMocks: clearAllMocks,
createMockInstance: createMockInstance,
fn: fn,
isMockFunction: isMockFunction,
resetAllMocks: resetAllMocks,
restoreAllMocks: restoreAllMocks,
spyOn: spyOn
});
export { spyOn as a, resetAllMocks as b, clearAllMocks as c, createMockInstance as d, fn as f, isMockFunction as i, restoreAllMocks as r, spyModule as s };
+143
View File
@@ -0,0 +1,143 @@
import { bn as SnapshotState, m as Test, bo as PromisifyAssertion, bp as Tester, bq as Plugin, ad as BenchResult, a as File } from './config.d.CU_b-wJj.js';
interface SnapshotMatcher<
R extends void | Promise<void>,
T = unknown
> {
<U extends { [P in keyof T]: any }>(snapshot: Partial<U>, hint?: string): R;
(hint?: string): R;
}
interface InlineSnapshotMatcher<
R extends void | Promise<void>,
T = unknown
> {
<U extends { [P in keyof T]: any }>(properties: Partial<U>, snapshot?: string, hint?: string): R;
(hint?: string): R;
}
declare module "vitest" {
interface MatcherState {
environment: string;
snapshotState: SnapshotState;
task?: Readonly<Test>;
}
interface ExpectPollOptions {
interval?: number;
timeout?: number;
message?: string;
}
interface ExpectStatic {
assert: Chai.AssertStatic;
unreachable: (message?: string) => never;
soft: <T>(actual: T, message?: string) => Assertion<void, T>;
poll: <T>(actual: (options: {
signal: AbortSignal;
}) => T, options?: ExpectPollOptions) => PromisifyAssertion<Awaited<T>>;
addEqualityTesters: (testers: Array<Tester>) => void;
assertions: (expected: number) => void;
hasAssertions: () => void;
addSnapshotSerializer: (plugin: Plugin) => void;
}
interface Assertion<
R,
T
> {
matchSnapshot: SnapshotMatcher<R, T>;
toMatchSnapshot: SnapshotMatcher<R, T>;
toMatchInlineSnapshot: InlineSnapshotMatcher<R, T>;
/**
* Checks that an error thrown by a function matches a previously recorded snapshot.
*
* @param hint - Optional custom error message.
*
* @example
* expect(functionWithError).toThrowErrorMatchingSnapshot();
*/
toThrowErrorMatchingSnapshot: (hint?: string) => R;
/**
* Checks that an error thrown by a function matches an inline snapshot within the test file.
* Useful for keeping snapshots close to the test code.
*
* @param snapshot - Optional inline snapshot string to match.
* @param hint - Optional custom error message.
*
* @example
* const throwError = () => { throw new Error('Error occurred') };
* expect(throwError).toThrowErrorMatchingInlineSnapshot(`"Error occurred"`);
*/
toThrowErrorMatchingInlineSnapshot: (snapshot?: string, hint?: string) => R;
/**
* Compares the received value to a snapshot saved in a specified file.
* Useful for cases where snapshot content is large or needs to be shared across tests.
*
* @param filepath - Path to the snapshot file.
* @param hint - Optional custom error message.
*
* @example
* await expect(largeData).toMatchFileSnapshot('path/to/snapshot.json');
*/
toMatchFileSnapshot: (filepath: string, hint?: string) => Promise<void>;
/**
* Asserts that a benchmark result is faster than another benchmark result.
* Compares mean latency — lower is faster.
*
* @example
* const result = await bench.compare(
* bench('lib1', () => { lib1() }),
* bench('lib2', () => { lib2() }),
* )
* expect(result.get('lib1')).toBeFasterThan(result.get('lib2'))
* expect(result.get('lib1')).toBeFasterThan(result.get('lib2'), { delta: 0.1 })
*/
toBeFasterThan: (expected: BenchResult, options?: {
delta?: number;
}) => R;
/**
* Asserts that a benchmark result is slower than another benchmark result.
* Compares mean latency — higher is slower.
*
* @example
* const result = await bench.compare(
* bench('lib1', () => { lib1() }),
* bench('lib2', () => { lib2() }),
* )
* expect(result.get('lib2')).toBeSlowerThan(result.get('lib1'))
* expect(result.get('lib2')).toBeSlowerThan(result.get('lib1'), { delta: 0.2 })
*/
toBeSlowerThan: (expected: BenchResult, options?: {
delta?: number;
}) => R;
/**
* Ensures a `vi.when` chain has been exhausted.
*
* A chain is exhausted when at least one `calledWith` with an associated action (`then*`) has been registered
* and every registered behavior has been fully consumed. A chain with no registered
* behaviors, or with `calledWith` entries that have no associated `then*` actions, is never considered exhausted.
*
* @see {@link https://vitest.dev/api/expect#tohavebeenexhausted}
*
* @example
* const w = vi.when(spy).calledWith('hello').thenReturnOnce('HELLO')
*
* expect(w).not.toHaveBeenExhausted()
*
* expect(spy('hello')).toBe('HELLO')
*
* expect(w).toHaveBeenExhausted()
*/
toHaveBeenExhausted: () => R;
}
}
interface HashMeta {
typecheck?: boolean;
__vitest_label__?: string;
}
declare function createFileTask(filepath: string, root: string, projectName: string | undefined, pool?: string, viteEnvironment?: string, meta?: HashMeta): File;
/**
* Generate a unique ID for a file based on its path and project name
* @param file File relative to the root of the project to keep ID the same between different machines
* @param projectName The name of the test project
*/
declare function generateFileHash(file: string, projectName: string | undefined, meta?: HashMeta): string;
export { createFileTask as c, generateFileHash as g };
+95
View File
@@ -0,0 +1,95 @@
// src/index.ts
var b = {
reset: [0, 0],
bold: [1, 22, "\x1B[22m\x1B[1m"],
dim: [2, 22, "\x1B[22m\x1B[2m"],
italic: [3, 23],
underline: [4, 24],
inverse: [7, 27],
hidden: [8, 28],
strikethrough: [9, 29],
black: [30, 39],
red: [31, 39],
green: [32, 39],
yellow: [33, 39],
blue: [34, 39],
magenta: [35, 39],
cyan: [36, 39],
white: [37, 39],
gray: [90, 39],
bgBlack: [40, 49],
bgRed: [41, 49],
bgGreen: [42, 49],
bgYellow: [43, 49],
bgBlue: [44, 49],
bgMagenta: [45, 49],
bgCyan: [46, 49],
bgWhite: [47, 49],
blackBright: [90, 39],
redBright: [91, 39],
greenBright: [92, 39],
yellowBright: [93, 39],
blueBright: [94, 39],
magentaBright: [95, 39],
cyanBright: [96, 39],
whiteBright: [97, 39],
bgBlackBright: [100, 49],
bgRedBright: [101, 49],
bgGreenBright: [102, 49],
bgYellowBright: [103, 49],
bgBlueBright: [104, 49],
bgMagentaBright: [105, 49],
bgCyanBright: [106, 49],
bgWhiteBright: [107, 49]
};
function i(e) {
return String(e);
}
i.open = "";
i.close = "";
function p() {
let e = {
isColorSupported: false,
reset: i
};
for (let r in b)
e[r] = i;
return e;
}
function B() {
let e = typeof process != "undefined" ? process : void 0, r = (e == null ? void 0 : e.env) || {}, a = r.FORCE_TTY !== "false", l = (e == null ? void 0 : e.argv) || [];
return !("NO_COLOR" in r || l.includes("--no-color")) && ("FORCE_COLOR" in r || l.includes("--color") || (e == null ? void 0 : e.platform) === "win32" || a && r.TERM !== "dumb" || "CI" in r) || typeof window != "undefined" && !!window.chrome;
}
function C({ force: e } = {}) {
let r = e || B(), a = (t, o, u, n) => {
let g = "", s = 0;
do
g += t.substring(s, n) + u, s = n + o.length, n = t.indexOf(o, s);
while (~n);
return g + t.substring(s);
}, l = (t, o, u = t) => {
let n = (g) => {
let s = String(g), h = s.indexOf(o, t.length);
return ~h ? t + a(s, o, u, h) + o : t + s + o;
};
return n.open = t, n.close = o, n;
}, c = {
isColorSupported: r
}, f = (t) => `\x1B[${t}m`;
for (let t in b) {
let o = b[t];
c[t] = r ? l(
f(o[0]),
f(o[1]),
o[2]
) : i;
}
return c;
}
var d = C();
function m() {
Object.assign(d, p());
}
var y = d;
export { m, y };
+182
View File
@@ -0,0 +1,182 @@
import { stripVTControlCharacters } from 'node:util';
import { t as truncateString$1 } from './display.pkpxlVcY.js';
import { i as isAbsolute, a as relative, d as dirname, b as basename, s as slash } from './pathe.M-eThtNZ.Clq7AXhv.js';
import { y } from './tinyrainbow.Ht9iggcq.js';
const F_RIGHT = "→";
const F_DOWN = "↓";
const F_DOWN_RIGHT = "↳";
const F_POINTER = "❯";
const F_DOT = "·";
const F_CHECK = "✓";
const F_CROSS = "×";
const F_LONG_DASH = "⎯";
const F_TODO = "□";
const F_TREE_NODE_MIDDLE = "├──";
const F_TREE_NODE_END = "└──";
const pointer = y.yellow(F_POINTER);
const skipped = y.dim(y.gray(F_DOWN));
const todo = y.dim(y.gray(F_TODO));
const benchmarkPass = y.green(F_DOT);
const testPass = y.green(F_CHECK);
const taskFail = y.red(F_CROSS);
const suiteFail = y.red(F_POINTER);
const pending = y.gray("·");
const separator = y.dim(" > ");
const labelDefaultColors = [
y.bgYellow,
y.bgCyan,
y.bgGreen,
y.bgMagenta
];
function getCols(delta = 0) {
let length = process.stdout?.columns;
if (!length || Number.isNaN(length)) length = 30;
return Math.max(length + delta, 0);
}
function errorBanner(message) {
return divider(y.bold(y.bgRed(` ${message} `)), null, null, y.red);
}
function divider(text, left, right, color) {
const cols = getCols();
const c = color || ((text) => text);
if (text) {
const textLength = stripVTControlCharacters(text).length;
if (left == null && right != null) left = cols - textLength - right;
else {
left = left ?? Math.floor((cols - textLength) / 2);
right = cols - textLength - left;
}
left = Math.max(0, left);
right = Math.max(0, right);
return `${c(F_LONG_DASH.repeat(left))}${text}${c(F_LONG_DASH.repeat(right))}`;
}
return F_LONG_DASH.repeat(cols);
}
function formatTestPath(root, path) {
if (isAbsolute(path)) path = relative(root, path);
const dir = dirname(path);
const ext = path.match(/(\.(spec|test)\.[cm]?[tj]sx?)$/)?.[0] || "";
const base = basename(path, ext);
return slash(y.dim(`${dir}/`) + y.bold(base)) + y.dim(ext);
}
function renderSnapshotSummary(rootDir, snapshots) {
const summary = [];
if (snapshots.added) summary.push(y.bold(y.green(`${snapshots.added} written`)));
if (snapshots.unmatched) summary.push(y.bold(y.red(`${snapshots.unmatched} failed`)));
if (snapshots.updated) summary.push(y.bold(y.green(`${snapshots.updated} updated `)));
if (snapshots.filesRemoved) if (snapshots.didUpdate) summary.push(y.bold(y.green(`${snapshots.filesRemoved} files removed `)));
else summary.push(y.bold(y.yellow(`${snapshots.filesRemoved} files obsolete `)));
if (snapshots.filesRemovedList && snapshots.filesRemovedList.length) {
const [head, ...tail] = snapshots.filesRemovedList;
summary.push(`${y.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, head)}`);
tail.forEach((key) => {
summary.push(` ${y.gray(F_DOT)} ${formatTestPath(rootDir, key)}`);
});
}
if (snapshots.unchecked) {
if (snapshots.didUpdate) summary.push(y.bold(y.green(`${snapshots.unchecked} removed`)));
else summary.push(y.bold(y.yellow(`${snapshots.unchecked} obsolete`)));
snapshots.uncheckedKeysByFile.forEach((uncheckedFile) => {
summary.push(`${y.gray(F_DOWN_RIGHT)} ${formatTestPath(rootDir, uncheckedFile.filePath)}`);
uncheckedFile.keys.forEach((key) => summary.push(` ${y.gray(F_DOT)} ${key}`));
});
}
return summary;
}
function countTestErrors(tasks) {
return tasks.reduce((c, i) => c + (i.result?.errors?.length || 0), 0);
}
function getStateString(tasks, name = "tests", showTotal = true) {
if (tasks.length === 0) return y.dim(`no ${name}`);
const passed = tasks.reduce((acc, i) => {
// Exclude expected failures from passed count
if (i.result?.state === "pass" && i.type === "test" && i.fails) return acc;
return i.result?.state === "pass" ? acc + 1 : acc;
}, 0);
const failed = tasks.reduce((acc, i) => i.result?.state === "fail" ? acc + 1 : acc, 0);
const skipped = tasks.reduce((acc, i) => i.mode === "skip" ? acc + 1 : acc, 0);
const todo = tasks.reduce((acc, i) => i.mode === "todo" ? acc + 1 : acc, 0);
const expectedFail = tasks.reduce((acc, i) => {
// Count tests that are marked as .fails and passed (which means they failed as expected)
if (i.result?.state === "pass" && i.type === "test" && i.fails) return acc + 1;
return acc;
}, 0);
return [
failed ? y.bold(y.red(`${failed} failed`)) : null,
passed ? y.bold(y.green(`${passed} passed`)) : null,
expectedFail ? y.cyan(`${expectedFail} expected fail`) : null,
skipped ? y.yellow(`${skipped} skipped`) : null,
todo ? y.gray(`${todo} todo`) : null
].filter(Boolean).join(y.dim(" | ")) + (showTotal ? y.gray(` (${tasks.length})`) : "");
}
function getStateSymbol(task) {
if (task.mode === "todo") return todo;
if (task.mode === "skip") return skipped;
if (!task.result) return pending;
if (task.result.state === "run" || task.result.state === "queued") {
if (task.type === "suite") return pointer;
}
if (task.result.state === "pass") return task.meta?.benchmark ? benchmarkPass : testPass;
if (task.result.state === "fail") return task.type === "suite" ? suiteFail : taskFail;
return " ";
}
function formatTimeString(date) {
return date.toTimeString().split(" ")[0];
}
function formatTime(time) {
if (time > 1e3) return `${(time / 1e3).toFixed(2)}s`;
return `${Math.round(time)}ms`;
}
function formatProjectName(project, suffix = " ") {
if (!project?.name) return "";
if (!y.isColorSupported) return `|${project.name}|${suffix}`;
let background = project.color && y[`bg${capitalize(project.color)}`];
if (!background) {
const index = project.name.split("").reduce((acc, v, idx) => acc + v.charCodeAt(0) + idx, 0);
background = labelDefaultColors[index % labelDefaultColors.length];
}
return y.black(background(` ${project.name} `)) + suffix;
}
function withLabel(color, label, message) {
const bgColor = `bg${color.charAt(0).toUpperCase()}${color.slice(1)}`;
return `${y.bold(y.black(y[bgColor](` ${label} `)))} ${message ? y[color](message) : ""}`;
}
function padSummaryTitle(str) {
return y.dim(`${str.padStart(11)} `);
}
function truncateString(text, maxLength) {
return truncateString$1(stripVTControlCharacters(text), maxLength);
}
function capitalize(text) {
return `${text[0].toUpperCase()}${text.slice(1)}`;
}
/**
* Returns the singular or plural form of a word based on the count.
*/
function noun(count, singular, plural) {
if (count === 1) return singular;
return plural;
}
var utils = /*#__PURE__*/Object.freeze({
__proto__: null,
countTestErrors: countTestErrors,
divider: divider,
errorBanner: errorBanner,
formatProjectName: formatProjectName,
formatTime: formatTime,
formatTimeString: formatTimeString,
getStateString: getStateString,
getStateSymbol: getStateSymbol,
noun: noun,
padSummaryTitle: padSummaryTitle,
renderSnapshotSummary: renderSnapshotSummary,
separator: separator,
taskFail: taskFail,
truncateString: truncateString,
withLabel: withLabel
});
export { F_POINTER as F, formatTimeString as a, taskFail as b, F_CHECK as c, divider as d, errorBanner as e, formatProjectName as f, F_DOWN_RIGHT as g, getStateSymbol as h, getStateString as i, formatTime as j, countTestErrors as k, F_TREE_NODE_END as l, F_TREE_NODE_MIDDLE as m, noun as n, F_RIGHT as o, padSummaryTitle as p, renderSnapshotSummary as r, separator as s, truncateString as t, utils as u, withLabel as w };
+43
View File
@@ -0,0 +1,43 @@
const NAME_WORKER_STATE = "__vitest_worker__";
class EnvironmentTeardownError extends Error {
name = "EnvironmentTeardownError";
}
function getWorkerState() {
// @ts-expect-error untyped global
const workerState = globalThis[NAME_WORKER_STATE];
if (!workerState) throw new Error("Vitest failed to access its internal state.\n\nOne of the following is possible:\n- \"vitest\" is imported directly without running \"vitest\" command\n- \"vitest\" is imported inside \"globalSetup\" (to fix this, use \"setupFiles\" instead, because \"globalSetup\" runs in a different context)\n- \"vitest\" is imported inside Vite / Vitest config file\n- Otherwise, it might be a Vitest bug. Please report it to https://github.com/vitest-dev/vitest/issues\n");
return workerState;
}
function getSafeWorkerState() {
// @ts-expect-error untyped global
return globalThis[NAME_WORKER_STATE];
}
function provideWorkerState(context, state) {
Object.defineProperty(context, NAME_WORKER_STATE, {
value: state,
configurable: true,
writable: true,
enumerable: false
});
return state;
}
function isChildProcess() {
return typeof process !== "undefined" && !!process.send;
}
function resetModules(modules, resetMocks = false) {
const skipPaths = [
/\/vitest\/dist\//,
/vitest-virtual-\w+\/dist/,
/@vitest\/dist/,
...!resetMocks ? [/^mock:/] : []
];
modules.idToModuleMap.forEach((node, path) => {
if (skipPaths.some((re) => re.test(path))) return;
node.promise = void 0;
node.exports = void 0;
node.evaluated = false;
node.importers.clear();
});
}
export { EnvironmentTeardownError as E, getSafeWorkerState as a, getWorkerState as g, isChildProcess as i, provideWorkerState as p, resetModules as r };
File diff suppressed because it is too large. Load diff
+265
View File
@@ -0,0 +1,265 @@
import { EvaluatedModules } from 'vite/module-runner';
import { F as FileSpecification, b as SerializedConfig, X as Task, Y as CancelReason } from './config.d.CU_b-wJj.js';
import { a as RuntimeRPC, R as RunnerRPC, G as GetterTracker } from './rpc.d.DA9Utv4e.js';
import { E as Environment } from './environment.d.C6xYahWA.js';
//#region src/messages.d.ts
declare const TYPE_REQUEST: "q";
interface RpcRequest {
/**
* Type
*/
t: typeof TYPE_REQUEST;
/**
* ID
*/
i?: string;
/**
* Method
*/
m: string;
/**
* Arguments
*/
a: any[];
/**
* Optional
*/
o?: boolean;
}
//#endregion
//#region src/utils.d.ts
type ArgumentsType<T> = T extends ((...args: infer A) => any) ? A : never;
type ReturnType<T> = T extends ((...args: any) => infer R) ? R : never;
type Thenable<T> = T | PromiseLike<T>;
//#endregion
//#region src/main.d.ts
type PromisifyFn<T> = ReturnType<T> extends Promise<any> ? T : (...args: ArgumentsType<T>) => Promise<Awaited<ReturnType<T>>>;
type BirpcResolver<This> = (this: This, name: string, resolved: (...args: unknown[]) => unknown) => Thenable<((...args: any[]) => any) | undefined>;
interface ChannelOptions {
/**
* Function to post raw message
*/
post: (data: any, ...extras: any[]) => Thenable<any>;
/**
* Listener to receive raw message
*/
on: (fn: (data: any, ...extras: any[]) => void) => Thenable<any>;
/**
* Clear the listener when `$close` is called
*/
off?: (fn: (data: any, ...extras: any[]) => void) => Thenable<any>;
/**
* Custom function to serialize data
*
* by default it passes the data as-is
*/
serialize?: (data: any) => any;
/**
* Custom function to deserialize data
*
* by default it passes the data as-is
*/
deserialize?: (data: any) => any;
/**
* Call the methods with the RPC context or the original functions object
*/
bind?: 'rpc' | 'functions';
/**
* Custom meta data to attached to the RPC instance's `$meta` property
*/
meta?: any;
}
interface EventOptions<RemoteFunctions extends object = Record<string, unknown>, LocalFunctions extends object = Record<string, unknown>, Proxify extends boolean = true> {
/**
* Names of remote functions that do not need response.
*/
eventNames?: (keyof RemoteFunctions)[];
/**
* Maximum timeout for waiting for response, in milliseconds.
*
* @default 60_000
*/
timeout?: number;
/**
* Whether to proxy the remote functions.
*
* When `proxify` is false, calling the remote function
* with `rpc.$call('method', ...args)` instead of `rpc.method(...args)`
* explicitly is required.
*
* @default true
*/
proxify?: Proxify;
/**
* Custom resolver to resolve function to be called
*
* For advanced use cases only
*/
resolver?: BirpcResolver<BirpcReturn<RemoteFunctions, LocalFunctions, Proxify>>;
/**
* Hook triggered before an event is sent to the remote
*
* @param req - Request parameters
* @param next - Function to continue the request
* @param resolve - Function to resolve the response directly
*/
onRequest?: (this: BirpcReturn<RemoteFunctions, LocalFunctions, Proxify>, req: RpcRequest, next: (req?: RpcRequest) => Promise<any>, resolve: (res: any) => void) => void | Promise<void>;
/**
* Custom error handler for errors occurred in local functions being called
*
* @returns `true` to prevent the error from being thrown
*/
onFunctionError?: (this: BirpcReturn<RemoteFunctions, LocalFunctions, Proxify>, error: Error, functionName: string, args: any[]) => boolean | void;
/**
* Custom error handler for errors occurred during serialization or messsaging
*
* @returns `true` to prevent the error from being thrown
*/
onGeneralError?: (this: BirpcReturn<RemoteFunctions, LocalFunctions, Proxify>, error: Error, functionName?: string, args?: any[]) => boolean | void;
/**
* Custom error handler for timeouts
*
* @returns `true` to prevent the error from being thrown
*/
onTimeoutError?: (this: BirpcReturn<RemoteFunctions, LocalFunctions, Proxify>, functionName: string, args: any[]) => boolean | void;
}
type BirpcOptions<RemoteFunctions extends object = Record<string, unknown>, LocalFunctions extends object = Record<string, unknown>, Proxify extends boolean = true> = EventOptions<RemoteFunctions, LocalFunctions, Proxify> & ChannelOptions;
type BirpcFn<T> = PromisifyFn<T> & {
/**
* Send event without asking for response
*/
asEvent: (...args: ArgumentsType<T>) => Promise<void>;
};
interface BirpcReturnBuiltin<RemoteFunctions, LocalFunctions = Record<string, unknown>> {
/**
* Raw functions object
*/
$functions: LocalFunctions;
/**
* Whether the RPC is closed
*/
readonly $closed: boolean;
/**
* Custom meta data attached to the RPC instance
*/
readonly $meta: any;
/**
* Close the RPC connection
*/
$close: (error?: Error) => void;
/**
* Reject pending calls
*/
$rejectPendingCalls: (handler?: PendingCallHandler) => Promise<void>[];
/**
* Call the remote function and wait for the result.
* An alternative to directly calling the function
*/
$call: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]>>>;
/**
* Same as `$call`, but returns `undefined` if the function is not defined on the remote side.
*/
$callOptional: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<Awaited<ReturnType<RemoteFunctions[K]> | undefined>>;
/**
* Send event without asking for response
*/
$callEvent: <K extends keyof RemoteFunctions>(method: K, ...args: ArgumentsType<RemoteFunctions[K]>) => Promise<void>;
/**
* Call the remote function with the raw options.
*/
$callRaw: (options: {
method: string;
args: unknown[];
event?: boolean;
optional?: boolean;
}) => Promise<Awaited<ReturnType<any>>[]>;
}
type ProxifiedRemoteFunctions<RemoteFunctions extends object = Record<string, unknown>> = { [K in keyof RemoteFunctions]: BirpcFn<RemoteFunctions[K]>; };
type BirpcReturn<RemoteFunctions extends object = Record<string, unknown>, LocalFunctions extends object = Record<string, unknown>, Proxify extends boolean = true> = Proxify extends true ? ProxifiedRemoteFunctions<RemoteFunctions> & BirpcReturnBuiltin<RemoteFunctions, LocalFunctions> : BirpcReturnBuiltin<RemoteFunctions, LocalFunctions>;
type PendingCallHandler = (options: Pick<PromiseEntry, 'method' | 'reject'>) => void | Promise<void>;
interface PromiseEntry {
resolve: (arg: any) => void;
reject: (error: any) => void;
method: string;
timeoutId?: ReturnType<typeof setTimeout>;
}
declare const setTimeout: any;
type WorkerRPC = BirpcReturn<RuntimeRPC, RunnerRPC>;
interface ContextTestEnvironment {
name: string;
options: Record<string, any> | null;
}
interface WorkerTestEnvironment {
name: string;
options: Record<string, any> | null;
}
type TestExecutionMethod = "run" | "collect";
interface WorkerExecuteContext {
files: FileSpecification[];
providedContext: Record<string, any>;
invalidates?: string[];
environment: ContextTestEnvironment;
/** Exposed to test runner as `VITEST_WORKER_ID`. Value is unique per each isolated worker. */
workerId: number;
}
interface MetaEnv {
[key: string]: any;
BASE_URL: string;
MODE: string;
DEV: boolean;
PROD: boolean;
SSR: boolean;
}
interface ContextRPC {
pool: string;
config: SerializedConfig;
projectName: string;
environment: WorkerTestEnvironment;
rpc: WorkerRPC;
metaEnv: MetaEnv;
files: FileSpecification[];
providedContext: Record<string, any>;
invalidates?: string[];
/** Exposed to test runner as `VITEST_WORKER_ID`. Value is unique per each isolated worker. */
workerId: number;
concurrencyId: number;
}
interface WorkerSetupContext {
environment: WorkerTestEnvironment;
pool: string;
config: SerializedConfig;
projectName: string;
rpc: WorkerRPC;
metaEnv: MetaEnv;
}
interface WorkerGlobalState {
ctx: ContextRPC;
config: SerializedConfig;
rpc: WorkerRPC;
current?: Task;
filepath?: string;
metaEnv: MetaEnv;
metaDefines?: Record<string, any>;
environment: Environment;
evaluatedModules: EvaluatedModules;
resolvingModules: Set<string>;
moduleExecutionInfo: Map<string, any>;
getterTracker?: GetterTracker;
onCancel: (listener: (reason: CancelReason) => unknown) => () => void;
onCleanup: (listener: () => unknown) => void;
providedContext: Record<string, any>;
durations: {
environment: number;
prepare: number;
/**
* Wall time the worker spent blocked on module fetch requests to the
* server, measured as the union of in-flight intervals.
*/
fetch: number;
};
onFilterStackTrace?: (trace: string) => string;
}
export type { BirpcReturn as B, ContextRPC as C, TestExecutionMethod as T, WorkerGlobalState as W, ContextTestEnvironment as a, WorkerExecuteContext as b, WorkerTestEnvironment as c, WorkerSetupContext as d, BirpcOptions as e };