misc: add benchmarking script for edge rendering (#40716)
This PR adds the benchmarking script I've been using for #40251 to measure the performance improvements that we make to the Edge SSR runtime. This tool: - uploads two version of the benchmarking project to Vercel, one with the latest canary and the one with your current local changes in dist (don't forget to build!) - runs some tests against the published url to measure TTFB - displays a nice chart and table What this doesn't do (yet): - allow you to choose which URL to compare - allow you to change the measured metric - run a battery of differnet test ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see `contributing.md` https://user-images.githubusercontent.com/11064311/191270204-04447e20-5a40-43a9-bcda-b7eaeb3d270a.mov ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
bb42d40fb7
commit
6be2868787
18 changed files with 864 additions and 15 deletions
8
bench/vercel/.env.dev
Normal file
8
bench/vercel/.env.dev
Normal file
|
@ -0,0 +1,8 @@
|
|||
# The Vercel team you want to deploy the project too
|
||||
VERCEL_TEST_TEAM=
|
||||
|
||||
# The corresponding Vercel token
|
||||
VERCEL_TEST_TOKEN=
|
||||
|
||||
# The Vercel project you want to deploy the test project too
|
||||
VERCEL_TEST_PROJECT_NAME=
|
5
bench/vercel/.gitignore
vendored
Normal file
5
bench/vercel/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
.vercel
|
||||
.next
|
||||
*.tgz
|
||||
yarn.lock
|
||||
.env
|
22
bench/vercel/README.md
Normal file
22
bench/vercel/README.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Benchmarking Next.js on production
|
||||
|
||||
This script allows you to measure some performance metrics of your local build of Next.js on production by uploading your current build to Vercel with an example app and running some basic benchmarks on it.
|
||||
|
||||
## Requirements
|
||||
|
||||
- the Vercel CLI
|
||||
|
||||
## Setup
|
||||
|
||||
Rename the provided `./env.local` file to `./env` and fill in the required `VERCEL_TEST_TOKEN` and `VERCEL_TEST_TEAM` values. You can find and generate those from vercel.com.
|
||||
|
||||
Run `pnpm install`, `pnpm bench` and profit.
|
||||
|
||||
Note: if you made some changes to Next.js, make sure you compiled them by running at the root of the monorepo either `pnpm dev` or `pnpm build --force`.
|
||||
|
||||
## How it works
|
||||
|
||||
- with the Vercel CLI, we setup a project
|
||||
- we `npm pack` the local Next build and add it to the repo
|
||||
- we upload the repo to Vercel and let it build
|
||||
- once it builds, we get the deployment url and run some tests
|
70
bench/vercel/bench.js
Normal file
70
bench/vercel/bench.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { Command } from 'commander'
|
||||
import console from 'console'
|
||||
|
||||
import chalk from 'chalk'
|
||||
|
||||
import PQueue from 'p-queue'
|
||||
import { generateProjects, cleanupProjectFolders } from './project-utils.js'
|
||||
import { printBenchmarkResults } from './chart.js'
|
||||
import { genRetryableRequest } from './gen-request.js'
|
||||
|
||||
const program = new Command()
|
||||
|
||||
const queue = new PQueue({ concurrency: 25 })
|
||||
const TTFB_OUTLIERS_THRESHOLD = 250
|
||||
|
||||
program.option('-p, --path <path>')
|
||||
|
||||
program.parse(process.argv)
|
||||
|
||||
const options = program.opts()
|
||||
|
||||
if (options.path) {
|
||||
console.log('Running benchmark for path: ', options.path)
|
||||
}
|
||||
|
||||
try {
|
||||
const [originDeploymentURL, headDeploymentURL] = await generateProjects()
|
||||
|
||||
const originBenchmarkURL = `${originDeploymentURL}${options.path || ''}`
|
||||
const headBenchmarkURL = `${headDeploymentURL}${options.path || ''}`
|
||||
|
||||
console.log(`Origin deployment URL: ${originBenchmarkURL}`)
|
||||
console.log(`Head deployment URL: ${headBenchmarkURL}`)
|
||||
console.log(`Running benchmark...`)
|
||||
|
||||
const benchResults = await runBenchmark(originBenchmarkURL)
|
||||
|
||||
const headBenchResults = await runBenchmark(headBenchmarkURL)
|
||||
|
||||
console.log(chalk.bold('Benchmark results for cold:'))
|
||||
printBenchmarkResults(
|
||||
{
|
||||
origin: benchResults,
|
||||
head: headBenchResults,
|
||||
},
|
||||
(r) => r.cold && r.firstByte <= TTFB_OUTLIERS_THRESHOLD && r.firstByte
|
||||
)
|
||||
console.log(chalk.bold('Benchmark results for hot:'))
|
||||
printBenchmarkResults(
|
||||
{
|
||||
origin: benchResults,
|
||||
head: headBenchResults,
|
||||
},
|
||||
(r) => !r.cold && r.firstByte <= TTFB_OUTLIERS_THRESHOLD && r.firstByte
|
||||
)
|
||||
} catch (err) {
|
||||
console.log(chalk.red('Benchmark failed: ', err))
|
||||
} finally {
|
||||
await cleanupProjectFolders()
|
||||
}
|
||||
|
||||
async function runBenchmark(url) {
|
||||
return (
|
||||
await Promise.all(
|
||||
Array.from({ length: 500 }).map(() =>
|
||||
queue.add(() => genRetryableRequest(url))
|
||||
)
|
||||
)
|
||||
).filter(Boolean)
|
||||
}
|
2
bench/vercel/benchmark-app/.gitignore
vendored
Normal file
2
bench/vercel/benchmark-app/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
.vercel
|
||||
webpack-stats-client.json
|
14
bench/vercel/benchmark-app/app/layout.js
Normal file
14
bench/vercel/benchmark-app/app/layout.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import * as React from 'react'
|
||||
|
||||
export default function Root({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<head></head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
runtime: 'experimental-edge',
|
||||
}
|
14
bench/vercel/benchmark-app/app/rsc/page.js
Normal file
14
bench/vercel/benchmark-app/app/rsc/page.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import * as React from 'react'
|
||||
|
||||
// if (!('hot' in Math)) Math.hot = false
|
||||
|
||||
export default function page() {
|
||||
// const previous = Math.hot
|
||||
// Math.hot = true
|
||||
// return <div>{previous ? 'HOT' : 'COLD'}</div>
|
||||
return <div>hello</div>
|
||||
}
|
||||
|
||||
export const config = {
|
||||
runtime: 'experimental-edge',
|
||||
}
|
34
bench/vercel/benchmark-app/next.config.js
Normal file
34
bench/vercel/benchmark-app/next.config.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
const { StatsWriterPlugin } = require('webpack-stats-plugin')
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
|
||||
|
||||
module.exports = {
|
||||
experimental: {
|
||||
appDir: true,
|
||||
},
|
||||
webpack: (config, options) => {
|
||||
const { nextRuntime = 'client' } = options
|
||||
if (process.env.ANALYZE) {
|
||||
if (nextRuntime === 'edge')
|
||||
config.plugins.push(
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'static',
|
||||
openAnalyzer: true,
|
||||
reportFilename: options.isServer
|
||||
? '../analyze/server.html'
|
||||
: './analyze/client.html',
|
||||
})
|
||||
)
|
||||
config.plugins.push(
|
||||
new StatsWriterPlugin({
|
||||
filename: `../webpack-stats-${nextRuntime}.json`,
|
||||
stats: {
|
||||
assets: true,
|
||||
chunks: true,
|
||||
modules: true,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
return config
|
||||
},
|
||||
}
|
14
bench/vercel/benchmark-app/package.json
Normal file
14
bench/vercel/benchmark-app/package.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "stats-app",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"webpack-bundle-analyzer": "^4.6.1",
|
||||
"webpack-stats-plugin": "^1.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
}
|
||||
}
|
20
bench/vercel/benchmark-app/pages/index.js
Normal file
20
bench/vercel/benchmark-app/pages/index.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
if (!('hot' in Math)) Math.hot = false
|
||||
|
||||
export default function page({ hot }) {
|
||||
return `${hot ? 'HOT' : 'COLD'}`
|
||||
}
|
||||
|
||||
export async function getServerSideProps() {
|
||||
const wasHot = Math.hot
|
||||
Math.hot = true
|
||||
|
||||
return {
|
||||
props: {
|
||||
hot: wasHot,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
runtime: 'experimental-edge',
|
||||
}
|
99
bench/vercel/chart.js
Normal file
99
bench/vercel/chart.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
import downsampler from 'downsample-lttb'
|
||||
import asciichart from 'asciichart'
|
||||
import terminalSize from 'term-size'
|
||||
|
||||
const CHART_WIDTH = terminalSize().columns - 15 // space for the labels
|
||||
|
||||
function getMetrics(data) {
|
||||
const sorted = [...data].sort((a, b) => a - b)
|
||||
const getPercentile = (percentile) => {
|
||||
const index = Math.floor((sorted.length - 1) * percentile)
|
||||
return sorted[index]
|
||||
}
|
||||
return {
|
||||
hits: sorted.length,
|
||||
confidenceInterval: round(getConfidenceInterval(sorted)),
|
||||
median: getPercentile(0.5),
|
||||
avg: sorted.reduce((a, b) => a + b, 0) / sorted.length,
|
||||
p75: getPercentile(0.75),
|
||||
p95: getPercentile(0.95),
|
||||
p99: getPercentile(0.99),
|
||||
p25: getPercentile(0.25),
|
||||
min: sorted[0],
|
||||
max: sorted[sorted.length - 1],
|
||||
}
|
||||
}
|
||||
|
||||
function round(num) {
|
||||
return Math.round(num * 100) / 100
|
||||
}
|
||||
|
||||
// thanks Copilot
|
||||
function getConfidenceInterval(data) {
|
||||
const n = data.length
|
||||
const m = data.reduce((a, b) => a + b) / n
|
||||
const s = Math.sqrt(
|
||||
data.map((x) => Math.pow(x - m, 2)).reduce((a, b) => a + b) / n
|
||||
)
|
||||
const z = 1.96 // 95% confidence
|
||||
const e = z * (s / Math.sqrt(n))
|
||||
return e
|
||||
}
|
||||
|
||||
export function downsample(data, maxPoints) {
|
||||
const sortedData = [...data].sort((a, b) => a - b)
|
||||
return downsampler
|
||||
.processData(
|
||||
// the downsampler expects a 2d array of [x, y] values, so we need to add an index
|
||||
sortedData.map((p, i) => [p, i]),
|
||||
maxPoints
|
||||
)
|
||||
.map((p) => p[0])
|
||||
}
|
||||
|
||||
export function printBenchmarkResults({ origin, head }, metricSelector) {
|
||||
const [processedOriginData, processedHeadData] = [origin, head].map(
|
||||
(results) => results.map(metricSelector).filter(Boolean)
|
||||
)
|
||||
|
||||
const [originMetrics, headMetrics] = [
|
||||
processedOriginData,
|
||||
processedHeadData,
|
||||
].map(getMetrics)
|
||||
|
||||
const deltaMetrics = {
|
||||
min: headMetrics.min - originMetrics.min,
|
||||
max: headMetrics.max - originMetrics.max,
|
||||
avg: headMetrics.avg - originMetrics.avg,
|
||||
median: headMetrics.median - originMetrics.median,
|
||||
p95: headMetrics.p95 - originMetrics.p95,
|
||||
p99: headMetrics.p99 - originMetrics.p99,
|
||||
p75: headMetrics.p75 - originMetrics.p75,
|
||||
p25: headMetrics.p25 - originMetrics.p25,
|
||||
}
|
||||
|
||||
console.table({
|
||||
origin: originMetrics,
|
||||
head: headMetrics,
|
||||
delta: deltaMetrics,
|
||||
})
|
||||
|
||||
const [originData, headData] = [processedOriginData, processedHeadData].map(
|
||||
(data) =>
|
||||
downsample(
|
||||
data,
|
||||
Math.min(
|
||||
CHART_WIDTH,
|
||||
processedOriginData.length,
|
||||
processedHeadData.length
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
console.log(
|
||||
asciichart.plot([originData, headData], {
|
||||
height: 15,
|
||||
colors: [asciichart.blue, asciichart.red],
|
||||
})
|
||||
)
|
||||
}
|
41
bench/vercel/gen-request.js
Normal file
41
bench/vercel/gen-request.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import https from 'https'
|
||||
import timer from '@szmarczak/http-timer'
|
||||
|
||||
// a wrapper around genAsyncRequest that will retry the request 5 times if it fails
|
||||
export async function genRetryableRequest(url) {
|
||||
let retries = 0
|
||||
while (retries < 5) {
|
||||
try {
|
||||
return await genAsyncRequest(url)
|
||||
} catch (err) {}
|
||||
retries++
|
||||
await new Promise((r) => setTimeout(r, 1000))
|
||||
}
|
||||
throw new Error(`Failed to fetch ${url}, too many retries`)
|
||||
}
|
||||
|
||||
// a wrapper around http.request that is enhanced with timing information
|
||||
async function genAsyncRequest(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = https.get(url)
|
||||
timer(request)
|
||||
request.on('response', (response) => {
|
||||
let body = ''
|
||||
response.on('data', (data) => {
|
||||
body += data
|
||||
})
|
||||
response.on('end', () => {
|
||||
resolve({
|
||||
...response.timings.phases,
|
||||
cold: !body.includes('HOT'),
|
||||
})
|
||||
})
|
||||
response.on('error', (err) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
request.on('error', (err) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
52
bench/vercel/generate-package-json.js
Normal file
52
bench/vercel/generate-package-json.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
import execa from 'execa'
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
|
||||
export async function generatePackageJson(folder, withLocalNext = false) {
|
||||
const packageJson = JSON.parse(
|
||||
await fs.readFile(path.join(folder, 'package.json'))
|
||||
)
|
||||
|
||||
const currentVersions = await getCurrentRootReactPackagesVersions()
|
||||
|
||||
packageJson.dependencies = packageJson.dependencies || {}
|
||||
packageJson.dependencies['react'] = currentVersions.react
|
||||
packageJson.dependencies['react-dom'] = currentVersions['react-dom']
|
||||
if (withLocalNext) {
|
||||
packageJson.dependencies.next = await packNextBuild(folder)
|
||||
} else {
|
||||
packageJson.dependencies.next = await getCurrentNextVersion()
|
||||
}
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(folder, 'package.json'),
|
||||
JSON.stringify(packageJson, null, 2)
|
||||
)
|
||||
}
|
||||
|
||||
export async function packNextBuild(folder) {
|
||||
const process = await execa('npm', [
|
||||
'pack',
|
||||
'../../packages/next',
|
||||
`--pack-destination=${folder}`,
|
||||
])
|
||||
|
||||
return `file:./${process.stdout}`
|
||||
}
|
||||
|
||||
async function getCurrentNextVersion() {
|
||||
const packageJson = JSON.parse(
|
||||
await fs.readFile('../../packages/next/package.json', 'utf8')
|
||||
)
|
||||
return packageJson.version
|
||||
}
|
||||
|
||||
async function getCurrentRootReactPackagesVersions() {
|
||||
const packageJson = JSON.parse(
|
||||
await fs.readFile('../../package.json', 'utf8')
|
||||
)
|
||||
return {
|
||||
react: packageJson.devDependencies['react-exp'],
|
||||
'react-dom': packageJson.devDependencies['react-dom-exp'],
|
||||
}
|
||||
}
|
24
bench/vercel/package.json
Normal file
24
bench/vercel/package.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "bench-production",
|
||||
"version": "1.0.0",
|
||||
"description": "Scripts for benchmarking in production.",
|
||||
"main": "bench.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"bench": "node bench.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@szmarczak/http-timer": "5.0.1",
|
||||
"asciichart": "1.5.25",
|
||||
"commander": "2.20.0",
|
||||
"dotenv": "10.0.0",
|
||||
"downsample-lttb": "0.0.1",
|
||||
"listr2": "5.0.5",
|
||||
"p-queue": "7.3.0",
|
||||
"term-size": "3.0.2",
|
||||
"webpack-bundle-analyzer": "^4.6.1",
|
||||
"webpack-stats-plugin": "^1.1.0"
|
||||
}
|
||||
}
|
218
bench/vercel/project-utils.js
Normal file
218
bench/vercel/project-utils.js
Normal file
|
@ -0,0 +1,218 @@
|
|||
import { config } from 'dotenv'
|
||||
|
||||
import fetch from 'node-fetch'
|
||||
import chalk from 'chalk'
|
||||
import execa from 'execa'
|
||||
import path from 'path'
|
||||
import url from 'url'
|
||||
import { generatePackageJson } from './generate-package-json.js'
|
||||
import { Listr } from 'listr2'
|
||||
|
||||
config()
|
||||
|
||||
const TEST_PROJECT_NAME = process.env.VERCEL_TEST_PROJECT_NAME
|
||||
const ORIGIN_PROJECT_NAME = TEST_PROJECT_NAME + '-origin'
|
||||
const HEAD_PROJECT_NAME = TEST_PROJECT_NAME + '-head'
|
||||
|
||||
const TEST_TEAM_NAME = process.env.VERCEL_TEST_TEAM
|
||||
const TEST_TOKEN = process.env.VERCEL_TEST_TOKEN
|
||||
const VERCEL_EDGE_FUNCTIONS_BRIDGE_PKG =
|
||||
process.env.VERCEL_EDGE_FUNCTIONS_BRIDGE_PKG
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const appFolder = path.join(__dirname, 'benchmark-app')
|
||||
const originAppFolder = path.join(__dirname, 'benchmark-app-origin')
|
||||
const headAppFolder = path.join(__dirname, 'benchmark-app-head')
|
||||
|
||||
export async function generateProjects() {
|
||||
const { originUrl, headUrl } = await new Listr(
|
||||
[
|
||||
{
|
||||
title: 'Origin project',
|
||||
task: (ctx, task) =>
|
||||
task.newListr(
|
||||
(parent) => [
|
||||
{
|
||||
title: 'Resetting project',
|
||||
task: async () => {
|
||||
await resetProject(ORIGIN_PROJECT_NAME)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'copying app',
|
||||
task: async () => {
|
||||
await execa('cp', ['-f', '-R', appFolder, originAppFolder])
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Set Next.js version in package.json',
|
||||
task: async () => {
|
||||
await generatePackageJson(originAppFolder)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'deploying project',
|
||||
task: async () => {
|
||||
const url = await deployProject(
|
||||
ORIGIN_PROJECT_NAME,
|
||||
originAppFolder
|
||||
)
|
||||
ctx.originUrl = url
|
||||
},
|
||||
},
|
||||
],
|
||||
{ concurrent: false }
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Head project',
|
||||
task: (ctx, task) =>
|
||||
task.newListr(
|
||||
(parent) => [
|
||||
{
|
||||
title: 'Resetting project',
|
||||
task: async () => {
|
||||
await resetProject(HEAD_PROJECT_NAME)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'copying app',
|
||||
task: async () => {
|
||||
await execa('cp', ['-f', '-R', appFolder, headAppFolder])
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'pack local Next.js version',
|
||||
task: async () => {
|
||||
await generatePackageJson(headAppFolder, true)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'deploying project',
|
||||
task: async () => {
|
||||
const url = await deployProject(
|
||||
HEAD_PROJECT_NAME,
|
||||
headAppFolder
|
||||
)
|
||||
ctx.headUrl = url
|
||||
},
|
||||
},
|
||||
],
|
||||
{ concurrent: false }
|
||||
),
|
||||
},
|
||||
],
|
||||
{ concurrent: true }
|
||||
).run()
|
||||
|
||||
return [originUrl, headUrl]
|
||||
}
|
||||
|
||||
export async function cleanupProjectFolders() {
|
||||
await Promise.all([
|
||||
execa('rm', ['-rf', originAppFolder]),
|
||||
execa('rm', ['-rf', headAppFolder]),
|
||||
])
|
||||
}
|
||||
|
||||
async function resetProject(projectName) {
|
||||
const deleteRes = await fetch(
|
||||
`https://vercel.com/api/v8/projects/${encodeURIComponent(
|
||||
projectName
|
||||
)}?teamId=${TEST_TEAM_NAME}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Authorization: `Bearer ${TEST_TOKEN}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!deleteRes.ok && deleteRes.status !== 404) {
|
||||
throw new Error(
|
||||
`Failed to delete project got status ${
|
||||
deleteRes.status
|
||||
}, ${await deleteRes.text()}`
|
||||
)
|
||||
}
|
||||
|
||||
const createRes = await fetch(
|
||||
`https://vercel.com/api/v8/projects?teamId=${TEST_TEAM_NAME}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
Authorization: `Bearer ${TEST_TOKEN}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
framework: 'nextjs',
|
||||
name: projectName,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
if (!createRes.ok) {
|
||||
throw new Error(
|
||||
`Failed to create project got status ${
|
||||
createRes.status
|
||||
}, ${await createRes.text()}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function deployProject(projectName, appFolder) {
|
||||
try {
|
||||
const vercelFlags = ['--scope', TEST_TEAM_NAME]
|
||||
const vercelEnv = { ...process.env, TOKEN: TEST_TOKEN }
|
||||
|
||||
// link the project
|
||||
const linkRes = await execa(
|
||||
'vercel',
|
||||
['link', '-p', projectName, '--confirm', ...vercelFlags],
|
||||
{
|
||||
cwd: appFolder,
|
||||
env: vercelEnv,
|
||||
}
|
||||
)
|
||||
|
||||
if (linkRes.exitCode !== 0) {
|
||||
throw new Error(
|
||||
`Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
|
||||
)
|
||||
}
|
||||
|
||||
const deployRes = await execa(
|
||||
'vercel',
|
||||
[
|
||||
'deploy',
|
||||
'--build-env',
|
||||
'NEXT_PRIVATE_TEST_MODE=1',
|
||||
'--build-env',
|
||||
'NEXT_TELEMETRY_DISABLED=1',
|
||||
...(VERCEL_EDGE_FUNCTIONS_BRIDGE_PKG
|
||||
? [
|
||||
'--build-env',
|
||||
`VERCEL_EDGE_FUNCTIONS_BRIDGE_PKG=${VERCEL_EDGE_FUNCTIONS_BRIDGE_PKG}`,
|
||||
]
|
||||
: []),
|
||||
'--force',
|
||||
...vercelFlags,
|
||||
],
|
||||
{
|
||||
cwd: appFolder,
|
||||
env: vercelEnv,
|
||||
}
|
||||
)
|
||||
|
||||
if (deployRes.exitCode !== 0) {
|
||||
throw new Error(
|
||||
`Failed to deploy project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
|
||||
)
|
||||
}
|
||||
|
||||
return deployRes.stdout
|
||||
} catch (err) {
|
||||
console.log(chalk.red('Deployment failed: ', err))
|
||||
throw err
|
||||
}
|
||||
}
|
239
pnpm-lock.yaml
239
pnpm-lock.yaml
|
@ -325,6 +325,30 @@ importers:
|
|||
webpack: 5.74.0_@swc+core@1.2.203
|
||||
webpack-bundle-analyzer: 4.3.0
|
||||
|
||||
bench/vercel:
|
||||
specifiers:
|
||||
'@szmarczak/http-timer': 5.0.1
|
||||
asciichart: 1.5.25
|
||||
commander: 2.20.0
|
||||
dotenv: 10.0.0
|
||||
downsample-lttb: 0.0.1
|
||||
listr2: 5.0.5
|
||||
p-queue: 7.3.0
|
||||
term-size: 3.0.2
|
||||
webpack-bundle-analyzer: ^4.6.1
|
||||
webpack-stats-plugin: ^1.1.0
|
||||
dependencies:
|
||||
'@szmarczak/http-timer': 5.0.1
|
||||
asciichart: 1.5.25
|
||||
commander: 2.20.0
|
||||
dotenv: 10.0.0
|
||||
downsample-lttb: 0.0.1
|
||||
listr2: 5.0.5
|
||||
p-queue: 7.3.0
|
||||
term-size: 3.0.2
|
||||
webpack-bundle-analyzer: 4.6.1
|
||||
webpack-stats-plugin: 1.1.0
|
||||
|
||||
packages/create-next-app:
|
||||
specifiers:
|
||||
'@types/async-retry': 1.4.2
|
||||
|
@ -6689,6 +6713,16 @@ packages:
|
|||
defer-to-connect: 2.0.0
|
||||
dev: true
|
||||
|
||||
/@szmarczak/http-timer/5.0.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==,
|
||||
}
|
||||
engines: { node: '>=14.16' }
|
||||
dependencies:
|
||||
defer-to-connect: 2.0.1
|
||||
dev: false
|
||||
|
||||
/@taskr/clear/1.1.0:
|
||||
resolution:
|
||||
{
|
||||
|
@ -8300,7 +8334,6 @@ packages:
|
|||
}
|
||||
engines: { node: '>=0.4.0' }
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/add-stream/1.0.0:
|
||||
resolution:
|
||||
|
@ -8371,7 +8404,6 @@ packages:
|
|||
dependencies:
|
||||
clean-stack: 2.2.0
|
||||
indent-string: 4.0.0
|
||||
dev: true
|
||||
|
||||
/ajv-keywords/3.5.2_ajv@6.12.6:
|
||||
resolution:
|
||||
|
@ -8756,7 +8788,10 @@ packages:
|
|||
dev: true
|
||||
|
||||
/array-flatten/1.1.1:
|
||||
resolution: { integrity: sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= }
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==,
|
||||
}
|
||||
dev: true
|
||||
|
||||
/array-ify/1.0.0:
|
||||
|
@ -8910,6 +8945,13 @@ packages:
|
|||
}
|
||||
dev: true
|
||||
|
||||
/asciichart/1.5.25:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-PNxzXIPPOtWq8T7bgzBtk9cI2lgS4SJZthUHEiQ1aoIc3lNzGfUvIvo9LiAnq26TACo9t1/4qP6KTGAUbzX9Xg==,
|
||||
}
|
||||
dev: false
|
||||
|
||||
/asn1.js/4.10.1:
|
||||
resolution:
|
||||
{
|
||||
|
@ -10599,7 +10641,6 @@ packages:
|
|||
integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==,
|
||||
}
|
||||
engines: { node: '>=6' }
|
||||
dev: true
|
||||
|
||||
/cli-boxes/2.2.1:
|
||||
resolution:
|
||||
|
@ -10674,6 +10715,17 @@ packages:
|
|||
string-width: 1.0.2
|
||||
dev: true
|
||||
|
||||
/cli-truncate/2.1.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==,
|
||||
}
|
||||
engines: { node: '>=8' }
|
||||
dependencies:
|
||||
slice-ansi: 3.0.0
|
||||
string-width: 4.2.3
|
||||
dev: false
|
||||
|
||||
/cli-width/2.2.0:
|
||||
resolution:
|
||||
{
|
||||
|
@ -10881,6 +10933,13 @@ packages:
|
|||
}
|
||||
dev: true
|
||||
|
||||
/colorette/2.0.19:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==,
|
||||
}
|
||||
dev: false
|
||||
|
||||
/colors/1.4.0:
|
||||
resolution:
|
||||
{
|
||||
|
@ -10921,7 +10980,6 @@ packages:
|
|||
{
|
||||
integrity: sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==,
|
||||
}
|
||||
dev: true
|
||||
|
||||
/commander/2.20.3:
|
||||
resolution:
|
||||
|
@ -10951,7 +11009,6 @@ packages:
|
|||
integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==,
|
||||
}
|
||||
engines: { node: '>= 10' }
|
||||
dev: true
|
||||
|
||||
/comment-json/3.0.3:
|
||||
resolution:
|
||||
|
@ -11297,7 +11354,10 @@ packages:
|
|||
safe-buffer: 5.1.2
|
||||
|
||||
/cookie-signature/1.0.6:
|
||||
resolution: { integrity: sha1-4wOogrNCzD7oylE6eZmXNNqzriw= }
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==,
|
||||
}
|
||||
dev: true
|
||||
|
||||
/cookie/0.4.0:
|
||||
|
@ -12400,6 +12460,14 @@ packages:
|
|||
engines: { node: '>=10' }
|
||||
dev: true
|
||||
|
||||
/defer-to-connect/2.0.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==,
|
||||
}
|
||||
engines: { node: '>=10' }
|
||||
dev: false
|
||||
|
||||
/define-lazy-prop/2.0.0:
|
||||
resolution:
|
||||
{
|
||||
|
@ -12953,7 +13021,13 @@ packages:
|
|||
integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==,
|
||||
}
|
||||
engines: { node: '>=10' }
|
||||
dev: true
|
||||
|
||||
/downsample-lttb/0.0.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Olebo5gyh44OAXTd2BKdcbN5VaZOIKFzoeo9JUFwxDlGt6Sd8fUo6SKaLcafy8aP2UrsKmWDpsscsFEghMjeZA==,
|
||||
}
|
||||
dev: false
|
||||
|
||||
/duplexer/0.1.1:
|
||||
resolution:
|
||||
|
@ -13973,7 +14047,6 @@ packages:
|
|||
{
|
||||
integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==,
|
||||
}
|
||||
dev: true
|
||||
|
||||
/events/3.3.0:
|
||||
resolution:
|
||||
|
@ -14842,7 +14915,10 @@ packages:
|
|||
dev: true
|
||||
|
||||
/forwarded/0.1.2:
|
||||
resolution: { integrity: sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= }
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Ua9xNhH0b8pwE3yRbFfXJvfdWF0UHNCdeyb2sbi9Ul/M+r3PTdrz7Cv4SCfZRMjmzEM9PhraqfZFbGTIg3OMyA==,
|
||||
}
|
||||
engines: { node: '>= 0.6' }
|
||||
dev: true
|
||||
|
||||
|
@ -19311,6 +19387,28 @@ packages:
|
|||
- zenObservable
|
||||
dev: true
|
||||
|
||||
/listr2/5.0.5:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-DpBel6fczu7oQKTXMekeprc0o3XDgGMkD7JNYyX+X0xbwK+xgrx9dcyKoXKqpLSUvAWfmoePS7kavniOcq3r4w==,
|
||||
}
|
||||
engines: { node: ^14.13.1 || >=16.0.0 }
|
||||
peerDependencies:
|
||||
enquirer: '>= 2.3.0 < 3'
|
||||
peerDependenciesMeta:
|
||||
enquirer:
|
||||
optional: true
|
||||
dependencies:
|
||||
cli-truncate: 2.1.0
|
||||
colorette: 2.0.19
|
||||
log-update: 4.0.0
|
||||
p-map: 4.0.0
|
||||
rfdc: 1.3.0
|
||||
rxjs: 7.5.7
|
||||
through: 2.3.8
|
||||
wrap-ansi: 7.0.0
|
||||
dev: false
|
||||
|
||||
/load-json-file/1.1.0:
|
||||
resolution: { integrity: sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= }
|
||||
engines: { node: '>=0.10.0' }
|
||||
|
@ -19667,6 +19765,19 @@ packages:
|
|||
wrap-ansi: 3.0.1
|
||||
dev: true
|
||||
|
||||
/log-update/4.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==,
|
||||
}
|
||||
engines: { node: '>=10' }
|
||||
dependencies:
|
||||
ansi-escapes: 4.3.0
|
||||
cli-cursor: 3.1.0
|
||||
slice-ansi: 4.0.0
|
||||
wrap-ansi: 6.2.0
|
||||
dev: false
|
||||
|
||||
/long/4.0.0:
|
||||
resolution:
|
||||
{
|
||||
|
@ -20168,7 +20279,10 @@ packages:
|
|||
dev: true
|
||||
|
||||
/merge-descriptors/1.0.1:
|
||||
resolution: { integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= }
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==,
|
||||
}
|
||||
dev: true
|
||||
|
||||
/merge-stream/2.0.0:
|
||||
|
@ -21985,7 +22099,6 @@ packages:
|
|||
engines: { node: '>=10' }
|
||||
dependencies:
|
||||
aggregate-error: 3.0.1
|
||||
dev: true
|
||||
|
||||
/p-pipe/3.1.0:
|
||||
resolution:
|
||||
|
@ -22006,6 +22119,17 @@ packages:
|
|||
p-timeout: 3.2.0
|
||||
dev: true
|
||||
|
||||
/p-queue/7.3.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-5fP+yVQ0qp0rEfZoDTlP2c3RYBgxvRsw30qO+VtPPc95lyvSG+x6USSh1TuLB4n96IO6I8/oXQGsTgtna4q2nQ==,
|
||||
}
|
||||
engines: { node: '>=12' }
|
||||
dependencies:
|
||||
eventemitter3: 4.0.7
|
||||
p-timeout: 5.1.0
|
||||
dev: false
|
||||
|
||||
/p-reduce/2.1.0:
|
||||
resolution:
|
||||
{
|
||||
|
@ -22044,6 +22168,14 @@ packages:
|
|||
p-finally: 1.0.0
|
||||
dev: true
|
||||
|
||||
/p-timeout/5.1.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==,
|
||||
}
|
||||
engines: { node: '>=12' }
|
||||
dev: false
|
||||
|
||||
/p-try/1.0.0:
|
||||
resolution: { integrity: sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= }
|
||||
engines: { node: '>=4' }
|
||||
|
@ -25611,6 +25743,13 @@ packages:
|
|||
engines: { node: '>=4' }
|
||||
dev: true
|
||||
|
||||
/rfdc/1.3.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==,
|
||||
}
|
||||
dev: false
|
||||
|
||||
/rgb-regex/1.0.1:
|
||||
resolution:
|
||||
{
|
||||
|
@ -25810,6 +25949,15 @@ packages:
|
|||
tslib: 2.4.0
|
||||
dev: true
|
||||
|
||||
/rxjs/7.5.7:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==,
|
||||
}
|
||||
dependencies:
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/sade/1.7.4:
|
||||
resolution:
|
||||
{
|
||||
|
@ -26333,6 +26481,18 @@ packages:
|
|||
engines: { node: '>=0.10.0' }
|
||||
dev: true
|
||||
|
||||
/slice-ansi/3.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==,
|
||||
}
|
||||
engines: { node: '>=8' }
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
astral-regex: 2.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
dev: false
|
||||
|
||||
/slice-ansi/4.0.0:
|
||||
resolution:
|
||||
{
|
||||
|
@ -27516,6 +27676,14 @@ packages:
|
|||
engines: { node: '>=8' }
|
||||
dev: true
|
||||
|
||||
/term-size/3.0.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-YfE8KwjrumviCxmeOS1r1hAwqUcd/AnhrG/Pol/Gry91EyUCS+jQH0qFUZOUkpGQ5rXsKMK6S6kjne53ytYS/w==,
|
||||
}
|
||||
engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
|
||||
dev: false
|
||||
|
||||
/terminal-link/2.1.1:
|
||||
resolution:
|
||||
{
|
||||
|
@ -29010,7 +29178,10 @@ packages:
|
|||
dev: true
|
||||
|
||||
/utils-merge/1.0.1:
|
||||
resolution: { integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= }
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==,
|
||||
}
|
||||
engines: { node: '>= 0.4.0' }
|
||||
dev: true
|
||||
|
||||
|
@ -29326,6 +29497,28 @@ packages:
|
|||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
/webpack-bundle-analyzer/4.6.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-oKz9Oz9j3rUciLNfpGFjOb49/jEpXNmWdVH8Ls//zNcnLlQdTGXQQMsBbb/gR7Zl8WNLxVCq+0Hqbx3zv6twBw==,
|
||||
}
|
||||
engines: { node: '>= 10.13.0' }
|
||||
hasBin: true
|
||||
dependencies:
|
||||
acorn: 8.8.0
|
||||
acorn-walk: 8.0.0
|
||||
chalk: 4.1.2
|
||||
commander: 7.2.0
|
||||
gzip-size: 6.0.0
|
||||
lodash: 4.17.21
|
||||
opener: 1.5.2
|
||||
sirv: 1.0.10
|
||||
ws: 7.5.3
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/webpack-sources/1.4.3:
|
||||
resolution:
|
||||
{
|
||||
|
@ -29344,6 +29537,13 @@ packages:
|
|||
engines: { node: '>=10.13.0' }
|
||||
dev: true
|
||||
|
||||
/webpack-stats-plugin/1.1.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-D0meHk1WYryUbuCnWJuomJFAYvqs0rxv/JFu1XJT1YYpczdgnP1/vz+u/5Z31jrTxT6dJSxCg+TuKTgjhoZS6g==,
|
||||
}
|
||||
dev: false
|
||||
|
||||
/webpack/5.74.0:
|
||||
resolution:
|
||||
{
|
||||
|
@ -29614,6 +29814,18 @@ packages:
|
|||
strip-ansi: 5.2.0
|
||||
dev: true
|
||||
|
||||
/wrap-ansi/6.2.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==,
|
||||
}
|
||||
engines: { node: '>=8' }
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
dev: false
|
||||
|
||||
/wrap-ansi/7.0.0:
|
||||
resolution:
|
||||
{
|
||||
|
@ -29624,7 +29836,6 @@ packages:
|
|||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
dev: true
|
||||
|
||||
/wrappy/1.0.2:
|
||||
resolution:
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
packages:
|
||||
- 'packages/*'
|
||||
- 'bench/vercel/'
|
||||
|
|
Loading…
Reference in a new issue