Compare commits

..

17 Commits

Author SHA1 Message Date
Matthew Stanciu
a0479338c1 , 2022-07-12 12:58:44 -07:00
Matthew Stanciu
37a8037128 Add 2022-07-12 11:55:25 -07:00
Matthew Stanciu
65fd5c49f9 Optionally pass in state to mock deployment 2022-07-08 16:36:54 -07:00
Matthew Stanciu
75a19a1b8f Remove console.logs 2022-07-08 16:20:09 -07:00
Matthew Stanciu
d2456a21a1 Merge branch 'main' into update/fix-ls-visual-bugs 2022-07-08 13:03:39 -07:00
Matthew Stanciu
8f02a5d0b1 Fix unit tests 2022-07-08 12:03:14 -07:00
Matthew Stanciu
f4cb7b3b8b Merge branch 'main' into update/fix-ls-visual-bugs 2022-07-08 11:57:56 -07:00
Matthew Stanciu
88c309ec40 use title module for state string 2022-07-08 10:46:42 -07:00
Matthew Stanciu
e383068427 styfle suggestions 2022-07-08 10:39:14 -07:00
Sean Massa
d660f110a3 Merge branch 'main' into update/fix-ls-visual-bugs 2022-07-08 11:39:39 -05:00
Matthew Stanciu
9fada88f99 Fix unit tests 2022-07-07 21:31:03 -07:00
Matthew Stanciu
0d9082dcb8 Merge branch 'main' into update/fix-ls-visual-bugs 2022-07-07 21:13:32 -07:00
Matthew Stanciu
4da688cff3 Merge branch 'update/fix-ls-visual-bugs' of https://github.com/vercel/vercel into update/fix-ls-visual-bugs 2022-07-07 17:04:07 -07:00
Matthew Stanciu
af40cf43ae Fix tests 2022-07-07 17:03:41 -07:00
Matthew Stanciu
4c100a191c Merge branch 'main' into update/fix-ls-visual-bugs 2022-07-07 16:43:15 -07:00
Matthew Stanciu
a637f2f949 Remove project ls message 2022-07-07 16:43:02 -07:00
Matthew Stanciu
f3683fff3f Fix ls visual bugs 2022-07-07 16:37:14 -07:00
324 changed files with 2740 additions and 5636 deletions

View File

