Refactor benchmark script (#6376)

* Add timer setting

* Setup benchmark code

* Setup memory benchmark

* Add compare function

* Add result preview

* Setup results preview

* Simplify script for CI

* Update CI

* Cleanup

* Temp remove fork guard

* Fix stuff

* Fix again

* Fix quotes

* Fix multiline output

* Simplify title

* Fix memory numbers

* Remove astro bin dir

* Fix gc

* Add repo guards

* Fix wrong call

* Set max space size

* Remove guard

* Bump memory a bit

* Organize neatly

* Update readme

* Try large md

* Try no gc

* Revert markdown and gc changes

* Test sha

* Try ref

* Try 128mb

* Set 256

* Add guard

* Apply suggestions from code review

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Add docs comment

---------

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Bjorn Lu 2023-03-01 16:46:06 +08:00 committed by GitHub
parent 045262ee99
commit f4937949d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 719 additions and 37 deletions

View file

@ -38,5 +38,11 @@ module.exports = {
'no-console': ['error', { allow: ['warn', 'error', 'info', 'debug'] }],
},
},
{
files: ['benchmark/**/*.js'],
rules: {
'no-console': 'off',
},
},
],
};

View file

@ -16,19 +16,14 @@ jobs:
permissions:
contents: read
outputs:
PR-BENCH-16: ${{ steps.benchmark-pr.outputs.BENCH_RESULT16 }}
PR-BENCH-18: ${{ steps.benchmark-pr.outputs.BENCH_RESULT18 }}
MAIN-BENCH-16: ${{ steps.benchmark-main.outputs.BENCH_RESULT16 }}
MAIN-BENCH-18: ${{ steps.benchmark-main.outputs.BENCH_RESULT18 }}
strategy:
matrix:
node-version: [16, 18]
PR-BENCH: ${{ steps.benchmark-pr.outputs.BENCH_RESULT }}
MAIN-BENCH: ${{ steps.benchmark-main.outputs.BENCH_RESULT }}
steps:
# https://github.com/actions/checkout/issues/331#issuecomment-1438220926
- uses: actions/checkout@v3
with:
persist-credentials: false
ref: ${{github.event.pull_request.head.sha}}
repository: ${{github.event.pull_request.head.repo.full_name}}
ref: refs/pull/${{ github.event.issue.number }}/head
- name: Setup PNPM
uses: pnpm/action-setup@v2
@ -45,13 +40,22 @@ jobs:
- name: Build Packages
run: pnpm run build
- name: Get bench command
id: bench-command
run: |
benchcmd=$(echo "${{ github.event.comment.body }}" | grep '!bench' | awk -F ' ' '{print $2}')
echo "bench=$benchcmd" >> $GITHUB_OUTPUT
shell: bash
- name: Run benchmark
id: benchmark-pr
run: |
pnpm run --silent benchmark 2> ./bench-result.md
result=$(awk '/requests in/' ./bench-result.md)
echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result"
echo "$result"
result=$(pnpm run --silent benchmark ${{ steps.bench-command.outputs.bench }})
processed=$(node ./benchmark/ci-helper.js "$result")
echo "BENCH_RESULT<<BENCHEOF" >> $GITHUB_OUTPUT
echo "### PR Benchmark" >> $GITHUB_OUTPUT
echo "$processed" >> $GITHUB_OUTPUT
echo "BENCHEOF" >> $GITHUB_OUTPUT
shell: bash
# main benchmark
@ -70,10 +74,12 @@ jobs:
- name: Run benchmark
id: benchmark-main
run: |
pnpm run --silent benchmark 2> ./bench-result.md
result=$(awk '/requests in/' ./bench-result.md)
echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$result"
echo "$result"
result=$(pnpm run --silent benchmark ${{ steps.bench-command.outputs.bench }})
processed=$(node ./benchmark/ci-helper.js "$result")
echo "BENCH_RESULT<<BENCHEOF" >> $GITHUB_OUTPUT
echo "### Main Benchmark" >> $GITHUB_OUTPUT
echo "$processed" >> $GITHUB_OUTPUT
echo "BENCHEOF" >> $GITHUB_OUTPUT
shell: bash
output-benchmark:
@ -89,12 +95,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
pr_number: ${{ github.event.issue.number }}
message: |
**Node**: 16
**PR**: ${{ needs.benchmark.outputs.PR-BENCH-16 }}
**MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-16 }}
${{ needs.benchmark.outputs.PR-BENCH }}
---
**Node**: 18
**PR**: ${{ needs.benchmark.outputs.PR-BENCH-18 }}
**MAIN**: ${{ needs.benchmark.outputs.MAIN-BENCH-18 }}
${{ needs.benchmark.outputs.MAIN-BENCH }}

