Compare commits

..

6 Commits

Author SHA1 Message Date
Nathan Rajlich
037eccf94c . 2022-06-17 00:06:48 -07:00
Nathan Rajlich
7502e1d4c1 Ignore .vercel/** in glob 2022-06-16 23:48:48 -07:00
Nathan Rajlich
97c8f7f443 Force isMonorepo to false 2022-06-16 23:33:57 -07:00
Nathan Rajlich
568c9db32c more debug 2022-06-16 21:15:30 -07:00
Nathan Rajlich
e459f6bdfa JSON 2022-06-16 20:17:38 -07:00
Nathan Rajlich
93585ba60b [next] Debug prepareCache() 2022-06-16 19:01:59 -07:00
148 changed files with 9616 additions and 2913 deletions

View File

@@ -1,4 +0,0 @@
# https://prettier.io/docs/en/ignore.html
# ignore this file with an intentional syntax error
packages/cli/test/dev/fixtures/edge-function-error/api/edge-error-syntax.js

View File

@@ -1,3 +0,0 @@
module.exports = {
extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
};

View File

@@ -1,11 +1,7 @@
node_modules
.cache
.env
.vercel
.output
/build/
/public/build
/api/index.js
/api/index.js.map
public/build
api/_build

View File

@@ -0,0 +1,5 @@
const { createRequestHandler } = require("@remix-run/vercel");
module.exports = createRequestHandler({
build: require("./_build")
});

View File

@@ -1,4 +1,4 @@
import { RemixBrowser } from "@remix-run/react";
import { hydrate } from "react-dom";
import { RemixBrowser } from "remix";
hydrate(<RemixBrowser />, document);

View File

@@ -1,6 +1,6 @@
import type { EntryContext } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { renderToString } from "react-dom/server";
import { RemixServer } from "remix";
import type { EntryContext } from "remix";
export default function handleRequest(
request: Request,
@@ -16,6 +16,6 @@ export default function handleRequest(
return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders,
headers: responseHeaders
});
}

View File

@@ -1,4 +1,3 @@
import type { LinksFunction, MetaFunction } from "@remix-run/node";
import {
Link,
Links,
@@ -7,13 +6,14 @@ import {
Outlet,
Scripts,
ScrollRestoration,
useCatch,
} from "@remix-run/react";
useCatch
} from "remix";
import type { LinksFunction } from "remix";
import darkStylesUrl from "~/styles/dark.css";
import globalStylesUrl from "~/styles/global.css";
import darkStylesUrl from "~/styles/dark.css";
// https://remix.run/api/conventions#links
// https://remix.run/api/app#links
export let links: LinksFunction = () => {
return [
{ rel: "stylesheet", href: globalStylesUrl },
@@ -25,12 +25,6 @@ export let links: LinksFunction = () => {
];
};
// https://remix.run/api/conventions#meta
export let meta: MetaFunction = () => ({
charset: "utf-8",
viewport: "width=device-width,initial-scale=1",
});
// https://remix.run/api/conventions#default-export
// https://remix.run/api/conventions#route-filenames
export default function App() {
@@ -43,7 +37,7 @@ export default function App() {
);
}
// https://remix.run/api/conventions#errorboundary
// https://remix.run/docs/en/v1/api/conventions#errorboundary
export function ErrorBoundary({ error }: { error: Error }) {
console.error(error);
return (
@@ -63,7 +57,7 @@ export function ErrorBoundary({ error }: { error: Error }) {
);
}
// https://remix.run/api/conventions#catchboundary
// https://remix.run/docs/en/v1/api/conventions#catchboundary
export function CatchBoundary() {
let caught = useCatch();
@@ -109,6 +103,8 @@ function Document({
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
{title ? <title>{title}</title> : null}
<Meta />
<Links />
@@ -117,7 +113,7 @@ function Document({
{children}
<ScrollRestoration />
<Scripts />
<LiveReload />
{process.env.NODE_ENV === "development" && <LiveReload />}
</body>
</html>
);

View File

@@ -1,5 +1,5 @@
import type { MetaFunction, LinksFunction } from "@remix-run/node";
import { Outlet } from "@remix-run/react";
import { Outlet } from "remix";
import type { MetaFunction, LinksFunction } from "remix";
import stylesUrl from "~/styles/demos/about.css";

View File

@@ -1,4 +1,4 @@
import { Link } from "@remix-run/react";
import { Link } from "remix";
export default function AboutIndex() {
return (

View File

@@ -1,4 +1,4 @@
import { Link } from "@remix-run/react";
import { Link } from "remix";
export default function AboutIndex() {
return (

View File

@@ -1,7 +1,6 @@
import type { ActionFunction } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
import { useEffect, useRef } from "react";
import type { ActionFunction } from "remix";
import { Form, json, useActionData, redirect } from "remix";
export function meta() {
return { title: "Actions Demo" };

View File

@@ -1,8 +1,8 @@
import type { MetaFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Link, Outlet, useCatch, useLoaderData } from "@remix-run/react";
import { useCatch, Link, json, useLoaderData, Outlet } from "remix";
export let meta: MetaFunction = () => ({ title: "Boundaries Demo" });
export function meta() {
return { title: "Boundaries Demo" };
}
export default function Boundaries() {
return (

View File

@@ -1,6 +1,5 @@
import type { LoaderFunction, MetaFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Link, useCatch, useLoaderData } from "@remix-run/react";
import { useCatch, Link, json, useLoaderData } from "remix";
import type { LoaderFunction, MetaFunction } from "remix";
// The `$` in route filenames becomes a pattern that's parsed from the URL and
// passed to your loaders so you can look up data.

View File

@@ -1,6 +1,5 @@
import type { LoaderFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Link, Outlet, useCatch, useLoaderData } from "@remix-run/react";
import { useCatch, Link, json, useLoaderData, Outlet } from "remix";
import type { LoaderFunction } from "remix";
export default function Boundaries() {
return (

View File

@@ -1,6 +1,5 @@
import type { MetaFunction, LoaderFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Link, useLoaderData } from "@remix-run/react";
import type { MetaFunction, LoaderFunction } from "remix";
import { useLoaderData, json, Link } from "remix";
type IndexData = {
resources: Array<{ name: string; url: string }>;

8345
examples/remix/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +1,26 @@
{
"private": true,
"sideEffects": false,
"name": "remix-app-template",
"description": "",
"license": "",
"scripts": {
"build": "remix build",
"dev": "remix dev"
"dev": "remix dev",
"postinstall": "remix setup node"
},
"dependencies": {
"@remix-run/node": "^1.5.1",
"@remix-run/react": "^1.5.1",
"@remix-run/vercel": "^1.5.1",
"@vercel/node": "^2.0.0",
"@remix-run/react": "^1.0.6",
"react": "^17.0.2",
"react-dom": "^17.0.2"
"react-dom": "^17.0.2",
"remix": "^1.0.6",
"@remix-run/serve": "^1.0.6",
"@remix-run/vercel": "^1.0.6"
},
"devDependencies": {
"@remix-run/dev": "^1.5.1",
"@remix-run/eslint-config": "^1.5.1",
"@remix-run/serve": "^1.5.1",
"@types/react": "^17.0.45",
"@types/react-dom": "^17.0.17",
"eslint": "^8.15.0",
"typescript": "^4.6.4"
"@remix-run/dev": "^1.0.6",
"@types/react": "^17.0.24",
"@types/react-dom": "^17.0.9",
"typescript": "^4.1.2"
},
"engines": {
"node": ">=14"
}
"sideEffects": false
}

View File

@@ -1,15 +1,9 @@
/**
* @type {import('@remix-run/dev').AppConfig}
* @type {import('@remix-run/dev/config').AppConfig}
*/
module.exports = {
serverBuildTarget: "vercel",
// When running locally in development mode, we use the built in remix
// server. This does not understand the vercel lambda module format,
// so we default back to the standard build output.
server: process.env.NODE_ENV === "development" ? undefined : "./server.js",
ignoredRouteFiles: ["**/.*"],
// appDirectory: "app",
// assetsBuildDirectory: "public/build",
// serverBuildPath: "api/index.js",
// publicPath: "/build/",
appDirectory: "app",
browserBuildDirectory: "public/build",
publicPath: "/build/",
serverBuildDirectory: "api/_build"
};

View File

@@ -1,4 +0,0 @@
import * as build from "@remix-run/dev/server-build";
import { createRequestHandler } from "@remix-run/vercel";
export default createRequestHandler({ build, mode: process.env.NODE_ENV });

View File

@@ -19,6 +19,7 @@
"prettier-plugin-svelte": "^2.5.0",
"svelte": "^3.46.0",
"svelte-check": "^2.2.6",
"svelte-preprocess": "^4.10.6",
"typescript": "~4.6.2"
},
"type": "module",

View File

@@ -7,12 +7,14 @@
let analyticsId = import.meta.env.VERCEL_ANALYTICS_ID;
$: if (browser && analyticsId) {
webVitals({
path: $page.url.pathname,
params: $page.params,
analyticsId
})
if (browser && analyticsId) {
page.subscribe(({ url, params }) =>
webVitals({
path: url.pathname,
params,
analyticsId
})
);
}
</script>

View File

@@ -1,18 +1,19 @@
import adapter from '@sveltejs/adapter-auto';
import preprocess from 'svelte-preprocess';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: preprocess({
replace: [
['import.meta.env.VERCEL_ANALYTICS_ID', JSON.stringify(process.env.VERCEL_ANALYTICS_ID)]
]
}),
kit: {
adapter: adapter(),
// Override http methods in the Todo forms
methodOverride: {
allowed: ['PATCH', 'DELETE']
},
vite: {
define: {
'import.meta.env.VERCEL_ANALYTICS_ID': JSON.stringify(process.env.VERCEL_ANALYTICS_ID)
}
}
}
};

View File