@@ -12,7 +12,6 @@ To get started, execute the following:
```
git clone https://github.com/vercel/vercel
cd vercel
yarn install
yarn bootstrap
yarn build

View File

@@ -1,5 +1,4 @@
# https://prettier.io/docs/en/ignore.html
# ignore these files with an intentional syntax error
# ignore this file with an intentional syntax error
packages/cli/test/dev/fixtures/edge-function-error/api/edge-error-syntax.js
packages/cli/test/fixtures/unit/commands/build/node-error/api/typescript.ts

View File

@@ -11,7 +11,7 @@
"dependencies": {
"@sentry/node": "5.11.1",
"got": "10.2.1",
"node-fetch": "2.6.7",
"node-fetch": "2.6.1",
"parse-github-url": "1.0.2",
"tar-fs": "2.0.0",
"unzip-stream": "0.3.0"

View File

@@ -26,12 +26,12 @@
"jest": "28.0.2",
"json5": "2.1.1",
"lint-staged": "9.2.5",
"node-fetch": "2.6.7",
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"prettier": "2.6.2",
"ts-eager": "2.0.2",
"ts-jest": "28.0.5",
"turbo": "1.3.2-canary.1"
"turbo": "1.3.1"
},
"scripts": {
"lerna": "lerna",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "5.0.5",
"version": "5.0.1",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -31,10 +31,11 @@
"@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0",
"@types/yazl": "2.4.2",
"@vercel/ncc": "0.34.0",
"@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1",
"async-retry": "1.2.3",
"async-sema": "2.1.4",
"boxen": "4.2.0",
"cross-spawn": "6.0.5",
"end-of-stream": "1.4.1",
"fs-extra": "10.0.0",
@@ -43,7 +44,7 @@
"js-yaml": "3.13.1",
"minimatch": "3.0.4",
"multistream": "2.1.1",
"node-fetch": "2.6.7",
"node-fetch": "2.6.1",
"semver": "6.1.1",
"typescript": "4.3.4",
"yazl": "2.5.1"

View File

@@ -33,11 +33,6 @@ export class EdgeFunction {
*/
envVarsInUse?: string[];
/**
* Extra binary files to be included in the edge function
*/
assets?: { name: string; path: string }[];
constructor(params: Omit<EdgeFunction, 'type'>) {
this.type = 'EdgeFunction';
this.name = params.name;
@@ -45,6 +40,5 @@ export class EdgeFunction {
this.entrypoint = params.entrypoint;
this.files = params.files;
this.envVarsInUse = params.envVarsInUse;
this.assets = params.assets;
}
}

View File

@@ -33,6 +33,9 @@ function getHint(isAuto = false) {
: `Please set "engines": { "node": "${range}" } in your \`package.json\` file to use Node.js ${major}.`;
}
const upstreamProvider =
'This change is the result of a decision made by an upstream infrastructure provider (AWS).';
export function getLatestNodeVersion() {
return allOptions[0];
}
@@ -72,7 +75,7 @@ export async function getSupportedNodeVersion(
throw new NowBuildError({
code: 'BUILD_UTILS_NODE_VERSION_DISCONTINUED',
link: 'http://vercel.link/node-version',
message: `${intro} ${getHint(isAuto)}`,
message: `${intro} ${getHint(isAuto)} ${upstreamProvider}`,
});
}
@@ -83,9 +86,9 @@ export async function getSupportedNodeVersion(
console.warn(
`Error: Node.js version ${
selection.range
} has reached End-of-Life. Deployments created on or after ${d} will fail to build. ${getHint(
} is deprecated. Deployments created on or after ${d} will fail to build. ${getHint(
isAuto
)}`
)} ${upstreamProvider}`
);
}

View File

@@ -305,46 +305,67 @@ export async function scanParentDirs(
): Promise<ScanParentDirsResult> {
assert(path.isAbsolute(destPath));
const pkgJsonPath = await walkParentDirs({
base: '/',
start: destPath,
filename: 'package.json',
});
const packageJson: PackageJson | undefined =
readPackageJson && pkgJsonPath
? JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'))
: undefined;
const [yarnLockPath, npmLockPath, pnpmLockPath] = await walkParentDirsMulti({
base: '/',
start: destPath,
filenames: ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml'],
});
let lockfileVersion: number | undefined;
let cliType: CliType = 'yarn';
let packageJson: PackageJson | undefined;
let packageJsonPath: string | undefined;
let currentDestPath = destPath;
let lockfileVersion: number | undefined;
const [hasYarnLock, packageLockJson, pnpmLockYaml] = await Promise.all([
Boolean(yarnLockPath),
npmLockPath
? readConfigFile<{ lockfileVersion: number }>(npmLockPath)
: null,
pnpmLockPath
? readConfigFile<{ lockfileVersion: number }>(pnpmLockPath)
: null,
]);
// eslint-disable-next-line no-constant-condition
while (true) {
packageJsonPath = path.join(currentDestPath, 'package.json');
// eslint-disable-next-line no-await-in-loop
if (await fs.pathExists(packageJsonPath)) {
// Only read the contents of the *first* `package.json` file found,
// since that's the one related to this installation.
if (readPackageJson && !packageJson) {
// eslint-disable-next-line no-await-in-loop
packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
}
// Priority order is Yarn > pnpm > npm
if (hasYarnLock) {
cliType = 'yarn';
} else if (pnpmLockYaml) {
cliType = 'pnpm';
// just ensure that it is read as a number and not a string
lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
} else if (packageLockJson) {
cliType = 'npm';
lockfileVersion = packageLockJson.lockfileVersion;
// eslint-disable-next-line no-await-in-loop
const [packageLockJson, hasYarnLock, pnpmLockYaml] = await Promise.all([
fs
.readJson(path.join(currentDestPath, 'package-lock.json'))
.catch(error => {
// If the file doesn't exist, fail gracefully otherwise error
if (error.code === 'ENOENT') {
return null;
}
throw error;
}),
fs.pathExists(path.join(currentDestPath, 'yarn.lock')),
readConfigFile<{ lockfileVersion: number }>(
path.join(currentDestPath, 'pnpm-lock.yaml')
),
]);
// Priority order is Yarn > pnpm > npm
// - find highest priority lock file and use that
if (hasYarnLock) {
cliType = 'yarn';
} else if (pnpmLockYaml) {
cliType = 'pnpm';
// just ensure that it is read as a number and not a string
lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
} else if (packageLockJson) {
cliType = 'npm';
lockfileVersion = packageLockJson.lockfileVersion;
}
// Only stop iterating if a lockfile was found, because it's possible
// that the lockfile is in a higher path than where the `package.json`
// file was found.
if (packageLockJson || hasYarnLock || pnpmLockYaml) {
break;
}
}
const newDestPath = path.dirname(currentDestPath);
if (currentDestPath === newDestPath) break;
currentDestPath = newDestPath;
}
const packageJsonPath = pkgJsonPath || undefined;
return { cliType, packageJson, lockfileVersion, packageJsonPath };
}
@@ -366,48 +387,11 @@ export async function walkParentDirs({
}
parent = path.dirname(current);
if (parent === current) {
// Reached root directory of the filesystem
break;
}
}
return null;
}
async function walkParentDirsMulti({
base,
start,
filenames,
}: {
base: string;
start: string;
filenames: string[];
}): Promise<(string | undefined)[]> {
let parent = '';
for (let current = start; base.length <= current.length; current = parent) {
const fullPaths = filenames.map(f => path.join(current, f));
const existResults = await Promise.all(
fullPaths.map(f => fs.pathExists(f))
);
const foundOneOrMore = existResults.some(b => b);
if (foundOneOrMore) {
return fullPaths.map((f, i) => (existResults[i] ? f : undefined));
}
parent = path.dirname(current);
if (parent === current) {
// Reached root directory of the filesystem
break;
}
}
return [];
}
function isSet<T>(v: any): v is Set<T> {
return v?.constructor?.name === 'Set';
}

View File

@@ -1,7 +1,6 @@
import ms from 'ms';
import path from 'path';
import fs, { readlink } from 'fs-extra';
import retry from 'async-retry';
import { strict as assert, strictEqual } from 'assert';
import { createZip } from '../src/lambda';
import { getSupportedNodeVersion } from '../src/fs/node-version';
@@ -19,7 +18,7 @@ import {
Meta,
} from '../src';
jest.setTimeout(10 * 1000);
jest.setTimeout(7 * 1000);
async function expectBuilderError(promise: Promise<any>, pattern: string) {
let result;
@@ -387,10 +386,10 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
12
);
expect(warningMessages).toStrictEqual([
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
'Error: Node.js version 10.x is deprecated. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
'Error: Node.js version 10.x is deprecated. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
'Error: Node.js version 12.x is deprecated. Deployments created on or after 2022-08-09 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
'Error: Node.js version 12.x is deprecated. Deployments created on or after 2022-08-09 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16. This change is the result of a decision made by an upstream infrastructure provider (AWS).',
]);
global.Date.now = realDateNow;
@@ -454,7 +453,6 @@ it('should return lockfileVersion 2 with npm7', async () => {
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('npm');
expect(result.lockfileVersion).toEqual(2);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should not return lockfileVersion with yarn', async () => {
@@ -462,7 +460,6 @@ it('should not return lockfileVersion with yarn', async () => {
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('yarn');
expect(result.lockfileVersion).toEqual(undefined);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should return lockfileVersion 1 with older versions of npm', async () => {
@@ -470,7 +467,6 @@ it('should return lockfileVersion 1 with older versions of npm', async () => {
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('npm');
expect(result.lockfileVersion).toEqual(1);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should detect npm Workspaces', async () => {
@@ -478,88 +474,48 @@ it('should detect npm Workspaces', async () => {
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('npm');
expect(result.lockfileVersion).toEqual(2);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should detect pnpm without workspace', async () => {
it('should detect pnpm', async () => {
const fixture = path.join(__dirname, 'fixtures', '22-pnpm');
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('pnpm');
expect(result.lockfileVersion).toEqual(5.3);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should detect pnpm with workspaces', async () => {
const fixture = path.join(__dirname, 'fixtures', '23-pnpm-workspaces/c');
it('should detect pnpm Workspaces', async () => {
const fixture = path.join(__dirname, 'fixtures', '23-pnpm-workspaces/a');
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('pnpm');
expect(result.lockfileVersion).toEqual(5.3);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should detect package.json in nested backend', async () => {
const fixture = path.join(
__dirname,
'../../node/test/fixtures/18.1-nested-packagejson/backend'
);
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('yarn');
expect(result.lockfileVersion).toEqual(undefined);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should detect package.json in nested frontend', async () => {
const fixture = path.join(
__dirname,
'../../node/test/fixtures/18.1-nested-packagejson/frontend'
);
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('yarn');
expect(result.lockfileVersion).toEqual(undefined);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should only invoke `runNpmInstall()` once per `package.json` file (serial)', async () => {
const meta: Meta = {};
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
const apiDir = path.join(fixture, 'api');
const retryOpts = { maxRetryTime: 1000 };
let run1, run2, run3;
await retry(async () => {
run1 = await runNpmInstall(apiDir, [], undefined, meta);
expect(run1).toEqual(true);
expect(
(meta.runNpmInstallSet as Set<string>).has(
path.join(fixture, 'package.json')
)
).toEqual(true);
}, retryOpts);
await retry(async () => {
run2 = await runNpmInstall(apiDir, [], undefined, meta);
expect(run2).toEqual(false);
}, retryOpts);
await retry(async () => {
run3 = await runNpmInstall(fixture, [], undefined, meta);
expect(run3).toEqual(false);
}, retryOpts);
const run1 = await runNpmInstall(apiDir, [], undefined, meta);
expect(run1).toEqual(true);
expect(
(meta.runNpmInstallSet as Set<string>).has(
path.join(fixture, 'package.json')
)
).toEqual(true);
const run2 = await runNpmInstall(apiDir, [], undefined, meta);
expect(run2).toEqual(false);
const run3 = await runNpmInstall(fixture, [], undefined, meta);
expect(run3).toEqual(false);
});
it('should only invoke `runNpmInstall()` once per `package.json` file (parallel)', async () => {
const meta: Meta = {};
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
const apiDir = path.join(fixture, 'api');
let results: [boolean, boolean, boolean] | undefined;
await retry(
async () => {
results = await Promise.all([
runNpmInstall(apiDir, [], undefined, meta),
runNpmInstall(apiDir, [], undefined, meta),
runNpmInstall(fixture, [], undefined, meta),
]);
},
{ maxRetryTime: 3000 }
);
const [run1, run2, run3] = results || [];
const [run1, run2, run3] = await Promise.all([
runNpmInstall(apiDir, [], undefined, meta),
runNpmInstall(apiDir, [], undefined, meta),
runNpmInstall(fixture, [], undefined, meta),
]);
expect(run1).toEqual(true);
expect(run2).toEqual(false);
expect(run3).toEqual(false);

View File

@@ -53,13 +53,13 @@ At this point you can make modifications to the CLI source code and test them ou
cd packages/cli
```
From within the `packages/cli` directory, you can use the "dev" script to quickly execute Vercel CLI from its TypeScript source code directly (without having to manually compile first). For example:
From within the `packages/cli` directory, you can use the `ts-eager` command line tool to quickly excute Vercel CLI from its TypeScript source code directly (without having to manually compile first). For example:
```bash
yarn dev deploy
yarn dev whoami
yarn dev login
yarn dev switch --debug
npx ts-eager src
npx ts-eager src login
npx ts-eager src switch --debug
npx ts-eager src dev
```
When you are satisfied with your changes, make a commit and create a pull request!

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "27.3.0",
"version": "27.0.0",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -18,8 +18,8 @@
"test-integration-dev": "yarn test test/dev/",
"prepublishOnly": "yarn build",
"coverage": "codecov",
"build": "ts-node ./scripts/build.ts",
"dev": "ts-node ./src/index.ts"
"build": "node -r ts-eager/register ./scripts/build.ts",
"build-dev": "node -r ts-eager/register ./scripts/build.ts --dev"
},
"bin": {
"vc": "./dist/index.js",
@@ -42,16 +42,16 @@
"node": ">= 14"
},
"dependencies": {
"@vercel/build-utils": "5.0.5",
"@vercel/go": "2.0.9",
"@vercel/hydrogen": "0.0.6",
"@vercel/next": "3.1.9",
"@vercel/node": "2.5.0",
"@vercel/python": "3.1.1",
"@vercel/redwood": "1.0.10",
"@vercel/remix": "1.0.11",
"@vercel/ruby": "1.3.17",
"@vercel/static-build": "1.0.10",
"@vercel/build-utils": "5.0.1",
"@vercel/go": "2.0.5",
"@vercel/hydrogen": "0.0.2",
"@vercel/next": "3.1.4",
"@vercel/node": "2.4.1",
"@vercel/python": "3.0.5",
"@vercel/redwood": "1.0.6",
"@vercel/remix": "1.0.6",
"@vercel/ruby": "1.3.13",
"@vercel/static-build": "1.0.5",
"update-notifier": "5.1.0"
},
"devDependencies": {
@@ -59,7 +59,6 @@
"@next/env": "11.1.2",
"@sentry/node": "5.5.0",
"@sindresorhus/slugify": "0.11.0",
"@swc/core": "1.2.218",
"@tootallnate/once": "1.1.2",
"@types/ansi-escapes": "3.0.0",
"@types/ansi-regex": "4.0.0",
@@ -97,11 +96,11 @@
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.1.4",
"@vercel/frameworks": "1.1.1",
"@vercel/fs-detectors": "2.0.1",
"@vercel/fun": "1.0.4",
"@vercel/ncc": "0.34.0",
"@vercel/client": "12.1.0",
"@vercel/frameworks": "1.1.0",
"@vercel/fs-detectors": "1.0.1",
"@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2",
"ajv": "6.12.2",
"alpha-sort": "2.0.1",
@@ -112,7 +111,6 @@
"async-retry": "1.1.3",
"async-sema": "2.1.4",
"ava": "2.2.0",
"boxen": "4.2.0",
"bytes": "3.0.0",
"chalk": "4.1.0",
"chance": "1.1.7",
@@ -149,7 +147,7 @@
"minimatch": "3.0.4",
"mri": "1.1.5",
"ms": "2.1.2",
"node-fetch": "2.6.7",
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"open": "8.4.0",
"ora": "3.4.0",
@@ -171,8 +169,8 @@
"title": "3.4.1",
"tmp-promise": "1.0.3",
"tree-kill": "1.2.2",
"ts-node": "10.9.1",
"typescript": "4.7.4",
"ts-node": "8.3.0",
"typescript": "4.3.4",
"universal-analytics": "0.4.20",
"utility-types": "2.1.0",
"which": "2.0.2",

View File

@@ -27,38 +27,40 @@ function envToString(key: string) {
}
async function main() {
// Read the secrets from GitHub Actions and generate a file.
// During local development, these secrets will be empty.
await createConstants();
const isDev = process.argv[2] === '--dev';
// `vercel dev` uses chokidar to watch the filesystem, but opts-out of the
// `fsevents` feature using `useFsEvents: false`, so delete the module here so
// that it is not compiled by ncc, which makes the npm package size larger
// than necessary.
await remove(join(dirRoot, '../../node_modules/fsevents'));
if (!isDev) {
// Read the secrets from GitHub Actions and generate a file.
// During local development, these secrets will be empty.
await createConstants();
// Compile the `doT.js` template files for `vercel dev`
console.log();
await execa(process.execPath, [join(__dirname, 'compile-templates.js')], {
stdio: 'inherit',
});
// `vercel dev` uses chokidar to watch the filesystem, but opts-out of the
// `fsevents` feature using `useFsEvents: false`, so delete the module here so
// that it is not compiled by ncc, which makes the npm package size larger
// than necessary.
await remove(join(dirRoot, '../../node_modules/fsevents'));
// Compile the `doT.js` template files for `vercel dev`
console.log();
await execa(process.execPath, [join(__dirname, 'compile-templates.js')], {
stdio: 'inherit',
});
}
// Do the initial `ncc` build
console.log();
const args = [
'ncc',
'build',
'--external',
'update-notifier',
'src/index.ts',
];
const args = ['ncc', 'build', '--external', 'update-notifier'];
if (isDev) {
args.push('--source-map');
}
args.push('src/index.ts');
await execa('yarn', args, { stdio: 'inherit', cwd: dirRoot });
// `ncc` has some issues with `@vercel/fun`'s runtime files:
// `ncc` has some issues with `@zeit/fun`'s runtime files:
// - Executable bits on the `bootstrap` files appear to be lost:
// https://github.com/vercel/ncc/pull/182
// https://github.com/zeit/ncc/pull/182
// - The `bootstrap.js` asset does not get copied into the output dir:
// https://github.com/vercel/ncc/issues/278
// https://github.com/zeit/ncc/issues/278
//
// Aside from those issues, all the same files from the `runtimes` directory
// should be copied into the output runtimes dir, specifically the `index.js`
@@ -68,7 +70,7 @@ async function main() {
// with `fun`'s cache invalidation mechanism and they need to be shasum'd.
const runtimes = join(
dirRoot,
'../../node_modules/@vercel/fun/dist/src/runtimes'
'../../node_modules/@zeit/fun/dist/src/runtimes'
);
await cpy('**/*', join(distRoot, 'runtimes'), {
parents: true,
@@ -77,7 +79,6 @@ async function main() {
// Band-aid to bundle stuff that `ncc` neglects to bundle
await cpy(join(dirRoot, 'src/util/projects/VERCEL_DIR_README.txt'), distRoot);
await cpy(join(dirRoot, 'src/util/dev/builder-worker.js'), distRoot);
console.log('Finished building Vercel CLI');
}

View File

@@ -22,7 +22,19 @@ export default async function ls(
) {
const { output } = client;
const { '--next': nextTimestamp } = opts;
const { contextName } = await getScope(client);
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
if (typeof nextTimestamp !== undefined && Number.isNaN(nextTimestamp)) {
output.error('Please provide a number for flag --next');

View File

@@ -23,7 +23,19 @@ export default async function rm(
args: string[]
) {
const { output } = client;
const { contextName } = await getScope(client);
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const [aliasOrId] = args;

View File

@@ -15,9 +15,10 @@ import { isValidName } from '../../util/is-valid-name';
import handleCertError from '../../util/certs/handle-cert-error';
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
import link from '../../util/output/link';
import { User } from '../../types';
import { getCommandName } from '../../util/pkg-name';
import toHost from '../../util/to-host';
import type { VercelConfig } from '@vercel/client';
import { VercelConfig } from '../../util/dev/types';
type Options = {
'--debug': boolean;
@@ -29,9 +30,23 @@ export default async function set(
opts: Partial<Options>,
args: string[]
) {
const setStamp = stamp();
const { output, localConfig } = client;
const { contextName, user } = await getScope(client);
const setStamp = stamp();
let user: User;
let contextName: string | null = null;
try {
({ contextName, user } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
// If there are more than two args we have to error
if (args.length > 2) {

View File

@@ -25,7 +25,7 @@ import {
MergeRoutesProps,
Route,
} from '@vercel/routing-utils';
import type { VercelConfig } from '@vercel/client';
import { VercelConfig } from '@vercel/client';
import pull from './pull';
import { staticFiles as getFiles } from '../util/get-files';
@@ -36,10 +36,7 @@ import * as cli from '../util/pkg-name';
import cliPkg from '../util/pkg';
import readJSONFile from '../util/read-json-file';
import { CantParseJSONFile } from '../util/errors-ts';
import {
ProjectLinkAndSettings,
readProjectSettings,
} from '../util/projects/project-settings';
import { readProjectSettings } from '../util/projects/project-settings';
import { VERCEL_DIR } from '../util/projects/link';
import confirm from '../util/input/confirm';
import { emoji, prependEmoji } from '../util/emoji';
@@ -49,31 +46,11 @@ import {
PathOverride,
writeBuildResult,
} from '../util/build/write-build-result';
import { importBuilders } from '../util/build/import-builders';
import { importBuilders, BuilderWithPkg } from '../util/build/import-builders';
import { initCorepack, cleanupCorepack } from '../util/build/corepack';
import { sortBuilders } from '../util/build/sort-builders';
import { toEnumerableError } from '../util/error';
type BuildResult = BuildResultV2 | BuildResultV3;
interface SerializedBuilder extends Builder {
error?: any;
require?: string;
requirePath?: string;
apiVersion: number;
}
/**
* Contents of the `builds.json` file.
*/
export interface BuildsManifest {
'//': string;
target: string;
argv: string[];
error?: any;
builds?: SerializedBuilder[];
}
const help = () => {
return console.log(`
${chalk.bold(`${cli.logo} ${cli.name} build`)}
@@ -189,83 +166,21 @@ export default async function main(client: Client): Promise<number> {
project = await readProjectSettings(join(cwd, VERCEL_DIR));
}
// Delete output directory from potential previous build
const outputDir = argv['--output']
? resolve(argv['--output'])
: join(cwd, OUTPUT_DIR);
await fs.remove(outputDir);
// TODO: load env vars from the API, fall back to local files if that fails
const buildsJson: BuildsManifest = {
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
target,
argv: process.argv,
};
const envToUnset = new Set<string>(['VERCEL', 'NOW_BUILDER']);
try {
const envPath = join(cwd, VERCEL_DIR, `.env.${target}.local`);
// TODO (maybe?): load env vars from the API, fall back to the local file if that fails
const dotenvResult = dotenv.config({
path: envPath,
debug: client.output.isDebugEnabled(),
});
if (dotenvResult.error) {
output.debug(
`Failed loading environment variables: ${dotenvResult.error}`
);
} else if (dotenvResult.parsed) {
for (const key of Object.keys(dotenvResult.parsed)) {
envToUnset.add(key);
}
output.debug(`Loaded environment variables from "${envPath}"`);
}
// For Vercel Analytics support
if (project.settings.analyticsId) {
envToUnset.add('VERCEL_ANALYTICS_ID');
process.env.VERCEL_ANALYTICS_ID = project.settings.analyticsId;
}
// Some build processes use these env vars to platform detect Vercel
process.env.VERCEL = '1';
process.env.NOW_BUILDER = '1';
return await doBuild(client, project, buildsJson, cwd, outputDir);
} catch (err: any) {
output.prettyError(err);
// Write error to `builds.json` file
buildsJson.error = toEnumerableError(err);
const buildsJsonPath = join(outputDir, 'builds.json');
const configJsonPath = join(outputDir, 'config.json');
await fs.outputJSON(buildsJsonPath, buildsJson, {
spaces: 2,
});
await fs.writeJSON(configJsonPath, { version: 3 }, { spaces: 2 });
return 1;
} finally {
// Unset environment variables that were added by dotenv
// (this is mostly for the unit tests)
for (const key of envToUnset) {
delete process.env[key];
}
const envPath = await checkExists([
join(cwd, VERCEL_DIR, `.env.${target}.local`),
join(cwd, `.env`),
]);
if (envPath) {
dotenv.config({ path: envPath, debug: client.output.isDebugEnabled() });
output.log(`Loaded env from "${relative(cwd, envPath)}"`);
}
}
/**
* Execute the Project's builders. If this function throws an error,
* then it will be serialized into the `builds.json` manifest file.
*/
async function doBuild(
client: Client,
project: ProjectLinkAndSettings,
buildsJson: BuildsManifest,
cwd: string,
outputDir: string
): Promise<number> {
const { output } = client;
// Some build processes use these env vars to platform detect Vercel
process.env.VERCEL = '1';
process.env.NOW_BUILDER = '1';
const workPath = join(cwd, project.settings.rootDirectory || '.');
// Load `package.json` and `vercel.json` files
@@ -283,23 +198,23 @@ async function doBuild(
normalizePath(relative(workPath, f))
);
const routesResult = getTransformedRoutes(vercelConfig || {});
const routesResult = getTransformedRoutes({ nowConfig: vercelConfig || {} });
if (routesResult.error) {
throw routesResult.error;
output.prettyError(routesResult.error);
return 1;
}
if (vercelConfig?.builds && vercelConfig.functions) {
throw new NowBuildError({
code: 'bad_request',
output.prettyError({
message:
'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.',
link: 'https://vercel.link/functions-and-builds',
});
return 1;
}
let builds = vercelConfig?.builds || [];
let zeroConfigRoutes: Route[] = [];
let isZeroConfig = false;
if (builds.length > 0) {
output.warn(
@@ -308,18 +223,17 @@ async function doBuild(
builds = builds.map(b => expandBuild(files, b)).flat();
} else {
// Zero config
isZeroConfig = true;
// Detect the Vercel Builders that will need to be invoked
const detectedBuilders = await detectBuilders(files, pkg, {
...vercelConfig,
projectSettings: project.settings,
ignoreBuildScript: true,
featHandleMiss: true,
});
if (detectedBuilders.errors && detectedBuilders.errors.length > 0) {
throw detectedBuilders.errors[0];
output.prettyError(detectedBuilders.errors[0]);
return 1;
}
for (const w of detectedBuilders.warnings) {
@@ -352,7 +266,13 @@ async function doBuild(
const builderSpecs = new Set(builds.map(b => b.use));
const buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
let buildersWithPkgs: Map<string, BuilderWithPkg>;
try {
buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
} catch (err: any) {
output.prettyError(err);
return 1;
}
// Populate Files -> FileFsRef mapping
const filesMap: Files = {};
@@ -362,6 +282,12 @@ async function doBuild(
filesMap[path] = new FileFsRef({ mode, fsPath });
}
// Delete output directory from potential previous build
const outputDir = argv['--output']
? resolve(argv['--output'])
: join(cwd, OUTPUT_DIR);
await fs.remove(outputDir);
const buildStamp = stamp();
// Create fresh new output directory
@@ -370,31 +296,32 @@ async function doBuild(
const ops: Promise<Error | void>[] = [];
// Write the `detectedBuilders` result to output dir
const buildsJsonBuilds = new Map<Builder, SerializedBuilder>(
builds.map(build => {
const builderWithPkg = buildersWithPkgs.get(build.use);
if (!builderWithPkg) {
throw new Error(`Failed to load Builder "${build.use}"`);
ops.push(
fs.writeJSON(
join(outputDir, 'builds.json'),
{
'//': '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) {
throw new Error(`Failed to load Builder "${build.use}"`);
}
const { builder, pkg: builderPkg } = builderWithPkg;
return {
require: builderPkg.name,
requirePath: builderWithPkg.path,
apiVersion: builder.version,
...build,
};
}),
},
{
spaces: 2,
}
const { builder, pkg: builderPkg } = builderWithPkg;
return [
build,
{
require: builderPkg.name,
requirePath: builderWithPkg.path,
apiVersion: builder.version,
...build,
},
];
})
)
);
buildsJson.builds = Array.from(buildsJsonBuilds.values());
const buildsJsonPath = join(outputDir, 'builds.json');
const writeBuildsJsonPromise = fs.writeJSON(buildsJsonPath, buildsJson, {
spaces: 2,
});
ops.push(writeBuildsJsonPromise);
// The `meta` config property is re-used for each Builder
// invocation so that Builders can share state between
@@ -405,89 +332,65 @@ async function doBuild(
};
// Execute Builders for detected entrypoints
// TODO: parallelize builds (except for frontend)
const sortedBuilders = sortBuilders(builds);
// TODO: parallelize builds
const buildResults: Map<Builder, BuildResult> = new Map();
const overrides: PathOverride[] = [];
const repoRootPath = cwd;
const corepackShimDir = await initCorepack({ repoRootPath });
const rootPackageJsonPath = repoRootPath || workPath;
const corepackShimDir = await initCorepack({ cwd, rootPackageJsonPath });
for (const build of sortedBuilders) {
for (const build of builds) {
if (typeof build.src !== 'string') continue;
const builderWithPkg = buildersWithPkgs.get(build.use);
if (!builderWithPkg) {
throw new Error(`Failed to load Builder "${build.use}"`);
}
const { builder, pkg: builderPkg } = builderWithPkg;
try {
const { builder, pkg: builderPkg } = builderWithPkg;
const buildConfig: Config = {
outputDirectory: project.settings.outputDirectory ?? undefined,
...build.config,
projectSettings: project.settings,
installCommand: project.settings.installCommand ?? undefined,
devCommand: project.settings.devCommand ?? undefined,
buildCommand: project.settings.buildCommand ?? undefined,
framework: project.settings.framework,
nodeVersion: project.settings.nodeVersion,
};
const buildOptions: BuildOptions = {
files: filesMap,
entrypoint: build.src,
workPath,
repoRootPath,
config: buildConfig,
meta,
};
output.debug(
`Building entrypoint "${build.src}" with "${builderPkg.name}"`
);
const buildResult = await builder.build(buildOptions);
const buildConfig: Config = isZeroConfig
? {
outputDirectory: project.settings.outputDirectory ?? undefined,
...build.config,
projectSettings: project.settings,
installCommand: project.settings.installCommand ?? undefined,
devCommand: project.settings.devCommand ?? undefined,
buildCommand: project.settings.buildCommand ?? undefined,
framework: project.settings.framework,
nodeVersion: project.settings.nodeVersion,
}
: build.config || {};
const buildOptions: BuildOptions = {
files: filesMap,
entrypoint: build.src,
workPath,
repoRootPath,
config: buildConfig,
meta,
};
output.debug(
`Building entrypoint "${build.src}" with "${builderPkg.name}"`
);
const buildResult = await builder.build(buildOptions);
// Store the build result to generate the final `config.json` after
// all builds have completed
buildResults.set(build, buildResult);
// Store the build result to generate the final `config.json` after
// all builds have completed
buildResults.set(build, buildResult);
// Start flushing the file outputs to the filesystem asynchronously
ops.push(
writeBuildResult(
outputDir,
buildResult,
build,
builder,
builderPkg,
vercelConfig
).then(
override => {
if (override) overrides.push(override);
},
err => err
)
);
} catch (err: any) {
const writeConfigJsonPromise = fs.writeJSON(
join(outputDir, 'config.json'),
{ version: 3 },
{ spaces: 2 }
);
await Promise.all([writeBuildsJsonPromise, writeConfigJsonPromise]);
const buildJsonBuild = buildsJsonBuilds.get(build);
if (buildJsonBuild) {
buildJsonBuild.error = toEnumerableError(err);
await fs.writeJSON(buildsJsonPath, buildsJson, {
spaces: 2,
});
}
return 1;
}
// Start flushing the file outputs to the filesystem asynchronously
ops.push(
writeBuildResult(
outputDir,
buildResult,
build,
builder,
builderPkg,
vercelConfig?.cleanUrls
).then(
override => {
if (override) overrides.push(override);
},
err => err
)
);
}
if (corepackShimDir) {
@@ -496,12 +399,15 @@ async function doBuild(
// Wait for filesystem operations to complete
// TODO render progress bar?
let hadError = false;
const errors = await Promise.all(ops);
for (const error of errors) {
if (error) {
throw error;
hadError = true;
output.prettyError(error);
}
}
if (hadError) return 1;
// Merge existing `config.json` file into the one that will be produced
const configPath = join(outputDir, 'config.json');
@@ -621,7 +527,7 @@ function mergeImages(
let images: BuildResultV2Typical['images'] = undefined;
for (const result of buildResults) {
if ('images' in result && result.images) {
images = Object.assign({}, images, result.images);
images = Object.assign({} || images, result.images);
}
}
return images;
@@ -639,3 +545,14 @@ function mergeWildcard(
}
return wildcard;
}
async function checkExists(paths: Iterable<string>) {
for (const path of paths) {
try {
await fs.stat(path);
return path;
} catch (err: any) {
if (err.code !== 'ENOENT') throw err;
}
}
}

View File

@@ -5,7 +5,6 @@ import stamp from '../../util/output/stamp';
import createCertFromFile from '../../util/certs/create-cert-from-file';
import createCertForCns from '../../util/certs/create-cert-for-cns';
import { getCommandName } from '../../util/pkg-name';
import { Cert } from '../../types';
interface Options {
'--overwrite'?: boolean;
@@ -22,7 +21,7 @@ async function add(
const { output } = client;
const addStamp = stamp();
let cert: Cert | Error;
let cert;
const {
'--overwrite': overwite,
@@ -31,7 +30,18 @@ async function add(
'--ca': caPath,
} = opts;
const { contextName } = await getScope(client);
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
if (overwite) {
output.error('Overwrite option is deprecated');

View File

@@ -39,7 +39,18 @@ export default async function issue(
'--ca': caPath,
} = opts;
const { contextName } = await getScope(client);
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
if (overwite) {
output.error('Overwrite option is deprecated');

View File

@@ -21,8 +21,18 @@ async function ls(
): Promise<number> {
const { output } = client;
const { '--next': nextTimestamp } = opts;
const { contextName } = await getScope(client);
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
if (typeof nextTimestamp !== 'undefined' && Number.isNaN(nextTimestamp)) {
output.error('Please provide a number for flag --next');
return 1;

View File

@@ -17,9 +17,21 @@ import { getCommandName } from '../../util/pkg-name';
type Options = {};
async function rm(client: Client, opts: Options, args: string[]) {
const rmStamp = stamp();
const { output } = client;
const { contextName } = await getScope(client);
const rmStamp = stamp();
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
if (args.length !== 1) {
output.error(

View File

@@ -15,7 +15,6 @@ export const help = () => `
)}
dev Start a local development server
env Manages the Environment Variables for your current Project
git Manage Git provider repository for your current Project
init [example] Initialize an example project
ls | list [app] Lists deployments
inspect [id] Displays information related to a deployment

View File

@@ -3,7 +3,7 @@ import fs from 'fs-extra';
import bytes from 'bytes';
import chalk from 'chalk';
import { join, resolve, basename } from 'path';
import { fileNameSymbol, VercelConfig } from '@vercel/client';
import { Dictionary, fileNameSymbol, VercelConfig } from '@vercel/client';
import code from '../../util/output/code';
import highlight from '../../util/output/highlight';
import { readLocalConfig } from '../../util/config/files';
@@ -38,7 +38,6 @@ import {
ConflictingPathSegment,
BuildError,
NotDomainOwner,
isAPIError,
} from '../../util/errors-ts';
import { SchemaValidationFailed } from '../../util/errors';
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
@@ -65,9 +64,7 @@ import { help } from './args';
import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
import parseTarget from '../../util/deploy/parse-target';
import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
import { createGitMeta } from '../../util/create-git-meta';
import { parseEnv } from '../../util/parse-env';
import { errorToString, isErrnoException, isError } from '../../util/is-error';
import { createGitMeta } from '../../util/deploy/create-git-meta';
export default async (client: Client) => {
const { output } = client;
@@ -98,7 +95,6 @@ export default async (client: Client) => {
// deprecated
'--name': String,
'-n': '--name',
'--no-clipboard': Boolean,
'--target': String,
});
} catch (error) {
@@ -187,17 +183,6 @@ export default async (client: Client) => {
);
}
if (argv['--no-clipboard']) {
output.print(
`${prependEmoji(
`The ${param(
'--no-clipboard'
)} option was ignored because it is the default behavior. Please remove it.`,
emoji('warning')
)}\n`
);
}
// build `target`
const target = parseTarget(output, argv['--target'], argv['--prod']);
if (typeof target === 'number') {
@@ -219,22 +204,6 @@ export default async (client: Client) => {
}
const prebuiltBuild = await getPrebuiltJson(path);
// Ensure that there was not a build error
const prebuiltError =
prebuiltBuild?.error ||
prebuiltBuild?.builds?.find(build => 'error' in build)?.error;
if (prebuiltError) {
output.log(
`Prebuilt deployment cannot be created because ${getCommandName(
'build'
)} failed with error:\n`
);
prettyError(prebuiltError);
return 1;
}
// Ensure that the deploy target matches the build target
const assumedTarget = target || 'preview';
if (prebuiltBuild?.target && prebuiltBuild.target !== assumedTarget) {
let specifyTarget = '';
@@ -287,11 +256,8 @@ export default async (client: Client) => {
'Which scope do you want to deploy to?',
autoConfirm
);
} catch (err: unknown) {
if (
isErrnoException(err) &&
(err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED')
) {
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
@@ -450,7 +416,7 @@ export default async (client: Client) => {
parseMeta(argv['--meta'])
);
const gitMetadata = await createGitMeta(path, output, project);
const gitMetadata = await createGitMeta(path, output);
// Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments
const deploymentEnv = Object.assign(
@@ -470,8 +436,8 @@ export default async (client: Client) => {
try {
await addProcessEnv(log, deploymentEnv);
await addProcessEnv(log, deploymentBuildEnv);
} catch (err: unknown) {
error(errorToString(err));
} catch (err) {
error(err.message);
return 1;
}
@@ -632,10 +598,8 @@ export default async (client: Client) => {
error('Uploading failed. Please try again.');
return 1;
}
} catch (err: unknown) {
if (isError(err)) {
debug(`Error: ${err}\n${err.stack}`);
}
} catch (err) {
debug(`Error: ${err}\n${err.stack}`);
if (err instanceof NotDomainOwner) {
output.error(err.message);
@@ -702,7 +666,13 @@ export default async (client: Client) => {
return 1;
}
if (isAPIError(err) && err.code === 'size_limit_exceeded') {
if (err.keyword === 'additionalProperties' && err.dataPath === '.scale') {
const { additionalProperty = '' } = err.params || {};
const message = `Invalid DC name for the scale option: ${additionalProperty}`;
error(message);
}
if (err.code === 'size_limit_exceeded') {
const { sizeLimit = 0 } = err;
const message = `File size limit exceeded (${bytes(sizeLimit)})`;
error(message);
@@ -912,3 +882,36 @@ const printDeploymentStatus = async (
output.print(message + link);
}
};
// Converts `env` Arrays, Strings and Objects into env Objects.
const parseEnv = (env?: string[] | Dictionary<string>) => {
if (!env) {
return {};
}
if (typeof env === 'string') {
// a single `--env` arg comes in as a String
env = [env];
}
if (Array.isArray(env)) {
return env.reduce((o, e) => {
let key;
let value;
const equalsSign = e.indexOf('=');
if (equalsSign === -1) {
key = e;
} else {
key = e.slice(0, equalsSign);
value = e.slice(equalsSign + 1);
}
o[key] = value;
return o;
}, {} as Dictionary<string | undefined>);
}
// assume it's already an Object
return env;
};

View File

@@ -15,7 +15,6 @@ import readConfig from '../../util/config/read-config';
import readJSONFile from '../../util/read-json-file';
import { getPkgName, getCommandName } from '../../util/pkg-name';
import { CantParseJSONFile } from '../../util/errors-ts';
import { isErrnoException } from '../../util/is-error';
const COMMAND_CONFIG = {
dev: ['dev'],
@@ -137,7 +136,7 @@ export default async function main(client: Client) {
try {
return await dev(client, argv, args);
} catch (err) {
if (isErrnoException(err) && err.code === 'ENOTFOUND') {
if (err.code === 'ENOTFOUND') {
// Error message will look like the following:
// "request to https://api.vercel.com/v2/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
@@ -149,9 +148,7 @@ export default async function main(client: Client) {
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
);
}
if (typeof err.stack === 'string') {
output.debug(err.stack);
}
output.debug(err.stack);
return 1;
}
output.prettyError(err);

View File

@@ -21,7 +21,18 @@ export default async function add(
args: string[]
) {
const { output } = client;
const { contextName } = await getScope(client);
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const parsedParams = parseAddDNSRecordArgs(args);
if (!parsedParams) {

View File

@@ -14,7 +14,18 @@ export default async function add(
args: string[]
) {
const { output } = client;
const { contextName } = await getScope(client);
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
if (args.length !== 2) {
output.error(

View File

@@ -24,7 +24,18 @@ export default async function ls(
) {
const { output } = client;
const { '--next': nextTimestamp } = opts;
const { contextName } = await getScope(client);
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const [domainName] = args;
const lsStamp = stamp();

View File

@@ -14,11 +14,21 @@ type Options = {};
export default async function rm(
client: Client,
_opts: Options,
opts: Options,
args: string[]
) {
const { output } = client;
await getScope(client);
try {
await getScope(client);
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const [recordId] = args;
if (args.length !== 1) {

View File

@@ -26,7 +26,18 @@ export default async function add(
) {
const { output } = client;
const force = opts['--force'];
const { contextName } = await getScope(client);
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const project = await getLinkedProject(client).then(result => {
if (result.status === 'linked') {

View File

@@ -11,7 +11,6 @@ import promptBool from '../../util/input/prompt-bool';
import purchaseDomain from '../../util/domains/purchase-domain';
import stamp from '../../util/output/stamp';
import { getCommandName } from '../../util/pkg-name';
import { errorToString } from '../../util/is-error';
type Options = {};
@@ -21,7 +20,18 @@ export default async function buy(
args: string[]
) {
const { output } = client;
const { contextName } = await getScope(client);
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const [domainName] = args;
if (!domainName) {
@@ -58,11 +68,6 @@ export default async function buy(
return 1;
}
if (renewalPrice instanceof Error) {
output.prettyError(renewalPrice);
return 1;
}
if (!(await getDomainStatus(client, domainName)).available) {
output.error(
`The domain ${param(domainName)} is ${chalk.underline(
@@ -104,11 +109,11 @@ export default async function buy(
try {
buyResult = await purchaseDomain(client, domainName, price, autoRenew);
} catch (err: unknown) {
} catch (err) {
output.error(
'An unexpected error occurred while purchasing your domain. Please try again later.'
);
output.debug(`Server response: ${errorToString(err)}`);
output.debug(`Server response: ${err.message}`);
return 1;
}

View File

@@ -23,7 +23,19 @@ export default async function inspect(
args: string[]
) {
const { output } = client;
const { contextName } = await getScope(client);
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const [domainName] = args;
const inspectStamp = stamp();

View File

@@ -25,13 +25,23 @@ export default async function ls(
) {
const { output } = client;
const { '--next': nextTimestamp } = opts;
let contextName = null;
if (typeof nextTimestamp !== undefined && Number.isNaN(nextTimestamp)) {
output.error('Please provide a number for flag --next');
return 1;
}
const { contextName } = await getScope(client);
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const lsStamp = stamp();

View File

@@ -25,7 +25,20 @@ export default async function move(
args: string[]
) {
const { output } = client;
const { contextName, user } = await getScope(client);
let contextName = null;
let user = null;
try {
({ contextName, user } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const { domainName, destination } = await getArgs(args);
if (!isRootDomain(domainName)) {
output.error(

View File

@@ -29,7 +29,18 @@ export default async function rm(
) {
const { output } = client;
const [domainName] = args;
const { contextName } = await getScope(client);
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
if (!domainName) {
output.error(
@@ -111,10 +122,10 @@ async function removeDomain(
output.debug(`Removing alias ${id}`);
try {
await removeAliasById(client, id);
} catch (err: unknown) {
} catch (error) {
// Ignore if the alias does not exist anymore
if (!ERRORS.isAPIError(err) || err.status !== 404) {
throw err;
if (error.status !== 404) {
throw error;
}
}
}
@@ -123,10 +134,10 @@ async function removeDomain(
output.debug(`Removing cert ${id}`);
try {
await deleteCertById(output, client, id);
} catch (err: unknown) {
} catch (error) {
// Ignore if the cert does not exist anymore
if (!ERRORS.isAPIError(err) || err.status !== 404) {
throw err;
if (error.status !== 404) {
throw error;
}
}
}

View File

@@ -23,7 +23,18 @@ export default async function transferIn(
args: string[]
) {
const { output } = client;
const { contextName } = await getScope(client);
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const [domainName] = args;
if (!domainName) {

View File

@@ -1,4 +1,5 @@
import chalk from 'chalk';
import inquirer from 'inquirer';
import { ProjectEnvTarget, Project, ProjectEnvType } from '../../types';
import { Output } from '../../util/output';
import Client from '../../util/client';
@@ -15,7 +16,6 @@ import param from '../../util/output/param';
import { emoji, prependEmoji } from '../../util/emoji';
import { isKnownError } from '../../util/env/known-error';
import { getCommandName } from '../../util/pkg-name';
import { isAPIError } from '../../util/errors-ts';
type Options = {
'--debug': boolean;
@@ -66,7 +66,7 @@ export default async function add(
}
while (!envName) {
const { inputName } = await client.prompt({
const { inputName } = await inquirer.prompt({
type: 'input',
name: 'inputName',
message: `Whats the name of the variable?`,
@@ -106,7 +106,7 @@ export default async function add(
if (stdInput) {
envValue = stdInput;
} else {
const { inputValue } = await client.prompt({
const { inputValue } = await inquirer.prompt({
type: 'input',
name: 'inputValue',
message: `Whats the value of ${envName}?`,
@@ -116,7 +116,7 @@ export default async function add(
}
while (envTargets.length === 0) {
const { inputTargets } = await client.prompt({
const { inputTargets } = await inquirer.prompt({
name: 'inputTargets',
type: 'checkbox',
message: `Add ${envName} to which Environments (select multiple)?`,
@@ -136,7 +136,7 @@ export default async function add(
envTargets.length === 1 &&
envTargets[0] === ProjectEnvTarget.Preview
) {
const { inputValue } = await client.prompt({
const { inputValue } = await inquirer.prompt({
type: 'input',
name: 'inputValue',
message: `Add ${envName} to which Git branch? (leave empty for all Preview branches)?`,
@@ -157,12 +157,12 @@ export default async function add(
envTargets,
envGitBranch
);
} catch (err: unknown) {
if (isAPIError(err) && isKnownError(err)) {
output.error(err.serverMessage);
} catch (error) {
if (isKnownError(error) && error.serverMessage) {
output.error(error.serverMessage);
return 1;
}
throw err;
throw error;
}
output.print(

View File

@@ -1,9 +1,7 @@
import chalk from 'chalk';
import { ProjectEnvTarget } from '../../types';
import Client from '../../util/client';
import {
getEnvTargetPlaceholder,
isValidEnvTarget,
} from '../../util/env/env-target';
import { getEnvTargetPlaceholder } from '../../util/env/env-target';
import getArgs from '../../util/get-args';
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
import getSubcommand from '../../util/get-subcommand';
@@ -31,7 +29,6 @@ const help = () => {
${chalk.dim('Options:')}
-h, --help Output usage information
--environment Set the Environment (development, preview, production) when pulling Environment Variables
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'
)} Path to the local ${'`vercel.json`'} file
@@ -114,7 +111,6 @@ export default async function main(client: Client) {
argv = getArgs(client.argv.slice(2), {
'--yes': Boolean,
'-y': '--yes',
'--environment': String,
});
} catch (error) {
handleError(error);
@@ -130,17 +126,6 @@ export default async function main(client: Client) {
const subArgs = argv._.slice(1);
const { subcommand, args } = getSubcommand(subArgs, COMMAND_CONFIG);
const { output, config } = client;
const target = argv['--environment']?.toLowerCase() || 'development';
if (!isValidEnvTarget(target)) {
output.error(
`Invalid environment \`${chalk.cyan(
target
)}\`. Valid options: ${getEnvTargetPlaceholder()}`
);
return 1;
}
const link = await getLinkedProject(client, cwd);
if (link.status === 'error') {
return link.exitCode;
@@ -165,7 +150,7 @@ export default async function main(client: Client) {
return pull(
client,
project,
target,
ProjectEnvTarget.Development,
argv,
args,
output,

View File

@@ -14,11 +14,6 @@ import param from '../../util/output/param';
import stamp from '../../util/output/stamp';
import { getCommandName } from '../../util/pkg-name';
import { EnvRecordsSource } from '../../util/env/get-env-records';
import {
buildDeltaString,
createEnvObject,
} from '../../util/env/diff-env-files';
import { isErrnoException } from '../../util/is-error';
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
@@ -41,8 +36,8 @@ function readHeadSync(path: string, length: number) {
function tryReadHeadSync(path: string, length: number) {
try {
return readHeadSync(path, length);
} catch (err: unknown) {
if (!isErrnoException(err) || err.code !== 'ENOENT') {
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}
@@ -74,7 +69,7 @@ export default async function pull(
const exists = typeof head !== 'undefined';
if (head === CONTENTS_PREFIX) {
output.log(`Overwriting existing ${chalk.bold(filename)} file`);
output.print(`Overwriting existing ${chalk.bold(filename)} file\n`);
} else if (
exists &&
!skipConfirmation &&
@@ -88,10 +83,10 @@ export default async function pull(
return 0;
}
output.log(
`Downloading \`${chalk.cyan(
environment
)}\` Environment Variables for Project ${chalk.bold(project.name)}`
output.print(
`Downloading "${environment}" Environment Variables for Project ${chalk.bold(
project.name
)}\n`
);
const pullStamp = stamp();
@@ -112,15 +107,6 @@ export default async function pull(
environment
);
let deltaString = '';
let oldEnv;
if (exists) {
oldEnv = await createEnvObject(fullPath, output);
if (oldEnv) {
deltaString = buildDeltaString(oldEnv, records);
}
}
const contents =
CONTENTS_PREFIX +
Object.entries(records)
@@ -139,13 +125,6 @@ export default async function pull(
)}\n`
);
output.print('\n');
if (deltaString) {
output.print(deltaString);
} else if (oldEnv && exists) {
output.log('No changes found.');
}
return 0;
}

View File

@@ -16,7 +16,6 @@ import param from '../../util/output/param';
import { emoji, prependEmoji } from '../../util/emoji';
import { isKnownError } from '../../util/env/known-error';
import { getCommandName } from '../../util/pkg-name';
import { isAPIError } from '../../util/errors-ts';
type Options = {
'--debug': boolean;
@@ -121,12 +120,12 @@ export default async function rm(
try {
output.spinner('Removing');
await removeEnvRecord(output, client, project.id, env);
} catch (err: unknown) {
if (isAPIError(err) && isKnownError(err)) {
output.error(err.serverMessage);
} catch (error) {
if (isKnownError(error) && error.serverMessage) {
output.error(error.serverMessage);
return 1;
}
throw err;
throw error;
}
output.print(

View File

@@ -1,205 +0,0 @@
import chalk from 'chalk';
import { join } from 'path';
import { Org, Project } from '../../types';
import Client from '../../util/client';
import { parseGitConfig, pluckRemoteUrls } from '../../util/create-git-meta';
import confirm from '../../util/input/confirm';
import list, { ListChoice } from '../../util/input/list';
import { Output } from '../../util/output';
import link from '../../util/output/link';
import { getCommandName } from '../../util/pkg-name';
import {
connectGitProvider,
disconnectGitProvider,
formatProvider,
parseRepoUrl,
} from '../../util/projects/connect-git-provider';
import validatePaths from '../../util/validate-paths';
export default async function connect(
client: Client,
argv: any,
args: string[],
project: Project | undefined,
org: Org | undefined
) {
const { output } = client;
const confirm = Boolean(argv['--confirm']);
if (args.length !== 0) {
output.error(
`Invalid number of arguments. Usage: ${chalk.cyan(
`${getCommandName('project connect')}`
)}`
);
return 2;
}
if (!project || !org) {
output.error(
`Can't find \`org\` or \`project\`. Make sure your current directory is linked to a Vercel projet by running ${getCommandName(
'link'
)}.`
);
return 1;
}
let paths = [process.cwd()];
const validate = await validatePaths(client, paths);
if (!validate.valid) {
return validate.exitCode;
}
const { path } = validate;
const gitProviderLink = project.link;
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
// get project from .git
const gitConfigPath = join(path, '.git/config');
const gitConfig = await parseGitConfig(gitConfigPath, output);
if (!gitConfig) {
output.error(
`No local git repo found. Run ${chalk.cyan(
'`git clone <url>`'
)} to clone a remote Git repository first.`
);
return 1;
}
const remoteUrls = pluckRemoteUrls(gitConfig);
if (!remoteUrls) {
output.error(
`No remote URLs found in your Git config. Make sure you've configured a remote repo in your local Git config. Run ${chalk.cyan(
'`git remote --help`'
)} for more details.`
);
return 1;
}
let remoteUrl: string;
if (Object.keys(remoteUrls).length > 1) {
output.log(`Found multiple remote URLs.`);
remoteUrl = await selectRemoteUrl(client, remoteUrls);
} else {
// If only one is found, get it — usually "origin"
remoteUrl = Object.values(remoteUrls)[0];
}
if (remoteUrl === '') {
output.log('Aborted.');
return 0;
}
output.log(`Connecting Git remote: ${link(remoteUrl)}`);
const parsedUrl = parseRepoUrl(remoteUrl);
if (!parsedUrl) {
output.error(
`Failed to parse Git repo data from the following remote URL: ${link(
remoteUrl
)}`
);
return 1;
}
const { provider, org: gitOrg, repo } = parsedUrl;
const repoPath = `${gitOrg}/${repo}`;
let connectedRepoPath;
if (!gitProviderLink) {
const connect = await connectGitProvider(
client,
org,
project.id,
provider,
repoPath
);
if (typeof connect === 'number') {
return connect;
}
} else {
const connectedProvider = gitProviderLink.type;
const connectedOrg = gitProviderLink.org;
const connectedRepo = gitProviderLink.repo;
connectedRepoPath = `${connectedOrg}/${connectedRepo}`;
const isSameRepo =
connectedProvider === provider &&
connectedOrg === gitOrg &&
connectedRepo === repo;
if (isSameRepo) {
output.log(
`${chalk.cyan(connectedRepoPath)} is already connected to your project.`
);
return 1;
}
const shouldReplaceRepo = await confirmRepoConnect(
client,
output,
confirm,
connectedRepoPath
);
if (!shouldReplaceRepo) {
return 0;
}
await disconnectGitProvider(client, org, project.id);
const connect = await connectGitProvider(
client,
org,
project.id,
provider,
repoPath
);
if (typeof connect === 'number') {
return connect;
}
}
output.log(
`Connected ${formatProvider(provider)} repository ${chalk.cyan(repoPath)}!`
);
return 0;
}
async function confirmRepoConnect(
client: Client,
output: Output,
yes: boolean,
connectedRepoPath: string
) {
let shouldReplaceProject = yes;
if (!shouldReplaceProject) {
shouldReplaceProject = await confirm(
client,
`Looks like you already have a repository connected: ${chalk.cyan(
connectedRepoPath
)}. Do you want to replace it?`,
true
);
if (!shouldReplaceProject) {
output.log(`Aborted. Repo not connected.`);
}
}
return shouldReplaceProject;
}
async function selectRemoteUrl(
client: Client,
remoteUrls: { [key: string]: string }
): Promise<string> {
let choices: ListChoice[] = [];
for (const [urlKey, urlValue] of Object.entries(remoteUrls)) {
choices.push({
name: `${urlValue} ${chalk.gray(`(${urlKey})`)}`,
value: urlValue,
short: urlKey,
});
}
return await list(client, {
message: 'Which remote do you want to connect?',
choices,
});
}

View File

@@ -1,58 +0,0 @@
import chalk from 'chalk';
import { Org, Project } from '../../types';
import Client from '../../util/client';
import confirm from '../../util/input/confirm';
import { getCommandName } from '../../util/pkg-name';
import { disconnectGitProvider } from '../../util/projects/connect-git-provider';
export default async function disconnect(
client: Client,
args: string[],
project: Project | undefined,
org: Org | undefined
) {
const { output } = client;
if (args.length !== 0) {
output.error(
`Invalid number of arguments. Usage: ${chalk.cyan(
`${getCommandName('project disconnect')}`
)}`
);
return 2;
}
if (!project || !org) {
output.error('An unexpected error occurred.');
return 1;
}
if (project.link) {
const { org: linkOrg, repo } = project.link;
output.print(
`Your Vercel project will no longer create deployments when you push to this repository.\n`
);
const confirmDisconnect = await confirm(
client,
`Are you sure you want to disconnect ${chalk.cyan(
`${linkOrg}/${repo}`
)} from your project?`,
false
);
if (confirmDisconnect) {
await disconnectGitProvider(client, org, project.id);
output.log(`Disconnected ${chalk.cyan(`${linkOrg}/${repo}`)}.`);
} else {
output.log('Aborted.');
}
} else {
output.error(
`No Git repository connected. Run ${getCommandName(
'project connect'
)} to connect one.`
);
return 1;
}
return 0;
}

View File

@@ -1,94 +0,0 @@
import chalk from 'chalk';
import Client from '../../util/client';
import { ensureLink } from '../../util/ensure-link';
import getArgs from '../../util/get-args';
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
import handleError from '../../util/handle-error';
import logo from '../../util/output/logo';
import { getPkgName } from '../../util/pkg-name';
import validatePaths from '../../util/validate-paths';
import connect from './connect';
import disconnect from './disconnect';
const help = () => {
console.log(`
${chalk.bold(`${logo} ${getPkgName()} git`)} <command>
${chalk.dim('Commands:')}
connect Connect your Git config "origin" remote as a Git provider to your project
disconnect Disconnect the Git provider repository from your project
${chalk.dim('Options:')}
-h, --help Output usage information
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN'
)} Login token
${chalk.dim('Examples:')}
${chalk.gray('')} Connect a Git provider repository
${chalk.cyan(`$ ${getPkgName()} git connect`)}
${chalk.gray('')} Disconnect the Git provider repository
${chalk.cyan(`$ ${getPkgName()} git disconnect`)}
`);
};
const COMMAND_CONFIG = {
connect: ['connect'],
disconnect: ['disconnect'],
};
export default async function main(client: Client) {
let argv: any;
let subcommand: string | string[];
try {
argv = getArgs(client.argv.slice(2), {
'--confirm': Boolean,
});
} catch (error) {
handleError(error);
return 1;
}
if (argv['--help']) {
help();
return 2;
}
argv._ = argv._.slice(1);
subcommand = argv._[0];
const args = argv._.slice(1);
const confirm = Boolean(argv['--confirm']);
const { output } = client;
let paths = [process.cwd()];
const pathValidation = await validatePaths(client, paths);
if (!pathValidation.valid) {
return pathValidation.exitCode;
}
const { path } = pathValidation;
const linkedProject = await ensureLink('git', client, path, confirm);
if (typeof linkedProject === 'number') {
return linkedProject;
}
const { org, project } = linkedProject;
switch (subcommand) {
case 'connect':
return await connect(client, argv, args, project, org);
case 'disconnect':
return await disconnect(client, args, project, org);
default:
output.error(getInvalidSubcommand(COMMAND_CONFIG));
help();
return 2;
}
}

View File

@@ -14,7 +14,6 @@ export default new Map([
['domain', 'domains'],
['domains', 'domains'],
['env', 'env'],
['git', 'git'],
['help', 'help'],
['init', 'init'],
['inspect', 'inspect'],

View File

@@ -7,7 +7,6 @@ import handleError from '../../util/handle-error';
import logo from '../../util/output/logo';
import init from './init';
import { getPkgName } from '../../util/pkg-name';
import { isError } from '../../util/is-error';
const COMMAND_CONFIG = {
init: ['init'],
@@ -71,11 +70,9 @@ export default async function main(client: Client) {
try {
return await init(client, argv, args);
} catch (err: unknown) {
} catch (err) {
output.prettyError(err);
if (isError(err) && typeof err.stack === 'string') {
output.debug(err.stack);
}
output.debug(err.stack);
return 1;
}
}

View File

@@ -12,9 +12,6 @@ import Client from '../util/client';
import { getDeployment } from '../util/get-deployment';
import { Deployment } from '@vercel/client';
import { Build } from '../types';
import title from 'title';
import { isErrnoException } from '../util/is-error';
import { isAPIError } from '../util/errors-ts';
const help = () => {
console.log(`
@@ -78,11 +75,8 @@ export default async function main(client: Client) {
try {
({ contextName } = await getScope(client));
} catch (err: unknown) {
if (
isErrnoException(err) &&
(err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED')
) {
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
error(err.message);
return 1;
}
@@ -98,38 +92,28 @@ export default async function main(client: Client) {
try {
deployment = await getDeployment(client, deploymentIdOrHost);
} catch (err: unknown) {
if (isAPIError(err)) {
if (err.status === 404) {
error(
`Failed to find deployment "${deploymentIdOrHost}" in ${chalk.bold(
contextName
)}`
);
return 1;
}
if (err.status === 403) {
error(
`No permission to access deployment "${deploymentIdOrHost}" in ${chalk.bold(
contextName
)}`
);
return 1;
}
} catch (err) {
if (err.status === 404) {
error(
`Failed to find deployment "${deploymentIdOrHost}" in ${chalk.bold(
contextName
)}`
);
return 1;
}
if (err.status === 403) {
error(
`No permission to access deployment "${deploymentIdOrHost}" in ${chalk.bold(
contextName
)}`
);
return 1;
}
// unexpected
throw err;
}
const {
id,
name,
url,
createdAt,
routes,
readyState,
alias: aliases,
} = deployment;
const { id, name, url, createdAt, routes, readyState } = deployment;
const { builds } =
deployment.version === 2
@@ -137,20 +121,20 @@ export default async function main(client: Client) {
: { builds: [] };
log(
`Fetched deployment ${chalk.bold(url)} in ${chalk.bold(
contextName
)} ${elapsed(Date.now() - depFetchStart)}`
`Fetched deployment "${url}" in ${chalk.bold(contextName)} ${elapsed(
Date.now() - depFetchStart
)}`
);
print('\n');
print(chalk.bold(' General\n\n'));
print(` ${chalk.cyan('id')}\t\t${id}\n`);
print(` ${chalk.cyan('name')}\t${name}\n`);
print(` ${chalk.cyan('status')}\t${stateString(readyState)}\n`);
print(` ${chalk.cyan('url')}\t\thttps://${url}\n`);
print(` ${chalk.cyan('readyState')}\t${stateString(readyState)}\n`);
print(` ${chalk.cyan('url')}\t\t${url}\n`);
if (createdAt) {
print(
` ${chalk.cyan('created')}\t${new Date(createdAt)} ${elapsed(
` ${chalk.cyan('createdAt')}\t${new Date(createdAt)} ${elapsed(
Date.now() - createdAt,
true
)}\n`
@@ -158,16 +142,6 @@ export default async function main(client: Client) {
}
print('\n\n');
if (aliases.length > 0) {
print(chalk.bold(' Aliases\n\n'));
let aliasList = '';
for (const alias of aliases) {
aliasList += `${chalk.gray('╶')} https://${alias}\n`;
}
print(indent(aliasList, 4));
print('\n\n');
}
if (builds.length > 0) {
const times: { [id: string]: string | null } = {};
@@ -191,24 +165,19 @@ export default async function main(client: Client) {
return 0;
}
// renders the state string
function stateString(s: Deployment['readyState']) {
const CIRCLE = '● ';
const sTitle = s && title(s);
switch (s) {
case 'INITIALIZING':
case 'BUILDING':
case 'DEPLOYING':
case 'ANALYZING':
return chalk.yellow(CIRCLE) + sTitle;
return chalk.yellow(s);
case 'ERROR':
return chalk.red(CIRCLE) + sTitle;
return chalk.red(s);
case 'READY':
return chalk.green(CIRCLE) + sTitle;
case 'QUEUED':
return chalk.gray(CIRCLE) + sTitle;
case 'CANCELED':
return chalk.gray(CIRCLE) + sTitle;
return s;
default:
return chalk.gray('UNKNOWN');
return chalk.gray(s || 'UNKNOWN');
}
}

View File

@@ -1,10 +1,10 @@
import chalk from 'chalk';
import ms from 'ms';
import table from 'text-table';
import title from 'title';
import Now from '../util';
import getArgs from '../util/get-args';
import { handleError } from '../util/error';
import cmd from '../util/output/cmd';
import logo from '../util/output/logo';
import elapsed from '../util/output/elapsed';
import strlen from '../util/strlen';
@@ -19,7 +19,6 @@ import validatePaths from '../util/validate-paths';
import { getLinkedProject } from '../util/projects/link';
import { ensureLink } from '../util/ensure-link';
import getScope from '../util/get-scope';
import { isAPIError } from '../util/errors-ts';
const help = () => {
console.log(`
@@ -43,6 +42,8 @@ const help = () => {
-m, --meta Filter deployments by metadata (e.g.: ${chalk.dim(
'`-m KEY=value`'
)}). Can appear many times.
-i, --inspect Display dashboard inspect URLs
--prod Filter for production URLs
-N, --next Show next page of results
${chalk.dim('Examples:')}
@@ -78,6 +79,9 @@ export default async function main(client: Client) {
'-m': '--meta',
'--next': Number,
'-N': '--next',
'--inspect': Boolean,
'-i': '--inspect',
'--prod': Boolean,
'--confirm': Boolean,
});
} catch (err) {
@@ -100,9 +104,10 @@ export default async function main(client: Client) {
}
const yes = argv['--confirm'] || false;
const inspect = argv['--inspect'] || false;
const prod = argv['--prod'] || false;
const meta = parseMeta(argv['--meta']);
const { includeScheme } = config;
let paths = [process.cwd()];
const pathValidation = await validatePaths(client, paths);
@@ -140,6 +145,7 @@ export default async function main(client: Client) {
link.project = linkedProject.project;
}
const isTeam = org?.type === 'team';
let { contextName, team } = await getScope(client);
// If user passed in a custom scope, update the current team & context name
@@ -153,7 +159,16 @@ export default async function main(client: Client) {
const { currentTeam } = config;
({ contextName } = await getScope(client));
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
error(err.message);
return 1;
}
throw err;
}
const nextTimestamp = argv['--next'];
@@ -220,8 +235,8 @@ export default async function main(client: Client) {
try {
await now.findDeployment(app);
} catch (err: unknown) {
if (isAPIError(err) && err.status === 404) {
} catch (err) {
if (err.status === 404) {
debug('Ignore findDeployment 404');
} else {
throw err;
@@ -247,37 +262,51 @@ export default async function main(client: Client) {
}
log(
`Deployments under ${chalk.bold(contextName)} ${elapsed(
`${prod ? `Production deployments` : `Deployments`} for ${chalk.bold(
chalk.magenta(app)
)} under ${chalk.bold(chalk.magenta(contextName))} ${elapsed(
Date.now() - start
)}`
);
// information to help the user find other deployments or instances
if (app == null) {
log(
`To list more deployments for a project run ${cmd(
`${getCommandName('ls [project]')}`
)}`
);
}
log(
`To list more deployments for a project, run ${getCommandName(
'ls [project]'
)}.`
);
print('\n');
client.output.print(
`${table(
[
['project', 'latest deployment', 'state', 'age', 'username'].map(
header => chalk.dim(header)
),
(isTeam
? [
'age',
inspect ? 'inspect url' : 'deployment url',
'state',
'duration',
'username',
]
: [
'age',
inspect ? 'inspect url' : 'deployment url',
'state',
'duration',
]
).map(header => chalk.bold(chalk.cyan(header))),
...deployments
.sort(sortRecent())
.map(dep => [
.map((dep, i) => [
[
getProjectName(dep),
chalk.bold((includeScheme ? 'https://' : '') + dep.url),
stateString(dep.state),
chalk.gray(ms(Date.now() - dep.createdAt)),
dep.creator.username,
i === 0
? chalk.bold(`${getDeployUrl(dep, inspect)}`)
: `${getDeployUrl(dep, inspect)}`,
stateString(dep.state),
chalk.gray(getDeploymentDuration(dep)),
isTeam ? chalk.gray(dep.creator.username) : '',
],
])
// flatten since the previous step returns a nested
@@ -290,8 +319,8 @@ export default async function main(client: Client) {
),
],
{
align: ['l', 'l', 'r', 'l', 'l'],
hsep: ' '.repeat(4),
align: isTeam ? ['l', 'l', 'l', 'l', 'l'] : ['l', 'l', 'l', 'l'],
hsep: ' '.repeat(isTeam ? 4 : 5),
stringLength: strlen,
}
).replace(/^/gm, ' ')}\n\n`
@@ -307,27 +336,43 @@ export default async function main(client: Client) {
}
}
function getProjectName(d: Deployment) {
// We group both file and files into a single project
if (d.name === 'file') {
return 'files';
}
function getDeployUrl(
deployment: Deployment,
inspect: boolean | undefined
): string {
return inspect ? deployment.inspectorUrl : 'https://' + deployment.url;
}
return d.name;
export function getDeploymentDuration(dep: Deployment): string {
if (!dep || !dep.ready || !dep.buildingAt) {
return '?';
}
const duration = ms(dep.ready - dep.buildingAt);
if (duration === '0ms') {
return '--';
}
return duration;
}
// renders the state string
export function stateString(s: string) {
const CIRCLE = '● ';
// make `s` title case
const sTitle = title(s);
switch (s) {
case 'INITIALIZING':
return chalk.yellow(s);
case 'BUILDING':
case 'DEPLOYING':
case 'ANALYZING':
return chalk.yellow(CIRCLE) + sTitle;
case 'ERROR':
return chalk.red(s);
return chalk.red(CIRCLE) + sTitle;
case 'READY':
return s;
return chalk.green(CIRCLE) + sTitle;
case 'QUEUED':
return chalk.white(CIRCLE) + sTitle;
case 'CANCELED':
return chalk.gray(sTitle);
default:
return chalk.gray('UNKNOWN');
}

View File

@@ -5,8 +5,6 @@ import { writeToConfigFile, writeToAuthConfigFile } from '../util/config/files';
import getArgs from '../util/get-args';
import Client from '../util/client';
import { getCommandName, getPkgName } from '../util/pkg-name';
import { isAPIError } from '../util/errors-ts';
import { errorToString } from '../util/is-error';
const help = () => {
console.log(`
@@ -65,14 +63,12 @@ export default async function main(client: Client): Promise<number> {
method: 'DELETE',
useCurrentTeam: false,
});
} catch (err: unknown) {
if (isAPIError(err)) {
if (err.status === 403) {
output.debug('Token is invalid so it cannot be revoked');
} else if (err.status !== 200) {
output.debug(err?.message ?? '');
exitCode = 1;
}
} catch (err) {
if (err.status === 403) {
output.debug('Token is invalid so it cannot be revoked');
} else if (err.status !== 200) {
output.debug(err?.message ?? '');
exitCode = 1;
}
}
@@ -90,8 +86,8 @@ export default async function main(client: Client): Promise<number> {
writeToConfigFile(config);
writeToAuthConfigFile(authConfig);
output.debug('Configuration has been deleted');
} catch (err: unknown) {
output.debug(errorToString(err));
} catch (err) {
output.debug(err?.message ?? '');
exitCode = 1;
}

View File

@@ -8,7 +8,6 @@ import { getPkgName } from '../util/pkg-name';
import getArgs from '../util/get-args';
import Client from '../util/client';
import { getDeployment } from '../util/get-deployment';
import { isAPIError } from '../util/errors-ts';
const help = () => {
console.log(`
@@ -126,24 +125,22 @@ export default async function main(client: Client) {
let deployment;
try {
deployment = await getDeployment(client, id);
} catch (err: unknown) {
} catch (err) {
output.stopSpinner();
if (isAPIError(err)) {
if (err.status === 404) {
output.error(
`Failed to find deployment "${id}" in ${chalk.bold(contextName)}`
);
return 1;
}
if (err.status === 403) {
output.error(
`No permission to access deployment "${id}" in ${chalk.bold(
contextName
)}`
);
return 1;
}
if (err.status === 404) {
output.error(
`Failed to find deployment "${id}" in ${chalk.bold(contextName)}`
);
return 1;
}
if (err.status === 403) {
output.error(
`No permission to access deployment "${id}" in ${chalk.bold(
contextName
)}`
);
return 1;
}
// unexpected
throw err;

View File

@@ -1,7 +1,6 @@
import chalk from 'chalk';
import ms from 'ms';
import Client from '../../util/client';
import { isAPIError } from '../../util/errors-ts';
import { getCommandName } from '../../util/pkg-name';
export default async function add(
@@ -37,12 +36,12 @@ export default async function add(
method: 'POST',
body: { name },
});
} catch (err: unknown) {
if (isAPIError(err) && err.status === 409) {
} catch (error) {
if (error.status === 409) {
// project already exists, so we can
// show a success message
} else {
throw err;
throw error;
}
}
const elapsed = ms(Date.now() - start);

View File

@@ -6,6 +6,7 @@ import getScope from '../../util/get-scope';
import handleError from '../../util/handle-error';
import logo from '../../util/output/logo';
import { getPkgName } from '../../util/pkg-name';
import validatePaths from '../../util/validate-paths';
import add from './add';
import list from './list';
import rm from './rm';
@@ -47,6 +48,7 @@ const COMMAND_CONFIG = {
ls: ['ls', 'list'],
add: ['add'],
rm: ['rm', 'remove'],
connect: ['connect'],
};
export default async function main(client: Client) {
@@ -57,6 +59,7 @@ export default async function main(client: Client) {
argv = getArgs(client.argv.slice(2), {
'--next': Number,
'-N': '--next',
'--yes': Boolean,
});
} catch (error) {
handleError(error);
@@ -72,7 +75,24 @@ export default async function main(client: Client) {
subcommand = argv._[0] || 'list';
const args = argv._.slice(1);
const { output } = client;
const { contextName } = await getScope(client);
let paths = [process.cwd()];
const pathValidation = await validatePaths(client, paths);
if (!pathValidation.valid) {
return pathValidation.exitCode;
}
let contextName = '';
try {
({ contextName } = await getScope(client));
} catch (error) {
if (error.code === 'NOT_AUTHORIZED' || error.code === 'TEAM_DELETED') {
output.error(error.message);
return 1;
}
throw error;
}
switch (subcommand) {
case 'ls':

View File

@@ -2,7 +2,6 @@ import chalk from 'chalk';
import ms from 'ms';
import Client from '../../util/client';
import { emoji, prependEmoji } from '../../util/emoji';
import { isAPIError } from '../../util/errors-ts';
import confirm from '../../util/input/confirm';
import { getCommandName } from '../../util/pkg-name';
@@ -33,8 +32,8 @@ export default async function rm(client: Client, args: string[]) {
await client.fetch(`/v2/projects/${e(name)}`, {
method: 'DELETE',
});
} catch (err: unknown) {
if (isAPIError(err) && err.status === 404) {
} catch (err) {
if (err.status === 404) {
client.output.error('No such project exists');
return 1;
}

View File

@@ -179,8 +179,6 @@ export default async function main(client: Client) {
return pullResultCode;
}
client.output.print('\n');
client.output.log('Downloading project settings');
await writeProjectSettings(cwd, project, org);
const settingsStamp = stamp();

View File

@@ -114,7 +114,18 @@ export default async function main(client: Client) {
return 1;
}
const { contextName } = await getScope(client);
let contextName: string | null = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
output.spinner(
`Fetching deployment(s) ${ids

View File

@@ -11,7 +11,6 @@ import { getPkgName, getCommandName } from '../../util/pkg-name';
import Client from '../../util/client';
import createTeam from '../../util/teams/create-team';
import patchTeam from '../../util/teams/patch-team';
import { errorToString, isError } from '../../util/is-error';
const validateSlugKeypress = (data: string, value: string) =>
// TODO: the `value` here should contain the current value + the keypress
@@ -57,8 +56,8 @@ export default async function add(client: Client): Promise<number> {
valid: team,
forceLowerCase: true,
});
} catch (err: unknown) {
if (isError(err) && err.message === 'USER_ABORT') {
} catch (err) {
if (err.message === 'USER_ABORT') {
output.log('Aborted');
return 0;
}
@@ -72,10 +71,10 @@ export default async function add(client: Client): Promise<number> {
try {
// eslint-disable-next-line no-await-in-loop
team = await createTeam(client, { slug });
} catch (err: unknown) {
} catch (err) {
output.stopSpinner();
output.print(eraseLines(2));
output.error(errorToString(err));
output.error(err.message);
}
} while (!team);
@@ -93,8 +92,8 @@ export default async function add(client: Client): Promise<number> {
label: `- ${teamNamePrefix}`,
validateKeypress: validateNameKeypress,
});
} catch (err: unknown) {
if (isError(err) && err.message === 'USER_ABORT') {
} catch (err) {
if (err.message === 'USER_ABORT') {
console.log(info('No name specified'));
return gracefulExit();
}

View File

@@ -11,8 +11,6 @@ import { getCommandName } from '../../util/pkg-name';
import { email as regexEmail } from '../../util/input/regexes';
import getTeams from '../../util/teams/get-teams';
import inviteUserToTeam from '../../util/teams/invite-user-to-team';
import { isAPIError } from '../../util/errors-ts';
import { errorToString, isError } from '../../util/is-error';
const validateEmail = (data: string) =>
regexEmail.test(data.trim()) || data.length === 0;
@@ -69,7 +67,17 @@ export default async function invite(
const currentTeam = teams.find(team => team.id === currentTeamId);
output.spinner('Fetching user information');
const user = await getUser(client);
let user;
try {
user = await getUser(client);
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
domains.push(user.email.split('@')[1]);
@@ -99,8 +107,8 @@ export default async function invite(
// eslint-disable-next-line no-await-in-loop
const res = await inviteUserToTeam(client, currentTeam.id, email);
userInfo = res.username;
} catch (err: unknown) {
if (isAPIError(err) && err.code === 'user_not_found') {
} catch (err) {
if (err.code === 'user_not_found') {
output.error(`No user exists with the email address "${email}".`);
return 1;
}
@@ -133,8 +141,8 @@ export default async function invite(
validateValue: validateEmail,
autoComplete: value => emailAutoComplete(value, currentTeam.slug),
});
} catch (err: unknown) {
if (!isError(err) || err.message !== 'USER_ABORT') {
} catch (err) {
if (err.message !== 'USER_ABORT') {
throw err;
}
}
@@ -166,7 +174,7 @@ export default async function invite(
} catch (err) {
output.stopSpinner();
process.stderr.write(eraseLines(emails.length + 2));
output.error(errorToString(err));
output.error(err.message);
hasError = true;
for (const email of emails) {
output.log(`${chalk.cyan(chars.tick)} ${sentEmailPrefix}${email}`);

View File

@@ -43,7 +43,17 @@ export default async function list(client: Client): Promise<number> {
const accountIsCurrent = !currentTeam;
output.spinner('Fetching user information');
const user = await getUser(client);
let user;
try {
user = await getUser(client);
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
if (accountIsCurrent) {
currentTeam = user.id;

View File

@@ -41,7 +41,18 @@ export default async (client: Client): Promise<number> => {
return 2;
}
const { contextName } = await getScope(client, { getTeam: false });
let contextName = null;
try {
({ contextName } = await getScope(client, { getTeam: false }));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
if (client.stdout.isTTY) {
output.log(contextName);

View File

@@ -1,12 +1,11 @@
#!/usr/bin/env node
import { isErrnoException, isError, errorToString } from './util/is-error';
try {
// Test to see if cwd has been deleted before
// importing 3rd party packages that might need cwd.
process.cwd();
} catch (err) {
if (isError(err) && err.message.includes('uv_cwd')) {
} catch (e) {
if (e && e.message && e.message.includes('uv_cwd')) {
console.error('Error! The current working directory does not exist.');
process.exit(1);
}
@@ -104,7 +103,7 @@ const main = async () => {
},
{ permissive: true }
);
} catch (err: unknown) {
} catch (err) {
handleError(err);
return 1;
}
@@ -174,7 +173,7 @@ const main = async () => {
const targetOrSubcommand = argv._[2];
// Currently no beta commands - add here as needed
const betaCommands: string[] = [];
const betaCommands: string[] = [''];
if (betaCommands.includes(targetOrSubcommand)) {
console.log(
`${chalk.grey(
@@ -202,11 +201,14 @@ const main = async () => {
// Ensure that the Vercel global configuration directory exists
try {
await mkdirp(VERCEL_DIR);
} catch (err: unknown) {
output.error(
`An unexpected error occurred while trying to create the global directory "${hp(
VERCEL_DIR
)}" ${errorToString(err)}`
} catch (err) {
console.error(
error(
`${
'An unexpected error occurred while trying to create the ' +
`global directory "${hp(VERCEL_DIR)}" `
}${err.message}`
)
);
}
@@ -215,13 +217,13 @@ const main = async () => {
try {
configExists = existsSync(VERCEL_CONFIG_PATH);
} catch (err: unknown) {
} catch (err) {
console.error(
error(
`${
'An unexpected error occurred while trying to find the ' +
`config file "${hp(VERCEL_CONFIG_PATH)}" `
}${errorToString(err)}`
}${err.message}`
)
);
@@ -239,7 +241,7 @@ const main = async () => {
`${
'An unexpected error occurred while trying to read the ' +
`config in "${hp(VERCEL_CONFIG_PATH)}" `
}${errorToString(err)}`
}${err.message}`
)
);
@@ -270,13 +272,13 @@ const main = async () => {
try {
configFiles.writeToConfigFile(config);
} catch (err: unknown) {
} catch (err) {
console.error(
error(
`${
'An unexpected error occurred while trying to write the ' +
`default config to "${hp(VERCEL_CONFIG_PATH)}" `
}${errorToString(err)}`
}${err.message}`
)
);
@@ -288,13 +290,13 @@ const main = async () => {
try {
authConfigExists = existsSync(VERCEL_AUTH_CONFIG_PATH);
} catch (err: unknown) {
} catch (err) {
console.error(
error(
`${
'An unexpected error occurred while trying to find the ' +
`auth file "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${errorToString(err)}`
}${err.message}`
)
);
@@ -315,13 +317,13 @@ const main = async () => {
if (authConfigExists) {
try {
authConfig = configFiles.readAuthConfigFile();
} catch (err: unknown) {
} catch (err) {
console.error(
error(
`${
'An unexpected error occurred while trying to read the ' +
`auth config in "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${errorToString(err)}`
}${err.message}`
)
);
@@ -343,13 +345,13 @@ const main = async () => {
try {
configFiles.writeToAuthConfigFile(authConfig);
} catch (err: unknown) {
} catch (err) {
console.error(
error(
`${
'An unexpected error occurred while trying to write the ' +
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${errorToString(err)}`
}${err.message}`
)
);
return 1;
@@ -541,8 +543,8 @@ const main = async () => {
try {
user = await getUser(client);
} catch (err: unknown) {
if (isErrnoException(err) && err.code === 'NOT_AUTHORIZED') {
} catch (err) {
if (err.code === 'NOT_AUTHORIZED') {
output.prettyError({
message: `You do not have access to the specified account`,
link: 'https://err.sh/vercel/scope-not-accessible',
@@ -562,8 +564,8 @@ const main = async () => {
try {
teams = await getTeams(client);
} catch (err: unknown) {
if (isErrnoException(err) && err.code === 'not_authorized') {
} catch (err) {
if (err.code === 'not_authorized') {
output.prettyError({
message: `You do not have access to the specified team`,
link: 'https://err.sh/vercel/scope-not-accessible',
@@ -592,8 +594,8 @@ const main = async () => {
}
}
const metric = metrics();
let exitCode;
let metric: ReturnType<typeof metrics> | undefined;
const eventCategory = 'Exit Code';
try {
@@ -630,9 +632,6 @@ const main = async () => {
case 'env':
func = require('./commands/env').default;
break;
case 'git':
func = require('./commands/git').default;
break;
case 'init':
func = require('./commands/init').default;
break;
@@ -696,14 +695,13 @@ const main = async () => {
if (shouldCollectMetrics) {
const category = 'Command Invocation';
if (!metric) metric = metrics();
metric
.timing(category, targetCommand, end, pkg.version)
.event(category, targetCommand, pkg.version)
.send();
}
} catch (err: unknown) {
if (isErrnoException(err) && err.code === 'ENOTFOUND') {
} catch (err) {
if (err.code === 'ENOTFOUND') {
// Error message will look like the following:
// "request to https://api.vercel.com/v2/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
@@ -715,16 +713,11 @@ const main = async () => {
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
);
}
if (typeof err.stack === 'string') {
output.debug(err.stack);
}
output.debug(err.stack);
return 1;
}
if (
isErrnoException(err) &&
(err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED')
) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.prettyError(err);
return 1;
}
@@ -736,10 +729,9 @@ const main = async () => {
}
if (shouldCollectMetrics) {
if (!metric) metric = metrics();
metric
.event(eventCategory, '1', pkg.version)
.exception(errorToString(err))
.exception(err.message)
.send();
}
@@ -747,24 +739,23 @@ const main = async () => {
// but instead show the message. Any error that is handled by this should
// actually be handled in the sub command instead. Please make sure
// that happens for anything that lands here. It should NOT bubble up to here.
if (isErrnoException(err)) {
if (typeof err.stack === 'string') {
output.debug(err.stack);
}
if (err.code) {
output.debug(err.stack);
output.prettyError(err);
} else {
await reportError(Sentry, client, err);
// Otherwise it is an unexpected error and we should show the trace
// and an unexpected error message
output.error(`An unexpected error occurred in ${subcommand}: ${err}`);
output.error(
`An unexpected error occurred in ${subcommand}: ${err.stack}`
);
}
return 1;
}
if (shouldCollectMetrics) {
if (!metric) metric = metrics();
metric.event(eventCategory, `${exitCode}`, pkg.version).send();
}

View File

@@ -28,7 +28,6 @@ export interface AuthConfig {
export interface GlobalConfig {
_?: string;
currentTeam?: string;
includeScheme?: string;
collectMetrics?: boolean;
api?: string;
@@ -248,40 +247,12 @@ export interface ProjectEnvVariable {
gitBranch?: string;
}
export interface DeployHook {
createdAt: number;
id: string;
name: string;
ref: string;
url: string;
}
export interface ProjectLinkData {
type: string;
repo: string;
repoId: number;
org?: string;
gitCredentialId: string;
productionBranch?: string | null;
sourceless: boolean;
createdAt: number;
updatedAt: number;
deployHooks?: DeployHook[];
}
export interface Project extends ProjectSettings {
id: string;
analytics?: {
id: string;
enabledAt?: number;
disabledAt?: number;
canceledAt?: number | null;
};
name: string;
accountId: string;
updatedAt: number;
createdAt: number;
link?: ProjectLinkData;
alias?: ProjectAliasTarget[];
latestDeployments?: Partial<Deployment>[];
}

View File

@@ -68,39 +68,37 @@ async function performCreateAlias(
body: { alias },
}
);
} catch (err: unknown) {
if (ERRORS.isAPIError(err)) {
if (err.code === 'cert_missing' || err.code === 'cert_expired') {
return new ERRORS.CertMissing(alias);
} catch (error) {
if (error.code === 'cert_missing' || error.code === 'cert_expired') {
return new ERRORS.CertMissing(alias);
}
if (error.status === 409) {
return { uid: error.uid, alias: error.alias } as AliasRecord;
}
if (error.code === 'deployment_not_found') {
return new ERRORS.DeploymentNotFound({
context: contextName,
id: deployment.uid,
});
}
if (error.code === 'gone') {
return new ERRORS.DeploymentFailedAliasImpossible();
}
if (error.code === 'invalid_alias') {
return new ERRORS.InvalidAlias(alias);
}
if (error.status === 403) {
if (error.code === 'alias_in_use') {
return new ERRORS.AliasInUse(alias);
}
if (err.status === 409) {
return { uid: err.uid, alias: err.alias } as AliasRecord;
}
if (err.code === 'deployment_not_found') {
return new ERRORS.DeploymentNotFound({
context: contextName,
id: deployment.uid,
});
}
if (err.code === 'gone') {
return new ERRORS.DeploymentFailedAliasImpossible();
}
if (err.code === 'invalid_alias') {
return new ERRORS.InvalidAlias(alias);
}
if (err.status === 403) {
if (err.code === 'alias_in_use') {
return new ERRORS.AliasInUse(alias);
}
if (err.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(alias, contextName);
}
}
if (err.status === 400) {
return new ERRORS.DeploymentNotReady({ url: deployment.url });
if (error.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(alias, contextName);
}
}
if (error.status === 400) {
return new ERRORS.DeploymentNotReady({ url: deployment.url });
}
throw err;
throw error;
}
}

View File

@@ -6,9 +6,11 @@ import { VERCEL_DIR } from '../projects/link';
import readJSONFile from '../read-json-file';
export async function initCorepack({
repoRootPath,
cwd,
rootPackageJsonPath,
}: {
repoRootPath: string;
cwd: string;
rootPackageJsonPath: string;
}): Promise<string | null> {
if (process.env.ENABLE_EXPERIMENTAL_COREPACK !== '1') {
// Since corepack is experimental, we need to exit early
@@ -16,7 +18,7 @@ export async function initCorepack({
return null;
}
const pkg = await readJSONFile<PackageJson>(
join(repoRootPath, 'package.json')
join(rootPackageJsonPath, 'package.json')
);
if (pkg instanceof CantParseJSONFile) {
console.warn(
@@ -30,13 +32,16 @@ export async function initCorepack({
console.log(
`Detected ENABLE_EXPERIMENTAL_COREPACK=1 and "${pkg.packageManager}" in package.json`
);
const corepackRootDir = join(repoRootPath, VERCEL_DIR, 'cache', 'corepack');
const corepackRootDir = join(cwd, VERCEL_DIR, 'cache', 'corepack');
const corepackHomeDir = join(corepackRootDir, 'home');
const corepackShimDir = join(corepackRootDir, 'shim');
await fs.mkdirp(corepackHomeDir);
await fs.mkdirp(corepackShimDir);
process.env.COREPACK_HOME = corepackHomeDir;
process.env.PATH = `${corepackShimDir}${delimiter}${process.env.PATH}`;
process.env.DEBUG = process.env.DEBUG
? `corepack,${process.env.DEBUG}`
: 'corepack';
const pkgManagerName = pkg.packageManager.split('@')[0];
// We must explicitly call `corepack enable npm` since `corepack enable`
// doesn't work with npm. See https://github.com/nodejs/corepack/pull/24
@@ -67,4 +72,11 @@ export function cleanupCorepack(corepackShimDir: string) {
''
);
}
if (process.env.DEBUG) {
if (process.env.DEBUG === 'corepack') {
delete process.env.DEBUG;
} else {
process.env.DEBUG = process.env.DEBUG.replace('corepack,', '');
}
}
}

View File

@@ -1,12 +0,0 @@
import frameworkList from '@vercel/frameworks';
export function sortBuilders<B extends { use: string }>(builds: B[]): B[] {
const frontendRuntimeSet = new Set(
frameworkList.map(f => f.useRuntime?.use || '@vercel/static-build')
);
const toNumber = (build: B) => (frontendRuntimeSet.has(build.use) ? 0 : 1);
return builds.sort((build1, build2) => {
return toNumber(build1) - toNumber(build2);
});
}

View File

@@ -1,14 +1,6 @@
import fs from 'fs-extra';
import mimeTypes from 'mime-types';
import {
basename,
dirname,
extname,
join,
relative,
resolve,
posix,
} from 'path';
import { basename, dirname, extname, join, relative, resolve } from 'path';
import {
Builder,
BuildResultV2,
@@ -23,47 +15,30 @@ import {
download,
EdgeFunction,
BuildResultBuildOutput,
getLambdaOptionsFromFunction,
normalizePath,
} from '@vercel/build-utils';
import pipe from 'promisepipe';
import { unzip } from './unzip';
import { VERCEL_DIR } from '../projects/link';
import { VercelConfig } from '@vercel/client';
const { normalize } = posix;
export const OUTPUT_DIR = join(VERCEL_DIR, 'output');
/**
* An entry in the "functions" object in `vercel.json`.
*/
interface FunctionConfiguration {
memory?: number;
maxDuration?: number;
}
export async function writeBuildResult(
outputDir: string,
buildResult: BuildResultV2 | BuildResultV3,
build: Builder,
builder: BuilderV2 | BuilderV3,
builderPkg: PackageJson,
vercelConfig: VercelConfig | null
cleanUrls?: boolean
) {
const { version } = builder;
if (typeof version !== 'number' || version === 2) {
return writeBuildResultV2(
outputDir,
buildResult as BuildResultV2,
vercelConfig
cleanUrls
);
} else if (version === 3) {
return writeBuildResultV3(
outputDir,
buildResult as BuildResultV3,
build,
vercelConfig
);
return writeBuildResultV3(outputDir, buildResult as BuildResultV3, build);
}
throw new Error(
`Unsupported Builder version \`${version}\` from "${builderPkg.name}"`
@@ -92,13 +67,6 @@ export interface PathOverride {
path?: string;
}
/**
* Remove duplicate slashes as well as leading/trailing slashes.
*/
function stripDuplicateSlashes(path: string): string {
return normalize(path).replace(/(^\/|\/$)/g, '');
}
/**
* Writes the output from the `build()` return value of a v2 Builder to
* the filesystem.
@@ -106,7 +74,7 @@ function stripDuplicateSlashes(path: string): string {
async function writeBuildResultV2(
outputDir: string,
buildResult: BuildResultV2,
vercelConfig: VercelConfig | null
cleanUrls?: boolean
) {
if ('buildOutputPath' in buildResult) {
await mergeBuilderOutput(outputDir, buildResult);
@@ -116,23 +84,16 @@ async function writeBuildResultV2(
const lambdas = new Map<Lambda, string>();
const overrides: Record<string, PathOverride> = {};
for (const [path, output] of Object.entries(buildResult.output)) {
const normalizedPath = stripDuplicateSlashes(path);
if (isLambda(output)) {
await writeLambda(outputDir, output, normalizedPath, undefined, lambdas);
await writeLambda(outputDir, output, path, lambdas);
} else if (isPrerender(output)) {
await writeLambda(
outputDir,
output.lambda,
normalizedPath,
undefined,
lambdas
);
await writeLambda(outputDir, output.lambda, path, lambdas);
// Write the fallback file alongside the Lambda directory
let fallback = output.fallback;
if (fallback) {
const ext = getFileExtension(fallback);
const fallbackName = `${normalizedPath}.prerender-fallback${ext}`;
const fallbackName = `${path}.prerender-fallback${ext}`;
const fallbackPath = join(outputDir, 'functions', fallbackName);
const stream = fallback.toStream();
await pipe(
@@ -148,7 +109,7 @@ async function writeBuildResultV2(
const prerenderConfigPath = join(
outputDir,
'functions',
`${normalizedPath}.prerender-config.json`
`${path}.prerender-config.json`
);
const prerenderConfig = {
...output,
@@ -157,20 +118,12 @@ async function writeBuildResultV2(
};
await fs.writeJSON(prerenderConfigPath, prerenderConfig, { spaces: 2 });
} else if (isFile(output)) {
await writeStaticFile(
outputDir,
output,
normalizedPath,
overrides,
vercelConfig?.cleanUrls
);
await writeStaticFile(outputDir, output, path, overrides, cleanUrls);
} else if (isEdgeFunction(output)) {
await writeEdgeFunction(outputDir, output, normalizedPath);
await writeEdgeFunction(outputDir, output, path);
} else {
throw new Error(
`Unsupported output type: "${
(output as any).type
}" for ${normalizedPath}`
`Unsupported output type: "${(output as any).type}" for ${path}`
);
}
}
@@ -184,28 +137,19 @@ async function writeBuildResultV2(
async function writeBuildResultV3(
outputDir: string,
buildResult: BuildResultV3,
build: Builder,
vercelConfig: VercelConfig | null
build: Builder
) {
const { output } = buildResult;
const src = build.src;
if (typeof src !== 'string') {
throw new Error(`Expected "build.src" to be a string`);
}
const functionConfiguration = vercelConfig
? await getLambdaOptionsFromFunction({
sourceFile: src,
config: vercelConfig,
})
: {};
const ext = extname(src);
const path = stripDuplicateSlashes(
build.config?.zeroConfig ? src.substring(0, src.length - ext.length) : src
);
const path = build.config?.zeroConfig
? src.substring(0, src.length - ext.length)
: src;
if (isLambda(output)) {
await writeLambda(outputDir, output, path, functionConfiguration);
await writeLambda(outputDir, output, path);
} else if (isEdgeFunction(output)) {
await writeEdgeFunction(outputDir, output, path);
} else {
@@ -291,7 +235,6 @@ async function writeEdgeFunction(
const config = {
runtime: 'edge',
...edgeFunction,
entrypoint: normalizePath(edgeFunction.entrypoint),
files: undefined,
type: undefined,
};
@@ -315,7 +258,6 @@ async function writeLambda(
outputDir: string,
lambda: Lambda,
path: string,
functionConfiguration?: FunctionConfiguration,
lambdas?: Map<Lambda, string>
) {
const dest = join(outputDir, 'functions', `${path}.func`);
@@ -350,14 +292,8 @@ async function writeLambda(
throw new Error('Malformed `Lambda` - no "files" present');
}
const memory = functionConfiguration?.memory ?? lambda.memory;
const maxDuration = functionConfiguration?.maxDuration ?? lambda.maxDuration;
const config = {
...lambda,
handler: normalizePath(lambda.handler),
memory,
maxDuration,
type: undefined,
files: undefined,
zipBuffer: undefined,

View File

@@ -15,19 +15,17 @@ export default async function createCertForCns(
try {
const certificate = await issueCert(client, cns);
return certificate;
} catch (err: unknown) {
if (ERRORS.isAPIError(err)) {
if (err.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(err.domain, context);
}
const mappedError = mapCertError(err, cns);
if (mappedError) {
return mappedError;
}
} catch (error) {
if (error.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(error.domain, context);
}
throw err;
const mappedError = mapCertError(error, cns);
if (mappedError) {
return mappedError;
}
throw error;
} finally {
output.stopSpinner();
}

View File

@@ -2,8 +2,6 @@ import { readFileSync } from 'fs';
import { resolve } from 'path';
import Client from '../client';
import { Cert } from '../../types';
import { isErrnoException } from '../is-error';
import { isAPIError } from '../errors-ts';
export default async function createCertFromFile(
client: Client,
@@ -27,15 +25,15 @@ export default async function createCertFromFile(
},
});
return certificate;
} catch (err: unknown) {
if (isErrnoException(err) && err.code === 'ENOENT') {
return new Error(`The specified file "${err.path}" doesn't exist.`);
} catch (error) {
if (error.code === 'ENOENT') {
return new Error(`The specified file "${error.path}" doesn't exist.`);
}
if (isAPIError(err) && err.status < 500) {
return err;
if (error.status < 500) {
return error;
}
throw err;
throw error;
}
}

View File

@@ -22,18 +22,16 @@ export default async function startCertOrder(
},
});
return cert;
} catch (err: unknown) {
if (ERRORS.isAPIError(err)) {
if (err.code === 'cert_order_not_found') {
return new ERRORS.CertOrderNotFound(cns);
}
const mappedError = mapCertError(err, cns);
if (mappedError) {
return mappedError;
}
} catch (error) {
if (error.code === 'cert_order_not_found') {
return new ERRORS.CertOrderNotFound(cns);
}
throw err;
const mappedError = mapCertError(error, cns);
if (mappedError) {
return mappedError;
}
throw error;
}
}

View File

@@ -5,10 +5,10 @@ import * as ERRORS from '../errors-ts';
export default async function getCertById(client: Client, id: string) {
try {
return await client.fetch<Cert>(`/v6/now/certs/${id}`);
} catch (err: unknown) {
if (ERRORS.isAPIError(err) && err.code === 'cert_not_found') {
} catch (error) {
if (error.code === 'cert_not_found') {
return new ERRORS.CertNotFound(id);
}
throw err;
throw error;
}
}

View File

@@ -17,10 +17,10 @@ export async function getCustomCertsForDomain(
`/v5/now/certs?${stringify({ domain, custom: true })}`
);
return certs;
} catch (err: unknown) {
if (ERRORS.isAPIError(err) && err.code === 'forbidden') {
} catch (error) {
if (error.code === 'forbidden') {
return new ERRORS.CertsPermissionDenied(context, domain);
}
throw err;
throw error;
}
}

View File

@@ -1,8 +1,6 @@
import retry from 'async-retry';
import { Cert } from '../../types';
import Client from '../client';
import { isAPIError } from '../errors-ts';
import { isError } from '../is-error';
// When it's a configuration error we should retry because of the DNS propagation
// otherwise we bail to handle the error in the upper level
@@ -12,15 +10,13 @@ export default async function issueCert(client: Client, cns: string[]) {
try {
return await client.fetch<Cert>('/v3/now/certs', {
method: 'POST',
body: { domains: cns },
body: { domains: cns }
});
} catch (err: unknown) {
if (isAPIError(err) && err.code === 'configuration_error') {
throw err;
} else if (isError(err)) {
bail(err);
} catch (error) {
if (error.code === 'configuration_error') {
throw error;
} else {
throw err;
bail(error);
}
}
},

View File

@@ -1,11 +1,9 @@
import * as ERRORS from '../errors-ts';
export default function mapCertError(error: ERRORS.APIError, cns?: string[]) {
export default function mapCertError(error: any, cns?: string[]) {
const errorCode: string = error.code;
if (errorCode === 'too_many_requests') {
const retryAfter =
typeof error.retryAfter === 'number' ? error.retryAfter : 0;
return new ERRORS.TooManyRequests('certificates', retryAfter);
return new ERRORS.TooManyRequests('certificates', error.retryAfter);
}
if (errorCode === 'not_found') {
return new ERRORS.DomainNotFound(error.domain);

View File

@@ -10,7 +10,6 @@ import error from '../output/error';
import highlight from '../output/highlight';
import { VercelConfig } from '../dev/types';
import { AuthConfig, GlobalConfig } from '../../types';
import { isErrnoException, isError } from '../is-error';
const VERCEL_DIR = getGlobalPathConfig();
const CONFIG_FILE_PATH = join(VERCEL_DIR, 'config.json');
@@ -26,27 +25,25 @@ export const readConfigFile = (): GlobalConfig => {
export const writeToConfigFile = (stuff: GlobalConfig): void => {
try {
return writeJSON.sync(CONFIG_FILE_PATH, stuff, { indent: 2 });
} catch (err: unknown) {
if (isErrnoException(err)) {
if (isErrnoException(err) && err.code === 'EPERM') {
console.error(
error(
`Not able to create ${highlight(
CONFIG_FILE_PATH
)} (operation not permitted).`
)
);
process.exit(1);
} else if (err.code === 'EBADF') {
console.error(
error(
`Not able to create ${highlight(
CONFIG_FILE_PATH
)} (bad file descriptor).`
)
);
process.exit(1);
}
} catch (err) {
if (err.code === 'EPERM') {
console.error(
error(
`Not able to create ${highlight(
CONFIG_FILE_PATH
)} (operation not permitted).`
)
);
process.exit(1);
} else if (err.code === 'EBADF') {
console.error(
error(
`Not able to create ${highlight(
CONFIG_FILE_PATH
)} (bad file descriptor).`
)
);
process.exit(1);
}
throw err;
@@ -68,27 +65,25 @@ export const writeToAuthConfigFile = (authConfig: AuthConfig) => {
indent: 2,
mode: 0o600,
});
} catch (err: unknown) {
if (isErrnoException(err)) {
if (err.code === 'EPERM') {
console.error(
error(
`Not able to create ${highlight(
AUTH_CONFIG_FILE_PATH
)} (operation not permitted).`
)
);
process.exit(1);
} else if (err.code === 'EBADF') {
console.error(
error(
`Not able to create ${highlight(
AUTH_CONFIG_FILE_PATH
)} (bad file descriptor).`
)
);
process.exit(1);
}
} catch (err) {
if (err.code === 'EPERM') {
console.error(
error(
`Not able to create ${highlight(
AUTH_CONFIG_FILE_PATH
)} (operation not permitted).`
)
);
process.exit(1);
} else if (err.code === 'EBADF') {
console.error(
error(
`Not able to create ${highlight(
AUTH_CONFIG_FILE_PATH
)} (bad file descriptor).`
)
);
process.exit(1);
}
throw err;
@@ -128,14 +123,12 @@ export function readLocalConfig(
if (existsSync(target)) {
config = loadJSON.sync(target);
}
} catch (err: unknown) {
if (isError(err) && err.name === 'JSONError') {
} catch (err) {
if (err.name === 'JSONError') {
console.error(error(err.message));
} else if (isErrnoException(err)) {
} else {
const code = err.code ? ` (${err.code})` : '';
console.error(error(`Failed to read config file: ${target}${code}`));
} else {
console.error(err);
}
process.exit(1);
}

View File

@@ -1,159 +0,0 @@
import fs from 'fs-extra';
import { join } from 'path';
import ini from 'ini';
import git from 'git-last-commit';
import { exec } from 'child_process';
import { GitMetadata, Project } from '../types';
import { Output } from './output';
import { errorToString } from './is-error';
export async function createGitMeta(
directory: string,
output: Output,
project?: Project | null
): Promise<GitMetadata | undefined> {
// If a Git repository is already connected via `vc git`, use that remote url
let remoteUrl;
if (project?.link) {
// in the form of org/repo
const { repo } = project.link;
const remoteUrls = await getRemoteUrls(
join(directory, '.git/config'),
output
);
if (remoteUrls) {
for (const urlValue of Object.values(remoteUrls)) {
if (urlValue.includes(repo)) {
remoteUrl = urlValue;
}
}
}
}
// If we couldn't get a remote url from the connected repo, default to the origin url
if (!remoteUrl) {
remoteUrl = await getOriginUrl(join(directory, '.git/config'), output);
}
// If we can't get the repo URL, then don't return any metadata
if (!remoteUrl) {
return;
}
const [commit, dirty] = await Promise.all([
getLastCommit(directory).catch(err => {
output.debug(
`Failed to get last commit. The directory is likely not a Git repo, there are no latest commits, or it is corrupted.\n${err}`
);
return;
}),
isDirty(directory, output),
]);
if (!commit) {
return;
}
return {
remoteUrl,
commitAuthorName: commit.author.name,
commitMessage: commit.subject,
commitRef: commit.branch,
commitSha: commit.hash,
dirty,
};
}
function getLastCommit(directory: string): Promise<git.Commit> {
return new Promise((resolve, reject) => {
git.getLastCommit(
(err, commit) => {
if (err) return reject(err);
resolve(commit);
},
{ dst: directory }
);
});
}
export function isDirty(directory: string, output: Output): Promise<boolean> {
return new Promise(resolve => {
exec('git status -s', { cwd: directory }, function (err, stdout, stderr) {
let debugMessage = `Failed to determine if Git repo has been modified:`;
if (err || stderr) {
if (err) debugMessage += `\n${err}`;
if (stderr) debugMessage += `\n${stderr.trim()}`;
output.debug(debugMessage);
return resolve(false);
}
resolve(stdout.trim().length > 0);
});
});
}
export async function parseGitConfig(configPath: string, output: Output) {
try {
return ini.parse(await fs.readFile(configPath, 'utf-8'));
} catch (err: unknown) {
output.debug(`Error while parsing repo data: ${errorToString(err)}`);
}
}
export function pluckRemoteUrls(gitConfig: {
[key: string]: any;
}): { [key: string]: string } | undefined {
let remoteUrls: { [key: string]: string } = {};
for (const key of Object.keys(gitConfig)) {
if (key.includes('remote')) {
// ex. remote "origin" — matches origin
const remoteName = key.match(/(?<=").*(?=")/g)?.[0];
const remoteUrl = gitConfig[key]?.url;
if (remoteName && remoteUrl) {
remoteUrls[remoteName] = remoteUrl;
}
}
}
if (Object.keys(remoteUrls).length === 0) {
return;
}
return remoteUrls;
}
export async function getRemoteUrls(
configPath: string,
output: Output
): Promise<{ [key: string]: string } | undefined> {
const config = await parseGitConfig(configPath, output);
if (!config) {
return;
}
const remoteUrls = pluckRemoteUrls(config);
return remoteUrls;
}
export function pluckOriginUrl(gitConfig: {
[key: string]: any;
}): string | undefined {
// Assuming "origin" is the remote url that the user would want to use
return gitConfig['remote "origin"']?.url;
}
export async function getOriginUrl(
configPath: string,
output: Output
): Promise<string | null> {
let gitConfig = await parseGitConfig(configPath, output);
if (!gitConfig) {
return null;
}
const originUrl = pluckOriginUrl(gitConfig);
if (originUrl) {
return originUrl;
}
return null;
}

View File

@@ -20,105 +20,103 @@ export default async function createDeploy(
): Promise<any | DeploymentError> {
try {
return await now.create(paths, createArgs, org, isSettingUpProject, cwd);
} catch (err: unknown) {
if (ERRORS_TS.isAPIError(err)) {
if (err.code === 'rate_limited') {
throw new ERRORS_TS.DeploymentsRateLimited(err.message);
} catch (error) {
if (error.code === 'rate_limited') {
throw new ERRORS_TS.DeploymentsRateLimited(error.message);
}
// Means that the domain used as a suffix no longer exists
if (error.code === 'domain_missing') {
throw new ERRORS_TS.DomainNotFound(error.value);
}
if (error.code === 'domain_not_found' && error.domain) {
throw new ERRORS_TS.DomainNotFound(error.domain);
}
// This error occures when a domain used in the `alias`
// is not yet verified
if (error.code === 'domain_not_verified' && error.domain) {
throw new ERRORS_TS.DomainNotVerified(error.domain);
}
// If the domain used as a suffix is not verified, we fail
if (error.code === 'domain_not_verified' && error.value) {
throw new ERRORS_TS.DomainVerificationFailed(error.value);
}
// If the domain isn't owned by the user
if (error.code === 'not_domain_owner') {
throw new ERRORS_TS.NotDomainOwner(error.message);
}
if (error.code === 'builds_rate_limited') {
throw new ERRORS_TS.BuildsRateLimited(error.message);
}
// If the user doesn't have permissions over the domain used as a suffix we fail
if (error.code === 'forbidden') {
throw new ERRORS_TS.DomainPermissionDenied(error.value, contextName);
}
if (error.code === 'bad_request' && error.keyword) {
throw new ERRORS.SchemaValidationFailed(
error.message,
error.keyword,
error.dataPath,
error.params
);
}
if (error.code === 'domain_configured') {
throw new ERRORS_TS.AliasDomainConfigured(error);
}
if (error.code === 'missing_build_script') {
throw new ERRORS_TS.MissingBuildScript(error);
}
if (error.code === 'conflicting_file_path') {
throw new ERRORS_TS.ConflictingFilePath(error);
}
if (error.code === 'conflicting_path_segment') {
throw new ERRORS_TS.ConflictingPathSegment(error);
}
// If the cert is missing we try to generate a new one and the retry
if (error.code === 'cert_missing') {
const result = await generateCertForDeploy(
client,
contextName,
error.value
);
if (result instanceof NowError) {
return result;
}
// Means that the domain used as a suffix no longer exists
if (err.code === 'domain_missing') {
throw new ERRORS_TS.DomainNotFound(err.value);
}
return createDeploy(
client,
now,
contextName,
paths,
createArgs,
org,
isSettingUpProject
);
}
if (err.code === 'domain_not_found' && err.domain) {
throw new ERRORS_TS.DomainNotFound(err.domain);
}
if (error.code === 'not_found') {
throw new ERRORS_TS.DeploymentNotFound({ context: contextName });
}
// This error occures when a domain used in the `alias`
// is not yet verified
if (err.code === 'domain_not_verified' && err.domain) {
throw new ERRORS_TS.DomainNotVerified(err.domain);
}
// If the domain used as a suffix is not verified, we fail
if (err.code === 'domain_not_verified' && err.value) {
throw new ERRORS_TS.DomainVerificationFailed(err.value);
}
// If the domain isn't owned by the user
if (err.code === 'not_domain_owner') {
throw new ERRORS_TS.NotDomainOwner(err.message);
}
if (err.code === 'builds_rate_limited') {
throw new ERRORS_TS.BuildsRateLimited(err.message);
}
// If the user doesn't have permissions over the domain used as a suffix we fail
if (err.code === 'forbidden') {
throw new ERRORS_TS.DomainPermissionDenied(err.value, contextName);
}
if (err.code === 'bad_request' && err.keyword) {
throw new ERRORS.SchemaValidationFailed(
err.message,
err.keyword,
err.dataPath,
err.params
);
}
if (err.code === 'domain_configured') {
throw new ERRORS_TS.AliasDomainConfigured(err);
}
if (err.code === 'missing_build_script') {
throw new ERRORS_TS.MissingBuildScript(err);
}
if (err.code === 'conflicting_file_path') {
throw new ERRORS_TS.ConflictingFilePath(err);
}
if (err.code === 'conflicting_path_segment') {
throw new ERRORS_TS.ConflictingPathSegment(err);
}
// If the cert is missing we try to generate a new one and the retry
if (err.code === 'cert_missing') {
const result = await generateCertForDeploy(
client,
contextName,
err.value
);
if (result instanceof NowError) {
return result;
}
return createDeploy(
client,
now,
contextName,
paths,
createArgs,
org,
isSettingUpProject
);
}
if (err.code === 'not_found') {
throw new ERRORS_TS.DeploymentNotFound({ context: contextName });
}
const certError = mapCertError(err);
if (certError) {
return certError;
}
const certError = mapCertError(error);
if (certError) {
return certError;
}
// If the error is unknown, we just throw
throw err;
throw error;
}
}

View File

@@ -0,0 +1,88 @@
import fs from 'fs-extra';
import { join } from 'path';
import ini from 'ini';
import git from 'git-last-commit';
import { exec } from 'child_process';
import { GitMetadata } from '../../types';
import { Output } from '../output';
export function isDirty(directory: string, output: Output): Promise<boolean> {
return new Promise(resolve => {
exec('git status -s', { cwd: directory }, function (err, stdout, stderr) {
let debugMessage = `Failed to determine if Git repo has been modified:`;
if (err || stderr) {
if (err) debugMessage += `\n${err}`;
if (stderr) debugMessage += `\n${stderr.trim()}`;
output.debug(debugMessage);
return resolve(false);
}
resolve(stdout.trim().length > 0);
});
});
}
function getLastCommit(directory: string): Promise<git.Commit> {
return new Promise((resolve, reject) => {
git.getLastCommit(
(err, commit) => {
if (err) return reject(err);
resolve(commit);
},
{ dst: directory }
);
});
}
export async function getRemoteUrl(
configPath: string,
output: Output
): Promise<string | null> {
let gitConfig;
try {
gitConfig = ini.parse(await fs.readFile(configPath, 'utf-8'));
} catch (error) {
output.debug(`Error while parsing repo data: ${error.message}`);
}
if (!gitConfig) {
return null;
}
const originUrl: string = gitConfig['remote "origin"']?.url;
if (originUrl) {
return originUrl;
}
return null;
}
export async function createGitMeta(
directory: string,
output: Output
): Promise<GitMetadata | undefined> {
const remoteUrl = await getRemoteUrl(join(directory, '.git/config'), output);
// If we can't get the repo URL, then don't return any metadata
if (!remoteUrl) {
return;
}
const [commit, dirty] = await Promise.all([
getLastCommit(directory).catch(err => {
output.debug(
`Failed to get last commit. The directory is likely not a Git repo, there are no latest commits, or it is corrupted.\n${err}`
);
return;
}),
isDirty(directory, output),
]);
if (!commit) {
return;
}
return {
remoteUrl,
commitAuthorName: commit.author.name,
commitMessage: commit.subject,
commitRef: commit.branch,
commitSha: commit.hash,
dirty,
};
}

View File

@@ -5,7 +5,6 @@ import {
DeploymentNotFound,
DeploymentPermissionDenied,
InvalidDeploymentId,
isAPIError,
} from '../errors-ts';
import mapCertError from '../certs/map-cert-error';
@@ -27,25 +26,23 @@ export default async function getDeploymentByIdOrHost(
)
: await getDeploymentById(client, idOrHost, apiVersion);
return deployment;
} catch (err: unknown) {
if (isAPIError(err)) {
if (err.status === 404) {
return new DeploymentNotFound({ id: idOrHost, context: contextName });
}
if (err.status === 403) {
return new DeploymentPermissionDenied(idOrHost, contextName);
}
if (err.status === 400 && err.message.includes('`id`')) {
return new InvalidDeploymentId(idOrHost);
}
const certError = mapCertError(err);
if (certError) {
return certError;
}
} catch (error) {
if (error.status === 404) {
return new DeploymentNotFound({ id: idOrHost, context: contextName });
}
if (error.status === 403) {
return new DeploymentPermissionDenied(idOrHost, contextName);
}
if (error.status === 400 && error.message.includes('`id`')) {
return new InvalidDeploymentId(idOrHost);
}
throw err;
const certError = mapCertError(error);
if (certError) {
return certError;
}
throw error;
}
}

View File

@@ -1,10 +1,7 @@
import fs from 'fs-extra';
import { join } from 'path';
import { BuildsManifest } from '../../commands/build';
export default async function getPrebuiltJson(
directory: string
): Promise<BuildsManifest | null> {
export default async function getPrebuiltJson(directory: string) {
try {
return await fs.readJSON(join(directory, '.vercel/output/builds.json'));
} catch (error) {

View File

@@ -11,11 +11,13 @@ import cliPkg from '../pkg';
import cmd from '../output/cmd';
import { Output } from '../output';
import { getDistTag } from '../get-dist-tag';
import { NoBuilderCacheError } from '../errors-ts';
import * as staticBuilder from './static-builder';
import { BuilderWithPackage } from './types';
import { isErrnoException } from '../is-error';
type CliPackageJson = typeof cliPkg;
const require_: typeof require = eval('require');
@@ -35,6 +37,8 @@ const localBuilders: { [key: string]: BuilderWithPackage } = {
'@vercel/static': createStaticBuilder('vercel'),
};
const distTag = getDistTag(cliPkg.version);
export const cacheDirPromise = prepareCacheDir();
export const builderDirPromise = prepareBuilderDir();
@@ -61,8 +65,8 @@ export async function prepareBuilderDir() {
try {
const buildersPkg = join(builderDir, 'package.json');
await writeJSON(buildersPkg, { private: true }, { flag: 'wx' });
} catch (err: unknown) {
if (!isErrnoException(err) || err.code !== 'EEXIST') {
} catch (err) {
if (err.code !== 'EEXIST') {
throw err;
}
}
@@ -98,8 +102,9 @@ function parseVersionSafe(rawSpec: string) {
export function filterPackage(
builderSpec: string,
distTag: string,
buildersPkg: PackageJson,
cliPkg: Partial<PackageJson>
cliPkg: Partial<CliPackageJson>
) {
if (builderSpec in localBuilders) return false;
const parsed = npa(builderSpec);
@@ -121,6 +126,31 @@ export function filterPackage(
return false;
}
// Skip install of already installed Runtime with tag compatible match
if (
parsed.name &&
parsed.type === 'tag' &&
parsed.fetchSpec === distTag &&
buildersPkg.dependencies
) {
const parsedInstalled = npa(
`${parsed.name}@${buildersPkg.dependencies[parsed.name]}`
);
if (parsedInstalled.type !== 'version') {
return true;
}
const semverInstalled = semver.parse(parsedInstalled.rawSpec);
if (!semverInstalled) {
return true;
}
if (semverInstalled.prerelease.length > 0) {
return semverInstalled.prerelease[0] !== distTag;
}
if (distTag === 'latest') {
return false;
}
}
return true;
}
@@ -153,7 +183,7 @@ export async function installBuilders(
// Filter out any packages that come packaged with Vercel CLI
const packagesToInstall = packages.filter(p =>
filterPackage(p, buildersPkgBefore, cliPkg)
filterPackage(p, distTag, buildersPkgBefore, cliPkg)
);
if (packagesToInstall.length === 0) {
@@ -332,12 +362,8 @@ export async function getBuilder(
builder: Object.freeze(mod),
package: Object.freeze(pkg),
};
} catch (err: unknown) {
if (
isErrnoException(err) &&
err.code === 'MODULE_NOT_FOUND' &&
!isRetry
) {
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND' && !isRetry) {
output.debug(
`Attempted to require ${requirePath}, but it is not installed`
);
@@ -366,13 +392,20 @@ export function isBundledBuilder(
return false;
}
const inCliDependencyList = !!dependencies[parsed.name];
const inScope = parsed.scope === '@vercel';
const isVersionedReference = ['tag', 'version', 'range'].includes(
parsed.type
);
const bundledVersion = dependencies[parsed.name];
if (bundledVersion) {
if (parsed.type === 'tag') {
if (parsed.fetchSpec === 'canary') {
return bundledVersion.includes('canary');
} else if (parsed.fetchSpec === 'latest') {
return !bundledVersion.includes('canary');
}
} else if (parsed.type === 'version') {
return parsed.fetchSpec === bundledVersion;
}
}
return inCliDependencyList && inScope && isVersionedReference;
return false;
}
function getPackageName(

View File

@@ -4,7 +4,7 @@ import ms from 'ms';
import bytes from 'bytes';
import { delimiter, dirname, join } from 'path';
import { fork, ChildProcess } from 'child_process';
import { createFunction } from '@vercel/fun';
import { createFunction } from '@zeit/fun';
import {
Builder,
BuildOptions,

View File

@@ -94,12 +94,6 @@ import { ProjectEnvVariable, ProjectSettings } from '../../types';
import exposeSystemEnvs from './expose-system-envs';
import { treeKill } from '../tree-kill';
import { nodeHeadersToFetchHeaders } from './headers';
import {
errorToString,
isErrnoException,
isError,
isSpawnError,
} from '../is-error';
const frontendRuntimeSet = new Set(
frameworkList.map(f => f.useRuntime?.use || '@vercel/static-build')
@@ -346,8 +340,8 @@ export default class DevServer {
}
fileChanged(name, changed, removed);
this.output.debug(`File created: ${name}`);
} catch (err: unknown) {
if (isErrnoException(err) && err.code === 'ENOENT') {
} catch (err) {
if (err.code === 'ENOENT') {
this.output.debug(`File created, but has since been deleted: ${name}`);
fileRemoved(name, this.files, changed, removed);
} else {
@@ -381,8 +375,8 @@ export default class DevServer {
this.files[name] = await FileFsRef.fromFsPath({ fsPath });
fileChanged(name, changed, removed);
this.output.debug(`File modified: ${name}`);
} catch (err: unknown) {
if (isErrnoException(err) && err.code === 'ENOENT') {
} catch (err) {
if (err.code === 'ENOENT') {
this.output.debug(`File modified, but has since been deleted: ${name}`);
fileRemoved(name, this.files, changed, removed);
} else {
@@ -513,8 +507,8 @@ export default class DevServer {
this.output.debug(`Using local env: ${filePath}`);
env = parseDotenv(dotenv);
env = this.injectSystemValuesInDotenv(env);
} catch (err: unknown) {
if (!isErrnoException(err) || err.code !== 'ENOENT') {
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}
@@ -564,8 +558,9 @@ export default class DevServer {
]);
await this.validateVercelConfig(vercelConfig);
const { error: routeError, routes: maybeRoutes } =
getTransformedRoutes(vercelConfig);
const { error: routeError, routes: maybeRoutes } = getTransformedRoutes({
nowConfig: vercelConfig,
});
if (routeError) {
this.output.prettyError(routeError);
await this.exit();
@@ -725,15 +720,13 @@ export default class DevServer {
const parsed: WithFileNameSymbol<T> = JSON.parse(raw);
parsed[fileNameSymbol] = rel;
return parsed;
} catch (err: unknown) {
if (isError(err)) {
if (isErrnoException(err) && err.code === 'ENOENT') {
this.output.debug(`No \`${rel}\` file present`);
} else if (err.name === 'SyntaxError') {
this.output.warn(
`There is a syntax error in the \`${rel}\` file: ${err.message}`
);
}
} catch (err) {
if (err.code === 'ENOENT') {
this.output.debug(`No \`${rel}\` file present`);
} else if (err.name === 'SyntaxError') {
this.output.warn(
`There is a syntax error in the \`${rel}\` file: ${err.message}`
);
} else {
throw err;
}
@@ -859,26 +852,22 @@ export default class DevServer {
while (typeof address !== 'string') {
try {
address = await listen(this.server, ...listenSpec);
} catch (err: unknown) {
if (isErrnoException(err)) {
this.output.debug(`Got listen error: ${err.code}`);
if (err.code === 'EADDRINUSE') {
if (typeof listenSpec[0] === 'number') {
// Increase port and try again
this.output.note(
`Requested port ${chalk.yellow(
String(listenSpec[0])
)} is already in use`
);
listenSpec[0]++;
} else {
this.output.error(
`Requested socket ${chalk.cyan(
listenSpec[0]
)} is already in use`
);
process.exit(1);
}
} catch (err) {
this.output.debug(`Got listen error: ${err.code}`);
if (err.code === 'EADDRINUSE') {
if (typeof listenSpec[0] === 'number') {
// Increase port and try again
this.output.note(
`Requested port ${chalk.yellow(
String(listenSpec[0])
)} is already in use`
);
listenSpec[0]++;
} else {
this.output.error(
`Requested socket ${chalk.cyan(listenSpec[0])} is already in use`
);
process.exit(1);
}
} else {
throw err;
@@ -1040,8 +1029,12 @@ export default class DevServer {
try {
await Promise.all(ops);
} catch (err: unknown) {
if (isErrnoException(err) && err.code === 'ERR_SERVER_NOT_RUNNING') {
} catch (err) {
// Node 8 doesn't have a code for that error
if (
err.code === 'ERR_SERVER_NOT_RUNNING' ||
err.message === 'Not running'
) {
process.exit(exitCode || 0);
} else {
throw err;
@@ -1311,16 +1304,13 @@ export default class DevServer {
try {
const vercelConfig = await this.getVercelConfig();
await this.serveProjectAsNowV2(req, res, requestId, vercelConfig);
} catch (err: unknown) {
} catch (err) {
console.error(err);
if (isError(err) && typeof err.stack === 'string') {
this.output.debug(err.stack);
}
this.output.debug(err.stack);
if (!res.finished) {
res.statusCode = 500;
res.end(errorToString(err));
res.end(err.message);
}
}
};
@@ -1544,16 +1534,16 @@ export default class DevServer {
);
}
}
} catch (err: unknown) {
} 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 (isSpawnError(err) && err.code === 'ENOENT') {
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 as any).link = 'https://vercel.link/command-not-found';
)}\nPlease ensure that ${cmd(err.path)} is properly installed`;
err.link = 'https://vercel.link/command-not-found';
}
await this.sendError(
@@ -1856,16 +1846,16 @@ export default class DevServer {
buildEnv: { ...envConfigs.buildEnv },
},
});
} catch (err: unknown) {
} 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 (isSpawnError(err) && err.code === 'ENOENT') {
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 as any).link = 'https://vercel.link/command-not-found';
)}\nPlease ensure that ${cmd(err.path)} is properly installed`;
err.link = 'https://vercel.link/command-not-found';
}
this.output.prettyError(err);

View File

@@ -1,6 +1,6 @@
import http from 'http';
import { ChildProcess } from 'child_process';
import { Lambda as FunLambda } from '@vercel/fun';
import { Lambda as FunLambda } from '@zeit/fun';
import {
Builder as BuildConfig,
BuildOptions,

View File

@@ -5,7 +5,6 @@ import {
DNSInvalidPort,
DNSInvalidType,
DNSConflictingRecord,
isAPIError,
} from '../errors-ts';
import { DNSRecordData } from '../../types';
@@ -27,34 +26,32 @@ export default async function addDNSRecord(
}
);
return record;
} catch (err: unknown) {
if (isAPIError(err)) {
if (err.status === 400 && err.code === 'invalid_type') {
return new DNSInvalidType(recordData.type);
}
if (err.status === 400 && err.message.includes('port')) {
return new DNSInvalidPort();
}
if (err.status === 400) {
return err;
}
if (err.status === 403) {
return new DNSPermissionDenied(domain);
}
if (err.status === 404) {
return new DomainNotFound(domain);
}
if (err.status === 409) {
const { oldId = '' } = err;
return new DNSConflictingRecord(oldId);
}
} catch (error) {
if (error.status === 400 && error.code === 'invalid_type') {
return new DNSInvalidType(recordData.type);
}
throw err;
if (error.status === 400 && error.message.includes('port')) {
return new DNSInvalidPort();
}
if (error.status === 400) {
return error;
}
if (error.status === 403) {
return new DNSPermissionDenied(domain);
}
if (error.status === 404) {
return new DomainNotFound(domain);
}
if (error.status === 409) {
const { oldId = '' } = error;
return new DNSConflictingRecord(oldId);
}
throw error;
}
}

View File

@@ -1,5 +1,5 @@
import { DNSRecord, PaginationOptions } from '../../types';
import { DomainNotFound, isAPIError } from '../errors-ts';
import { DomainNotFound } from '../errors-ts';
import { Output } from '../output';
import Client from '../client';
@@ -27,10 +27,10 @@ export default async function getDomainDNSRecords(
const data = await client.fetch<Response>(url);
return data;
} catch (err: unknown) {
if (isAPIError(err) && err.code === 'not_found') {
} catch (error) {
if (error.code === 'not_found') {
return new DomainNotFound(domain);
}
throw err;
throw error;
}
}

View File

@@ -2,7 +2,7 @@ import chalk from 'chalk';
import { readFileSync } from 'fs';
import { resolve } from 'path';
import { Response } from 'node-fetch';
import { DomainNotFound, InvalidDomain, isAPIError } from '../errors-ts';
import { DomainNotFound, InvalidDomain } from '../errors-ts';
import Client from '../client';
type JSONResponse = {
@@ -33,17 +33,15 @@ export default async function importZonefile(
const { recordIds } = (await res.json()) as JSONResponse;
return recordIds;
} catch (err: unknown) {
if (isAPIError(err)) {
if (err.code === 'not_found') {
return new DomainNotFound(domain, contextName);
}
if (err.code === 'invalid_domain') {
return new InvalidDomain(domain);
}
} catch (error) {
if (error.code === 'not_found') {
return new DomainNotFound(domain, contextName);
}
throw err;
if (error.code === 'invalid_domain') {
return new InvalidDomain(domain);
}
throw error;
}
}

View File

@@ -1,6 +1,6 @@
import chalk from 'chalk';
import retry from 'async-retry';
import { DomainAlreadyExists, InvalidDomain, isAPIError } from '../errors-ts';
import { DomainAlreadyExists, InvalidDomain } from '../errors-ts';
import { Domain } from '../../types';
import Client from '../client';
@@ -29,18 +29,16 @@ async function performAddRequest(client: Client, domainName: string) {
method: 'POST',
});
return domain;
} catch (err: unknown) {
if (isAPIError(err)) {
if (err.code === 'invalid_name') {
return new InvalidDomain(domainName);
}
if (err.code === 'domain_already_exists') {
return new DomainAlreadyExists(domainName);
}
} catch (error) {
if (error.code === 'invalid_name') {
return new InvalidDomain(domainName);
}
throw err;
if (error.code === 'domain_already_exists') {
return new DomainAlreadyExists(domainName);
}
throw error;
}
},
{ retries: 5, maxTimeout: 8000 }

View File

@@ -1,11 +1,7 @@
import chalk from 'chalk';
import Client from '../client';
import { Domain } from '../../types';
import {
DomainPermissionDenied,
DomainNotFound,
isAPIError,
} from '../errors-ts';
import { DomainPermissionDenied, DomainNotFound } from '../errors-ts';
type Response = {
domain: Domain;
@@ -29,17 +25,15 @@ export default async function getDomainByName(
`/v4/domains/${encodeURIComponent(domainName)}`
);
return domain;
} catch (err: unknown) {
if (isAPIError(err)) {
if (err.status === 404) {
return new DomainNotFound(domainName, contextName);
}
if (err.status === 403) {
return new DomainPermissionDenied(domainName, contextName);
}
} catch (error) {
if (error.status === 404) {
return new DomainNotFound(domainName, contextName);
}
throw err;
if (error.status === 403) {
return new DomainPermissionDenied(domainName, contextName);
}
throw error;
}
}

View File

@@ -1,6 +1,5 @@
import Client from '../client';
import { DomainConfig } from '../../types';
import { isAPIError } from '../errors-ts';
export async function getDomainConfig(client: Client, domainName: string) {
try {
@@ -9,11 +8,11 @@ export async function getDomainConfig(client: Client, domainName: string) {
);
return config;
} catch (err: unknown) {
if (isAPIError(err) && err.status < 500) {
return err;
} catch (error) {
if (error.status < 500) {
return error;
}
throw err;
throw error;
}
}

View File

@@ -1,5 +1,5 @@
import { stringify } from 'querystring';
import { isAPIError, UnsupportedTLD } from '../errors-ts';
import { UnsupportedTLD } from '../errors-ts';
import Client from '../client';
type Response = {
@@ -15,17 +15,15 @@ export default async function getDomainPrice(
try {
const querystr = type ? stringify({ name, type }) : stringify({ name });
return await client.fetch<Response>(`/v3/domains/price?${querystr}`);
} catch (err: unknown) {
if (isAPIError(err)) {
if (err.code === 'unsupported_tld') {
return new UnsupportedTLD(name);
}
if (err.status < 500) {
return err;
}
} catch (error) {
if (error.code === 'unsupported_tld') {
return new UnsupportedTLD(name);
}
throw err;
if (error.status < 500) {
return error;
}
throw error;
}
}

View File

@@ -1,7 +1,6 @@
import chalk from 'chalk';
import Client from '../client';
import { Domain } from '../../types';
import { isAPIError } from '../errors-ts';
type Response = {
domain: Domain;
@@ -21,11 +20,11 @@ export async function getDomain(
);
return domain;
} catch (err: unknown) {
if (isAPIError(err) && err.status < 500) {
return err;
} catch (error) {
if (error.status < 500) {
return error;
}
throw err;
throw error;
}
}

View File

@@ -20,27 +20,25 @@ export default async function moveOutDomain(
method: 'PATCH',
}
);
} catch (err: unknown) {
if (ERRORS.isAPIError(err)) {
if (err.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(name, contextName);
}
if (err.code === 'not_found') {
return new ERRORS.DomainNotFound(name);
}
if (err.code === 'invalid_move_destination') {
return new ERRORS.InvalidMoveDestination(destination);
}
if (err.code === 'domain_move_conflict') {
const { pendingAsyncPurchase, resolvable, suffix, message } = err;
return new ERRORS.DomainMoveConflict({
message,
pendingAsyncPurchase,
resolvable,
suffix,
});
}
} catch (error) {
if (error.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(name, contextName);
}
throw err;
if (error.code === 'not_found') {
return new ERRORS.DomainNotFound(name);
}
if (error.code === 'invalid_move_destination') {
return new ERRORS.InvalidMoveDestination(destination);
}
if (error.code === 'domain_move_conflict') {
const { pendingAsyncPurchase, resolvable, suffix, message } = error;
return new ERRORS.DomainMoveConflict({
message,
pendingAsyncPurchase,
resolvable,
suffix,
});
}
throw error;
}
}

View File

@@ -20,30 +20,28 @@ export default async function purchaseDomain(
body: { name, expectedPrice, renew },
method: 'POST',
});
} catch (err: unknown) {
if (ERRORS.isAPIError(err)) {
if (err.code === 'invalid_domain') {
return new ERRORS.InvalidDomain(name);
}
if (err.code === 'not_available') {
return new ERRORS.DomainNotAvailable(name);
}
if (err.code === 'service_unavailabe') {
return new ERRORS.DomainServiceNotAvailable(name);
}
if (err.code === 'unexpected_error') {
return new ERRORS.UnexpectedDomainPurchaseError(name);
}
if (err.code === 'source_not_found') {
return new ERRORS.SourceNotFound();
}
if (err.code === 'payment_error') {
return new ERRORS.DomainPaymentError();
}
if (err.code === 'unsupported_tld') {
return new ERRORS.UnsupportedTLD(name);
}
} catch (error) {
if (error.code === 'invalid_domain') {
return new ERRORS.InvalidDomain(name);
}
throw err;
if (error.code === 'not_available') {
return new ERRORS.DomainNotAvailable(name);
}
if (error.code === 'service_unavailabe') {
return new ERRORS.DomainServiceNotAvailable(name);
}
if (error.code === 'unexpected_error') {
return new ERRORS.UnexpectedDomainPurchaseError(name);
}
if (error.code === 'source_not_found') {
return new ERRORS.SourceNotFound();
}
if (error.code === 'payment_error') {
return new ERRORS.DomainPaymentError();
}
if (error.code === 'unsupported_tld') {
return new ERRORS.UnsupportedTLD(name);
}
throw error;
}
}

View File

@@ -10,26 +10,24 @@ export default async function removeDomainByName(
return await now.fetch(`/v3/domains/${encodeURIComponent(domain)}`, {
method: 'DELETE',
});
} catch (err: unknown) {
if (ERRORS.isAPIError(err)) {
if (err.code === 'not_found') {
return new ERRORS.DomainNotFound(domain);
}
if (err.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(domain, contextName);
}
if (err.code === 'domain_removal_conflict') {
return new ERRORS.DomainRemovalConflict({
aliases: err.aliases,
certs: err.certs,
message: err.message,
pendingAsyncPurchase: err.pendingAsyncPurchase,
resolvable: err.resolvable,
suffix: err.suffix,
transferring: err.transferring,
});
}
} catch (error) {
if (error.code === 'not_found') {
return new ERRORS.DomainNotFound(domain);
}
throw err;
if (error.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(domain, contextName);
}
if (error.code === 'domain_removal_conflict') {
return new ERRORS.DomainRemovalConflict({
aliases: error.aliases,
certs: error.certs,
message: error.message,
pendingAsyncPurchase: error.pendingAsyncPurchase,
resolvable: error.resolvable,
suffix: error.suffix,
transferring: error.transferring,
});
}
throw error;
}
}

View File

@@ -14,27 +14,25 @@ export default async function setCustomSuffix(
suffix,
},
});
} catch (err: unknown) {
if (ERRORS.isAPIError(err)) {
if (err.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(domain, contextName);
}
if (err.code === 'domain_external') {
return new ERRORS.DomainExternal(domain);
}
if (err.code === 'domain_invalid') {
return new ERRORS.InvalidDomain(domain);
}
if (err.code === 'domain_not_found') {
return new ERRORS.DomainNotFound(domain);
}
if (err.code === 'domain_not_verified') {
return new ERRORS.DomainNotVerified(domain);
}
if (err.code === 'domain_permission_denied') {
return new ERRORS.DomainPermissionDenied(domain, contextName);
}
} catch (error) {
if (error.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(domain, contextName);
}
throw err;
if (error.code === 'domain_external') {
return new ERRORS.DomainExternal(domain);
}
if (error.code === 'domain_invalid') {
return new ERRORS.InvalidDomain(domain);
}
if (error.code === 'domain_not_found') {
return new ERRORS.DomainNotFound(domain);
}
if (error.code === 'domain_not_verified') {
return new ERRORS.DomainNotVerified(domain);
}
if (error.code === 'domain_permission_denied') {
return new ERRORS.DomainPermissionDenied(domain, contextName);
}
throw error;
}
}

View File

@@ -17,27 +17,25 @@ export default async function transferInDomain(
body: { method: 'transfer-in', name, authCode, expectedPrice },
method: 'POST',
});
} catch (err: unknown) {
if (ERRORS.isAPIError(err)) {
if (err.code === 'invalid_name') {
return new ERRORS.InvalidDomain(name);
}
if (err.code === 'domain_already_exists') {
return new ERRORS.DomainNotAvailable(name);
}
if (err.code === 'not_transferable') {
return new ERRORS.DomainNotTransferable(name);
}
if (err.code === 'invalid_auth_code') {
return new ERRORS.InvalidTransferAuthCode(name, authCode);
}
if (err.code === 'source_not_found') {
return new ERRORS.SourceNotFound();
}
if (err.code === 'registration_failed') {
return new ERRORS.DomainRegistrationFailed(name, err.message);
}
} catch (error) {
if (error.code === 'invalid_name') {
return new ERRORS.InvalidDomain(name);
}
throw err;
if (error.code === 'domain_already_exists') {
return new ERRORS.DomainNotAvailable(name);
}
if (error.code === 'not_transferable') {
return new ERRORS.DomainNotTransferable(name);
}
if (error.code === 'invalid_auth_code') {
return new ERRORS.InvalidTransferAuthCode(name, authCode);
}
if (error.code === 'source_not_found') {
return new ERRORS.SourceNotFound();
}
if (error.code === 'registration_failed') {
return new ERRORS.DomainRegistrationFailed(name, error.message);
}
throw error;
}
}

View File

@@ -29,6 +29,6 @@ export default async function addEnvRecord(
const url = `/v8/projects/${projectId}/env`;
await client.fetch(url, {
method: 'POST',
body,
body: JSON.stringify(body),
});
}

View File

@@ -1,80 +0,0 @@
import { Output } from '../output';
import { Dictionary } from '@vercel/client';
import { readFile } from 'fs-extra';
import { parseEnv } from '../parse-env';
import chalk from 'chalk';
export async function createEnvObject(
envPath: string,
output: Output
): Promise<Dictionary<string | undefined> | undefined> {
// Originally authored by Tyler Waters under MIT License: https://github.com/tswaters/env-file-parser/blob/f17c009b39da599380e069ee72728d1cafdb56b8/lib/parse.js
// https://github.com/tswaters/env-file-parser/blob/f17c009b39da599380e069ee72728d1cafdb56b8/LICENSE
const envArr = (await readFile(envPath, 'utf-8'))
// remove double quotes
.replace(/"/g, '')
// split on new line
.split(/\r?\n|\r/)
// filter comments
.filter(line => /^[^#]/.test(line))
// needs equal sign
.filter(line => /=/i.test(line));
const parsedEnv = parseEnv(envArr);
if (Object.keys(parsedEnv).length === 0) {
output.debug('Failed to parse env file.');
return;
}
return parsedEnv;
}
function findChanges(
oldEnv: Dictionary<string | undefined>,
newEnv: Dictionary<string | undefined>
): {
added: string[];
changed: string[];
removed: string[];
} {
const added = [];
const changed = [];
for (const key of Object.keys(newEnv)) {
if (oldEnv[key] === undefined) {
added.push(key);
} else if (oldEnv[key] !== newEnv[key]) {
changed.push(key);
}
delete oldEnv[key];
}
const removed = Object.keys(oldEnv);
return {
added,
changed,
removed,
};
}
export function buildDeltaString(
oldEnv: Dictionary<string | undefined>,
newEnv: Dictionary<string | undefined>
): string {
const { added, changed, removed } = findChanges(oldEnv, newEnv);
let deltaString = '';
deltaString += chalk.green(addDeltaSection('+', added));
deltaString += chalk.yellow(addDeltaSection('~', changed));
deltaString += chalk.red(addDeltaSection('-', removed));
return deltaString ? chalk.gray('Changes:\n') + deltaString : deltaString;
}
function addDeltaSection(prefix: string, arr: string[]): string {
return (
arr
.sort()
.map(item => `${prefix} ${item}`)
.join('\n') + '\n'
);
}

View File

@@ -1,5 +1,3 @@
import { isErrnoException } from '../is-error';
const knownErrorsCodes = new Set([
'PAYMENT_REQUIRED',
'BAD_REQUEST',
@@ -9,8 +7,7 @@ const knownErrorsCodes = new Set([
'ENV_SHOULD_BE_A_SECRET',
]);
export function isKnownError(error: unknown) {
const code = isErrnoException(error) ? error.code : null;
if (!code) return false;
export function isKnownError(error: { code?: string }) {
const code = error && typeof error.code === 'string' ? error.code : '';
return knownErrorsCodes.has(code.toUpperCase());
}

View File

@@ -87,18 +87,3 @@ export async function responseErrorMessage(
return `${message} (${res.status})`;
}
/**
* Returns a new Object with enumberable properties that match
* the provided `err` instance, for use with `JSON.stringify()`.
*/
export function toEnumerableError<E extends Partial<Error>>(err: E) {
const enumerable: {
[K in keyof E]?: E[K];
} = {};
enumerable.name = err.name;
for (const key of Object.getOwnPropertyNames(err) as (keyof E)[]) {
enumerable[key] = err[key];
}
return enumerable;
}

View File

@@ -5,7 +5,6 @@ import { NowError } from './now-error';
import code from './output/code';
import { getCommandName } from './pkg-name';
import chalk from 'chalk';
import { isError } from './is-error';
/**
* This error is thrown when there is an API error with a payload. The error
@@ -46,10 +45,6 @@ export class APIError extends Error {
}
}
export function isAPIError(v: unknown): v is APIError {
return isError(v) && 'status' in v;
}
/**
* When you're fetching information for the current team but the client can't
* retrieve information. This means that the team was probably deleted or the

View File

@@ -81,21 +81,21 @@ async function printEvents(
return;
}
poller = startPoller();
} catch (err: unknown) {
} catch (error) {
stream.end();
finish(err);
finish(error);
}
}, 5000);
})();
}
let finishCalled = false;
function finish(err?: unknown) {
function finish(error?: Error) {
if (finishCalled) return;
finishCalled = true;
clearTimeout(poller);
if (err) {
reject(err);
if (error) {
reject(error);
} else {
resolve();
}

View File

@@ -10,7 +10,6 @@ import humanizePath from './humanize-path';
import readJSONFile from './read-json-file';
import { VercelConfig } from './dev/types';
import { Output } from './output';
import { isErrnoException } from './is-error';
let config: VercelConfig;
@@ -26,8 +25,8 @@ export default async function getConfig(
let localPath: string;
try {
localPath = process.cwd();
} catch (err: unknown) {
if (isErrnoException(err) && err.code === 'ENOENT') {
} catch (err) {
if (err.code === 'ENOENT') {
return new WorkingDirectoryDoesNotExist();
}
throw err;

View File

@@ -7,7 +7,6 @@ import {
Secret,
} from '../types';
import getEnvRecords, { EnvRecordsSource } from './env/get-env-records';
import { isAPIError } from './errors-ts';
export default async function getDecryptedEnvRecords(
output: Output,
@@ -40,12 +39,12 @@ export default async function getDecryptedEnvRecords(
);
return { id, type, key, value: secret.value, found: true };
} catch (err: unknown) {
if (isAPIError(err) && err.status === 404) {
} catch (error) {
if (error && error.status === 404) {
return { id, type, key, value: '', found: false };
}
throw err;
throw error;
}
}

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