2
.gitignore vendored
View file

@ -6,6 +6,8 @@ dist/
_site/
scripts/smoke/*-main/
scripts/memory/project/src/pages/
benchmark/projects/
benchmark/results/
*.log
package-lock.json
.turbo/

5
benchmark/README.md Normal file
View file

@ -0,0 +1,5 @@
# benchmark
Astro's main benchmark suite. It exposes the `astro-benchmark` CLI command. Run `astro-benchmark --help` to see all available commands!
If you'd like to understand how the benchmark works, check out the other READMEs in the subfolders.

View file

@ -0,0 +1,7 @@
# bench
This `bench` folder contains different benchmarking files that you can run via `astro-benchmark <bench-file-name>`, e.g. `astro-benchmark memory`. Files that start with an underscore are not benchmarking files.
Benchmarking files will run against a project to measure its performance, and write the results down as JSON in the `results` folder. The `results` folder is gitignored and its result files can be safely deleted if you're not using them.
You can duplicate `_template.js` to start a new benchmark test. All shared utilities are kept in `_util.js`.

View file

@ -0,0 +1,12 @@
/** Default project to run for this benchmark if not specified */
export const defaultProject = 'project-name';
/**
* Run benchmark on `projectDir` and write results to `outputFile`.
* Use `console.log` to report the results too. Logs that start with 10 `=`
* and end with 10 `=` will be extracted by CI to display in the PR comment.
* Usually after the first 10 `=` you'll want to add a title like `#### Test`.
* @param {URL} projectDir
* @param {URL} outputFile
*/
export async function run(projectDir, outputFile) {}

3
benchmark/bench/_util.js Normal file
View file

@ -0,0 +1,3 @@
import { createRequire } from 'module';
export const astroBin = createRequire(import.meta.url).resolve('astro');

58
benchmark/bench/memory.js Normal file
View file

@ -0,0 +1,58 @@
import fs from 'fs/promises';
import { fileURLToPath } from 'url';
import { execaCommand } from 'execa';
import { markdownTable } from 'markdown-table';
import { astroBin } from './_util.js';
/** @typedef {Record<string, import('../../packages/astro/src/core/config/timer').Stat>} AstroTimerStat */
/** Default project to run for this benchmark if not specified */
export const defaultProject = 'memory-default';
/**
* @param {URL} projectDir
* @param {URL} outputFile
*/
export async function run(projectDir, outputFile) {
const root = fileURLToPath(projectDir);
const outputFilePath = fileURLToPath(outputFile);
console.log('Building and benchmarking...');
await execaCommand(`node --expose-gc --max_old_space_size=256 ${astroBin} build`, {
cwd: root,
stdio: 'inherit',
env: {
ASTRO_TIMER_PATH: outputFilePath,
},
});
console.log('Raw results written to', outputFilePath);
console.log('Result preview:');
console.log('='.repeat(10));
console.log(`#### Memory\n\n`);
console.log(printResult(JSON.parse(await fs.readFile(outputFilePath, 'utf-8'))));
console.log('='.repeat(10));
console.log('Done!');
}
/**
* @param {AstroTimerStat} output
*/
function printResult(output) {
return markdownTable(
[
['', 'Elapsed time (s)', 'Memory used (MB)', 'Final memory (MB)'],
...Object.entries(output).map(([name, stat]) => [
name,
(stat.elapsedTime / 1000).toFixed(2),
(stat.heapUsedChange / 1024 / 1024).toFixed(2),
(stat.heapUsedTotal / 1024 / 1024).toFixed(2),
]),
],
{
align: ['l', 'r', 'r', 'r'],
}
);
}

View file

