From c92fcd6a0461c934c9eb1b9d8080da81b9a7e38d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Mi=C5=9Btal?= Date: Mon, 8 Jan 2024 16:00:14 +0100 Subject: [PATCH] Migrate to `vitest` runner --- create-vite-express/src/cli.ts | 8 +- create-vite-express/src/templates.ts | 10 + package.json | 7 +- tests/cli.test.ts | 40 -- tests/{env => envs/basic}/dist/index.html | 0 .../basic}/dist/subpath/index.html | 0 tests/{env => envs/basic}/dist/test.txt | 0 tests/{env => envs/basic}/index.html | 0 tests/{env => envs/basic}/public/test.txt | 0 tests/{env => envs/basic}/subpath/index.html | 0 .../indexless}/dist/subpath/index.html | 0 .../indexless}/subpath/index.html | 0 tests/index.test.ts | 15 - tests/lib/runner.ts | 134 ------ tests/lib/utils.ts | 106 ----- tests/libs/utils.ts | 250 ++++++++++ tests/server.test.ts | 447 +++++++++--------- tests/templates.test.ts | 188 +++----- vite.config.ts | 7 + yarn.lock | 321 ++++++++++++- 20 files changed, 900 insertions(+), 633 deletions(-) create mode 100644 create-vite-express/src/templates.ts delete mode 100644 tests/cli.test.ts rename tests/{env => envs/basic}/dist/index.html (100%) rename tests/{env => envs/basic}/dist/subpath/index.html (100%) rename tests/{env => envs/basic}/dist/test.txt (100%) rename tests/{env => envs/basic}/index.html (100%) rename tests/{env => envs/basic}/public/test.txt (100%) rename tests/{env => envs/basic}/subpath/index.html (100%) rename tests/{noIndexEnv => envs/indexless}/dist/subpath/index.html (100%) rename tests/{noIndexEnv => envs/indexless}/subpath/index.html (100%) delete mode 100644 tests/index.test.ts delete mode 100644 tests/lib/runner.ts delete mode 100644 tests/lib/utils.ts create mode 100644 tests/libs/utils.ts create mode 100644 vite.config.ts diff --git a/create-vite-express/src/cli.ts b/create-vite-express/src/cli.ts index 337ef47..24ffcd8 100644 --- a/create-vite-express/src/cli.ts +++ b/create-vite-express/src/cli.ts @@ -3,13 +3,7 @@ import * as kolorist from "kolorist"; import path from "path"; import prompts from "prompts"; -type Template = prompts.Choice & { color: (msg: string) => string }; - -const TEMPLATES: Template[] = [ - { title: "Vanilla", value: "vanilla", color: kolorist.yellow }, - { title: "React", value: "react", color: kolorist.cyan }, - { title: "Vue", value: "vue", color: kolorist.green }, -]; +import { TEMPLATES } from "./templates"; async function main() { const answers = await prompts([ diff --git a/create-vite-express/src/templates.ts b/create-vite-express/src/templates.ts new file mode 100644 index 0000000..63f6ee2 --- /dev/null +++ b/create-vite-express/src/templates.ts @@ -0,0 +1,10 @@ +import * as kolorist from "kolorist"; +import prompts from "prompts"; + +type Template = prompts.Choice & { color: (msg: string) => string }; + +export const TEMPLATES: Template[] = [ + { title: "Vanilla", value: "vanilla", color: kolorist.yellow }, + { title: "React", value: "react", color: kolorist.cyan }, + { title: "Vue", value: "vue", color: kolorist.green }, +]; diff --git a/package.json b/package.json index ce9cb93..7b17c76 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "dev": "concurrently -i -P \"sh scripts/dev.sh {1}\" \"yarn build --watch\" --", "build": "rollup --config rollup.config.js --bundleConfigAsCjs", "postbuild": "sed -i \"s/export default _default/export = _default/\" dist/main.d.ts", - "test": "tsx tests/index.test.ts", - "test:local": "LOCAL_BUILD_TEST=true tsx tests/index.test.ts", + "pretest": "yarn build", + "test": "vitest --pool=forks", "format": "prettier --write --loglevel silent", "lint": "eslint --fix", "type-check": "tsc --noEmit; tsc --noEmit -p create-vite-express/tsconfig.json", @@ -52,7 +52,8 @@ "tslib": "^2.6.2", "tsx": "^4.5.0", "typescript": "^5.3.2", - "vite": "^5.0.2" + "vite": "^5.0.2", + "vitest": "^1.1.1" }, "files": [ "./dist" diff --git a/tests/cli.test.ts b/tests/cli.test.ts deleted file mode 100644 index 8186ff1..0000000 --- a/tests/cli.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import fs from "fs"; - -import { expectCommandOutput, it, run, test } from "./lib/runner"; -import { installYarn } from "./lib/utils"; - -const baseDir = process.cwd(); -const templates = fs.readdirSync("create-vite-express/templates"); - -for (const template of templates) { - test(`[CLI] Template "${template}"`, async (done) => { - process.chdir(`create-vite-express/templates/${template}`); - - await installYarn(); - it("yarn installed"); - - await expectCommandOutput("yarn dev", [/Running in/, /development/]); - it("dev command works"); - - await expectCommandOutput("yarn build"); - it("app can be built"); - - await expectCommandOutput("yarn start", [/Running in/, /production/]); - it("production build works"); - - // Vue needs Volar extension to support .vue files correctly in TS LS - // https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin - // so it's easier to just ignore it - if (template.includes("-ts") && !template.includes("vue")) { - await expectCommandOutput( - "yarn tsc -p src/client/tsconfig.json --noEmit", - ); - it("client passes typecheck"); - } - - process.chdir(baseDir); - done(); - }); -} - -run(); diff --git a/tests/env/dist/index.html b/tests/envs/basic/dist/index.html similarity index 100% rename from tests/env/dist/index.html rename to tests/envs/basic/dist/index.html diff --git a/tests/env/dist/subpath/index.html b/tests/envs/basic/dist/subpath/index.html similarity index 100% rename from tests/env/dist/subpath/index.html rename to tests/envs/basic/dist/subpath/index.html diff --git a/tests/env/dist/test.txt b/tests/envs/basic/dist/test.txt similarity index 100% rename from tests/env/dist/test.txt rename to tests/envs/basic/dist/test.txt diff --git a/tests/env/index.html b/tests/envs/basic/index.html similarity index 100% rename from tests/env/index.html rename to tests/envs/basic/index.html diff --git a/tests/env/public/test.txt b/tests/envs/basic/public/test.txt similarity index 100% rename from tests/env/public/test.txt rename to tests/envs/basic/public/test.txt diff --git a/tests/env/subpath/index.html b/tests/envs/basic/subpath/index.html similarity index 100% rename from tests/env/subpath/index.html rename to tests/envs/basic/subpath/index.html diff --git a/tests/noIndexEnv/dist/subpath/index.html b/tests/envs/indexless/dist/subpath/index.html similarity index 100% rename from tests/noIndexEnv/dist/subpath/index.html rename to tests/envs/indexless/dist/subpath/index.html diff --git a/tests/noIndexEnv/subpath/index.html b/tests/envs/indexless/subpath/index.html similarity index 100% rename from tests/noIndexEnv/subpath/index.html rename to tests/envs/indexless/subpath/index.html diff --git a/tests/index.test.ts b/tests/index.test.ts deleted file mode 100644 index 6d7192d..0000000 --- a/tests/index.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { execSync } from "child_process"; - -import { runTestFile } from "./lib/runner"; -import { isLocalBuild } from "./lib/utils"; - -if (isLocalBuild()) { - execSync("rm -f vite-express-*.tgz && yarn build && yarn pack", { - stdio: "inherit", - }); -} - -runTestFile("server.test.ts"); -runTestFile("server.test.ts", { mode: "production" }); -runTestFile("templates.test.ts"); -runTestFile("cli.test.ts"); diff --git a/tests/lib/runner.ts b/tests/lib/runner.ts deleted file mode 100644 index 6e9ed2f..0000000 --- a/tests/lib/runner.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { execSync, spawn } from "child_process"; -import pc from "picocolors"; - -import { getExecutionTimeSeconds, log, wait } from "../lib/utils"; - -type Test = { - name: string; - fn: (done: () => void) => Promise; -}; -export class TestError extends Error {} - -export function test(description: string, fn: Test["fn"]) { - tests.push({ - name: description, - fn: async (done) => - fn(done).catch((e) => { - throw e; - }), - }); -} - -export function it(description: string) { - log.it(description); -} - -export function expect(value: T) { - return { - toBe: (expected: T) => { - if (value !== expected) - throw new TestError( - `Given value is not equal to the expected one\n\tgiven: ${pc.red( - JSON.stringify(value), - )}\n\texpected: ${pc.green(JSON.stringify(expected))}`, - ); - }, - toMatch: (regex: RegExp) => { - if (!regex.test(String(value))) - throw new TestError( - `Given value does not match regex\n\tgiven: ${pc.red( - String(value).trim(), - )}\n\texpected to match: ${pc.green(String(regex))}`, - ); - }, - }; -} - -export async function expectCommandOutput( - cmd: string, - matchOutputRegex?: RegExp[], -) { - const [command, ...args] = cmd.split(" "); - - const child = spawn(command, args, { detached: true }); - - await new Promise((resolve, reject) => { - child.stdout?.on("data", (msg) => { - process.stdout.write(msg); - if (matchOutputRegex?.every((regex) => regex.test(msg))) { - if (child.pid) process.kill(-child.pid); - resolve(); - } - }); - - child.stderr?.on("data", (msg) => { - process.stdout.write(msg); - }); - - child.on("close", (code) => { - if ((code ?? 0) > 0) { - reject(`Process "${cmd}" exited with code ${code}`); - } else if (matchOutputRegex) { - reject("Process closed without expected output"); - } else resolve(); - }); - }); - - await wait(100); -} - -let time: [number, number]; -let passedTestCount = 0; -const tests: Test[] = []; - -export async function run() { - time = process.hrtime(); - - for (const test of tests) { - try { - log.test(test.name); - const time = await getExecutionTimeSeconds( - () => - new Promise((resolve, reject) => - test.fn(resolve).catch(reject), - ), - ); - passedTestCount++; - log.pass(`Done in ${pc.gray(`${time}s`)}`); - } catch (e) { - if (e instanceof TestError) { - log.fail(e.message); - printSummary(); - process.exit(1); - } else throw e; - } - } - - printSummary(); - process.exit(0); -} - -process.on("unhandledRejection", (e) => { - log.fail(e instanceof TestError ? e.message : String(e)); - printSummary(); - process.exit(1); -}); - -export function runTestFile( - fileName: string, - options?: { mode: "production" | "development" }, -) { - execSync(`tsx tests/${fileName}`, { - stdio: "inherit", - env: { ...process.env, NODE_ENV: options?.mode ?? "development" }, - }); -} - -function printSummary() { - log.summary( - process.hrtime(time), - passedTestCount, - tests.length - passedTestCount, - tests.length, - ); -} diff --git a/tests/lib/utils.ts b/tests/lib/utils.ts deleted file mode 100644 index b0ef660..0000000 --- a/tests/lib/utils.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { execSync } from "child_process"; -import fs from "fs"; -import colors from "picocolors"; -import { ElementHandle, Page } from "puppeteer"; -import { rimraf } from "rimraf"; - -export const log = { - test: (msg: string) => { - process.stdout.write( - `${colors.bgYellow(colors.black(colors.bold(" TEST ")))} ${msg}\n`, - ); - }, - it: (msg: string) => { - process.stdout.write(`\t${colors.green("✓")} ${msg}\n`); - }, - fail: (msg: string) => { - process.stdout.write( - `${colors.bgRed(colors.black(colors.bold(" FAIL ")))} ${msg}\n`, - ); - }, - pass: (msg: string) => { - process.stdout.write( - `${colors.bgGreen(colors.black(colors.bold(" PASS ")))} ${msg}\n`, - ); - }, - summary: ( - hrtime: [number, number], - passed: number, - failed: number, - total: number, - ) => { - process.stdout.write("------------\n"); - if (passed === total) { - log.pass( - `${colors.green(`${passed} tests passed`)} in ${colors.gray( - Number(hrtime[0] + hrtime[1] / 10 ** 9).toPrecision(5), - )}s`, - ); - } else { - log.fail( - `${colors.red(`${failed} failed`)}, ${colors.green( - `${passed} passed`, - )} in ${colors.gray( - Number(hrtime[0] + hrtime[1] / 10 ** 9).toPrecision(5), - )}s `, - ); - } - process.stdout.write("------------\n"); - }, -}; - -export async function getExecutionTimeSeconds( - fn: () => unknown | Promise, -) { - const t = process.hrtime(); - await fn(); - const t2 = process.hrtime(t); - return Number(t2[0] + t2[1] / 10 ** 9).toPrecision(4); -} - -export async function wait(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -export function replaceStringInFile( - path: string, - searchValue: string | RegExp, - replaceValue: string, -) { - fs.writeFileSync( - path, - fs.readFileSync(path, "utf-8").replace(searchValue, replaceValue), - "utf-8", - ); -} - -export async function getButton(page: Page) { - await page.waitForSelector("button"); - return await page.$("button"); -} - -export async function getButtonText( - button: ElementHandle | null, -) { - return await button?.evaluate((p) => p.innerText); -} - -async function removeNodeModules() { - await rimraf("node_modules"); - await rimraf("yarn.lock"); -} - -export async function installYarn() { - await removeNodeModules(); - execSync("yarn install", { stdio: "inherit" }); - - if (isLocalBuild()) { - execSync("npm install --no-save ../../../vite-express-*.tgz", { - stdio: "inherit", - }); - } -} - -export function isLocalBuild() { - return process.env.LOCAL_BUILD_TEST === "true"; -} diff --git a/tests/libs/utils.ts b/tests/libs/utils.ts new file mode 100644 index 0000000..81e89a0 --- /dev/null +++ b/tests/libs/utils.ts @@ -0,0 +1,250 @@ +import { ChildProcessWithoutNullStreams, spawn } from "child_process"; +import fs from "fs"; +import os from "os"; +import path from "path"; +import { expect } from "vitest"; + +export function cmd(command: string, options?: { verbose?: boolean }) { + return new CommandRunBuilder(command, options?.verbose); +} + +type Action = (process: ChildProcessWithoutNullStreams) => Promise; + +class CommandActionsQueue { + private index = 0; + private queue: Action[] = []; + + push(fn: (process: ChildProcessWithoutNullStreams) => Promise) { + this.queue.push(fn); + } + + async run(process: ChildProcessWithoutNullStreams, onFinish: () => void) { + const fn = this.queue[this.index++]; + if (fn) { + await fn(process); + await this.run(process, onFinish); + } else { + process.stdin?.end(); + onFinish(); + } + } +} + +type CommandAssertion = (proc: ChildProcessWithoutNullStreams) => void; + +class CommandStream { + private stream: string = ""; + + value() { + return this.stream; + } + + write(msg: string) { + this.stream += msg; + } +} + +class CommandRunBuilder { + private options = { cwd: process.cwd() }; + + private command: string; + private args: string[]; + + private stdout = new CommandStream(); + private stderr = new CommandStream(); + + private assertions: CommandAssertion[] = []; + private queue = new CommandActionsQueue(); + + constructor( + command: string, + private quiet?: boolean, + ) { + const [bin, ...args] = command.split(" "); + this.command = bin; + this.args = args; + } + + code(code: number) { + this.assertions.push((proc) => { + expect(proc.exitCode).toBe(code); + }); + + return this; + } + + cwd(path: string) { + this.options.cwd = path; + + return this; + } + + tmpdir() { + return this.cwd(fs.mkdtempSync(path.join(os.tmpdir(), "/"))); + } + + success() { + return this.code(0); + } + + error() { + this.assertions.push((proc) => { + expect(proc.exitCode).toBeGreaterThan(0); + }); + + return this; + } + + private streamEqual(stream: CommandStream, input: string) { + this.assertions.push(() => { + expect(stream.value()).toEqual(input); + }); + + return this; + } + + private streamMatches(stream: CommandStream, pattern: string | RegExp) { + this.assertions.push(() => { + expect(stream.value()).toMatch(pattern); + }); + + return this; + } + + stdoutEqual(input: string) { + return this.streamEqual(this.stdout, input); + } + + stdoutMatches(pattern: string | RegExp) { + return this.streamMatches(this.stdout, pattern); + } + + stderrEqual(input: string) { + return this.streamEqual(this.stderr, input); + } + + stderrMatches(pattern: string | RegExp) { + return this.streamMatches(this.stderr, pattern); + } + + awaitOutput(msg: string | RegExp | (string | RegExp)[]) { + this.queue.push(async (proc) => { + return new Promise((resolve) => { + proc.stdout?.on("data", (data: object) => { + if (Array.isArray(msg)) { + if (msg.every((pattern) => data.toString().match(pattern))) + resolve(); + } else { + if (data.toString().match(msg)) resolve(); + } + }); + }); + }); + + return this; + } + + wait(ms: number) { + this.queue.push(async () => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); + + return this; + } + + close() { + this.queue.push(async (proc) => { + return new Promise((resolve, reject) => { + if (proc.exitCode !== null) return resolve(); + + if (proc.pid) { + process.kill(-proc.pid); + resolve(); + } else { + reject("Cannot close child process: Missing PID"); + } + }); + }); + + return this; + } + + write(msg: string) { + this.queue.push(async (proc) => { + proc.stdin?.write(msg + "\n"); + }); + + return this; + } + + key(key: "enter" | "space" | "esc" | "left" | "up" | "right" | "down") { + const keyCode = () => { + switch (key) { + case "up": + return "\u001b[A"; + case "down": + return "\u001b[B"; + case "right": + return "\u001b[C"; + case "left": + return "\u001b[D"; + default: + throw new Error(`Unsupported key ${key}`); + } + }; + + this.queue.push(async (proc) => { + proc.stdin?.write(keyCode()); + }); + + return this; + } + + enter() { + return this.write(""); + } + + assert() { + return new Promise((resolve, reject) => { + const proc = spawn(this.command, this.args, { + ...this.options, + detached: true, + }); + + process.on("exit", () => { + if (proc.pid) process.kill(-proc.pid); + }); + + proc.stdout?.on("data", (data) => { + this.stdout.write(data.toString()); + if (!this.quiet) console.log(data.toString()); + }); + proc.stderr?.on("data", (err) => { + this.stderr.write(err.toString()); + if (!this.quiet) console.error(err.toString()); + }); + + const running = new Promise((resolve) => + proc.on("close", () => resolve()).on("exit", () => resolve()), + ); + + proc.on("spawn", () => { + this.queue.run(proc, () => { + running.then(() => { + this.assertions.forEach((assertion) => { + try { + assertion(proc); + } catch (err) { + reject(err); + } + }); + + resolve(); + }); + }); + }); + }); + } +} diff --git a/tests/server.test.ts b/tests/server.test.ts index ea0522a..db040da 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -1,367 +1,382 @@ import express from "express"; import http from "http"; +import { AddressInfo } from "net"; import path from "path"; -import SocketIO from "socket.io"; +import * as SocketIO from "socket.io"; import { io as SocketIOClient } from "socket.io-client"; import request from "supertest"; +import { beforeAll, describe, expect, test } from "vitest"; import ViteExpress from "../src/main"; -import { expect, it, run, test } from "./lib/runner"; - -const baseDir = process.cwd(); - -test("Express app", async (done) => { - process.chdir(path.join(__dirname, "env")); +describe("Basic app", () => { const app = express(); - app.get("/hello", (_, res) => { - res.send("Hello Vite Express!"); - }); + beforeAll(async () => { + process.chdir(path.join(__dirname, "envs/basic")); - const server = ViteExpress.listen(app, 3000, async () => { - let response = await request(app).get("/hello"); - expect(response.text).toBe("Hello Vite Express!"); + app.get("/hello", (req, res) => { + res.send("Hello Vite Express!"); + }); - response = await request(app).get("/api"); - expect(response.text).toBe("Response from API!"); + await new Promise((done) => { + ViteExpress.listen(app, 0, async () => done()); + }); + }); - it("get api routes work"); + test("get api routes work", async () => { + await request(app).get("/hello").expect(200, "Hello Vite Express!"); + }); - response = await request(app).get("/"); + test("html is served correctly", async () => { + let response = await request(app).get("/").expect(200); expect(response.text).toMatch(/

index<\/h1>/); - response = await request(app).get("/route"); + response = await request(app).get("/route").expect(200); expect(response.text).toMatch(/

index<\/h1>/); + }); - it("html is served correctly"); - - response = await request(app).get("/subpath/"); + test("subpath html is served correctly", async () => { + let response = await request(app).get("/subpath/"); expect(response.text).toMatch(/

subpath<\/h1>/); response = await request(app).get("/subpath/route"); expect(response.text).toMatch(/

subpath<\/h1>/); + }); - it("subpath html is served correctly"); - - response = await request(app).get("/some/path/route"); + test("fallback to closest index towards root", async () => { + let response = await request(app).get("/some/path/route"); expect(response.text).toMatch(/

index<\/h1>/); response = await request(app).get("/subpath/to/some/route"); expect(response.text).toMatch(/

subpath<\/h1>/); - - it("fallback to closest index toward root"); - - response = await request(app).get("/test.txt"); - expect(response.text).toBe("Hello from test.txt"); - - it("static files are served correctly"); - - server.close(() => { - process.chdir(baseDir); - done(); - }); }); - app.get("/api", (_, res) => { - res.send("Response from API!"); + test("static files are served correctly", async () => { + const response = await request(app).get("/test.txt"); + expect(response.text).toBe("Hello from test.txt"); }); }); -test("Express app with explicit static middleware", async (done) => { - process.chdir(path.join(__dirname, "env")); - +describe("Basic app with explicit static middleware", () => { const app = express(); - app.use((_, res, next) => { - res.header("before", "1"); - next(); - }); - app.use(ViteExpress.static()); - app.use((_, res, next) => { - res.header("after", "1"); - next(); - }); - - app.get("/hello", (_, res) => { - res.send("Hello Vite Express!"); - }); - - const server = ViteExpress.listen(app, 3000, async () => { - let response = await request(app).get("/hello"); - expect(response.text).toBe("Hello Vite Express!"); + beforeAll(async () => { + process.chdir(path.join(__dirname, "envs/basic")); - response = await request(app).get("/api"); - expect(response.text).toBe("Response from API!"); + app.use((_, res, next) => { + res.header("before", "1"); + next(); + }); + app.use(ViteExpress.static()); + app.use((_, res, next) => { + res.header("after", "1"); + next(); + }); + app.get("/hello", (req, res) => { + res.send("Hello Vite Express!"); + }); - it("get api routes work"); + await new Promise((done) => { + ViteExpress.listen(app, 0, async () => done()); + }); + }); - response = await request(app).get("/"); - expect(response.text).toMatch(//); - response = await request(app).get("/route"); - expect(response.text).toMatch(//); + test("get api routes work", async () => { + await request(app).get("/hello").expect(200, "Hello Vite Express!"); + }); - it("html is served correctly"); + test("html is served correctly", async () => { + let response = await request(app).get("/").expect(200); + expect(response.text).toMatch(/

index<\/h1>/); + response = await request(app).get("/route").expect(200); + expect(response.text).toMatch(/

index<\/h1>/); + }); - response = await request(app).get("/subpath/"); + test("subpath html is served correctly", async () => { + let response = await request(app).get("/subpath/"); expect(response.text).toMatch(/

subpath<\/h1>/); response = await request(app).get("/subpath/route"); expect(response.text).toMatch(/

subpath<\/h1>/); - it("subpath html is served correctly"); - expect(response.headers.before).toBe("1"); expect(response.headers.after).toBe("1"); + }); - response = await request(app).get("/some/path/route"); + test("fallback to closest index towards root", async () => { + let response = await request(app).get("/some/path/route"); expect(response.text).toMatch(/

index<\/h1>/); response = await request(app).get("/subpath/to/some/route"); expect(response.text).toMatch(/

subpath<\/h1>/); + }); - it("fallback to closest index toward root"); - - response = await request(app).get("/test.txt"); + test("static files are served correctly", async () => { + const response = await request(app).get("/test.txt"); expect(response.text).toBe("Hello from test.txt"); + }); - it("static files are served correctly"); - + test("static files middleware respects invocation order", async () => { + const response = await request(app).get("/test.txt"); expect(response.headers.before).toBe("1"); expect(response.headers.after).toBe(undefined); - - it("static files middleware respects invocation order"); - - server.close(() => { - process.chdir(baseDir); - done(); - }); - }); - - app.get("/api", (_, res) => { - res.send("Response from API!"); }); }); -test("Express app with custom http server", async (done) => { - process.chdir(path.join(__dirname, "env")); - +describe("App with custom http server", () => { const app = express(); - const server = http.createServer(app).listen(3000); - app.get("/hello", (_, res) => { - res.send("Hello Vite Express!"); - }); + beforeAll(async () => { + process.chdir(path.join(__dirname, "envs/basic")); - ViteExpress.bind(app, server, async () => { - let response = await request(app).get("/hello"); - expect(response.text).toBe("Hello Vite Express!"); + const server = http.createServer(app).listen(0); - response = await request(app).get("/api"); - expect(response.text).toBe("Response from API!"); + app.get("/hello", (_, res) => { + res.send("Hello Vite Express!"); + }); - it("get api routes work"); + await new Promise((done) => { + ViteExpress.bind(app, server, async () => done()); + }); + }); - response = await request(app).get("/"); - expect(response.text).toMatch(//); - response = await request(app).get("/route"); - expect(response.text).toMatch(//); + test("get api routes work", async () => { + await request(app).get("/hello").expect(200, "Hello Vite Express!"); + }); - it("html is served correctly"); + test("html is served correctly", async () => { + let response = await request(app).get("/").expect(200); + expect(response.text).toMatch(/

index<\/h1>/); + response = await request(app).get("/route").expect(200); + expect(response.text).toMatch(/

index<\/h1>/); + }); - response = await request(app).get("/subpath/"); + test("subpath html is served correctly", async () => { + let response = await request(app).get("/subpath/"); expect(response.text).toMatch(/

subpath<\/h1>/); response = await request(app).get("/subpath/route"); expect(response.text).toMatch(/

subpath<\/h1>/); + }); - it("subpath html is served correctly"); - - response = await request(app).get("/some/path/route"); + test("fallback to closest index towards root", async () => { + let response = await request(app).get("/some/path/route"); expect(response.text).toMatch(/

index<\/h1>/); response = await request(app).get("/subpath/to/some/route"); expect(response.text).toMatch(/

subpath<\/h1>/); + }); - it("fallback to closest index toward root"); - - response = await request(app).get("/test.txt"); + test("static files are served correctly", async () => { + const response = await request(app).get("/test.txt"); expect(response.text).toBe("Hello from test.txt"); + }); +}); + +describe("App with socket.io", () => { + const app = express(); + const server = http.createServer(app).listen(0); - it("static files are served correctly"); + beforeAll(async () => { + process.chdir(path.join(__dirname, "envs/basic")); - server.close(() => { - process.chdir(baseDir); - done(); + const serverSocket = new SocketIO.Server(server); + + serverSocket.on("connection", (socket) => { + socket.on("message", () => { + socket.emit("response", "Hello from socket.io"); + }); + }); + + await new Promise((done) => { + ViteExpress.bind(app, server, async () => done()); }); }); - app.get("/api", (_, res) => { - res.send("Response from API!"); + test("emits and receives events", async () => { + const client = SocketIOClient( + `http://localhost:${(server.address() as AddressInfo).port}`, + ); + + const response = await new Promise((done) => { + client.on("connect", () => { + client.emit("message"); + client.on("response", (response) => { + done(response); + }); + }); + }); + + expect(response).toBe("Hello from socket.io"); }); }); -test("Express app with socket.io", async (done) => { - process.chdir(path.join(__dirname, "env")); - +describe("App with transformer function", () => { const app = express(); - const server = http.createServer(app).listen(3000); - const serverSocket = new SocketIO.Server(server); + beforeAll(async () => { + process.chdir(path.join(__dirname, "envs/basic")); + + ViteExpress.config({ + transformer: (html) => + html.replace("", ''), + }); - serverSocket.on("connection", (socket) => { - socket.on("message", () => { - socket.emit("response", "Hello from socket.io"); + await new Promise((done) => { + ViteExpress.listen(app, 0, async () => done()); }); }); - it("Creates socket.io server"); + test("html is transformed correctly", async () => { + let response = await request(app).get("/").expect(200); + expect(response.text).toMatch(/

index<\/h1>/); + response = await request(app).get("/route").expect(200); + expect(response.text).toMatch(/

index<\/h1>/); - ViteExpress.bind(app, server, async () => { - const client = SocketIOClient("http://localhost:3000"); + expect(response.text).toMatch(//); + }); - client.on("connect", () => { - client.emit("message"); - client.on("response", (response: unknown) => { - expect(response).toBe("Hello from socket.io"); + test("subpath html is transformed correctly", async () => { + let response = await request(app).get("/subpath/"); + expect(response.text).toMatch(/

subpath<\/h1>/); + response = await request(app).get("/subpath/route"); + expect(response.text).toMatch(/

subpath<\/h1>/); - it("Emits and receives events"); + expect(response.text).toMatch(//); + }); - serverSocket.close(); - server.close(() => { - process.chdir(baseDir); - done(); - }); - }); - }); + test("fallback to closest index towards root", async () => { + let response = await request(app).get("/some/path/route"); + expect(response.text).toMatch(/

index<\/h1>/); + response = await request(app).get("/subpath/to/some/route"); + expect(response.text).toMatch(/

subpath<\/h1>/); }); -}); -test("Express app with transformer function", async (done) => { - process.chdir(path.join(__dirname, "env")); + test("static files are served correctly", async () => { + const response = await request(app).get("/test.txt"); + expect(response.text).toBe("Hello from test.txt"); + }); +}); +describe("App with async transformer function", () => { const app = express(); - ViteExpress.config({ - transformer: (html) => html.replace("", ''), - }); + beforeAll(async () => { + process.chdir(path.join(__dirname, "envs/basic")); - const server = ViteExpress.listen(app, 3000, async () => { - let response = await request(app).get("/"); - expect(response.text).toMatch(//); - response = await request(app).get("/route"); - expect(response.text).toMatch(//); + ViteExpress.config({ + transformer: async (html) => + html.replace("", ''), + }); - it("html is served correctly"); + await new Promise((done) => { + ViteExpress.listen(app, 0, async () => done()); + }); + }); - expect(response.text).toMatch(//); + test("html is transformed correctly", async () => { + let response = await request(app).get("/").expect(200); + expect(response.text).toMatch(/

index<\/h1>/); + response = await request(app).get("/route").expect(200); + expect(response.text).toMatch(/

index<\/h1>/); - it("html is transformed correctly"); + expect(response.text).toMatch(//); + }); - response = await request(app).get("/subpath/"); + test("subpath html is transformed correctly", async () => { + let response = await request(app).get("/subpath/"); expect(response.text).toMatch(/

subpath<\/h1>/); response = await request(app).get("/subpath/route"); expect(response.text).toMatch(/

subpath<\/h1>/); - it("subpath html is served correctly"); - expect(response.text).toMatch(//); + }); - it("subpath html is transformed correctly"); - - response = await request(app).get("/some/path/route"); + test("fallback to closest index towards root", async () => { + let response = await request(app).get("/some/path/route"); expect(response.text).toMatch(/

index<\/h1>/); response = await request(app).get("/subpath/to/some/route"); expect(response.text).toMatch(/

subpath<\/h1>/); + }); - it("fallback to closest index toward root"); - - response = await request(app).get("/test.txt"); + test("static files are served correctly", async () => { + const response = await request(app).get("/test.txt"); expect(response.text).toBe("Hello from test.txt"); - - it("static files are served correctly"); - - server.close(() => { - process.chdir(baseDir); - done(); - }); }); }); -test("Express app with ignored paths", async (done) => { - process.chdir(path.join(__dirname, "env")); - +describe("App with ignored paths", async () => { const app = express(); - ViteExpress.config({ ignorePaths: /ignored/ }); + beforeAll(async () => { + process.chdir(path.join(__dirname, "envs/basic")); - const server = ViteExpress.listen(app, 3000, async () => { - let response = await request(app).get("/"); - expect(response.text).toMatch(//); - response = await request(app).get("/route"); - expect(response.text).toMatch(//); + ViteExpress.config({ + ignorePaths: /ignored/, + }); - it("html is served correctly"); + await new Promise((done) => { + ViteExpress.listen(app, 0, async () => done()); + }); + }); - response = await request(app).get("/subpath/"); + test("html is served correctly", async () => { + let response = await request(app).get("/").expect(200); + expect(response.text).toMatch(/

index<\/h1>/); + response = await request(app).get("/route").expect(200); + expect(response.text).toMatch(/

index<\/h1>/); + }); + + test("subpath html is served correctly", async () => { + let response = await request(app).get("/subpath/"); expect(response.text).toMatch(/

subpath<\/h1>/); response = await request(app).get("/subpath/route"); expect(response.text).toMatch(/

subpath<\/h1>/); + }); - it("subpath html is served correctly"); - - response = await request(app).get("/some/path/route"); + test("fallback to closest index towards root", async () => { + let response = await request(app).get("/some/path/route"); expect(response.text).toMatch(/

index<\/h1>/); response = await request(app).get("/subpath/to/some/route"); expect(response.text).toMatch(/

subpath<\/h1>/); + }); - it("fallback to closest index toward root"); - - response = await request(app).get("/ignored"); + test("regex ignore is respected", async () => { + const response = await request(app).get("/ignored"); expect(response.text).toMatch(/Cannot GET \/ignored/); + }); - it("regex ignore is respected"); - + test("function ignore is respected", async () => { ViteExpress.config({ ignorePaths: (path) => path === "/fnignored" }); - response = await request(app).get("/fnignored"); + let response = await request(app).get("/fnignored"); expect(response.text).toMatch(/Cannot GET \/fnignored/); response = await request(app).get("/ignored"); expect(response.text).toMatch(/index/); + }); - it("fn ignore is respected"); - - response = await request(app).get("/test.txt"); + test("static files are served correctly", async () => { + const response = await request(app).get("/test.txt"); expect(response.text).toBe("Hello from test.txt"); - - it("static files are served correctly"); - - server.close(() => { - process.chdir(baseDir); - done(); - }); }); }); -test("Express with no index at root", async (done) => { +describe("App with no index at root", async () => { const app = express(); - process.chdir(path.join(__dirname, "noIndexEnv")); + beforeAll(async () => { + process.chdir(path.join(__dirname, "envs/indexless")); + + await new Promise((done) => { + ViteExpress.listen(app, 0, async () => done()); + }); + }); - const server = ViteExpress.listen(app, 3000, async () => { - // default middleware fallback will respond with 404 + test("fallback with next middleware if no index found", async () => { let response = await request(app).get("/"); expect(response.status).toBe(404); response = await request(app).get("/route"); expect(response.status).toBe(404); response = await request(app).get("/some/path/route"); expect(response.status).toBe(404); + }); - it("fallback with next middleware if no index found"); - - response = await request(app).get("/subpath/route"); + test("html is served correctly", async () => { + const response = await request(app).get("/subpath/route"); expect(response.text).toMatch(/

subpath<\/h1>/); - - it("html is served correctly"); - - server.close(() => { - process.chdir(baseDir); - done(); - }); }); }); - -run(); diff --git a/tests/templates.test.ts b/tests/templates.test.ts index 6f89b1b..dfe0836 100644 --- a/tests/templates.test.ts +++ b/tests/templates.test.ts @@ -1,122 +1,90 @@ -import express from "express"; import fs from "fs"; -import puppeteer from "puppeteer"; - -import ViteExpress from "../src/main"; -import { expect, it, run, test } from "./lib/runner"; -import { - getButton, - getButtonText, - installYarn, - replaceStringInFile, - wait, -} from "./lib/utils"; - -const baseDir = process.cwd(); -const templates = fs.readdirSync("create-vite-express/templates"); - -const templatesHotReloadTestFileMap: Record = { - react: "./src/client/App.jsx", - "react-ts": "./src/client/App.tsx", - vue: "./src/client/components/HelloWorld.vue", - "vue-ts": "./src/client/components/HelloWorld.vue", -}; - -for (const template of templates) { - test(`Template "${template}"`, async (done) => { - process.chdir(`create-vite-express/templates/${template}`); - await installYarn(); - - ViteExpress.config({ inlineViteConfig: undefined }); - // BUG: There is a problem with Vue Hot Reload when app is build - // await ViteExpress.build(); - - await testCase(template, done); +import os from "os"; +import path from "path"; +import { describe, test } from "vitest"; + +import { TEMPLATES } from "../create-vite-express/src/templates"; +import { cmd } from "./libs/utils"; + +// const TEMPLATES_HMR_FILE_MAP = { +// react: "./src/client/App.jsx", +// "react-ts": "./src/client/App.tsx", +// vue: "./src/client/components/HelloWorld.vue", +// "vue-ts": "./src/client/components/HelloWorld.vue", +// }; + +const cliPath = path.resolve(__dirname, "../create-vite-express/src/cli.ts"); + +TEMPLATES.forEach((template, i) => { + describe(`Template "${template.value}"`, () => { + const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), "/")); + testCase({ ts: false, templateIndex: i, tmpdir }); }); - test(`Template "${template}" with default inline config`, async (done) => { - process.chdir(`create-vite-express/templates/${template}`); - - ViteExpress.config({ inlineViteConfig: {} }); - await ViteExpress.build(); - - await testCase(template, done); + describe(`Template "${template.value}-ts"`, () => { + const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), "/")); + testCase({ ts: true, templateIndex: i, tmpdir }); }); - - test(`Template "${template}" with custom inline config`, async (done) => { - process.chdir(`create-vite-express/templates/${template}`); - - const base = "/admin"; - - ViteExpress.config({ - inlineViteConfig: { base, build: { outDir: "out" } }, - }); - await ViteExpress.build(); - - await testCase(template, done, base); +}); + +const testCase = async ({ + ts, + templateIndex, + tmpdir, +}: { + ts: boolean; + templateIndex: number; + tmpdir: string; +}) => { + console.log(tmpdir); + + test("install template", async () => { + return cmd(`tsx ${cliPath}`) + .cwd(tmpdir) + .write("test") + .awaitOutput("Select a framework") + .write("\u001b[B".repeat(templateIndex)) + .awaitOutput("Do you use TypeScript?") + .write(ts ? "y" : "n") + .awaitOutput("Happy hacking!") + .success() + .assert(); }); -} - -const testCase = async (template: string, done: () => void, base = "/") => { - const server = ViteExpress.listen(express(), 3000, () => { - const browser = puppeteer.launch({ headless: "new" }); - - browser.then(async (browser) => { - const page = await browser.newPage(); - await page.goto(`http://localhost:3000${base}`); - it("test set up"); - - replaceStringInFile( - "./index.html", - /(.+)<\/title>/, - "<title>Test - $1", - ); - - await wait(200); - - let button = await getButton(page); - await button?.click(); - expect(await getButtonText(button)).toBe("count is 1"); - - replaceStringInFile( - "./index.html", - /Test - (.+)<\/title>/, - "<title>$1", - ); - - await wait(200); - - button = await getButton(page); - expect(await getButtonText(button)).toBe("count is 0"); - - it("automatic page reload works"); - - if (templatesHotReloadTestFileMap[template]) { - const filePath = templatesHotReloadTestFileMap[template]; - - await button?.click(); - await button?.click(); - - replaceStringInFile(filePath, "count is", "button count is"); - await wait(200); - expect(await getButtonText(button)).toBe("button count is 2"); + test("rewrite vite-express to local dependency", async () => { + return cmd(`npm install ${path.resolve(__dirname, "..")}`) + .cwd(path.join(tmpdir, "test")) + .success() + .assert(); + }); - replaceStringInFile(filePath, "button count is", "count is"); - await wait(200); - expect(await getButtonText(button)).toBe("count is 2"); + test("install dependencies", async () => { + return cmd("npm install").cwd(path.join(tmpdir, "test")).success().assert(); + }); - it("hot reload works"); - } + test("run app in development mode", async () => { + return cmd("npm run dev") + .cwd(path.join(tmpdir, "test")) + .awaitOutput(["Running in", "development"]) + .awaitOutput("Server is listening on port 3000...") + .close() + .assert(); + }); - await browser.close(); + test("build app", async () => { + return cmd("npm run build") + .cwd(path.join(tmpdir, "test")) + .success() + .stdoutMatches("built in") + .assert(); + }); - server.close(() => { - process.chdir(baseDir); - done(); - }); - }); + test("run app in production mode", async () => { + return cmd("npm run start") + .cwd(path.join(tmpdir, "test")) + .awaitOutput(["Running in", "production"]) + .awaitOutput("Server is listening on port 3000...") + .close() + .assert(); }); }; - -run(); diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..939970d --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + testTimeout: 60000, + }, +}); diff --git a/yarn.lock b/yarn.lock index 8d9698f..da4e5ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -503,6 +503,13 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": version "0.3.2" resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" @@ -535,6 +542,11 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/sourcemap-codec@^1.4.15": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": version "0.3.17" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz" @@ -668,6 +680,11 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.5.2.tgz#7e59216d929a6b444304000be40c32d2d127fe4f" integrity sha512-pL0RXRHuuGLhvs7ayX/SAHph1hrDPXOM5anyYUQXWJEENxw3nfHkzv8FfVlEVcLyKPAEgDRkd6RKZq2SMqS/yg== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@socket.io/component-emitter@~3.1.0": version "3.1.0" resolved "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz" @@ -971,6 +988,49 @@ "@types/babel__core" "^7.20.4" react-refresh "^0.14.0" +"@vitest/expect@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.1.1.tgz#6b00a5e9ecccdc9da112e89214693a857564e39c" + integrity sha512-Qpw01C2Hyb3085jBkOJLQ7HRX0Ncnh2qV4p+xWmmhcIUlMykUF69zsnZ1vPmAjZpomw9+5tWEGOQ0GTfR8U+kA== + dependencies: + "@vitest/spy" "1.1.1" + "@vitest/utils" "1.1.1" + chai "^4.3.10" + +"@vitest/runner@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.1.1.tgz#c2c2a6baa25f3964c3434e94628b324bc0f19587" + integrity sha512-8HokyJo1SnSi3uPFKfWm/Oq1qDwLC4QDcVsqpXIXwsRPAg3gIDh8EbZ1ri8cmQkBxdOu62aOF9B4xcqJhvt4xQ== + dependencies: + "@vitest/utils" "1.1.1" + p-limit "^5.0.0" + pathe "^1.1.1" + +"@vitest/snapshot@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.1.1.tgz#40261901102e131cb09f23034884ad2c1c5af317" + integrity sha512-WnMHjv4VdHLbFGgCdVVvyRkRPnOKN75JJg+LLTdr6ah7YnL75W+7CTIMdzPEPzaDxA8r5yvSVlc1d8lH3yE28w== + dependencies: + magic-string "^0.30.5" + pathe "^1.1.1" + pretty-format "^29.7.0" + +"@vitest/spy@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.1.1.tgz#49a9c3f9b86f07b86333fc14d1667691b9a77a5c" + integrity sha512-hDU2KkOTfFp4WFFPWwHFauddwcKuGQ7gF6Un/ZZkCogoAiTMN7/7YKvUDbywPZZ754iCQGjdUmXN3t4k0jm1IQ== + dependencies: + tinyspy "^2.2.0" + +"@vitest/utils@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.1.1.tgz#493d1963d917a3ac29fbd4c36c1c31cfd17a7b41" + integrity sha512-E9LedH093vST/JuBSyHLFMpxJKW3dLhe/flUSPFedoyj4wKiFX7Jm8gYLtOIiin59dgrssfmFv0BJ1u8P/LC/A== + dependencies: + diff-sequences "^29.6.3" + loupe "^2.3.7" + pretty-format "^29.7.0" + abbrev@1: version "1.1.1" resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" @@ -989,6 +1049,16 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^8.3.0: + version "8.3.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.1.tgz#2f10f5b69329d90ae18c58bf1fa8fccd8b959a43" + integrity sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw== + +acorn@^8.10.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + acorn@^8.8.2, acorn@^8.9.0: version "8.11.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" @@ -1042,6 +1112,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + ansi-styles@^6.0.0, ansi-styles@^6.1.0: version "6.2.1" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz" @@ -1075,6 +1150,11 @@ asap@^2.0.0: resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + ast-types@^0.13.4: version "0.13.4" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" @@ -1190,6 +1270,11 @@ bytes@3.1.2: resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + call-bind@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" @@ -1208,6 +1293,19 @@ caniuse-lite@^1.0.30001541: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001564.tgz#eaa8bbc58c0cbccdcb7b41186df39dd2ba591889" integrity sha512-DqAOf+rhof+6GVx1y+xzbFPeOumfQnhYzVnZD6LAXijR77yPtm9mfOcqOnT3mpnJiZVT+kwLAFnRlZcIz+c6bg== +chai@^4.3.10: + version "4.3.10" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384" + integrity sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" + pathval "^1.1.1" + type-detect "^4.0.8" + chalk@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" @@ -1230,6 +1328,13 @@ chalk@^4.0.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +check-error@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" + integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== + dependencies: + get-func-name "^2.0.2" + chokidar@^3.5.2: version "3.5.3" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" @@ -1452,6 +1557,13 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +deep-eql@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" + integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== + dependencies: + type-detect "^4.0.0" + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" @@ -1494,6 +1606,11 @@ dezalgo@^1.0.4: asap "^2.0.0" wrappy "1" +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" @@ -1823,7 +1940,7 @@ eventemitter3@^5.0.1: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== -execa@8.0.1: +execa@8.0.1, execa@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== @@ -2055,6 +2172,11 @@ get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-func-name@^2.0.1, get-func-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + get-intrinsic@^1.0.2: version "1.2.0" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz" @@ -2403,6 +2525,11 @@ json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -2456,6 +2583,14 @@ listr2@7.0.2: rfdc "^1.3.0" wrap-ansi "^8.1.0" +local-pkg@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.5.0.tgz#093d25a346bae59a99f80e75f6e9d36d7e8c925c" + integrity sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg== + dependencies: + mlly "^1.4.2" + pkg-types "^1.0.3" + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" @@ -2484,6 +2619,13 @@ log-update@^5.0.1: strip-ansi "^7.0.1" wrap-ansi "^8.0.1" +loupe@^2.3.6, loupe@^2.3.7: + version "2.3.7" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== + dependencies: + get-func-name "^2.0.1" + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" @@ -2508,6 +2650,13 @@ lru-cache@^7.14.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484" integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== +magic-string@^0.30.5: + version "0.30.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9" + integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" @@ -2602,6 +2751,16 @@ mkdirp-classic@^0.5.2: resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== +mlly@^1.2.0, mlly@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.2.tgz#7cf406aa319ff6563d25da6b36610a93f2a8007e" + integrity sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg== + dependencies: + acorn "^8.10.0" + pathe "^1.1.1" + pkg-types "^1.0.3" + ufo "^1.3.0" + ms@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" @@ -2617,7 +2776,7 @@ ms@2.1.3: resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@^3.3.6: +nanoid@^3.3.6, nanoid@^3.3.7: version "3.3.7" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== @@ -2741,6 +2900,13 @@ p-limit@^3.0.2: dependencies: yocto-queue "^0.1.0" +p-limit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-5.0.0.tgz#6946d5b7140b649b7a33a027d89b4c625b3a5985" + integrity sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ== + dependencies: + yocto-queue "^1.0.0" + p-locate@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" @@ -2836,6 +3002,16 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathe@^1.1.0, pathe@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a" + integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q== + +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== + pend@~1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" @@ -2856,6 +3032,15 @@ pidtree@0.6.0: resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== +pkg-types@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868" + integrity sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A== + dependencies: + jsonc-parser "^3.2.0" + mlly "^1.2.0" + pathe "^1.1.0" + postcss@^8.4.31: version "8.4.31" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" @@ -2865,6 +3050,15 @@ postcss@^8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.4.32: + version "8.4.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9" + integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" @@ -2875,6 +3069,15 @@ prettier@^3.1.0: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.0.tgz#c6d16474a5f764ea1a4a373c593b779697744d5e" integrity sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw== +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + progress@2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" @@ -2985,6 +3188,11 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + react-refresh@^0.14.0: version "0.14.0" resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz" @@ -3180,6 +3388,11 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + signal-exit@^3.0.2: version "3.0.7" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" @@ -3328,11 +3541,21 @@ spawn-command@0.0.2: resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e" integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ== +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + statuses@2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +std-env@^3.5.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" + integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== + streamx@^2.15.0: version "2.15.5" resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.5.tgz#87bcef4dc7f0b883f9359671203344a4e004c7f1" @@ -3404,6 +3627,13 @@ strip-json-comments@^3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-literal@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-1.3.0.tgz#db3942c2ec1699e6836ad230090b84bb458e3a07" + integrity sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg== + dependencies: + acorn "^8.10.0" + superagent@^8.0.5: version "8.0.9" resolved "https://registry.npmjs.org/superagent/-/superagent-8.0.9.tgz" @@ -3492,6 +3722,21 @@ through@^2.3.8: resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +tinybench@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.5.1.tgz#3408f6552125e53a5a48adee31261686fd71587e" + integrity sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg== + +tinypool@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.1.tgz#b6c4e4972ede3e3e5cda74a3da1679303d386b03" + integrity sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg== + +tinyspy@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.0.tgz#9dc04b072746520b432f77ea2c2d17933de5d6ce" + integrity sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" @@ -3558,6 +3803,11 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-detect@^4.0.0, type-detect@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" @@ -3581,6 +3831,11 @@ typescript@^5.3.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== +ufo@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.2.tgz#c7d719d0628a1c80c006d2240e0d169f6e3c0496" + integrity sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA== + unbzip2-stream@1.4.3: version "1.4.3" resolved "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz" @@ -3639,6 +3894,28 @@ vary@^1, vary@~1.1.2: resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +vite-node@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.1.1.tgz#8cf16d5f841898de919653462c56dc99bb7d2b94" + integrity sha512-2bGE5w4jvym5v8llF6Gu1oBrmImoNSs4WmRVcavnG2me6+8UQntTqLiAMFyiAobp+ZXhj5ZFhI7SmLiFr/jrow== + dependencies: + cac "^6.7.14" + debug "^4.3.4" + pathe "^1.1.1" + picocolors "^1.0.0" + vite "^5.0.0" + +vite@^5.0.0: + version "5.0.10" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.10.tgz#1e13ef5c3cf5aa4eed81f5df6d107b3c3f1f6356" + integrity sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw== + dependencies: + esbuild "^0.19.3" + postcss "^8.4.32" + rollup "^4.2.0" + optionalDependencies: + fsevents "~2.3.3" + vite@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.2.tgz#3c94627dace83b9bf04b64eaf618038e30fb95c0" @@ -3650,6 +3927,33 @@ vite@^5.0.2: optionalDependencies: fsevents "~2.3.3" +vitest@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.1.1.tgz#8ebd1a3cdca05da6e589b7d1f504ae952fecbeef" + integrity sha512-Ry2qs4UOu/KjpXVfOCfQkTnwSXYGrqTbBZxw6reIYEFjSy1QUARRg5pxiI5BEXy+kBVntxUYNMlq4Co+2vD3fQ== + dependencies: + "@vitest/expect" "1.1.1" + "@vitest/runner" "1.1.1" + "@vitest/snapshot" "1.1.1" + "@vitest/spy" "1.1.1" + "@vitest/utils" "1.1.1" + acorn-walk "^8.3.0" + cac "^6.7.14" + chai "^4.3.10" + debug "^4.3.4" + execa "^8.0.1" + local-pkg "^0.5.0" + magic-string "^0.30.5" + pathe "^1.1.1" + picocolors "^1.0.0" + std-env "^3.5.0" + strip-literal "^1.3.0" + tinybench "^2.5.1" + tinypool "^0.8.1" + vite "^5.0.0" + vite-node "1.1.1" + why-is-node-running "^2.2.2" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" @@ -3670,6 +3974,14 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +why-is-node-running@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.2.2.tgz#4185b2b4699117819e7154594271e7e344c9973e" + integrity sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -3767,3 +4079,8 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yocto-queue@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" + integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==