mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-12 12:57:47 +00:00
Compare commits
12 Commits
vercel-plu
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28f3bf9ef6 | ||
|
|
a936e92b8b | ||
|
|
ab1decf79d | ||
|
|
34408a7902 | ||
|
|
dc2d814d0f | ||
|
|
2402db92eb | ||
|
|
a1787c740d | ||
|
|
17fd88e044 | ||
|
|
03a8fbd3a7 | ||
|
|
8d37c1045f | ||
|
|
30c433d248 | ||
|
|
d89a79601c |
6
.github/CONTRIBUTING.md
vendored
6
.github/CONTRIBUTING.md
vendored
@@ -2,11 +2,11 @@
|
||||
|
||||
When contributing to this repository, please first discuss the change you wish to make via [GitHub Discussions](https://github.com/vercel/vercel/discussions/new) with the owners of this repository before submitting a Pull Request.
|
||||
|
||||
Please read our [code of conduct](CODE_OF_CONDUCT.md) and follow it in all your interactions with the project.
|
||||
Please read our [Code of Conduct](CODE_OF_CONDUCT.md) and follow it in all your interactions with the project.
|
||||
|
||||
## Local development
|
||||
|
||||
This project is configured in a monorepo pattern where one repo contains multiple npm packages. Dependencies are installed and managed with `yarn`, not `npm` CLI.
|
||||
This project is configured in a monorepo, where one repository contains multiple npm packages. Dependencies are installed and managed with `yarn`, not `npm` CLI.
|
||||
|
||||
To get started, execute the following:
|
||||
|
||||
@@ -23,7 +23,7 @@ Make sure all the tests pass before making changes.
|
||||
|
||||
## Verifying your change
|
||||
|
||||
Once you are done with your changes (we even suggest doing it along the way ), make sure all the test still run by running
|
||||
Once you are done with your changes (we even suggest doing it along the way), make sure all the test still run by running:
|
||||
|
||||
```
|
||||
yarn build && yarn test
|
||||
|
||||
36
README.md
36
README.md
@@ -3,13 +3,21 @@
|
||||
<img src="https://assets.vercel.com/image/upload/v1588805858/repositories/vercel/logo.png" height="96">
|
||||
<h3 align="center">Vercel</h3>
|
||||
</a>
|
||||
<p align="center">Develop. Preview. Ship.</p>
|
||||
</p>
|
||||
|
||||
[](https://github.com/vercel/vercel/actions/workflows/test-unit.yml)
|
||||
[](https://github.com/vercel/vercel/discussions)
|
||||
<p align="center">
|
||||
Develop. Preview. Ship.
|
||||
</p>
|
||||
|
||||
## Usage
|
||||
<p align="center">
|
||||
<a href="https://vercel.com/docs"><strong>Documentation</strong></a> ·
|
||||
<a href="https://vercel.com/changelog"><strong>Changelog</strong></a> ·
|
||||
<a href="https://vercel.com/templates"><strong>Templates</strong></a> ·
|
||||
<a href="https://vercel.com/cli"><strong>CLI</strong></a>
|
||||
</p>
|
||||
<br/>
|
||||
|
||||
## Vercel
|
||||
|
||||
Vercel is a platform for **static sites and frontend frameworks**, built to integrate with your headless content, commerce, or database.
|
||||
|
||||
@@ -17,22 +25,16 @@ We provide a **frictionless developer experience** to take care of the hard thin
|
||||
|
||||
We make it easy for frontend teams to **develop, preview, and ship** delightful user experiences, where performance is the default.
|
||||
|
||||
Get started by [Importing a Git Project](https://vercel.com/new) and use `git push` to deploy. Alternatively, you can [install Vercel CLI](https://vercel.com/cli).
|
||||
## Deploy
|
||||
|
||||
Get started by [importing a project](https://vercel.com/new) or using the [Vercel CLI](https://vercel.com/cli). Then, `git push` to deploy.
|
||||
|
||||
## Documentation
|
||||
|
||||
For details on how to use Vercel, check out our [documentation](https://vercel.com/docs).
|
||||
|
||||
## Caught a Bug?
|
||||
## Contributing
|
||||
|
||||
1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device
|
||||
2. Install dependencies with `yarn install`
|
||||
3. Compile the code: `yarn build`
|
||||
4. Link the package to the global module directory: `cd ./packages/cli && yarn link`
|
||||
5. You can start using `vercel` anywhere inside the command line
|
||||
|
||||
As always, you should use `yarn test-unit` to run the tests and see if your changes have broken anything.
|
||||
|
||||
## How to Create a Release
|
||||
|
||||
If you have write access to this repository, you can read more about how to publish a release [here](https://github.com/vercel/vercel/wiki/Creating-a-Release).
|
||||
- [Code of Conduct](https://github.com/vercel/vercel/blob/main/.github/CODE_OF_CONDUCT.md)
|
||||
- [Contributing Guidelines](https://github.com/vercel/vercel/blob/main/.github/CONTRIBUTING.md)
|
||||
- [MIT License](https://github.com/vercel/vercel/blob/main/LICENSE)
|
||||
|
||||
50
examples/README.md
vendored
50
examples/README.md
vendored
@@ -1,28 +1,6 @@
|
||||
# Vercel Examples
|
||||
|
||||
This is the public list of examples for **Vercel**.
|
||||
|
||||
All of these ready to deploy examples feature a frontend framework or static site, created with zero configuration using the CLI tools they provide.
|
||||
|
||||
The `+functions` examples feature an `/api` directory as well, highlighting how to use serverless functions on top of a framework, again with zero configuration required.
|
||||
|
||||
## What is Vercel?
|
||||
|
||||
Vercel is a cloud platform for static frontends and serverless functions. It enables developers to host websites and web applications that deploy instantly, scale automatically, and require no supervision.
|
||||
|
||||
## What Does this Repository Contain?
|
||||
|
||||
This repository consists of multiple examples, created for use with the [Vercel](https://vercel.com) platform. In addition to this, it also contains:
|
||||
|
||||
- [Code of Conduct](https://github.com/vercel/vercel/blob/main/.github/CODE_OF_CONDUCT.md) - our Code of Conduct, adapted from the [Contributor Covenant](http://contributor-covenant.org)
|
||||
- [Contributing Guidelines](https://github.com/vercel/vercel/blob/main/.github/CONTRIBUTING.md) - a guide on how to contribute to the examples repository
|
||||
- [License](https://github.com/vercel/vercel/blob/main/LICENSE) - the standard MIT license under which these examples are published
|
||||
|
||||
We recommend familiarizing yourself with the above sections, particularly if you are looking to make a contribution.
|
||||
|
||||
## Deploying Examples
|
||||
|
||||
To get started using any of these examples as your own project, [install Vercel](https://vercel.com/download) and use either of the following commands in your terminal:
|
||||
To get started using any of these examples as your own project, [install Vercel](https://vercel.com/cli) and use either of the following commands in your terminal:
|
||||
|
||||
```sh
|
||||
vercel init # Pick an example in the CLI
|
||||
@@ -30,7 +8,7 @@ vercel init <example> # Create a new project from a specific <example>
|
||||
vercel init <example> <name> # Create a new project from a specific <example> with a different folder <name>
|
||||
```
|
||||
|
||||
Deploying your project takes seconds and can be done with **just a single command**:
|
||||
Deploying your project can be done with **a single command**:
|
||||
|
||||
```sh
|
||||
vercel # Deploy your project with the CLI
|
||||
@@ -38,26 +16,6 @@ vercel # Deploy your project with the CLI
|
||||
|
||||
With the `vercel` command, your project will be built and served by Vercel, providing you with a URL that can be shared immediately.
|
||||
|
||||
## New Examples
|
||||
|
||||
We are continuously improving our examples based on best practices and feedback from the community. As a result, it is possible that example names will change and on occasion deprecated in favor of an improved implementation.
|
||||
|
||||
For example, the previous `nodejs` example showed a static frontend with a Node.js API. This is illustrated in the `svelte` example. Below is a table that lists some of the most popular previous examples and the equivalent replacement:
|
||||
|
||||
| Previous Example | New Example |
|
||||
| ----------------- | ---------------------------------------------------------------------------------------- |
|
||||
| **monorepo** | [gatsby-functions](https://github.com/vercel/vercel/tree/main/examples/gatsby) |
|
||||
| **nodejs** | [svelte-functions](https://github.com/vercel/vercel/tree/main/examples/svelte) |
|
||||
| **nextjs-static** | [nextjs](https://github.com/vercel/vercel/tree/main/examples/nextjs) |
|
||||
| **vanilla-go** | [create-react-app](https://github.com/vercel/vercel/tree/main/examples/create-react-app) |
|
||||
| **typescript** | [gatsby-functions](https://github.com/vercel/vercel/tree/main/examples/gatsby) |
|
||||
|
||||
## Migrating and Upgrading
|
||||
|
||||
If you have an existing project you would like to deploy with Vercel, we recommend reading our guide on [migrating to Vercel and zero configuration](https://vercel.com/guides/migrate-to-vercel). By combining the guide with this repository, you will quickly be able to understand how to deploy your application.
|
||||
|
||||
If you would like to upgrade a project to take advantage of zero configuration, you may find the [upgrade guide](https://vercel.com/guides/upgrade-to-zero-configuration) useful. The upgrade guide covers how to remove configuration from existing projects along with how to use the `/api` directory.
|
||||
|
||||
## How to Contribute
|
||||
|
||||
Contributing examples should be an enjoyable experience, as such we have created a set of [contributing guidelines](https://github.com/vercel/vercel/blob/main/.github/CONTRIBUTING.md) to help you do so.
|
||||
@@ -74,10 +32,6 @@ An issue can be raised by clicking the 'Issues' tab at the top of the repository
|
||||
|
||||
When submitting an issue, please thoroughly and concisely describe the problem you are experiencing so that we may easily understand and resolve the issue in a timely manner.
|
||||
|
||||
## License
|
||||
|
||||
This repository is an open source project. See the [License](https://github.com/vercel/vercel/blob/main/LICENSE).
|
||||
|
||||
## Get In Touch
|
||||
|
||||
If you have any questions that are not covered by raising an issue then please get in touch with us on [GitHub Discussions](https://github.com/vercel/vercel/discussions). There you will find both members of the community and staff who are happy to help answer questions on anything Vercel related.
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function Home() {
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://github.com/vercel/next.js/tree/master/examples"
|
||||
href="https://github.com/vercel/next.js/tree/canary/examples"
|
||||
className={styles.card}
|
||||
>
|
||||
<h2>Examples →</h2>
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
padding: 0 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 5rem 0;
|
||||
min-height: 100vh;
|
||||
padding: 4rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -18,10 +13,10 @@
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
border-top: 1px solid #eaeaea;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 2rem 0;
|
||||
border-top: 1px solid #eaeaea;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -56,6 +51,7 @@
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 4rem 0;
|
||||
line-height: 1.5;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
@@ -75,7 +71,6 @@
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
max-width: 800px;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -87,7 +82,7 @@
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 10px;
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
width: 45%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "2.13.1-canary.0",
|
||||
"version": "2.13.1-canary.2",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
@@ -30,7 +30,7 @@
|
||||
"@types/node-fetch": "^2.1.6",
|
||||
"@types/semver": "6.0.0",
|
||||
"@types/yazl": "^2.4.1",
|
||||
"@vercel/frameworks": "0.5.1-canary.20",
|
||||
"@vercel/frameworks": "0.5.1-canary.21",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"aggregate-error": "3.0.1",
|
||||
"async-retry": "1.2.3",
|
||||
|
||||
@@ -2,8 +2,8 @@ import fs from 'fs-extra';
|
||||
import { join, parse, relative, dirname, basename, extname } from 'path';
|
||||
import glob from './fs/glob';
|
||||
import { normalizePath } from './fs/normalize-path';
|
||||
import { FILES_SYMBOL, Lambda } from './lambda';
|
||||
import type { BuildOptions, Files } from './types';
|
||||
import { Lambda } from './lambda';
|
||||
import type { BuildOptions } from './types';
|
||||
import { debug, getIgnoreFilter } from '.';
|
||||
|
||||
// `.output` was already created by the Build Command, so we have
|
||||
@@ -116,8 +116,7 @@ export function _experimental_convertRuntimeToPlugin(
|
||||
},
|
||||
});
|
||||
|
||||
// @ts-ignore This symbol is a private API
|
||||
const lambdaFiles: Files = output[FILES_SYMBOL];
|
||||
const lambdaFiles = output.files;
|
||||
|
||||
// When deploying, the `files` that are passed to the Legacy Runtimes already
|
||||
// have certain files that are ignored stripped, but locally, that list of
|
||||
|
||||
@@ -328,8 +328,12 @@ export async function runNpmInstall(
|
||||
const opts: SpawnOptionsExtended = { cwd: destPath, ...spawnOpts };
|
||||
const env = opts.env ? { ...opts.env } : { ...process.env };
|
||||
delete env.NODE_ENV;
|
||||
opts.env = env;
|
||||
|
||||
opts.env = getEnvForPackageManager({
|
||||
cliType,
|
||||
lockfileVersion,
|
||||
nodeVersion,
|
||||
env,
|
||||
});
|
||||
let commandArgs: string[];
|
||||
|
||||
if (cliType === 'npm') {
|
||||
@@ -337,25 +341,9 @@ export async function runNpmInstall(
|
||||
commandArgs = args
|
||||
.filter(a => a !== '--prefer-offline')
|
||||
.concat(['install', '--no-audit', '--unsafe-perm']);
|
||||
|
||||
// If the lockfile version is 2 or greater and the node version is less than 16 than we will force npm7 to be used
|
||||
if (
|
||||
typeof lockfileVersion === 'number' &&
|
||||
lockfileVersion >= 2 &&
|
||||
(nodeVersion?.major || 0) < 16
|
||||
) {
|
||||
// Ensure that npm 7 is at the beginning of the `$PATH`
|
||||
env.PATH = `/node16/bin-npm7:${env.PATH}`;
|
||||
console.log('Detected `package-lock.json` generated by npm 7...');
|
||||
}
|
||||
} else {
|
||||
opts.prettyCommand = 'yarn install';
|
||||
commandArgs = ['install', ...args];
|
||||
|
||||
// Yarn v2 PnP mode may be activated, so force "node-modules" linker style
|
||||
if (!env.YARN_NODE_LINKER) {
|
||||
env.YARN_NODE_LINKER = 'node-modules';
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NPM_ONLY_PRODUCTION) {
|
||||
@@ -365,6 +353,65 @@ export async function runNpmInstall(
|
||||
return spawnAsync(cliType, commandArgs, opts);
|
||||
}
|
||||
|
||||
export function getEnvForPackageManager({
|
||||
cliType,
|
||||
lockfileVersion,
|
||||
nodeVersion,
|
||||
env,
|
||||
}: {
|
||||
cliType: CliType;
|
||||
lockfileVersion: number | undefined;
|
||||
nodeVersion: NodeVersion | undefined;
|
||||
env: { [x: string]: string | undefined };
|
||||
}) {
|
||||
const newEnv: { [x: string]: string | undefined } = { ...env };
|
||||
if (cliType === 'npm') {
|
||||
if (
|
||||
typeof lockfileVersion === 'number' &&
|
||||
lockfileVersion >= 2 &&
|
||||
(nodeVersion?.major || 0) < 16
|
||||
) {
|
||||
// Ensure that npm 7 is at the beginning of the `$PATH`
|
||||
newEnv.PATH = `/node16/bin-npm7:${env.PATH}`;
|
||||
console.log('Detected `package-lock.json` generated by npm 7...');
|
||||
}
|
||||
} else {
|
||||
// Yarn v2 PnP mode may be activated, so force "node-modules" linker style
|
||||
if (!env.YARN_NODE_LINKER) {
|
||||
newEnv.YARN_NODE_LINKER = 'node-modules';
|
||||
}
|
||||
}
|
||||
|
||||
return newEnv;
|
||||
}
|
||||
|
||||
export async function runCustomInstallCommand({
|
||||
destPath,
|
||||
installCommand,
|
||||
nodeVersion,
|
||||
spawnOpts,
|
||||
}: {
|
||||
destPath: string;
|
||||
installCommand: string;
|
||||
nodeVersion: NodeVersion;
|
||||
spawnOpts?: SpawnOptions;
|
||||
}) {
|
||||
console.log(`Running "install" command: \`${installCommand}\`...`);
|
||||
const { cliType, lockfileVersion } = await scanParentDirs(destPath);
|
||||
const env = getEnvForPackageManager({
|
||||
cliType,
|
||||
lockfileVersion,
|
||||
nodeVersion,
|
||||
env: spawnOpts?.env || {},
|
||||
});
|
||||
debug(`Running with $PATH:`, env?.PATH || '');
|
||||
await execCommand(installCommand, {
|
||||
...spawnOpts,
|
||||
env,
|
||||
cwd: destPath,
|
||||
});
|
||||
}
|
||||
|
||||
export async function runPackageJsonScript(
|
||||
destPath: string,
|
||||
scriptNames: string | Iterable<string>,
|
||||
@@ -385,23 +432,24 @@ export async function runPackageJsonScript(
|
||||
debug('Running user script...');
|
||||
const runScriptTime = Date.now();
|
||||
|
||||
const opts: SpawnOptionsExtended = { cwd: destPath, ...spawnOpts };
|
||||
const env = (opts.env = { ...process.env, ...opts.env });
|
||||
const opts: SpawnOptionsExtended = {
|
||||
cwd: destPath,
|
||||
...spawnOpts,
|
||||
env: getEnvForPackageManager({
|
||||
cliType,
|
||||
lockfileVersion,
|
||||
nodeVersion: undefined,
|
||||
env: {
|
||||
...process.env,
|
||||
...spawnOpts?.env,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
if (cliType === 'npm') {
|
||||
opts.prettyCommand = `npm run ${scriptName}`;
|
||||
|
||||
if (typeof lockfileVersion === 'number' && lockfileVersion >= 2) {
|
||||
// Ensure that npm 7 is at the beginning of the `$PATH`
|
||||
env.PATH = `/node16/bin-npm7:${env.PATH}`;
|
||||
}
|
||||
} else {
|
||||
opts.prettyCommand = `yarn run ${scriptName}`;
|
||||
|
||||
// Yarn v2 PnP mode may be activated, so force "node-modules" linker style
|
||||
if (!env.YARN_NODE_LINKER) {
|
||||
env.YARN_NODE_LINKER = 'node-modules';
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Running "${opts.prettyCommand}"`);
|
||||
|
||||
@@ -20,6 +20,8 @@ import {
|
||||
runBundleInstall,
|
||||
runPipInstall,
|
||||
runShellScript,
|
||||
runCustomInstallCommand,
|
||||
getEnvForPackageManager,
|
||||
getNodeVersion,
|
||||
getSpawnOptions,
|
||||
getNodeBinPath,
|
||||
@@ -61,6 +63,8 @@ export {
|
||||
runBundleInstall,
|
||||
runPipInstall,
|
||||
runShellScript,
|
||||
runCustomInstallCommand,
|
||||
getEnvForPackageManager,
|
||||
getNodeVersion,
|
||||
getLatestNodeVersion,
|
||||
getDiscontinuedNodeVersions,
|
||||
|
||||
@@ -13,17 +13,6 @@ interface Environment {
|
||||
}
|
||||
|
||||
interface LambdaOptions {
|
||||
zipBuffer: Buffer;
|
||||
handler: string;
|
||||
runtime: string;
|
||||
memory?: number;
|
||||
maxDuration?: number;
|
||||
environment: Environment;
|
||||
allowQuery?: string[];
|
||||
regions?: string[];
|
||||
}
|
||||
|
||||
interface CreateLambdaOptions {
|
||||
files: Files;
|
||||
handler: string;
|
||||
runtime: string;
|
||||
@@ -32,6 +21,10 @@ interface CreateLambdaOptions {
|
||||
environment?: Environment;
|
||||
allowQuery?: string[];
|
||||
regions?: string[];
|
||||
/**
|
||||
* @deprecated Use `files` property instead.
|
||||
*/
|
||||
zipBuffer?: Buffer;
|
||||
}
|
||||
|
||||
interface GetLambdaOptionsFromFunctionOptions {
|
||||
@@ -39,11 +32,9 @@ interface GetLambdaOptionsFromFunctionOptions {
|
||||
config?: Pick<Config, 'functions'>;
|
||||
}
|
||||
|
||||
export const FILES_SYMBOL = Symbol('files');
|
||||
|
||||
export class Lambda {
|
||||
public type: 'Lambda';
|
||||
public zipBuffer: Buffer;
|
||||
public files: Files;
|
||||
public handler: string;
|
||||
public runtime: string;
|
||||
public memory?: number;
|
||||
@@ -51,19 +42,54 @@ export class Lambda {
|
||||
public environment: Environment;
|
||||
public allowQuery?: string[];
|
||||
public regions?: string[];
|
||||
/**
|
||||
* @deprecated Use `await lambda.createZip()` instead.
|
||||
*/
|
||||
public zipBuffer?: Buffer;
|
||||
|
||||
constructor({
|
||||
zipBuffer,
|
||||
files,
|
||||
handler,
|
||||
runtime,
|
||||
maxDuration,
|
||||
memory,
|
||||
environment,
|
||||
environment = {},
|
||||
allowQuery,
|
||||
regions,
|
||||
zipBuffer,
|
||||
}: LambdaOptions) {
|
||||
if (!zipBuffer) {
|
||||
assert(typeof files === 'object', '"files" must be an object');
|
||||
}
|
||||
assert(typeof handler === 'string', '"handler" is not a string');
|
||||
assert(typeof runtime === 'string', '"runtime" is not a string');
|
||||
assert(typeof environment === 'object', '"environment" is not an object');
|
||||
|
||||
if (memory !== undefined) {
|
||||
assert(typeof memory === 'number', '"memory" is not a number');
|
||||
}
|
||||
|
||||
if (maxDuration !== undefined) {
|
||||
assert(typeof maxDuration === 'number', '"maxDuration" is not a number');
|
||||
}
|
||||
|
||||
if (allowQuery !== undefined) {
|
||||
assert(Array.isArray(allowQuery), '"allowQuery" is not an Array');
|
||||
assert(
|
||||
allowQuery.every(q => typeof q === 'string'),
|
||||
'"allowQuery" is not a string Array'
|
||||
);
|
||||
}
|
||||
|
||||
if (regions !== undefined) {
|
||||
assert(Array.isArray(regions), '"regions" is not an Array');
|
||||
assert(
|
||||
regions.every(r => typeof r === 'string'),
|
||||
'"regions" is not a string Array'
|
||||
);
|
||||
}
|
||||
this.type = 'Lambda';
|
||||
this.zipBuffer = zipBuffer;
|
||||
this.files = files;
|
||||
this.handler = handler;
|
||||
this.runtime = runtime;
|
||||
this.memory = memory;
|
||||
@@ -71,70 +97,36 @@ export class Lambda {
|
||||
this.environment = environment;
|
||||
this.allowQuery = allowQuery;
|
||||
this.regions = regions;
|
||||
this.zipBuffer = zipBuffer;
|
||||
}
|
||||
|
||||
async createZip(): Promise<Buffer> {
|
||||
let { zipBuffer } = this;
|
||||
if (!zipBuffer) {
|
||||
await sema.acquire();
|
||||
try {
|
||||
zipBuffer = await createZip(this.files);
|
||||
} finally {
|
||||
sema.release();
|
||||
}
|
||||
}
|
||||
return zipBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
const sema = new Sema(10);
|
||||
const mtime = new Date(1540000000000);
|
||||
|
||||
export async function createLambda({
|
||||
files,
|
||||
handler,
|
||||
runtime,
|
||||
memory,
|
||||
maxDuration,
|
||||
environment = {},
|
||||
allowQuery,
|
||||
regions,
|
||||
}: CreateLambdaOptions): Promise<Lambda> {
|
||||
assert(typeof files === 'object', '"files" must be an object');
|
||||
assert(typeof handler === 'string', '"handler" is not a string');
|
||||
assert(typeof runtime === 'string', '"runtime" is not a string');
|
||||
assert(typeof environment === 'object', '"environment" is not an object');
|
||||
/**
|
||||
* @deprecated Use `new Lambda()` instead.
|
||||
*/
|
||||
export async function createLambda(opts: LambdaOptions): Promise<Lambda> {
|
||||
const lambda = new Lambda(opts);
|
||||
|
||||
if (memory !== undefined) {
|
||||
assert(typeof memory === 'number', '"memory" is not a number');
|
||||
}
|
||||
// backwards compat
|
||||
lambda.zipBuffer = await lambda.createZip();
|
||||
|
||||
if (maxDuration !== undefined) {
|
||||
assert(typeof maxDuration === 'number', '"maxDuration" is not a number');
|
||||
}
|
||||
|
||||
if (allowQuery !== undefined) {
|
||||
assert(Array.isArray(allowQuery), '"allowQuery" is not an Array');
|
||||
assert(
|
||||
allowQuery.every(q => typeof q === 'string'),
|
||||
'"allowQuery" is not a string Array'
|
||||
);
|
||||
}
|
||||
|
||||
if (regions !== undefined) {
|
||||
assert(Array.isArray(regions), '"regions" is not an Array');
|
||||
assert(
|
||||
regions.every(r => typeof r === 'string'),
|
||||
'"regions" is not a string Array'
|
||||
);
|
||||
}
|
||||
|
||||
await sema.acquire();
|
||||
|
||||
try {
|
||||
const zipBuffer = await createZip(files);
|
||||
const lambda = new Lambda({
|
||||
zipBuffer,
|
||||
handler,
|
||||
runtime,
|
||||
memory,
|
||||
maxDuration,
|
||||
environment,
|
||||
regions,
|
||||
});
|
||||
// @ts-ignore This symbol is a private API
|
||||
lambda[FILES_SYMBOL] = files;
|
||||
return lambda;
|
||||
} finally {
|
||||
sema.release();
|
||||
}
|
||||
return lambda;
|
||||
}
|
||||
|
||||
export async function createZip(files: Files): Promise<Buffer> {
|
||||
@@ -177,7 +169,7 @@ export async function getLambdaOptionsFromFunction({
|
||||
}: GetLambdaOptionsFromFunctionOptions): Promise<
|
||||
Pick<LambdaOptions, 'memory' | 'maxDuration'>
|
||||
> {
|
||||
if (config && config.functions) {
|
||||
if (config?.functions) {
|
||||
for (const [pattern, fn] of Object.entries(config.functions)) {
|
||||
if (sourceFile === pattern || minimatch(sourceFile, pattern)) {
|
||||
return {
|
||||
|
||||
109
packages/build-utils/test/unit.get-env-for-package-manager.test.ts
vendored
Normal file
109
packages/build-utils/test/unit.get-env-for-package-manager.test.ts
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
import assert from 'assert';
|
||||
import { getEnvForPackageManager, NodeVersion } from '../src';
|
||||
import { CliType } from '../src/fs/run-user-scripts';
|
||||
|
||||
describe('Test `getEnvForPackageManager()`', () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'should do nothing to env for npm < 6 and node < 16',
|
||||
args: {
|
||||
cliType: 'npm' as CliType,
|
||||
nodeVersion: {
|
||||
major: 14,
|
||||
} as NodeVersion,
|
||||
lockfileVersion: 1,
|
||||
env: {
|
||||
FOO: 'bar',
|
||||
},
|
||||
},
|
||||
want: {
|
||||
FOO: 'bar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should set path if npm 7+ is detected and node < 16',
|
||||
args: {
|
||||
cliType: 'npm' as CliType,
|
||||
nodeVersion: {
|
||||
major: 14,
|
||||
} as NodeVersion,
|
||||
lockfileVersion: 2,
|
||||
env: {
|
||||
FOO: 'bar',
|
||||
PATH: 'foo',
|
||||
},
|
||||
},
|
||||
want: {
|
||||
FOO: 'bar',
|
||||
PATH: `/node16/bin-npm7:foo`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should not set path if node is 16 and npm 7+ is detected',
|
||||
args: {
|
||||
cliType: 'npm' as CliType,
|
||||
nodeVersion: {
|
||||
major: 16,
|
||||
} as NodeVersion,
|
||||
lockfileVersion: 2,
|
||||
env: {
|
||||
FOO: 'bar',
|
||||
PATH: 'foo',
|
||||
},
|
||||
},
|
||||
want: {
|
||||
FOO: 'bar',
|
||||
PATH: 'foo',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should set YARN_NODE_LINKER w/yarn if it is not already defined',
|
||||
args: {
|
||||
cliType: 'yarn' as CliType,
|
||||
nodeVersion: {
|
||||
major: 16,
|
||||
} as NodeVersion,
|
||||
lockfileVersion: 2,
|
||||
env: {
|
||||
FOO: 'bar',
|
||||
},
|
||||
},
|
||||
want: {
|
||||
FOO: 'bar',
|
||||
YARN_NODE_LINKER: 'node-modules',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should not set YARN_NODE_LINKER if it already exists',
|
||||
args: {
|
||||
cliType: 'yarn' as CliType,
|
||||
nodeVersion: {
|
||||
major: 16,
|
||||
} as NodeVersion,
|
||||
lockfileVersion: 2,
|
||||
env: {
|
||||
FOO: 'bar',
|
||||
YARN_NODE_LINKER: 'exists',
|
||||
},
|
||||
},
|
||||
want: {
|
||||
FOO: 'bar',
|
||||
YARN_NODE_LINKER: 'exists',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const { name, want, args } of cases) {
|
||||
it(name, () => {
|
||||
assert.deepStrictEqual(
|
||||
getEnvForPackageManager({
|
||||
cliType: args.cliType,
|
||||
lockfileVersion: args.lockfileVersion,
|
||||
nodeVersion: args.nodeVersion,
|
||||
env: args.env,
|
||||
}),
|
||||
want
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "23.1.3-canary.73",
|
||||
"version": "23.1.3-canary.75",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -43,14 +43,14 @@
|
||||
"node": ">= 12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.13.1-canary.0",
|
||||
"@vercel/go": "1.2.4-canary.5",
|
||||
"@vercel/node": "1.12.2-canary.8",
|
||||
"@vercel/python": "2.1.2-canary.3",
|
||||
"@vercel/ruby": "1.2.10-canary.1",
|
||||
"@vercel/build-utils": "2.13.1-canary.2",
|
||||
"@vercel/go": "1.2.4-canary.7",
|
||||
"@vercel/node": "1.12.2-canary.10",
|
||||
"@vercel/python": "2.1.2-canary.5",
|
||||
"@vercel/ruby": "1.2.10-canary.3",
|
||||
"update-notifier": "4.1.0",
|
||||
"vercel-plugin-middleware": "0.0.0-canary.25",
|
||||
"vercel-plugin-node": "1.12.2-canary.40"
|
||||
"vercel-plugin-middleware": "0.0.0-canary.27",
|
||||
"vercel-plugin-node": "1.12.2-canary.42"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/env": "11.1.2",
|
||||
@@ -90,8 +90,9 @@
|
||||
"@types/update-notifier": "5.1.0",
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@vercel/client": "10.2.3-canary.51",
|
||||
"@vercel/frameworks": "0.5.1-canary.20",
|
||||
"@vercel/client": "10.2.3-canary.53",
|
||||
"@vercel/fetch-retry": "5.0.3",
|
||||
"@vercel/frameworks": "0.5.1-canary.21",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@vercel/nft": "0.17.0",
|
||||
"@zeit/fun": "0.11.2",
|
||||
@@ -142,7 +143,7 @@
|
||||
"ms": "2.1.2",
|
||||
"node-fetch": "2.6.1",
|
||||
"npm-package-arg": "6.1.0",
|
||||
"open": "8.2.0",
|
||||
"open": "8.4.0",
|
||||
"ora": "3.4.0",
|
||||
"pcre-to-regexp": "1.0.0",
|
||||
"pluralize": "7.0.0",
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Output } from '../../util/output';
|
||||
import * as ERRORS from '../../util/errors-ts';
|
||||
import assignAlias from '../../util/alias/assign-alias';
|
||||
import Client from '../../util/client';
|
||||
import formatNSTable from '../../util/format-ns-table';
|
||||
import getDeploymentByIdOrHost from '../../util/deploy/get-deployment-by-id-or-host';
|
||||
import { getDeploymentForAlias } from '../../util/alias/get-deployment-by-alias';
|
||||
import getScope from '../../util/get-scope';
|
||||
@@ -226,29 +225,6 @@ function handleSetupDomainError<T>(
|
||||
output: Output,
|
||||
error: SetupDomainError | T
|
||||
): T | 1 {
|
||||
if (
|
||||
error instanceof ERRORS.DomainVerificationFailed ||
|
||||
error instanceof ERRORS.DomainNsNotVerifiedForWildcard
|
||||
) {
|
||||
const { nsVerification, domain } = error.meta;
|
||||
|
||||
output.error(
|
||||
`We could not alias since the domain ${domain} could not be verified due to the following reasons:\n`
|
||||
);
|
||||
output.print(
|
||||
`Nameservers verification failed since we see a different set than the intended set:`
|
||||
);
|
||||
output.print(
|
||||
`\n${formatNSTable(
|
||||
nsVerification.intendedNameservers,
|
||||
nsVerification.nameservers,
|
||||
{ extraSpace: ' ' }
|
||||
)}\n\n`
|
||||
);
|
||||
output.print(' Read more: https://err.sh/vercel/domain-verification\n');
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (error instanceof ERRORS.DomainPermissionDenied) {
|
||||
output.error(
|
||||
`You don't have permissions over domain ${chalk.underline(
|
||||
|
||||
411
packages/cli/src/commands/bisect/index.ts
Normal file
411
packages/cli/src/commands/bisect/index.ts
Normal file
@@ -0,0 +1,411 @@
|
||||
import open from 'open';
|
||||
import boxen from 'boxen';
|
||||
import execa from 'execa';
|
||||
import plural from 'pluralize';
|
||||
import inquirer from 'inquirer';
|
||||
import { resolve } from 'path';
|
||||
import chalk, { Chalk } from 'chalk';
|
||||
import { URLSearchParams, parse } from 'url';
|
||||
|
||||
import sleep from '../../util/sleep';
|
||||
import formatDate from '../../util/format-date';
|
||||
import link from '../../util/output/link';
|
||||
import logo from '../../util/output/logo';
|
||||
import getArgs from '../../util/get-args';
|
||||
import Client from '../../util/client';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
import { Output } from '../../util/output';
|
||||
import { Deployment, PaginationOptions } from '../../types';
|
||||
|
||||
interface DeploymentV6
|
||||
extends Pick<
|
||||
Deployment,
|
||||
'url' | 'target' | 'projectId' | 'ownerId' | 'meta' | 'inspectorUrl'
|
||||
> {
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
interface Deployments {
|
||||
deployments: DeploymentV6[];
|
||||
pagination: PaginationOptions;
|
||||
}
|
||||
|
||||
const pkgName = getPkgName();
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${pkgName} bisect`)} [options]
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
-d, --debug Debug mode [off]
|
||||
-b, --bad Known bad URL
|
||||
-g, --good Known good URL
|
||||
-o, --open Automatically open each URL in the browser
|
||||
-p, --path Subpath of the deployment URL to test
|
||||
-r, --run Test script to run for each deployment
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Bisect the current project interactively
|
||||
|
||||
${chalk.cyan(`$ ${pkgName} bisect`)}
|
||||
|
||||
${chalk.gray('–')} Bisect with a known bad deployment
|
||||
|
||||
${chalk.cyan(`$ ${pkgName} bisect --bad example-310pce9i0.vercel.app`)}
|
||||
|
||||
${chalk.gray('–')} Bisect specifying a deployment that was working 3 days ago
|
||||
|
||||
${chalk.cyan(`$ ${pkgName} bisect --good 3d`)}
|
||||
|
||||
${chalk.gray('–')} Automated bisect with a run script
|
||||
|
||||
${chalk.cyan(`$ ${pkgName} bisect --run ./test.sh`)}
|
||||
`);
|
||||
};
|
||||
|
||||
export default async function main(client: Client): Promise<number> {
|
||||
const { output } = client;
|
||||
|
||||
const argv = getArgs(client.argv.slice(2), {
|
||||
'--bad': String,
|
||||
'-b': '--bad',
|
||||
'--good': String,
|
||||
'-g': '--good',
|
||||
'--open': Boolean,
|
||||
'-o': '--open',
|
||||
'--path': String,
|
||||
'-p': '--path',
|
||||
'--run': String,
|
||||
'-r': '--run',
|
||||
});
|
||||
|
||||
if (argv['--help']) {
|
||||
help();
|
||||
return 2;
|
||||
}
|
||||
|
||||
let bad =
|
||||
argv['--bad'] ||
|
||||
(await prompt(output, `Specify a URL where the bug occurs:`));
|
||||
let good =
|
||||
argv['--good'] ||
|
||||
(await prompt(output, `Specify a URL where the bug does not occur:`));
|
||||
let subpath = argv['--path'] || '';
|
||||
let run = argv['--run'] || '';
|
||||
const openEnabled = argv['--open'] || false;
|
||||
|
||||
if (run) {
|
||||
run = resolve(run);
|
||||
}
|
||||
|
||||
if (!bad.startsWith('https://')) {
|
||||
bad = `https://${bad}`;
|
||||
}
|
||||
let parsed = parse(bad);
|
||||
if (!parsed.hostname) {
|
||||
output.error('Invalid input: no hostname provided');
|
||||
return 1;
|
||||
}
|
||||
bad = parsed.hostname;
|
||||
if (typeof parsed.path === 'string' && parsed.path !== '/') {
|
||||
if (subpath && subpath !== parsed.path) {
|
||||
output.note(
|
||||
`Ignoring subpath ${chalk.bold(
|
||||
parsed.path
|
||||
)} in favor of \`--path\` argument ${chalk.bold(subpath)}`
|
||||
);
|
||||
} else {
|
||||
subpath = parsed.path;
|
||||
}
|
||||
}
|
||||
|
||||
const badDeploymentPromise = getDeployment(client, bad).catch(err => err);
|
||||
|
||||
if (!good.startsWith('https://')) {
|
||||
good = `https://${good}`;
|
||||
}
|
||||
parsed = parse(good);
|
||||
if (!parsed.hostname) {
|
||||
output.error('Invalid input: no hostname provided');
|
||||
return 1;
|
||||
}
|
||||
good = parsed.hostname;
|
||||
if (
|
||||
typeof parsed.path === 'string' &&
|
||||
parsed.path !== '/' &&
|
||||
subpath &&
|
||||
subpath !== parsed.path
|
||||
) {
|
||||
output.note(
|
||||
`Ignoring subpath ${chalk.bold(
|
||||
parsed.path
|
||||
)} which does not match ${chalk.bold(subpath)}`
|
||||
);
|
||||
}
|
||||
|
||||
const goodDeploymentPromise = getDeployment(client, good).catch(err => err);
|
||||
|
||||
if (!subpath) {
|
||||
subpath = await prompt(
|
||||
output,
|
||||
`Specify the URL subpath where the bug occurs:`
|
||||
);
|
||||
}
|
||||
|
||||
output.spinner('Retrieving deployments…');
|
||||
const [badDeployment, goodDeployment] = await Promise.all([
|
||||
badDeploymentPromise,
|
||||
goodDeploymentPromise,
|
||||
]);
|
||||
|
||||
if (badDeployment) {
|
||||
if (badDeployment instanceof Error) {
|
||||
badDeployment.message += ` "${bad}"`;
|
||||
output.prettyError(badDeployment);
|
||||
return 1;
|
||||
}
|
||||
bad = badDeployment.url;
|
||||
} else {
|
||||
output.error(`Failed to retrieve ${chalk.bold('bad')} Deployment: ${bad}`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { projectId } = badDeployment;
|
||||
|
||||
if (goodDeployment) {
|
||||
if (goodDeployment instanceof Error) {
|
||||
goodDeployment.message += ` "${good}"`;
|
||||
output.prettyError(goodDeployment);
|
||||
return 1;
|
||||
}
|
||||
good = goodDeployment.url;
|
||||
} else {
|
||||
output.error(
|
||||
`Failed to retrieve ${chalk.bold('good')} Deployment: ${good}`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (projectId !== goodDeployment.projectId) {
|
||||
output.error(`Good and Bad deployments must be from the same Project`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (badDeployment.createdAt < goodDeployment.createdAt) {
|
||||
output.error(`Good deployment must be older than the Bad deployment`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (badDeployment.target !== goodDeployment.target) {
|
||||
output.error(
|
||||
`Bad deployment target "${badDeployment.target || 'preview'}" does not match good deployment target "${goodDeployment.target || 'preview'}"`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Fetch all the project's "READY" deployments with the pagination API
|
||||
let deployments: DeploymentV6[] = [];
|
||||
let next: number | undefined = badDeployment.createdAt + 1;
|
||||
do {
|
||||
const query = new URLSearchParams();
|
||||
query.set('projectId', projectId);
|
||||
if (badDeployment.target) {
|
||||
query.set('target', badDeployment.target);
|
||||
}
|
||||
query.set('limit', '100');
|
||||
query.set('state', 'READY');
|
||||
if (next) {
|
||||
query.set('until', String(next));
|
||||
}
|
||||
|
||||
const res = await client.fetch<Deployments>(`/v6/deployments?${query}`, {
|
||||
accountId: badDeployment.ownerId,
|
||||
});
|
||||
|
||||
next = res.pagination.next;
|
||||
|
||||
let newDeployments = res.deployments;
|
||||
|
||||
// If we have the "good" deployment in this chunk, then we're done
|
||||
for (let i = 0; i < newDeployments.length; i++) {
|
||||
if (newDeployments[i].url === good) {
|
||||
newDeployments = newDeployments.slice(0, i + 1);
|
||||
next = undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
deployments = deployments.concat(newDeployments);
|
||||
|
||||
if (next) {
|
||||
// Small sleep to avoid rate limiting
|
||||
await sleep(100);
|
||||
}
|
||||
} while (next);
|
||||
|
||||
if (!deployments.length) {
|
||||
output.error(
|
||||
'Cannot bisect because this project does not have any deployments'
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// The first deployment is the one that was marked
|
||||
// as "bad", so that one does not need to be tested
|
||||
let lastBad = deployments.shift()!;
|
||||
|
||||
while (deployments.length > 0) {
|
||||
// Add a blank space before the next step
|
||||
output.print('\n');
|
||||
const middleIndex = Math.floor(deployments.length / 2);
|
||||
const deployment = deployments[middleIndex];
|
||||
const rem = plural('deployment', deployments.length, true);
|
||||
const steps = Math.floor(Math.log2(deployments.length));
|
||||
const pSteps = plural('step', steps, true);
|
||||
output.log(
|
||||
chalk.magenta(
|
||||
`${chalk.bold(
|
||||
'Bisecting:'
|
||||
)} ${rem} left to test after this (roughly ${pSteps})`
|
||||
),
|
||||
chalk.magenta
|
||||
);
|
||||
const testUrl = `https://${deployment.url}${subpath}`;
|
||||
output.log(`${chalk.bold('Deployment URL:')} ${link(testUrl)}`);
|
||||
|
||||
output.log(`${chalk.bold('Date:')} ${formatDate(deployment.createdAt)}`);
|
||||
|
||||
const commit = getCommit(deployment);
|
||||
if (commit) {
|
||||
const shortSha = commit.sha.substring(0, 7);
|
||||
const firstLine = commit.message.split('\n')[0];
|
||||
output.log(`${chalk.bold('Commit:')} [${shortSha}] ${firstLine}`);
|
||||
}
|
||||
|
||||
let action: string;
|
||||
if (run) {
|
||||
const proc = await execa(run, [testUrl], {
|
||||
stdio: 'inherit',
|
||||
reject: false,
|
||||
env: {
|
||||
...process.env,
|
||||
HOST: deployment.url,
|
||||
URL: testUrl,
|
||||
},
|
||||
});
|
||||
if (proc instanceof Error && typeof proc.exitCode !== 'number') {
|
||||
// Script does not exist or is not executable, so exit
|
||||
output.prettyError(proc);
|
||||
return 1;
|
||||
}
|
||||
const { exitCode } = proc;
|
||||
let color: Chalk;
|
||||
if (exitCode === 0) {
|
||||
color = chalk.green;
|
||||
action = 'good';
|
||||
} else if (exitCode === 125) {
|
||||
action = 'skip';
|
||||
color = chalk.grey;
|
||||
} else {
|
||||
action = 'bad';
|
||||
color = chalk.red;
|
||||
}
|
||||
output.log(
|
||||
`Run script returned exit code ${chalk.bold(String(exitCode))}: ${color(
|
||||
action
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
if (openEnabled) {
|
||||
await open(testUrl);
|
||||
}
|
||||
const answer = await inquirer.prompt({
|
||||
type: 'expand',
|
||||
name: 'action',
|
||||
message: 'Select an action:',
|
||||
choices: [
|
||||
{ key: 'g', name: 'Good', value: 'good' },
|
||||
{ key: 'b', name: 'Bad', value: 'bad' },
|
||||
{ key: 's', name: 'Skip', value: 'skip' },
|
||||
],
|
||||
});
|
||||
action = answer.action;
|
||||
}
|
||||
|
||||
if (action === 'good') {
|
||||
deployments = deployments.slice(0, middleIndex);
|
||||
} else if (action === 'bad') {
|
||||
lastBad = deployment;
|
||||
deployments = deployments.slice(middleIndex + 1);
|
||||
} else if (action === 'skip') {
|
||||
deployments.splice(middleIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
output.print('\n');
|
||||
|
||||
let result = [
|
||||
chalk.bold(
|
||||
`The first bad deployment is: ${link(`https://${lastBad.url}`)}`
|
||||
),
|
||||
'',
|
||||
` ${chalk.bold('Date:')} ${formatDate(lastBad.createdAt)}`,
|
||||
];
|
||||
|
||||
const commit = getCommit(lastBad);
|
||||
if (commit) {
|
||||
const shortSha = commit.sha.substring(0, 7);
|
||||
const firstLine = commit.message.split('\n')[0];
|
||||
result.push(` ${chalk.bold('Commit:')} [${shortSha}] ${firstLine}`);
|
||||
}
|
||||
|
||||
result.push(`${chalk.bold('Inspect:')} ${link(lastBad.inspectorUrl)}`);
|
||||
|
||||
output.print(boxen(result.join('\n'), { padding: 1 }));
|
||||
output.print('\n');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getDeployment(
|
||||
client: Client,
|
||||
hostname: string
|
||||
): Promise<DeploymentV6> {
|
||||
const query = new URLSearchParams();
|
||||
query.set('url', hostname);
|
||||
query.set('resolve', '1');
|
||||
query.set('noState', '1');
|
||||
return client.fetch<DeploymentV6>(`/v10/deployments/get?${query}`);
|
||||
}
|
||||
|
||||
function getCommit(deployment: DeploymentV6) {
|
||||
const sha =
|
||||
deployment.meta?.githubCommitSha ||
|
||||
deployment.meta?.gitlabCommitSha ||
|
||||
deployment.meta?.bitbucketCommitSha;
|
||||
if (!sha) return null;
|
||||
const message =
|
||||
deployment.meta?.githubCommitMessage ||
|
||||
deployment.meta?.gitlabCommitMessage ||
|
||||
deployment.meta?.bitbucketCommitMessage;
|
||||
return { sha, message };
|
||||
}
|
||||
|
||||
async function prompt(output: Output, message: string): Promise<string> {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const { val } = await inquirer.prompt({
|
||||
type: 'input',
|
||||
name: 'val',
|
||||
message,
|
||||
});
|
||||
if (val) {
|
||||
return val;
|
||||
} else {
|
||||
output.error('A value must be specified');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ export const help = () => `
|
||||
${chalk.dim('Advanced')}
|
||||
|
||||
rm | remove [id] Removes a deployment
|
||||
bisect Use binary search to find the deployment that introduced a bug
|
||||
domains [name] Manages your domain names
|
||||
projects Manages your Projects
|
||||
dns [name] Manages your DNS records
|
||||
|
||||
@@ -2,6 +2,7 @@ export default new Map([
|
||||
['alias', 'alias'],
|
||||
['aliases', 'alias'],
|
||||
['billing', 'billing'],
|
||||
['bisect', 'bisect'],
|
||||
['build', 'build'],
|
||||
['cc', 'billing'],
|
||||
['cert', 'certs'],
|
||||
|
||||
@@ -627,6 +627,9 @@ const main = async () => {
|
||||
case 'billing':
|
||||
func = await import('./commands/billing');
|
||||
break;
|
||||
case 'bisect':
|
||||
func = await import('./commands/bisect');
|
||||
break;
|
||||
case 'build':
|
||||
func = await import('./commands/build');
|
||||
break;
|
||||
|
||||
@@ -85,10 +85,6 @@ export type Domain = {
|
||||
transferredAt?: number | null;
|
||||
orderedAt?: number;
|
||||
serviceType: 'zeit.world' | 'external' | 'na';
|
||||
verified: boolean;
|
||||
nsVerifiedAt: number | null;
|
||||
txtVerifiedAt: number | null;
|
||||
verificationRecord: string;
|
||||
nameservers: string[];
|
||||
intendedNameservers: string[];
|
||||
creator: {
|
||||
@@ -133,6 +129,13 @@ export type Deployment = {
|
||||
created: number;
|
||||
createdAt: number;
|
||||
creator: { uid: string; username: string };
|
||||
target: string | null;
|
||||
ownerId: string;
|
||||
projectId: string;
|
||||
inspectorUrl: string;
|
||||
meta: {
|
||||
[key: string]: any;
|
||||
};
|
||||
};
|
||||
|
||||
export type Alias = {
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
BuildResultV3,
|
||||
BuilderOutputs,
|
||||
EnvConfigs,
|
||||
BuiltLambda,
|
||||
} from './types';
|
||||
import { normalizeRoutes } from '@vercel/routing-utils';
|
||||
import getUpdateCommand from '../get-update-command';
|
||||
@@ -288,7 +289,7 @@ export async function executeBuild(
|
||||
// subclass type instances.
|
||||
for (const name of Object.keys(output)) {
|
||||
const obj = output[name] as File;
|
||||
let lambda: Lambda;
|
||||
let lambda: BuiltLambda;
|
||||
let fileRef: FileFsRef;
|
||||
let fileBlob: FileBlob;
|
||||
switch (obj.type) {
|
||||
@@ -302,7 +303,7 @@ export async function executeBuild(
|
||||
output[name] = fileBlob;
|
||||
break;
|
||||
case 'Lambda':
|
||||
lambda = Object.assign(Object.create(Lambda.prototype), obj) as Lambda;
|
||||
lambda = Object.assign(Object.create(Lambda.prototype), obj);
|
||||
// Convert the JSON-ified Buffer object back into an actual Buffer
|
||||
lambda.zipBuffer = Buffer.from((obj as any).zipBuffer.data);
|
||||
output[name] = lambda;
|
||||
|
||||
@@ -66,6 +66,7 @@ export interface BuilderInputs {
|
||||
}
|
||||
|
||||
export interface BuiltLambda extends Lambda {
|
||||
zipBuffer: Buffer;
|
||||
fn?: FunLambda;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export async function getDomain(
|
||||
);
|
||||
try {
|
||||
const { domain } = await client.fetch<Response>(
|
||||
`/v4/domains/${domainName}`
|
||||
`/v5/domains/${domainName}`
|
||||
);
|
||||
|
||||
return domain;
|
||||
|
||||
@@ -7,9 +7,7 @@ import addDomain from './add-domain';
|
||||
import Client from '../client';
|
||||
import maybeGetDomainByName from './maybe-get-domain-by-name';
|
||||
import purchaseDomainIfAvailable from './purchase-domain-if-available';
|
||||
import verifyDomain from './verify-domain';
|
||||
import extractDomain from '../alias/extract-domain';
|
||||
import isWildcardAlias from '../alias/is-wildcard-alias';
|
||||
|
||||
export default async function setupDomain(
|
||||
output: Output,
|
||||
@@ -27,36 +25,6 @@ export default async function setupDomain(
|
||||
if (info) {
|
||||
const { name: domain } = info;
|
||||
output.debug(`Domain ${domain} found for the given context`);
|
||||
if (!info.verified || (!info.nsVerifiedAt && isWildcardAlias(alias))) {
|
||||
output.debug(
|
||||
`Domain ${domain} is not verified, trying to perform a verification`
|
||||
);
|
||||
const verificationResult = await verifyDomain(
|
||||
client,
|
||||
domain,
|
||||
contextName
|
||||
);
|
||||
if (verificationResult instanceof ERRORS.DomainVerificationFailed) {
|
||||
output.debug(`Domain ${domain} verification failed`);
|
||||
return verificationResult;
|
||||
}
|
||||
if (!verificationResult.nsVerifiedAt && isWildcardAlias(alias)) {
|
||||
return new ERRORS.DomainNsNotVerifiedForWildcard({
|
||||
domain,
|
||||
nsVerification: {
|
||||
intendedNameservers: verificationResult.intendedNameservers,
|
||||
nameservers: verificationResult.nameservers,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
output.debug(`Domain ${domain} successfuly verified`);
|
||||
return maybeGetDomainByName(client, contextName, domain) as Promise<
|
||||
Domain
|
||||
>;
|
||||
}
|
||||
|
||||
output.debug(`Domain ${domain} is already verified`);
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -92,21 +60,6 @@ export default async function setupDomain(
|
||||
return addResult;
|
||||
}
|
||||
|
||||
if (!addResult.verified) {
|
||||
const verificationResult = await verifyDomain(
|
||||
client,
|
||||
domain,
|
||||
contextName
|
||||
);
|
||||
if (verificationResult instanceof ERRORS.DomainVerificationFailed) {
|
||||
output.debug(`Domain ${domain} was added but it couldn't be verified`);
|
||||
return verificationResult;
|
||||
}
|
||||
|
||||
output.debug(`Domain ${domain} successfuly added and manually verified`);
|
||||
return verificationResult;
|
||||
}
|
||||
|
||||
output.debug(
|
||||
`Domain ${domain} successfuly added and automatically verified`
|
||||
);
|
||||
@@ -120,23 +73,6 @@ export default async function setupDomain(
|
||||
aliasDomain
|
||||
)) as Domain;
|
||||
const { name: domain } = purchasedDomain;
|
||||
if (!purchasedDomain.verified) {
|
||||
const verificationResult = await verifyDomain(client, domain, contextName);
|
||||
if (verificationResult instanceof ERRORS.DomainVerificationFailed) {
|
||||
output.debug(
|
||||
`Domain ${domain} was purchased but verification is still pending`
|
||||
);
|
||||
return new ERRORS.DomainVerificationFailed({
|
||||
domain: verificationResult.meta.domain,
|
||||
nsVerification: verificationResult.meta.nsVerification,
|
||||
txtVerification: verificationResult.meta.txtVerification,
|
||||
purchased: true,
|
||||
});
|
||||
}
|
||||
|
||||
output.debug(`Domain ${domain} was purchased and it was manually verified`);
|
||||
return maybeGetDomainByName(client, contextName, domain) as Promise<Domain>;
|
||||
}
|
||||
|
||||
output.debug(
|
||||
`Domain ${domain} was purchased and it is automatically verified`
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import retry from 'async-retry';
|
||||
import { Domain } from '../../types';
|
||||
import * as ERRORS from '../errors-ts';
|
||||
import Client from '../client';
|
||||
|
||||
export default async function verifyDomain(
|
||||
client: Client,
|
||||
domainName: string,
|
||||
contextName: string
|
||||
) {
|
||||
client.output.spinner(
|
||||
`Verifying domain ${domainName} under ${chalk.bold(contextName)}`
|
||||
);
|
||||
try {
|
||||
const { domain } = await performVerifyDomain(client, domainName);
|
||||
return domain;
|
||||
} catch (error) {
|
||||
if (error.code === 'verification_failed') {
|
||||
return new ERRORS.DomainVerificationFailed({
|
||||
purchased: false,
|
||||
domain: error.name as string,
|
||||
nsVerification: error.nsVerification as ERRORS.NSVerificationError,
|
||||
txtVerification: error.txtVerification as ERRORS.TXTVerificationError,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
type Response = {
|
||||
domain: Domain;
|
||||
};
|
||||
|
||||
async function performVerifyDomain(client: Client, domain: string) {
|
||||
return retry(
|
||||
async () =>
|
||||
client.fetch<Response>(
|
||||
`/v4/domains/${encodeURIComponent(domain)}/verify`,
|
||||
{
|
||||
body: {},
|
||||
method: 'POST',
|
||||
}
|
||||
),
|
||||
{ retries: 5, maxTimeout: 8000 }
|
||||
);
|
||||
}
|
||||
@@ -260,31 +260,6 @@ export type TXTVerificationError = {
|
||||
values: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* This error is returned when the domain is not verified by nameservers for wildcard alias.
|
||||
*/
|
||||
export class DomainNsNotVerifiedForWildcard extends NowError<
|
||||
'DOMAIN_NS_NOT_VERIFIED_FOR_WILDCARD',
|
||||
{
|
||||
domain: string;
|
||||
nsVerification: NSVerificationError;
|
||||
}
|
||||
> {
|
||||
constructor({
|
||||
domain,
|
||||
nsVerification,
|
||||
}: {
|
||||
domain: string;
|
||||
nsVerification: NSVerificationError;
|
||||
}) {
|
||||
super({
|
||||
code: 'DOMAIN_NS_NOT_VERIFIED_FOR_WILDCARD',
|
||||
meta: { domain, nsVerification },
|
||||
message: `The domain ${domain} is not verified by nameservers for wildcard alias.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when a domain is validated because we tried to add it to an account
|
||||
* via API or for any other reason.
|
||||
|
||||
5
packages/cli/test/integration.js
vendored
5
packages/cli/test/integration.js
vendored
@@ -7,9 +7,10 @@ import { Readable } from 'stream';
|
||||
import { homedir } from 'os';
|
||||
import _execa from 'execa';
|
||||
import XDGAppPaths from 'xdg-app-paths';
|
||||
import fetch from 'node-fetch';
|
||||
import nodeFetch from 'node-fetch';
|
||||
import tmp from 'tmp-promise';
|
||||
import retry from 'async-retry';
|
||||
import createFetchRetry from '@vercel/fetch-retry';
|
||||
import fs, {
|
||||
writeFile,
|
||||
readFile,
|
||||
@@ -24,6 +25,8 @@ import pkg from '../package';
|
||||
import prepareFixtures from './helpers/prepare';
|
||||
import { fetchTokenWithRetry } from '../../../test/lib/deployment/now-deploy';
|
||||
|
||||
const fetch = createFetchRetry(nodeFetch);
|
||||
|
||||
// log command when running `execa`
|
||||
function execa(file, args, options) {
|
||||
console.log(`$ vercel ${args.join(' ')}`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/client",
|
||||
"version": "10.2.3-canary.51",
|
||||
"version": "10.2.3-canary.53",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://vercel.com",
|
||||
@@ -40,7 +40,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.13.1-canary.0",
|
||||
"@vercel/build-utils": "2.13.1-canary.2",
|
||||
"@zeit/fetch": "5.2.0",
|
||||
"async-retry": "1.2.3",
|
||||
"async-sema": "3.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/frameworks",
|
||||
"version": "0.5.1-canary.20",
|
||||
"version": "0.5.1-canary.21",
|
||||
"main": "./dist/frameworks.js",
|
||||
"types": "./dist/frameworks.d.ts",
|
||||
"files": [
|
||||
|
||||
@@ -1941,7 +1941,7 @@ export const frameworks = [
|
||||
},
|
||||
devCommand: {
|
||||
placeholder: 'vite',
|
||||
value: 'vite',
|
||||
value: 'vite --port $PORT',
|
||||
},
|
||||
outputDirectory: {
|
||||
value: 'dist',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/go",
|
||||
"version": "1.2.4-canary.5",
|
||||
"version": "1.2.4-canary.7",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
|
||||
@@ -24,7 +24,7 @@
|
||||
"@types/fs-extra": "^5.0.5",
|
||||
"@types/node-fetch": "^2.3.0",
|
||||
"@types/tar": "^4.0.0",
|
||||
"@vercel/build-utils": "2.13.1-canary.0",
|
||||
"@vercel/build-utils": "2.13.1-canary.2",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"async-retry": "1.3.1",
|
||||
"execa": "^1.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel-plugin-middleware",
|
||||
"version": "0.0.0-canary.25",
|
||||
"version": "0.0.0-canary.27",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "",
|
||||
@@ -30,7 +30,7 @@
|
||||
"@types/node-fetch": "^2",
|
||||
"@types/ua-parser-js": "0.7.36",
|
||||
"@types/uuid": "8.3.1",
|
||||
"@vercel/build-utils": "2.13.1-canary.0",
|
||||
"@vercel/build-utils": "2.13.1-canary.2",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"cookie": "0.4.1",
|
||||
"formdata-node": "4.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/node",
|
||||
"version": "1.12.2-canary.8",
|
||||
"version": "1.12.2-canary.10",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
|
||||
@@ -32,7 +32,7 @@
|
||||
"@types/cookie": "0.3.3",
|
||||
"@types/etag": "1.8.0",
|
||||
"@types/test-listen": "1.1.0",
|
||||
"@vercel/build-utils": "2.13.1-canary.0",
|
||||
"@vercel/build-utils": "2.13.1-canary.2",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@vercel/nft": "0.14.0",
|
||||
"@vercel/node-bridge": "2.1.1-canary.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "vercel-plugin-go",
|
||||
"version": "1.0.0-canary.36",
|
||||
"version": "1.0.0-canary.38",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
@@ -17,8 +17,8 @@
|
||||
"prepublishOnly": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.13.1-canary.0",
|
||||
"@vercel/go": "1.2.4-canary.5"
|
||||
"@vercel/build-utils": "2.13.1-canary.2",
|
||||
"@vercel/go": "1.2.4-canary.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel-plugin-node",
|
||||
"version": "1.12.2-canary.40",
|
||||
"version": "1.12.2-canary.42",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
|
||||
@@ -34,7 +34,7 @@
|
||||
"@types/node-fetch": "2",
|
||||
"@types/test-listen": "1.1.0",
|
||||
"@types/yazl": "2.4.2",
|
||||
"@vercel/build-utils": "2.13.1-canary.0",
|
||||
"@vercel/build-utils": "2.13.1-canary.2",
|
||||
"@vercel/fun": "1.0.3",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@vercel/nft": "0.14.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "vercel-plugin-python",
|
||||
"version": "1.0.0-canary.37",
|
||||
"version": "1.0.0-canary.39",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
@@ -17,8 +17,8 @@
|
||||
"prepublishOnly": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.13.1-canary.0",
|
||||
"@vercel/python": "2.1.2-canary.3"
|
||||
"@vercel/build-utils": "2.13.1-canary.2",
|
||||
"@vercel/python": "2.1.2-canary.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "*",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": false,
|
||||
"name": "vercel-plugin-ruby",
|
||||
"version": "1.0.0-canary.36",
|
||||
"version": "1.0.0-canary.38",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
@@ -17,8 +17,8 @@
|
||||
"prepublishOnly": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.13.1-canary.0",
|
||||
"@vercel/ruby": "1.2.10-canary.1"
|
||||
"@vercel/build-utils": "2.13.1-canary.2",
|
||||
"@vercel/ruby": "1.2.10-canary.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/python",
|
||||
"version": "2.1.2-canary.3",
|
||||
"version": "2.1.2-canary.5",
|
||||
"main": "./dist/index.js",
|
||||
"license": "MIT",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
|
||||
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/execa": "^0.9.0",
|
||||
"@vercel/build-utils": "2.13.1-canary.0",
|
||||
"@vercel/build-utils": "2.13.1-canary.2",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"execa": "^1.0.0",
|
||||
"typescript": "4.3.4"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@vercel/ruby",
|
||||
"author": "Nathan Cahill <nathan@nathancahill.com>",
|
||||
"version": "1.2.10-canary.1",
|
||||
"version": "1.2.10-canary.3",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
|
||||
@@ -22,7 +22,7 @@
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "8.0.0",
|
||||
"@types/semver": "6.0.0",
|
||||
"@vercel/build-utils": "2.13.1-canary.0",
|
||||
"@vercel/build-utils": "2.13.1-canary.2",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"execa": "2.0.4",
|
||||
"fs-extra": "^7.0.1",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://openapi.vercel.sh/vercel.json",
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/",
|
||||
|
||||
16
yarn.lock
16
yarn.lock
@@ -2613,6 +2613,14 @@
|
||||
"@typescript-eslint/types" "4.28.0"
|
||||
eslint-visitor-keys "^2.0.0"
|
||||
|
||||
"@vercel/fetch-retry@5.0.3":
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@vercel/fetch-retry/-/fetch-retry-5.0.3.tgz#cce5d23f6e64f6f525c24e2ac7c78f65d6c5b1f4"
|
||||
integrity sha512-DIIoBY92r+sQ6iHSf5WjKiYvkdsDIMPWKYATlE0KcUAj2RV6SZK9UWpUzBRKsofXqedOqpVjrI0IE6AWL7JRtg==
|
||||
dependencies:
|
||||
async-retry "^1.3.1"
|
||||
debug "^3.1.0"
|
||||
|
||||
"@vercel/fun@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@vercel/fun/-/fun-1.0.3.tgz#5e5c4921a3a3a35ee129ce063e6b3f5c4b1eae48"
|
||||
@@ -9346,10 +9354,10 @@ onetime@^5.1.2:
|
||||
dependencies:
|
||||
mimic-fn "^2.1.0"
|
||||
|
||||
open@8.2.0:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/open/-/open-8.2.0.tgz#d6a4788b00009a9d60df471ecb89842a15fdcfc1"
|
||||
integrity sha512-O8uInONB4asyY3qUcEytpgwxQG3O0fJ/hlssoUHsBboOIRVZzT6Wq+Rwj5nffbeUhOdMjpXeISpDDzHCMRDuOQ==
|
||||
open@8.4.0:
|
||||
version "8.4.0"
|
||||
resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8"
|
||||
integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==
|
||||
dependencies:
|
||||
define-lazy-prop "^2.0.0"
|
||||
is-docker "^2.1.1"
|
||||
|
||||
Reference in New Issue
Block a user