@ -0,0 +1,85 @@
import fs from 'fs/promises';
import { fileURLToPath } from 'url';
import autocannon from 'autocannon';
import { execaCommand } from 'execa';
import { waitUntilBusy } from 'port-authority';
import { astroBin } from './_util.js';
const port = 4321;
export const defaultProject = 'server-stress-default';
/**
* @param {URL} projectDir
* @param {URL} outputFile
*/
export async function run(projectDir, outputFile) {
const root = fileURLToPath(projectDir);
console.log('Building...');
await execaCommand(`${astroBin} build`, {
cwd: root,
stdio: 'inherit',
});
console.log('Previewing...');
const previewProcess = execaCommand(`${astroBin} preview --port ${port}`, {
cwd: root,
stdio: 'inherit',
});
console.log('Waiting for server ready...');
await waitUntilBusy(port, { timeout: 5000 });
console.log('Running benchmark...');
const result = await benchmarkCannon();
console.log('Killing server...');
if (!previewProcess.kill('SIGTERM')) {
console.warn('Failed to kill server process id:', previewProcess.pid);
}
console.log('Writing results to', fileURLToPath(outputFile));
await fs.writeFile(outputFile, JSON.stringify(result, null, 2));
console.log('Result preview:');
console.log('='.repeat(10));
console.log(`#### Server stress\n\n`);
let text = autocannon.printResult(result);
// Truncate the logs in CI so that the generated comment from the `!bench` command
// is shortened. Also we only need this information when comparing runs.
// Full log example: https://github.com/mcollina/autocannon#command-line
if (process.env.CI) {
text = text.match(/^.*?requests in.*?read$/m)?.[0];
}
console.log(text);
console.log('='.repeat(10));
console.log('Done!');
}
/**
* @returns {Promise<import('autocannon').Result>}
*/
async function benchmarkCannon() {
return new Promise((resolve, reject) => {
const instance = autocannon(
{
url: `http://localhost:${port}`,
connections: 100,
duration: 30,
pipelining: 10,
},
(err, result) => {
if (err) {
reject(err);
} else {
// @ts-expect-error untyped but documented
instance.stop();
resolve(result);
}
}
);
autocannon.track(instance, { renderResultsTable: false });
});
}

13
benchmark/ci-helper.js Normal file
View file

@ -0,0 +1,13 @@
// This script helps extract the benchmark logs that are between the `==========` lines.
// They are a convention defined in the `./bench/_template.js` file, which are used to log
// out with the `!bench` command. See `/.github/workflows/benchmark.yml` to see how it's used.
const benchLogs = process.argv[2];
const resultRegex = /==========(.*?)==========/gs;
let processedLog = '';
let m;
while ((m = resultRegex.exec(benchLogs))) {
processedLog += m[1] + '\n';
}
console.log(processedLog);

79
benchmark/index.js Executable file
View file

@ -0,0 +1,79 @@
import fs from 'fs/promises';
import path from 'path';
import { pathToFileURL } from 'url';
import mri from 'mri';
const args = mri(process.argv.slice(2));
if (args.help || args.h) {
console.log(`\
astro-benchmark <command> [options]
Command
[empty] Run all benchmarks
memory Run build memory and speed test
server-stress Run server stress test
Options
--project <project-name> Project to use for benchmark, see benchmark/make-project/ for available names
--output <output-file> Output file to write results to
`);
process.exit(0);
}
const commandName = args._[0];
const benchmarks = {
memory: () => import('./bench/memory.js'),
'server-stress': () => import('./bench/server-stress.js'),
};
if (commandName && !(commandName in benchmarks)) {
console.error(`Invalid benchmark name: ${commandName}`);
process.exit(1);
}
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);
} else {
// Run all benchmarks
for (const name in benchmarks) {
const bench = benchmarks[name];
const benchMod = await bench();
const projectDir = await makeProject(args.project || benchMod.defaultProject);
const outputFile = await getOutputFile(name);
await benchMod.run(projectDir, outputFile);
}
}
async function makeProject(name) {
console.log('Making project:', name);
const projectDir = new URL(`./projects/${name}/`, import.meta.url);
const makeProjectMod = await import(`./make-project/${name}.js`);
await makeProjectMod.run(projectDir);
console.log('Finished making project:', name);
return projectDir;
}
/**
* @param {string} benchmarkName
*/
async function getOutputFile(benchmarkName) {
let file;
if (args.output) {
file = pathToFileURL(path.resolve(args.output));
} else {
file = new URL(`./results/${benchmarkName}-bench-${Date.now()}.json`, import.meta.url);
}
// Prepare output file directory
await fs.mkdir(new URL('./', file), { recursive: true });
return file;
}

