diff --git a/.github/workflows/continuous_benchmark.yml b/.github/workflows/continuous_benchmark.yml new file mode 100644 index 0000000000..42aaf7fc69 --- /dev/null +++ b/.github/workflows/continuous_benchmark.yml @@ -0,0 +1,50 @@ +name: Continuous benchmark + +on: + workflow_dispatch: + pull_request: + branches: + - main + push: + branches: + - main + +env: + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + FORCE_COLOR: true + CODSPEED_TOKEN: ${{ secrets.CODSPEED_TOKEN }} + CODSPEED: true + +jobs: + codspeed: + if: ${{ github.repository_owner == 'withastro' }} + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Setup PNPM + uses: pnpm/action-setup@v3 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Build + run: pnpm run build + + - name: Run the benchmarks + uses: CodSpeedHQ/action@b587655f756aab640e742fec141261bc6f0a569d # v3.0.1 + timeout-minutes: 30 + with: + run: pnpm benchmark codspeed + diff --git a/benchmark/bench/_util.js b/benchmark/bench/_util.js index 23c4726046..b16a16e1ce 100644 --- a/benchmark/bench/_util.js +++ b/benchmark/bench/_util.js @@ -14,7 +14,7 @@ export const astroBin = path.resolve(astroPkgPath, '../astro.js'); export function calculateStat(numbers) { const avg = numbers.reduce((a, b) => a + b, 0) / numbers.length; const stdev = Math.sqrt( - numbers.map((x) => Math.pow(x - avg, 2)).reduce((a, b) => a + b, 0) / numbers.length + numbers.map((x) => Math.pow(x - avg, 2)).reduce((a, b) => a + b, 0) / numbers.length, ); const max = Math.max(...numbers); return { avg, stdev, max }; diff --git a/benchmark/bench/cli-startup.js b/benchmark/bench/cli-startup.js index 2e9eccf645..9144797d75 100644 --- a/benchmark/bench/cli-startup.js +++ b/benchmark/bench/cli-startup.js @@ -1,6 +1,6 @@ import { fileURLToPath } from 'node:url'; -import { exec } from 'tinyexec'; import { markdownTable } from 'markdown-table'; +import { exec } from 'tinyexec'; import { astroBin, calculateStat } from './_util.js'; /** Default project to run for this benchmark if not specified */ @@ -8,9 +8,8 @@ export const defaultProject = 'render-default'; /** * @param {URL} projectDir - * @param {URL} outputFile */ -export async function run(projectDir, outputFile) { +export async function run(projectDir) { const root = fileURLToPath(projectDir); console.log('Benchmarking `astro --help`...'); @@ -28,7 +27,7 @@ export async function run(projectDir, outputFile) { printResult({ 'astro --help': helpStat, 'astro info': infoStat, - }) + }), ); console.log('='.repeat(10)); } @@ -69,6 +68,6 @@ function printResult(result) { ], { align: ['l', 'r', 'r', 'r'], - } + }, ); } diff --git a/benchmark/bench/codspeed.js b/benchmark/bench/codspeed.js new file mode 100644 index 0000000000..2ad783e8aa --- /dev/null +++ b/benchmark/bench/codspeed.js @@ -0,0 +1,50 @@ +import path from 'node:path'; +import { withCodSpeed } from '@codspeed/tinybench-plugin'; +import { Bench } from 'tinybench'; +import { exec } from 'tinyexec'; +import { renderPages } from '../make-project/render-default.js'; +import { astroBin } from './_util.js'; + +export async function run({ memory: _memory, render, stress: _stress }) { + const options = { + iterations: 10, + }; + const bench = process.env.CODSPEED ? withCodSpeed(new Bench(options)) : new Bench(options); + let app; + bench.add( + 'Rendering', + async () => { + console.info('Start task.'); + const result = {}; + for (const fileName of renderPages) { + const pathname = '/' + fileName.slice(0, -path.extname(fileName).length); + const request = new Request(new URL(pathname, 'http://exmpale.com')); + const response = await app.render(request); + const html = await response.text(); + if (!result[pathname]) result[pathname] = []; + result[pathname].push(html); + } + console.info('Finish task.'); + return result; + }, + { + async beforeAll() { + // build for rendering + await exec(astroBin, ['build'], { + nodeOptions: { + cwd: render.root, + stdio: 'inherit', + }, + }); + + const entry = new URL('./dist/server/entry.mjs', `file://${render.root}`); + const { manifest, createApp } = await import(entry); + app = createApp(manifest); + app.manifest = manifest; + }, + }, + ); + + await bench.run(); + console.table(bench.table()); +} diff --git a/benchmark/bench/memory.js b/benchmark/bench/memory.js index 9a3f94bc73..5c746870e7 100644 --- a/benchmark/bench/memory.js +++ b/benchmark/bench/memory.js @@ -56,6 +56,6 @@ function printResult(output) { ], { align: ['l', 'r', 'r', 'r'], - } + }, ); } diff --git a/benchmark/bench/render.js b/benchmark/bench/render.js index 8dfd47fbbe..02f75a73b2 100644 --- a/benchmark/bench/render.js +++ b/benchmark/bench/render.js @@ -1,12 +1,12 @@ -import { exec } from 'tinyexec'; -import { markdownTable } from 'markdown-table'; import fs from 'node:fs/promises'; import http from 'node:http'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import { markdownTable } from 'markdown-table'; import { waitUntilBusy } from 'port-authority'; -import { calculateStat, astroBin } from './_util.js'; +import { exec } from 'tinyexec'; import { renderPages } from '../make-project/render-default.js'; +import { astroBin, calculateStat } from './_util.js'; const port = 4322; @@ -60,14 +60,14 @@ export async function run(projectDir, outputFile) { console.log('Done!'); } -async function benchmarkRenderTime() { +export async function benchmarkRenderTime(portToListen = port) { /** @type {Record} */ const result = {}; for (const fileName of renderPages) { // Render each file 100 times and push to an array for (let i = 0; i < 100; i++) { const pathname = '/' + fileName.slice(0, -path.extname(fileName).length); - const renderTime = await fetchRenderTime(`http://localhost:${port}${pathname}`); + const renderTime = await fetchRenderTime(`http://localhost:${portToListen}${pathname}`); if (!result[pathname]) result[pathname] = []; result[pathname].push(renderTime); } @@ -97,7 +97,7 @@ function printResult(result) { ], { align: ['l', 'r', 'r', 'r'], - } + }, ); } diff --git a/benchmark/bench/server-stress.js b/benchmark/bench/server-stress.js index 9e93c4cd93..5bcaa69634 100644 --- a/benchmark/bench/server-stress.js +++ b/benchmark/bench/server-stress.js @@ -1,10 +1,10 @@ -import autocannon from 'autocannon'; -import { exec } from 'tinyexec'; -import { markdownTable } from 'markdown-table'; import fs from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; +import autocannon from 'autocannon'; +import { markdownTable } from 'markdown-table'; import { waitUntilBusy } from 'port-authority'; import pb from 'pretty-bytes'; +import { exec } from 'tinyexec'; import { astroBin } from './_util.js'; const port = 4321; @@ -28,9 +28,11 @@ export async function run(projectDir, outputFile) { }); console.log('Previewing...'); - const previewProcess = execaCommand(`${astroBin} preview --port ${port}`, { - cwd: root, - stdio: 'inherit', + const previewProcess = await exec(astroBin, ['preview', '--port', port], { + nodeOptions: { + cwd: root, + stdio: 'inherit', + }, }); console.log('Waiting for server ready...'); @@ -59,7 +61,7 @@ export async function run(projectDir, outputFile) { /** * @returns {Promise} */ -async function benchmarkCannon() { +export async function benchmarkCannon() { return new Promise((resolve, reject) => { const instance = autocannon( { @@ -76,7 +78,7 @@ async function benchmarkCannon() { instance.stop(); resolve(result); } - } + }, ); autocannon.track(instance, { renderResultsTable: false }); }); @@ -95,7 +97,7 @@ function printResult(output) { ], { align: ['l', 'r', 'r', 'r'], - } + }, ); const reqAndBytesTable = markdownTable( @@ -106,7 +108,7 @@ function printResult(output) { ], { align: ['l', 'r', 'r', 'r', 'r'], - } + }, ); return `${latencyTable}\n\n${reqAndBytesTable}`; diff --git a/benchmark/index.js b/benchmark/index.js index 1c38993b13..2f2846e1db 100755 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -1,7 +1,7 @@ import mri from 'mri'; import fs from 'node:fs/promises'; import path from 'node:path'; -import { pathToFileURL } from 'node:url'; +import {fileURLToPath, pathToFileURL} from 'node:url'; const args = mri(process.argv.slice(2)); @@ -14,6 +14,7 @@ Command memory Run build memory and speed test render Run rendering speed test server-stress Run server stress test + codspeed Run codspeed test cli-startup Run CLI startup speed test Options @@ -29,6 +30,7 @@ const benchmarks = { render: () => import('./bench/render.js'), 'server-stress': () => import('./bench/server-stress.js'), 'cli-startup': () => import('./bench/cli-startup.js'), + codspeed: () => import('./bench/codspeed.js') }; if (commandName && !(commandName in benchmarks)) { @@ -37,12 +39,26 @@ if (commandName && !(commandName in benchmarks)) { } if (commandName) { - // Run single benchmark - const bench = benchmarks[commandName]; - const benchMod = await bench(); - const projectDir = await makeProject(args.project || benchMod.defaultProject); - const outputFile = await getOutputFile(commandName); - await benchMod.run(projectDir, outputFile); + if (commandName === 'codspeed') { + const render = await makeProject('render-bench'); + const rootRender = fileURLToPath(render); + const bench = benchmarks[commandName]; + const benchMod = await bench(); + const payload = { + render: { + root: rootRender, + output: await getOutputFile('render') + }, + }; + await benchMod.run(payload); + } else { + // Run single benchmark + const bench = benchmarks[commandName]; + const benchMod = await bench(); + const projectDir = await makeProject(args.project || benchMod.defaultProject); + const outputFile = await getOutputFile(commandName); + await benchMod.run(projectDir, outputFile); + } } else { // Run all benchmarks for (const name in benchmarks) { @@ -54,7 +70,7 @@ if (commandName) { } } -async function makeProject(name) { +export async function makeProject(name) { console.log('Making project:', name); const projectDir = new URL(`./projects/${name}/`, import.meta.url); @@ -78,6 +94,5 @@ async function getOutputFile(benchmarkName) { // Prepare output file directory await fs.mkdir(new URL('./', file), { recursive: true }); - return file; } diff --git a/benchmark/make-project/render-bench.js b/benchmark/make-project/render-bench.js new file mode 100644 index 0000000000..9d10d9bf5f --- /dev/null +++ b/benchmark/make-project/render-bench.js @@ -0,0 +1,132 @@ +import fs from 'node:fs/promises'; +import { loremIpsumHtml, loremIpsumMd } from './_util.js'; + +// Map of files to be generated and tested for rendering. +// Ideally each content should be similar for comparison. +const renderFiles = { + 'components/ListItem.astro': `\ +--- +const { className, item, attrs } = Astro.props; +const nested = item !== 0; +--- +
  • + + {item} + +
  • + `, + 'components/Sublist.astro': `\ +--- +import ListItem from '../components/ListItem.astro'; +const { items } = Astro.props; +const className = "text-red-500"; +const style = { color: "red" }; +--- + + `, + 'pages/astro.astro': `\ +--- +const className = "text-red-500"; +const style = { color: "red" }; +const items = Array.from({ length: 10000 }, (_, i) => ({i})); +--- + + + My Site + + +

    List

    + + ${Array.from({ length: 1000 }) + .map(() => `

    ${loremIpsumHtml}

    `) + .join('\n')} + +`, + 'pages/md.md': `\ +# List + +${Array.from({ length: 1000 }, (_, i) => i) + .map((v) => `- ${v}`) + .join('\n')} + +${Array.from({ length: 1000 }) + .map(() => loremIpsumMd) + .join('\n\n')} +`, + 'pages/mdx.mdx': `\ +export const className = "text-red-500"; +export const style = { color: "red" }; +export const items = Array.from({ length: 1000 }, (_, i) => i); + +# List + + + +${Array.from({ length: 1000 }) + .map(() => loremIpsumMd) + .join('\n\n')} +`, +}; + +export const renderPages = []; +for (const file of Object.keys(renderFiles)) { + if (file.startsWith('pages/')) { + renderPages.push(file.replace('pages/', '')); + } +} + +/** + * @param {URL} projectDir + */ +export async function run(projectDir) { + await fs.rm(projectDir, { recursive: true, force: true }); + await fs.mkdir(new URL('./src/pages', projectDir), { recursive: true }); + await fs.mkdir(new URL('./src/components', projectDir), { recursive: true }); + + await Promise.all( + Object.entries(renderFiles).map(([name, content]) => { + return fs.writeFile(new URL(`./src/${name}`, projectDir), content, 'utf-8'); + }) + ); + + await fs.writeFile( + new URL('./astro.config.js', projectDir), + `\ +import { defineConfig } from 'astro/config'; +import adapter from '@benchmark/adapter'; +import mdx from '@astrojs/mdx'; + +export default defineConfig({ + integrations: [mdx()], + output: 'server', + adapter: adapter(), +});`, + 'utf-8' + ); +} diff --git a/benchmark/package.json b/benchmark/package.json index 2fe6ba7b9a..428afe56d2 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -10,6 +10,7 @@ "@astrojs/mdx": "workspace:*", "@astrojs/node": "^8.3.4", "@benchmark/timer": "workspace:*", + "@benchmark/adapter": "workspace:*", "astro": "workspace:*", "autocannon": "^7.15.0", "markdown-table": "^3.0.4", @@ -18,5 +19,9 @@ "pretty-bytes": "^6.1.1", "sharp": "^0.33.3", "tinyexec": "^0.3.1" + }, + "devDependencies": { + "@codspeed/tinybench-plugin": "^3.1.1", + "tinybench": "^2.9.0" } } diff --git a/benchmark/packages/adapter/README.md b/benchmark/packages/adapter/README.md new file mode 100644 index 0000000000..5b8e33ed4e --- /dev/null +++ b/benchmark/packages/adapter/README.md @@ -0,0 +1,3 @@ +# @benchmark/timer + +Like `@astrojs/node`, but returns the rendered time in milliseconds for the page instead of the page content itself. This is used for internal benchmarks only. diff --git a/benchmark/packages/adapter/package.json b/benchmark/packages/adapter/package.json new file mode 100644 index 0000000000..2bdb73ce9b --- /dev/null +++ b/benchmark/packages/adapter/package.json @@ -0,0 +1,35 @@ +{ + "name": "@benchmark/adapter", + "description": "Bench adapter", + "private": true, + "version": "0.0.0", + "type": "module", + "types": "./dist/index.d.ts", + "author": "withastro", + "license": "MIT", + "keywords": [ + "withastro", + "astro-adapter" + ], + "exports": { + ".": "./dist/index.js", + "./server.js": "./dist/server.js", + "./package.json": "./package.json" + }, + "scripts": { + "build": "astro-scripts build \"src/**/*.ts\" && tsc", + "build:ci": "astro-scripts build \"src/**/*.ts\"", + "dev": "astro-scripts dev \"src/**/*.ts\"" + }, + "dependencies": { + "server-destroy": "^1.0.1" + }, + "peerDependencies": { + "astro": "workspace:*" + }, + "devDependencies": { + "@types/server-destroy": "^1.0.4", + "astro": "workspace:*", + "astro-scripts": "workspace:*" + } +} diff --git a/benchmark/packages/adapter/src/index.ts b/benchmark/packages/adapter/src/index.ts new file mode 100644 index 0000000000..f2345deb08 --- /dev/null +++ b/benchmark/packages/adapter/src/index.ts @@ -0,0 +1,32 @@ +import type { AstroAdapter, AstroIntegration } from 'astro'; + +export default function createIntegration(): AstroIntegration { + return { + name: '@benchmark/timer', + hooks: { + 'astro:config:setup': ({ updateConfig }) => { + updateConfig({ + vite: { + ssr: { + noExternal: ['@benchmark/timer'], + }, + }, + }); + }, + 'astro:config:done': ({ setAdapter }) => { + setAdapter({ + name: '@benchmark/adapter', + serverEntrypoint: '@benchmark/adapter/server.js', + exports: ['manifest', 'createApp'], + supportedAstroFeatures: { + serverOutput: 'stable', + envGetSecret: 'experimental', + staticOutput: 'stable', + hybridOutput: 'stable', + i18nDomains: 'stable', + }, + }); + }, + }, + }; +} diff --git a/benchmark/packages/adapter/src/server.ts b/benchmark/packages/adapter/src/server.ts new file mode 100644 index 0000000000..ca69fe28f2 --- /dev/null +++ b/benchmark/packages/adapter/src/server.ts @@ -0,0 +1,34 @@ +import * as fs from 'node:fs'; +import type { SSRManifest } from 'astro'; +import { App } from 'astro/app'; +import { applyPolyfills } from 'astro/app/node'; + +applyPolyfills(); + +class MyApp extends App { + #manifest: SSRManifest | undefined; + #streaming: boolean; + constructor(manifest: SSRManifest, streaming = false) { + super(manifest, streaming); + this.#manifest = manifest; + this.#streaming = streaming; + } + + async render(request: Request) { + const url = new URL(request.url); + if (this.#manifest?.assets.has(url.pathname)) { + const filePath = new URL('../../client/' + this.removeBase(url.pathname), import.meta.url); + const data = await fs.promises.readFile(filePath); + return new Response(data); + } + + return super.render(request); + } +} + +export function createExports(manifest: SSRManifest) { + return { + manifest, + createApp: (streaming: boolean) => new MyApp(manifest, streaming), + }; +} diff --git a/benchmark/packages/adapter/tsconfig.json b/benchmark/packages/adapter/tsconfig.json new file mode 100644 index 0000000000..1504b4b6df --- /dev/null +++ b/benchmark/packages/adapter/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "outDir": "./dist" + } +} diff --git a/benchmark/packages/timer/src/index.ts b/benchmark/packages/timer/src/index.ts index 1c54e37276..f83a61c36c 100644 --- a/benchmark/packages/timer/src/index.ts +++ b/benchmark/packages/timer/src/index.ts @@ -6,7 +6,9 @@ export function getAdapter(): AstroAdapter { serverEntrypoint: '@benchmark/timer/server.js', previewEntrypoint: '@benchmark/timer/preview.js', exports: ['handler'], - supportedAstroFeatures: {}, + supportedAstroFeatures: { + serverOutput: 'stable', + }, }; } diff --git a/benchmark/packages/timer/src/server.ts b/benchmark/packages/timer/src/server.ts index 9905a627b7..edcfaa248f 100644 --- a/benchmark/packages/timer/src/server.ts +++ b/benchmark/packages/timer/src/server.ts @@ -13,6 +13,6 @@ export function createExports(manifest: SSRManifest) { const end = performance.now(); res.write(end - start + ''); res.end(); - }, + } }; } diff --git a/biome.jsonc b/biome.jsonc index 227f37a08d..1730745539 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -9,20 +9,16 @@ "**/_temp-fixtures/**", "**/vendor/**", "**/.vercel/**", + "benchmark/projects/", + "benchmark/results/", ], - "include": ["test/**", "e2e/**", "packages/**", "/scripts/**"], + "include": ["test/**", "e2e/**", "packages/**", "/scripts/**", "benchmark/bench"], }, "formatter": { "indentStyle": "tab", "indentWidth": 2, "lineWidth": 100, - "ignore": [ - "benchmark/projects/", - "benchmark/results/", - ".changeset", - "pnpm-lock.yaml", - "*.astro", - ], + "ignore": [".changeset", "pnpm-lock.yaml", "*.astro"], }, "organizeImports": { "enabled": true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4da3b6b3d2..b7db4c5b90 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,9 @@ importers: '@astrojs/node': specifier: ^8.3.4 version: 8.3.4(astro@packages+astro) + '@benchmark/adapter': + specifier: workspace:* + version: link:packages/adapter '@benchmark/timer': specifier: workspace:* version: link:packages/timer @@ -93,6 +96,29 @@ importers: tinyexec: specifier: ^0.3.1 version: 0.3.1 + devDependencies: + '@codspeed/tinybench-plugin': + specifier: ^3.1.1 + version: 3.1.1(tinybench@2.9.0) + tinybench: + specifier: ^2.9.0 + version: 2.9.0 + + benchmark/packages/adapter: + dependencies: + server-destroy: + specifier: ^1.0.1 + version: 1.0.1 + devDependencies: + '@types/server-destroy': + specifier: ^1.0.4 + version: 1.0.4 + astro: + specifier: workspace:* + version: link:../../../packages/astro + astro-scripts: + specifier: workspace:* + version: link:../../../scripts benchmark/packages/timer: dependencies: @@ -5935,6 +5961,14 @@ packages: bundledDependencies: - is-unicode-supported + '@codspeed/core@3.1.1': + resolution: {integrity: sha512-ONhERVDAtkm0nc+FYPivDozoMOlNUP2BWRBFDJYATGA18Iap5Kd2mZ1/Lwz54RB5+g+3YDOpsvotHa4hd3Q+7Q==} + + '@codspeed/tinybench-plugin@3.1.1': + resolution: {integrity: sha512-LVF4End0kDU9V7CzuwAcmngSPJNnpduPnr+csOKvcG++FsYwfUuBJ1rvLPtv6yTkvxpUmUEsj6VA7/AEIBGZVw==} + peerDependencies: + tinybench: ^2.3.0 + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -7399,6 +7433,9 @@ packages: peerDependencies: postcss: ^8.1.0 + axios@1.7.7: + resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -8212,6 +8249,10 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + find-up@6.3.0: + resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + find-yarn-workspace-root2@1.2.16: resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} @@ -8226,6 +8267,15 @@ packages: resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} engines: {node: '>=8'} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -8751,6 +8801,10 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lodash.chunk@4.2.0: resolution: {integrity: sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==} @@ -9174,6 +9228,10 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-gyp-build@4.8.2: + resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} + hasBin: true + node-html-parser@6.1.13: resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} @@ -9276,6 +9334,10 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-limit@6.1.0: resolution: {integrity: sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==} engines: {node: '>=18'} @@ -9288,6 +9350,10 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-map@2.1.0: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} @@ -9352,6 +9418,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -9687,6 +9757,9 @@ packages: property-information@6.5.0: resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} @@ -11501,6 +11574,23 @@ snapshots: picocolors: 1.1.0 sisteransi: 1.0.5 + '@codspeed/core@3.1.1': + dependencies: + axios: 1.7.7 + find-up: 6.3.0 + form-data: 4.0.0 + node-gyp-build: 4.8.2 + transitivePeerDependencies: + - debug + + '@codspeed/tinybench-plugin@3.1.1(tinybench@2.9.0)': + dependencies: + '@codspeed/core': 3.1.1 + stack-trace: 1.0.0-pre2 + tinybench: 2.9.0 + transitivePeerDependencies: + - debug + '@colors/colors@1.5.0': optional: true @@ -13033,6 +13123,14 @@ snapshots: postcss: 8.4.47 postcss-value-parser: 4.2.0 + axios@1.7.7: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axobject-query@4.1.0: {} babel-plugin-jsx-dom-expressions@0.38.5(@babel/core@7.26.0): @@ -13771,6 +13869,11 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + find-up@6.3.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + find-yarn-workspace-root2@1.2.16: dependencies: micromatch: 4.0.8 @@ -13785,6 +13888,8 @@ snapshots: flattie@1.1.1: {} + follow-redirects@1.15.9: {} + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.3 @@ -14418,6 +14523,10 @@ snapshots: dependencies: p-locate: 5.0.0 + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + lodash.chunk@4.2.0: {} lodash.clonedeep@4.5.0: {} @@ -15091,6 +15200,8 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 + node-gyp-build@4.8.2: {} + node-html-parser@6.1.13: dependencies: css-select: 5.1.0 @@ -15201,6 +15312,10 @@ snapshots: dependencies: yocto-queue: 0.1.0 + p-limit@4.0.0: + dependencies: + yocto-queue: 1.1.1 + p-limit@6.1.0: dependencies: yocto-queue: 1.1.1 @@ -15213,6 +15328,10 @@ snapshots: dependencies: p-limit: 3.1.0 + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + p-map@2.1.0: {} p-queue@8.0.1: @@ -15282,6 +15401,8 @@ snapshots: path-exists@4.0.0: {} + path-exists@5.0.0: {} + path-key@3.1.1: {} path-key@4.0.0: {} @@ -15631,6 +15752,8 @@ snapshots: property-information@6.5.0: {} + proxy-from-env@1.1.0: {} + pseudomap@1.0.2: {} psl@1.9.0: {}