Add initial vercel-plugin-middleware (#6892)

* Add initial `vercel-plugin-middleware`

* Ignore `entries.js` from ESLint

* Add `runDevMiddleware()` stub

* Add test

* Add support for "_middleware.{js,ts}" to `vercel dev` (#6880)

* Add websandbox from next.js codebase.

* Use node-fetch instead of next's polyfilled fetch.

* Handle middleware rewrites.

* Add response, headers, and request to websandbox context.

* Move websandbox dependency to middleware plugin.

* Add integration tests, update websandbox to support ts files and json imports.

* commit yarn.lock changes after rebasing

* Clean up left over console.logs, fix some tsc issues, and rebase issue.

* Fix failing test and eslint.

* Fix middleware test on windows.

* [examples] Update Vercel Next.js example template to 12.0.1 (#6905)

* Mark the Plugins as external to CLI's ncc build

* [cli] Improve tracing in vc build (#6898)

* [cli] Fix tracing for `vc build`

* Ignore object when there are no changes

* Make Next < 12 work with FS API w/ nft

* Update packages/cli/src/commands/build.ts

Co-authored-by: Nathan Rajlich <n@n8.io>

* Document how Next.js processing works in build

* [cli] Fix static assets (#6906)

* Make sure output path is .next

* Fix up require-server-files for processing

* Fix typo

* Move static

* Update static rename

Co-authored-by: Andy Bitz <artzbitz@gmail.com>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>

* Publish Canary

 - vercel@23.1.3-canary.17
 - @vercel/client@10.2.3-canary.15
 - @vercel/static-config@0.0.1-canary.0

* [cli] Ignore `.env` and `.gitignore` in "vc build" (#6910)

* Publish Canary

 - vercel@23.1.3-canary.18

* Pass workPath to plugins.

The new plugin architecture doesn't pass a full BuildOptions object, previous
to this commit it wasn't passing any options at all. I've added workingPath to
support running dev/build from directories other than the project root.

* Remove error state when package.json exists, but no build script

This allows vercel build to continue working for projects that are not using
frameworks, but use package.json to manage dependencies.

* Fix types, pull in middleware header fix from next.js

Next js PR w/ the header fix:
https://github.com/vercel/next.js/pull/30560

* Fix missing entries file for vc build.

* Update call signature of middleware when using vc build.

Co-authored-by: Drew Bredvick <dbredvick@gmail.com>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Jared Palmer <jared@jaredpalmer.com>
Co-authored-by: Andy Bitz <artzbitz@gmail.com>
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>

Co-authored-by: Gary Borton <gdborton@gmail.com>
Co-authored-by: Drew Bredvick <dbredvick@gmail.com>
Co-authored-by: Jared Palmer <jared@jaredpalmer.com>
Co-authored-by: Andy Bitz <artzbitz@gmail.com>
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>
This commit is contained in:
Nathan Rajlich
2021-10-29 17:15:20 -07:00
committed by GitHub
parent df9a4afa5c
commit 44868d79b6
40 changed files with 2585 additions and 137 deletions

View File

@@ -1,11 +1,10 @@
node_modules
dist
examples
packages/build-utils/test/fixtures
packages/*/test/fixtures
packages/cli/@types
packages/cli/download
packages/cli/dist
packages/cli/test/fixtures
packages/cli/test/dev/fixtures
packages/cli/bin
packages/cli/link
@@ -13,7 +12,6 @@ packages/cli/src/util/dev/templates/*.ts
packages/client/tests/fixtures
packages/client/lib
packages/node/src/bridge.ts
packages/node/test/fixtures
packages/node-bridge/bridge.js
packages/node-bridge/launcher.js
packages/static-config/test/fixtures
packages/middleware/src/entries.js

View File

@@ -49,7 +49,16 @@ async function main() {
// Do the initial `ncc` build
console.log();
const args = ['ncc', 'build', '--external', 'update-notifier'];
const args = [
'ncc',
'build',
'--external',
'update-notifier',
'--external',
'vercel-plugin-node',
'--external',
'vercel-plugin-middleware',
];
if (isDev) {
args.push('--source-map');
}

View File

@@ -268,10 +268,10 @@ export default async function main(client: Client) {
// Clean the output directory
fs.removeSync(join(cwd, OUTPUT_DIR));
let result: boolean;
if (typeof buildState.buildCommand === 'string') {
client.output.log(`Running Build Command: ${cmd(buildState.buildCommand)}`);
result = await execCommand(buildState.buildCommand, {
await execCommand(buildState.buildCommand, {
...spawnOpts,
// Yarn v2 PnP mode may be activated, so force
// "node-modules" linker style
@@ -282,24 +282,12 @@ export default async function main(client: Client) {
cwd: cwd,
});
} else if (fs.existsSync(join(cwd, 'package.json'))) {
result = await runPackageJsonScript(
await runPackageJsonScript(
client,
cwd,
['vercel-build', 'now-build', 'build'],
spawnOpts
);
} else {
// no package.json exists and no build command present
result = true;
}
if (!result) {
client.output.error(
`Missing required "${cmd(
buildState.buildCommand || 'vercel-build' || 'build'
)}" script in ${param(cwd)}"\n`
);
return 1;
}
if (!fs.existsSync(join(cwd, OUTPUT_DIR))) {
@@ -550,7 +538,9 @@ export default async function main(client: Client) {
console.log = (...args: any[]) => prefixedLog(prefix, args, origLog);
console.error = (...args: any[]) =>
prefixedLog(prefix, args, origErr);
await plugin.build();
await plugin.build({
workPath: cwd,
});
client.output.debug(
`Completed ${fullName} ${chalk.dim(`${pluginStamp()}`)}`
);

View File

@@ -22,6 +22,8 @@ import deepEqual from 'fast-deep-equal';
import which from 'which';
import npa from 'npm-package-arg';
import { runDevMiddleware } from 'vercel-plugin-middleware';
import { getVercelIgnore, fileNameSymbol } from '@vercel/client';
import {
getTransformedRoutes,
@@ -1416,6 +1418,26 @@ export default class DevServer {
let prevUrl = req.url;
let prevHeaders: HttpHeadersConfig = {};
const middlewareResult = await runDevMiddleware(req, res, this.cwd);
if (middlewareResult) {
if (middlewareResult.finished) {
return;
}
if (middlewareResult.pathname) {
const origUrl = url.parse(req.url || '/', true);
origUrl.pathname = middlewareResult.pathname;
prevUrl = url.format(origUrl);
}
if (middlewareResult.query && prevUrl) {
const origUrl = url.parse(req.url || '/', true);
delete origUrl.search;
Object.assign(origUrl.query, middlewareResult.query);
prevUrl = url.format(origUrl);
}
}
for (const phase of phases) {
statusCode = undefined;

View File

@@ -0,0 +1,5 @@
import { response } from './response';
export default () => {
return new Response(response);
};

View File

@@ -0,0 +1 @@
export const response = 'response';

View File

@@ -0,0 +1,10 @@
import response from './response.json';
export default function () {
return new Response(JSON.stringify(response), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
}

View File

@@ -0,0 +1 @@
not hello world

View File

@@ -0,0 +1,3 @@
{
"text": "hello world"
}

View File

@@ -335,4 +335,32 @@ describe('DevServer', () => {
expect(body).toEqual('The page could not be found.\n\nNOT_FOUND\n');
})
);
it(
'should support edge middleware',
testFixture('edge-middleware', async server => {
const response = await fetch(`${server.address}/index.html`);
const body = await response.json();
expect(body).toEqual(
JSON.parse(
fs.readFileSync(
path.join(
__dirname,
'../../fixtures/unit/edge-middleware/response.json'
),
'utf8'
)
)
);
})
);
it(
'should work with middleware written in typescript',
testFixture('edge-middleware-ts', async server => {
const response = await fetch(`${server.address}/index.html`);
const body = await response.text();
expect(body).toStrictEqual('response');
})
);
});

View File

@@ -0,0 +1,2 @@
entries.js
dist

2
packages/middleware/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/dist
/test/fixtures/*/.output

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env node
const fs = require('fs-extra');
const execa = require('execa');
const { join } = require('path');
async function main() {
const outDir = join(__dirname, 'dist');
// Start fresh
await fs.remove(outDir);
await execa('tsc', [], {
stdio: 'inherit',
});
await fs.copyFile(
join(__dirname, 'src/entries.js'),
join(outDir, 'entries.js')
);
}
main().catch(err => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,59 @@
{
"name": "vercel-plugin-middleware",
"version": "0.0.0-canary.3",
"license": "MIT",
"main": "./dist/index",
"homepage": "",
"repository": {
"type": "git",
"url": "https://github.com/vercel/vercel.git",
"directory": "packages/middleware"
},
"scripts": {
"build": "node build",
"test-unit": "jest",
"prepublishOnly": "node build"
},
"files": [
"dist"
],
"dependencies": {
"@peculiar/webcrypto": "1.2.0",
"cookie": "0.4.1",
"esbuild": "0.13.10",
"formdata-node": "4.3.1",
"globby": "9",
"http-proxy": "1.18.1",
"node-fetch": "^2",
"querystring": "0.2.1",
"ua-parser-js": "1.0.2",
"url": "0.11.0",
"uuid": "8.3.2",
"web-streams-polyfill": "3.1.1"
},
"devDependencies": {
"@types/cookie": "0.4.1",
"@types/glob": "7.2.0",
"@types/http-proxy": "1.17.7",
"@types/jest": "27.0.2",
"@types/node": "16.11.6",
"@types/node-fetch": "^2",
"@types/ua-parser-js": "0.7.36",
"@types/uuid": "8.3.1",
"@vercel/ncc": "0.24.0"
},
"jest": {
"preset": "ts-jest",
"globals": {
"ts-jest": {
"diagnostics": false,
"isolatedModules": true
}
},
"verbose": false,
"testEnvironment": "node",
"testMatch": [
"<rootDir>/test/**/*.test.ts"
]
}
}

View File

@@ -0,0 +1,15 @@
import * as middleware from './_middleware';
_ENTRIES = typeof _ENTRIES === 'undefined' ? {} : _ENTRIES;
_ENTRIES['middleware_pages/_middleware'] = {
default: async function (ev) {
const result = await middleware.default(ev.request, ev);
if (result instanceof Response) {
return {
promise: Promise.resolve(),
waitUntil: Promise.resolve(),
response: result,
};
}
return result;
},
};

View File

@@ -0,0 +1,335 @@
import globby from 'globby';
import { extname, join, basename } from 'path';
import * as esbuild from 'esbuild';
import { promises as fsp } from 'fs';
import { IncomingMessage, ServerResponse } from 'http';
import Proxy from 'http-proxy';
import { run } from './websandbox';
import type { FetchEventResult } from './websandbox/types';
import { ParsedUrlQuery, stringify as stringifyQs } from 'querystring';
import {
format as formatUrl,
parse as parseUrl,
UrlWithParsedQuery,
} from 'url';
import { toNodeHeaders } from './websandbox/utils';
const SUPPORTED_EXTENSIONS = ['.js', '.ts'];
// File name of the `entries.js` file that gets copied into the
// project directory. Use a name that is unlikely to conflict.
const ENTRIES_NAME = '___vc_entries.js';
async function getMiddlewareFile(workingDirectory: string) {
// Only the root-level `_middleware.*` files are considered.
// For more granular routing, the Project's Framework (i.e. Next.js)
// middleware support should be used.
const middlewareFiles = await globby(join(workingDirectory, '_middleware.*'));
if (middlewareFiles.length === 0) {
// No middleware file at the root of the project, so bail...
return;
}
if (middlewareFiles.length > 1) {
throw new Error(
`Only one middleware file is allowed. Found: ${middlewareFiles.join(
', '
)}`
);
}
const ext = extname(middlewareFiles[0]);
if (!SUPPORTED_EXTENSIONS.includes(ext)) {
throw new Error(`Unsupported file type: ${ext}`);
}
return middlewareFiles[0];
}
export async function build({ workPath }: { workPath: string }) {
const entriesPath = join(workPath, ENTRIES_NAME);
const middlewareFile = await getMiddlewareFile(workPath);
if (!middlewareFile) return;
console.log('Compiling middleware file: %j', middlewareFile);
// Create `_ENTRIES` wrapper
await fsp.copyFile(join(__dirname, 'entries.js'), entriesPath);
// Build
try {
await esbuild.build({
entryPoints: [entriesPath],
bundle: true,
absWorkingDir: workPath,
outfile: join(workPath, '.output/server/pages/_middleware.js'),
});
} finally {
await fsp.unlink(entriesPath);
}
// Write middleware manifest
const middlewareManifest = {
version: 1,
sortedMiddleware: ['/'],
middleware: {
'/': {
env: [],
files: ['server/pages/_middleware.js'],
name: 'pages/_middleware',
page: '/',
regexp: '^/.*$',
},
},
};
const middlewareManifestData = JSON.stringify(middlewareManifest, null, 2);
const middlewareManifestPath = join(
workPath,
'.output/server/middleware-manifest.json'
);
await fsp.writeFile(middlewareManifestPath, middlewareManifestData);
}
const stringifyQuery = (req: IncomingMessage, query: ParsedUrlQuery) => {
const initialQueryValues = Object.values((req as any).__NEXT_INIT_QUERY);
return stringifyQs(query, undefined, undefined, {
encodeURIComponent(value: any) {
if (initialQueryValues.some(val => val === value)) {
return encodeURIComponent(value);
}
return value;
},
});
};
// eslint-disable-next-line
async function runMiddlewareCatchAll(
req: IncomingMessage,
res: ServerResponse,
requestId: string,
name: string,
path: string
) {
let result: FetchEventResult | null = null;
const parsedUrl = parseUrl(req.url!, true);
try {
result = await runMiddleware({
request: req,
response: res,
name: name,
path,
requestId: requestId,
parsedUrl,
parsed: parseUrl(req.url!, true),
});
} catch (err) {
// if (isError(err) && err.code === 'ENOENT') {
// await this.render404(req, res, parsed)
// return { finished: true }
// }
// const error = isError(err) ? err : new Error(err + '')
// console.error(error)
// TODO - pretty sure we'll need this.
// res.statusCode = 500;
// this.sendError(req, res, requestId, 'error in middleware', 500);
return { finished: true };
}
if (result === null) {
return { finished: true };
}
if (
!result.response.headers.has('x-middleware-rewrite') &&
!result.response.headers.has('x-middleware-next') &&
!result.response.headers.has('Location')
) {
result.response.headers.set('x-middleware-refresh', '1');
}
result.response.headers.delete('x-middleware-next');
for (const [key, value] of Object.entries(
toNodeHeaders(result.response.headers)
)) {
if (key !== 'content-encoding' && value !== undefined) {
res.setHeader(key, value);
}
}
const preflight =
req.method === 'HEAD' && req.headers['x-middleware-preflight'];
if (preflight) {
res.writeHead(200);
res.end();
return {
finished: true,
};
}
res.statusCode = result.response.status;
res.statusMessage = result.response.statusText;
const location = result.response.headers.get('Location');
if (location) {
res.statusCode = result.response.status;
if (res.statusCode === 308) {
res.setHeader('Refresh', `0;url=${location}`);
}
res.end();
return {
finished: true,
};
}
if (result.response.headers.has('x-middleware-rewrite')) {
const rewrite = result.response.headers.get('x-middleware-rewrite')!;
const rewriteParsed = parseUrl(rewrite, true);
if (rewriteParsed.protocol) {
return proxyRequest(req, res, rewriteParsed);
}
(req as any)._nextRewroteUrl = rewrite;
(req as any)._nextDidRewrite = (req as any)._nextRewroteUrl !== req.url;
return {
finished: false,
pathname: rewriteParsed.pathname,
query: {
...parsedUrl.query,
...rewriteParsed.query,
},
};
}
if (result.response.headers.has('x-middleware-refresh')) {
res.writeHead(result.response.status);
if (result.response.body instanceof Buffer) {
res.write(result.response.body);
} else {
//@ts-ignore
for await (const chunk of result.response.body || []) {
res.write(chunk);
}
}
res.end();
return {
finished: true,
};
}
return {
finished: false,
};
}
const proxyRequest = async (
req: IncomingMessage,
res: ServerResponse,
parsedUrl: UrlWithParsedQuery
) => {
const { query } = parsedUrl;
delete (parsedUrl as any).query;
parsedUrl.search = stringifyQuery(req, query);
const target = formatUrl(parsedUrl);
const proxy = new Proxy({
target,
changeOrigin: true,
ignorePath: true,
xfwd: true,
proxyTimeout: 30_000, // limit proxying to 30 seconds
});
await new Promise((proxyResolve, proxyReject) => {
let finished = false;
proxy.on('proxyReq', (proxyReq: any) => {
proxyReq.on('close', () => {
if (!finished) {
finished = true;
proxyResolve(true);
}
});
});
proxy.on('error', (err: any) => {
if (!finished) {
finished = true;
proxyReject(err);
}
});
proxy.web(req, res);
});
return {
finished: true,
};
};
async function runMiddleware(params: {
request: IncomingMessage;
response: ServerResponse;
parsedUrl: UrlWithParsedQuery;
parsed: UrlWithParsedQuery;
requestId: string;
name: string;
path: string;
}): Promise<FetchEventResult | null> {
const page: { name?: string; params?: { [key: string]: string } } = {};
let result: FetchEventResult | null = null;
result = await run({
name: params.name,
path: params.path,
request: {
headers: params.request.headers,
method: params.request.method || 'GET',
url: params.request.url!,
// url: (params.request as any).__NEXT_INIT_URL,
page,
},
});
result.waitUntil.catch((error: any) => {
console.error(`Uncaught: middleware waitUntil errored`, error);
});
// TODO - is this needed?
// if (!result) {
// this.send404(params.request, params.response, params.requestId);
// }
return result;
}
// Should run the middleware in the `vm` sandbox and return the result
// back to `vercel dev`. If no middleware file exists then this function
// should return `finished: false` (very quickly, since this is being
// invoked for every HTTP request!).
export async function runDevMiddleware(
req: IncomingMessage,
res: ServerResponse,
workingDirectory: string
): ReturnType<typeof runMiddlewareCatchAll> {
const middlewareFile = await getMiddlewareFile(workingDirectory);
if (!middlewareFile) {
return {
finished: false,
};
}
return runMiddlewareCatchAll(
req,
res,
'',
basename(middlewareFile),
middlewareFile
);
}

View File

@@ -0,0 +1,65 @@
import type { RequestData, FetchEventResult } from './types';
import { DeprecationError } from './error';
import { fromNodeHeaders } from './utils';
import { NextFetchEvent } from './spec-extension/fetch-event';
import { NextRequest, RequestInit } from './spec-extension/request';
import { SpecResponse } from './spec-extension/response';
import { waitUntilSymbol } from './spec-compliant/fetch-event';
import { Response } from 'node-fetch';
export async function adapter(params: {
handler: (request: NextRequest, event: NextFetchEvent) => Promise<Response>;
page: string;
request: RequestData;
}): Promise<FetchEventResult> {
const url = params.request.url.startsWith('/')
? `https://${params.request.headers.host}${params.request.url}`
: params.request.url;
const request = new NextRequestHint({
page: params.page,
input: url,
init: {
geo: params.request.geo,
//@ts-ignore
headers: fromNodeHeaders(params.request.headers),
ip: params.request.ip,
method: params.request.method,
page: params.request.page,
},
});
const event = new NextFetchEvent({ request, page: params.page });
const original = await params.handler(request, event);
return {
response: original || SpecResponse.next(),
waitUntil: Promise.all(event[waitUntilSymbol]),
};
}
class NextRequestHint extends NextRequest {
sourcePage: string;
constructor(params: {
init: RequestInit;
input: Request | string;
page: string;
}) {
//@ts-ignore
super(params.input, params.init);
this.sourcePage = params.page;
}
get request() {
throw new DeprecationError({ page: this.sourcePage });
}
respondWith() {
throw new DeprecationError({ page: this.sourcePage });
}
waitUntil() {
throw new DeprecationError({ page: this.sourcePage });
}
}

View File

@@ -0,0 +1,12 @@
export class DeprecationError extends Error {
constructor({ page }: { page: string }) {
super(`The middleware "${page}" accepts an async API directly with the form:
export function middleware(request, event) {
return new Response("Hello " + request.url)
}
Read more: https://nextjs.org/docs/messages/middleware-new-signature
`);
}
}

View File

@@ -0,0 +1,76 @@
import { isBlob } from './is';
import { streamToIterator } from './utils';
const carriage = '\r\n';
const dashes = '--';
const carriageLength = 2;
function escape(str: string) {
return str.replace(/"/g, '\\"');
}
function getFooter(boundary: string) {
return `${dashes}${boundary}${dashes}${carriage.repeat(2)}`;
}
function getHeader(boundary: string, name: string, field: FormDataEntryValue) {
let header = '';
header += `${dashes}${boundary}${carriage}`;
header += `Content-Disposition: form-data; name="${escape(name)}"`;
if (isBlob(field)) {
header += `; filename="${escape(field.name)}"${carriage}`;
header += `Content-Type: ${field.type || 'application/octet-stream'}`;
}
return `${header}${carriage.repeat(2)}`;
}
export function getBoundary() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
let str = '';
for (let i = 0; i < array.length; i++) {
str += array[i].toString(16).padStart(2, '0');
}
return str;
}
export async function* formDataIterator(
form: FormData,
boundary: string
): AsyncIterableIterator<Uint8Array> {
const encoder = new TextEncoder();
//@ts-ignore
for (const [name, value] of form) {
yield encoder.encode(getHeader(boundary, name, value));
if (isBlob(value)) {
// @ts-ignore /shrug
const stream: ReadableStream<Uint8Array> = value.stream();
yield* streamToIterator(stream);
} else {
yield encoder.encode(value);
}
yield encoder.encode(carriage);
}
yield encoder.encode(getFooter(boundary));
}
export function getFormDataLength(form: FormData, boundary: string) {
let length = 0;
//@ts-ignore
for (const [name, value] of form) {
length += Buffer.byteLength(getHeader(boundary, name, value));
length += isBlob(value) ? value.size : Buffer.byteLength(String(value));
length += carriageLength;
}
length += Buffer.byteLength(getFooter(boundary));
return length;
}

View File

@@ -0,0 +1 @@
export * from './sandbox/sandbox';

View File

@@ -0,0 +1,80 @@
/**
* The ArrayBuffer object is used to represent a generic, fixed-length raw
* binary data buffer. It is an array of bytes, often referred to in other
* languages as a "byte array". You cannot directly manipulate the contents of
* an ArrayBuffer; instead, you create one of the typed array objects or a
* DataView object which represents the buffer in a specific format, and use
* that to read and write the contents of the buffer.
*/
export function isArrayBuffer(value: any): value is ArrayBuffer {
return Object.prototype.isPrototypeOf.call(ArrayBuffer, value);
}
/**
* ArrayBufferView is a helper type representing any of the following JS
* TypedArray types which correspond to the list below. It is checked by duck
* typing the provided object.
*/
export function isArrayBufferView(value: any): value is ArrayBufferView {
return ArrayBuffer.isView(value);
}
/**
* The DataView view provides a low-level interface for reading and writing
* multiple number types in a binary ArrayBuffer, without having to care about
* the platform's endianness.
*/
export function isDataView(value: any): value is DataView {
return Object.prototype.isPrototypeOf.call(DataView, value);
}
/**
* The URLSearchParams interface defines utility methods to work with the
* query string of a URL.
*/
export function isURLSearchParams(value: any): value is URLSearchParams {
return Object.prototype.isPrototypeOf.call(URLSearchParams, value);
}
/**
* The Blob object represents a blob, which is a file-like object of immutable,
* raw data; they can be read as text or binary data. Blobs can represent data
* that isn't necessarily in a JavaScript-native format.
*/
export function isBlob(value: any): value is Blob {
return Object.prototype.isPrototypeOf.call(Blob, value);
}
/**
* The FormData interface provides a way to easily construct a set of key/value
* pairs representing form fields and their values, which can then be easily
* sent using the XMLHttpRequest.send() method. It uses the same format a
* form would use if the encoding type were set to "multipart/form-data".
*/
export function isFormData(value: any): value is FormData {
return Object.prototype.isPrototypeOf.call(FormData, value);
}
/**
* The ReadableStream interface of the Streams API represents a readable stream
* of byte data. Because we want to allow alternative implementations we also
* duck type here.
*/
export function isReadableStream(value: any): value is ReadableStream {
return (
value &&
(Object.prototype.isPrototypeOf.call(ReadableStream, value) ||
(value.constructor.name === 'ReadableStream' && 'getReader' in value))
);
}
/**
* Checks in an object implements an Iterable interface
*/
export function isIterable(object: any): object is Iterable<unknown> {
return (
object &&
Symbol.iterator in object &&
typeof object[Symbol.iterator] === 'function'
);
}

View File

@@ -0,0 +1,127 @@
import { Crypto as WebCrypto } from '@peculiar/webcrypto';
import { TransformStream } from 'web-streams-polyfill';
import { v4 as uuid } from 'uuid';
import crypto from 'crypto';
export function atob(b64Encoded: string) {
return Buffer.from(b64Encoded, 'base64').toString('binary');
}
export function btoa(str: string) {
return Buffer.from(str, 'binary').toString('base64');
}
class TextEncoderRuntime {
encoder: TextEncoder;
constructor() {
this.encoder = new TextEncoder();
}
get encoding() {
return this.encoder.encoding;
}
public encode(input: string) {
return this.encoder.encode(input);
}
}
class TextDecoderRuntime {
decoder: TextDecoder;
constructor() {
this.decoder = new TextDecoder();
}
get encoding() {
return this.decoder.encoding;
}
get fatal() {
return this.decoder.fatal;
}
get ignoreBOM() {
return this.decoder.ignoreBOM;
}
public decode(input: BufferSource, options?: TextDecodeOptions) {
return this.decoder.decode(input, options);
}
}
export { TextDecoderRuntime as TextDecoder };
export { TextEncoderRuntime as TextEncoder };
export class Crypto extends WebCrypto {
// @ts-ignore Remove once types are updated and we deprecate node 12
randomUUID = crypto.randomUUID || uuid;
}
export class ReadableStream<T> {
constructor(opts: UnderlyingSource = {}) {
let closed = false;
let pullPromise: any;
let transformController: TransformStreamDefaultController;
const { readable, writable } = new TransformStream(
{
start: (controller: TransformStreamDefaultController) => {
transformController = controller;
},
},
undefined,
{
highWaterMark: 1,
}
);
const writer = writable.getWriter();
const encoder = new TextEncoder();
const controller: ReadableStreamController<T> = {
get desiredSize() {
return transformController.desiredSize;
},
close: () => {
if (!closed) {
closed = true;
writer.close();
}
},
enqueue: (chunk: T) => {
writer.write(typeof chunk === 'string' ? encoder.encode(chunk) : chunk);
pull();
},
error: (reason: any) => {
transformController.error(reason);
},
};
const pull = () => {
if (opts.pull) {
if (!pullPromise) {
pullPromise = Promise.resolve().then(() => {
pullPromise = 0;
opts.pull!(controller);
});
}
}
};
if (opts.start) {
opts.start(controller);
}
if (opts.cancel) {
readable.cancel = (reason: any) => {
opts.cancel!(reason);
return readable.cancel(reason);
};
}
pull();
return readable;
}
}

View File

@@ -0,0 +1,226 @@
import type { RequestData, FetchEventResult, NodeHeaders } from '../types';
import { Blob, File, FormData } from 'formdata-node';
import { dirname, extname, resolve } from 'path';
import { readFileSync } from 'fs';
import { TransformStream } from 'web-streams-polyfill';
import * as polyfills from './polyfills';
import cookie from 'cookie';
import vm from 'vm';
import fetch, {
Headers,
RequestInit,
Response,
Request,
RequestInfo,
} from 'node-fetch';
import { adapter } from '../adapter';
import * as esbuild from 'esbuild';
import m from 'module';
interface URLLike {
href: string;
}
let cache:
| {
context: { [key: string]: any };
paths: Map<string, string>;
require: Map<string, any>;
sandbox: vm.Context;
}
| undefined;
const WEBPACK_HASH_REGEX =
/__webpack_require__\.h = function\(\) \{ return "[0-9a-f]+"; \}/g;
/**
* The cache is cleared when a path is cached and the content has changed. The
* hack ignores changes than only change the compilation hash. Instead it is
* probably better to disable HMR for middleware entries.
*/
export function clearSandboxCache(path: string, content: Buffer | string) {
const prev = cache?.paths.get(path)?.replace(WEBPACK_HASH_REGEX, '');
if (prev === undefined) return;
if (prev === content.toString().replace(WEBPACK_HASH_REGEX, '')) return;
cache = undefined;
}
export async function run(params: {
name: string;
path: string;
request: RequestData;
}): Promise<FetchEventResult> {
if (cache === undefined) {
const context: { [key: string]: any } = {
atob: polyfills.atob,
Blob,
btoa: polyfills.btoa,
clearInterval,
clearTimeout,
console: {
assert: console.assert.bind(console),
error: console.error.bind(console),
info: console.info.bind(console),
log: console.log.bind(console),
time: console.time.bind(console),
timeEnd: console.timeEnd.bind(console),
timeLog: console.timeLog.bind(console),
warn: console.warn.bind(console),
},
Crypto: polyfills.Crypto,
crypto: new polyfills.Crypto(),
Response,
Headers,
Request,
fetch: (input: RequestInfo, init: RequestInit = {}) => {
const url = getFetchURL(input, params.request.headers);
init.headers = getFetchHeaders(params.name, init);
if (isRequestLike(input)) {
return fetch(url, {
...init,
headers: {
...Object.fromEntries(input.headers),
...Object.fromEntries(init.headers),
},
});
}
return fetch(url, init);
},
File,
FormData,
process: { env: { ...process.env } },
ReadableStream: polyfills.ReadableStream,
setInterval,
setTimeout,
TextDecoder: polyfills.TextDecoder,
TextEncoder: polyfills.TextEncoder,
TransformStream,
URL,
URLSearchParams,
};
context.self = context;
cache = {
context,
require: new Map<string, any>([
[require.resolve('cookie'), { exports: cookie }],
]),
paths: new Map<string, string>(),
sandbox: vm.createContext(context),
};
}
try {
const content = readFileSync(params.path, 'utf-8');
const esBuildResult = esbuild.transformSync(content, {
format: 'cjs',
});
const x = vm.runInNewContext(m.wrap(esBuildResult.code), cache.sandbox, {
filename: params.path,
});
const module = {
exports: {},
loaded: false,
id: params.path,
};
x(
module.exports,
sandboxRequire.bind(null, params.path),
module,
dirname(params.path),
params.path
);
const adapterResult = await adapter({
request: params.request,
// @ts-ignore
handler: module.exports.default,
page: params.path,
});
return adapterResult;
} catch (error) {
cache = undefined;
throw error;
}
}
function sandboxRequire(referrer: string, specifier: string) {
const resolved = require.resolve(specifier, {
paths: [resolve(dirname(referrer))],
});
const cached = cache?.require.get(resolved);
if (cached !== undefined) {
return cached.exports;
}
const module = {
exports: {},
loaded: false,
id: resolved,
};
cache?.require.set(resolved, module);
const transformOptions: esbuild.TransformOptions = {
format: 'cjs',
};
if (extname(resolved) === '.json') {
transformOptions.loader = 'json';
}
const transformedContent = esbuild.transformSync(
readFileSync(resolved, 'utf-8'),
transformOptions
).code;
const fn = vm.runInContext(
`(function(module,exports,require,__dirname,__filename) {${transformedContent}\n})`,
cache!.sandbox
);
try {
fn(
module,
module.exports,
sandboxRequire.bind(null, resolved),
dirname(resolved),
resolved
);
} finally {
cache?.require.delete(resolved);
}
module.loaded = true;
return module.exports;
}
function getFetchHeaders(middleware: string, init: RequestInit) {
const headers = new Headers(init.headers ?? {});
const prevsub = headers.get(`x-middleware-subrequest`) || '';
const value = prevsub.split(':').concat(middleware).join(':');
headers.set(`x-middleware-subrequest`, value);
headers.set(`user-agent`, `Next.js Middleware`);
return headers;
}
function getFetchURL(input: RequestInfo, headers: NodeHeaders = {}): string {
const initurl = isRequestLike(input)
? input.url
: isURLLike(input)
? input.href
: input;
if (initurl.startsWith('/')) {
const host = headers.host?.toString();
const localhost =
host === '127.0.0.1' ||
host === 'localhost' ||
host?.startsWith('localhost:');
return `${localhost ? 'http' : 'https'}://${host}${initurl}`;
}
return initurl;
}
function isURLLike(obj: unknown): obj is URLLike {
return Boolean(obj && typeof obj === 'object' && 'href' in obj);
}
function isRequestLike(obj: unknown): obj is Request {
return Boolean(obj && typeof obj === 'object' && 'url' in obj);
}

View File

@@ -0,0 +1,237 @@
import { formDataIterator, getBoundary } from '../form-data';
import { streamToIterator } from '../utils';
import * as util from '../is';
import { URLSearchParams } from 'url';
const INTERNALS = Symbol('internal body');
abstract class BaseBody implements Body {
abstract headers: Headers;
[INTERNALS]: {
bodyInit?: BodyInit;
boundary?: string;
disturbed: boolean;
stream?: ReadableStream<Uint8Array> | null;
};
constructor(bodyInit?: BodyInit) {
this[INTERNALS] = {
bodyInit: bodyInit,
disturbed: false,
};
if (util.isFormData(bodyInit)) {
this[INTERNALS].boundary = getBoundary();
}
}
get body(): ReadableStream<Uint8Array> | null {
const body = this[INTERNALS].bodyInit;
if (!body) {
return null;
}
// eslint-disable-next-line
const that = this;
if (!this[INTERNALS].stream) {
const readable = new ReadableStream({
async start(controller) {
if (typeof body === 'string') {
const encoder = new TextEncoder();
controller.enqueue(encoder.encode(body));
} else if (util.isBlob(body)) {
const buffer = await body.arrayBuffer();
controller.enqueue(new Uint8Array(buffer));
} else if (util.isDataView(body)) {
controller.enqueue(body);
} else if (util.isArrayBuffer(body)) {
controller.enqueue(body);
} else if (util.isArrayBufferView(body)) {
controller.enqueue(body);
} else if (util.isURLSearchParams(body)) {
const encoder = new TextEncoder();
controller.enqueue(encoder.encode(body.toString()));
} else if (util.isFormData(body)) {
for await (const chunk of formDataIterator(
body,
that[INTERNALS].boundary!
)) {
controller.enqueue(chunk);
}
} else if (util.isReadableStream(body)) {
for await (const chunk of streamToIterator(body)) {
if (chunk.length) {
controller.enqueue(chunk);
}
}
} else {
const text = Object.prototype.toString.call(body);
const encoder = new TextEncoder();
controller.enqueue(encoder.encode(text));
}
controller.close();
},
});
// Spy on reading chunks to set the stream as disturbed
const getReader = readable.getReader.bind(readable);
readable.getReader = () => {
const reader = getReader();
const read = reader.read.bind(reader);
reader.read = () => {
this[INTERNALS].disturbed = true;
return read();
};
return reader;
};
this[INTERNALS].stream = readable;
}
return this[INTERNALS].stream!;
}
get bodyUsed(): boolean {
return this[INTERNALS].disturbed;
}
_consume() {
if (this[INTERNALS].disturbed) {
return Promise.reject(
new TypeError(
`Body has already been used. It can only be used once. Use tee() first if you need to read it twice.`
)
);
}
this[INTERNALS].disturbed = true;
const body = this.body;
return new Promise<Uint8Array>((resolve, reject) => {
let buffer = new Uint8Array(0);
if (!body) {
return resolve(buffer);
}
const reader = body.getReader();
(function pump() {
reader.read().then(({ value, done }) => {
if (done) {
return resolve(buffer);
} else if (value) {
const merge = new Uint8Array(buffer.length + value.length);
merge.set(buffer);
merge.set(value, buffer.length);
buffer = merge;
}
pump();
}, reject);
})();
});
}
async arrayBuffer() {
const buffer = await this._consume();
const arrayBuffer = new ArrayBuffer(buffer.length);
const view = new Uint8Array(arrayBuffer);
for (let i = 0; i < buffer.length; ++i) {
view[i] = buffer[i];
}
return arrayBuffer;
}
async blob() {
const buffer = await this._consume();
return new Blob([buffer]);
}
async formData() {
const bodyInit = this[INTERNALS].bodyInit;
if (util.isURLSearchParams(bodyInit)) {
const form = new FormData();
for (const [key, value] of bodyInit) {
form.append(key, value);
}
return form;
} else if (util.isFormData(bodyInit)) {
return bodyInit;
} else {
throw new TypeError(
`Unrecognized Content-Type header value. FormData can only parse the following MIME types: multipart/form-data, application/x-www-form-urlencoded.`
);
}
}
async text() {
const decoder = new TextDecoder();
const buffer = await this._consume();
return decoder.decode(buffer);
}
async json() {
const text = await this.text();
try {
return JSON.parse(text);
} catch (err: any) {
throw new TypeError(`invalid json body reason: ${err.message}`);
}
}
}
export { BaseBody as Body };
export type BodyInit =
| null
| string
| Blob
| BufferSource
| FormData
| URLSearchParams
| ReadableStream<Uint8Array>;
export function extractContentType(instance: BaseBody) {
const body = instance[INTERNALS].bodyInit;
if (typeof body === 'string') {
return 'text/plain;charset=UTF-8';
} else if (util.isBlob(body)) {
return body.type;
} else if (util.isDataView(body)) {
return null;
} else if (util.isArrayBuffer(body)) {
return null;
} else if (util.isArrayBufferView(body)) {
return null;
} else if (util.isURLSearchParams(body)) {
return 'application/x-www-form-urlencoded;charset=UTF-8';
} else if (util.isFormData(body)) {
return `multipart/form-data;boundary=${instance[INTERNALS].boundary}`;
} else if (util.isReadableStream(body)) {
return null;
} else {
return 'text/plain;charset=UTF-8';
}
}
export function cloneBody(instance: BaseBody) {
if (instance.bodyUsed) {
throw new Error('cannot clone body after it is used');
}
const body = instance[INTERNALS].bodyInit;
if (util.isReadableStream(body)) {
const [r1, r2] = body.tee();
instance[INTERNALS].bodyInit = r1;
return r2;
}
return body || null;
}
export function getInstanceBody(instance: BaseBody) {
return instance[INTERNALS].bodyInit;
}

View File

@@ -0,0 +1,26 @@
export const responseSymbol = Symbol('response');
export const passThroughSymbol = Symbol('passThrough');
export const waitUntilSymbol = Symbol('waitUntil');
export class FetchEvent {
readonly [waitUntilSymbol]: Promise<any>[] = [];
[responseSymbol]?: Promise<Response>;
[passThroughSymbol] = false;
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor() {}
respondWith(response: Response | Promise<Response>): void {
if (!this[responseSymbol]) {
this[responseSymbol] = Promise.resolve(response);
}
}
passThroughOnException(): void {
this[passThroughSymbol] = true;
}
waitUntil(promise: Promise<any>): void {
this[waitUntilSymbol].push(promise);
}
}

View File

@@ -0,0 +1,238 @@
import { isIterable } from '../is';
const MAP = Symbol('map');
const INTERNAL = Symbol('internal');
const INVALID_TOKEN_REGEX = /[^^_`a-zA-Z\-0-9!#$%&'*+.|~]/;
const INVALID_HEADER_CHAR_REGEX = /[^\t\x20-\x7e\x80-\xff]/;
class BaseHeaders implements Headers {
[MAP]: { [k: string]: string[] } = {};
constructor(init?: HeadersInit) {
if (init instanceof BaseHeaders) {
const rawHeaders = init.raw();
for (const headerName of Object.keys(rawHeaders)) {
for (const value of rawHeaders[headerName]) {
this.append(headerName, value);
}
}
} else if (isIterable(init)) {
const pairs = [];
for (const pair of init) {
if (!isIterable(pair)) {
throw new TypeError('Each header pair must be iterable');
}
pairs.push(Array.from(pair));
}
for (const pair of pairs) {
if (pair.length !== 2) {
throw new TypeError('Each header pair must be a name/value tuple');
}
this.append(pair[0], pair[1]);
}
} else if (typeof init === 'object') {
for (const key of Object.keys(init)) {
// @ts-ignore
this.append(key, init[key]);
}
} else if (init) {
throw new TypeError('Provided initializer must be an object');
}
}
get(name: string) {
const _name = `${name}`;
validateName(_name);
const key = find(this[MAP], _name);
if (key === undefined) {
return null;
}
return this[MAP][key].join(', ');
}
forEach(
callback: (value: string, name: string, parent: BaseHeaders) => void,
thisArg: any = undefined
): void {
let pairs = getHeaders(this);
let i = 0;
while (i < pairs.length) {
const [name, value] = pairs[i];
callback.call(thisArg, value, name, this);
pairs = getHeaders(this);
i++;
}
}
set(name: string, value: string) {
name = `${name}`;
value = `${value}`;
validateName(name);
validateValue(value);
const key = find(this[MAP], name);
this[MAP][key !== undefined ? key : name] = [value];
}
append(name: string, value: string) {
name = `${name}`;
value = `${value}`;
validateName(name);
validateValue(value);
const key = find(this[MAP], name);
if (key !== undefined) {
this[MAP][key].push(value);
} else {
this[MAP][name] = [value];
}
}
has(name: string) {
name = `${name}`;
validateName(name);
return find(this[MAP], name) !== undefined;
}
delete(name: string) {
name = `${name}`;
validateName(name);
const key = find(this[MAP], name);
if (key !== undefined) {
delete this[MAP][key];
}
}
raw() {
return this[MAP];
}
keys() {
return createHeadersIterator(this, 'key');
}
values() {
return createHeadersIterator(this, 'value');
}
entries() {
return createHeadersIterator(this, 'key+value');
}
[Symbol.iterator]() {
return createHeadersIterator(this, 'key+value');
}
}
function createHeadersIterator(
target: BaseHeaders,
kind: 'key' | 'value' | 'key+value'
) {
const iterator = Object.create(HeadersIteratorPrototype);
iterator[INTERNAL] = {
target,
kind,
index: 0,
};
return iterator;
}
function validateName(name: string) {
name = `${name}`;
if (INVALID_TOKEN_REGEX.test(name)) {
throw new TypeError(`${name} is not a legal HTTP header name`);
}
}
function validateValue(value: string) {
value = `${value}`;
if (INVALID_HEADER_CHAR_REGEX.test(value)) {
throw new TypeError(`${value} is not a legal HTTP header value`);
}
}
function find(
map: { [k: string]: string[] },
name: string
): string | undefined {
name = name.toLowerCase();
for (const key in map) {
if (key.toLowerCase() === name) {
return key;
}
}
return undefined;
}
Object.defineProperty(BaseHeaders.prototype, Symbol.toStringTag, {
value: 'Headers',
writable: false,
enumerable: false,
configurable: true,
});
Object.defineProperties(BaseHeaders.prototype, {
append: { enumerable: true },
delete: { enumerable: true },
entries: { enumerable: true },
forEach: { enumerable: true },
get: { enumerable: true },
has: { enumerable: true },
keys: { enumerable: true },
raw: { enumerable: false },
set: { enumerable: true },
values: { enumerable: true },
});
function getHeaders(
headers: BaseHeaders,
kind: 'key' | 'value' | 'key+value' = 'key+value'
) {
const fn =
kind === 'key'
? (key: string) => key.toLowerCase()
: kind === 'value'
? (key: string) => headers[MAP][key].join(', ')
: (key: string) => [key.toLowerCase(), headers[MAP][key].join(', ')];
return Object.keys(headers[MAP])
.sort()
.map(key => fn(key));
}
const HeadersIteratorPrototype = Object.setPrototypeOf(
{
next() {
if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) {
throw new TypeError('Value of `this` is not a HeadersIterator');
}
const { target, kind, index } = this[INTERNAL];
const values = getHeaders(target, kind);
const len = values.length;
if (index >= len) {
return {
value: undefined,
done: true,
};
}
this[INTERNAL].index = index + 1;
return {
value: values[index],
done: false,
};
},
},
Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
);
Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, {
value: 'HeadersIterator',
writable: false,
enumerable: false,
configurable: true,
});
export { BaseHeaders as Headers };

View File

@@ -0,0 +1,124 @@
import { Body, cloneBody, extractContentType, getInstanceBody } from './body';
import { Headers as BaseHeaders } from './headers';
import { notImplemented } from '../utils';
export const INTERNALS = Symbol('internal request');
class BaseRequest extends Body implements Request {
[INTERNALS]: {
credentials: RequestCredentials;
headers: Headers;
method: string;
redirect: RequestRedirect;
url: URL;
};
constructor(input: BaseRequest | string, init: RequestInit = {}) {
const method = init.method?.toUpperCase() ?? 'GET';
if (
(method === 'GET' || method === 'HEAD') &&
(init.body || (input instanceof BaseRequest && getInstanceBody(input)))
) {
throw new TypeError('Request with GET/HEAD method cannot have body');
}
let inputBody: BodyInit | null = null;
if (init.body) {
inputBody = init.body;
} else if (input instanceof BaseRequest && getInstanceBody(input)) {
inputBody = cloneBody(input);
}
super(inputBody);
const headers = new BaseHeaders(
init.headers || getProp(input, 'headers') || {}
);
if (inputBody !== null) {
const contentType = extractContentType(this);
if (contentType !== null && !headers.has('Content-Type')) {
headers.append('Content-Type', contentType);
}
}
this[INTERNALS] = {
credentials:
init.credentials || getProp(input, 'credentials') || 'same-origin',
headers,
method,
redirect: init.redirect || getProp(input, 'redirect') || 'follow',
url: new URL(typeof input === 'string' ? input : input.url),
};
}
get url() {
return this[INTERNALS].url.toString();
}
get credentials() {
return this[INTERNALS].credentials;
}
get method() {
return this[INTERNALS].method;
}
get headers() {
return this[INTERNALS].headers;
}
get redirect() {
return this[INTERNALS].redirect;
}
// @ts-ignore
public clone() {
return new BaseRequest(this);
}
get cache() {
return notImplemented('Request', 'cache');
}
get integrity() {
return notImplemented('Request', 'integrity');
}
get keepalive() {
return notImplemented('Request', 'keepalive');
}
get mode() {
return notImplemented('Request', 'mode');
}
get destination() {
return notImplemented('Request', 'destination');
}
get referrer() {
return notImplemented('Request', 'referrer');
}
get referrerPolicy() {
return notImplemented('Request', 'referrerPolicy');
}
get signal() {
return notImplemented('Request', 'signal');
}
get [Symbol.toStringTag]() {
return 'Request';
}
}
export { BaseRequest as Request };
function getProp<K extends keyof BaseRequest>(
input: BaseRequest | string,
key: K
): BaseRequest[K] | undefined {
return input instanceof BaseRequest ? input[key] : undefined;
}

View File

@@ -0,0 +1,113 @@
import { Body, BodyInit, cloneBody, extractContentType } from './body';
const INTERNALS = Symbol('internal response');
const REDIRECTS = new Set([301, 302, 303, 307, 308]);
class BaseResponse extends Body implements Response {
[INTERNALS]: {
headers: Headers;
status: number;
statusText: string;
type: 'default' | 'error';
url?: URL;
};
constructor(body?: BodyInit | null, init?: ResponseInit) {
super(body);
this[INTERNALS] = {
headers: new Headers(init?.headers),
status: init?.status || 200,
statusText: init?.statusText || '',
type: 'default',
url: init?.url ? new URL(init.url) : undefined,
};
if (this[INTERNALS].status < 200 || this[INTERNALS].status > 599) {
throw new RangeError(
`Responses may only be constructed with status codes in the range 200 to 599, inclusive.`
);
}
if (body !== null && !this[INTERNALS].headers.has('Content-Type')) {
const contentType = extractContentType(this);
if (contentType) {
this[INTERNALS].headers.append('Content-Type', contentType);
}
}
}
static redirect(url: string, status = 302) {
if (!REDIRECTS.has(status)) {
throw new RangeError(
'Failed to execute "redirect" on "response": Invalid status code'
);
}
return new Response(null, {
headers: { Location: url },
status,
});
}
static error() {
const response = new BaseResponse(null, { status: 0, statusText: '' });
response[INTERNALS].type = 'error';
return response;
}
get url() {
return this[INTERNALS].url?.toString() || '';
}
get ok() {
return this[INTERNALS].status >= 200 && this[INTERNALS].status < 300;
}
get status() {
return this[INTERNALS].status;
}
get statusText() {
return this[INTERNALS].statusText;
}
get headers() {
return this[INTERNALS].headers;
}
get redirected() {
return (
this[INTERNALS].status > 299 &&
this[INTERNALS].status < 400 &&
this[INTERNALS].headers.has('Location')
);
}
get type() {
return this[INTERNALS].type;
}
// @ts-ignore
clone() {
return new BaseResponse(cloneBody(this), {
headers: this.headers,
status: this.status,
statusText: this.statusText,
url: this.url,
});
}
get [Symbol.toStringTag]() {
return 'Response';
}
}
export interface ResponseInit {
headers?: HeadersInit;
status?: number;
statusText?: string;
url?: string;
}
export { BaseResponse as Response };

View File

@@ -0,0 +1,26 @@
import { DeprecationError } from '../error';
import { FetchEvent } from '../spec-compliant/fetch-event';
import { NextRequest } from './request';
export class NextFetchEvent extends FetchEvent {
sourcePage: string;
constructor(params: { request: NextRequest; page: string }) {
//@ts-ignore
super(params.request);
this.sourcePage = params.page;
}
// @ts-ignore
get request() {
throw new DeprecationError({
page: this.sourcePage,
});
}
respondWith() {
throw new DeprecationError({
page: this.sourcePage,
});
}
}

View File

@@ -0,0 +1,98 @@
import type { IResult } from 'ua-parser-js';
import cookie from 'cookie';
import parseua from 'ua-parser-js';
import { Request, RequestInit as NodeFetchRequestInit } from 'node-fetch';
export const INTERNALS = Symbol('internal request');
export class NextRequest extends Request {
[INTERNALS]: {
cookieParser(): { [key: string]: string };
geo: { city?: string; country?: string; region?: string };
ip?: string;
page?: { name?: string; params?: { [key: string]: string } };
ua?: IResult | null;
url: URL;
};
constructor(input: Request | string, init: RequestInit = {}) {
//@ts-ignore
super(input, init);
const cookieParser = () => {
const value = this.headers.get('cookie');
return value ? cookie.parse(value) : {};
};
this[INTERNALS] = {
cookieParser,
geo: init.geo || {},
ip: init.ip,
page: init.page,
url: new URL(typeof input === 'string' ? input : input.url),
};
}
public get cookies() {
return this[INTERNALS].cookieParser();
}
public get geo() {
return this[INTERNALS].geo;
}
public get ip() {
return this[INTERNALS].ip;
}
public get preflight() {
return this.headers.get('x-middleware-preflight');
}
public get nextUrl() {
return this[INTERNALS].url;
}
public get page() {
return {
name: this[INTERNALS].page?.name,
params: this[INTERNALS].page?.params,
};
}
public get ua() {
if (typeof this[INTERNALS].ua !== 'undefined') {
return this[INTERNALS].ua || undefined;
}
const uaString = this.headers.get('user-agent');
if (!uaString) {
this[INTERNALS].ua = null;
return this[INTERNALS].ua || undefined;
}
this[INTERNALS].ua = {
...parseua(uaString),
};
return this[INTERNALS].ua;
}
//@ts-ignore
public get url() {
return this[INTERNALS].url.toString();
}
}
export interface RequestInit extends NodeFetchRequestInit {
geo?: {
city?: string;
country?: string;
region?: string;
};
ip?: string;
page?: {
name?: string;
params?: { [key: string]: string };
};
}

View File

@@ -0,0 +1,97 @@
import type { CookieSerializeOptions } from 'cookie';
import cookie from 'cookie';
import { Response, ResponseInit as NodeFetchResponseInit } from 'node-fetch';
const INTERNALS = Symbol('internal response');
const REDIRECTS = new Set([301, 302, 303, 307, 308]);
export class SpecResponse extends Response {
[INTERNALS]: {
cookieParser(): { [key: string]: string };
url?: URL;
};
constructor(body?: BodyInit | null, init: ResponseInit = {}) {
// TODO - why is this failing?
// @ts-ignore
super(body, init);
const cookieParser = () => {
const value = this.headers.get('cookie');
return value ? cookie.parse(value) : {};
};
this[INTERNALS] = {
cookieParser,
url: init.url ? new URL(init.url) : undefined,
};
}
public get cookies() {
return this[INTERNALS].cookieParser();
}
public cookie(
name: string,
value: { [key: string]: any } | string,
opts: CookieSerializeOptions = {}
) {
const val =
typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value);
if (opts.maxAge) {
opts.expires = new Date(Date.now() + opts.maxAge);
opts.maxAge /= 1000;
}
if (opts.path == null) {
opts.path = '/';
}
this.headers.append(
'Set-Cookie',
cookie.serialize(name, String(val), opts)
);
return this;
}
public clearCookie(name: string, opts: CookieSerializeOptions = {}) {
return this.cookie(name, '', { expires: new Date(1), path: '/', ...opts });
}
static redirect(url: string | URL, status = 302) {
if (!REDIRECTS.has(status)) {
throw new RangeError(
'Failed to execute "redirect" on "response": Invalid status code'
);
}
return new SpecResponse(null, {
headers: { Location: typeof url === 'string' ? url : url.toString() },
status,
});
}
static rewrite(destination: string | URL) {
return new SpecResponse(null, {
headers: {
'x-middleware-rewrite':
typeof destination === 'string'
? destination
: destination.toString(),
},
});
}
static next() {
return new SpecResponse(null, {
headers: {
'x-middleware-next': '1',
},
});
}
}
interface ResponseInit extends NodeFetchResponseInit {
url?: string;
}

View File

@@ -0,0 +1,26 @@
import { Response } from 'node-fetch';
export interface NodeHeaders {
[header: string]: string | string[] | undefined;
}
export interface RequestData {
geo?: {
city?: string;
country?: string;
region?: string;
};
headers: NodeHeaders;
ip?: string;
method: string;
page?: {
name?: string;
params?: { [key: string]: string };
};
url: string;
}
export interface FetchEventResult {
response: Response;
waitUntil: Promise<any>;
}

View File

@@ -0,0 +1,124 @@
import type { NodeHeaders } from './types';
import { Headers } from 'node-fetch';
export async function* streamToIterator<T>(
readable: ReadableStream<T>
): AsyncIterableIterator<T> {
const reader = readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
if (value) {
yield value;
}
}
reader.releaseLock();
}
export function notImplemented(name: string, method: string): any {
throw new Error(
`Failed to get the '${method}' property on '${name}': the property is not implemented`
);
}
export function fromNodeHeaders(object: NodeHeaders): Headers {
const headers = new Headers();
for (const [key, value] of Object.entries(object)) {
const values = Array.isArray(value) ? value : [value];
for (const v of values) {
if (v !== undefined) {
headers.append(key, v);
}
}
}
return headers;
}
export function toNodeHeaders(headers?: Headers): NodeHeaders {
const result: NodeHeaders = {};
if (headers) {
for (const [key, value] of headers.entries()) {
result[key] = value;
if (key.toLowerCase() === 'set-cookie') {
result[key] = splitCookiesString(value);
}
}
}
return result;
}
/*
Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
that are within a single set-cookie field-value, such as in the Expires portion.
This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2
Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128
React Native's fetch does this for *every* header, including set-cookie.
Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25
Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
*/
export function splitCookiesString(cookiesString: string) {
const cookiesStrings = [];
let pos = 0;
let start;
let ch;
let lastComma;
let nextStart;
let cookiesSeparatorFound;
function skipWhitespace() {
while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
pos += 1;
}
return pos < cookiesString.length;
}
function notSpecialChar() {
ch = cookiesString.charAt(pos);
return ch !== '=' && ch !== ';' && ch !== ',';
}
while (pos < cookiesString.length) {
start = pos;
cookiesSeparatorFound = false;
while (skipWhitespace()) {
ch = cookiesString.charAt(pos);
if (ch === ',') {
// ',' is a cookie separator if we have later first '=', not ';' or ','
lastComma = pos;
pos += 1;
skipWhitespace();
nextStart = pos;
while (pos < cookiesString.length && notSpecialChar()) {
pos += 1;
}
// currently special character
if (pos < cookiesString.length && cookiesString.charAt(pos) === '=') {
// we found cookies separator
cookiesSeparatorFound = true;
// pos is inside the next cookie, so back up and return it.
pos = nextStart;
cookiesStrings.push(cookiesString.substring(start, lastComma));
start = pos;
} else {
// in param ',' or param separator ';',
// we continue from that comma
pos = lastComma + 1;
}
} else {
pos += 1;
}
}
if (!cookiesSeparatorFound || pos >= cookiesString.length) {
cookiesStrings.push(cookiesString.substring(start, cookiesString.length));
}
}
return cookiesStrings;
}

View File

@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`build() should build simple middleware 1`] = `
Object {
"middleware": Object {
"/": Object {
"env": Array [],
"files": Array [
"server/pages/_middleware.js",
],
"name": "pages/_middleware",
"page": "/",
"regexp": "^/.*$",
},
},
"sortedMiddleware": Array [
"/",
],
"version": 1,
}
`;

24
packages/middleware/test/build.test.ts vendored Normal file
View File

@@ -0,0 +1,24 @@
import { join } from 'path';
import { promises as fsp } from 'fs';
import { build } from '../src';
describe('build()', () => {
it('should build simple middleware', async () => {
const fixture = join(__dirname, 'fixtures/simple');
await build({
workPath: fixture,
});
const middlewareManifest = JSON.parse(
await fsp.readFile(
join(fixture, '.output/server/middleware-manifest.json'),
'utf8'
)
);
expect(middlewareManifest).toMatchSnapshot();
expect(
await fsp.stat(join(fixture, '.output/server/pages/_middleware.js'))
).toBeTruthy();
});
});

View File

@@ -0,0 +1 @@
export default () => new Response('Hi from the edge!');

View File

@@ -0,0 +1,4 @@
{
"extends": "../tsconfig.json",
"include": ["*.test.ts"]
}

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"lib": ["esnext", "dom", "dom.iterable"],
"target": "es2018",
"module": "commonjs",
"outDir": "dist",
"sourceMap": false,
"declaration": true,
"moduleResolution": "node",
"typeRoots": ["./@types", "./node_modules/@types"]
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

1
utils/run.js vendored
View File

@@ -7,6 +7,7 @@ const allPackages = [
'frameworks',
'build-utils',
'static-config',
'middleware',
'client',
'node-bridge',
'node',

412
yarn.lock
View File

@@ -1753,6 +1753,34 @@
dependencies:
"@types/node" ">= 8"
"@peculiar/asn1-schema@^2.0.38":
version "2.0.38"
resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.0.38.tgz#98b6f12daad275ecd6774dfe31fb62f362900412"
integrity sha512-zZ64UpCTm9me15nuCpPgJghSdbEm8atcDQPCyK+bKXjZAQ1735NCZXCSCfbckbQ4MH36Rm9403n/qMq77LFDzQ==
dependencies:
"@types/asn1js" "^2.0.2"
asn1js "^2.1.1"
pvtsutils "^1.2.0"
tslib "^2.3.0"
"@peculiar/json-schema@^1.1.12":
version "1.1.12"
resolved "https://registry.yarnpkg.com/@peculiar/json-schema/-/json-schema-1.1.12.tgz#fe61e85259e3b5ba5ad566cb62ca75b3d3cd5339"
integrity sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==
dependencies:
tslib "^2.0.0"
"@peculiar/webcrypto@1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.2.0.tgz#e1375ba60e0e5e53e67290f917d77bf813ef5ade"
integrity sha512-ln2CvfmTzXSr877zM1+3JTyvbtaDXsoDivvEyeYAzB4RQIM+Pw82gp1nQFp9xM4BylBBrip/R36Gp+WJFCoU3Q==
dependencies:
"@peculiar/asn1-schema" "^2.0.38"
"@peculiar/json-schema" "^1.1.12"
pvtsutils "^1.2.1"
tslib "^2.3.1"
webcrypto-core "^1.3.0"
"@samverschueren/stream-to-observable@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
@@ -1885,6 +1913,11 @@
resolved "https://registry.yarnpkg.com/@types/ansi-regex/-/ansi-regex-4.0.0.tgz#cb20bb66da7700ea9b26f16971f03f0e092eddad"
integrity sha512-r1W316vjsZXn1/csLC4HcCJs6jIHIzksHJd7xx+Dl+PAb0S2Dh9cR8ZsIMEfGmbBtP7JNWlf2KKahSkDP6rg3g==
"@types/asn1js@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/asn1js/-/asn1js-2.0.2.tgz#bb1992291381b5f06e22a829f2ae009267cdf8c5"
integrity sha512-t4YHCgtD+ERvH0FyxvNlYwJ2ezhqw7t+Ygh4urQ7dJER8i185JPv6oIM3ey5YQmGN6Zp9EMbpohkjZi9t3UxwA==
"@types/async-retry@1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@types/async-retry/-/async-retry-1.2.1.tgz#fa9ac165907a8ee78f4924f4e393b656c65b5bb4"
@@ -1987,6 +2020,11 @@
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803"
integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==
"@types/cookie@0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==
"@types/cross-spawn@6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@types/cross-spawn/-/cross-spawn-6.0.0.tgz#320aaf1d1a12979f1b84fe7a5590a7e860bf3a80"
@@ -2093,6 +2131,14 @@
"@types/minimatch" "*"
"@types/node" "*"
"@types/glob@7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==
dependencies:
"@types/minimatch" "*"
"@types/node" "*"
"@types/glob@^7.1.1":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.2.tgz#06ca26521353a545d94a0adc74f38a59d232c987"
@@ -2116,6 +2162,13 @@
"@types/events" "*"
"@types/node" "*"
"@types/http-proxy@1.17.7":
version "1.17.7"
resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.7.tgz#30ea85cc2c868368352a37f0d0d3581e24834c6f"
integrity sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w==
dependencies:
"@types/node" "*"
"@types/inquirer@7.3.1":
version "7.3.1"
resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-7.3.1.tgz#1f231224e7df11ccfaf4cf9acbcc3b935fea292d"
@@ -2261,6 +2314,14 @@
"@types/node" "*"
form-data "^3.0.0"
"@types/node-fetch@^2":
version "2.5.12"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66"
integrity sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node-fetch@^2.1.6", "@types/node-fetch@^2.3.0":
version "2.5.7"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c"
@@ -2294,6 +2355,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.20.tgz#7b693038ce661fe57a7ffa4679440b5e7c5e8b99"
integrity sha512-VAe+DiwpnC/g448uN+/3gRl4th0BTdrR9gSLIOHA+SUQskaYZQDOHG7xmjiE7JUhjbXnbXytf6Ih+/pA6CtMFQ==
"@types/node@16.11.6":
version "16.11.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae"
integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==
"@types/normalize-package-data@^2.4.0":
version "2.4.0"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
@@ -2410,6 +2476,11 @@
resolved "https://registry.yarnpkg.com/@types/title/-/title-3.4.1.tgz#583cd7561291eac4ade4bef0d589d23c27bf71e7"
integrity sha512-GAt+5DJl84n7Qz+/weJteapG8j2csP/5NOg8H4dnaTxuqsK6AcZ4hPYsvvjAjcEVv5l9dfK8rz4pOfpgjlrNqg==
"@types/ua-parser-js@0.7.36":
version "0.7.36"
resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz#9bd0b47f26b5a3151be21ba4ce9f5fa457c5f190"
integrity sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==
"@types/universal-analytics@0.4.2":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@types/universal-analytics/-/universal-analytics-0.4.2.tgz#d21a122a984bf8261eb206bcb7ccb1c0e8353d04"
@@ -2423,6 +2494,11 @@
"@types/configstore" "*"
boxen "^4.2.0"
"@types/uuid@8.3.1":
version "8.3.1"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f"
integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==
"@types/which@1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@types/which/-/which-1.3.2.tgz#9c246fc0c93ded311c8512df2891fb41f6227fdf"
@@ -3055,6 +3131,13 @@ asn1@~0.2.3:
dependencies:
safer-buffer "~2.1.0"
asn1js@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-2.1.1.tgz#bb3896191ebb5fb1caeda73436a6c6e20a2eedff"
integrity sha512-t9u0dU0rJN4ML+uxgN6VM2Z4H5jWIYm0w8LsZLzMJaQsgL3IJNbxHgmbWDvJAwspyHpDFuzUaUFh4c05UB4+6g==
dependencies:
pvutils latest
assert-plus@1.0.0, assert-plus@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
@@ -4275,6 +4358,11 @@ cookie@0.4.0:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
cookie@0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
copy-concurrently@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
@@ -5019,113 +5107,113 @@ es6-promisify@^5.0.0:
dependencies:
es6-promise "^4.0.3"
esbuild-android-arm64@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.8.tgz#c20e875c3c98164b1ffba9b28637bdf96f5e9e7c"
integrity sha512-AilbChndywpk7CdKkNSZ9klxl+9MboLctXd9LwLo3b0dawmOF/i/t2U5d8LM6SbT1Xw36F8yngSUPrd8yPs2RA==
esbuild-android-arm64@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.10.tgz#3545c71bf01e8b36535681078cdb0191c8654452"
integrity sha512-1sCdVAq64yMp2Uhlu+97/enFxpmrj31QHtThz7K+/QGjbHa7JZdBdBsZCzWJuntKHZ+EU178tHYkvjaI9z5sGg==
esbuild-darwin-64@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.8.tgz#f46e6b471ddbf62265234808a6a1aa91df18a417"
integrity sha512-b6sdiT84zV5LVaoF+UoMVGJzR/iE2vNUfUDfFQGrm4LBwM/PWXweKpuu6RD9mcyCq18cLxkP6w/LD/w9DtX3ng==
esbuild-darwin-64@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.10.tgz#143e34d7f5d3860cc681c64c860f531e60496b5b"
integrity sha512-XlL+BYZ2h9cz3opHfFgSHGA+iy/mljBFIRU9q++f9SiBXEZTb4gTW/IENAD1l9oKH0FdO9rUpyAfV+lM4uAxrg==
esbuild-darwin-arm64@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.8.tgz#a991157a6013facd4f2e14159b7da52626c90154"
integrity sha512-R8YuPiiJayuJJRUBG4H0VwkEKo6AvhJs2m7Tl0JaIer3u1FHHXwGhMxjJDmK+kXwTFPriSysPvcobXC/UrrZCQ==
esbuild-darwin-arm64@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.10.tgz#456a044b789d5d256af8d264314da5217ca9fcd1"
integrity sha512-RZMMqMTyActMrXKkW71IQO8B0tyQm0Bm+ZJQWNaHJchL5LlqazJi7rriwSocP+sKLszHhsyTEBBh6qPdw5g5yQ==
esbuild-freebsd-64@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.8.tgz#301601d2e443ad458960e359b402a17d9500be9d"
integrity sha512-zBn6urrn8FnKC+YSgDxdof9jhPCeU8kR/qaamlV4gI8R3KUaUK162WYM7UyFVAlj9N0MyD3AtB+hltzu4cysTw==
esbuild-freebsd-64@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.10.tgz#dcd829a4a95226716faae8a2f378f08688f921b6"
integrity sha512-pf4BEN9reF3jvZEZdxljVgOv5JS4kuYFCI78xk+2HWustbLvTP0b9XXfWI/OD0ZLWbyLYZYIA+VbVe4tdAklig==
esbuild-freebsd-arm64@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.8.tgz#039a63acc12ec0892006c147ea221e55f9125a9f"
integrity sha512-pWW2slN7lGlkx0MOEBoUGwRX5UgSCLq3dy2c8RIOpiHtA87xAUpDBvZK10MykbT+aMfXc0NI2lu1X+6kI34xng==
esbuild-freebsd-arm64@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.10.tgz#116c254b3eb1b9d1dd6f12e0271967de4512ca09"
integrity sha512-j9PUcuNWmlxr4/ry4dK/s6zKh42Jhh/N5qnAAj7tx3gMbkIHW0JBoVSbbgp97p88X9xgKbXx4lG2sJDhDWmsYQ==
esbuild-linux-32@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.8.tgz#c537b67d7e694b60bfa2786581412838c6ba0284"
integrity sha512-T0I0ueeKVO/Is0CAeSEOG9s2jeNNb8jrrMwG9QBIm3UU18MRB60ERgkS2uV3fZ1vP2F8i3Z2e3Zju4lg9dhVmw==
esbuild-linux-32@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.10.tgz#131971622c38e5aa014303a494a1b5c3cc90f2be"
integrity sha512-imtdHG5ru0xUUXuc2ofdtyw0fWlHYXV7JjF7oZHgmn0b+B4o4Nr6ZON3xxoo1IP8wIekW+7b9exIf/MYq0QV7w==
esbuild-linux-64@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.8.tgz#0092fc8a064001a777bfa0e3b425bb8be8f96e6a"
integrity sha512-Bm8SYmFtvfDCIu9sjKppFXzRXn2BVpuCinU1ChTuMtdKI/7aPpXIrkqBNOgPTOQO9AylJJc1Zw6EvtKORhn64w==
esbuild-linux-64@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.10.tgz#48826c388abd5dde3fc098a8ef38d8b548674f93"
integrity sha512-O7fzQIH2e7GC98dvoTH0rad5BVLm9yU3cRWfEmryCEIFTwbNEWCEWOfsePuoGOHRtSwoVY1hPc21CJE4/9rWxQ==
esbuild-linux-arm64@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.8.tgz#5cd3f2bb924212971482e8dbc25c4afd09b28110"
integrity sha512-X4pWZ+SL+FJ09chWFgRNO3F+YtvAQRcWh0uxKqZSWKiWodAB20flsW/OWFYLXBKiVCTeoGMvENZS/GeVac7+tQ==
esbuild-linux-arm64@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.10.tgz#0be9ffc92e30641869c7fbca0ec5d30fa8cbddd6"
integrity sha512-bkGxN67S2n0PF4zhh87/92kBTsH2xXLuH6T5omReKhpXdJZF5SVDSk5XU/nngARzE+e6QK6isK060Dr5uobzNw==
esbuild-linux-arm@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.8.tgz#ad634f96bf2975536907aeb9fdb75a3194f4ddce"
integrity sha512-4/HfcC40LJ4GPyboHA+db0jpFarTB628D1ifU+/5bunIgY+t6mHkJWyxWxAAE8wl/ZIuRYB9RJFdYpu1AXGPdg==
esbuild-linux-arm@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.10.tgz#8c15bcaa41a022c834f049a71a7d1fbade507532"
integrity sha512-R2Jij4A0K8BcmBehvQeUteQEcf24Y2YZ6mizlNFuJOBPxe3vZNmkZ4mCE7Pf1tbcqA65qZx8J3WSHeGJl9EsJA==
esbuild-linux-mips64le@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.8.tgz#57857edfebf9bf65766dc8be1637f2179c990572"
integrity sha512-o7e0D+sqHKT31v+mwFircJFjwSKVd2nbkHEn4l9xQ1hLR+Bv8rnt3HqlblY3+sBdlrOTGSwz0ReROlKUMJyldA==
esbuild-linux-mips64le@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.10.tgz#5bb33a2bc82e9c78ed724f345a8359610ddc9695"
integrity sha512-UDNO5snJYOLWrA2uOUxM/PVbzzh2TR7Zf2i8zCCuFlYgvAb/81XO+Tasp3YAElDpp4VGqqcpBXLtofa9nrnJGA==
esbuild-linux-ppc64le@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.8.tgz#fdb82a059a5b86bb10fb42091b4ebcf488b9cd46"
integrity sha512-eZSQ0ERsWkukJp2px/UWJHVNuy0lMoz/HZcRWAbB6reoaBw7S9vMzYNUnflfL3XA6WDs+dZn3ekHE4Y2uWLGig==
esbuild-linux-ppc64le@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.10.tgz#18703cd0d52447d97486735b8e79fba7d81eac65"
integrity sha512-xu6J9rMWu1TcEGuEmoc8gsTrJCEPsf+QtxK4IiUZNde9r4Q4nlRVah4JVZP3hJapZgZJcxsse0XiKXh1UFdOeA==
esbuild-netbsd-64@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.8.tgz#d7879e7123d3b2c04754ece8bd061aa6866deeff"
integrity sha512-gZX4kP7gVvOrvX0ZwgHmbuHczQUwqYppxqtoyC7VNd80t5nBHOFXVhWo2Ad/Lms0E8b+wwgI/WjZFTCpUHOg9Q==
esbuild-netbsd-64@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.10.tgz#3ecb06158aadb5b7396a5b7632069181b1591c56"
integrity sha512-d+Gr0ScMC2J83Bfx/ZvJHK0UAEMncctwgjRth9d4zppYGLk/xMfFKxv5z1ib8yZpQThafq8aPm8AqmFIJrEesw==
esbuild-openbsd-64@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.8.tgz#88b280b6cb0a3f6adb60abf27fc506c506a35cf0"
integrity sha512-afzza308X4WmcebexbTzAgfEWt9MUkdTvwIa8xOu4CM2qGbl2LanqEl8/LUs8jh6Gqw6WsicEK52GPrS9wvkcw==
esbuild-openbsd-64@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.10.tgz#3a6950b1d955de921ac52f7af0b4865e89d6e4f1"
integrity sha512-OuCYc+bNKumBvxflga+nFzZvxsgmWQW+z4rMGIjM5XIW0nNbGgRc5p/0PSDv0rTdxAmwCpV69fezal0xjrDaaA==
esbuild-sunos-64@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.8.tgz#229ae7c7703196a58acd0f0291ad9bebda815d63"
integrity sha512-mWPZibmBbuMKD+LDN23LGcOZ2EawMYBONMXXHmbuxeT0XxCNwadbCVwUQ/2p5Dp5Kvf6mhrlIffcnWOiCBpiVw==
esbuild-sunos-64@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.10.tgz#ad407f721a8b5727fca31958b5eab1b0232e2d73"
integrity sha512-gUkgivZK11bD56wDoLsnYrsOHD/zHzzLSdqKcIl3wRMulfHpRBpoX8gL0dbWr+8N9c+1HDdbNdvxSRmZ4RCVwg==
esbuild-windows-32@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.8.tgz#892d093e32a21c0c9135e5a0ffdc380aeb70e763"
integrity sha512-QsZ1HnWIcnIEApETZWw8HlOhDSWqdZX2SylU7IzGxOYyVcX7QI06ety/aDcn437mwyO7Ph4RrbhB+2ntM8kX8A==
esbuild-windows-32@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.10.tgz#ddaaa0b6e172df6512edc7a91bd2456615cfa914"
integrity sha512-C1xJ54E56dGWRaYcTnRy7amVZ9n1/D/D2/qVw7e5EtS7p+Fv/yZxxgqyb1hMGKXgtFYX4jMpU5eWBF/AsYrn+A==
esbuild-windows-64@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.8.tgz#7defd8d79ae3bb7e6f53b65a7190be7daf901686"
integrity sha512-76Fb57B9eE/JmJi1QmUW0tRLQZfGo0it+JeYoCDTSlbTn7LV44ecOHIMJSSgZADUtRMWT9z0Kz186bnaB3amSg==
esbuild-windows-64@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.10.tgz#93d861abf36bf71b6e61f5cbd2e42762ce5cb83a"
integrity sha512-6+EXEXopEs3SvPFAHcps2Krp/FvqXXsOQV33cInmyilb0ZBEQew4MIoZtMIyB3YXoV6//dl3i6YbPrFZaWEinQ==
esbuild-windows-arm64@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.8.tgz#e59ae004496fd8a5ab67bfc7945a2e47480d6fb9"
integrity sha512-HW6Mtq5eTudllxY2YgT62MrVcn7oq2o8TAoAvDUhyiEmRmDY8tPwAhb1vxw5/cdkbukM3KdMYtksnUhF/ekWeg==
esbuild-windows-arm64@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.10.tgz#85a2d338aaa8b0cd1d8ecbe9150def9a608e8947"
integrity sha512-xTqM/XKhORo6u9S5I0dNJWEdWoemFjogLUTVLkQMVyUV3ZuMChahVA+bCqKHdyX55pCFxD/8v2fm3/sfFMWN+g==
esbuild@0.13.8:
version "0.13.8"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.8.tgz#bd7cc51b881ab067789f88e17baca74724c1ec4f"
integrity sha512-A4af7G7YZLfG5OnARJRMtlpEsCkq/zHZQXewgPA864l9D6VjjbH1SuFYK/OSV6BtHwDGkdwyRrX0qQFLnMfUcw==
esbuild@0.13.10:
version "0.13.10"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.10.tgz#e3d24d59f1d8b2130d746ca858efcb80e1d99b26"
integrity sha512-0NfCsnAh5XatHIx6Cu93wpR2v6opPoOMxONYhaAoZKzGYqAE+INcDeX2wqMdcndvPQdWCuuCmvlnsh0zmbHcSQ==
optionalDependencies:
esbuild-android-arm64 "0.13.8"
esbuild-darwin-64 "0.13.8"
esbuild-darwin-arm64 "0.13.8"
esbuild-freebsd-64 "0.13.8"
esbuild-freebsd-arm64 "0.13.8"
esbuild-linux-32 "0.13.8"
esbuild-linux-64 "0.13.8"
esbuild-linux-arm "0.13.8"
esbuild-linux-arm64 "0.13.8"
esbuild-linux-mips64le "0.13.8"
esbuild-linux-ppc64le "0.13.8"
esbuild-netbsd-64 "0.13.8"
esbuild-openbsd-64 "0.13.8"
esbuild-sunos-64 "0.13.8"
esbuild-windows-32 "0.13.8"
esbuild-windows-64 "0.13.8"
esbuild-windows-arm64 "0.13.8"
esbuild-android-arm64 "0.13.10"
esbuild-darwin-64 "0.13.10"
esbuild-darwin-arm64 "0.13.10"
esbuild-freebsd-64 "0.13.10"
esbuild-freebsd-arm64 "0.13.10"
esbuild-linux-32 "0.13.10"
esbuild-linux-64 "0.13.10"
esbuild-linux-arm "0.13.10"
esbuild-linux-arm64 "0.13.10"
esbuild-linux-mips64le "0.13.10"
esbuild-linux-ppc64le "0.13.10"
esbuild-netbsd-64 "0.13.10"
esbuild-openbsd-64 "0.13.10"
esbuild-sunos-64 "0.13.10"
esbuild-windows-32 "0.13.10"
esbuild-windows-64 "0.13.10"
esbuild-windows-arm64 "0.13.10"
esbuild@^0.11.20:
version "0.11.23"
@@ -5842,6 +5930,14 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
formdata-node@4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.3.1.tgz#e7b78a2a51f524ccc483b4d223c89690fd22061d"
integrity sha512-8xKSa9et4zb+yziWsD/bI+EYjdg1z2p9EpKr+o+Yk12F/wP66bmDdvjj2ZXd2K/MJlR3HBzWnuV7f82jzHRqCA==
dependencies:
node-domexception "1.0.0"
web-streams-polyfill "4.0.0-beta.1"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@@ -6232,6 +6328,20 @@ globals@^13.6.0, globals@^13.9.0:
dependencies:
type-fest "^0.20.2"
globby@9, globby@^9.2.0:
version "9.2.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d"
integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==
dependencies:
"@types/glob" "^7.1.1"
array-union "^1.0.2"
dir-glob "^2.2.2"
fast-glob "^2.2.6"
glob "^7.1.3"
ignore "^4.0.3"
pify "^4.0.1"
slash "^2.0.0"
globby@^10.0.1:
version "10.0.2"
resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543"
@@ -6269,20 +6379,6 @@ globby@^6.1.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
globby@^9.2.0:
version "9.2.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d"
integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==
dependencies:
"@types/glob" "^7.1.1"
array-union "^1.0.2"
dir-glob "^2.2.2"
fast-glob "^2.2.6"
glob "^7.1.3"
ignore "^4.0.3"
pify "^4.0.1"
slash "^2.0.0"
got@^9.6.0:
version "9.6.0"
resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
@@ -8793,6 +8889,11 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-domexception@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
node-fetch-npm@^2.0.2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz#6507d0e17a9ec0be3bec516958a497cec54bf5a4"
@@ -8812,6 +8913,13 @@ node-fetch@2.6.1, node-fetch@^2.2.1, node-fetch@^2.3.0, node-fetch@^2.5.0, node-
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-fetch@^2:
version "2.6.5"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd"
integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==
dependencies:
whatwg-url "^5.0.0"
node-gyp-build@^4.2.2:
version "4.2.3"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739"
@@ -9823,6 +9931,11 @@ pumpify@^1.3.3:
inherits "^2.0.3"
pump "^2.0.0"
punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
punycode@^1.3.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
@@ -9840,6 +9953,18 @@ pupa@^2.0.1:
dependencies:
escape-goat "^2.0.0"
pvtsutils@^1.2.0, pvtsutils@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.2.1.tgz#8212e846ca9afb21e40cebb0691755649f9f498a"
integrity sha512-Q867jEr30lBR2YSFFLZ0/XsEvpweqH6Kj096wmlRAFXrdRGPCNq2iz9B5Tk085EZ+OBZyYAVA5UhPkjSHGrUzQ==
dependencies:
tslib "^2.3.1"
pvutils@latest:
version "1.0.17"
resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.0.17.tgz#ade3c74dfe7178944fe44806626bd2e249d996bf"
integrity sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ==
q@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@@ -9860,11 +9985,16 @@ qs@~6.5.1, qs@~6.5.2:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
querystring@^0.2.0:
querystring@0.2.0, querystring@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
querystring@0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd"
integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==
quick-lru@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8"
@@ -11459,6 +11589,11 @@ tr46@^2.1.0:
dependencies:
punycode "^2.1.1"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
tree-kill@1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.1.tgz#5398f374e2f292b9dcc7b2e71e30a5c3bb6c743a"
@@ -11558,6 +11693,11 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
tslib@^2.0.0, tslib@^2.3.0, tslib@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
@@ -11651,6 +11791,11 @@ typescript@4.3.4:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.4.tgz#3f85b986945bcf31071decdd96cf8bfa65f9dcbc"
integrity sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==
ua-parser-js@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.2.tgz#e2976c34dbfb30b15d2c300b2a53eac87c57a775"
integrity sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg==
uglify-js@^3.1.4:
version "3.13.5"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.5.tgz#5d71d6dbba64cf441f32929b1efce7365bb4f113"
@@ -11848,6 +11993,14 @@ url-parse-lax@^3.0.0:
dependencies:
prepend-http "^2.0.0"
url@0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=
dependencies:
punycode "1.3.2"
querystring "0.2.0"
urlgrey@0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f"
@@ -11885,16 +12038,16 @@ uuid@3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
uuid@8.3.2, uuid@^8.0.0:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uuid@^3.0.0, uuid@^3.0.1, uuid@^3.3.2:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^8.0.0:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-compile-cache@^2.0.3:
version "2.1.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
@@ -11929,13 +12082,6 @@ vary@~1.1.2:
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
vercel-plugin-middleware@0.0.0-canary.3:
version "0.0.0-canary.3"
resolved "https://registry.yarnpkg.com/vercel-plugin-middleware/-/vercel-plugin-middleware-0.0.0-canary.3.tgz#7fddd5436f1379af5d8a37f40c28232bff5f5042"
integrity sha512-hn8FCux27K2w1UIz9Hjj/Tt7cwHlDqlVdWEYK6CJBgDJsKZoXUsQxLraKU39UsWcth+ryug+O6U0bUZq+3ngGA==
dependencies:
esbuild "0.13.8"
vercel-plugin-node@1.12.2-plugin.0:
version "1.12.2-plugin.0"
resolved "https://registry.yarnpkg.com/vercel-plugin-node/-/vercel-plugin-node-1.12.2-plugin.0.tgz#0fa8933e03bfcf97381cec200eeafa71f71814e2"
@@ -11982,6 +12128,32 @@ wcwidth@^1.0.0, wcwidth@^1.0.1:
dependencies:
defaults "^1.0.3"
web-streams-polyfill@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.1.1.tgz#1516f2d4ea8f1bdbfed15eb65cb2df87098c8364"
integrity sha512-Czi3fG883e96T4DLEPRvufrF2ydhOOW1+1a6c3gNjH2aIh50DNFBdfwh2AKoOf1rXvpvavAoA11Qdq9+BKjE0Q==
web-streams-polyfill@4.0.0-beta.1:
version "4.0.0-beta.1"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.1.tgz#3b19b9817374b7cee06d374ba7eeb3aeb80e8c95"
integrity sha512-3ux37gEX670UUphBF9AMCq8XM6iQ8Ac6A+DSRRjDoRBm1ufCkaCDdNVbaqq60PsEkdNlLKrGtv/YBP4EJXqNtQ==
webcrypto-core@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.3.0.tgz#848c5af59785634de1408e7b8a659c14738f4cab"
integrity sha512-/+Hz+uNM6T8FtizWRYMNdGTXxWaljLFzQ5GKU4WqCTZKpaki94YqDA39h/SpWxEZfgkVMZzrqqtPlfy2+BloQw==
dependencies:
"@peculiar/asn1-schema" "^2.0.38"
"@peculiar/json-schema" "^1.1.12"
asn1js "^2.1.1"
pvtsutils "^1.2.0"
tslib "^2.3.1"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
@@ -12014,6 +12186,14 @@ whatwg-mimetype@^2.3.0:
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
whatwg-url@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"