View file

@ -0,0 +1,7 @@
# make-project
This `make-project` folder contains different files to programmatically create a new Astro project. They are created inside the `projects` folder and are gitignored. These projects are used by benchmarks for testing.
Each benchmark can specify the default project to run in its `defaultProject` export, but it can be overriden if `--project <project-name>` is passed through the CLI.
You can duplicate `_template.js` to start a new project script. All shared utilities are kept in `_util.js`.

View file

@ -0,0 +1,6 @@
/**
* Create a new project in the `projectDir` directory. Make sure to clean up the
* previous artifacts here before generating files.
* @param {URL} projectDir
*/
export async function run(projectDir) {}

View file

@ -0,0 +1,2 @@
export const loremIpsum =
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";

View file

@ -0,0 +1,59 @@
import fs from 'fs/promises';
import { loremIpsum } from './_util.js';
/**
* @param {URL} projectDir
*/
export async function run(projectDir) {
await fs.rm(projectDir, { recursive: true, force: true });
await fs.mkdir(new URL('./src/pages/blog', projectDir), { recursive: true });
await fs.mkdir(new URL('./src/content/blog', projectDir), { recursive: true });
const promises = [];
for (let i = 0; i < 100; i++) {
const content = `\
---
const i = ${i};
---
<span>{i}</span>
`;
promises.push(
fs.writeFile(new URL(`./src/pages/page-${i}.astro`, projectDir), content, 'utf-8')
);
}
for (let i = 0; i < 100; i++) {
const content = `\
# Article ${i}
${loremIpsum}
`;
promises.push(
fs.writeFile(new URL(`./src/content/blog/article-${i}.md`, projectDir), content, 'utf-8')
);
}
await fs.writeFile(
new URL(`./src/pages/blog/[...slug].astro`, projectDir),
`\
---
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const blogEntries = await getCollection('blog');
return blogEntries.map(entry => ({
params: { slug: entry.slug }, props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<h1>{entry.data.title}</h1>
<Content />
`,
'utf-8'
);
await Promise.all(promises);
}

View file

@ -0,0 +1,47 @@
import fs from 'fs/promises';
import { loremIpsum } from './_util.js';
/**
* @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.writeFile(
new URL('./src/pages/index.astro', projectDir),
`\
---
const content = "${loremIpsum}"
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>Astro</title>
</head>
<body>
<h1>Astro</h1>
<div>
${Array.from({ length: 60 }).map(() => '<p>{content}</p>')}
</div>
</body>
</html>`,
'utf-8'
);
await fs.writeFile(
new URL('./astro.config.js', projectDir),
`\
import { defineConfig } from 'astro/config';
import nodejs from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: nodejs({ mode: 'standalone' }),
});`,
'utf-8'
);
}

18
benchmark/package.json Normal file
View file

@ -0,0 +1,18 @@
{
"name": "astro-benchmark",
"private": true,
"type": "module",
"version": "0.0.0",
"bin": {
"astro-benchmark": "./index.js"
},
"dependencies": {
"@astrojs/node": "workspace:*",
"astro": "workspace:*",
"autocannon": "^7.10.0",
"execa": "^6.1.0",
"markdown-table": "^3.0.3",
"mri": "^1.2.0",
"port-authority": "^2.0.1"
}
}

View file

@ -23,7 +23,7 @@
"test:vite-ci": "turbo run test --filter=astro --output-logs=new-only --no-deps --concurrency=1",
"test:e2e": "cd packages/astro && pnpm playwright install && pnpm run test:e2e",
"test:e2e:match": "cd packages/astro && pnpm playwright install && pnpm run test:e2e:match",
"benchmark": "pnpm --filter @benchmark/simple run build && pnpm dlx concurrently -k -s first --raw \"node packages/astro/test/benchmark/simple/server.mjs\" \"pnpm dlx autocannon -c 100 -d 30 -p 10 localhost:3002/\"",
"benchmark": "astro-benchmark",
"lint": "eslint --cache .",
"version": "changeset version && pnpm install --no-frozen-lockfile && pnpm run format",
"preinstall": "npx only-allow pnpm"
@ -76,7 +76,8 @@
}
},
"dependencies": {
"@astrojs/webapi": "workspace:*"
"@astrojs/webapi": "workspace:*",
"astro-benchmark": "workspace:*"
},
"devDependencies": {
"@changesets/changelog-github": "0.4.4",

View file

@ -16,6 +16,7 @@ import type { z } from 'zod';
import type { SerializedSSRManifest } from '../core/app/types';
import type { PageBuildData } from '../core/build/types';
import type { AstroConfigSchema } from '../core/config';
import type { AstroTimer } from '../core/config/timer';
import type { AstroCookies } from '../core/cookies';
import type { AstroComponentFactory, AstroComponentInstance } from '../runtime/server';
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../core/constants.js';
@ -992,6 +993,7 @@ export interface AstroSettings {
tsConfigPath: string | undefined;
watchFiles: string[];
forceDisableTelemetry: boolean;
timer: AstroTimer;
}
export type AsyncRendererComponentFn<U> = (

View file

@ -167,6 +167,9 @@ class AstroBuilder {
buildMode: this.settings.config.output,
});
}
// Benchmark results
this.settings.timer.writeStats();
}
/** Build the given Astro project. */

