mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-24 03:39:11 +00:00
Compare commits
18 Commits
@vercel/ru
...
@vercel/bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41ce96a2db | ||
|
|
a4b4397151 | ||
|
|
473159f1da | ||
|
|
c6267b5acc | ||
|
|
8a919043a2 | ||
|
|
c2e7be80e8 | ||
|
|
d95175253a | ||
|
|
e2106d12f6 | ||
|
|
84f95465f7 | ||
|
|
0ae74546a6 | ||
|
|
be2ae2c539 | ||
|
|
4969a65209 | ||
|
|
7275878b2b | ||
|
|
53d429e3f5 | ||
|
|
3f1384bd1a | ||
|
|
703b2649bc | ||
|
|
2497909d9b | ||
|
|
0ad45c8b13 |
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/cgi",
|
||||
"version": "1.0.7-canary.1",
|
||||
"version": "1.0.7",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
141
packages/cli/src/commands/login.ts
Normal file
141
packages/cli/src/commands/login.ts
Normal 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;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
112
packages/cli/src/util/login/email.ts
Normal file
112
packages/cli/src/util/login/email.ts
Normal 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;
|
||||
}
|
||||
@@ -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}`, {
|
||||
|
||||
93
packages/cli/src/util/login/sso.ts
Normal file
93
packages/cli/src/util/login/sso.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
13
packages/cli/src/util/login/types.ts
Normal file
13
packages/cli/src/util/login/types.ts
Normal 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;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { cyan } from 'chalk';
|
||||
import chars from './chars';
|
||||
|
||||
const ok = msg => `${cyan(chars.tick)} ${msg}`;
|
||||
|
||||
export default ok;
|
||||
6
packages/cli/src/util/output/ok.ts
Normal file
6
packages/cli/src/util/output/ok.ts
Normal 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;
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
33
packages/cli/test/unit.js
vendored
33
packages/cli/test/unit.js
vendored
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/node-bridge",
|
||||
"version": "1.3.3-canary.0",
|
||||
"version": "1.3.3",
|
||||
"license": "MIT",
|
||||
"main": "./index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
1
packages/node/test/fixtures/09-include-files/file.ts
vendored
Normal file
1
packages/node/test/fixtures/09-include-files/file.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
const foo = 'hello TS!';
|
||||
8
packages/node/test/fixtures/09-include-files/include-ts-file.js
vendored
Normal file
8
packages/node/test/fixtures/09-include-files/include-ts-file.js
vendored
Normal 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);
|
||||
};
|
||||
@@ -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!"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
46
packages/routing-utils/test/index.spec.js
vendored
46
packages/routing-utils/test/index.spec.js
vendored
@@ -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,
|
||||
};
|
||||
|
||||
133
packages/routing-utils/test/superstatic.spec.js
vendored
133
packages/routing-utils/test/superstatic.spec.js
vendored
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
92
yarn.lock
92
yarn.lock
@@ -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=
|
||||
|
||||
Reference in New Issue
Block a user