Compare commits

..

18 Commits

Author SHA1 Message Date
Nathan Rajlich
41ce96a2db Publish Canary
- @vercel/build-utils@2.10.2-canary.4
 - vercel@21.3.4-canary.4
 - @vercel/client@9.0.9-canary.4
 - @vercel/frameworks@0.3.2-canary.4
 - @vercel/routing-utils@1.10.2-canary.2
2021-03-22 17:52:18 -07:00
Connor Davis
a4b4397151 [routing-utils] Add schema descriptions to routing schema (#6023)
Our automated documentation requires descriptions in the schema

#### Tests

- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
2021-03-23 00:51:30 +00:00
dav-is
473159f1da Publish Canary
- @vercel/build-utils@2.10.2-canary.3
 - vercel@21.3.4-canary.3
 - @vercel/client@9.0.9-canary.3
 - @vercel/frameworks@0.3.2-canary.3
 - @vercel/routing-utils@1.10.2-canary.1
2021-03-22 16:45:42 -04:00
Connor Davis
c6267b5acc [routing-utils] Add has type host to schema (#6022)
We need to support `{ "source": "/", "has": { "type", "host", "value": "vercel.com" }, "destination": "/prod" }`

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [x] Issue from task tracker has a link to this PR

CH-19565
2021-03-22 20:37:27 +00:00
Steven
8a919043a2 [frameworks] Fix "svelte" detection and Dev Command (#6014)
- We were matching any project using [sirv](https://www.npmjs.com/package/sirv) which was too broad.
- Hot reloading didn't work because default dev command was incorrect.

This PR updates the framework detection and default dev command to match the example:

c2e7be80e8/examples/svelte/package.json (L6)
2021-03-19 22:21:46 +00:00
Steven
c2e7be80e8 Publish Canary
- @vercel/build-utils@2.10.2-canary.2
 - vercel@21.3.4-canary.2
 - @vercel/client@9.0.9-canary.2
 - @vercel/frameworks@0.3.2-canary.2
2021-03-18 12:12:43 -04:00
Steven
d95175253a [frameworks] Add envPrefix property (#6007) 2021-03-18 12:11:30 -04:00
Steven
e2106d12f6 Publish Canary
- @vercel/build-utils@2.10.2-canary.1
 - vercel@21.3.4-canary.1
 - @vercel/client@9.0.9-canary.1
 - @vercel/frameworks@0.3.2-canary.1
2021-03-12 11:14:27 -05:00
Steven
84f95465f7 [frameworks] Add static 404 route to CRA (#5972)
Since CRA is an SPA (all routes fallback to index.html), we can't do a proper custom 404.

But we can do a custom 404 when accessing the static directory, for example `/static/foo.html`.

To handle something like `/foo`, the user needs to do a client-side routing 404 like this example: https://reactrouter.com/web/example/no-match


### 📋 Checklist

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2021-03-12 16:10:44 +00:00
Nathan Rajlich
0ae74546a6 [cli] Add SAML Single Sign-On to vercel login (#5957) 2021-03-11 15:29:07 -08:00
JJ Kasper
be2ae2c539 Publish Canary
- @vercel/build-utils@2.10.2-canary.0
 - vercel@21.3.4-canary.0
 - @vercel/client@9.0.9-canary.0
 - @vercel/frameworks@0.3.2-canary.0
 - @vercel/routing-utils@1.10.2-canary.0
2021-03-11 14:00:10 -06:00
JJ Kasper
4969a65209 [routing-utils] Add has route field to schema (#5919)
### Related Issues

x-ref: https://github.com/vercel/next.js/pull/22341
x-ref: https://github.com/vercel/next.js/issues/22345

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2021-03-11 19:44:15 +00:00
ernestd
7275878b2b [frameworks] Update demo URLs to use *.examples.vercel.com (#5960) 2021-03-10 11:45:52 -08:00
Nathan Rajlich
53d429e3f5 [cli] Convert vercel login command to TypeScript (#5951)
* WIP convert `vc login` command to TypeScript

* Fix build
2021-03-08 15:59:42 -08:00
Nathan Rajlich
3f1384bd1a [cli] Remove "micro" dependency (#5950) 2021-03-08 13:48:18 -08:00
Steven
703b2649bc Publish Stable
- @vercel/build-utils@2.10.1
 - @vercel/cgi@1.0.7
 - vercel@21.3.3
 - @vercel/client@9.0.8
 - @vercel/frameworks@0.3.1
 - @vercel/go@1.2.2
 - @vercel/node-bridge@1.3.3
 - @vercel/node@1.9.1
 - @vercel/python@2.0.1
 - @vercel/routing-utils@1.10.1
 - @vercel/ruby@1.2.6
2021-03-08 16:08:16 -05:00
Steven
2497909d9b Publish Canary
- vercel@21.3.3-canary.3
 - @vercel/node@1.9.1-canary.4
2021-03-08 13:40:30 -05:00
Steven
0ad45c8b13 [node] Fix tsCompile uninitialized error (#4275)
This PR fixes an error that happens when `includeFiles` has `.ts` files:

```
ReferenceError: Cannot access 'tsCompile' before initialization
```

~~However its not clear what the expected behavior is. Should the `.ts` files be compiled to `.js` or should they be considered assets and included as-is?~~

We will assume that `includeFiles` is only for assets and thus `.ts` files should not be compiled, they're included as-is.

[ch20529]
2021-03-08 18:11:51 +00:00
37 changed files with 815 additions and 436 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "2.10.1-canary.0",
"version": "2.10.2-canary.4",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -29,7 +29,7 @@
"@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0",
"@types/yazl": "^2.4.1",
"@vercel/frameworks": "0.3.1-canary.0",
"@vercel/frameworks": "0.3.2-canary.4",
"@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1",
"async-retry": "1.2.3",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/cgi",
"version": "1.0.7-canary.1",
"version": "1.0.7",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "21.3.3-canary.2",
"version": "21.3.4-canary.4",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -61,11 +61,11 @@
"node": ">= 10"
},
"dependencies": {
"@vercel/build-utils": "2.10.1-canary.0",
"@vercel/go": "1.2.2-canary.0",
"@vercel/node": "1.9.1-canary.3",
"@vercel/python": "2.0.1-canary.0",
"@vercel/ruby": "1.2.6-canary.1",
"@vercel/build-utils": "2.10.2-canary.4",
"@vercel/go": "1.2.2",
"@vercel/node": "1.9.1",
"@vercel/python": "2.0.1",
"@vercel/ruby": "1.2.6",
"update-notifier": "4.1.0"
},
"devDependencies": {
@@ -83,7 +83,6 @@
"@types/glob": "7.1.1",
"@types/http-proxy": "1.16.2",
"@types/load-json-file": "2.0.7",
"@types/micro": "7.3.3",
"@types/mime-types": "2.1.0",
"@types/minimatch": "3.0.3",
"@types/mri": "1.1.0",
@@ -100,7 +99,7 @@
"@types/universal-analytics": "0.4.2",
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@vercel/frameworks": "0.3.1-canary.0",
"@vercel/frameworks": "0.3.2-canary.4",
"@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2",
@@ -141,7 +140,6 @@
"jaro-winkler": "0.2.8",
"jsonlines": "0.1.1",
"load-json-file": "3.0.0",
"micro": "9.1.2",
"mime-types": "2.1.24",
"minimatch": "3.0.4",
"mri": "1.1.5",
@@ -149,6 +147,7 @@
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"nyc": "13.2.0",
"open": "8.0.2",
"ora": "3.4.0",
"pcre-to-regexp": "1.0.0",
"pluralize": "7.0.0",

View File

@@ -1,262 +0,0 @@
import { stringify as stringifyQuery } from 'querystring';
import fetch from 'node-fetch';
import debugFactory from 'debug';
import promptEmail from 'email-prompt';
import ms from 'ms';
import { validate as validateEmail } from 'email-validator';
import chalk from 'chalk';
import ua from '../util/ua.ts';
import getArgs from '../util/get-args';
import error from '../util/output/error';
import highlight from '../util/output/highlight';
import ok from '../util/output/ok';
import param from '../util/output/param.ts';
import eraseLines from '../util/output/erase-lines';
import sleep from '../util/sleep';
import { handleError } from '../util/error';
import { writeToAuthConfigFile, writeToConfigFile } from '../util/config/files';
import getGlobalPathConfig from '../util/config/global-path';
import hp from '../util/humanize-path';
import logo from '../util/output/logo';
import exit from '../util/exit';
import executeLogin from '../util/login/login.ts';
import { prependEmoji, emoji } from '../util/emoji';
import { getCommandName, getPkgName } from '../util/pkg-name.ts';
const debug = debugFactory(`${getPkgName()}:login`);
const help = () => {
console.log(`
${chalk.bold(`${logo} ${getPkgName()} login`)} <email>
${chalk.dim('Options:')}
-h, --help Output usage information
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'
)} Path to the local ${'`vercel.json`'} file
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
'DIR'
)} Path to the global ${'`.vercel`'} directory
${chalk.dim('Examples:')}
${chalk.gray('')} Log into the Vercel platform
${chalk.cyan(`$ ${getPkgName()} login`)}
${chalk.gray('')} Log in using a specific email address
${chalk.cyan(`$ ${getPkgName()} login john@doe.com`)}
`);
};
const verify = async ({ apiUrl, email, verificationToken }) => {
const query = {
email,
token: verificationToken,
};
debug('GET /now/registration/verify');
let res;
try {
res = await fetch(
`${apiUrl}/now/registration/verify?${stringifyQuery(query)}`,
{
headers: { 'User-Agent': ua },
}
);
} catch (err) {
debug(`error fetching /now/registration/verify: $O`, err.stack);
throw new Error(
error(
`An unexpected error occurred while trying to verify your login: ${err.message}`
)
);
}
debug('parsing response from GET /now/registration/verify');
let body;
try {
body = await res.json();
} catch (err) {
debug(
`error parsing the response from /now/registration/verify: $O`,
err.stack
);
throw new Error(
error(
`An unexpected error occurred while trying to verify your login: ${err.message}`
)
);
}
return body.token;
};
const readEmail = async () => {
let email;
try {
email = await promptEmail({ start: `Enter your email: ` });
} catch (err) {
console.log(); // \n
if (err.message === 'User abort') {
throw new Error(`${chalk.red('Aborted!')} No changes made`);
}
if (err.message === 'stdin lacks setRawMode support') {
throw new Error(
error(
`Interactive mode not supported please run ${getCommandName(
`login you@domain.com`
)}`
)
);
}
}
console.log(); // \n
return email;
};
const login = async ctx => {
let argv;
try {
argv = getArgs(ctx.argv.slice(2));
} catch (err) {
handleError(err);
return 1;
}
if (argv.help) {
help();
await exit(0);
}
const { apiUrl, output } = ctx;
argv._ = argv._.slice(1);
let email;
let emailIsValid = false;
const possibleAddress = argv._[0];
// if the last arg is not the command itself, then maybe it's an email
if (possibleAddress) {
if (!validateEmail(possibleAddress)) {
// if it's not a valid email, let's just error
console.log(error(`Invalid email: ${param(possibleAddress)}.`));
return 1;
}
// valid email, no need to prompt the user
email = possibleAddress;
} else {
do {
try {
email = await readEmail();
} catch (err) {
let erase = '';
if (err.message.includes('Aborted')) {
// no need to keep the prompt if the user `ctrl+c`ed
erase = eraseLines(2);
}
console.log(erase + err.message);
return 1;
}
emailIsValid = validateEmail(email);
if (!emailIsValid) {
// let's erase the `> Enter email [...]`
// we can't use `console.log()` because it appends a `\n`
// we need this check because `email-prompt` doesn't print
// anything if there's no TTY
process.stdout.write(eraseLines(2));
}
} while (!emailIsValid);
}
let verificationToken;
let securityCode;
output.spinner('Sending you an email');
try {
const data = await executeLogin(apiUrl, email);
verificationToken = data.token;
securityCode = data.securityCode;
} catch (err) {
output.error(err.message);
return 1;
}
output.stopSpinner();
// Clear up `Sending email` success message
process.stdout.write(eraseLines(possibleAddress ? 1 : 2));
output.print(
`We sent an email to ${highlight(
email
)}. Please follow the steps provided inside it and make sure the security code matches ${highlight(
securityCode
)}.\n`
);
output.spinner('Waiting for your confirmation');
let token;
while (!token) {
try {
await sleep(ms('1s'));
token = await verify({ apiUrl, email, verificationToken });
} catch (err) {
if (/invalid json response body/.test(err.message)) {
// /now/registraton is currently returning plain text in that case
// we just wait for the user to click on the link
} else {
output.error(err.message);
return 1;
}
}
}
output.stopSpinner();
console.log(ok('Email confirmed'));
// There's no need to save the user since we always
// pull the user data fresh from the server.
ctx.authConfig.token = token;
// New user, so we can't keep the team
delete ctx.config.currentTeam;
writeToAuthConfigFile(ctx.authConfig);
writeToConfigFile(ctx.config);
output.debug(`Saved credentials in "${hp(getGlobalPathConfig())}"`);
console.log(
`${chalk.cyan('Congratulations!')} ` +
`You are now logged in. In order to deploy something, run ${getCommandName()}.`
);
output.print(
`${prependEmoji(
`Connect your Git Repositories to deploy every branch push automatically (https://vercel.link/git).`,
emoji('tip')
)}\n`
);
return ctx;
};
export default login;

View File

@@ -0,0 +1,141 @@
import inquirer from 'inquirer';
import { validate as validateEmail } from 'email-validator';
import chalk from 'chalk';
import hp from '../util/humanize-path';
import getArgs from '../util/get-args';
import error from '../util/output/error';
import handleError from '../util/handle-error';
import logo from '../util/output/logo';
import doSsoLogin from '../util/login/sso';
import doEmailLogin from '../util/login/email';
import { prependEmoji, emoji } from '../util/emoji';
import { getCommandName, getPkgName } from '../util/pkg-name';
import getGlobalPathConfig from '../util/config/global-path';
import { writeToAuthConfigFile, writeToConfigFile } from '../util/config/files';
import { NowContext } from '../types';
const help = () => {
console.log(`
${chalk.bold(`${logo} ${getPkgName()} login`)} <email or team>
${chalk.dim('Options:')}
-h, --help Output usage information
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'
)} Path to the local ${'`vercel.json`'} file
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
'DIR'
)} Path to the global ${'`.vercel`'} directory
${chalk.dim('Examples:')}
${chalk.gray('')} Log into the Vercel platform
${chalk.cyan(`$ ${getPkgName()} login`)}
${chalk.gray('')} Log in using a specific email address
${chalk.cyan(`$ ${getPkgName()} login john@doe.com`)}
${chalk.gray('')} Log in using a specific team "slug" for SAML Single Sign-On
${chalk.cyan(`$ ${getPkgName()} login acme`)}
`);
};
const readInput = async () => {
let input;
while (!input) {
try {
const { val } = await inquirer.prompt({
type: 'input',
name: 'val',
message: 'Enter your email or team slug:',
});
input = val;
} catch (err) {
console.log(); // \n
if (err.isTtyError) {
throw new Error(
error(
`Interactive mode not supported please run ${getCommandName(
`login you@domain.com`
)}`
)
);
}
}
}
console.log(); // \n
return input;
};
export default async function login(ctx: NowContext): Promise<number> {
let argv;
try {
argv = getArgs(ctx.argv.slice(2));
} catch (err) {
handleError(err);
return 1;
}
if (argv['--help']) {
help();
return 2;
}
const { apiUrl, output } = ctx;
const input = argv._[1] || (await readInput());
// TODO: add proper validation
const isValidSlug = true;
let result: number | string = 1;
if (validateEmail(input)) {
result = await doEmailLogin(input, { output, apiUrl, ctx });
} else if (isValidSlug) {
result = await doSsoLogin(input, { output, apiUrl, ctx });
} else {
output.error(`Invalid input: "${input}"`);
output.log(`Please enter a valid email address or team slug`);
return 2;
}
// The login function failed, so it returned an exit code
if (typeof result === 'number') {
return result;
}
// When `result` is a string it's the user's authentication token.
// It needs to be saved to the configuration file.
ctx.authConfig.token = result;
// New user, so we can't keep the team
delete ctx.config.currentTeam;
writeToAuthConfigFile(ctx.authConfig);
writeToConfigFile(ctx.config);
output.debug(`Saved credentials in "${hp(getGlobalPathConfig())}"`);
console.log(
`${chalk.cyan('Congratulations!')} ` +
`You are now logged in. In order to deploy something, run ${getCommandName()}.`
);
output.print(
`${prependEmoji(
`Connect your Git Repositories to deploy every branch push automatically (https://vercel.link/git).`,
emoji('tip')
)}\n`
);
return 0;
}

View File

@@ -41,7 +41,7 @@ export default async function ({ apiUrl, token, debug, args, config, output }) {
user = await getUser(client);
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
console.error(error(err.message));
output.error(err.message);
return 1;
}
@@ -56,7 +56,7 @@ export default async function ({ apiUrl, token, debug, args, config, output }) {
currentTeam = list.find(team => team.id === currentTeam);
if (!currentTeam) {
console.error(error(`You are not a part of the current team anymore`));
output.error(`You are not a part of the current team anymore`);
return 1;
}
}
@@ -129,6 +129,8 @@ export default async function ({ apiUrl, token, debug, args, config, output }) {
choices.unshift(choice);
}
output.stopSpinner();
let message;
if (currentTeam) {

View File

@@ -1,4 +1,4 @@
export default code =>
export default (code?: number) =>
new Promise(() => {
// We give stdout some time to flush out
// because there's a node bug where

View File

@@ -13,11 +13,11 @@ interface Spec {
export default function getArgs<T extends Spec>(
argv: string[],
argsOptions: T,
argsOptions?: T,
argOptions: ArgOptions = {}
) {
return arg(Object.assign({}, getCommonArgs(), argsOptions), {
...argOptions,
argv
argv,
});
}

View File

@@ -0,0 +1,112 @@
import ms from 'ms';
import { stringify as stringifyQuery } from 'querystring';
import fetch from 'node-fetch';
import sleep from '../sleep';
import ua from '../ua';
import error from '../output/error';
import highlight from '../output/highlight';
import eraseLines from '../output/erase-lines';
import executeLogin from './login';
import { LoginParams } from './types';
async function verify(
email: string,
verificationToken: string,
{ apiUrl, output }: LoginParams
): Promise<string> {
const query = {
email,
token: verificationToken,
};
output.debug('GET /now/registration/verify');
let res;
try {
res = await fetch(
`${apiUrl}/now/registration/verify?${stringifyQuery(query)}`,
{
headers: { 'User-Agent': ua },
}
);
} catch (err) {
output.debug(`error fetching /now/registration/verify: ${err.stack}`);
throw new Error(
error(
`An unexpected error occurred while trying to verify your login: ${err.message}`
)
);
}
output.debug('parsing response from GET /now/registration/verify');
let body;
try {
body = await res.json();
} catch (err) {
output.debug(
`error parsing the response from /now/registration/verify: ${err.stack}`
);
throw new Error(
error(
`An unexpected error occurred while trying to verify your login: ${err.message}`
)
);
}
return body.token;
}
export default async function doEmailLogin(
email: string,
{ apiUrl, output, ctx }: LoginParams
): Promise<number | string> {
let securityCode;
let verificationToken;
output.spinner('Sending you an email');
try {
const data = await executeLogin(apiUrl, email);
verificationToken = data.token;
securityCode = data.securityCode;
} catch (err) {
output.error(err.message);
return 1;
}
// Clear up `Sending email` success message
output.print(eraseLines(1));
output.print(
`We sent an email to ${highlight(
email
)}. Please follow the steps provided inside it and make sure the security code matches ${highlight(
securityCode
)}.\n`
);
output.spinner('Waiting for your confirmation');
let token = '';
while (!token) {
try {
await sleep(ms('1s'));
token = await verify(email, verificationToken, { apiUrl, output, ctx });
} catch (err) {
if (/invalid json response body/.test(err.message)) {
// /now/registraton is currently returning plain text in that case
// we just wait for the user to click on the link
} else {
output.error(err.message);
return 1;
}
}
}
output.success('Email confirmed');
return token;
}

View File

@@ -3,21 +3,15 @@ import { hostname } from 'os';
import { InvalidEmail, AccountNotFound } from '../errors-ts';
import ua from '../ua';
import { getTitleName } from '../pkg-name';
type LoginData = {
token: string;
securityCode: string;
};
import { LoginData } from './types';
export default async function login(
apiUrl: string,
email: string,
mode: 'login' | 'signup' = 'login'
): Promise<LoginData | InvalidEmail | AccountNotFound> {
): Promise<LoginData> {
const hyphens = new RegExp('-', 'g');
const host = hostname()
.replace(hyphens, ' ')
.replace('.local', '');
const host = hostname().replace(hyphens, ' ').replace('.local', '');
const tokenName = `${getTitleName()} CLI on ${host}`;
const response = await fetch(`${apiUrl}/now/registration?mode=${mode}`, {

View File

@@ -0,0 +1,93 @@
import http from 'http';
import open from 'open';
import fetch from 'node-fetch';
import { hostname } from 'os';
import { URL } from 'url';
import listen from 'async-listen';
import { getTitleName } from '../pkg-name';
import { LoginParams } from './types';
export default async function doSsoLogin(
teamIdOrSlug: string,
{ apiUrl, output }: LoginParams
): Promise<number | string> {
output.print(`Logging in to team "${teamIdOrSlug}"`);
const hyphens = new RegExp('-', 'g');
const host = hostname().replace(hyphens, ' ').replace('.local', '');
const tokenName = `${getTitleName()} CLI on ${host}`;
const server = http.createServer();
const address = await listen(server);
const { port } = new URL(address);
try {
const url = new URL('/registration/sso/auth', apiUrl);
url.searchParams.append('mode', 'login');
url.searchParams.append('next', `http://localhost:${port}`);
url.searchParams.append('teamId', teamIdOrSlug);
url.searchParams.append('tokenName', tokenName);
output.spinner(
'Please complete the SAML Single Sign-On authentication in your web browser'
);
const [req] = await Promise.all([
new Promise<http.IncomingMessage>((resolve, reject) => {
server.once('request', (req, res) => {
resolve(req);
// Redirect the user's web browser back
// to the Vercel email confirmation page
res.statusCode = 302;
// TODO: maybe a dedicated page for CLI login success?
res.setHeader(
'location',
'https://vercel.com/notifications/email-confirmed'
);
res.end();
});
server.once('error', reject);
}),
open(url.href),
]);
const query = new URL(req.url || '/', 'http://localhost').searchParams;
const loginError = query.get('loginError');
if (loginError) {
const err = JSON.parse(loginError);
output.error(err.message);
return 1;
}
const email = query.get('email');
const verificationToken = query.get('token');
if (!email || !verificationToken) {
output.error(
'Verification token was not provided. Please contact support.'
);
return 1;
}
output.spinner('Verifying authentication token');
const verifyUrl = new URL('/registration/verify', apiUrl);
verifyUrl.searchParams.append('email', email);
verifyUrl.searchParams.append('token', verificationToken);
const verifyRes = await fetch(verifyUrl.href);
if (!verifyRes.ok) {
output.error(
`Unexpected ${verifyRes.status} status code from verify API`
);
output.debug(await verifyRes.text());
return 1;
}
output.success(`SAML authentication complete`);
const body = await verifyRes.json();
return body.token;
} finally {
server.close();
}
}

View File

@@ -0,0 +1,13 @@
import { Output } from '../output';
import { NowContext } from '../../types';
export interface LoginParams {
apiUrl: string;
output: Output;
ctx: NowContext;
}
export interface LoginData {
token: string;
securityCode: string;
}

View File

@@ -1,6 +0,0 @@
import { cyan } from 'chalk';
import chars from './chars';
const ok = msg => `${cyan(chars.tick)} ${msg}`;
export default ok;

View File

@@ -0,0 +1,6 @@
import chalk from 'chalk';
import chars from './chars';
const ok = (msg: string) => `${chalk.cyan(chars.tick)} ${msg}`;
export default ok;

View File

@@ -1,8 +1,8 @@
// Packages
const http = require('http');
const listen = require('test-listen');
const micro = require('micro');
module.exports = fn => {
const srv = micro(fn);
const srv = http.createServer(fn);
return listen(srv);
};

View File

@@ -1,5 +1,4 @@
import { basename, join, sep } from 'path';
import { send } from 'micro';
import test from 'ava';
import sinon from 'sinon';
import { asc as alpha } from 'alpha-sort';
@@ -22,6 +21,12 @@ const prefix = `${join(__dirname, 'fixtures', 'unit')}${sep}`;
const base = path => path.replace(prefix, '');
const fixture = name => join(prefix, name);
const send = (res, statusCode, body) => {
res.statusCode = statusCode;
res.setHeader('Content-Type', 'application/json; charset=utf8');
res.end(JSON.stringify(body));
};
const getStaticFiles = async dir => {
const files = await getStaticFiles_(dir, {
output,
@@ -163,7 +168,7 @@ test('`wait` utility does not invoke spinner when stopped before delay', async t
});
test('4xx response error with fallback message', async t => {
const fn = async (req, res) => {
const fn = (req, res) => {
send(res, 404, {});
};
@@ -175,7 +180,7 @@ test('4xx response error with fallback message', async t => {
});
test('4xx response error without fallback message', async t => {
const fn = async (req, res) => {
const fn = (req, res) => {
send(res, 404, {});
};
@@ -187,7 +192,7 @@ test('4xx response error without fallback message', async t => {
});
test('5xx response error without fallback message', async t => {
const fn = async (req, res) => {
const fn = (req, res) => {
send(res, 500, '');
};
@@ -199,7 +204,7 @@ test('5xx response error without fallback message', async t => {
});
test('4xx response error as correct JSON', async t => {
const fn = async (req, res) => {
const fn = (req, res) => {
send(res, 400, {
error: {
message: 'The request is not correct',
@@ -215,7 +220,7 @@ test('4xx response error as correct JSON', async t => {
});
test('5xx response error as HTML', async t => {
const fn = async (req, res) => {
const fn = (req, res) => {
send(res, 500, 'This is a malformed error');
};
@@ -227,7 +232,7 @@ test('5xx response error as HTML', async t => {
});
test('5xx response error with random JSON', async t => {
const fn = async (req, res) => {
const fn = (req, res) => {
send(res, 500, {
wrong: 'property',
});
@@ -294,7 +299,7 @@ test('getProjectName with a directory', t => {
});
test('4xx error message with broken JSON', async t => {
const fn = async (req, res) => {
const fn = (req, res) => {
send(res, 403, `32puuuh2332`);
};
@@ -306,7 +311,7 @@ test('4xx error message with broken JSON', async t => {
});
test('4xx error message with proper message', async t => {
const fn = async (req, res) => {
const fn = (req, res) => {
send(res, 403, {
error: {
message: 'This is a test',
@@ -322,7 +327,7 @@ test('4xx error message with proper message', async t => {
});
test('5xx error message with proper message', async t => {
const fn = async (req, res) => {
const fn = (req, res) => {
send(res, 500, {
error: {
message: 'This is a test',
@@ -338,7 +343,7 @@ test('5xx error message with proper message', async t => {
});
test('4xx response error with broken JSON', async t => {
const fn = async (req, res) => {
const fn = (req, res) => {
send(res, 403, `122{"sss"`);
};
@@ -350,7 +355,7 @@ test('4xx response error with broken JSON', async t => {
});
test('4xx response error as correct JSON with more properties', async t => {
const fn = async (req, res) => {
const fn = (req, res) => {
send(res, 403, {
error: {
message: 'The request is not correct',
@@ -368,7 +373,7 @@ test('4xx response error as correct JSON with more properties', async t => {
});
test('429 response error with retry header', async t => {
const fn = async (req, res) => {
const fn = (req, res) => {
res.setHeader('Retry-After', '20');
send(res, 429, {
@@ -387,7 +392,7 @@ test('429 response error with retry header', async t => {
});
test('429 response error without retry header', async t => {
const fn = async (req, res) => {
const fn = (req, res) => {
send(res, 429, {
error: {
message: 'You were rate limited',

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "9.0.8-canary.0",
"version": "9.0.9-canary.4",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -37,7 +37,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "2.10.1-canary.0",
"@vercel/build-utils": "2.10.2-canary.4",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/frameworks",
"version": "0.3.1-canary.0",
"version": "0.3.2-canary.4",
"main": "./dist/frameworks.js",
"types": "./dist/frameworks.d.ts",
"files": [
@@ -20,7 +20,7 @@
"@types/js-yaml": "3.12.1",
"@types/node": "12.0.4",
"@types/node-fetch": "2.5.8",
"@vercel/routing-utils": "1.10.1-canary.0",
"@vercel/routing-utils": "1.10.2-canary.2",
"ajv": "6.12.2",
"jest": "24.9.0",
"ts-jest": "24.1.0",

View File

@@ -22,13 +22,14 @@ export const frameworks = [
{
name: 'Blitz.js',
slug: 'blitzjs',
demo: 'https://blitzjs.now-examples.now.sh',
demo: 'https://blitzjs.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/blitz.svg',
tagline: 'Blitz.js: The Fullstack React Framework',
description:
'A brand new Blitz.js app - the result of running `npx blitz new`.',
website: 'https://blitzjs.com',
envPrefix: 'NEXT_PUBLIC_',
useRuntime: { src: 'package.json', use: '@vercel/next' },
detectors: {
every: [
@@ -60,7 +61,7 @@ export const frameworks = [
{
name: 'Next.js',
slug: 'nextjs',
demo: 'https://nextjs.now-examples.now.sh',
demo: 'https://nextjs.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/next.svg',
tagline:
@@ -68,6 +69,7 @@ export const frameworks = [
description: 'A Next.js app and a Serverless Function API.',
website: 'https://nextjs.org',
sort: 1,
envPrefix: 'NEXT_PUBLIC_',
useRuntime: { src: 'package.json', use: '@vercel/next' },
detectors: {
every: [
@@ -105,7 +107,7 @@ export const frameworks = [
{
name: 'Gatsby.js',
slug: 'gatsby',
demo: 'https://gatsby.now-examples.now.sh',
demo: 'https://gatsby.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/gatsby.svg',
tagline:
@@ -114,6 +116,7 @@ export const frameworks = [
'A Gatsby app, using the default starter theme and a Serverless Function API.',
website: 'https://gatsbyjs.org',
sort: 2,
envPrefix: 'GATSBY_',
detectors: {
every: [
{
@@ -193,7 +196,7 @@ export const frameworks = [
{
name: 'Hexo',
slug: 'hexo',
demo: 'https://hexo.now-examples.now.sh',
demo: 'https://hexo.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/hexo.svg',
tagline:
@@ -232,7 +235,7 @@ export const frameworks = [
{
name: 'Eleventy',
slug: 'eleventy',
demo: 'https://eleventy.now-examples.now.sh',
demo: 'https://eleventy.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/eleventy.svg',
tagline:
@@ -272,7 +275,7 @@ export const frameworks = [
{
name: 'Docusaurus 2',
slug: 'docusaurus-2',
demo: 'https://docusaurus-2.now-examples.now.sh',
demo: 'https://docusaurus-2.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/docusaurus.svg',
tagline:
@@ -364,7 +367,7 @@ export const frameworks = [
{
name: 'Docusaurus 1',
slug: 'docusaurus',
demo: 'https://docusaurus.now-examples.now.sh',
demo: 'https://docusaurus.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/docusaurus.svg',
tagline:
@@ -417,7 +420,7 @@ export const frameworks = [
{
name: 'Preact',
slug: 'preact',
demo: 'https://preact.now-examples.now.sh',
demo: 'https://preact.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/preact.svg',
tagline:
@@ -464,7 +467,7 @@ export const frameworks = [
{
name: 'Dojo',
slug: 'dojo',
demo: 'https://dojo.now-examples.now.sh',
demo: 'https://dojo.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/dojo.png',
tagline: 'Dojo is a modern progressive, TypeScript first framework.',
@@ -519,7 +522,7 @@ export const frameworks = [
{
name: 'Ember.js',
slug: 'ember',
demo: 'https://ember.now-examples.now.sh',
demo: 'https://ember.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/ember.svg',
tagline:
@@ -566,13 +569,14 @@ export const frameworks = [
{
name: 'Vue.js',
slug: 'vue',
demo: 'https://vue.now-examples.now.sh',
demo: 'https://vue.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/vue.svg',
tagline:
'Vue.js is a versatile JavaScript framework that is as approachable as it is performant.',
description: 'A Vue.js app, created with the Vue CLI.',
website: 'https://vuejs.org',
envPrefix: 'VUE_APP_',
detectors: {
every: [
{
@@ -623,7 +627,7 @@ export const frameworks = [
{
name: 'Scully',
slug: 'scully',
demo: 'https://scully.now-examples.now.sh',
demo: 'https://scully.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/scullyio-logo.png',
tagline: 'Scully is a static site generator for Angular.',
@@ -660,7 +664,7 @@ export const frameworks = [
{
name: 'Ionic Angular',
slug: 'ionic-angular',
demo: 'https://ionic-angular.now-examples.now.sh',
demo: 'https://ionic-angular.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/ionic.svg',
tagline:
@@ -707,7 +711,7 @@ export const frameworks = [
{
name: 'Angular',
slug: 'angular',
demo: 'https://angular.now-examples.now.sh',
demo: 'https://angular.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/angular.svg',
tagline:
@@ -768,7 +772,7 @@ export const frameworks = [
{
name: 'Polymer',
slug: 'polymer',
demo: 'https://polymer.now-examples.now.sh',
demo: 'https://polymer.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/polymer.svg',
tagline:
@@ -826,7 +830,7 @@ export const frameworks = [
{
name: 'Svelte',
slug: 'svelte',
demo: 'https://svelte.now-examples.now.sh',
demo: 'https://svelte.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/svelte.svg',
tagline:
@@ -836,6 +840,11 @@ export const frameworks = [
website: 'https://svelte.dev',
detectors: {
every: [
{
path: 'package.json',
matchContent:
'"(dev)?(d|D)ependencies":\\s*{[^}]*"svelte":\\s*".+?"[^}]*}',
},
{
path: 'package.json',
matchContent:
@@ -851,14 +860,14 @@ export const frameworks = [
placeholder: '`npm run build` or `rollup -c`',
},
devCommand: {
value: 'sirv public --single --dev --port $PORT',
value: 'rollup -c -w',
},
outputDirectory: {
value: 'public',
},
},
dependency: 'sirv-cli',
devCommand: 'sirv public --single --dev --port $PORT',
devCommand: 'rollup -c -w',
buildCommand: 'rollup -c',
getOutputDirName: async () => 'public',
defaultRoutes: [
@@ -874,7 +883,7 @@ export const frameworks = [
{
name: 'Ionic React',
slug: 'ionic-react',
demo: 'https://ionic-react.now-examples.now.sh',
demo: 'https://ionic-react.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/ionic.svg',
tagline:
@@ -926,6 +935,7 @@ export const frameworks = [
{
handle: 'filesystem',
},
{ src: '/static/(.*)', status: 404, dest: '/404.html' },
{
src: '/(.*)',
headers: { 'cache-control': 's-maxage=0' },
@@ -936,13 +946,14 @@ export const frameworks = [
{
name: 'Create React App',
slug: 'create-react-app',
demo: 'https://react-functions.now-examples.now.sh',
demo: 'https://react-functions.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/react.svg',
tagline: 'Create React App allows you to get going with React in no time.',
description:
'A React app, bootstrapped with create-react-app, and a Serverless Function API.',
website: 'https://create-react-app.dev',
envPrefix: 'REACT_APP_',
detectors: {
some: [
{
@@ -993,6 +1004,7 @@ export const frameworks = [
{
handle: 'filesystem',
},
{ src: '/static/(.*)', status: 404, dest: '/404.html' },
{
src: '/(.*)',
headers: { 'cache-control': 's-maxage=0' },
@@ -1003,7 +1015,7 @@ export const frameworks = [
{
name: 'Gridsome',
slug: 'gridsome',
demo: 'https://gridsome.now-examples.now.sh',
demo: 'https://gridsome.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/gridsome.svg',
tagline:
@@ -1041,7 +1053,7 @@ export const frameworks = [
{
name: 'UmiJS',
slug: 'umijs',
demo: 'https://umijs.now-examples.now.sh',
demo: 'https://umijs.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/umi.svg',
tagline:
@@ -1088,7 +1100,7 @@ export const frameworks = [
{
name: 'Sapper',
slug: 'sapper',
demo: 'https://sapper.now-examples.now.sh',
demo: 'https://sapper.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/svelte.svg',
tagline:
@@ -1126,7 +1138,7 @@ export const frameworks = [
{
name: 'Saber',
slug: 'saber',
demo: 'https://saber.now-examples.now.sh',
demo: 'https://saber.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/saber.svg',
tagline:
@@ -1178,7 +1190,7 @@ export const frameworks = [
{
name: 'Stencil',
slug: 'stencil',
demo: 'https://stencil.now-examples.now.sh',
demo: 'https://stencil.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/stencil.svg',
tagline:
@@ -1240,13 +1252,14 @@ export const frameworks = [
{
name: 'Nuxt.js',
slug: 'nuxtjs',
demo: 'https://nuxtjs.now-examples.now.sh',
demo: 'https://nuxtjs.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/nuxt.svg',
tagline:
'Nuxt.js is the web comprehensive framework that lets you dream big with Vue.js.',
description: 'A Nuxt.js app, bootstrapped with create-nuxt-app.',
website: 'https://nuxtjs.org',
envPrefix: 'NUXT_ENV_',
detectors: {
every: [
{
@@ -1298,12 +1311,13 @@ export const frameworks = [
{
name: 'RedwoodJS',
slug: 'redwoodjs',
demo: 'https://redwoodjs.now-examples.now.sh',
demo: 'https://redwoodjs.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/redwoodjs.svg',
tagline: 'RedwoodJS is a full-stack framework for the Jamstack.',
description: 'A RedwoodJS app, bootstraped with create-redwood-app.',
website: 'https://redwoodjs.com',
envPrefix: 'REDWOOD_ENV_',
useRuntime: { src: 'package.json', use: '@vercel/redwood' },
ignoreRuntimes: ['@vercel/node'],
detectors: {
@@ -1336,7 +1350,7 @@ export const frameworks = [
{
name: 'Hugo',
slug: 'hugo',
demo: 'https://hugo.now-examples.now.sh',
demo: 'https://hugo.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/hugo.svg',
tagline:
@@ -1391,7 +1405,7 @@ export const frameworks = [
{
name: 'Jekyll',
slug: 'jekyll',
demo: 'https://jekyll.now-examples.vercel.app',
demo: 'https://jekyll.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/jekyll.svg',
tagline:
@@ -1433,7 +1447,7 @@ export const frameworks = [
{
name: 'Brunch',
slug: 'brunch',
demo: 'https://brunch.now-examples.now.sh',
demo: 'https://brunch.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/brunch.svg',
tagline:
@@ -1468,7 +1482,7 @@ export const frameworks = [
{
name: 'Middleman',
slug: 'middleman',
demo: 'https://middleman.now-examples.vercel.app',
demo: 'https://middleman.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/middleman.svg',
tagline:
@@ -1504,7 +1518,7 @@ export const frameworks = [
{
name: 'Zola',
slug: 'zola',
demo: 'https://zola.now-examples.vercel.app',
demo: 'https://zola.examples.vercel.com',
logo:
'https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/zola.png',
tagline: 'Everything you need to make a static site engine in one binary.',

View File

@@ -75,6 +75,11 @@ export interface Framework {
* @example 1
*/
sort?: number;
/**
* The environment variable prefix used to inline values into the browser bundle.
* @example "NEXT_PUBLIC_"
*/
envPrefix?: string;
/**
* Runtime configuration required to run the framework in Vercel
*/

View File

@@ -73,6 +73,7 @@ const Schema = {
tagline: { type: 'string' },
website: { type: 'string' },
description: { type: 'string' },
envPrefix: { type: 'string' },
useRuntime: {
type: 'object',
required: ['src', 'use'],
@@ -136,6 +137,7 @@ const Schema = {
cachePattern: { type: 'string' },
buildCommand: { type: ['string', 'null'] },
devCommand: { type: ['string', 'null'] },
defaultVersion: { type: 'string' },
},
},
};

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/go",
"version": "1.2.2-canary.0",
"version": "1.2.2",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node-bridge",
"version": "1.3.3-canary.0",
"version": "1.3.3",
"license": "MIT",
"main": "./index.js",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "1.9.1-canary.3",
"version": "1.9.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",

View File

@@ -129,7 +129,7 @@ async function compile(
watch: string[];
}> {
const inputFiles = new Set<string>([entrypointPath]);
const preparedFiles: Files = {};
const sourceCache = new Map<string, string | Buffer | null>();
const fsCache = new Map<string, File>();
const tsCompiled = new Set<string>();
@@ -149,17 +149,7 @@ async function compile(
const { fsPath } = entry;
const relPath = relative(baseDir, fsPath);
fsCache.set(relPath, entry);
const stream = entry.toStream();
const { data } = await FileBlob.fromStream({ stream });
if (relPath.endsWith('.ts') || relPath.endsWith('.tsx')) {
sourceCache.set(
relPath,
compileTypeScript(fsPath, data.toString())
);
} else {
sourceCache.set(relPath, data);
}
inputFiles.add(fsPath);
preparedFiles[relPath] = entry;
})
);
}
@@ -170,8 +160,6 @@ async function compile(
[...inputFiles].map(p => relative(workPath, p)).join(', ')
);
const preparedFiles: Files = {};
let tsCompile: Register;
function compileTypeScript(path: string, source: string): string {
const relPath = relative(baseDir, path);

View File

@@ -0,0 +1 @@
const foo = 'hello TS!';

View File

@@ -0,0 +1,8 @@
const { readFileSync } = require('fs');
const { join } = require('path');
module.exports = (req, res) => {
const file = join(__dirname, 'file.ts');
const content = readFileSync(file, 'utf8');
res.end(content);
};

View File

@@ -21,6 +21,13 @@
"config": {
"includeFiles": "templates/accepts-string.edge"
}
},
{
"src": "include-ts-file.js",
"use": "@vercel/node",
"config": {
"includeFiles": "file.ts"
}
}
],
"probes": [
@@ -35,6 +42,10 @@
{
"path": "/root.js",
"mustContain": "hello Root!"
},
{
"path": "/include-ts-file.js",
"mustContain": "hello TS!"
}
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/python",
"version": "2.0.1-canary.0",
"version": "2.0.1",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/routing-utils",
"version": "1.10.1-canary.0",
"version": "1.10.2-canary.2",
"description": "Vercel routing utilities",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",

View File

@@ -1,9 +1,65 @@
export const hasSchema = {
description: 'An array of requirements that are needed to match',
type: 'array',
maxItems: 16,
items: {
anyOf: [
{
type: 'object',
additionalProperties: false,
required: ['type', 'value'],
properties: {
type: {
description: 'The type of request element to check',
type: 'string',
enum: ['host'],
},
value: {
description:
'A regular expression used to match the value. Named groups can be used in the destination',
type: 'string',
maxLength: 4096,
},
},
},
{
type: 'object',
additionalProperties: false,
required: ['type', 'key'],
properties: {
type: {
description: 'The type of request element to check',
type: 'string',
enum: ['header', 'cookie', 'query'],
},
key: {
description:
'The name of the element contained in the particular type',
type: 'string',
maxLength: 4096,
},
value: {
description:
'A regular expression used to match the value. Named groups can be used in the destination',
type: 'string',
maxLength: 4096,
},
},
},
],
},
} as const;
/**
* An ajv schema for the routes array
*/
export const routesSchema = {
type: 'array',
maxItems: 1024,
deprecated: true,
description:
'A list of routes objects used to rewrite paths to point towards other internal or external paths',
example: [{ dest: 'https://docs.example.com', src: '/docs' }],
items: {
anyOf: [
{
@@ -91,6 +147,7 @@ export const routesSchema = {
},
},
},
has: hasSchema,
},
},
{
@@ -107,70 +164,90 @@ export const routesSchema = {
},
],
},
};
} as const;
export const rewritesSchema = {
type: 'array',
maxItems: 1024,
description: 'A list of rewrite definitions.',
items: {
type: 'object',
additionalProperties: false,
required: ['source', 'destination'],
properties: {
source: {
description:
'A pattern that matches each incoming pathname (excluding querystring).',
type: 'string',
maxLength: 4096,
},
destination: {
description:
'An absolute pathname to an existing resource or an external URL.',
type: 'string',
maxLength: 4096,
},
has: hasSchema,
},
},
};
} as const;
export const redirectsSchema = {
title: 'Redirects',
type: 'array',
maxItems: 1024,
description: 'A list of redirect definitions.',
items: {
type: 'object',
additionalProperties: false,
required: ['source', 'destination'],
properties: {
source: {
description:
'A pattern that matches each incoming pathname (excluding querystring).',
type: 'string',
maxLength: 4096,
},
destination: {
description:
'A location destination defined as an absolute pathname or external URL.',
type: 'string',
maxLength: 4096,
},
permanent: {
description:
'A boolean to toggle between permanent and temporary redirect. When `true`, the status code is `308`. When `false` the status code is `307`.',
type: 'boolean',
},
statusCode: {
private: true,
type: 'integer',
minimum: 100,
maximum: 999,
},
has: hasSchema,
},
},
};
} as const;
export const headersSchema = {
type: 'array',
maxItems: 1024,
description: 'A list of header definitions.',
items: {
type: 'object',
additionalProperties: false,
required: ['source', 'headers'],
properties: {
source: {
description:
'A pattern that matches each incoming pathname (excluding querystring)',
type: 'string',
maxLength: 4096,
},
headers: {
description:
'An array of key/value pairs representing each response header.',
type: 'array',
maxItems: 1024,
items: {
@@ -189,14 +266,19 @@ export const headersSchema = {
},
},
},
has: hasSchema,
},
},
};
} as const;
export const cleanUrlsSchema = {
description:
'When set to `true`, all HTML files and Serverless Functions will have their extension removed. When visiting a path that ends with the extension, a 308 response will redirect the client to the extensionless path.',
type: 'boolean',
};
} as const;
export const trailingSlashSchema = {
description:
'When `false`, visiting a path that ends with a forward slash will respond with a `308` status code and redirect to the path without the trailing slash.',
type: 'boolean',
};
} as const;

View File

@@ -65,6 +65,10 @@ export function convertRedirects(
headers: { Location: loc },
status,
};
if (r.has) {
route.has = r.has;
}
return route;
} catch (e) {
throw new Error(`Failed to parse redirect: ${JSON.stringify(r)}`);
@@ -78,6 +82,10 @@ export function convertRewrites(rewrites: Rewrite[]): Route[] {
try {
const dest = replaceSegments(segments, r.destination);
const route: Route = { src, dest, check: true };
if (r.has) {
route.has = r.has;
}
return route;
} catch (e) {
throw new Error(`Failed to parse rewrite: ${JSON.stringify(r)}`);
@@ -112,6 +120,10 @@ export function convertHeaders(headers: Header[]): Route[] {
headers: obj,
continue: true,
};
if (h.has) {
route.has = h.has;
}
return route;
});
}

View File

@@ -9,6 +9,18 @@ export type RouteApiError = {
errors?: string[]; // array of all error messages
};
export type HasField = Array<
| {
type: 'host';
value: string;
}
| {
type: 'header' | 'cookie' | 'query';
key: string;
value?: string;
}
>;
export type Source = {
src: string;
dest?: string;
@@ -18,6 +30,7 @@ export type Source = {
check?: boolean;
important?: boolean;
status?: number;
has?: HasField;
locale?: {
redirect?: Record<string, string>;
cookie?: string;
@@ -67,6 +80,7 @@ export interface VercelConfig {
export interface Rewrite {
source: string;
destination: string;
has?: HasField;
}
export interface Redirect {
@@ -74,11 +88,13 @@ export interface Redirect {
destination: string;
permanent?: boolean;
statusCode?: number;
has?: HasField;
}
export interface Header {
source: string;
headers: HeaderKeyValue[];
has?: HasField;
}
export interface HeaderKeyValue {

View File

@@ -82,6 +82,16 @@ describe('normalizeRoutes', () => {
dest: '/404',
status: 404,
},
{
src: '^/hello$',
dest: '/another',
has: [
{ type: 'header', key: 'x-rewrite' },
{ type: 'cookie', key: 'loggedIn', value: 'yup' },
{ type: 'query', key: 'authorized', value: 'yup' },
{ type: 'host', value: 'vercel.com' },
],
},
];
assertValid(routes);
@@ -942,11 +952,32 @@ describe('getTransformedRoutes', () => {
rewrites: [
{ source: '/page', destination: '/page.html' },
{ source: '/home', destination: '/index.html' },
{
source: '/home',
destination: '/another',
has: [
{ type: 'header', key: 'x-rewrite' },
{ type: 'cookie', key: 'loggedIn', value: 'yup' },
{ type: 'query', key: 'authorized', value: 'yup' },
{ type: 'host', value: 'vercel.com' },
],
},
],
redirects: [
{ source: '/version1', destination: '/api1.py' },
{ source: '/version2', destination: '/api2.py', statusCode: 302 },
{ source: '/version3', destination: '/api3.py', permanent: true },
{
source: '/version4',
destination: '/api4.py',
has: [
{ type: 'header', key: 'x-redirect' },
{ type: 'cookie', key: 'loggedIn', value: 'yup' },
{ type: 'query', key: 'authorized', value: 'yup' },
{ type: 'host', value: 'vercel.com' },
],
permanent: false,
},
],
headers: [
{
@@ -971,6 +1002,21 @@ describe('getTransformedRoutes', () => {
},
],
},
{
source: '/add-header',
has: [
{ type: 'header', key: 'x-header' },
{ type: 'cookie', key: 'loggedIn', value: 'yup' },
{ type: 'query', key: 'authorized', value: 'yup' },
{ type: 'host', value: 'vercel.com' },
],
headers: [
{
key: 'Cache-Control',
value: 'max-age=forever',
},
],
},
],
trailingSlashSchema: false,
};

View File

@@ -198,6 +198,26 @@ test('convertRedirects', () => {
source: '/hello/:world',
destination: '/somewhere?else={:world}',
},
{
source: '/hello',
destination: '/another',
has: [
{
type: 'header',
key: 'x-rewrite',
},
{
type: 'cookie',
key: 'loggedIn',
value: '1',
},
{
type: 'host',
value: 'vercel.com',
},
],
permanent: false,
},
]);
const expected = [
@@ -288,6 +308,28 @@ test('convertRedirects', () => {
src: '^\\/hello(?:\\/([^\\/]+?))$',
status: 308,
},
{
has: [
{
key: 'x-rewrite',
type: 'header',
},
{
key: 'loggedIn',
type: 'cookie',
value: '1',
},
{
type: 'host',
value: 'vercel.com',
},
],
headers: {
Location: '/another',
},
src: '^\\/hello$',
status: 307,
},
];
deepEqual(actual, expected);
@@ -309,6 +351,7 @@ test('convertRedirects', () => {
['/optional', '/optional/1'],
['/feature-first', '/feature-second'],
['/hello/world', '/hello/again'],
['/hello'],
];
const mustNotMatch = [
@@ -328,6 +371,7 @@ test('convertRedirects', () => {
['/optionalnope', '/optionally'],
['/feature/first', '/feature'],
['/hello', '/hello/another/one'],
['/helloooo'],
];
assertRegexMatches(actual, mustMatch, mustNotMatch);
@@ -384,6 +428,25 @@ test('convertRewrites', () => {
source: '/hello/:world',
destination: '/somewhere?else={:world}',
},
{
source: '/hello',
destination: '/another',
has: [
{
type: 'header',
key: 'x-rewrite',
},
{
type: 'cookie',
key: 'loggedIn',
value: '1',
},
{
type: 'host',
value: 'vercel.com',
},
],
},
]);
const expected = [
@@ -470,6 +533,26 @@ test('convertRewrites', () => {
src: '^\\/hello(?:\\/([^\\/]+?))$',
check: true,
},
{
check: true,
dest: '/another',
has: [
{
key: 'x-rewrite',
type: 'header',
},
{
key: 'loggedIn',
type: 'cookie',
value: '1',
},
{
type: 'host',
value: 'vercel.com',
},
],
src: '^\\/hello$',
},
];
deepEqual(actual, expected);
@@ -493,6 +576,7 @@ test('convertRewrites', () => {
['/hello/post-123.html', '/post-123.html'],
['/feature-first', '/feature-second'],
['/hello/world', '/hello/again'],
['/hello'],
];
const mustNotMatch = [
@@ -514,6 +598,7 @@ test('convertRewrites', () => {
['/hello/post.html'],
['/feature/first', '/feature'],
['/hello', '/hello/another/one'],
['/hllooo'],
];
assertRegexMatches(actual, mustMatch, mustNotMatch);
@@ -610,6 +695,30 @@ test('convertHeaders', () => {
},
],
},
{
source: '/hello',
headers: [
{
key: 'x-header',
value: 'something',
},
],
has: [
{
key: 'x-rewrite',
type: 'header',
},
{
key: 'loggedIn',
type: 'cookie',
value: '1',
},
{
type: 'host',
value: 'vercel.com',
},
],
},
]);
const expected = [
@@ -647,6 +756,28 @@ test('convertHeaders', () => {
},
src: '^\\/like\\/params(?:\\/([^\\/]+?))$',
},
{
continue: true,
has: [
{
key: 'x-rewrite',
type: 'header',
},
{
key: 'loggedIn',
type: 'cookie',
value: '1',
},
{
type: 'host',
value: 'vercel.com',
},
],
headers: {
'x-header': 'something',
},
src: '^\\/hello$',
},
];
deepEqual(actual, expected);
@@ -656,6 +787,7 @@ test('convertHeaders', () => {
['404.html'],
['/blog/first-post', '/blog/another/one'],
['/like/params/first', '/like/params/second'],
['/hello'],
];
const mustNotMatch = [
@@ -663,6 +795,7 @@ test('convertHeaders', () => {
['403.html', '500.html'],
['/blogg', '/random'],
['/non-match', '/like/params', '/like/params/'],
['/hellooo'],
];
assertRegexMatches(actual, mustMatch, mustNotMatch);

View File

@@ -1,7 +1,7 @@
{
"name": "@vercel/ruby",
"author": "Nathan Cahill <nathan@nathancahill.com>",
"version": "1.2.6-canary.1",
"version": "1.2.6",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",

View File

@@ -1798,13 +1798,6 @@
resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-4.1.1.tgz#b2d87a5e3df8d4b18ca426c5105cd701c2306d40"
integrity sha512-8mNEUG6diOrI6pMqOHrHPDBB1JsrpedeMK9AWGzVCQ7StRRribiT9BRvUmF8aUws9iBbVlgVekOT5Sgzc1MTKw==
"@types/micro@7.3.3":
version "7.3.3"
resolved "https://registry.yarnpkg.com/@types/micro/-/micro-7.3.3.tgz#31ead8df18ac10d58b7be1186d4b2d977b13a938"
integrity sha512-I3n3QYT7lqAxkyAoTZyg1yrvo38BxW/7ZafLAXZF/zZQOnAnQzg6j9XOuSmUEL5GGVFKWw4iqM+ZLnqb2154TA==
dependencies:
"@types/node" "*"
"@types/mime-types@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73"
@@ -4041,6 +4034,11 @@ defer-to-connect@^1.0.1:
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==
define-lazy-prop@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
define-properties@^1.1.2, define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@@ -4107,11 +4105,6 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
depd@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
integrity sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
@@ -5645,16 +5638,6 @@ http-cache-semantics@^4.0.0:
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
http-errors@1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
integrity sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=
dependencies:
depd "1.1.1"
inherits "2.0.3"
setprototypeof "1.0.3"
statuses ">= 1.3.1 < 2"
http-errors@1.7.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
@@ -5762,11 +5745,6 @@ husky@3.0.4:
run-node "^1.0.0"
slash "^3.0.0"
iconv-lite@0.4.19:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -5890,11 +5868,6 @@ inherits@2.0.1:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
version "1.3.7"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
@@ -6079,6 +6052,11 @@ is-directory@^0.3.1:
resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
is-docker@^2.0.0, is-docker@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156"
integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==
is-error@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/is-error/-/is-error-2.2.2.tgz#c10ade187b3c93510c5470a5567833ee25649843"
@@ -6281,7 +6259,7 @@ is-ssh@^1.3.0:
dependencies:
protocols "^1.1.0"
is-stream@1.1.0, is-stream@^1.0.1, is-stream@^1.1.0:
is-stream@^1.0.1, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
@@ -6335,6 +6313,13 @@ is-wsl@^1.1.0:
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
dependencies:
is-docker "^2.0.0"
is-yarn-global@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
@@ -7569,16 +7554,6 @@ merge2@^1.2.3, merge2@^1.3.0:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
micro@9.1.2:
version "9.1.2"
resolved "https://registry.yarnpkg.com/micro/-/micro-9.1.2.tgz#fabb79ca60bf7696551943d4b932dcd9eddbbc96"
integrity sha512-wteCRJBbzcatTkUSsD/IKqMiLkq1yK3KL5MlimiFxT8SWbVtD3R6CirpglildwRUvadItM9pScLM2AiBpqiCuQ==
dependencies:
content-type "1.0.4"
is-stream "1.1.0"
mri "1.1.0"
raw-body "2.3.2"
micro@9.3.5-canary.3:
version "9.3.5-canary.3"
resolved "https://registry.yarnpkg.com/micro/-/micro-9.3.5-canary.3.tgz#e957598abb9ab05aea8453e0150a521fe22135c3"
@@ -7775,11 +7750,6 @@ move-concurrently@^1.0.1:
rimraf "^2.5.4"
run-queue "^1.0.3"
mri@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.0.tgz#5c0a3f29c8ccffbbb1ec941dcec09d71fa32f36a"
integrity sha1-XAo/KcjM/7ux7JQdzsCdcfoy82o=
mri@1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.5.tgz#ce21dba2c69f74a9b7cf8a1ec62307e089e223e0"
@@ -8269,6 +8239,15 @@ onetime@^5.1.0:
dependencies:
mimic-fn "^2.1.0"
open@8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/open/-/open-8.0.2.tgz#8c3e95cce93ba2fc8d99968ee8bfefecdb50b84f"
integrity sha512-NV5QmWJrTaNBLHABJyrb+nd5dXI5zfea/suWawBhkHzAbVhLLiJdrqMgxMypGK9Eznp2Ltoh7SAVkQ3XAucX7Q==
dependencies:
define-lazy-prop "^2.0.0"
is-docker "^2.1.1"
is-wsl "^2.2.0"
opencollective-postinstall@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
@@ -8981,16 +8960,6 @@ range-parser@1.2.0:
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=
raw-body@2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
integrity sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=
dependencies:
bytes "3.0.0"
http-errors "1.6.2"
iconv-lite "0.4.19"
unpipe "1.0.0"
raw-body@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c"
@@ -9655,11 +9624,6 @@ set-value@^2.0.0, set-value@^2.0.1:
is-plain-object "^2.0.3"
split-string "^3.0.1"
setprototypeof@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
integrity sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=
setprototypeof@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
@@ -9971,7 +9935,7 @@ static-extend@^0.1.1:
define-property "^0.2.5"
object-copy "^0.1.0"
"statuses@>= 1.2.1 < 2", "statuses@>= 1.3.1 < 2", "statuses@>= 1.5.0 < 2":
"statuses@>= 1.2.1 < 2", "statuses@>= 1.5.0 < 2":
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=