@@ -31,7 +31,7 @@
"prettier": "2.6.2",
"ts-eager": "2.0.2",
"ts-jest": "28.0.5",
"turbo": "1.3.1"
"turbo": "1.2.14"
},
"scripts": {
"lerna": "lerna",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "4.2.1",
"version": "4.1.1-canary.1",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -31,7 +31,7 @@
"@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0",
"@types/yazl": "2.4.2",
"@vercel/frameworks": "1.0.2",
"@vercel/frameworks": "1.0.2-canary.0",
"@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1",
"async-retry": "1.2.3",

View File

@@ -61,13 +61,6 @@ export interface SpawnOptionsExtended extends SpawnOptions {
* Pretty formatted command that is being spawned for logging purposes.
*/
prettyCommand?: string;
/**
* Returns instead of throwing an error when the process exits with a
* non-0 exit code. When relevant, the returned object will include
* the error code, stdout and stderr.
*/
ignoreNon0Exit?: boolean;
}
export function spawnAsync(
@@ -86,7 +79,7 @@ export function spawnAsync(
child.on('error', reject);
child.on('close', (code, signal) => {
if (code === 0 || opts.ignoreNon0Exit) {
if (code === 0) {
return resolve();
}
@@ -130,24 +123,24 @@ export function execAsync(
child.on('error', reject);
child.on('close', (code, signal) => {
if (code === 0 || opts.ignoreNon0Exit) {
return resolve({
code,
stdout: Buffer.concat(stdoutList).toString(),
stderr: Buffer.concat(stderrList).toString(),
});
if (code !== 0) {
const cmd = opts.prettyCommand
? `Command "${opts.prettyCommand}"`
: 'Command';
return reject(
new NowBuildError({
code: `BUILD_UTILS_EXEC_${code || signal}`,
message: `${cmd} exited with ${code || signal}`,
})
);
}
const cmd = opts.prettyCommand
? `Command "${opts.prettyCommand}"`
: 'Command';
return reject(
new NowBuildError({
code: `BUILD_UTILS_EXEC_${code || signal}`,
message: `${cmd} exited with ${code || signal}`,
})
);
return resolve({
code,
stdout: Buffer.concat(stdoutList).toString(),
stderr: Buffer.concat(stderrList).toString(),
});
});
}
);
@@ -173,30 +166,9 @@ export async function execCommand(command: string, options: SpawnOptions = {}) {
return true;
}
export async function getNodeBinPath({
cwd,
}: {
cwd: string;
}): Promise<string | undefined> {
const { code, stdout, stderr } = await execAsync('npm', ['bin'], {
cwd,
prettyCommand: 'npm bin',
// in some rare cases, we saw `npm bin` exit with a non-0 code, but still
// output the right bin path, so we ignore the exit code
ignoreNon0Exit: true,
});
const nodeBinPath = stdout.trim();
if (path.isAbsolute(nodeBinPath)) {
return nodeBinPath;
}
throw new NowBuildError({
code: `BUILD_UTILS_GET_NODE_BIN_PATH`,
message: `Running \`npm bin\` failed to return a valid bin path (code=${code}, stdout=${stdout}, stderr=${stderr})`,
});
export async function getNodeBinPath({ cwd }: { cwd: string }) {
const { stdout } = await execAsync('npm', ['bin'], { cwd });
return stdout.trim();
}
async function chmodPlusX(fsPath: string) {

View File

@@ -1,29 +0,0 @@
import { execAsync, NowBuildError } from '../src';
it('should execute a command', async () => {
const { code, stdout, stderr } = await execAsync('echo', ['hello']);
expect(code).toBe(0);
expect(stdout).toContain('hello');
expect(stderr).toBe('');
});
it('should throw if the command exits with non-0 code', async () => {
await expect(execAsync('find', ['unknown-file'])).rejects.toBeInstanceOf(
NowBuildError
);
});
it('should return if the command exits with non-0 code and ignoreNon0Exit=true', async () => {
const { code, stdout, stderr } = await execAsync('find', ['unknown-file'], {
ignoreNon0Exit: true,
});
expect(code).toBe(process.platform === 'win32' ? 2 : 1);
expect(stdout).toBe('');
expect(stderr).toContain(
process.platform === 'win32'
? 'Parameter format not correct'
: 'No such file or directory'
);
});

View File

@@ -1,21 +0,0 @@
import { spawnAsync, NowBuildError } from '../src';
it('should execute a command', async () => {
// should resolve (it doesn't return anything, so it resolves with "undefined")
await expect(spawnAsync('echo', ['hello'])).resolves.toBeUndefined();
});
it('should throw if the command exits with non-0 code', async () => {
await expect(spawnAsync('find', ['unknown-file'])).rejects.toBeInstanceOf(
NowBuildError
);
});
it('should return if the command exits with non-0 code and ignoreNon0Exit=true', async () => {
// should resolve (it doesn't return anything, so it resolves with "undefined")
await expect(
spawnAsync('find', ['unknown-file'], {
ignoreNon0Exit: true,
})
).resolves.toBeUndefined();
});

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "25.2.3",
"version": "25.1.1-canary.4",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -42,15 +42,15 @@
"node": ">= 14"
},
"dependencies": {
"@vercel/build-utils": "4.2.1",
"@vercel/go": "2.0.3",
"@vercel/next": "3.1.2",
"@vercel/node": "2.3.3",
"@vercel/python": "3.0.3",
"@vercel/redwood": "1.0.4",
"@vercel/remix": "1.0.4",
"@vercel/ruby": "1.3.11",
"@vercel/static-build": "1.0.3",
"@vercel/build-utils": "4.1.1-canary.1",
"@vercel/go": "2.0.2-canary.1",
"@vercel/next": "3.0.3-canary.1",
"@vercel/node": "2.2.1-canary.0",
"@vercel/python": "3.0.2-canary.1",
"@vercel/redwood": "1.0.2-canary.1",
"@vercel/remix": "1.0.2-canary.1",
"@vercel/ruby": "1.3.10-canary.1",
"@vercel/static-build": "1.0.2-canary.1",
"update-notifier": "5.1.0"
},
"devDependencies": {
@@ -95,8 +95,8 @@
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.0.3",
"@vercel/frameworks": "1.0.2",
"@vercel/client": "12.0.2-canary.1",
"@vercel/frameworks": "1.0.2-canary.0",
"@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2",

View File

@@ -10,6 +10,7 @@ import confirm from '../../util/input/confirm';
import findAliasByAliasOrId from '../../util/alias/find-alias-by-alias-or-id';
import { Alias } from '../../types';
import { Output } from '../../util/output';
import { isValidName } from '../../util/is-valid-name';
import { getCommandName } from '../../util/pkg-name';
@@ -70,7 +71,7 @@ export default async function rm(
}
const removeStamp = stamp();
if (!opts['--yes'] && !(await confirmAliasRemove(client, alias))) {
if (!opts['--yes'] && !(await confirmAliasRemove(output, alias))) {
output.log('Aborted');
return 0;
}
@@ -84,7 +85,7 @@ export default async function rm(
return 0;
}
async function confirmAliasRemove(client: Client, alias: Alias) {
async function confirmAliasRemove(output: Output, alias: Alias) {
const srcUrl = alias.deployment
? chalk.underline(alias.deployment.url)
: null;
@@ -103,7 +104,7 @@ async function confirmAliasRemove(client: Client, alias: Alias) {
}
);
client.output.log(`The following alias will be removed permanently`);
client.output.print(` ${tbl}\n`);
return confirm(client, chalk.red('Are you sure?'), false);
output.log(`The following alias will be removed permanently`);
output.print(` ${tbl}\n`);
return confirm(chalk.red('Are you sure?'), false);
}

View File

@@ -187,7 +187,6 @@ export default async client => {
if (cardId) {
const label = `Are you sure that you to set this card as the default?`;
const confirmation = await promptBool(label, {
...client,
trailing: '\n',
});
@@ -263,7 +262,7 @@ export default async client => {
// typed `vercel billing rm <some-id>`) is valid
if (cardId) {
const label = `Are you sure that you want to remove this card?`;
const confirmation = await promptBool(label, client);
const confirmation = await promptBool(label);
if (!confirmation) {
console.log('Aborted');
break;

View File

@@ -16,7 +16,6 @@ import Client from '../../util/client';
import { getPkgName } from '../../util/pkg-name';
import { Output } from '../../util/output';
import { Deployment, PaginationOptions } from '../../types';
import { normalizeURL } from '../../util/bisect/normalize-url';
interface DeploymentV6
extends Pick<
@@ -98,7 +97,9 @@ export default async function main(client: Client): Promise<number> {
run = resolve(run);
}
bad = normalizeURL(bad);
if (!bad.startsWith('https://')) {
bad = `https://${bad}`;
}
let parsed = parse(bad);
if (!parsed.hostname) {
output.error('Invalid input: no hostname provided');
@@ -119,7 +120,9 @@ export default async function main(client: Client): Promise<number> {
const badDeploymentPromise = getDeployment(client, bad).catch(err => err);
good = normalizeURL(good);
if (!good.startsWith('https://')) {
good = `https://${good}`;
}
parsed = parse(good);
if (!parsed.hostname) {
output.error('Invalid input: no hostname provided');

View File

@@ -1,7 +1,7 @@
import fs from 'fs-extra';
import chalk from 'chalk';
import dotenv from 'dotenv';
import { join, normalize, relative, resolve } from 'path';
import { join, normalize, relative } from 'path';
import {
detectBuilders,
normalizePath,
@@ -65,7 +65,6 @@ const help = () => {
'DIR'
)} Path to the global ${'`.vercel`'} directory
--cwd [path] The current working directory
--output [path] Directory where built assets should be written to
--prod Build a production deployment
-d, --debug Debug mode [off]
-y, --yes Skip the confirmation prompt
@@ -102,7 +101,6 @@ export default async function main(client: Client): Promise<number> {
// Parse CLI args
const argv = getArgs(client.argv.slice(2), {
'--cwd': String,
'--output': String,
'--prod': Boolean,
'--yes': Boolean,
});
@@ -140,7 +138,6 @@ export default async function main(client: Client): Promise<number> {
}
confirmed = await confirm(
client,
`No Project Settings found locally. Run ${cli.getCommandName(
'pull'
)} for retrieving them?`,
@@ -283,9 +280,7 @@ export default async function main(client: Client): Promise<number> {
}
// Delete output directory from potential previous build
const outputDir = argv['--output']
? resolve(argv['--output'])
: join(cwd, OUTPUT_DIR);
const outputDir = join(cwd, OUTPUT_DIR);
await fs.remove(outputDir);
const buildStamp = stamp();
@@ -302,7 +297,6 @@ export default async function main(client: Client): Promise<number> {
{
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
target,
argv: process.argv,
builds: builds.map(build => {
const builderWithPkg = buildersWithPkgs.get(build.use);
if (!builderWithPkg) {
@@ -311,7 +305,6 @@ export default async function main(client: Client): Promise<number> {
const { builder, pkg: builderPkg } = builderWithPkg;
return {
require: builderPkg.name,
requirePath: builderWithPkg.path,
apiVersion: builder.version,
...build,
};
@@ -470,12 +463,11 @@ export default async function main(client: Client): Promise<number> {
};
await fs.writeJSON(join(outputDir, 'config.json'), config, { spaces: 2 });
const relOutputDir = relative(cwd, outputDir);
output.print(
`${prependEmoji(
`Build Completed in ${chalk.bold(
relOutputDir.startsWith('..') ? outputDir : relOutputDir
)} ${chalk.gray(buildStamp())}`,
`Build Completed in ${chalk.bold(OUTPUT_DIR)} ${chalk.gray(
buildStamp()
)}`,
emoji('success')
)}\n`
);

View File

@@ -165,7 +165,7 @@ export default async (client: Client) => {
const quiet = !isTTY;
// check paths
const pathValidation = await validatePaths(client, paths);
const pathValidation = await validatePaths(output, paths);
if (!pathValidation.valid) {
return pathValidation.exitCode;
@@ -243,7 +243,6 @@ export default async (client: Client) => {
const shouldStartSetup =
autoConfirm ||
(await confirm(
client,
`Set up and deploy ${chalk.cyan(`${toHumanPath(path)}`)}?`,
true
));
@@ -288,7 +287,7 @@ export default async (client: Client) => {
if (typeof projectOrNewProjectName === 'string') {
newProjectName = projectOrNewProjectName;
rootDirectory = await inputRootDirectory(client, path, autoConfirm);
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
} else {
project = projectOrNewProjectName;
rootDirectory = project.rootDirectory;
@@ -522,7 +521,7 @@ export default async (client: Client) => {
}
const settings = await editProjectSettings(
client,
output,
projectSettings,
framework,
false,

View File

@@ -110,11 +110,9 @@ export default async function dev(
// v3 Build Output because it will incorrectly be detected by
// @vercel/static-build in BuildOutputV3.getBuildOutputDirectory()
if (!devCommand) {
output.log(`Removing ${OUTPUT_DIR}`);
const outputDir = join(cwd, OUTPUT_DIR);
if (await fs.pathExists(outputDir)) {
output.log(`Removing ${OUTPUT_DIR}`);
await fs.remove(outputDir);
}
await fs.remove(outputDir);
}
const devServer = new DevServer(cwd, {

View File

@@ -46,7 +46,7 @@ export default async function add(
const addStamp = stamp();
const { domain, data: argData } = parsedParams;
const data = await getDNSData(client, argData);
const data = await getDNSData(output, argData);
if (!data) {
output.log(`Aborted`);
return 1;

View File

@@ -87,8 +87,7 @@ export default async function buy(
!(await promptBool(
`Buy now for ${chalk.bold(`$${price}`)} (${`${period}yr${
period > 1 ? 's' : ''
}`})?`,
client
}`})?`
))
) {
return 0;
@@ -100,7 +99,7 @@ export default async function buy(
: `Auto renew every ${renewalPrice.period} years for ${chalk.bold(
`$${price}`
)}?`,
{ ...client, defaultValue: true }
{ defaultValue: true }
);
let buyResult;

View File

@@ -77,8 +77,7 @@ export default async function move(
!(await promptBool(
`Are you sure you want to move ${param(domainName)} to ${param(
destination
)}?`,
client
)}?`
))
) {
output.log('Aborted');
@@ -96,8 +95,7 @@ export default async function move(
);
if (
!(await promptBool(
`Are you sure you want to move ${param(domainName)}?`,
client
`Are you sure you want to move ${param(domainName)}?`
))
) {
output.log('Aborted');

View File

@@ -92,10 +92,7 @@ export default async function rm(
const skipConfirmation = opts['--yes'] || false;
if (
!skipConfirmation &&
!(await promptBool(
`Are you sure you want to remove ${param(domainName)}?`,
client
))
!(await promptBool(`Are you sure you want to remove ${param(domainName)}?`))
) {
output.log('Aborted');
return 0;
@@ -233,7 +230,7 @@ async function removeDomain(
if (
!skipConfirmation &&
!(await promptBool(`Remove conflicts associated with domain?`, client))
!(await promptBool(`Remove conflicts associated with domain?`))
) {
output.log('Aborted');
return 0;

View File

@@ -81,8 +81,7 @@ export default async function transferIn(
const shouldTransfer = await promptBool(
transferPolicy === 'no-change'
? `Transfer now for ${chalk.bold(`$${price}`)}?`
: `Transfer now with 1yr renewal for ${chalk.bold(`$${price}`)}?`,
client
: `Transfer now with 1yr renewal for ${chalk.bold(`$${price}`)}?`
);
if (!shouldTransfer) {
return 0;

View File

@@ -31,7 +31,7 @@ export default async function add(
// improve the way we show inquirer prompts
require('../../util/input/patch-inquirer');
const stdInput = await readStandardInput(client.stdin);
const stdInput = await readStandardInput();
let [envName, envTargetArg, envGitBranch] = args;
if (args.length > 3) {

View File

@@ -74,7 +74,6 @@ export default async function pull(
exists &&
!skipConfirmation &&
!(await confirm(
client,
`Found existing file ${param(filename)}. Do you want to overwrite?`,
false
))

View File

@@ -104,7 +104,6 @@ export default async function rm(
if (
!skipConfirmation &&
!(await confirm(
client,
`Removing Environment Variable ${param(env.key)} from ${formatEnvTarget(
env
)} in Project ${chalk.bold(project.name)}. Are you sure?`,

View File

@@ -65,7 +65,7 @@ export default async function init(
return extractExample(client, name, dir, force, 'v1');
}
const found = await guess(client, exampleList, name);
const found = await guess(exampleList, name);
if (typeof found === 'string') {
return extractExample(client, found, dir, force);
@@ -194,7 +194,7 @@ function prepareFolder(cwd: string, folder: string, force?: boolean) {
/**
* Guess which example user try to init
*/
async function guess(client: Client, exampleList: string[], name: string) {
async function guess(exampleList: string[], name: string) {
const GuessError = new Error(
`No example found for ${chalk.bold(name)}, run ${getCommandName(
`init`
@@ -208,7 +208,7 @@ async function guess(client: Client, exampleList: string[], name: string) {
const found = didYouMean(name, exampleList, 0.7);
if (typeof found === 'string') {
if (await promptBool(`Did you mean ${chalk.bold(found)}?`, client)) {
if (await promptBool(`Did you mean ${chalk.bold(found)}?`)) {
return found;
}
} else {

View File

@@ -387,8 +387,6 @@ const main = async () => {
// Shared API `Client` instance for all sub-commands to utilize
client = new Client({
apiUrl,
stdin: process.stdin,
stdout: process.stdout,
output,
config,
authConfig,

View File

@@ -1,7 +0,0 @@
function hasScheme(url: string): Boolean {
return url.startsWith('http://') || url.startsWith('https://');
}
export function normalizeURL(url: string): string {
return hasScheme(url) ? url : `https://${url}`;
}

View File

@@ -385,14 +385,8 @@ export async function* findDirs(
}
for (const path of paths) {
const abs = join(dir, path);
let stat: fs.Stats;
try {
stat = await fs.lstat(abs);
} catch (err: any) {
if (err.code === 'ENOENT') continue;
throw err;
}
if (stat.isDirectory()) {
const s = await fs.stat(abs);
if (s.isDirectory()) {
if (path === name) {
yield relative(root, abs);
} else {

View File

@@ -32,8 +32,6 @@ export interface ClientOptions {
argv: string[];
apiUrl: string;
authConfig: AuthConfig;
stdin: NodeJS.ReadStream;
stdout: NodeJS.WriteStream;
output: Output;
config: GlobalConfig;
localConfig?: VercelConfig;
@@ -47,8 +45,6 @@ export default class Client extends EventEmitter {
argv: string[];
apiUrl: string;
authConfig: AuthConfig;
stdin: NodeJS.ReadStream;
stdout: NodeJS.WriteStream;
output: Output;
config: GlobalConfig;
localConfig?: VercelConfig;
@@ -59,8 +55,6 @@ export default class Client extends EventEmitter {
this.argv = opts.argv;
this.apiUrl = opts.apiUrl;
this.authConfig = opts.authConfig;
this.stdin = opts.stdin;
this.stdout = opts.stdout;
this.output = opts.output;
this.config = opts.config;
this.localConfig = opts.localConfig;

View File

@@ -425,6 +425,10 @@ export async function getBuildMatches(
src = extensionless;
}
// We need to escape brackets since `glob` will
// try to find a group otherwise
src = src.replace(/(\[|\])/g, '[$1]');
const files = fileList
.filter(name => name === src || minimatch(name, src, { dot: true }))
.map(name => join(cwd, name));

View File

@@ -1,18 +0,0 @@
import { Headers } from 'node-fetch';
import { IncomingHttpHeaders, OutgoingHttpHeaders } from 'http';
export function nodeHeadersToFetchHeaders(
nodeHeaders: IncomingHttpHeaders | OutgoingHttpHeaders
): Headers {
const headers = new Headers();
for (const [name, value] of Object.entries(nodeHeaders)) {
if (Array.isArray(value)) {
for (const val of value) {
headers.append(name, val);
}
} else if (typeof value !== 'undefined') {
headers.set(name, String(value));
}
}
return headers;
}

View File

@@ -1,13 +1,12 @@
import ms from 'ms';
import url, { URL } from 'url';
import http from 'http';
import fs from 'fs-extra';
import chalk from 'chalk';
import fetch from 'node-fetch';
import plural from 'pluralize';
import rawBody from 'raw-body';
import listen from 'async-listen';
import minimatch from 'minimatch';
import ms from 'ms';
import httpProxy from 'http-proxy';
import { randomBytes } from 'crypto';
import serveHandler from 'serve-handler';
@@ -17,11 +16,11 @@ import path, { isAbsolute, basename, dirname, extname, join } from 'path';
import once from '@tootallnate/once';
import directoryTemplate from 'serve-handler/src/directory';
import getPort from 'get-port';
import { ChildProcess } from 'child_process';
import isPortReachable from 'is-port-reachable';
import deepEqual from 'fast-deep-equal';
import which from 'which';
import npa from 'npm-package-arg';
import type { ChildProcess } from 'child_process';
import { getVercelIgnore, fileNameSymbol } from '@vercel/client';
import {
@@ -91,7 +90,6 @@ import {
import { ProjectEnvVariable, ProjectSettings } from '../../types';
import exposeSystemEnvs from './expose-system-envs';
import { treeKill } from '../tree-kill';
import { nodeHeadersToFetchHeaders } from './headers';
const frontendRuntimeSet = new Set(
frameworkList.map(f => f.useRuntime?.use || '@vercel/static-build')
@@ -329,8 +327,6 @@ export default class DevServer {
): Promise<void> {
const name = relative(this.cwd, fsPath);
try {
await this.getVercelConfig();
this.files[name] = await FileFsRef.fromFsPath({ fsPath });
const extensionless = this.getExtensionlessFile(name);
if (extensionless) {
@@ -597,7 +593,7 @@ export default class DevServer {
await this.exit();
}
if (warnings?.length > 0) {
if (warnings && warnings.length > 0) {
warnings.forEach(warning =>
this.output.warn(warning.message, null, warning.link, warning.action)
);
@@ -1110,7 +1106,6 @@ export default class DevServer {
view = errorTemplate({
http_status_code: statusCode,
http_status_description,
error_code,
request_id: requestId,
});
}
@@ -1342,6 +1337,32 @@ export default class DevServer {
return false;
};
/*
runDevMiddleware = async (
req: http.IncomingMessage,
res: http.ServerResponse
) => {
const { devMiddlewarePlugins } = await loadCliPlugins(
this.cwd,
this.output
);
try {
for (let plugin of devMiddlewarePlugins) {
const result = await plugin.plugin.runDevMiddleware(req, res, this.cwd);
if (result.finished) {
return result;
}
}
return { finished: false };
} catch (e) {
return {
finished: true,
error: e,
};
}
};
*/
/**
* Serve project directory as a v2 deployment.
*/
@@ -1408,143 +1429,13 @@ export default class DevServer {
let statusCode: number | undefined;
let prevUrl = req.url;
let prevHeaders: HttpHeadersConfig = {};
let middlewarePid: number | undefined;
// Run the middleware file, if present, and apply any
// mutations to the incoming request based on the
// result of the middleware invocation.
const middleware = [...this.buildMatches.values()].find(
m => m.config?.middleware === true
);
if (middleware) {
let startMiddlewareResult: StartDevServerResult | undefined;
// TODO: can we add some caching to prevent (re-)starting
// the middleware server for every HTTP request?
const { envConfigs, files, devCacheDir, cwd: workPath } = this;
try {
startMiddlewareResult =
await middleware.builderWithPkg.builder.startDevServer?.({
files,
entrypoint: middleware.entrypoint,
workPath,
repoRootPath: this.cwd,
config: middleware.config || {},
meta: {
isDev: true,
devCacheDir,
requestUrl: req.url,
env: { ...envConfigs.runEnv },
buildEnv: { ...envConfigs.buildEnv },
},
});
/*
const middlewareResult = await this.runDevMiddleware(req, res);
if (startMiddlewareResult) {
const { port, pid } = startMiddlewareResult;
middlewarePid = pid;
this.devServerPids.add(pid);
const middlewareReqHeaders = nodeHeadersToFetchHeaders(req.headers);
// Add the Vercel platform proxy request headers
const proxyHeaders = this.getProxyHeaders(req, requestId, true);
for (const [name, value] of nodeHeadersToFetchHeaders(proxyHeaders)) {
middlewareReqHeaders.set(name, value);
}
const middlewareRes = await fetch(
`http://127.0.0.1:${port}${parsed.path}`,
{
headers: middlewareReqHeaders,
method: req.method,
redirect: 'manual',
}
);
if (middlewareRes.status === 500) {
await this.sendError(
req,
res,
requestId,
'EDGE_FUNCTION_INVOCATION_FAILED',
500
);
return;
}
// Apply status code from middleware invocation,
// for i.e. redirects or a custom 404 page
res.statusCode = middlewareRes.status;
let rewritePath = '';
let contentType = '';
let shouldContinue = false;
const skipMiddlewareHeaders = new Set([
'date',
'connection',
'content-length',
'transfer-encoding',
]);
for (const [name, value] of middlewareRes.headers) {
if (name === 'x-middleware-next') {
shouldContinue = value === '1';
} else if (name === 'x-middleware-rewrite') {
rewritePath = value;
shouldContinue = true;
} else if (name === 'content-type') {
contentType = value;
} else if (!skipMiddlewareHeaders.has(name)) {
// Any other kind of response header should be included
// on both the incoming HTTP request (for when proxying
// to another function) and the outgoing HTTP response.
res.setHeader(name, value);
req.headers[name] = value;
}
}
if (!shouldContinue) {
const middlewareBody = await middlewareRes.buffer();
this.setResponseHeaders(res, requestId);
if (middlewareBody.length > 0) {
res.setHeader('content-length', middlewareBody.length);
if (contentType) {
res.setHeader('content-type', contentType);
}
res.end(middlewareBody);
} else {
res.end();
}
return;
}
if (rewritePath) {
// TODO: add validation?
debug(`Detected rewrite path from middleware: "${rewritePath}"`);
prevUrl = rewritePath;
// Retain orginal pathname, but override query parameters from the rewrite
const beforeRewriteUrl = req.url || '/';
const rewriteUrlParsed = url.parse(beforeRewriteUrl, true);
delete rewriteUrlParsed.search;
rewriteUrlParsed.query = url.parse(rewritePath, true).query;
req.url = url.format(rewriteUrlParsed);
debug(
`Rewrote incoming HTTP URL from "${beforeRewriteUrl}" to "${req.url}"`
);
}
}
} catch (err) {
// `startDevServer()` threw an error. Most likely this means the dev
// server process exited before sending the port information message
// (missing dependency at runtime, for example).
if (err.code === 'ENOENT') {
err.message = `Command not found: ${chalk.cyan(
err.path,
...err.spawnargs
)}\nPlease ensure that ${cmd(err.path)} is properly installed`;
err.link = 'https://vercel.link/command-not-found';
}
await this.sendError(
if (middlewareResult) {
if (middlewareResult.error) {
this.sendError(
req,
res,
requestId,
@@ -1552,12 +1443,24 @@ export default class DevServer {
500
);
return;
} finally {
if (middlewarePid) {
this.killBuilderDevServer(middlewarePid);
}
}
if (middlewareResult.finished) {
return;
}
if (middlewareResult.pathname) {
const origUrl = url.parse(req.url || '/', true);
origUrl.pathname = middlewareResult.pathname;
prevUrl = url.format(origUrl);
}
if (middlewareResult.query && prevUrl) {
const origUrl = url.parse(req.url || '/', true);
delete origUrl.search;
Object.assign(origUrl.query, middlewareResult.query);
prevUrl = url.format(origUrl);
}
}
*/
for (const phase of phases) {
statusCode = undefined;
@@ -1837,10 +1740,7 @@ export default class DevServer {
isDev: true,
requestPath,
devCacheDir,
env: {
...envConfigs.runEnv,
VERCEL_BUILDER_DEBUG: this.output.debugEnabled ? '1' : undefined,
},
env: { ...envConfigs.runEnv },
buildEnv: { ...envConfigs.buildEnv },
},
});
@@ -2285,7 +2185,13 @@ function proxyPass(
`Failed to complete request to ${req.url}: ${error}`
);
if (!res.headersSent) {
devServer.sendError(req, res, requestId, 'FUNCTION_INVOCATION_FAILED');
devServer.sendError(
req,
res,
requestId,
'NO_RESPONSE_FROM_FUNCTION',
502
);
}
}
);
@@ -2363,12 +2269,11 @@ async function findBuildMatch(
if (!isIndex(match.src)) {
return match;
} else {
// If isIndex === true and ends in `.html`, we're done.
// Otherwise, keep searching.
if (extname(match.src) === '.html') {
return match;
}
// if isIndex === true and ends in .html, we're done. Otherwise, keep searching
bestIndexMatch = match;
if (extname(match.src) === '.html') {
return bestIndexMatch;
}
}
}
}
@@ -2390,13 +2295,6 @@ async function shouldServe(
config,
builderWithPkg: { builder },
} = match;
// "middleware" file is not served as a regular asset,
// instead it gets invoked as part of the routing logic.
if (config?.middleware === true) {
return false;
}
const cleanSrc = src.endsWith('.html') ? src.slice(0, -5) : src;
const trimmedPath = requestPath.endsWith('/')
? requestPath.slice(0, -1)

View File

@@ -2,29 +2,26 @@ import chalk from 'chalk';
import { DNSRecordData } from '../../types';
import textInput from '../input/text';
import promptBool from '../input/prompt-bool';
import Client from '../client';
import { Output } from '../output';
const RECORD_TYPES = ['A', 'AAAA', 'ALIAS', 'CAA', 'CNAME', 'MX', 'SRV', 'TXT'];
export default async function getDNSData(
client: Client,
output: Output,
data: null | DNSRecordData
): Promise<DNSRecordData | null> {
if (data) {
return data;
}
const { output } = client;
try {
// first ask for type, branch from there
const possibleTypes = new Set(RECORD_TYPES);
const type = (
await textInput({
label: `- Record type (${RECORD_TYPES.join(', ')}): `,
validateValue: (v: string) =>
Boolean(v && possibleTypes.has(v.trim().toUpperCase())),
})
)
const type = (await textInput({
label: `- Record type (${RECORD_TYPES.join(', ')}): `,
validateValue: (v: string) =>
Boolean(v && possibleTypes.has(v.trim().toUpperCase()))
}))
.trim()
.toUpperCase();
@@ -42,7 +39,7 @@ export default async function getDNSData(
target
)}.`
);
return (await verifyData(client))
return (await verifyData())
? {
name,
type,
@@ -50,8 +47,8 @@ export default async function getDNSData(
priority,
weight,
port,
target,
},
target
}
}
: null;
}
@@ -64,23 +61,23 @@ export default async function getDNSData(
`${mxPriority}`
)} ${chalk.cyan(value)}`
);
return (await verifyData(client))
return (await verifyData())
? {
name,
type,
value,
mxPriority,
mxPriority
}
: null;
}
const value = await getTrimmedString(`- ${type} value: `);
output.log(`${chalk.cyan(name)} ${chalk.bold(type)} ${chalk.cyan(value)}`);
return (await verifyData(client))
return (await verifyData())
? {
name,
type,
value,
value
}
: null;
} catch (error) {
@@ -88,13 +85,13 @@ export default async function getDNSData(
}
}
async function verifyData(client: Client) {
return promptBool('Is this correct?', client);
async function verifyData() {
return promptBool('Is this correct?');
}
async function getRecordName(type: string) {
const input = await textInput({
label: `- ${type} name: `,
label: `- ${type} name: `
});
return input === '@' ? '' : input;
}
@@ -103,14 +100,14 @@ async function getNumber(label: string) {
return Number(
await textInput({
label,
validateValue: v => Boolean(v && Number(v)),
validateValue: v => Boolean(v && Number(v))
})
);
}
async function getTrimmedString(label: string) {
const res = await textInput({
label,
validateValue: v => Boolean(v && v.trim().length > 0),
validateValue: v => Boolean(v && v.trim().length > 0)
});
return res.trim();
}

View File

@@ -54,8 +54,7 @@ export default async function purchaseDomainIfAvailable(
!(await promptBool(
`Buy ${chalk.underline(domain)} for ${chalk.bold(
`$${price}`
)} (${plural('yr', period, true)})?`,
client
)} (${plural('yr', period, true)})?`
))
) {
output.print(eraseLines(1));

View File

@@ -1,19 +1,12 @@
import inquirer from 'inquirer';
import Client from '../client';
export default async function confirm(
client: Client,
message: string,
preferred: boolean
): Promise<boolean> {
require('./patch-inquirer');
const prompt = inquirer.createPromptModule({
input: client.stdin,
output: client.stdout,
});
const answers = await prompt({
const answers = await inquirer.prompt({
type: 'confirm',
name: 'value',
message,

View File

@@ -1,8 +1,8 @@
import inquirer from 'inquirer';
import confirm from './confirm';
import chalk from 'chalk';
import { Output } from '../output';
import frameworkList, { Framework } from '@vercel/frameworks';
import Client from '../client';
import { isSettingValue } from '../is-setting-value';
import { ProjectSettings } from '../../types';
@@ -22,14 +22,12 @@ const settingKeys = Object.keys(settingMap).sort() as unknown as readonly [
export type PartialProjectSettings = Pick<ProjectSettings, ConfigKeys>;
export default async function editProjectSettings(
client: Client,
output: Output,
projectSettings: PartialProjectSettings | null,
framework: Framework | null,
autoConfirm: boolean,
localConfigurationOverrides: PartialProjectSettings | null
): Promise<ProjectSettings> {
const { output } = client;
// Create initial settings object defaulting everything to `null` and assigning what may exist in `projectSettings`
const settings: ProjectSettings = Object.assign(
{
@@ -120,7 +118,7 @@ export default async function editProjectSettings(
// Prompt the user if they want to modify any settings not defined by local configuration.
if (
autoConfirm ||
!(await confirm(client, 'Want to modify these settings?', false))
!(await confirm('Want to modify these settings?', false))
) {
return settings;
}

View File

@@ -47,16 +47,11 @@ export default async function inputProject(
if (!detectedProject) {
// did not auto-detect a project to link
shouldLinkProject = await confirm(
client,
`Link to existing project?`,
false
);
shouldLinkProject = await confirm(`Link to existing project?`, false);
} else {
// auto-detected a project to link
if (
await confirm(
client,
`Found project ${chalk.cyan(
`${org.slug}/${detectedProject.name}`
)}. Link to it?`,
@@ -68,7 +63,6 @@ export default async function inputProject(
// user doesn't want to link the auto-detected project
shouldLinkProject = await confirm(
client,
`Link to different existing project?`,
true
);
@@ -79,11 +73,7 @@ export default async function inputProject(
let project: Project | ProjectNotFound | null = null;
while (!project || project instanceof ProjectNotFound) {
const prompt = inquirer.createPromptModule({
input: client.stdin,
output: client.stdout,
});
const answers = await prompt({
const answers = await inquirer.prompt({
type: 'input',
name: 'existingProjectName',
message: `Whats the name of your existing project?`,

View File

@@ -1,12 +1,12 @@
import path from 'path';
import chalk from 'chalk';
import inquirer from 'inquirer';
import { Output } from '../output';
import { validateRootDirectory } from '../validate-paths';
import Client from '../client';
export async function inputRootDirectory(
client: Client,
cwd: string,
output: Output,
autoConfirm = false
) {
if (autoConfirm) {
@@ -15,11 +15,7 @@ export async function inputRootDirectory(
// eslint-disable-next-line no-constant-condition
while (true) {
const prompt = inquirer.createPromptModule({
input: client.stdin,
output: client.stdout,
});
const { rootDirectory } = await prompt({
const { rootDirectory } = await inquirer.prompt({
type: 'input',
name: 'rootDirectory',
message: `In which directory is your code located?`,
@@ -42,7 +38,7 @@ export async function inputRootDirectory(
if (
(await validateRootDirectory(
client.output,
output,
cwd,
fullPath,
'Please choose a different one.'

View File

@@ -5,21 +5,21 @@ type Options = {
defaultValue?: boolean;
noChar?: string;
resolveChars?: Set<string>;
stdin: NodeJS.ReadStream;
stdout: NodeJS.WriteStream;
stdin?: NodeJS.ReadStream;
stdout?: NodeJS.WriteStream;
trailing?: string;
yesChar?: string;
};
export default async function promptBool(label: string, options: Options) {
export default async function promptBool(label: string, options: Options = {}) {
const {
stdin,
stdout,
defaultValue = false,
abortSequences = new Set(['\u0003']),
resolveChars = new Set(['\r']),
yesChar = 'y',
noChar = 'n',
stdin = process.stdin,
stdout = process.stdout,
trailing = '',
} = options;

View File

@@ -1,15 +1,13 @@
export default async function readStandardInput(
stdin: NodeJS.ReadStream
): Promise<string> {
export default async function readStandardInput(): Promise<string> {
return new Promise<string>(resolve => {
setTimeout(() => resolve(''), 500);
if (stdin.isTTY) {
if (process.stdin.isTTY) {
// found tty so we know there is nothing piped to stdin
resolve('');
} else {
stdin.setEncoding('utf8');
stdin.once('data', resolve);
process.stdin.setEncoding('utf8');
process.stdin.once('data', resolve);
}
});
}

View File

@@ -80,7 +80,6 @@ export default async function setupAndLink(
const shouldStartSetup =
autoConfirm ||
(await confirm(
client,
`${setupMsg} ${chalk.cyan(`${toHumanPath(path)}`)}?`,
true
));
@@ -121,7 +120,7 @@ export default async function setupAndLink(
if (typeof projectOrNewProjectName === 'string') {
newProjectName = projectOrNewProjectName;
rootDirectory = await inputRootDirectory(client, path, autoConfirm);
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
} else {
const project = projectOrNewProjectName;
@@ -225,7 +224,7 @@ export default async function setupAndLink(
const { projectSettings, framework } = deployment;
settings = await editProjectSettings(
client,
output,
projectSettings,
framework,
autoConfirm,

View File

@@ -14,7 +14,7 @@ export default async function reauthenticate(
client.output.log(
`You must re-authenticate with SAML to use ${bold(error.scope)} scope.`
);
if (await confirm(client, `Log in with SAML?`, true)) {
if (await confirm(`Log in with SAML?`, true)) {
return doSamlLogin(client, error.teamId);
}
} else {

View File

@@ -5,7 +5,6 @@ import chalk from 'chalk';
import { homedir } from 'os';
import confirm from './input/confirm';
import toHumanPath from './humanize-path';
import Client from './client';
const stat = promisify(lstatRaw);
@@ -52,11 +51,9 @@ export async function validateRootDirectory(
}
export default async function validatePaths(
client: Client,
output: Output,
paths: string[]
): Promise<{ valid: true; path: string } | { valid: false; exitCode: number }> {
const { output } = client;
// can't deploy more than 1 path
if (paths.length > 1) {
output.print(`${chalk.red('Error!')} Can't deploy more than one path.\n`);
@@ -88,7 +85,6 @@ export default async function validatePaths(
// ask confirmation if the directory is home
if (path === homedir()) {
const shouldDeployHomeDirectory = await confirm(
client,
`You are deploying your home directory. Do you want to continue?`,
false
);

View File

@@ -1,7 +0,0 @@
export const config = {
runtime: 'invalid-runtime-value',
};
export default async function edge(request, event) {
throw new Error('intentional runtime error');
}

View File

@@ -1,8 +0,0 @@
export const config = {
runtime: 'experimental-edge',
};
export async function notTheDefaultExport(request, event) {
// this will never be run
return new Response('some response body');
}

View File

@@ -1,7 +0,0 @@
export const config = {
runtime: 'experimental-edge',
};
export default async function edge(request, event) {
throw new Error('intentional runtime error');
}

View File

@@ -1,10 +0,0 @@
export const config = {
runtime: 'experimental-edge',
};
export default async function edge(request, event) {
// this should never be executed
return new Response('some response body');
}
throw new Error('intentional startup error');

View File

@@ -1,9 +0,0 @@
export const config = {
runtime: 'experimental-edge'
}
export default async function edge(request, event) {
return new Response('some response body');
// intentional missing closing bracket to produce syntax error
// }

View File

@@ -1,9 +0,0 @@
import unknownModule from 'unknown-module-893427589372458934795843';
export const config = {
runtime: 'experimental-edge',
};
export default async function edge(request, event) {
return new Response(unknownModule('some response body'));
}

View File

@@ -1,9 +0,0 @@
export const config = {
runtime: 'experimental-edge',
};
export default async function edge(request, event) {
return new Response('responding with intentional 500 from user code', {
status: 500,
});
}

View File

@@ -1,8 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
decamelize@6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-6.0.0.tgz#8cad4d916fde5c41a264a43d0ecc56fe3d31749e"
integrity sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==

View File

@@ -1 +0,0 @@
export default () => new Response(null, { status: 500 });

View File

@@ -1 +0,0 @@
throw new Error('Middleware init error');

View File

@@ -1,3 +0,0 @@
export default () => {
throw new Error('Middleware handler error');
};

View File

@@ -1,13 +0,0 @@
// Supports both a single string value or an array of matchers
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
};
export default function middleware(request, _event) {
const response = new Response('middleware response');
// Set custom header
response.headers.set('x-modified-edge', 'true');
return response;
}

View File

@@ -1,9 +0,0 @@
export default req => {
const url = new URL(req.url);
return new Response(null, {
status: 302,
headers: {
location: `https://vercel.com${url.pathname}${url.search}`,
},
});
};

View File

@@ -1 +0,0 @@
export default () => new Response('hi from middleware');

View File

@@ -1,5 +0,0 @@
export default (req, res) => {
res.json({
url: req.url,
});
};

View File

@@ -1,6 +0,0 @@
export default () =>
new Response(null, {
headers: {
'x-middleware-rewrite': '/api/fn?from-middleware=true',
},
});

View File

@@ -1 +0,0 @@
<h1>Another</h1>

View File

@@ -1 +0,0 @@
<h1>Index</h1>

View File

@@ -1,19 +0,0 @@
export default req => {
const url = new URL(req.url);
if (url.pathname === '/') {
// Pass-through "index.html" page
return new Response(null, {
headers: {
'x-middleware-next': '1',
},
});
}
// Everything else goes to "another.html"
return new Response(null, {
headers: {
'x-middleware-rewrite': '/another.html',
},
});
};

View File

@@ -1,3 +0,0 @@
{
"version": 2
}

View File

@@ -23,7 +23,7 @@ test('[vercel dev] should support edge functions', async () => {
const body = { hello: 'world' };
let res = await fetch(`http://localhost:${port}/api/edge-success`, {
let res = await fetch(`http://localhost:${port}/api/edge-function`, {
method: 'POST',
headers: {
'content-type': 'application/json',
@@ -36,7 +36,7 @@ test('[vercel dev] should support edge functions', async () => {
// are set up; so, we test that they are all passed through properly
expect(await res.json()).toMatchObject({
headerContentType: 'application/json',
url: `http://localhost:${port}/api/edge-success`,
url: `http://localhost:${port}/api/edge-function`,
method: 'POST',
body: '{"hello":"world"}',
decamelized: 'some_camel_case_thing',
@@ -48,235 +48,6 @@ test('[vercel dev] should support edge functions', async () => {
}
});
test(
'[vercel dev] edge functions respond properly the same as production',
testFixtureStdio('edge-function', async (testPath: any) => {
await testPath(500, '/api/edge-500-response');
await testPath(200, '/api/edge-success');
})
);
test('[vercel dev] should support edge functions returning intentional 500 responses', async () => {
const dir = fixture('edge-function');
const { dev, port, readyResolver } = await testFixture(dir);
try {
await readyResolver;
const body = { hello: 'world' };
let res = await fetch(`http://localhost:${port}/api/edge-500-response`, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify(body),
});
validateResponseHeaders(res);
expect(await res.status).toBe(500);
expect(await res.text()).toBe(
'responding with intentional 500 from user code'
);
} finally {
await dev.kill('SIGTERM');
}
});
test('[vercel dev] should handle runtime errors thrown in edge functions', async () => {
const dir = fixture('edge-function-error');
const { dev, port, readyResolver } = await testFixture(dir);
try {
await readyResolver;
let res = await fetch(`http://localhost:${port}/api/edge-error-runtime`, {
method: 'GET',
headers: {
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
},
});
validateResponseHeaders(res);
const { stdout, stderr } = await dev.kill('SIGTERM');
expect(await res.text()).toMatch(
/<strong>500<\/strong>: INTERNAL_SERVER_ERROR/g
);
expect(stdout).toMatch(/Unhandled rejection: intentional runtime error/g);
expect(stderr).toMatch(
/Failed to complete request to \/api\/edge-error-runtime: Error: socket hang up/g
);
} finally {
await dev.kill('SIGTERM');
}
});
test('[vercel dev] should handle config errors thrown in edge functions', async () => {
const dir = fixture('edge-function-error');
const { dev, port, readyResolver } = await testFixture(dir);
try {
await readyResolver;
let res = await fetch(`http://localhost:${port}/api/edge-error-config`, {
method: 'GET',
headers: {
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
},
});
validateResponseHeaders(res);
const { stderr } = await dev.kill('SIGTERM');
expect(await res.text()).toMatch(
/<strong>500<\/strong>: INTERNAL_SERVER_ERROR/g
);
expect(stderr).toMatch(
/Invalid function runtime "invalid-runtime-value" for "api\/edge-error-config.js". Valid runtimes are: \["experimental-edge"\]/g
);
expect(stderr).toMatch(
/Failed to complete request to \/api\/edge-error-config: Error: socket hang up/g
);
} finally {
await dev.kill('SIGTERM');
}
});
test('[vercel dev] should handle startup errors thrown in edge functions', async () => {
const dir = fixture('edge-function-error');
const { dev, port, readyResolver } = await testFixture(dir);
try {
await readyResolver;
let res = await fetch(`http://localhost:${port}/api/edge-error-startup`, {
method: 'GET',
headers: {
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
},
});
validateResponseHeaders(res);
const { stderr } = await dev.kill('SIGTERM');
expect(await res.text()).toMatch(
/<strong>500<\/strong>: INTERNAL_SERVER_ERROR/g
);
expect(stderr).toMatch(/Failed to instantiate edge runtime./g);
expect(stderr).toMatch(/intentional startup error/g);
expect(stderr).toMatch(
/Failed to complete request to \/api\/edge-error-startup: Error: socket hang up/g
);
} finally {
await dev.kill('SIGTERM');
}
});
test('[vercel dev] should handle syntax errors thrown in edge functions', async () => {
const dir = fixture('edge-function-error');
const { dev, port, readyResolver } = await testFixture(dir);
try {
await readyResolver;
let res = await fetch(`http://localhost:${port}/api/edge-error-syntax`, {
method: 'GET',
headers: {
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
},
});
validateResponseHeaders(res);
const { stderr } = await dev.kill('SIGTERM');
expect(await res.text()).toMatch(
/<strong>500<\/strong>: INTERNAL_SERVER_ERROR/g
);
expect(stderr).toMatch(/Failed to instantiate edge runtime./g);
expect(stderr).toMatch(/Unexpected end of file/g);
expect(stderr).toMatch(
/Failed to complete request to \/api\/edge-error-syntax: Error: socket hang up/g
);
} finally {
await dev.kill('SIGTERM');
}
});
test('[vercel dev] should handle import errors thrown in edge functions', async () => {
const dir = fixture('edge-function-error');
const { dev, port, readyResolver } = await testFixture(dir);
try {
await readyResolver;
let res = await fetch(
`http://localhost:${port}/api/edge-error-unknown-import`,
{
method: 'GET',
headers: {
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
},
}
);
validateResponseHeaders(res);
const { stderr } = await dev.kill('SIGTERM');
expect(await res.text()).toMatch(
/<strong>500<\/strong>: INTERNAL_SERVER_ERROR/g
);
expect(stderr).toMatch(
/Could not resolve "unknown-module-893427589372458934795843"/g
);
expect(stderr).toMatch(
/Failed to complete request to \/api\/edge-error-unknown-import: Error: socket hang up/g
);
} finally {
await dev.kill('SIGTERM');
}
});
test('[vercel dev] should handle missing handler errors thrown in edge functions', async () => {
const dir = fixture('edge-function-error');
const { dev, port, readyResolver } = await testFixture(dir);
try {
await readyResolver;
let res = await fetch(
`http://localhost:${port}/api/edge-error-no-handler`,
{
method: 'GET',
headers: {
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
},
}
);
validateResponseHeaders(res);
const { stdout, stderr } = await dev.kill('SIGTERM');
expect(await res.text()).toMatch(
/<strong>500<\/strong>: INTERNAL_SERVER_ERROR/g
);
expect(stdout).toMatch(
/No default export was found. Add a default export to handle requests./g
);
expect(stderr).toMatch(
/Failed to complete request to \/api\/edge-error-no-handler: Error: socket hang up/g
);
} finally {
await dev.kill('SIGTERM');
}
});
test('[vercel dev] should support request body', async () => {
const dir = fixture('node-request-body');
const { dev, port, readyResolver } = await testFixture(dir);

View File

@@ -1,17 +1,7 @@
// eslint-disable-next-line
import { join } from 'path';
import ms from 'ms';
import fs, { mkdirp } from 'fs-extra';
import path from 'path';
const {
exec,
fetch,
fixture,
sleep,
testFixture,
testFixtureStdio,
validateResponseHeaders,
} = require('./utils.js');
const { exec, fixture, testFixture, testFixtureStdio } = require('./utils.js');
test('[vercel dev] validate redirects', async () => {
const directory = fixture('invalid-redirects');
@@ -344,44 +334,3 @@ test(
await testPath(200, '/', /A simple deployment with the Vercel API!/m);
})
);
test(
'[vercel dev] add a `api/fn.ts` when `api` does not exist at startup`',
testFixtureStdio('no-api', async (_testPath: any, port: any) => {
const directory = fixture('no-api');
const apiDir = join(directory, 'api');
try {
{
const response = await fetch(`http://localhost:${port}/api/new-file`);
validateResponseHeaders(response);
expect(response.status).toBe(404);
}
const fileContents = `
export const config = {
runtime: 'experimental-edge'
}
export default async function edge(request, event) {
return new Response('from new file');
}
`;
await mkdirp(apiDir);
await fs.writeFile(join(apiDir, 'new-file.js'), fileContents);
// Wait until file events have been processed
await sleep(ms('1s'));
{
const response = await fetch(`http://localhost:${port}/api/new-file`);
validateResponseHeaders(response);
const body = await response.text();
expect(body.trim()).toBe('from new file');
}
} finally {
await fs.remove(apiDir);
}
})
);

View File

@@ -433,85 +433,3 @@ test(
await testPath(404, '/i-do-not-exist');
})
);
test(
'[vercel dev] Middleware that returns a 200 response',
testFixtureStdio('middleware-response', async (testPath: any) => {
await testPath(200, '/', 'hi from middleware');
await testPath(200, '/another', 'hi from middleware');
})
);
test(
'[vercel dev] Middleware that does basic rewrite',
testFixtureStdio('middleware-rewrite', async (testPath: any) => {
await testPath(200, '/', '<h1>Index</h1>');
await testPath(200, '/index', '<h1>Another</h1>');
await testPath(200, '/another', '<h1>Another</h1>');
await testPath(200, '/another.html', '<h1>Another</h1>');
await testPath(200, '/foo', '<h1>Another</h1>');
})
);
test(
'[vercel dev] Middleware that rewrites with custom query params',
testFixtureStdio('middleware-rewrite-query', async (testPath: any) => {
await testPath(200, '/?foo=bar', '{"url":"/?from-middleware=true"}');
await testPath(
200,
'/another?foo=bar',
'{"url":"/another?from-middleware=true"}'
);
await testPath(
200,
'/api/fn?foo=bar',
'{"url":"/api/fn?from-middleware=true"}'
);
})
);
test(
'[vercel dev] Middleware that redirects',
testFixtureStdio('middleware-redirect', async (testPath: any) => {
await testPath(302, '/', null, {
location: 'https://vercel.com/',
});
await testPath(302, '/home', null, {
location: 'https://vercel.com/home',
});
await testPath(302, '/?foo=bar', null, {
location: 'https://vercel.com/?foo=bar',
});
})
);
test(
'[vercel dev] Middleware with error in function handler',
testFixtureStdio('middleware-error-in-handler', async (testPath: any) => {
await testPath(500, '/', /EDGE_FUNCTION_INVOCATION_FAILED/);
})
);
test(
'[vercel dev] Middleware with error at init',
testFixtureStdio('middleware-error-at-init', async (testPath: any) => {
await testPath(500, '/', /EDGE_FUNCTION_INVOCATION_FAILED/);
})
);
test(
'[vercel dev] Middleware with an explicit 500 response',
testFixtureStdio('middleware-500-response', async (testPath: any) => {
await testPath(500, '/', /EDGE_FUNCTION_INVOCATION_FAILED/);
})
);
test(
'[vercel dev] Middleware with `matchers` config',
testFixtureStdio('middleware-matchers', async (testPath: any) => {
await testPath(404, '/');
await testPath(404, '/another');
await testPath(200, '/about/page', 'middleware response');
await testPath(200, '/dashboard/home', 'middleware response');
})
);

View File

@@ -257,10 +257,6 @@ async function testFixture(directory, opts = {}, args = []) {
dev.kill = async (...args) => {
dev._kill(...args);
await exitResolver;
return {
stdout,
stderr,
};
};
return {

View File

@@ -1,7 +0,0 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"framework": null
}
}

View File

@@ -1 +0,0 @@
<h1>Vercel</h1>

View File

@@ -1,5 +0,0 @@
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
};
export default () => new Response('middleware');

View File

@@ -1 +1 @@
export default () => new Response('middleware');
export default req => new Response('middleware');

View File

@@ -1,5 +1,4 @@
import chalk from 'chalk';
import { PassThrough } from 'stream';
import { createServer, Server } from 'http';
import express, { Express, Router } from 'express';
import listen from 'async-listen';
@@ -24,13 +23,10 @@ export class MockClient extends Client {
// Gets populated in `startMockServer()`
apiUrl: '',
authConfig: {},
stdin: new PassThrough(),
stdout: new PassThrough(),
output: new Output(),
config: {},
localConfig: {},
});
this.mockOutput = jest.fn();
this.app = express();
@@ -57,12 +53,6 @@ export class MockClient extends Client {
}
reset() {
this.stdin = new PassThrough();
this.stdin.isTTY = true;
this.stdout = new PassThrough();
this.stdout.isTTY = true;
this.output = new Output();
this.mockOutput = jest.fn();
this.output.print = s => {

View File

@@ -1,7 +1,6 @@
import ms from 'ms';
import fs from 'fs-extra';
import { join } from 'path';
import { getWriteableDirectory } from '@vercel/build-utils';
import build from '../../../src/commands/build';
import { client } from '../../mocks/client';
import { defaultProject, useProject } from '../../mocks/project';
@@ -402,7 +401,7 @@ describe('build', () => {
expect(config).toMatchObject({
version: 3,
routes: [
{ src: '^/.*$', middlewarePath: 'middleware', continue: true },
{ src: '/.*', middlewarePath: 'middleware', continue: true },
{ handle: 'filesystem' },
{ src: '^/api(/.*)?$', status: 404 },
{ handle: 'error' },
@@ -422,101 +421,4 @@ describe('build', () => {
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
it('should build root-level `middleware.js` with "matcher" config', async () => {
const cwd = fixture('middleware-with-matcher');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
const exitCode = await build(client);
expect(exitCode).toEqual(0);
// `builds.json` says that "@vercel/static" was run
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds).toMatchObject({
target: 'preview',
builds: [
{
require: '@vercel/node',
apiVersion: 3,
use: '@vercel/node',
src: 'middleware.js',
config: {
zeroConfig: true,
middleware: true,
},
},
{
require: '@vercel/static',
apiVersion: 2,
use: '@vercel/static',
src: '!{api/**,package.json,middleware.[jt]s}',
config: {
zeroConfig: true,
},
},
],
});
// `config.json` includes the "middlewarePath" route
const config = await fs.readJSON(join(output, 'config.json'));
expect(config).toMatchObject({
version: 3,
routes: [
{
src: '^\\/about(?:\\/((?:[^\\/#\\?]+?)(?:\\/(?:[^\\/#\\?]+?))*))?[\\/#\\?]?$|^\\/dashboard(?:\\/((?:[^\\/#\\?]+?)(?:\\/(?:[^\\/#\\?]+?))*))?[\\/#\\?]?$',
middlewarePath: 'middleware',
continue: true,
},
{ handle: 'filesystem' },
{ src: '^/api(/.*)?$', status: 404 },
{ handle: 'error' },
{ status: 404, src: '^(?!/api).*$', dest: '/404.html' },
],
});
// "static" directory contains `index.html`, but *not* `middleware.js`
const staticFiles = await fs.readdir(join(output, 'static'));
expect(staticFiles.sort()).toEqual(['index.html']);
// "functions" directory contains `middleware.func`
const functions = await fs.readdir(join(output, 'functions'));
expect(functions.sort()).toEqual(['middleware.func']);
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
it('should support `--output` parameter', async () => {
const cwd = fixture('static');
const output = await getWriteableDirectory();
try {
process.chdir(cwd);
client.setArgv('build', '--output', output);
const exitCode = await build(client);
expect(exitCode).toEqual(0);
// `builds.json` says that "@vercel/static" was run
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds).toMatchObject({
target: 'preview',
builds: [
{
require: '@vercel/static',
apiVersion: 2,
src: '**',
use: '@vercel/static',
},
],
});
// "static" directory contains static files
const files = await fs.readdir(join(output, 'static'));
expect(files.sort()).toEqual(['index.html']);
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
});

View File

@@ -1,25 +0,0 @@
import confirm from '../../src/util/input/confirm';
import { client } from '../mocks/client';
describe('MockClient', () => {
it('should mock `confirm()`', async () => {
// true
let confirmedPromise = confirm(client, 'Do the thing?', false);
client.stdin.write('yes\n');
client.stdout.setEncoding('utf8');
client.stdout.on('data', d => console.log({ d }));
let confirmed = await confirmedPromise;
expect(confirmed).toEqual(true);
// false
confirmedPromise = confirm(client, 'Do the thing?', false);
client.stdin.write('no\n');
confirmed = await confirmedPromise;
expect(confirmed).toEqual(false);
});
});

View File

@@ -1,16 +0,0 @@
import { normalizeURL } from '../../../../src/util/bisect/normalize-url';
describe('normalize-url', () => {
it('should add https to url without scheme', () => {
const normalizedUrl = normalizeURL('vercel.com');
expect(normalizedUrl).toEqual('https://vercel.com');
});
it('should not add anything to a url that starts with https', () => {
const normalizedUrl = normalizeURL('https://vercel.com');
expect(normalizedUrl).toEqual('https://vercel.com');
});
it('should not add anything to a url that starts with http', () => {
const normalizedUrl = normalizeURL('http://vercel.com');
expect(normalizedUrl).toEqual('http://vercel.com');
});
});

View File

@@ -1,6 +1,13 @@
import { Framework, frameworks } from '@vercel/frameworks';
import editProjectSettings from '../../../../src/util/input/edit-project-settings';
import { client } from '../../../mocks/client';
import { Output } from '../../../../src/util/output';
let output: Output;
beforeEach(() => {
output = new Output();
output.print = jest.fn();
});
const otherFramework = frameworks.find(
fwk => fwk.name === 'Other'
@@ -13,7 +20,7 @@ describe('editProjectSettings', () => {
describe('with no settings, "Other" framework, and no overrides provided', () => {
test('should default all settings to `null` and print user default framework settings', async () => {
const settings = await editProjectSettings(
client,
output,
null,
otherFramework,
true,
@@ -27,14 +34,22 @@ describe('editProjectSettings', () => {
installCommand: null,
outputDirectory: null,
});
expect(client.mockOutput.mock.calls.length).toBe(5);
expect(client.mockOutput.mock.calls[0][0]).toMatch(
expect((output.print as jest.Mock).mock.calls.length).toBe(5);
expect((output.print as jest.Mock).mock.calls[0][0]).toMatch(
/No framework detected. Default Project Settings:/
);
expect(client.mockOutput.mock.calls[1][0]).toMatch(/Build Command/);
expect(client.mockOutput.mock.calls[2][0]).toMatch(/Development Command/);
expect(client.mockOutput.mock.calls[3][0]).toMatch(/Install Command/);
expect(client.mockOutput.mock.calls[4][0]).toMatch(/Output Directory/);
expect((output.print as jest.Mock).mock.calls[1][0]).toMatch(
/Build Command/
);
expect((output.print as jest.Mock).mock.calls[2][0]).toMatch(
/Development Command/
);
expect((output.print as jest.Mock).mock.calls[3][0]).toMatch(
/Install Command/
);
expect((output.print as jest.Mock).mock.calls[4][0]).toMatch(
/Output Directory/
);
});
});
@@ -48,21 +63,29 @@ describe('editProjectSettings', () => {
outputDirectory: 'OUTPUT_DIRECTORY',
};
const settings = await editProjectSettings(
client,
output,
projectSettings,
otherFramework,
true,
null
);
expect(settings).toStrictEqual({ ...projectSettings, framework: null });
expect(client.mockOutput.mock.calls.length).toBe(5);
expect(client.mockOutput.mock.calls[0][0]).toMatch(
expect((output.print as jest.Mock).mock.calls.length).toBe(5);
expect((output.print as jest.Mock).mock.calls[0][0]).toMatch(
/No framework detected. Default Project Settings:/
);
expect(client.mockOutput.mock.calls[1][0]).toMatch(/Build Command/);
expect(client.mockOutput.mock.calls[2][0]).toMatch(/Development Command/);
expect(client.mockOutput.mock.calls[3][0]).toMatch(/Install Command/);
expect(client.mockOutput.mock.calls[4][0]).toMatch(/Output Directory/);
expect((output.print as jest.Mock).mock.calls[1][0]).toMatch(
/Build Command/
);
expect((output.print as jest.Mock).mock.calls[2][0]).toMatch(
/Development Command/
);
expect((output.print as jest.Mock).mock.calls[3][0]).toMatch(
/Install Command/
);
expect((output.print as jest.Mock).mock.calls[4][0]).toMatch(
/Output Directory/
);
});
});
@@ -76,20 +99,28 @@ describe('editProjectSettings', () => {
outputDirectory: 'OUTPUT_DIRECTORY',
};
const settings = await editProjectSettings(
client,
output,
projectSettings,
nextJSFramework,
true,
null
);
expect(client.mockOutput.mock.calls.length).toBe(5);
expect(client.mockOutput.mock.calls[0][0]).toMatch(
expect((output.print as jest.Mock).mock.calls.length).toBe(5);
expect((output.print as jest.Mock).mock.calls[0][0]).toMatch(
/Auto-detected Project Settings/
);
expect(client.mockOutput.mock.calls[1][0]).toMatch(/Build Command/);
expect(client.mockOutput.mock.calls[2][0]).toMatch(/Development Command/);
expect(client.mockOutput.mock.calls[3][0]).toMatch(/Install Command/);
expect(client.mockOutput.mock.calls[4][0]).toMatch(/Output Directory/);
expect((output.print as jest.Mock).mock.calls[1][0]).toMatch(
/Build Command/
);
expect((output.print as jest.Mock).mock.calls[2][0]).toMatch(
/Development Command/
);
expect((output.print as jest.Mock).mock.calls[3][0]).toMatch(
/Install Command/
);
expect((output.print as jest.Mock).mock.calls[4][0]).toMatch(
/Output Directory/
);
expect(settings).toStrictEqual({
...projectSettings,
framework: nextJSFramework.slug,
@@ -115,28 +146,38 @@ describe('editProjectSettings', () => {
outputDirectory: 'OUTPUT_DIRECTORY',
};
const settings = await editProjectSettings(
client,
output,
projectSettings,
nextJSFramework,
true,
overrides
);
expect(client.mockOutput.mock.calls.length).toBe(9);
expect(client.mockOutput.mock.calls[0][0]).toMatch(
expect((output.print as jest.Mock).mock.calls.length).toBe(9);
expect((output.print as jest.Mock).mock.calls[0][0]).toMatch(
/Local settings detected in vercel.json:/
);
expect(client.mockOutput.mock.calls[1][0]).toMatch(/Build Command:/);
expect(client.mockOutput.mock.calls[2][0]).toMatch(/Ignore Command:/);
expect(client.mockOutput.mock.calls[3][0]).toMatch(
expect((output.print as jest.Mock).mock.calls[1][0]).toMatch(
/Build Command:/
);
expect((output.print as jest.Mock).mock.calls[2][0]).toMatch(
/Ignore Command:/
);
expect((output.print as jest.Mock).mock.calls[3][0]).toMatch(
/Development Command:/
);
expect(client.mockOutput.mock.calls[4][0]).toMatch(/Framework:/);
expect(client.mockOutput.mock.calls[5][0]).toMatch(/Install Command:/);
expect(client.mockOutput.mock.calls[6][0]).toMatch(/Output Directory:/);
expect(client.mockOutput.mock.calls[7][0]).toMatch(
expect((output.print as jest.Mock).mock.calls[4][0]).toMatch(
/Framework:/
);
expect((output.print as jest.Mock).mock.calls[5][0]).toMatch(
/Install Command:/
);
expect((output.print as jest.Mock).mock.calls[6][0]).toMatch(
/Output Directory:/
);
expect((output.print as jest.Mock).mock.calls[7][0]).toMatch(
/Merging default Project Settings for Svelte. Previously listed overrides are prioritized./
);
expect(client.mockOutput.mock.calls[8][0]).toMatch(
expect((output.print as jest.Mock).mock.calls[8][0]).toMatch(
/Auto-detected Project Settings/
);
@@ -155,28 +196,38 @@ describe('editProjectSettings', () => {
outputDirectory: 'OUTPUT_DIRECTORY',
};
const settings = await editProjectSettings(
client,
output,
null,
nextJSFramework,
true,
overrides
);
expect(client.mockOutput.mock.calls.length).toBe(9);
expect(client.mockOutput.mock.calls[0][0]).toMatch(
expect((output.print as jest.Mock).mock.calls.length).toBe(9);
expect((output.print as jest.Mock).mock.calls[0][0]).toMatch(
/Local settings detected in vercel.json:/
);
expect(client.mockOutput.mock.calls[1][0]).toMatch(/Build Command:/);
expect(client.mockOutput.mock.calls[2][0]).toMatch(/Ignore Command:/);
expect(client.mockOutput.mock.calls[3][0]).toMatch(
expect((output.print as jest.Mock).mock.calls[1][0]).toMatch(
/Build Command:/
);
expect((output.print as jest.Mock).mock.calls[2][0]).toMatch(
/Ignore Command:/
);
expect((output.print as jest.Mock).mock.calls[3][0]).toMatch(
/Development Command:/
);
expect(client.mockOutput.mock.calls[4][0]).toMatch(/Framework:/);
expect(client.mockOutput.mock.calls[5][0]).toMatch(/Install Command:/);
expect(client.mockOutput.mock.calls[6][0]).toMatch(/Output Directory:/);
expect(client.mockOutput.mock.calls[7][0]).toMatch(
expect((output.print as jest.Mock).mock.calls[4][0]).toMatch(
/Framework:/
);
expect((output.print as jest.Mock).mock.calls[5][0]).toMatch(
/Install Command:/
);
expect((output.print as jest.Mock).mock.calls[6][0]).toMatch(
/Output Directory:/
);
expect((output.print as jest.Mock).mock.calls[7][0]).toMatch(
/Merging default Project Settings for Svelte. Previously listed overrides are prioritized./
);
expect(client.mockOutput.mock.calls[8][0]).toMatch(
expect((output.print as jest.Mock).mock.calls[8][0]).toMatch(
/Auto-detected Project Settings/
);
expect(settings).toStrictEqual(overrides);
@@ -194,28 +245,38 @@ describe('editProjectSettings', () => {
outputDirectory: 'OUTPUT_DIRECTORY',
};
const settings = await editProjectSettings(
client,
output,
null,
null,
true,
overrides
);
expect(client.mockOutput.mock.calls.length).toBe(9);
expect(client.mockOutput.mock.calls[0][0]).toMatch(
expect((output.print as jest.Mock).mock.calls.length).toBe(9);
expect((output.print as jest.Mock).mock.calls[0][0]).toMatch(
/Local settings detected in vercel.json:/
);
expect(client.mockOutput.mock.calls[1][0]).toMatch(/Build Command:/);
expect(client.mockOutput.mock.calls[2][0]).toMatch(/Ignore Command:/);
expect(client.mockOutput.mock.calls[3][0]).toMatch(
expect((output.print as jest.Mock).mock.calls[1][0]).toMatch(
/Build Command:/
);
expect((output.print as jest.Mock).mock.calls[2][0]).toMatch(
/Ignore Command:/
);
expect((output.print as jest.Mock).mock.calls[3][0]).toMatch(
/Development Command:/
);
expect(client.mockOutput.mock.calls[4][0]).toMatch(/Framework:/);
expect(client.mockOutput.mock.calls[5][0]).toMatch(/Install Command:/);
expect(client.mockOutput.mock.calls[6][0]).toMatch(/Output Directory:/);
expect(client.mockOutput.mock.calls[7][0]).toMatch(
expect((output.print as jest.Mock).mock.calls[4][0]).toMatch(
/Framework:/
);
expect((output.print as jest.Mock).mock.calls[5][0]).toMatch(
/Install Command:/
);
expect((output.print as jest.Mock).mock.calls[6][0]).toMatch(
/Output Directory:/
);
expect((output.print as jest.Mock).mock.calls[7][0]).toMatch(
/Merging default Project Settings for Svelte. Previously listed overrides are prioritized./
);
expect(client.mockOutput.mock.calls[8][0]).toMatch(
expect((output.print as jest.Mock).mock.calls[8][0]).toMatch(
/Auto-detected Project Settings/
);

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "12.0.3",
"version": "12.0.2-canary.1",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -42,7 +42,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "4.2.1",
"@vercel/build-utils": "4.1.1-canary.1",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

@@ -1,3 +0,0 @@
# `@vercel/edge`
A set of utilities to help you deploy your app on the Edge using Vercel.

View File

@@ -1,30 +0,0 @@
{
"name": "@vercel/edge",
"version": "0.0.1",
"license": "MIT",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsup src/index.ts --dts --format esm,cjs",
"test-unit": "jest",
"prepublishOnly": "yarn build"
},
"devDependencies": {
"@edge-runtime/jest-environment": "1.1.0-beta.7",
"@types/jest": "27.4.1",
"ts-node": "8.9.1",
"tsup": "6.1.2",
"typescript": "4.7.4"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"globals": {
"ts-jest": {
"diagnostics": true,
"isolatedModules": true
}
}
}
}

View File

@@ -1,82 +0,0 @@
/**
* City of the original client IP calculated by Vercel Proxy.
*/
export const CITY_HEADER_NAME = 'x-vercel-ip-city';
/**
* Country of the original client IP calculated by Vercel Proxy.
*/
export const COUNTRY_HEADER_NAME = 'x-vercel-ip-country';
/**
* Ip from Vercel Proxy. Do not confuse it with the client Ip.
*/
export const IP_HEADER_NAME = 'x-real-ip';
/**
* Latitude of the original client IP calculated by Vercel Proxy.
*/
export const LATITUDE_HEADER_NAME = 'x-vercel-ip-latitude';
/**
* Longitude of the original client IP calculated by Vercel Proxy.
*/
export const LONGITUDE_HEADER_NAME = 'x-vercel-ip-longitude';
/**
* Region of the original client IP calculated by Vercel Proxy.
*/
export const REGION_HEADER_NAME = 'x-vercel-ip-country-region';
/**
* We define a new type so this function can be reused with
* the global `Request`, `node-fetch` and other types.
*/
interface Request {
headers: {
get(name: string): string | null;
};
}
/**
* The location information of a given request
*/
export interface Geo {
/** The city that the request originated from */
city?: string;
/** The country that the request originated from */
country?: string;
/** The Vercel Edge Network region that received the request */
region?: string;
/** The latitude of the client */
latitude?: string;
/** The longitude of the client */
longitude?: string;
}
function getHeader(request: Request, key: string): string | undefined {
return request.headers.get(key) ?? undefined;
}
/**
* Returns the IP address of the request from the headers.
*
* @see {@link IP_HEADER_NAME}
*/
export function ipAddress(request: Request): string | undefined {
return getHeader(request, IP_HEADER_NAME);
}
/**
* Returns the location information from for the incoming request
*
* @see {@link CITY_HEADER_NAME}
* @see {@link COUNTRY_HEADER_NAME}
* @see {@link REGION_HEADER_NAME}
* @see {@link LATITUDE_HEADER_NAME}
* @see {@link LONGITUDE_HEADER_NAME}
*/
export function geolocation(request: Request): Geo {
return {
city: getHeader(request, CITY_HEADER_NAME),
country: getHeader(request, COUNTRY_HEADER_NAME),
region: getHeader(request, REGION_HEADER_NAME),
latitude: getHeader(request, LATITUDE_HEADER_NAME),
longitude: getHeader(request, LONGITUDE_HEADER_NAME),
};
}

Some files were not shown because too many files have changed in this diff Show More