View file

@ -33,6 +33,8 @@ export async function staticBuild(opts: StaticBuildOptions) {
throw new AstroError(AstroErrorData.NoAdapterInstalled);
}
settings.timer.start('SSR build');
// The pages to be built for rendering purposes.
const pageInput = new Set<string>();
@ -43,10 +45,6 @@ export async function staticBuild(opts: StaticBuildOptions) {
// Build internals needed by the CSS plugin
const internals = createBuildInternals();
const timer: Record<string, number> = {};
timer.buildStart = performance.now();
for (const [component, pageData] of Object.entries(allPages)) {
const astroModuleURL = new URL('./' + component, settings.config.root);
const astroModuleId = prependForwardSlash(component);
@ -70,10 +68,13 @@ export async function staticBuild(opts: StaticBuildOptions) {
registerAllPlugins(container);
// Build your project (SSR application code, assets, client JS, etc.)
timer.ssr = performance.now();
const ssrTime = performance.now();
info(opts.logging, 'build', `Building ${settings.config.output} entrypoints...`);
const ssrOutput = await ssrBuild(opts, internals, pageInput, container);
info(opts.logging, 'build', dim(`Completed in ${getTimeStat(timer.ssr, performance.now())}.`));
info(opts.logging, 'build', dim(`Completed in ${getTimeStat(ssrTime, performance.now())}.`));
settings.timer.end('SSR build');
settings.timer.start('Client build');
const rendererClientEntrypoints = settings.renderers
.map((r) => r.clientEntrypoint)
@ -91,23 +92,27 @@ export async function staticBuild(opts: StaticBuildOptions) {
}
// Run client build first, so the assets can be fed into the SSR rendered version.
timer.clientBuild = performance.now();
const clientOutput = await clientBuild(opts, internals, clientInput, container);
timer.generate = performance.now();
await runPostBuildHooks(container, ssrOutput, clientOutput);
settings.timer.end('Client build');
switch (settings.config.output) {
case 'static': {
settings.timer.start('Static generate');
await generatePages(opts, internals);
await cleanServerOutput(opts);
settings.timer.end('Static generate');
return;
}
case 'server': {
settings.timer.start('Server generate');
await generatePages(opts, internals);
await cleanStaticOutput(opts, internals);
info(opts.logging, null, `\n${bgMagenta(black(' finalizing server assets '))}\n`);
await ssrMoveAssets(opts);
settings.timer.end('Server generate');
return;
}
}

View file

@ -5,6 +5,7 @@ import { fileURLToPath, pathToFileURL } from 'url';
import jsxRenderer from '../../jsx/renderer.js';
import { createDefaultDevConfig } from './config.js';
import { loadTSConfig } from './tsconfig.js';
import { AstroTimer } from './timer.js';
export function createBaseSettings(config: AstroConfig): AstroSettings {
return {
@ -19,6 +20,7 @@ export function createBaseSettings(config: AstroConfig): AstroSettings {
scripts: [],
watchFiles: [],
forceDisableTelemetry: false,
timer: new AstroTimer(),
};
}

View file

@ -0,0 +1,65 @@
import fs from 'fs';
// Type used by `bench-memory.js`
export interface Stat {
elapsedTime: number;
heapUsedChange: number;
heapUsedTotal: number;
}
interface OngoingStat {
startTime: number;
startHeap: number;
}
/**
* Timer to track certain operations' performance. Used by Astro's scripts only.
* Set `process.env.ASTRO_TIMER_PATH` truthy to enable.
*/
export class AstroTimer {
private enabled: boolean;
private ongoingTimers: Map<string, OngoingStat> = new Map();
private stats: Record<string, Stat> = {};
constructor() {
this.enabled = !!process.env.ASTRO_TIMER_PATH;
}
/**
* Start a timer for a scope with a given name.
*/
start(name: string) {
if (!this.enabled) return;
globalThis.gc?.();
this.ongoingTimers.set(name, {
startTime: performance.now(),
startHeap: process.memoryUsage().heapUsed,
});
}
/**
* End a timer for a scope with a given name.
*/
end(name: string) {
if (!this.enabled) return;
const stat = this.ongoingTimers.get(name);
if (!stat) return;
globalThis.gc?.();
const endHeap = process.memoryUsage().heapUsed;
this.stats[name] = {
elapsedTime: performance.now() - stat.startTime,
heapUsedChange: endHeap - stat.startHeap,
heapUsedTotal: endHeap,
};
this.ongoingTimers.delete(name);
}
/**
* Write stats to `process.env.ASTRO_TIMER_PATH`
*/
writeStats() {
if (!this.enabled) return;
// @ts-expect-error
fs.writeFileSync(process.env.ASTRO_TIMER_PATH, JSON.stringify(this.stats, null, 2));
}
}

View file

@ -21,6 +21,7 @@ importers:
'@types/node': ^18.7.21
'@typescript-eslint/eslint-plugin': ^5.27.1
'@typescript-eslint/parser': ^5.27.1
astro-benchmark: workspace:*
del: ^7.0.0
esbuild: ^0.15.18
eslint: ^8.17.0
@ -38,6 +39,7 @@ importers:
typescript: ~4.7.3
dependencies:
'@astrojs/webapi': link:packages/webapi
astro-benchmark: link:benchmark
devDependencies:
'@changesets/changelog-github': 0.4.4
'@changesets/cli': 2.23.0_kcozqtpxuwjzskw6zg5royevn4
@ -61,6 +63,24 @@ importers:
turbo: 1.2.5
typescript: 4.7.4
benchmark:
specifiers:
'@astrojs/node': workspace:*
astro: workspace:*
autocannon: ^7.10.0
execa: ^6.1.0
markdown-table: ^3.0.3
mri: ^1.2.0
port-authority: ^2.0.1
dependencies:
'@astrojs/node': link:../packages/integrations/node
astro: link:../packages/astro
autocannon: 7.10.0
execa: 6.1.0
markdown-table: 3.0.3
mri: 1.2.0
port-authority: 2.0.1
examples/basics:
specifiers:
astro: ^2.0.16
@ -3878,6 +3898,10 @@ packages:
leven: 3.1.0
dev: false
/@assemblyscript/loader/0.19.23:
resolution: {integrity: sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw==}
dev: false
/@astro-community/astro-embed-integration/0.1.2_astro@packages+astro:
resolution: {integrity: sha512-ONBDHkOUZ7ssQNzRc5XRZtBBJR0zC68Gm2FCm5w6fxxciDkRkU9Zn9BSssgaNrLPfsXycxFLtQZT3dX9ZPsAxw==}
peerDependencies:
@ -5632,6 +5656,13 @@ packages:
mime: 3.0.0
dev: true
/@colors/colors/1.5.0:
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'}
requiresBuild: true
dev: false
optional: true
/@csstools/postcss-cascade-layers/1.1.1_postcss@8.4.21:
resolution: {integrity: sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==}
engines: {node: ^12 || ^14 || >=16}
@ -8139,11 +8170,44 @@ packages:
resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==}
dev: false
/asynckit/0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: false
/at-least-node/1.0.0:
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
engines: {node: '>= 4.0.0'}
dev: false
/autocannon/7.10.0:
resolution: {integrity: sha512-PY1UrXL4NHE7J0hA6GGN2r8xjiAePS/bii3Hz7NOvp4JO3xDNBgRftDjfAxj1t6FDWXiXEOuKF/pdDiisIS8ZA==}
hasBin: true
dependencies:
chalk: 4.1.2
char-spinner: 1.0.1
cli-table3: 0.6.3
color-support: 1.1.3
cross-argv: 2.0.0
form-data: 4.0.0
has-async-hooks: 1.0.0
hdr-histogram-js: 3.0.0
hdr-histogram-percentiles-obj: 3.0.0
http-parser-js: 0.5.8
hyperid: 3.1.1
lodash.chunk: 4.2.0
lodash.clonedeep: 4.5.0
lodash.flatten: 4.4.0
manage-path: 2.0.0
on-net-listen: 1.1.2
pretty-bytes: 5.6.0
progress: 2.0.3
reinterval: 1.1.0
retimer: 3.0.0
semver: 7.3.8
subarg: 1.0.0
timestring: 6.0.0
dev: false
/autoprefixer/10.4.13_postcss@8.4.21:
resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==}
engines: {node: ^10 || ^12 || >=14}
@ -8524,6 +8588,10 @@ packages:
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
dev: false
/char-spinner/1.0.1:
resolution: {integrity: sha512-acv43vqJ0+N0rD+Uw3pDHSxP30FHrywu2NO6/wBaHChJIizpDeBUd6NjqhNhy9LGaEAhZAXn46QzmlAvIWd16g==}
dev: false
/character-entities-html4/2.1.0:
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
@ -8622,6 +8690,15 @@ packages:
engines: {node: '>=6'}
dev: false
/cli-table3/0.6.3:
resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==}
engines: {node: 10.* || >= 12.*}
dependencies:
string-width: 4.2.3
optionalDependencies:
'@colors/colors': 1.5.0
dev: false
/cliui/6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
dependencies:
@ -8696,6 +8773,13 @@ packages:
resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==}
dev: false
/combined-stream/1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: false
/comma-separated-tokens/2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
dev: false
@ -8774,6 +8858,10 @@ packages:
resolution: {integrity: sha512-izfGgKyzzIyLaeb1EtZ3KbglkS6AKp9cv7LxmiyoOu+fXfol1tQDC0Cof0enVZGNtudTHW+3lfuW9ZkLQss4Wg==}
dev: true
/cross-argv/2.0.0:
resolution: {integrity: sha512-YIaY9TR5Nxeb8SMdtrU8asWVM4jqJDNDYlKV21LxtYcfNJhp1kEsgSa6qXwXgzN0WQWGODps0+TlGp2xQSHwOg==}
dev: false
/cross-spawn/5.1.0:
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
dependencies:
@ -9046,6 +9134,11 @@ packages:
slash: 4.0.0
dev: true
/delayed-stream/1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: false
/delegates/1.0.0:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
dev: false
@ -10030,6 +10123,15 @@ packages:
dependencies:
is-callable: 1.2.7
/form-data/4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: false
/format/0.2.2:
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
engines: {node: '>=0.4.x'}
@ -10376,6 +10478,10 @@ packages:
engines: {node: '>=6'}
dev: true
/has-async-hooks/1.0.0:
resolution: {integrity: sha512-YF0VPGjkxr7AyyQQNykX8zK4PvtEDsUJAPqwu06UFz1lb6EvI53sPh5H1kWxg8NXI5LsfRCZ8uX9NkYDZBb/mw==}
dev: false
/has-bigints/1.0.2:
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
@ -10579,6 +10685,19 @@ packages:
space-separated-tokens: 2.0.2
dev: false
/hdr-histogram-js/3.0.0:
resolution: {integrity: sha512-/EpvQI2/Z98mNFYEnlqJ8Ogful8OpArLG/6Tf2bPnkutBVLIeMVNHjk1ZDfshF2BUweipzbk+dB1hgSB7SIakw==}
engines: {node: '>=14'}
dependencies:
'@assemblyscript/loader': 0.19.23
base64-js: 1.5.1
pako: 1.0.11
dev: false
/hdr-histogram-percentiles-obj/3.0.0:
resolution: {integrity: sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==}
dev: false
/he/1.2.0:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
@ -10629,6 +10748,10 @@ packages:
statuses: 2.0.1
toidentifier: 1.0.1
/http-parser-js/0.5.8:
resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==}
dev: false
/http-proxy-agent/4.0.1:
resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==}
engines: {node: '>= 6'}
@ -10662,6 +10785,13 @@ packages:
resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==}
engines: {node: '>=12.20.0'}
/hyperid/3.1.1:
resolution: {integrity: sha512-RveV33kIksycSf7HLkq1sHB5wW0OwuX8ot8MYnY++gaaPXGFfKpBncHrAWxdpuEeRlazUMGWefwP1w6o6GaumA==}
dependencies:
uuid: 8.3.2
uuid-parse: 1.1.0
dev: false
/iconv-lite/0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
@ -11265,10 +11395,22 @@ packages:
dependencies:
p-locate: 5.0.0
/lodash.chunk/4.2.0:
resolution: {integrity: sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==}
dev: false
/lodash.clonedeep/4.5.0:
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
dev: false
/lodash.debounce/4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
dev: false
/lodash.flatten/4.4.0:
resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==}
dev: false
/lodash.merge/4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true
@ -11372,6 +11514,10 @@ packages:
semver: 6.3.0
dev: false
/manage-path/2.0.0:
resolution: {integrity: sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A==}
dev: false
/map-obj/1.0.1:
resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==}
engines: {node: '>=0.10.0'}
@ -11974,14 +12120,12 @@ packages:
/mime-db/1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: true
/mime-types/2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: true
/mime/1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
@ -12082,6 +12226,10 @@ packages:
/minimist/1.2.7:
resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==}
/minimist/1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: false
/minipass/3.3.6:
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
engines: {node: '>=8'}
@ -12381,6 +12529,11 @@ packages:
ee-first: 1.1.1
dev: false
/on-net-listen/1.1.2:
resolution: {integrity: sha512-y1HRYy8s/RlcBvDUwKXSmkODMdx4KSuIvloCnQYJ2LdBBC1asY4HtfhXwe3UWknLakATZDnbzht2Ijw3M1EqFg==}
engines: {node: '>=9.4.0 || ^8.9.4'}
dev: false
/once/1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@ -12553,6 +12706,10 @@ packages:
netmask: 2.0.2
dev: true
/pako/1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
dev: false
/parent-module/1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@ -12723,6 +12880,10 @@ packages:
playwright-core: 1.30.0
dev: true
/port-authority/2.0.1:
resolution: {integrity: sha512-Hz/WvSNt5+7x+Rq1Cn6DetJOZxKtLDehJ1mLCYge6ju4QvSF/PHvRgy94e1SKJVI96AJTcqEdNwkkaAFad+TXQ==}
dev: false
/postcss-attribute-case-insensitive/5.0.2_postcss@8.4.21:
resolution: {integrity: sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==}
engines: {node: ^12 || ^14 || >=16}
@ -13227,6 +13388,11 @@ packages:
engines: {node: '>=6'}
dev: false
/progress/2.0.3:
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
engines: {node: '>=0.4.0'}
dev: false
/prompts/2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
@ -13529,6 +13695,10 @@ packages:
unified: 10.1.2
dev: false
/reinterval/1.1.0:
resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==}
dev: false
/remark-code-titles/0.1.2:
resolution: {integrity: sha512-KsHQbaI4FX8Ozxqk7YErxwmBiveUqloKuVqyPG2YPLHojpgomodWgRfG4B+bOtmn/5bfJ8khw4rR0lvgVFl2Uw==}
dependencies:
@ -13691,6 +13861,10 @@ packages:
unified: 10.1.2
dev: false
/retimer/3.0.0:
resolution: {integrity: sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==}
dev: false
/reusify/1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@ -14334,6 +14508,12 @@ packages:
inline-style-parser: 0.1.1
dev: false
/subarg/1.0.0:
resolution: {integrity: sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==}
dependencies:
minimist: 1.2.8
dev: false
/suf-log/2.5.3:
resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==}
dependencies:
@ -14513,6 +14693,11 @@ packages:
engines: {node: '>=6'}
dev: false
/timestring/6.0.0:
resolution: {integrity: sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==}
engines: {node: '>=8'}
dev: false
/tiny-glob/0.2.9:
resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}
dependencies:
@ -15087,6 +15272,15 @@ packages:
/util-deprecate/1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
/uuid-parse/1.1.0:
resolution: {integrity: sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==}
dev: false
/uuid/8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
dev: false
/uvu/0.5.6:
resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==}
engines: {node: '>=8'}

View file

@ -3,3 +3,4 @@ packages:
- 'examples/**/*'
- 'smoke/**/*'
- 'scripts'
- 'benchmark'