mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 12:57:46 +00:00
Compare commits
79 Commits
vercel@27.
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8c7db59cf | ||
|
|
57b230e25f | ||
|
|
ab3fb25790 | ||
|
|
88d98f7497 | ||
|
|
90c1895949 | ||
|
|
46a1f3670b | ||
|
|
4b025fee92 | ||
|
|
dc8293dc13 | ||
|
|
78883dea23 | ||
|
|
b5b792e42f | ||
|
|
8993a3c4af | ||
|
|
57241aad81 | ||
|
|
4773ff5efd | ||
|
|
d8c7308eb6 | ||
|
|
5df1c89138 | ||
|
|
f5d879143c | ||
|
|
9a55809515 | ||
|
|
56adf15823 | ||
|
|
1acab3d06c | ||
|
|
081b38466b | ||
|
|
c397fd1856 | ||
|
|
afd303b94a | ||
|
|
b12387034a | ||
|
|
5af65d5a24 | ||
|
|
1ee9a96a62 | ||
|
|
76130faf26 | ||
|
|
fb3601d178 | ||
|
|
aebfb6812d | ||
|
|
73999e7253 | ||
|
|
989dad5570 | ||
|
|
68c2dea601 | ||
|
|
63f2da2f68 | ||
|
|
57e5f81361 | ||
|
|
fd5e440533 | ||
|
|
2a45805b26 | ||
|
|
5523383e50 | ||
|
|
0ecbb24cab | ||
|
|
922223bd19 | ||
|
|
0ad7fd34f4 | ||
|
|
3d3774ee7e | ||
|
|
50f8eec7cb | ||
|
|
45374e2f90 | ||
|
|
fd9142b6f3 | ||
|
|
8cf67b549b | ||
|
|
5dc6f48e44 | ||
|
|
66c8544e8f | ||
|
|
0140db38fa | ||
|
|
e5421c27e8 | ||
|
|
5afc527233 | ||
|
|
de9518b010 | ||
|
|
c322d1dbba | ||
|
|
18c19ead76 | ||
|
|
9d80c27382 | ||
|
|
bef1aec766 | ||
|
|
4f4a42813f | ||
|
|
181a492d91 | ||
|
|
1be7a80bb8 | ||
|
|
0428d4744e | ||
|
|
2a929a4bb9 | ||
|
|
accd308dc5 | ||
|
|
e2d4efab08 | ||
|
|
7e0dd6f808 | ||
|
|
8971e02e49 | ||
|
|
10c91c8579 | ||
|
|
bfdbe58675 | ||
|
|
7bdaf107b7 | ||
|
|
8de100f0e1 | ||
|
|
38a6785859 | ||
|
|
c67d1a8525 | ||
|
|
c5a7c574a2 | ||
|
|
d2f8d178f7 | ||
|
|
f9a747764c | ||
|
|
27d80f13cd | ||
|
|
8c668c925d | ||
|
|
4b1b33c143 | ||
|
|
a8d4147554 | ||
|
|
09339f494d | ||
|
|
ee4d772ae9 | ||
|
|
61e8103404 |
1
.github/CONTRIBUTING.md
vendored
1
.github/CONTRIBUTING.md
vendored
@@ -12,6 +12,7 @@ To get started, execute the following:
|
|||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/vercel/vercel
|
git clone https://github.com/vercel/vercel
|
||||||
|
cd vercel
|
||||||
yarn install
|
yarn install
|
||||||
yarn bootstrap
|
yarn bootstrap
|
||||||
yarn build
|
yarn build
|
||||||
|
|||||||
10
.github/workflows/test.yml
vendored
10
.github/workflows/test.yml
vendored
@@ -17,6 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
tests: ${{ steps['set-tests'].outputs['tests'] }}
|
tests: ${{ steps['set-tests'].outputs['tests'] }}
|
||||||
|
dplUrl: ${{ steps.waitForTarball.outputs.url }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- run: git --version
|
- run: git --version
|
||||||
@@ -32,6 +33,12 @@ jobs:
|
|||||||
echo "Files to test:"
|
echo "Files to test:"
|
||||||
echo "$TESTS_ARRAY"
|
echo "$TESTS_ARRAY"
|
||||||
echo "::set-output name=tests::$TESTS_ARRAY"
|
echo "::set-output name=tests::$TESTS_ARRAY"
|
||||||
|
- uses: patrickedqvist/wait-for-vercel-preview@ae34b392ef30297f2b672f9afb3c329bde9bd487
|
||||||
|
id: waitForTarball
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
max_timeout: 360
|
||||||
|
check_interval: 5
|
||||||
|
|
||||||
test:
|
test:
|
||||||
timeout-minutes: 120
|
timeout-minutes: 120
|
||||||
@@ -69,13 +76,14 @@ jobs:
|
|||||||
- run: yarn install --network-timeout 1000000
|
- run: yarn install --network-timeout 1000000
|
||||||
|
|
||||||
- name: Build ${{matrix.packageName}} and all its dependencies
|
- name: Build ${{matrix.packageName}} and all its dependencies
|
||||||
run: yarn turbo run build --cache-dir=".turbo" --scope=${{matrix.packageName}} --include-dependencies --no-deps
|
run: node_modules/.bin/turbo run build --cache-dir=".turbo" --scope=${{matrix.packageName}} --include-dependencies --no-deps
|
||||||
env:
|
env:
|
||||||
FORCE_COLOR: '1'
|
FORCE_COLOR: '1'
|
||||||
- name: Test ${{matrix.packageName}}
|
- name: Test ${{matrix.packageName}}
|
||||||
run: node_modules/.bin/turbo run test --cache-dir=".turbo" --scope=${{matrix.packageName}} --no-deps -- ${{ join(matrix.testPaths, ' ') }}
|
run: node_modules/.bin/turbo run test --cache-dir=".turbo" --scope=${{matrix.packageName}} --no-deps -- ${{ join(matrix.testPaths, ' ') }}
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
|
VERCEL_CLI_VERSION: ${{ needs.setup.outputs.dplUrl }}/tarballs/vercel.tgz
|
||||||
VERCEL_TEAM_TOKEN: ${{ secrets.VERCEL_TEAM_TOKEN }}
|
VERCEL_TEAM_TOKEN: ${{ secrets.VERCEL_TEAM_TOKEN }}
|
||||||
VERCEL_REGISTRATION_URL: ${{ secrets.VERCEL_REGISTRATION_URL }}
|
VERCEL_REGISTRATION_URL: ${{ secrets.VERCEL_REGISTRATION_URL }}
|
||||||
FORCE_COLOR: '1'
|
FORCE_COLOR: '1'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# https://prettier.io/docs/en/ignore.html
|
# https://prettier.io/docs/en/ignore.html
|
||||||
|
|
||||||
# ignore this file with an intentional syntax error
|
# ignore these files with an intentional syntax error
|
||||||
packages/cli/test/dev/fixtures/edge-function-error/api/edge-error-syntax.js
|
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
|
||||||
|
|||||||
@@ -5,13 +5,12 @@
|
|||||||
"description": "API for the vercel/vercel repo",
|
"description": "API for the vercel/vercel repo",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"//TODO": "We should add this pkg to yarn workspaces",
|
"//TODO": "We should add this pkg to yarn workspaces"
|
||||||
"vercel-build": "cd .. && yarn install && yarn vercel-build"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/node": "5.11.1",
|
"@sentry/node": "5.11.1",
|
||||||
"got": "10.2.1",
|
"got": "10.2.1",
|
||||||
"node-fetch": "2.6.1",
|
"node-fetch": "2.6.7",
|
||||||
"parse-github-url": "1.0.2",
|
"parse-github-url": "1.0.2",
|
||||||
"tar-fs": "2.0.0",
|
"tar-fs": "2.0.0",
|
||||||
"unzip-stream": "0.3.0"
|
"unzip-stream": "0.3.0"
|
||||||
|
|||||||
@@ -26,12 +26,12 @@
|
|||||||
"jest": "28.0.2",
|
"jest": "28.0.2",
|
||||||
"json5": "2.1.1",
|
"json5": "2.1.1",
|
||||||
"lint-staged": "9.2.5",
|
"lint-staged": "9.2.5",
|
||||||
"node-fetch": "2.6.1",
|
"node-fetch": "2.6.7",
|
||||||
"npm-package-arg": "6.1.0",
|
"npm-package-arg": "6.1.0",
|
||||||
"prettier": "2.6.2",
|
"prettier": "2.6.2",
|
||||||
"ts-eager": "2.0.2",
|
"ts-eager": "2.0.2",
|
||||||
"ts-jest": "28.0.5",
|
"ts-jest": "28.0.5",
|
||||||
"turbo": "1.3.1"
|
"turbo": "1.3.2-canary.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lerna": "lerna",
|
"lerna": "lerna",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/build-utils",
|
"name": "@vercel/build-utils",
|
||||||
"version": "5.0.1",
|
"version": "5.0.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.js",
|
"types": "./dist/index.d.js",
|
||||||
@@ -35,7 +35,6 @@
|
|||||||
"aggregate-error": "3.0.1",
|
"aggregate-error": "3.0.1",
|
||||||
"async-retry": "1.2.3",
|
"async-retry": "1.2.3",
|
||||||
"async-sema": "2.1.4",
|
"async-sema": "2.1.4",
|
||||||
"boxen": "4.2.0",
|
|
||||||
"cross-spawn": "6.0.5",
|
"cross-spawn": "6.0.5",
|
||||||
"end-of-stream": "1.4.1",
|
"end-of-stream": "1.4.1",
|
||||||
"fs-extra": "10.0.0",
|
"fs-extra": "10.0.0",
|
||||||
@@ -44,7 +43,7 @@
|
|||||||
"js-yaml": "3.13.1",
|
"js-yaml": "3.13.1",
|
||||||
"minimatch": "3.0.4",
|
"minimatch": "3.0.4",
|
||||||
"multistream": "2.1.1",
|
"multistream": "2.1.1",
|
||||||
"node-fetch": "2.6.1",
|
"node-fetch": "2.6.7",
|
||||||
"semver": "6.1.1",
|
"semver": "6.1.1",
|
||||||
"typescript": "4.3.4",
|
"typescript": "4.3.4",
|
||||||
"yazl": "2.5.1"
|
"yazl": "2.5.1"
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ export class EdgeFunction {
|
|||||||
*/
|
*/
|
||||||
envVarsInUse?: string[];
|
envVarsInUse?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extra binary files to be included in the edge function
|
||||||
|
*/
|
||||||
|
assets?: { name: string; path: string }[];
|
||||||
|
|
||||||
constructor(params: Omit<EdgeFunction, 'type'>) {
|
constructor(params: Omit<EdgeFunction, 'type'>) {
|
||||||
this.type = 'EdgeFunction';
|
this.type = 'EdgeFunction';
|
||||||
this.name = params.name;
|
this.name = params.name;
|
||||||
@@ -40,5 +45,6 @@ export class EdgeFunction {
|
|||||||
this.entrypoint = params.entrypoint;
|
this.entrypoint = params.entrypoint;
|
||||||
this.files = params.files;
|
this.files = params.files;
|
||||||
this.envVarsInUse = params.envVarsInUse;
|
this.envVarsInUse = params.envVarsInUse;
|
||||||
|
this.assets = params.assets;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ function getHint(isAuto = false) {
|
|||||||
: `Please set "engines": { "node": "${range}" } in your \`package.json\` file to use Node.js ${major}.`;
|
: `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() {
|
export function getLatestNodeVersion() {
|
||||||
return allOptions[0];
|
return allOptions[0];
|
||||||
}
|
}
|
||||||
@@ -75,7 +72,7 @@ export async function getSupportedNodeVersion(
|
|||||||
throw new NowBuildError({
|
throw new NowBuildError({
|
||||||
code: 'BUILD_UTILS_NODE_VERSION_DISCONTINUED',
|
code: 'BUILD_UTILS_NODE_VERSION_DISCONTINUED',
|
||||||
link: 'http://vercel.link/node-version',
|
link: 'http://vercel.link/node-version',
|
||||||
message: `${intro} ${getHint(isAuto)} ${upstreamProvider}`,
|
message: `${intro} ${getHint(isAuto)}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,9 +83,9 @@ export async function getSupportedNodeVersion(
|
|||||||
console.warn(
|
console.warn(
|
||||||
`Error: Node.js version ${
|
`Error: Node.js version ${
|
||||||
selection.range
|
selection.range
|
||||||
} is deprecated. Deployments created on or after ${d} will fail to build. ${getHint(
|
} has reached End-of-Life. Deployments created on or after ${d} will fail to build. ${getHint(
|
||||||
isAuto
|
isAuto
|
||||||
)} ${upstreamProvider}`
|
)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -305,67 +305,46 @@ export async function scanParentDirs(
|
|||||||
): Promise<ScanParentDirsResult> {
|
): Promise<ScanParentDirsResult> {
|
||||||
assert(path.isAbsolute(destPath));
|
assert(path.isAbsolute(destPath));
|
||||||
|
|
||||||
let cliType: CliType = 'yarn';
|
const pkgJsonPath = await walkParentDirs({
|
||||||
let packageJson: PackageJson | undefined;
|
base: '/',
|
||||||
let packageJsonPath: string | undefined;
|
start: destPath,
|
||||||
let currentDestPath = 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 lockfileVersion: number | undefined;
|
||||||
|
let cliType: CliType = 'yarn';
|
||||||
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
const [hasYarnLock, packageLockJson, pnpmLockYaml] = await Promise.all([
|
||||||
while (true) {
|
Boolean(yarnLockPath),
|
||||||
packageJsonPath = path.join(currentDestPath, 'package.json');
|
npmLockPath
|
||||||
// eslint-disable-next-line no-await-in-loop
|
? readConfigFile<{ lockfileVersion: number }>(npmLockPath)
|
||||||
if (await fs.pathExists(packageJsonPath)) {
|
: null,
|
||||||
// Only read the contents of the *first* `package.json` file found,
|
pnpmLockPath
|
||||||
// since that's the one related to this installation.
|
? readConfigFile<{ lockfileVersion: number }>(pnpmLockPath)
|
||||||
if (readPackageJson && !packageJson) {
|
: null,
|
||||||
// eslint-disable-next-line no-await-in-loop
|
]);
|
||||||
packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// Priority order is Yarn > pnpm > npm
|
||||||
const [packageLockJson, hasYarnLock, pnpmLockYaml] = await Promise.all([
|
if (hasYarnLock) {
|
||||||
fs
|
cliType = 'yarn';
|
||||||
.readJson(path.join(currentDestPath, 'package-lock.json'))
|
} else if (pnpmLockYaml) {
|
||||||
.catch(error => {
|
cliType = 'pnpm';
|
||||||
// If the file doesn't exist, fail gracefully otherwise error
|
// just ensure that it is read as a number and not a string
|
||||||
if (error.code === 'ENOENT') {
|
lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
|
||||||
return null;
|
} else if (packageLockJson) {
|
||||||
}
|
cliType = 'npm';
|
||||||
throw error;
|
lockfileVersion = packageLockJson.lockfileVersion;
|
||||||
}),
|
|
||||||
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 };
|
return { cliType, packageJson, lockfileVersion, packageJsonPath };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,11 +366,48 @@ export async function walkParentDirs({
|
|||||||
}
|
}
|
||||||
|
|
||||||
parent = path.dirname(current);
|
parent = path.dirname(current);
|
||||||
|
|
||||||
|
if (parent === current) {
|
||||||
|
// Reached root directory of the filesystem
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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> {
|
function isSet<T>(v: any): v is Set<T> {
|
||||||
return v?.constructor?.name === 'Set';
|
return v?.constructor?.name === 'Set';
|
||||||
}
|
}
|
||||||
|
|||||||
20
packages/build-utils/test/integration.test.ts
vendored
20
packages/build-utils/test/integration.test.ts
vendored
@@ -1,22 +1,12 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import {
|
import {
|
||||||
packAndDeploy,
|
|
||||||
testDeployment,
|
testDeployment,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
} from '../../../test/lib/deployment/test-deployment';
|
} from '../../../test/lib/deployment/test-deployment';
|
||||||
|
|
||||||
jest.setTimeout(4 * 60 * 1000);
|
jest.setTimeout(4 * 60 * 1000);
|
||||||
|
|
||||||
const builderUrl = '@canary';
|
|
||||||
let buildUtilsUrl: string;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
const buildUtilsPath = path.resolve(__dirname, '..');
|
|
||||||
buildUtilsUrl = await packAndDeploy(buildUtilsPath);
|
|
||||||
console.log('buildUtilsUrl', buildUtilsUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||||
|
|
||||||
// Fixtures that have separate tests and should be skipped in the loop
|
// Fixtures that have separate tests and should be skipped in the loop
|
||||||
@@ -42,10 +32,7 @@ for (const fixture of fs.readdirSync(fixturesPath)) {
|
|||||||
// eslint-disable-next-line no-loop-func
|
// eslint-disable-next-line no-loop-func
|
||||||
it(`Should build "${fixture}"`, async () => {
|
it(`Should build "${fixture}"`, async () => {
|
||||||
await expect(
|
await expect(
|
||||||
testDeployment(
|
testDeployment(path.join(fixturesPath, fixture))
|
||||||
{ builderUrl, buildUtilsUrl },
|
|
||||||
path.join(fixturesPath, fixture)
|
|
||||||
)
|
|
||||||
).resolves.toBeDefined();
|
).resolves.toBeDefined();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -68,10 +55,7 @@ for (const builder of buildersToTestWith) {
|
|||||||
// eslint-disable-next-line no-loop-func
|
// eslint-disable-next-line no-loop-func
|
||||||
it(`Should build "${builder}/${fixture}"`, async () => {
|
it(`Should build "${builder}/${fixture}"`, async () => {
|
||||||
await expect(
|
await expect(
|
||||||
testDeployment(
|
testDeployment(path.join(fixturesPath2, fixture))
|
||||||
{ builderUrl, buildUtilsUrl },
|
|
||||||
path.join(fixturesPath2, fixture)
|
|
||||||
)
|
|
||||||
).resolves.toBeDefined();
|
).resolves.toBeDefined();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
92
packages/build-utils/test/unit.test.ts
vendored
92
packages/build-utils/test/unit.test.ts
vendored
@@ -1,6 +1,7 @@
|
|||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs, { readlink } from 'fs-extra';
|
import fs, { readlink } from 'fs-extra';
|
||||||
|
import retry from 'async-retry';
|
||||||
import { strict as assert, strictEqual } from 'assert';
|
import { strict as assert, strictEqual } from 'assert';
|
||||||
import { createZip } from '../src/lambda';
|
import { createZip } from '../src/lambda';
|
||||||
import { getSupportedNodeVersion } from '../src/fs/node-version';
|
import { getSupportedNodeVersion } from '../src/fs/node-version';
|
||||||
@@ -18,7 +19,7 @@ import {
|
|||||||
Meta,
|
Meta,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
|
|
||||||
jest.setTimeout(7 * 1000);
|
jest.setTimeout(10 * 1000);
|
||||||
|
|
||||||
async function expectBuilderError(promise: Promise<any>, pattern: string) {
|
async function expectBuilderError(promise: Promise<any>, pattern: string) {
|
||||||
let result;
|
let result;
|
||||||
@@ -386,10 +387,10 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
|
|||||||
12
|
12
|
||||||
);
|
);
|
||||||
expect(warningMessages).toStrictEqual([
|
expect(warningMessages).toStrictEqual([
|
||||||
'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 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 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 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 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 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 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).',
|
'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.',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
global.Date.now = realDateNow;
|
global.Date.now = realDateNow;
|
||||||
@@ -453,6 +454,7 @@ it('should return lockfileVersion 2 with npm7', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('npm');
|
expect(result.cliType).toEqual('npm');
|
||||||
expect(result.lockfileVersion).toEqual(2);
|
expect(result.lockfileVersion).toEqual(2);
|
||||||
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return lockfileVersion with yarn', async () => {
|
it('should not return lockfileVersion with yarn', async () => {
|
||||||
@@ -460,6 +462,7 @@ it('should not return lockfileVersion with yarn', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('yarn');
|
expect(result.cliType).toEqual('yarn');
|
||||||
expect(result.lockfileVersion).toEqual(undefined);
|
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 () => {
|
it('should return lockfileVersion 1 with older versions of npm', async () => {
|
||||||
@@ -467,6 +470,7 @@ it('should return lockfileVersion 1 with older versions of npm', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('npm');
|
expect(result.cliType).toEqual('npm');
|
||||||
expect(result.lockfileVersion).toEqual(1);
|
expect(result.lockfileVersion).toEqual(1);
|
||||||
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect npm Workspaces', async () => {
|
it('should detect npm Workspaces', async () => {
|
||||||
@@ -474,48 +478,88 @@ it('should detect npm Workspaces', async () => {
|
|||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('npm');
|
expect(result.cliType).toEqual('npm');
|
||||||
expect(result.lockfileVersion).toEqual(2);
|
expect(result.lockfileVersion).toEqual(2);
|
||||||
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect pnpm', async () => {
|
it('should detect pnpm without workspace', async () => {
|
||||||
const fixture = path.join(__dirname, 'fixtures', '22-pnpm');
|
const fixture = path.join(__dirname, 'fixtures', '22-pnpm');
|
||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('pnpm');
|
expect(result.cliType).toEqual('pnpm');
|
||||||
expect(result.lockfileVersion).toEqual(5.3);
|
expect(result.lockfileVersion).toEqual(5.3);
|
||||||
|
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect pnpm Workspaces', async () => {
|
it('should detect pnpm with workspaces', async () => {
|
||||||
const fixture = path.join(__dirname, 'fixtures', '23-pnpm-workspaces/a');
|
const fixture = path.join(__dirname, 'fixtures', '23-pnpm-workspaces/c');
|
||||||
const result = await scanParentDirs(fixture);
|
const result = await scanParentDirs(fixture);
|
||||||
expect(result.cliType).toEqual('pnpm');
|
expect(result.cliType).toEqual('pnpm');
|
||||||
expect(result.lockfileVersion).toEqual(5.3);
|
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 () => {
|
it('should only invoke `runNpmInstall()` once per `package.json` file (serial)', async () => {
|
||||||
const meta: Meta = {};
|
const meta: Meta = {};
|
||||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
||||||
const apiDir = path.join(fixture, 'api');
|
const apiDir = path.join(fixture, 'api');
|
||||||
const run1 = await runNpmInstall(apiDir, [], undefined, meta);
|
const retryOpts = { maxRetryTime: 1000 };
|
||||||
expect(run1).toEqual(true);
|
let run1, run2, run3;
|
||||||
expect(
|
await retry(async () => {
|
||||||
(meta.runNpmInstallSet as Set<string>).has(
|
run1 = await runNpmInstall(apiDir, [], undefined, meta);
|
||||||
path.join(fixture, 'package.json')
|
expect(run1).toEqual(true);
|
||||||
)
|
expect(
|
||||||
).toEqual(true);
|
(meta.runNpmInstallSet as Set<string>).has(
|
||||||
const run2 = await runNpmInstall(apiDir, [], undefined, meta);
|
path.join(fixture, 'package.json')
|
||||||
expect(run2).toEqual(false);
|
)
|
||||||
const run3 = await runNpmInstall(fixture, [], undefined, meta);
|
).toEqual(true);
|
||||||
expect(run3).toEqual(false);
|
}, 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);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only invoke `runNpmInstall()` once per `package.json` file (parallel)', async () => {
|
it('should only invoke `runNpmInstall()` once per `package.json` file (parallel)', async () => {
|
||||||
const meta: Meta = {};
|
const meta: Meta = {};
|
||||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
||||||
const apiDir = path.join(fixture, 'api');
|
const apiDir = path.join(fixture, 'api');
|
||||||
const [run1, run2, run3] = await Promise.all([
|
let results: [boolean, boolean, boolean] | undefined;
|
||||||
runNpmInstall(apiDir, [], undefined, meta),
|
await retry(
|
||||||
runNpmInstall(apiDir, [], undefined, meta),
|
async () => {
|
||||||
runNpmInstall(fixture, [], undefined, meta),
|
results = await Promise.all([
|
||||||
]);
|
runNpmInstall(apiDir, [], undefined, meta),
|
||||||
|
runNpmInstall(apiDir, [], undefined, meta),
|
||||||
|
runNpmInstall(fixture, [], undefined, meta),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
{ maxRetryTime: 3000 }
|
||||||
|
);
|
||||||
|
const [run1, run2, run3] = results || [];
|
||||||
expect(run1).toEqual(true);
|
expect(run1).toEqual(true);
|
||||||
expect(run2).toEqual(false);
|
expect(run2).toEqual(false);
|
||||||
expect(run3).toEqual(false);
|
expect(run3).toEqual(false);
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ At this point you can make modifications to the CLI source code and test them ou
|
|||||||
cd packages/cli
|
cd packages/cli
|
||||||
```
|
```
|
||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx ts-eager src
|
yarn dev deploy
|
||||||
npx ts-eager src login
|
yarn dev whoami
|
||||||
npx ts-eager src switch --debug
|
yarn dev login
|
||||||
npx ts-eager src dev
|
yarn dev switch --debug
|
||||||
```
|
```
|
||||||
|
|
||||||
When you are satisfied with your changes, make a commit and create a pull request!
|
When you are satisfied with your changes, make a commit and create a pull request!
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vercel",
|
"name": "vercel",
|
||||||
"version": "27.0.2",
|
"version": "27.3.2",
|
||||||
"preferGlobal": true,
|
"preferGlobal": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "The command-line interface for Vercel",
|
"description": "The command-line interface for Vercel",
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
"test-integration-dev": "yarn test test/dev/",
|
"test-integration-dev": "yarn test test/dev/",
|
||||||
"prepublishOnly": "yarn build",
|
"prepublishOnly": "yarn build",
|
||||||
"coverage": "codecov",
|
"coverage": "codecov",
|
||||||
"build": "node -r ts-eager/register ./scripts/build.ts",
|
"build": "ts-node ./scripts/build.ts",
|
||||||
"build-dev": "node -r ts-eager/register ./scripts/build.ts --dev"
|
"dev": "ts-node ./src/index.ts"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"vc": "./dist/index.js",
|
"vc": "./dist/index.js",
|
||||||
@@ -42,16 +42,16 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/build-utils": "5.0.1",
|
"@vercel/build-utils": "5.0.7",
|
||||||
"@vercel/go": "2.0.5",
|
"@vercel/go": "2.0.11",
|
||||||
"@vercel/hydrogen": "0.0.2",
|
"@vercel/hydrogen": "0.0.8",
|
||||||
"@vercel/next": "3.1.4",
|
"@vercel/next": "3.1.11",
|
||||||
"@vercel/node": "2.4.2",
|
"@vercel/node": "2.5.2",
|
||||||
"@vercel/python": "3.0.5",
|
"@vercel/python": "3.1.3",
|
||||||
"@vercel/redwood": "1.0.6",
|
"@vercel/redwood": "1.0.12",
|
||||||
"@vercel/remix": "1.0.7",
|
"@vercel/remix": "1.0.13",
|
||||||
"@vercel/ruby": "1.3.13",
|
"@vercel/ruby": "1.3.19",
|
||||||
"@vercel/static-build": "1.0.5",
|
"@vercel/static-build": "1.0.12",
|
||||||
"update-notifier": "5.1.0"
|
"update-notifier": "5.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
"@next/env": "11.1.2",
|
"@next/env": "11.1.2",
|
||||||
"@sentry/node": "5.5.0",
|
"@sentry/node": "5.5.0",
|
||||||
"@sindresorhus/slugify": "0.11.0",
|
"@sindresorhus/slugify": "0.11.0",
|
||||||
|
"@swc/core": "1.2.218",
|
||||||
"@tootallnate/once": "1.1.2",
|
"@tootallnate/once": "1.1.2",
|
||||||
"@types/ansi-escapes": "3.0.0",
|
"@types/ansi-escapes": "3.0.0",
|
||||||
"@types/ansi-regex": "4.0.0",
|
"@types/ansi-regex": "4.0.0",
|
||||||
@@ -96,11 +97,11 @@
|
|||||||
"@types/which": "1.3.2",
|
"@types/which": "1.3.2",
|
||||||
"@types/write-json-file": "2.2.1",
|
"@types/write-json-file": "2.2.1",
|
||||||
"@types/yauzl-promise": "2.1.0",
|
"@types/yauzl-promise": "2.1.0",
|
||||||
"@vercel/client": "12.1.0",
|
"@vercel/client": "12.1.6",
|
||||||
"@vercel/frameworks": "1.1.0",
|
"@vercel/frameworks": "1.1.1",
|
||||||
"@vercel/fs-detectors": "1.0.2",
|
"@vercel/fs-detectors": "2.0.2",
|
||||||
|
"@vercel/fun": "1.0.4",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"@zeit/fun": "0.11.2",
|
|
||||||
"@zeit/source-map-support": "0.6.2",
|
"@zeit/source-map-support": "0.6.2",
|
||||||
"ajv": "6.12.2",
|
"ajv": "6.12.2",
|
||||||
"alpha-sort": "2.0.1",
|
"alpha-sort": "2.0.1",
|
||||||
@@ -111,6 +112,7 @@
|
|||||||
"async-retry": "1.1.3",
|
"async-retry": "1.1.3",
|
||||||
"async-sema": "2.1.4",
|
"async-sema": "2.1.4",
|
||||||
"ava": "2.2.0",
|
"ava": "2.2.0",
|
||||||
|
"boxen": "4.2.0",
|
||||||
"bytes": "3.0.0",
|
"bytes": "3.0.0",
|
||||||
"chalk": "4.1.0",
|
"chalk": "4.1.0",
|
||||||
"chance": "1.1.7",
|
"chance": "1.1.7",
|
||||||
@@ -147,7 +149,7 @@
|
|||||||
"minimatch": "3.0.4",
|
"minimatch": "3.0.4",
|
||||||
"mri": "1.1.5",
|
"mri": "1.1.5",
|
||||||
"ms": "2.1.2",
|
"ms": "2.1.2",
|
||||||
"node-fetch": "2.6.1",
|
"node-fetch": "2.6.7",
|
||||||
"npm-package-arg": "6.1.0",
|
"npm-package-arg": "6.1.0",
|
||||||
"open": "8.4.0",
|
"open": "8.4.0",
|
||||||
"ora": "3.4.0",
|
"ora": "3.4.0",
|
||||||
@@ -169,8 +171,8 @@
|
|||||||
"title": "3.4.1",
|
"title": "3.4.1",
|
||||||
"tmp-promise": "1.0.3",
|
"tmp-promise": "1.0.3",
|
||||||
"tree-kill": "1.2.2",
|
"tree-kill": "1.2.2",
|
||||||
"ts-node": "8.3.0",
|
"ts-node": "10.9.1",
|
||||||
"typescript": "4.3.4",
|
"typescript": "4.7.4",
|
||||||
"universal-analytics": "0.4.20",
|
"universal-analytics": "0.4.20",
|
||||||
"utility-types": "2.1.0",
|
"utility-types": "2.1.0",
|
||||||
"which": "2.0.2",
|
"which": "2.0.2",
|
||||||
|
|||||||
@@ -27,40 +27,38 @@ function envToString(key: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const isDev = process.argv[2] === '--dev';
|
// Read the secrets from GitHub Actions and generate a file.
|
||||||
|
// During local development, these secrets will be empty.
|
||||||
|
await createConstants();
|
||||||
|
|
||||||
if (!isDev) {
|
// `vercel dev` uses chokidar to watch the filesystem, but opts-out of the
|
||||||
// Read the secrets from GitHub Actions and generate a file.
|
// `fsevents` feature using `useFsEvents: false`, so delete the module here so
|
||||||
// During local development, these secrets will be empty.
|
// that it is not compiled by ncc, which makes the npm package size larger
|
||||||
await createConstants();
|
// than necessary.
|
||||||
|
await remove(join(dirRoot, '../../node_modules/fsevents'));
|
||||||
|
|
||||||
// `vercel dev` uses chokidar to watch the filesystem, but opts-out of the
|
// Compile the `doT.js` template files for `vercel dev`
|
||||||
// `fsevents` feature using `useFsEvents: false`, so delete the module here so
|
console.log();
|
||||||
// that it is not compiled by ncc, which makes the npm package size larger
|
await execa(process.execPath, [join(__dirname, 'compile-templates.js')], {
|
||||||
// than necessary.
|
stdio: 'inherit',
|
||||||
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
|
// Do the initial `ncc` build
|
||||||
console.log();
|
console.log();
|
||||||
const args = ['ncc', 'build', '--external', 'update-notifier'];
|
const args = [
|
||||||
if (isDev) {
|
'ncc',
|
||||||
args.push('--source-map');
|
'build',
|
||||||
}
|
'--external',
|
||||||
args.push('src/index.ts');
|
'update-notifier',
|
||||||
|
'src/index.ts',
|
||||||
|
];
|
||||||
await execa('yarn', args, { stdio: 'inherit', cwd: dirRoot });
|
await execa('yarn', args, { stdio: 'inherit', cwd: dirRoot });
|
||||||
|
|
||||||
// `ncc` has some issues with `@zeit/fun`'s runtime files:
|
// `ncc` has some issues with `@vercel/fun`'s runtime files:
|
||||||
// - Executable bits on the `bootstrap` files appear to be lost:
|
// - Executable bits on the `bootstrap` files appear to be lost:
|
||||||
// https://github.com/zeit/ncc/pull/182
|
// https://github.com/vercel/ncc/pull/182
|
||||||
// - The `bootstrap.js` asset does not get copied into the output dir:
|
// - The `bootstrap.js` asset does not get copied into the output dir:
|
||||||
// https://github.com/zeit/ncc/issues/278
|
// https://github.com/vercel/ncc/issues/278
|
||||||
//
|
//
|
||||||
// Aside from those issues, all the same files from the `runtimes` directory
|
// Aside from those issues, all the same files from the `runtimes` directory
|
||||||
// should be copied into the output runtimes dir, specifically the `index.js`
|
// should be copied into the output runtimes dir, specifically the `index.js`
|
||||||
@@ -70,7 +68,7 @@ async function main() {
|
|||||||
// with `fun`'s cache invalidation mechanism and they need to be shasum'd.
|
// with `fun`'s cache invalidation mechanism and they need to be shasum'd.
|
||||||
const runtimes = join(
|
const runtimes = join(
|
||||||
dirRoot,
|
dirRoot,
|
||||||
'../../node_modules/@zeit/fun/dist/src/runtimes'
|
'../../node_modules/@vercel/fun/dist/src/runtimes'
|
||||||
);
|
);
|
||||||
await cpy('**/*', join(distRoot, 'runtimes'), {
|
await cpy('**/*', join(distRoot, 'runtimes'), {
|
||||||
parents: true,
|
parents: true,
|
||||||
@@ -79,6 +77,7 @@ async function main() {
|
|||||||
|
|
||||||
// Band-aid to bundle stuff that `ncc` neglects to bundle
|
// 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/projects/VERCEL_DIR_README.txt'), distRoot);
|
||||||
|
await cpy(join(dirRoot, 'src/util/dev/builder-worker.js'), distRoot);
|
||||||
|
|
||||||
console.log('Finished building Vercel CLI');
|
console.log('Finished building Vercel CLI');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,19 +22,7 @@ export default async function ls(
|
|||||||
) {
|
) {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
const { '--next': nextTimestamp } = opts;
|
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)) {
|
if (typeof nextTimestamp !== undefined && Number.isNaN(nextTimestamp)) {
|
||||||
output.error('Please provide a number for flag --next');
|
output.error('Please provide a number for flag --next');
|
||||||
|
|||||||
@@ -23,19 +23,7 @@ export default async function rm(
|
|||||||
args: string[]
|
args: string[]
|
||||||
) {
|
) {
|
||||||
const { output } = client;
|
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;
|
const [aliasOrId] = args;
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ import { isValidName } from '../../util/is-valid-name';
|
|||||||
import handleCertError from '../../util/certs/handle-cert-error';
|
import handleCertError from '../../util/certs/handle-cert-error';
|
||||||
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
|
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
|
||||||
import link from '../../util/output/link';
|
import link from '../../util/output/link';
|
||||||
import { User } from '../../types';
|
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
import toHost from '../../util/to-host';
|
import toHost from '../../util/to-host';
|
||||||
import { VercelConfig } from '../../util/dev/types';
|
import type { VercelConfig } from '@vercel/client';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
'--debug': boolean;
|
'--debug': boolean;
|
||||||
@@ -30,23 +29,9 @@ export default async function set(
|
|||||||
opts: Partial<Options>,
|
opts: Partial<Options>,
|
||||||
args: string[]
|
args: string[]
|
||||||
) {
|
) {
|
||||||
const { output, localConfig } = client;
|
|
||||||
|
|
||||||
const setStamp = stamp();
|
const setStamp = stamp();
|
||||||
|
const { output, localConfig } = client;
|
||||||
let user: User;
|
const { contextName, user } = await getScope(client);
|
||||||
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 there are more than two args we have to error
|
||||||
if (args.length > 2) {
|
if (args.length > 2) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
MergeRoutesProps,
|
MergeRoutesProps,
|
||||||
Route,
|
Route,
|
||||||
} from '@vercel/routing-utils';
|
} from '@vercel/routing-utils';
|
||||||
import { VercelConfig } from '@vercel/client';
|
import type { VercelConfig } from '@vercel/client';
|
||||||
|
|
||||||
import pull from './pull';
|
import pull from './pull';
|
||||||
import { staticFiles as getFiles } from '../util/get-files';
|
import { staticFiles as getFiles } from '../util/get-files';
|
||||||
@@ -36,7 +36,10 @@ import * as cli from '../util/pkg-name';
|
|||||||
import cliPkg from '../util/pkg';
|
import cliPkg from '../util/pkg';
|
||||||
import readJSONFile from '../util/read-json-file';
|
import readJSONFile from '../util/read-json-file';
|
||||||
import { CantParseJSONFile } from '../util/errors-ts';
|
import { CantParseJSONFile } from '../util/errors-ts';
|
||||||
import { readProjectSettings } from '../util/projects/project-settings';
|
import {
|
||||||
|
ProjectLinkAndSettings,
|
||||||
|
readProjectSettings,
|
||||||
|
} from '../util/projects/project-settings';
|
||||||
import { VERCEL_DIR } from '../util/projects/link';
|
import { VERCEL_DIR } from '../util/projects/link';
|
||||||
import confirm from '../util/input/confirm';
|
import confirm from '../util/input/confirm';
|
||||||
import { emoji, prependEmoji } from '../util/emoji';
|
import { emoji, prependEmoji } from '../util/emoji';
|
||||||
@@ -46,11 +49,31 @@ import {
|
|||||||
PathOverride,
|
PathOverride,
|
||||||
writeBuildResult,
|
writeBuildResult,
|
||||||
} from '../util/build/write-build-result';
|
} from '../util/build/write-build-result';
|
||||||
import { importBuilders, BuilderWithPkg } from '../util/build/import-builders';
|
import { importBuilders } from '../util/build/import-builders';
|
||||||
import { initCorepack, cleanupCorepack } from '../util/build/corepack';
|
import { initCorepack, cleanupCorepack } from '../util/build/corepack';
|
||||||
|
import { sortBuilders } from '../util/build/sort-builders';
|
||||||
|
import { toEnumerableError } from '../util/error';
|
||||||
|
|
||||||
type BuildResult = BuildResultV2 | BuildResultV3;
|
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 = () => {
|
const help = () => {
|
||||||
return console.log(`
|
return console.log(`
|
||||||
${chalk.bold(`${cli.logo} ${cli.name} build`)}
|
${chalk.bold(`${cli.logo} ${cli.name} build`)}
|
||||||
@@ -166,21 +189,83 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
project = await readProjectSettings(join(cwd, VERCEL_DIR));
|
project = await readProjectSettings(join(cwd, VERCEL_DIR));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: load env vars from the API, fall back to local files if that fails
|
// Delete output directory from potential previous build
|
||||||
|
const outputDir = argv['--output']
|
||||||
|
? resolve(argv['--output'])
|
||||||
|
: join(cwd, OUTPUT_DIR);
|
||||||
|
await fs.remove(outputDir);
|
||||||
|
|
||||||
const envPath = await checkExists([
|
const buildsJson: BuildsManifest = {
|
||||||
join(cwd, VERCEL_DIR, `.env.${target}.local`),
|
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
|
||||||
join(cwd, `.env`),
|
target,
|
||||||
]);
|
argv: process.argv,
|
||||||
if (envPath) {
|
};
|
||||||
dotenv.config({ path: envPath, debug: client.output.isDebugEnabled() });
|
|
||||||
output.log(`Loaded env from "${relative(cwd, envPath)}"`);
|
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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Some build processes use these env vars to platform detect Vercel
|
/**
|
||||||
process.env.VERCEL = '1';
|
* Execute the Project's builders. If this function throws an error,
|
||||||
process.env.NOW_BUILDER = '1';
|
* 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;
|
||||||
const workPath = join(cwd, project.settings.rootDirectory || '.');
|
const workPath = join(cwd, project.settings.rootDirectory || '.');
|
||||||
|
|
||||||
// Load `package.json` and `vercel.json` files
|
// Load `package.json` and `vercel.json` files
|
||||||
@@ -198,23 +283,23 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
normalizePath(relative(workPath, f))
|
normalizePath(relative(workPath, f))
|
||||||
);
|
);
|
||||||
|
|
||||||
const routesResult = getTransformedRoutes({ nowConfig: vercelConfig || {} });
|
const routesResult = getTransformedRoutes(vercelConfig || {});
|
||||||
if (routesResult.error) {
|
if (routesResult.error) {
|
||||||
output.prettyError(routesResult.error);
|
throw routesResult.error;
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vercelConfig?.builds && vercelConfig.functions) {
|
if (vercelConfig?.builds && vercelConfig.functions) {
|
||||||
output.prettyError({
|
throw new NowBuildError({
|
||||||
|
code: 'bad_request',
|
||||||
message:
|
message:
|
||||||
'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.',
|
'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.',
|
||||||
link: 'https://vercel.link/functions-and-builds',
|
link: 'https://vercel.link/functions-and-builds',
|
||||||
});
|
});
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let builds = vercelConfig?.builds || [];
|
let builds = vercelConfig?.builds || [];
|
||||||
let zeroConfigRoutes: Route[] = [];
|
let zeroConfigRoutes: Route[] = [];
|
||||||
|
let isZeroConfig = false;
|
||||||
|
|
||||||
if (builds.length > 0) {
|
if (builds.length > 0) {
|
||||||
output.warn(
|
output.warn(
|
||||||
@@ -223,17 +308,18 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
builds = builds.map(b => expandBuild(files, b)).flat();
|
builds = builds.map(b => expandBuild(files, b)).flat();
|
||||||
} else {
|
} else {
|
||||||
// Zero config
|
// Zero config
|
||||||
|
isZeroConfig = true;
|
||||||
|
|
||||||
// Detect the Vercel Builders that will need to be invoked
|
// Detect the Vercel Builders that will need to be invoked
|
||||||
const detectedBuilders = await detectBuilders(files, pkg, {
|
const detectedBuilders = await detectBuilders(files, pkg, {
|
||||||
...vercelConfig,
|
...vercelConfig,
|
||||||
projectSettings: project.settings,
|
projectSettings: project.settings,
|
||||||
|
ignoreBuildScript: true,
|
||||||
featHandleMiss: true,
|
featHandleMiss: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (detectedBuilders.errors && detectedBuilders.errors.length > 0) {
|
if (detectedBuilders.errors && detectedBuilders.errors.length > 0) {
|
||||||
output.prettyError(detectedBuilders.errors[0]);
|
throw detectedBuilders.errors[0];
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const w of detectedBuilders.warnings) {
|
for (const w of detectedBuilders.warnings) {
|
||||||
@@ -266,13 +352,7 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
|
|
||||||
const builderSpecs = new Set(builds.map(b => b.use));
|
const builderSpecs = new Set(builds.map(b => b.use));
|
||||||
|
|
||||||
let buildersWithPkgs: Map<string, BuilderWithPkg>;
|
const buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
|
||||||
try {
|
|
||||||
buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
|
|
||||||
} catch (err: any) {
|
|
||||||
output.prettyError(err);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate Files -> FileFsRef mapping
|
// Populate Files -> FileFsRef mapping
|
||||||
const filesMap: Files = {};
|
const filesMap: Files = {};
|
||||||
@@ -282,12 +362,6 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
filesMap[path] = new FileFsRef({ mode, fsPath });
|
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();
|
const buildStamp = stamp();
|
||||||
|
|
||||||
// Create fresh new output directory
|
// Create fresh new output directory
|
||||||
@@ -296,32 +370,31 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
const ops: Promise<Error | void>[] = [];
|
const ops: Promise<Error | void>[] = [];
|
||||||
|
|
||||||
// Write the `detectedBuilders` result to output dir
|
// Write the `detectedBuilders` result to output dir
|
||||||
ops.push(
|
const buildsJsonBuilds = new Map<Builder, SerializedBuilder>(
|
||||||
fs.writeJSON(
|
builds.map(build => {
|
||||||
join(outputDir, 'builds.json'),
|
const builderWithPkg = buildersWithPkgs.get(build.use);
|
||||||
{
|
if (!builderWithPkg) {
|
||||||
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
|
throw new Error(`Failed to load Builder "${build.use}"`);
|
||||||
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
|
// The `meta` config property is re-used for each Builder
|
||||||
// invocation so that Builders can share state between
|
// invocation so that Builders can share state between
|
||||||
@@ -332,64 +405,89 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Execute Builders for detected entrypoints
|
// Execute Builders for detected entrypoints
|
||||||
// TODO: parallelize builds
|
// TODO: parallelize builds (except for frontend)
|
||||||
|
const sortedBuilders = sortBuilders(builds);
|
||||||
const buildResults: Map<Builder, BuildResult> = new Map();
|
const buildResults: Map<Builder, BuildResult> = new Map();
|
||||||
const overrides: PathOverride[] = [];
|
const overrides: PathOverride[] = [];
|
||||||
const repoRootPath = cwd;
|
const repoRootPath = cwd;
|
||||||
const corepackShimDir = await initCorepack({ repoRootPath });
|
const corepackShimDir = await initCorepack({ repoRootPath });
|
||||||
|
|
||||||
for (const build of builds) {
|
for (const build of sortedBuilders) {
|
||||||
if (typeof build.src !== 'string') continue;
|
if (typeof build.src !== 'string') continue;
|
||||||
|
|
||||||
const builderWithPkg = buildersWithPkgs.get(build.use);
|
const builderWithPkg = buildersWithPkgs.get(build.use);
|
||||||
if (!builderWithPkg) {
|
if (!builderWithPkg) {
|
||||||
throw new Error(`Failed to load Builder "${build.use}"`);
|
throw new Error(`Failed to load Builder "${build.use}"`);
|
||||||
}
|
}
|
||||||
const { builder, pkg: builderPkg } = builderWithPkg;
|
|
||||||
|
|
||||||
const buildConfig: Config = {
|
try {
|
||||||
outputDirectory: project.settings.outputDirectory ?? undefined,
|
const { builder, pkg: builderPkg } = builderWithPkg;
|
||||||
...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);
|
|
||||||
|
|
||||||
// Store the build result to generate the final `config.json` after
|
const buildConfig: Config = isZeroConfig
|
||||||
// all builds have completed
|
? {
|
||||||
buildResults.set(build, buildResult);
|
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);
|
||||||
|
|
||||||
// Start flushing the file outputs to the filesystem asynchronously
|
// Store the build result to generate the final `config.json` after
|
||||||
ops.push(
|
// all builds have completed
|
||||||
writeBuildResult(
|
buildResults.set(build, buildResult);
|
||||||
outputDir,
|
|
||||||
buildResult,
|
// Start flushing the file outputs to the filesystem asynchronously
|
||||||
build,
|
ops.push(
|
||||||
builder,
|
writeBuildResult(
|
||||||
builderPkg,
|
outputDir,
|
||||||
vercelConfig?.cleanUrls
|
buildResult,
|
||||||
).then(
|
build,
|
||||||
override => {
|
builder,
|
||||||
if (override) overrides.push(override);
|
builderPkg,
|
||||||
},
|
vercelConfig
|
||||||
err => err
|
).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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (corepackShimDir) {
|
if (corepackShimDir) {
|
||||||
@@ -398,15 +496,12 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
|
|
||||||
// Wait for filesystem operations to complete
|
// Wait for filesystem operations to complete
|
||||||
// TODO render progress bar?
|
// TODO render progress bar?
|
||||||
let hadError = false;
|
|
||||||
const errors = await Promise.all(ops);
|
const errors = await Promise.all(ops);
|
||||||
for (const error of errors) {
|
for (const error of errors) {
|
||||||
if (error) {
|
if (error) {
|
||||||
hadError = true;
|
throw error;
|
||||||
output.prettyError(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hadError) return 1;
|
|
||||||
|
|
||||||
// Merge existing `config.json` file into the one that will be produced
|
// Merge existing `config.json` file into the one that will be produced
|
||||||
const configPath = join(outputDir, 'config.json');
|
const configPath = join(outputDir, 'config.json');
|
||||||
@@ -526,7 +621,7 @@ function mergeImages(
|
|||||||
let images: BuildResultV2Typical['images'] = undefined;
|
let images: BuildResultV2Typical['images'] = undefined;
|
||||||
for (const result of buildResults) {
|
for (const result of buildResults) {
|
||||||
if ('images' in result && result.images) {
|
if ('images' in result && result.images) {
|
||||||
images = Object.assign({} || images, result.images);
|
images = Object.assign({}, images, result.images);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return images;
|
return images;
|
||||||
@@ -544,14 +639,3 @@ function mergeWildcard(
|
|||||||
}
|
}
|
||||||
return wildcard;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import stamp from '../../util/output/stamp';
|
|||||||
import createCertFromFile from '../../util/certs/create-cert-from-file';
|
import createCertFromFile from '../../util/certs/create-cert-from-file';
|
||||||
import createCertForCns from '../../util/certs/create-cert-for-cns';
|
import createCertForCns from '../../util/certs/create-cert-for-cns';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
|
import { Cert } from '../../types';
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
'--overwrite'?: boolean;
|
'--overwrite'?: boolean;
|
||||||
@@ -21,7 +22,7 @@ async function add(
|
|||||||
const { output } = client;
|
const { output } = client;
|
||||||
const addStamp = stamp();
|
const addStamp = stamp();
|
||||||
|
|
||||||
let cert;
|
let cert: Cert | Error;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
'--overwrite': overwite,
|
'--overwrite': overwite,
|
||||||
@@ -30,18 +31,7 @@ async function add(
|
|||||||
'--ca': caPath,
|
'--ca': caPath,
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
let contextName = null;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overwite) {
|
if (overwite) {
|
||||||
output.error('Overwrite option is deprecated');
|
output.error('Overwrite option is deprecated');
|
||||||
|
|||||||
@@ -39,18 +39,7 @@ export default async function issue(
|
|||||||
'--ca': caPath,
|
'--ca': caPath,
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
let contextName = null;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overwite) {
|
if (overwite) {
|
||||||
output.error('Overwrite option is deprecated');
|
output.error('Overwrite option is deprecated');
|
||||||
|
|||||||
@@ -21,18 +21,8 @@ async function ls(
|
|||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
const { '--next': nextTimestamp } = opts;
|
const { '--next': nextTimestamp } = opts;
|
||||||
let contextName = null;
|
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;
|
|
||||||
}
|
|
||||||
if (typeof nextTimestamp !== 'undefined' && Number.isNaN(nextTimestamp)) {
|
if (typeof nextTimestamp !== 'undefined' && Number.isNaN(nextTimestamp)) {
|
||||||
output.error('Please provide a number for flag --next');
|
output.error('Please provide a number for flag --next');
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@@ -17,21 +17,9 @@ import { getCommandName } from '../../util/pkg-name';
|
|||||||
type Options = {};
|
type Options = {};
|
||||||
|
|
||||||
async function rm(client: Client, opts: Options, args: string[]) {
|
async function rm(client: Client, opts: Options, args: string[]) {
|
||||||
const { output } = client;
|
|
||||||
const rmStamp = stamp();
|
const rmStamp = stamp();
|
||||||
|
const { output } = client;
|
||||||
let contextName = null;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.length !== 1) {
|
if (args.length !== 1) {
|
||||||
output.error(
|
output.error(
|
||||||
|
|||||||
@@ -15,26 +15,28 @@ export const help = () => `
|
|||||||
)}
|
)}
|
||||||
dev Start a local development server
|
dev Start a local development server
|
||||||
env Manages the Environment Variables for your current Project
|
env Manages the Environment Variables for your current Project
|
||||||
|
git Manage Git provider repository for your current Project
|
||||||
|
help [cmd] Displays complete help for [cmd]
|
||||||
init [example] Initialize an example project
|
init [example] Initialize an example project
|
||||||
ls | list [app] Lists deployments
|
|
||||||
inspect [id] Displays information related to a deployment
|
inspect [id] Displays information related to a deployment
|
||||||
link [path] Link local directory to a Vercel Project
|
link [path] Link local directory to a Vercel Project
|
||||||
|
ls | list [app] Lists deployments
|
||||||
login [email] Logs into your account or creates a new one
|
login [email] Logs into your account or creates a new one
|
||||||
logout Logs out of your account
|
logout Logs out of your account
|
||||||
pull [path] Pull your Project Settings from the cloud
|
pull [path] Pull your Project Settings from the cloud
|
||||||
switch [scope] Switches between teams and your personal account
|
switch [scope] Switches between teams and your personal account
|
||||||
help [cmd] Displays complete help for [cmd]
|
|
||||||
|
|
||||||
${chalk.dim('Advanced')}
|
${chalk.dim('Advanced')}
|
||||||
|
|
||||||
rm | remove [id] Removes a deployment
|
alias [cmd] Manages your domain aliases
|
||||||
bisect Use binary search to find the deployment that introduced a bug
|
bisect Use binary search to find the deployment that introduced a bug
|
||||||
domains [name] Manages your domain names
|
|
||||||
projects Manages your Projects
|
|
||||||
dns [name] Manages your DNS records
|
|
||||||
certs [cmd] Manages your SSL certificates
|
certs [cmd] Manages your SSL certificates
|
||||||
secrets [name] Manages your global Secrets, for use in Environment Variables
|
dns [name] Manages your DNS records
|
||||||
|
domains [name] Manages your domain names
|
||||||
logs [url] Displays the logs for a deployment
|
logs [url] Displays the logs for a deployment
|
||||||
|
projects Manages your Projects
|
||||||
|
rm | remove [id] Removes a deployment
|
||||||
|
secrets [name] Manages your global Secrets, for use in Environment Variables
|
||||||
teams Manages your teams
|
teams Manages your teams
|
||||||
whoami Shows the username of the currently logged in user
|
whoami Shows the username of the currently logged in user
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import fs from 'fs-extra';
|
|||||||
import bytes from 'bytes';
|
import bytes from 'bytes';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { join, resolve, basename } from 'path';
|
import { join, resolve, basename } from 'path';
|
||||||
import { Dictionary, fileNameSymbol, VercelConfig } from '@vercel/client';
|
import { fileNameSymbol, VercelConfig } from '@vercel/client';
|
||||||
import code from '../../util/output/code';
|
import code from '../../util/output/code';
|
||||||
import highlight from '../../util/output/highlight';
|
import highlight from '../../util/output/highlight';
|
||||||
import { readLocalConfig } from '../../util/config/files';
|
import { readLocalConfig } from '../../util/config/files';
|
||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
ConflictingPathSegment,
|
ConflictingPathSegment,
|
||||||
BuildError,
|
BuildError,
|
||||||
NotDomainOwner,
|
NotDomainOwner,
|
||||||
|
isAPIError,
|
||||||
} from '../../util/errors-ts';
|
} from '../../util/errors-ts';
|
||||||
import { SchemaValidationFailed } from '../../util/errors';
|
import { SchemaValidationFailed } from '../../util/errors';
|
||||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||||
@@ -64,7 +65,9 @@ import { help } from './args';
|
|||||||
import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
|
import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
|
||||||
import parseTarget from '../../util/deploy/parse-target';
|
import parseTarget from '../../util/deploy/parse-target';
|
||||||
import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
|
import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
|
||||||
import { createGitMeta } from '../../util/deploy/create-git-meta';
|
import { createGitMeta } from '../../util/create-git-meta';
|
||||||
|
import { parseEnv } from '../../util/parse-env';
|
||||||
|
import { errorToString, isErrnoException, isError } from '../../util/is-error';
|
||||||
|
|
||||||
export default async (client: Client) => {
|
export default async (client: Client) => {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
@@ -95,6 +98,7 @@ export default async (client: Client) => {
|
|||||||
// deprecated
|
// deprecated
|
||||||
'--name': String,
|
'--name': String,
|
||||||
'-n': '--name',
|
'-n': '--name',
|
||||||
|
'--no-clipboard': Boolean,
|
||||||
'--target': String,
|
'--target': String,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -183,6 +187,17 @@ 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`
|
// build `target`
|
||||||
const target = parseTarget(output, argv['--target'], argv['--prod']);
|
const target = parseTarget(output, argv['--target'], argv['--prod']);
|
||||||
if (typeof target === 'number') {
|
if (typeof target === 'number') {
|
||||||
@@ -204,6 +219,22 @@ export default async (client: Client) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const prebuiltBuild = await getPrebuiltJson(path);
|
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';
|
const assumedTarget = target || 'preview';
|
||||||
if (prebuiltBuild?.target && prebuiltBuild.target !== assumedTarget) {
|
if (prebuiltBuild?.target && prebuiltBuild.target !== assumedTarget) {
|
||||||
let specifyTarget = '';
|
let specifyTarget = '';
|
||||||
@@ -256,8 +287,11 @@ export default async (client: Client) => {
|
|||||||
'Which scope do you want to deploy to?',
|
'Which scope do you want to deploy to?',
|
||||||
autoConfirm
|
autoConfirm
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
if (
|
||||||
|
isErrnoException(err) &&
|
||||||
|
(err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED')
|
||||||
|
) {
|
||||||
output.error(err.message);
|
output.error(err.message);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -416,7 +450,7 @@ export default async (client: Client) => {
|
|||||||
parseMeta(argv['--meta'])
|
parseMeta(argv['--meta'])
|
||||||
);
|
);
|
||||||
|
|
||||||
const gitMetadata = await createGitMeta(path, output);
|
const gitMetadata = await createGitMeta(path, output, project);
|
||||||
|
|
||||||
// Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments
|
// Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments
|
||||||
const deploymentEnv = Object.assign(
|
const deploymentEnv = Object.assign(
|
||||||
@@ -436,8 +470,8 @@ export default async (client: Client) => {
|
|||||||
try {
|
try {
|
||||||
await addProcessEnv(log, deploymentEnv);
|
await addProcessEnv(log, deploymentEnv);
|
||||||
await addProcessEnv(log, deploymentBuildEnv);
|
await addProcessEnv(log, deploymentBuildEnv);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
error(err.message);
|
error(errorToString(err));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,8 +632,10 @@ export default async (client: Client) => {
|
|||||||
error('Uploading failed. Please try again.');
|
error('Uploading failed. Please try again.');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
debug(`Error: ${err}\n${err.stack}`);
|
if (isError(err)) {
|
||||||
|
debug(`Error: ${err}\n${err.stack}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (err instanceof NotDomainOwner) {
|
if (err instanceof NotDomainOwner) {
|
||||||
output.error(err.message);
|
output.error(err.message);
|
||||||
@@ -666,13 +702,7 @@ export default async (client: Client) => {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err.keyword === 'additionalProperties' && err.dataPath === '.scale') {
|
if (isAPIError(err) && err.code === 'size_limit_exceeded') {
|
||||||
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 { sizeLimit = 0 } = err;
|
||||||
const message = `File size limit exceeded (${bytes(sizeLimit)})`;
|
const message = `File size limit exceeded (${bytes(sizeLimit)})`;
|
||||||
error(message);
|
error(message);
|
||||||
@@ -882,36 +912,3 @@ const printDeploymentStatus = async (
|
|||||||
output.print(message + link);
|
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;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import readConfig from '../../util/config/read-config';
|
|||||||
import readJSONFile from '../../util/read-json-file';
|
import readJSONFile from '../../util/read-json-file';
|
||||||
import { getPkgName, getCommandName } from '../../util/pkg-name';
|
import { getPkgName, getCommandName } from '../../util/pkg-name';
|
||||||
import { CantParseJSONFile } from '../../util/errors-ts';
|
import { CantParseJSONFile } from '../../util/errors-ts';
|
||||||
|
import { isErrnoException } from '../../util/is-error';
|
||||||
|
|
||||||
const COMMAND_CONFIG = {
|
const COMMAND_CONFIG = {
|
||||||
dev: ['dev'],
|
dev: ['dev'],
|
||||||
@@ -136,7 +137,7 @@ export default async function main(client: Client) {
|
|||||||
try {
|
try {
|
||||||
return await dev(client, argv, args);
|
return await dev(client, argv, args);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ENOTFOUND') {
|
if (isErrnoException(err) && err.code === 'ENOTFOUND') {
|
||||||
// Error message will look like the following:
|
// Error message will look like the following:
|
||||||
// "request to https://api.vercel.com/v2/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
|
// "request to https://api.vercel.com/v2/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
|
||||||
const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
|
const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
|
||||||
@@ -148,7 +149,9 @@ export default async function main(client: Client) {
|
|||||||
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
|
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
output.debug(err.stack);
|
if (typeof err.stack === 'string') {
|
||||||
|
output.debug(err.stack);
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
output.prettyError(err);
|
output.prettyError(err);
|
||||||
|
|||||||
@@ -21,18 +21,7 @@ export default async function add(
|
|||||||
args: string[]
|
args: string[]
|
||||||
) {
|
) {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
let contextName = null;
|
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 parsedParams = parseAddDNSRecordArgs(args);
|
const parsedParams = parseAddDNSRecordArgs(args);
|
||||||
if (!parsedParams) {
|
if (!parsedParams) {
|
||||||
|
|||||||
@@ -14,18 +14,7 @@ export default async function add(
|
|||||||
args: string[]
|
args: string[]
|
||||||
) {
|
) {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
let contextName = null;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.length !== 2) {
|
if (args.length !== 2) {
|
||||||
output.error(
|
output.error(
|
||||||
|
|||||||
@@ -24,18 +24,7 @@ export default async function ls(
|
|||||||
) {
|
) {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
const { '--next': nextTimestamp } = opts;
|
const { '--next': nextTimestamp } = opts;
|
||||||
let contextName = null;
|
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 [domainName] = args;
|
const [domainName] = args;
|
||||||
const lsStamp = stamp();
|
const lsStamp = stamp();
|
||||||
|
|||||||
@@ -14,21 +14,11 @@ type Options = {};
|
|||||||
|
|
||||||
export default async function rm(
|
export default async function rm(
|
||||||
client: Client,
|
client: Client,
|
||||||
opts: Options,
|
_opts: Options,
|
||||||
args: string[]
|
args: string[]
|
||||||
) {
|
) {
|
||||||
const { output } = client;
|
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;
|
const [recordId] = args;
|
||||||
if (args.length !== 1) {
|
if (args.length !== 1) {
|
||||||
|
|||||||
@@ -26,18 +26,7 @@ export default async function add(
|
|||||||
) {
|
) {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
const force = opts['--force'];
|
const force = opts['--force'];
|
||||||
let contextName = null;
|
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 project = await getLinkedProject(client).then(result => {
|
const project = await getLinkedProject(client).then(result => {
|
||||||
if (result.status === 'linked') {
|
if (result.status === 'linked') {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import promptBool from '../../util/input/prompt-bool';
|
|||||||
import purchaseDomain from '../../util/domains/purchase-domain';
|
import purchaseDomain from '../../util/domains/purchase-domain';
|
||||||
import stamp from '../../util/output/stamp';
|
import stamp from '../../util/output/stamp';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
|
import { errorToString } from '../../util/is-error';
|
||||||
|
|
||||||
type Options = {};
|
type Options = {};
|
||||||
|
|
||||||
@@ -20,18 +21,7 @@ export default async function buy(
|
|||||||
args: string[]
|
args: string[]
|
||||||
) {
|
) {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
let contextName = null;
|
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 [domainName] = args;
|
const [domainName] = args;
|
||||||
if (!domainName) {
|
if (!domainName) {
|
||||||
@@ -68,6 +58,11 @@ export default async function buy(
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (renewalPrice instanceof Error) {
|
||||||
|
output.prettyError(renewalPrice);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(await getDomainStatus(client, domainName)).available) {
|
if (!(await getDomainStatus(client, domainName)).available) {
|
||||||
output.error(
|
output.error(
|
||||||
`The domain ${param(domainName)} is ${chalk.underline(
|
`The domain ${param(domainName)} is ${chalk.underline(
|
||||||
@@ -109,11 +104,11 @@ export default async function buy(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
buyResult = await purchaseDomain(client, domainName, price, autoRenew);
|
buyResult = await purchaseDomain(client, domainName, price, autoRenew);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
output.error(
|
output.error(
|
||||||
'An unexpected error occurred while purchasing your domain. Please try again later.'
|
'An unexpected error occurred while purchasing your domain. Please try again later.'
|
||||||
);
|
);
|
||||||
output.debug(`Server response: ${err.message}`);
|
output.debug(`Server response: ${errorToString(err)}`);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,19 +23,7 @@ export default async function inspect(
|
|||||||
args: string[]
|
args: string[]
|
||||||
) {
|
) {
|
||||||
const { output } = client;
|
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 [domainName] = args;
|
||||||
const inspectStamp = stamp();
|
const inspectStamp = stamp();
|
||||||
|
|||||||
@@ -25,23 +25,13 @@ export default async function ls(
|
|||||||
) {
|
) {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
const { '--next': nextTimestamp } = opts;
|
const { '--next': nextTimestamp } = opts;
|
||||||
let contextName = null;
|
|
||||||
|
|
||||||
if (typeof nextTimestamp !== undefined && Number.isNaN(nextTimestamp)) {
|
if (typeof nextTimestamp !== undefined && Number.isNaN(nextTimestamp)) {
|
||||||
output.error('Please provide a number for flag --next');
|
output.error('Please provide a number for flag --next');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const { contextName } = await getScope(client);
|
||||||
({ 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();
|
const lsStamp = stamp();
|
||||||
|
|
||||||
|
|||||||
@@ -25,20 +25,7 @@ export default async function move(
|
|||||||
args: string[]
|
args: string[]
|
||||||
) {
|
) {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
let contextName = null;
|
const { contextName, user } = await getScope(client);
|
||||||
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);
|
const { domainName, destination } = await getArgs(args);
|
||||||
if (!isRootDomain(domainName)) {
|
if (!isRootDomain(domainName)) {
|
||||||
output.error(
|
output.error(
|
||||||
|
|||||||
@@ -29,18 +29,7 @@ export default async function rm(
|
|||||||
) {
|
) {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
const [domainName] = args;
|
const [domainName] = args;
|
||||||
let contextName = null;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!domainName) {
|
if (!domainName) {
|
||||||
output.error(
|
output.error(
|
||||||
@@ -122,10 +111,10 @@ async function removeDomain(
|
|||||||
output.debug(`Removing alias ${id}`);
|
output.debug(`Removing alias ${id}`);
|
||||||
try {
|
try {
|
||||||
await removeAliasById(client, id);
|
await removeAliasById(client, id);
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
// Ignore if the alias does not exist anymore
|
// Ignore if the alias does not exist anymore
|
||||||
if (error.status !== 404) {
|
if (!ERRORS.isAPIError(err) || err.status !== 404) {
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,10 +123,10 @@ async function removeDomain(
|
|||||||
output.debug(`Removing cert ${id}`);
|
output.debug(`Removing cert ${id}`);
|
||||||
try {
|
try {
|
||||||
await deleteCertById(output, client, id);
|
await deleteCertById(output, client, id);
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
// Ignore if the cert does not exist anymore
|
// Ignore if the cert does not exist anymore
|
||||||
if (error.status !== 404) {
|
if (!ERRORS.isAPIError(err) || err.status !== 404) {
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,18 +23,7 @@ export default async function transferIn(
|
|||||||
args: string[]
|
args: string[]
|
||||||
) {
|
) {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
let contextName = null;
|
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 [domainName] = args;
|
const [domainName] = args;
|
||||||
if (!domainName) {
|
if (!domainName) {
|
||||||
|
|||||||
18
packages/cli/src/commands/env/add.ts
vendored
18
packages/cli/src/commands/env/add.ts
vendored
@@ -1,5 +1,4 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import inquirer from 'inquirer';
|
|
||||||
import { ProjectEnvTarget, Project, ProjectEnvType } from '../../types';
|
import { ProjectEnvTarget, Project, ProjectEnvType } from '../../types';
|
||||||
import { Output } from '../../util/output';
|
import { Output } from '../../util/output';
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
@@ -16,6 +15,7 @@ import param from '../../util/output/param';
|
|||||||
import { emoji, prependEmoji } from '../../util/emoji';
|
import { emoji, prependEmoji } from '../../util/emoji';
|
||||||
import { isKnownError } from '../../util/env/known-error';
|
import { isKnownError } from '../../util/env/known-error';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
|
import { isAPIError } from '../../util/errors-ts';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
'--debug': boolean;
|
'--debug': boolean;
|
||||||
@@ -66,7 +66,7 @@ export default async function add(
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (!envName) {
|
while (!envName) {
|
||||||
const { inputName } = await inquirer.prompt({
|
const { inputName } = await client.prompt({
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'inputName',
|
name: 'inputName',
|
||||||
message: `What’s the name of the variable?`,
|
message: `What’s the name of the variable?`,
|
||||||
@@ -106,7 +106,7 @@ export default async function add(
|
|||||||
if (stdInput) {
|
if (stdInput) {
|
||||||
envValue = stdInput;
|
envValue = stdInput;
|
||||||
} else {
|
} else {
|
||||||
const { inputValue } = await inquirer.prompt({
|
const { inputValue } = await client.prompt({
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'inputValue',
|
name: 'inputValue',
|
||||||
message: `What’s the value of ${envName}?`,
|
message: `What’s the value of ${envName}?`,
|
||||||
@@ -116,7 +116,7 @@ export default async function add(
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (envTargets.length === 0) {
|
while (envTargets.length === 0) {
|
||||||
const { inputTargets } = await inquirer.prompt({
|
const { inputTargets } = await client.prompt({
|
||||||
name: 'inputTargets',
|
name: 'inputTargets',
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
message: `Add ${envName} to which Environments (select multiple)?`,
|
message: `Add ${envName} to which Environments (select multiple)?`,
|
||||||
@@ -136,7 +136,7 @@ export default async function add(
|
|||||||
envTargets.length === 1 &&
|
envTargets.length === 1 &&
|
||||||
envTargets[0] === ProjectEnvTarget.Preview
|
envTargets[0] === ProjectEnvTarget.Preview
|
||||||
) {
|
) {
|
||||||
const { inputValue } = await inquirer.prompt({
|
const { inputValue } = await client.prompt({
|
||||||
type: 'input',
|
type: 'input',
|
||||||
name: 'inputValue',
|
name: 'inputValue',
|
||||||
message: `Add ${envName} to which Git branch? (leave empty for all Preview branches)?`,
|
message: `Add ${envName} to which Git branch? (leave empty for all Preview branches)?`,
|
||||||
@@ -157,12 +157,12 @@ export default async function add(
|
|||||||
envTargets,
|
envTargets,
|
||||||
envGitBranch
|
envGitBranch
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (isKnownError(error) && error.serverMessage) {
|
if (isAPIError(err) && isKnownError(err)) {
|
||||||
output.error(error.serverMessage);
|
output.error(err.serverMessage);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.print(
|
output.print(
|
||||||
|
|||||||
21
packages/cli/src/commands/env/index.ts
vendored
21
packages/cli/src/commands/env/index.ts
vendored
@@ -1,7 +1,9 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { ProjectEnvTarget } from '../../types';
|
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import { getEnvTargetPlaceholder } from '../../util/env/env-target';
|
import {
|
||||||
|
getEnvTargetPlaceholder,
|
||||||
|
isValidEnvTarget,
|
||||||
|
} from '../../util/env/env-target';
|
||||||
import getArgs from '../../util/get-args';
|
import getArgs from '../../util/get-args';
|
||||||
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
|
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
|
||||||
import getSubcommand from '../../util/get-subcommand';
|
import getSubcommand from '../../util/get-subcommand';
|
||||||
@@ -29,6 +31,7 @@ const help = () => {
|
|||||||
${chalk.dim('Options:')}
|
${chalk.dim('Options:')}
|
||||||
|
|
||||||
-h, --help Output usage information
|
-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(
|
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||||
'FILE'
|
'FILE'
|
||||||
)} Path to the local ${'`vercel.json`'} file
|
)} Path to the local ${'`vercel.json`'} file
|
||||||
@@ -111,6 +114,7 @@ export default async function main(client: Client) {
|
|||||||
argv = getArgs(client.argv.slice(2), {
|
argv = getArgs(client.argv.slice(2), {
|
||||||
'--yes': Boolean,
|
'--yes': Boolean,
|
||||||
'-y': '--yes',
|
'-y': '--yes',
|
||||||
|
'--environment': String,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error);
|
handleError(error);
|
||||||
@@ -126,6 +130,17 @@ export default async function main(client: Client) {
|
|||||||
const subArgs = argv._.slice(1);
|
const subArgs = argv._.slice(1);
|
||||||
const { subcommand, args } = getSubcommand(subArgs, COMMAND_CONFIG);
|
const { subcommand, args } = getSubcommand(subArgs, COMMAND_CONFIG);
|
||||||
const { output, config } = client;
|
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);
|
const link = await getLinkedProject(client, cwd);
|
||||||
if (link.status === 'error') {
|
if (link.status === 'error') {
|
||||||
return link.exitCode;
|
return link.exitCode;
|
||||||
@@ -150,7 +165,7 @@ export default async function main(client: Client) {
|
|||||||
return pull(
|
return pull(
|
||||||
client,
|
client,
|
||||||
project,
|
project,
|
||||||
ProjectEnvTarget.Development,
|
target,
|
||||||
argv,
|
argv,
|
||||||
args,
|
args,
|
||||||
output,
|
output,
|
||||||
|
|||||||
35
packages/cli/src/commands/env/pull.ts
vendored
35
packages/cli/src/commands/env/pull.ts
vendored
@@ -14,6 +14,11 @@ import param from '../../util/output/param';
|
|||||||
import stamp from '../../util/output/stamp';
|
import stamp from '../../util/output/stamp';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
import { EnvRecordsSource } from '../../util/env/get-env-records';
|
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';
|
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
|
||||||
|
|
||||||
@@ -36,8 +41,8 @@ function readHeadSync(path: string, length: number) {
|
|||||||
function tryReadHeadSync(path: string, length: number) {
|
function tryReadHeadSync(path: string, length: number) {
|
||||||
try {
|
try {
|
||||||
return readHeadSync(path, length);
|
return readHeadSync(path, length);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code !== 'ENOENT') {
|
if (!isErrnoException(err) || err.code !== 'ENOENT') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,7 +74,7 @@ export default async function pull(
|
|||||||
const exists = typeof head !== 'undefined';
|
const exists = typeof head !== 'undefined';
|
||||||
|
|
||||||
if (head === CONTENTS_PREFIX) {
|
if (head === CONTENTS_PREFIX) {
|
||||||
output.print(`Overwriting existing ${chalk.bold(filename)} file\n`);
|
output.log(`Overwriting existing ${chalk.bold(filename)} file`);
|
||||||
} else if (
|
} else if (
|
||||||
exists &&
|
exists &&
|
||||||
!skipConfirmation &&
|
!skipConfirmation &&
|
||||||
@@ -83,10 +88,10 @@ export default async function pull(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.print(
|
output.log(
|
||||||
`Downloading "${environment}" Environment Variables for Project ${chalk.bold(
|
`Downloading \`${chalk.cyan(
|
||||||
project.name
|
environment
|
||||||
)}\n`
|
)}\` Environment Variables for Project ${chalk.bold(project.name)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const pullStamp = stamp();
|
const pullStamp = stamp();
|
||||||
@@ -107,6 +112,15 @@ export default async function pull(
|
|||||||
environment
|
environment
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let deltaString = '';
|
||||||
|
let oldEnv;
|
||||||
|
if (exists) {
|
||||||
|
oldEnv = await createEnvObject(fullPath, output);
|
||||||
|
if (oldEnv) {
|
||||||
|
deltaString = buildDeltaString(oldEnv, records);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const contents =
|
const contents =
|
||||||
CONTENTS_PREFIX +
|
CONTENTS_PREFIX +
|
||||||
Object.entries(records)
|
Object.entries(records)
|
||||||
@@ -125,6 +139,13 @@ export default async function pull(
|
|||||||
)}\n`
|
)}\n`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
output.print('\n');
|
||||||
|
if (deltaString) {
|
||||||
|
output.print(deltaString);
|
||||||
|
} else if (oldEnv && exists) {
|
||||||
|
output.log('No changes found.');
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
9
packages/cli/src/commands/env/rm.ts
vendored
9
packages/cli/src/commands/env/rm.ts
vendored
@@ -16,6 +16,7 @@ import param from '../../util/output/param';
|
|||||||
import { emoji, prependEmoji } from '../../util/emoji';
|
import { emoji, prependEmoji } from '../../util/emoji';
|
||||||
import { isKnownError } from '../../util/env/known-error';
|
import { isKnownError } from '../../util/env/known-error';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
|
import { isAPIError } from '../../util/errors-ts';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
'--debug': boolean;
|
'--debug': boolean;
|
||||||
@@ -120,12 +121,12 @@ export default async function rm(
|
|||||||
try {
|
try {
|
||||||
output.spinner('Removing');
|
output.spinner('Removing');
|
||||||
await removeEnvRecord(output, client, project.id, env);
|
await removeEnvRecord(output, client, project.id, env);
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (isKnownError(error) && error.serverMessage) {
|
if (isAPIError(err) && isKnownError(err)) {
|
||||||
output.error(error.serverMessage);
|
output.error(err.serverMessage);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.print(
|
output.print(
|
||||||
|
|||||||
205
packages/cli/src/commands/git/connect.ts
Normal file
205
packages/cli/src/commands/git/connect.ts
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
58
packages/cli/src/commands/git/disconnect.ts
Normal file
58
packages/cli/src/commands/git/disconnect.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
94
packages/cli/src/commands/git/index.ts
Normal file
94
packages/cli/src/commands/git/index.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ export default new Map([
|
|||||||
['domain', 'domains'],
|
['domain', 'domains'],
|
||||||
['domains', 'domains'],
|
['domains', 'domains'],
|
||||||
['env', 'env'],
|
['env', 'env'],
|
||||||
|
['git', 'git'],
|
||||||
['help', 'help'],
|
['help', 'help'],
|
||||||
['init', 'init'],
|
['init', 'init'],
|
||||||
['inspect', 'inspect'],
|
['inspect', 'inspect'],
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import handleError from '../../util/handle-error';
|
|||||||
import logo from '../../util/output/logo';
|
import logo from '../../util/output/logo';
|
||||||
import init from './init';
|
import init from './init';
|
||||||
import { getPkgName } from '../../util/pkg-name';
|
import { getPkgName } from '../../util/pkg-name';
|
||||||
|
import { isError } from '../../util/is-error';
|
||||||
|
|
||||||
const COMMAND_CONFIG = {
|
const COMMAND_CONFIG = {
|
||||||
init: ['init'],
|
init: ['init'],
|
||||||
@@ -70,9 +71,11 @@ export default async function main(client: Client) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
return await init(client, argv, args);
|
return await init(client, argv, args);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
output.prettyError(err);
|
output.prettyError(err);
|
||||||
output.debug(err.stack);
|
if (isError(err) && typeof err.stack === 'string') {
|
||||||
|
output.debug(err.stack);
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import Client from '../util/client';
|
|||||||
import { getDeployment } from '../util/get-deployment';
|
import { getDeployment } from '../util/get-deployment';
|
||||||
import { Deployment } from '@vercel/client';
|
import { Deployment } from '@vercel/client';
|
||||||
import { Build } from '../types';
|
import { Build } from '../types';
|
||||||
|
import title from 'title';
|
||||||
|
import { isErrnoException } from '../util/is-error';
|
||||||
|
import { isAPIError } from '../util/errors-ts';
|
||||||
|
|
||||||
const help = () => {
|
const help = () => {
|
||||||
console.log(`
|
console.log(`
|
||||||
@@ -75,8 +78,11 @@ export default async function main(client: Client) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
({ contextName } = await getScope(client));
|
({ contextName } = await getScope(client));
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
if (
|
||||||
|
isErrnoException(err) &&
|
||||||
|
(err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED')
|
||||||
|
) {
|
||||||
error(err.message);
|
error(err.message);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -92,28 +98,38 @@ export default async function main(client: Client) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
deployment = await getDeployment(client, deploymentIdOrHost);
|
deployment = await getDeployment(client, deploymentIdOrHost);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.status === 404) {
|
if (isAPIError(err)) {
|
||||||
error(
|
if (err.status === 404) {
|
||||||
`Failed to find deployment "${deploymentIdOrHost}" in ${chalk.bold(
|
error(
|
||||||
contextName
|
`Failed to find deployment "${deploymentIdOrHost}" in ${chalk.bold(
|
||||||
)}`
|
contextName
|
||||||
);
|
)}`
|
||||||
return 1;
|
);
|
||||||
}
|
return 1;
|
||||||
if (err.status === 403) {
|
}
|
||||||
error(
|
if (err.status === 403) {
|
||||||
`No permission to access deployment "${deploymentIdOrHost}" in ${chalk.bold(
|
error(
|
||||||
contextName
|
`No permission to access deployment "${deploymentIdOrHost}" in ${chalk.bold(
|
||||||
)}`
|
contextName
|
||||||
);
|
)}`
|
||||||
return 1;
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// unexpected
|
// unexpected
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id, name, url, createdAt, routes, readyState } = deployment;
|
const {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
createdAt,
|
||||||
|
routes,
|
||||||
|
readyState,
|
||||||
|
alias: aliases,
|
||||||
|
} = deployment;
|
||||||
|
|
||||||
const { builds } =
|
const { builds } =
|
||||||
deployment.version === 2
|
deployment.version === 2
|
||||||
@@ -121,20 +137,20 @@ export default async function main(client: Client) {
|
|||||||
: { builds: [] };
|
: { builds: [] };
|
||||||
|
|
||||||
log(
|
log(
|
||||||
`Fetched deployment "${url}" in ${chalk.bold(contextName)} ${elapsed(
|
`Fetched deployment ${chalk.bold(url)} in ${chalk.bold(
|
||||||
Date.now() - depFetchStart
|
contextName
|
||||||
)}`
|
)} ${elapsed(Date.now() - depFetchStart)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
print('\n');
|
print('\n');
|
||||||
print(chalk.bold(' General\n\n'));
|
print(chalk.bold(' General\n\n'));
|
||||||
print(` ${chalk.cyan('id')}\t\t${id}\n`);
|
print(` ${chalk.cyan('id')}\t\t${id}\n`);
|
||||||
print(` ${chalk.cyan('name')}\t${name}\n`);
|
print(` ${chalk.cyan('name')}\t${name}\n`);
|
||||||
print(` ${chalk.cyan('readyState')}\t${stateString(readyState)}\n`);
|
print(` ${chalk.cyan('status')}\t${stateString(readyState)}\n`);
|
||||||
print(` ${chalk.cyan('url')}\t\t${url}\n`);
|
print(` ${chalk.cyan('url')}\t\thttps://${url}\n`);
|
||||||
if (createdAt) {
|
if (createdAt) {
|
||||||
print(
|
print(
|
||||||
` ${chalk.cyan('createdAt')}\t${new Date(createdAt)} ${elapsed(
|
` ${chalk.cyan('created')}\t${new Date(createdAt)} ${elapsed(
|
||||||
Date.now() - createdAt,
|
Date.now() - createdAt,
|
||||||
true
|
true
|
||||||
)}\n`
|
)}\n`
|
||||||
@@ -142,6 +158,16 @@ export default async function main(client: Client) {
|
|||||||
}
|
}
|
||||||
print('\n\n');
|
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) {
|
if (builds.length > 0) {
|
||||||
const times: { [id: string]: string | null } = {};
|
const times: { [id: string]: string | null } = {};
|
||||||
|
|
||||||
@@ -165,19 +191,24 @@ export default async function main(client: Client) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// renders the state string
|
|
||||||
function stateString(s: Deployment['readyState']) {
|
function stateString(s: Deployment['readyState']) {
|
||||||
|
const CIRCLE = '● ';
|
||||||
|
const sTitle = s && title(s);
|
||||||
switch (s) {
|
switch (s) {
|
||||||
case 'INITIALIZING':
|
case 'INITIALIZING':
|
||||||
return chalk.yellow(s);
|
case 'BUILDING':
|
||||||
|
case 'DEPLOYING':
|
||||||
|
case 'ANALYZING':
|
||||||
|
return chalk.yellow(CIRCLE) + sTitle;
|
||||||
case 'ERROR':
|
case 'ERROR':
|
||||||
return chalk.red(s);
|
return chalk.red(CIRCLE) + sTitle;
|
||||||
|
|
||||||
case 'READY':
|
case 'READY':
|
||||||
return s;
|
return chalk.green(CIRCLE) + sTitle;
|
||||||
|
case 'QUEUED':
|
||||||
|
return chalk.gray(CIRCLE) + sTitle;
|
||||||
|
case 'CANCELED':
|
||||||
|
return chalk.gray(CIRCLE) + sTitle;
|
||||||
default:
|
default:
|
||||||
return chalk.gray(s || 'UNKNOWN');
|
return chalk.gray('UNKNOWN');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import validatePaths from '../util/validate-paths';
|
|||||||
import { getLinkedProject } from '../util/projects/link';
|
import { getLinkedProject } from '../util/projects/link';
|
||||||
import { ensureLink } from '../util/ensure-link';
|
import { ensureLink } from '../util/ensure-link';
|
||||||
import getScope from '../util/get-scope';
|
import getScope from '../util/get-scope';
|
||||||
|
import { isAPIError } from '../util/errors-ts';
|
||||||
|
|
||||||
const help = () => {
|
const help = () => {
|
||||||
console.log(`
|
console.log(`
|
||||||
@@ -152,16 +153,7 @@ export default async function main(client: Client) {
|
|||||||
|
|
||||||
const { currentTeam } = config;
|
const { currentTeam } = config;
|
||||||
|
|
||||||
try {
|
({ contextName } = await getScope(client));
|
||||||
({ 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'];
|
const nextTimestamp = argv['--next'];
|
||||||
|
|
||||||
@@ -228,8 +220,8 @@ export default async function main(client: Client) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await now.findDeployment(app);
|
await now.findDeployment(app);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.status === 404) {
|
if (isAPIError(err) && err.status === 404) {
|
||||||
debug('Ignore findDeployment 404');
|
debug('Ignore findDeployment 404');
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { writeToConfigFile, writeToAuthConfigFile } from '../util/config/files';
|
|||||||
import getArgs from '../util/get-args';
|
import getArgs from '../util/get-args';
|
||||||
import Client from '../util/client';
|
import Client from '../util/client';
|
||||||
import { getCommandName, getPkgName } from '../util/pkg-name';
|
import { getCommandName, getPkgName } from '../util/pkg-name';
|
||||||
|
import { isAPIError } from '../util/errors-ts';
|
||||||
|
import { errorToString } from '../util/is-error';
|
||||||
|
|
||||||
const help = () => {
|
const help = () => {
|
||||||
console.log(`
|
console.log(`
|
||||||
@@ -63,12 +65,14 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
useCurrentTeam: false,
|
useCurrentTeam: false,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.status === 403) {
|
if (isAPIError(err)) {
|
||||||
output.debug('Token is invalid so it cannot be revoked');
|
if (err.status === 403) {
|
||||||
} else if (err.status !== 200) {
|
output.debug('Token is invalid so it cannot be revoked');
|
||||||
output.debug(err?.message ?? '');
|
} else if (err.status !== 200) {
|
||||||
exitCode = 1;
|
output.debug(err?.message ?? '');
|
||||||
|
exitCode = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,8 +90,8 @@ export default async function main(client: Client): Promise<number> {
|
|||||||
writeToConfigFile(config);
|
writeToConfigFile(config);
|
||||||
writeToAuthConfigFile(authConfig);
|
writeToAuthConfigFile(authConfig);
|
||||||
output.debug('Configuration has been deleted');
|
output.debug('Configuration has been deleted');
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
output.debug(err?.message ?? '');
|
output.debug(errorToString(err));
|
||||||
exitCode = 1;
|
exitCode = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { getPkgName } from '../util/pkg-name';
|
|||||||
import getArgs from '../util/get-args';
|
import getArgs from '../util/get-args';
|
||||||
import Client from '../util/client';
|
import Client from '../util/client';
|
||||||
import { getDeployment } from '../util/get-deployment';
|
import { getDeployment } from '../util/get-deployment';
|
||||||
|
import { isAPIError } from '../util/errors-ts';
|
||||||
|
|
||||||
const help = () => {
|
const help = () => {
|
||||||
console.log(`
|
console.log(`
|
||||||
@@ -125,22 +126,24 @@ export default async function main(client: Client) {
|
|||||||
let deployment;
|
let deployment;
|
||||||
try {
|
try {
|
||||||
deployment = await getDeployment(client, id);
|
deployment = await getDeployment(client, id);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
output.stopSpinner();
|
output.stopSpinner();
|
||||||
|
|
||||||
if (err.status === 404) {
|
if (isAPIError(err)) {
|
||||||
output.error(
|
if (err.status === 404) {
|
||||||
`Failed to find deployment "${id}" in ${chalk.bold(contextName)}`
|
output.error(
|
||||||
);
|
`Failed to find deployment "${id}" in ${chalk.bold(contextName)}`
|
||||||
return 1;
|
);
|
||||||
}
|
return 1;
|
||||||
if (err.status === 403) {
|
}
|
||||||
output.error(
|
if (err.status === 403) {
|
||||||
`No permission to access deployment "${id}" in ${chalk.bold(
|
output.error(
|
||||||
contextName
|
`No permission to access deployment "${id}" in ${chalk.bold(
|
||||||
)}`
|
contextName
|
||||||
);
|
)}`
|
||||||
return 1;
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// unexpected
|
// unexpected
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
|
import { isAPIError } from '../../util/errors-ts';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
|
|
||||||
export default async function add(
|
export default async function add(
|
||||||
@@ -36,12 +37,12 @@ export default async function add(
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { name },
|
body: { name },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.status === 409) {
|
if (isAPIError(err) && err.status === 409) {
|
||||||
// project already exists, so we can
|
// project already exists, so we can
|
||||||
// show a success message
|
// show a success message
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const elapsed = ms(Date.now() - start);
|
const elapsed = ms(Date.now() - start);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import getScope from '../../util/get-scope';
|
|||||||
import handleError from '../../util/handle-error';
|
import handleError from '../../util/handle-error';
|
||||||
import logo from '../../util/output/logo';
|
import logo from '../../util/output/logo';
|
||||||
import { getPkgName } from '../../util/pkg-name';
|
import { getPkgName } from '../../util/pkg-name';
|
||||||
import validatePaths from '../../util/validate-paths';
|
|
||||||
import add from './add';
|
import add from './add';
|
||||||
import list from './list';
|
import list from './list';
|
||||||
import rm from './rm';
|
import rm from './rm';
|
||||||
@@ -48,7 +47,6 @@ const COMMAND_CONFIG = {
|
|||||||
ls: ['ls', 'list'],
|
ls: ['ls', 'list'],
|
||||||
add: ['add'],
|
add: ['add'],
|
||||||
rm: ['rm', 'remove'],
|
rm: ['rm', 'remove'],
|
||||||
connect: ['connect'],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function main(client: Client) {
|
export default async function main(client: Client) {
|
||||||
@@ -59,7 +57,6 @@ export default async function main(client: Client) {
|
|||||||
argv = getArgs(client.argv.slice(2), {
|
argv = getArgs(client.argv.slice(2), {
|
||||||
'--next': Number,
|
'--next': Number,
|
||||||
'-N': '--next',
|
'-N': '--next',
|
||||||
'--yes': Boolean,
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error);
|
handleError(error);
|
||||||
@@ -75,24 +72,7 @@ export default async function main(client: Client) {
|
|||||||
subcommand = argv._[0] || 'list';
|
subcommand = argv._[0] || 'list';
|
||||||
const args = argv._.slice(1);
|
const args = argv._.slice(1);
|
||||||
const { output } = client;
|
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) {
|
switch (subcommand) {
|
||||||
case 'ls':
|
case 'ls':
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import { emoji, prependEmoji } from '../../util/emoji';
|
import { emoji, prependEmoji } from '../../util/emoji';
|
||||||
|
import { isAPIError } from '../../util/errors-ts';
|
||||||
import confirm from '../../util/input/confirm';
|
import confirm from '../../util/input/confirm';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
|
|
||||||
@@ -32,8 +33,8 @@ export default async function rm(client: Client, args: string[]) {
|
|||||||
await client.fetch(`/v2/projects/${e(name)}`, {
|
await client.fetch(`/v2/projects/${e(name)}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.status === 404) {
|
if (isAPIError(err) && err.status === 404) {
|
||||||
client.output.error('No such project exists');
|
client.output.error('No such project exists');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,6 +179,8 @@ export default async function main(client: Client) {
|
|||||||
return pullResultCode;
|
return pullResultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.output.print('\n');
|
||||||
|
client.output.log('Downloading project settings');
|
||||||
await writeProjectSettings(cwd, project, org);
|
await writeProjectSettings(cwd, project, org);
|
||||||
|
|
||||||
const settingsStamp = stamp();
|
const settingsStamp = stamp();
|
||||||
|
|||||||
@@ -114,18 +114,7 @@ export default async function main(client: Client) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextName: string | null = null;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
output.spinner(
|
output.spinner(
|
||||||
`Fetching deployment(s) ${ids
|
`Fetching deployment(s) ${ids
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { getPkgName, getCommandName } from '../../util/pkg-name';
|
|||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import createTeam from '../../util/teams/create-team';
|
import createTeam from '../../util/teams/create-team';
|
||||||
import patchTeam from '../../util/teams/patch-team';
|
import patchTeam from '../../util/teams/patch-team';
|
||||||
|
import { errorToString, isError } from '../../util/is-error';
|
||||||
|
|
||||||
const validateSlugKeypress = (data: string, value: string) =>
|
const validateSlugKeypress = (data: string, value: string) =>
|
||||||
// TODO: the `value` here should contain the current value + the keypress
|
// TODO: the `value` here should contain the current value + the keypress
|
||||||
@@ -56,8 +57,8 @@ export default async function add(client: Client): Promise<number> {
|
|||||||
valid: team,
|
valid: team,
|
||||||
forceLowerCase: true,
|
forceLowerCase: true,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.message === 'USER_ABORT') {
|
if (isError(err) && err.message === 'USER_ABORT') {
|
||||||
output.log('Aborted');
|
output.log('Aborted');
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -71,10 +72,10 @@ export default async function add(client: Client): Promise<number> {
|
|||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
team = await createTeam(client, { slug });
|
team = await createTeam(client, { slug });
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
output.stopSpinner();
|
output.stopSpinner();
|
||||||
output.print(eraseLines(2));
|
output.print(eraseLines(2));
|
||||||
output.error(err.message);
|
output.error(errorToString(err));
|
||||||
}
|
}
|
||||||
} while (!team);
|
} while (!team);
|
||||||
|
|
||||||
@@ -92,8 +93,8 @@ export default async function add(client: Client): Promise<number> {
|
|||||||
label: `- ${teamNamePrefix}`,
|
label: `- ${teamNamePrefix}`,
|
||||||
validateKeypress: validateNameKeypress,
|
validateKeypress: validateNameKeypress,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.message === 'USER_ABORT') {
|
if (isError(err) && err.message === 'USER_ABORT') {
|
||||||
console.log(info('No name specified'));
|
console.log(info('No name specified'));
|
||||||
return gracefulExit();
|
return gracefulExit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { getCommandName } from '../../util/pkg-name';
|
|||||||
import { email as regexEmail } from '../../util/input/regexes';
|
import { email as regexEmail } from '../../util/input/regexes';
|
||||||
import getTeams from '../../util/teams/get-teams';
|
import getTeams from '../../util/teams/get-teams';
|
||||||
import inviteUserToTeam from '../../util/teams/invite-user-to-team';
|
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) =>
|
const validateEmail = (data: string) =>
|
||||||
regexEmail.test(data.trim()) || data.length === 0;
|
regexEmail.test(data.trim()) || data.length === 0;
|
||||||
@@ -67,17 +69,7 @@ export default async function invite(
|
|||||||
const currentTeam = teams.find(team => team.id === currentTeamId);
|
const currentTeam = teams.find(team => team.id === currentTeamId);
|
||||||
|
|
||||||
output.spinner('Fetching user information');
|
output.spinner('Fetching user information');
|
||||||
let user;
|
const user = await getUser(client);
|
||||||
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]);
|
domains.push(user.email.split('@')[1]);
|
||||||
|
|
||||||
@@ -107,8 +99,8 @@ export default async function invite(
|
|||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
const res = await inviteUserToTeam(client, currentTeam.id, email);
|
const res = await inviteUserToTeam(client, currentTeam.id, email);
|
||||||
userInfo = res.username;
|
userInfo = res.username;
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code === 'user_not_found') {
|
if (isAPIError(err) && err.code === 'user_not_found') {
|
||||||
output.error(`No user exists with the email address "${email}".`);
|
output.error(`No user exists with the email address "${email}".`);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -141,8 +133,8 @@ export default async function invite(
|
|||||||
validateValue: validateEmail,
|
validateValue: validateEmail,
|
||||||
autoComplete: value => emailAutoComplete(value, currentTeam.slug),
|
autoComplete: value => emailAutoComplete(value, currentTeam.slug),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.message !== 'USER_ABORT') {
|
if (!isError(err) || err.message !== 'USER_ABORT') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,7 +166,7 @@ export default async function invite(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
output.stopSpinner();
|
output.stopSpinner();
|
||||||
process.stderr.write(eraseLines(emails.length + 2));
|
process.stderr.write(eraseLines(emails.length + 2));
|
||||||
output.error(err.message);
|
output.error(errorToString(err));
|
||||||
hasError = true;
|
hasError = true;
|
||||||
for (const email of emails) {
|
for (const email of emails) {
|
||||||
output.log(`${chalk.cyan(chars.tick)} ${sentEmailPrefix}${email}`);
|
output.log(`${chalk.cyan(chars.tick)} ${sentEmailPrefix}${email}`);
|
||||||
|
|||||||
@@ -43,17 +43,7 @@ export default async function list(client: Client): Promise<number> {
|
|||||||
const accountIsCurrent = !currentTeam;
|
const accountIsCurrent = !currentTeam;
|
||||||
|
|
||||||
output.spinner('Fetching user information');
|
output.spinner('Fetching user information');
|
||||||
let user;
|
const user = await getUser(client);
|
||||||
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) {
|
if (accountIsCurrent) {
|
||||||
currentTeam = user.id;
|
currentTeam = user.id;
|
||||||
|
|||||||
@@ -41,18 +41,7 @@ export default async (client: Client): Promise<number> => {
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextName = null;
|
const { contextName } = await getScope(client, { getTeam: false });
|
||||||
|
|
||||||
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) {
|
if (client.stdout.isTTY) {
|
||||||
output.log(contextName);
|
output.log(contextName);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
import { isErrnoException, isError, errorToString } from './util/is-error';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Test to see if cwd has been deleted before
|
// Test to see if cwd has been deleted before
|
||||||
// importing 3rd party packages that might need cwd.
|
// importing 3rd party packages that might need cwd.
|
||||||
process.cwd();
|
process.cwd();
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
if (e && e.message && e.message.includes('uv_cwd')) {
|
if (isError(err) && err.message.includes('uv_cwd')) {
|
||||||
console.error('Error! The current working directory does not exist.');
|
console.error('Error! The current working directory does not exist.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -103,7 +104,7 @@ const main = async () => {
|
|||||||
},
|
},
|
||||||
{ permissive: true }
|
{ permissive: true }
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
handleError(err);
|
handleError(err);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -201,14 +202,11 @@ const main = async () => {
|
|||||||
// Ensure that the Vercel global configuration directory exists
|
// Ensure that the Vercel global configuration directory exists
|
||||||
try {
|
try {
|
||||||
await mkdirp(VERCEL_DIR);
|
await mkdirp(VERCEL_DIR);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
console.error(
|
output.error(
|
||||||
error(
|
`An unexpected error occurred while trying to create the global directory "${hp(
|
||||||
`${
|
VERCEL_DIR
|
||||||
'An unexpected error occurred while trying to create the ' +
|
)}" ${errorToString(err)}`
|
||||||
`global directory "${hp(VERCEL_DIR)}" `
|
|
||||||
}${err.message}`
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,13 +215,13 @@ const main = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
configExists = existsSync(VERCEL_CONFIG_PATH);
|
configExists = existsSync(VERCEL_CONFIG_PATH);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${
|
`${
|
||||||
'An unexpected error occurred while trying to find the ' +
|
'An unexpected error occurred while trying to find the ' +
|
||||||
`config file "${hp(VERCEL_CONFIG_PATH)}" `
|
`config file "${hp(VERCEL_CONFIG_PATH)}" `
|
||||||
}${err.message}`
|
}${errorToString(err)}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -241,7 +239,7 @@ const main = async () => {
|
|||||||
`${
|
`${
|
||||||
'An unexpected error occurred while trying to read the ' +
|
'An unexpected error occurred while trying to read the ' +
|
||||||
`config in "${hp(VERCEL_CONFIG_PATH)}" `
|
`config in "${hp(VERCEL_CONFIG_PATH)}" `
|
||||||
}${err.message}`
|
}${errorToString(err)}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -272,13 +270,13 @@ const main = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
configFiles.writeToConfigFile(config);
|
configFiles.writeToConfigFile(config);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${
|
`${
|
||||||
'An unexpected error occurred while trying to write the ' +
|
'An unexpected error occurred while trying to write the ' +
|
||||||
`default config to "${hp(VERCEL_CONFIG_PATH)}" `
|
`default config to "${hp(VERCEL_CONFIG_PATH)}" `
|
||||||
}${err.message}`
|
}${errorToString(err)}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -290,13 +288,13 @@ const main = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
authConfigExists = existsSync(VERCEL_AUTH_CONFIG_PATH);
|
authConfigExists = existsSync(VERCEL_AUTH_CONFIG_PATH);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${
|
`${
|
||||||
'An unexpected error occurred while trying to find the ' +
|
'An unexpected error occurred while trying to find the ' +
|
||||||
`auth file "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
`auth file "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
||||||
}${err.message}`
|
}${errorToString(err)}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -317,13 +315,13 @@ const main = async () => {
|
|||||||
if (authConfigExists) {
|
if (authConfigExists) {
|
||||||
try {
|
try {
|
||||||
authConfig = configFiles.readAuthConfigFile();
|
authConfig = configFiles.readAuthConfigFile();
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${
|
`${
|
||||||
'An unexpected error occurred while trying to read the ' +
|
'An unexpected error occurred while trying to read the ' +
|
||||||
`auth config in "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
`auth config in "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
||||||
}${err.message}`
|
}${errorToString(err)}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -345,13 +343,13 @@ const main = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
configFiles.writeToAuthConfigFile(authConfig);
|
configFiles.writeToAuthConfigFile(authConfig);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
console.error(
|
console.error(
|
||||||
error(
|
error(
|
||||||
`${
|
`${
|
||||||
'An unexpected error occurred while trying to write the ' +
|
'An unexpected error occurred while trying to write the ' +
|
||||||
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
|
||||||
}${err.message}`
|
}${errorToString(err)}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -543,8 +541,8 @@ const main = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
user = await getUser(client);
|
user = await getUser(client);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code === 'NOT_AUTHORIZED') {
|
if (isErrnoException(err) && err.code === 'NOT_AUTHORIZED') {
|
||||||
output.prettyError({
|
output.prettyError({
|
||||||
message: `You do not have access to the specified account`,
|
message: `You do not have access to the specified account`,
|
||||||
link: 'https://err.sh/vercel/scope-not-accessible',
|
link: 'https://err.sh/vercel/scope-not-accessible',
|
||||||
@@ -564,8 +562,8 @@ const main = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
teams = await getTeams(client);
|
teams = await getTeams(client);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code === 'not_authorized') {
|
if (isErrnoException(err) && err.code === 'not_authorized') {
|
||||||
output.prettyError({
|
output.prettyError({
|
||||||
message: `You do not have access to the specified team`,
|
message: `You do not have access to the specified team`,
|
||||||
link: 'https://err.sh/vercel/scope-not-accessible',
|
link: 'https://err.sh/vercel/scope-not-accessible',
|
||||||
@@ -594,8 +592,8 @@ const main = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const metric = metrics();
|
|
||||||
let exitCode;
|
let exitCode;
|
||||||
|
let metric: ReturnType<typeof metrics> | undefined;
|
||||||
const eventCategory = 'Exit Code';
|
const eventCategory = 'Exit Code';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -632,6 +630,9 @@ const main = async () => {
|
|||||||
case 'env':
|
case 'env':
|
||||||
func = require('./commands/env').default;
|
func = require('./commands/env').default;
|
||||||
break;
|
break;
|
||||||
|
case 'git':
|
||||||
|
func = require('./commands/git').default;
|
||||||
|
break;
|
||||||
case 'init':
|
case 'init':
|
||||||
func = require('./commands/init').default;
|
func = require('./commands/init').default;
|
||||||
break;
|
break;
|
||||||
@@ -695,13 +696,14 @@ const main = async () => {
|
|||||||
if (shouldCollectMetrics) {
|
if (shouldCollectMetrics) {
|
||||||
const category = 'Command Invocation';
|
const category = 'Command Invocation';
|
||||||
|
|
||||||
|
if (!metric) metric = metrics();
|
||||||
metric
|
metric
|
||||||
.timing(category, targetCommand, end, pkg.version)
|
.timing(category, targetCommand, end, pkg.version)
|
||||||
.event(category, targetCommand, pkg.version)
|
.event(category, targetCommand, pkg.version)
|
||||||
.send();
|
.send();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code === 'ENOTFOUND') {
|
if (isErrnoException(err) && err.code === 'ENOTFOUND') {
|
||||||
// Error message will look like the following:
|
// Error message will look like the following:
|
||||||
// "request to https://api.vercel.com/v2/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
|
// "request to https://api.vercel.com/v2/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
|
||||||
const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
|
const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
|
||||||
@@ -713,11 +715,16 @@ const main = async () => {
|
|||||||
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
|
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
output.debug(err.stack);
|
if (typeof err.stack === 'string') {
|
||||||
|
output.debug(err.stack);
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
if (
|
||||||
|
isErrnoException(err) &&
|
||||||
|
(err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED')
|
||||||
|
) {
|
||||||
output.prettyError(err);
|
output.prettyError(err);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -729,9 +736,10 @@ const main = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldCollectMetrics) {
|
if (shouldCollectMetrics) {
|
||||||
|
if (!metric) metric = metrics();
|
||||||
metric
|
metric
|
||||||
.event(eventCategory, '1', pkg.version)
|
.event(eventCategory, '1', pkg.version)
|
||||||
.exception(err.message)
|
.exception(errorToString(err))
|
||||||
.send();
|
.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -739,23 +747,24 @@ const main = async () => {
|
|||||||
// but instead show the message. Any error that is handled by this should
|
// but instead show the message. Any error that is handled by this should
|
||||||
// actually be handled in the sub command instead. Please make sure
|
// 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.
|
// that happens for anything that lands here. It should NOT bubble up to here.
|
||||||
if (err.code) {
|
if (isErrnoException(err)) {
|
||||||
output.debug(err.stack);
|
if (typeof err.stack === 'string') {
|
||||||
|
output.debug(err.stack);
|
||||||
|
}
|
||||||
output.prettyError(err);
|
output.prettyError(err);
|
||||||
} else {
|
} else {
|
||||||
await reportError(Sentry, client, err);
|
await reportError(Sentry, client, err);
|
||||||
|
|
||||||
// Otherwise it is an unexpected error and we should show the trace
|
// Otherwise it is an unexpected error and we should show the trace
|
||||||
// and an unexpected error message
|
// and an unexpected error message
|
||||||
output.error(
|
output.error(`An unexpected error occurred in ${subcommand}: ${err}`);
|
||||||
`An unexpected error occurred in ${subcommand}: ${err.stack}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldCollectMetrics) {
|
if (shouldCollectMetrics) {
|
||||||
|
if (!metric) metric = metrics();
|
||||||
metric.event(eventCategory, `${exitCode}`, pkg.version).send();
|
metric.event(eventCategory, `${exitCode}`, pkg.version).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -248,12 +248,40 @@ export interface ProjectEnvVariable {
|
|||||||
gitBranch?: string;
|
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 {
|
export interface Project extends ProjectSettings {
|
||||||
id: string;
|
id: string;
|
||||||
|
analytics?: {
|
||||||
|
id: string;
|
||||||
|
enabledAt?: number;
|
||||||
|
disabledAt?: number;
|
||||||
|
canceledAt?: number | null;
|
||||||
|
};
|
||||||
name: string;
|
name: string;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
|
link?: ProjectLinkData;
|
||||||
alias?: ProjectAliasTarget[];
|
alias?: ProjectAliasTarget[];
|
||||||
latestDeployments?: Partial<Deployment>[];
|
latestDeployments?: Partial<Deployment>[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,37 +68,39 @@ async function performCreateAlias(
|
|||||||
body: { alias },
|
body: { alias },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'cert_missing' || error.code === 'cert_expired') {
|
if (ERRORS.isAPIError(err)) {
|
||||||
return new ERRORS.CertMissing(alias);
|
if (err.code === 'cert_missing' || err.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 (error.code === 'forbidden') {
|
if (err.status === 409) {
|
||||||
return new ERRORS.DomainPermissionDenied(alias, contextName);
|
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.status === 400) {
|
|
||||||
return new ERRORS.DeploymentNotReady({ url: deployment.url });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export async function resolveBuilders(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === '@vercel/static') {
|
if (name === '@vercel/static' || name === '@now/static') {
|
||||||
// `@vercel/static` is a special-case built-in builder
|
// `@vercel/static` is a special-case built-in builder
|
||||||
builders.set(name, {
|
builders.set(name, {
|
||||||
builder: staticBuilder,
|
builder: staticBuilder,
|
||||||
|
|||||||
12
packages/cli/src/util/build/sort-builders.ts
Normal file
12
packages/cli/src/util/build/sort-builders.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import mimeTypes from 'mime-types';
|
import mimeTypes from 'mime-types';
|
||||||
import { basename, dirname, extname, join, relative, resolve } from 'path';
|
import {
|
||||||
|
basename,
|
||||||
|
dirname,
|
||||||
|
extname,
|
||||||
|
join,
|
||||||
|
relative,
|
||||||
|
resolve,
|
||||||
|
posix,
|
||||||
|
} from 'path';
|
||||||
import {
|
import {
|
||||||
Builder,
|
Builder,
|
||||||
BuildResultV2,
|
BuildResultV2,
|
||||||
@@ -15,30 +23,47 @@ import {
|
|||||||
download,
|
download,
|
||||||
EdgeFunction,
|
EdgeFunction,
|
||||||
BuildResultBuildOutput,
|
BuildResultBuildOutput,
|
||||||
|
getLambdaOptionsFromFunction,
|
||||||
|
normalizePath,
|
||||||
} from '@vercel/build-utils';
|
} from '@vercel/build-utils';
|
||||||
import pipe from 'promisepipe';
|
import pipe from 'promisepipe';
|
||||||
import { unzip } from './unzip';
|
import { unzip } from './unzip';
|
||||||
import { VERCEL_DIR } from '../projects/link';
|
import { VERCEL_DIR } from '../projects/link';
|
||||||
|
import { VercelConfig } from '@vercel/client';
|
||||||
|
|
||||||
|
const { normalize } = posix;
|
||||||
export const OUTPUT_DIR = join(VERCEL_DIR, 'output');
|
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(
|
export async function writeBuildResult(
|
||||||
outputDir: string,
|
outputDir: string,
|
||||||
buildResult: BuildResultV2 | BuildResultV3,
|
buildResult: BuildResultV2 | BuildResultV3,
|
||||||
build: Builder,
|
build: Builder,
|
||||||
builder: BuilderV2 | BuilderV3,
|
builder: BuilderV2 | BuilderV3,
|
||||||
builderPkg: PackageJson,
|
builderPkg: PackageJson,
|
||||||
cleanUrls?: boolean
|
vercelConfig: VercelConfig | null
|
||||||
) {
|
) {
|
||||||
const { version } = builder;
|
const { version } = builder;
|
||||||
if (typeof version !== 'number' || version === 2) {
|
if (typeof version !== 'number' || version === 2) {
|
||||||
return writeBuildResultV2(
|
return writeBuildResultV2(
|
||||||
outputDir,
|
outputDir,
|
||||||
buildResult as BuildResultV2,
|
buildResult as BuildResultV2,
|
||||||
cleanUrls
|
vercelConfig
|
||||||
);
|
);
|
||||||
} else if (version === 3) {
|
} else if (version === 3) {
|
||||||
return writeBuildResultV3(outputDir, buildResult as BuildResultV3, build);
|
return writeBuildResultV3(
|
||||||
|
outputDir,
|
||||||
|
buildResult as BuildResultV3,
|
||||||
|
build,
|
||||||
|
vercelConfig
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported Builder version \`${version}\` from "${builderPkg.name}"`
|
`Unsupported Builder version \`${version}\` from "${builderPkg.name}"`
|
||||||
@@ -67,6 +92,13 @@ export interface PathOverride {
|
|||||||
path?: string;
|
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
|
* Writes the output from the `build()` return value of a v2 Builder to
|
||||||
* the filesystem.
|
* the filesystem.
|
||||||
@@ -74,7 +106,7 @@ export interface PathOverride {
|
|||||||
async function writeBuildResultV2(
|
async function writeBuildResultV2(
|
||||||
outputDir: string,
|
outputDir: string,
|
||||||
buildResult: BuildResultV2,
|
buildResult: BuildResultV2,
|
||||||
cleanUrls?: boolean
|
vercelConfig: VercelConfig | null
|
||||||
) {
|
) {
|
||||||
if ('buildOutputPath' in buildResult) {
|
if ('buildOutputPath' in buildResult) {
|
||||||
await mergeBuilderOutput(outputDir, buildResult);
|
await mergeBuilderOutput(outputDir, buildResult);
|
||||||
@@ -84,16 +116,23 @@ async function writeBuildResultV2(
|
|||||||
const lambdas = new Map<Lambda, string>();
|
const lambdas = new Map<Lambda, string>();
|
||||||
const overrides: Record<string, PathOverride> = {};
|
const overrides: Record<string, PathOverride> = {};
|
||||||
for (const [path, output] of Object.entries(buildResult.output)) {
|
for (const [path, output] of Object.entries(buildResult.output)) {
|
||||||
|
const normalizedPath = stripDuplicateSlashes(path);
|
||||||
if (isLambda(output)) {
|
if (isLambda(output)) {
|
||||||
await writeLambda(outputDir, output, path, lambdas);
|
await writeLambda(outputDir, output, normalizedPath, undefined, lambdas);
|
||||||
} else if (isPrerender(output)) {
|
} else if (isPrerender(output)) {
|
||||||
await writeLambda(outputDir, output.lambda, path, lambdas);
|
await writeLambda(
|
||||||
|
outputDir,
|
||||||
|
output.lambda,
|
||||||
|
normalizedPath,
|
||||||
|
undefined,
|
||||||
|
lambdas
|
||||||
|
);
|
||||||
|
|
||||||
// Write the fallback file alongside the Lambda directory
|
// Write the fallback file alongside the Lambda directory
|
||||||
let fallback = output.fallback;
|
let fallback = output.fallback;
|
||||||
if (fallback) {
|
if (fallback) {
|
||||||
const ext = getFileExtension(fallback);
|
const ext = getFileExtension(fallback);
|
||||||
const fallbackName = `${path}.prerender-fallback${ext}`;
|
const fallbackName = `${normalizedPath}.prerender-fallback${ext}`;
|
||||||
const fallbackPath = join(outputDir, 'functions', fallbackName);
|
const fallbackPath = join(outputDir, 'functions', fallbackName);
|
||||||
const stream = fallback.toStream();
|
const stream = fallback.toStream();
|
||||||
await pipe(
|
await pipe(
|
||||||
@@ -109,7 +148,7 @@ async function writeBuildResultV2(
|
|||||||
const prerenderConfigPath = join(
|
const prerenderConfigPath = join(
|
||||||
outputDir,
|
outputDir,
|
||||||
'functions',
|
'functions',
|
||||||
`${path}.prerender-config.json`
|
`${normalizedPath}.prerender-config.json`
|
||||||
);
|
);
|
||||||
const prerenderConfig = {
|
const prerenderConfig = {
|
||||||
...output,
|
...output,
|
||||||
@@ -118,12 +157,20 @@ async function writeBuildResultV2(
|
|||||||
};
|
};
|
||||||
await fs.writeJSON(prerenderConfigPath, prerenderConfig, { spaces: 2 });
|
await fs.writeJSON(prerenderConfigPath, prerenderConfig, { spaces: 2 });
|
||||||
} else if (isFile(output)) {
|
} else if (isFile(output)) {
|
||||||
await writeStaticFile(outputDir, output, path, overrides, cleanUrls);
|
await writeStaticFile(
|
||||||
|
outputDir,
|
||||||
|
output,
|
||||||
|
normalizedPath,
|
||||||
|
overrides,
|
||||||
|
vercelConfig?.cleanUrls
|
||||||
|
);
|
||||||
} else if (isEdgeFunction(output)) {
|
} else if (isEdgeFunction(output)) {
|
||||||
await writeEdgeFunction(outputDir, output, path);
|
await writeEdgeFunction(outputDir, output, normalizedPath);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported output type: "${(output as any).type}" for ${path}`
|
`Unsupported output type: "${
|
||||||
|
(output as any).type
|
||||||
|
}" for ${normalizedPath}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,19 +184,28 @@ async function writeBuildResultV2(
|
|||||||
async function writeBuildResultV3(
|
async function writeBuildResultV3(
|
||||||
outputDir: string,
|
outputDir: string,
|
||||||
buildResult: BuildResultV3,
|
buildResult: BuildResultV3,
|
||||||
build: Builder
|
build: Builder,
|
||||||
|
vercelConfig: VercelConfig | null
|
||||||
) {
|
) {
|
||||||
const { output } = buildResult;
|
const { output } = buildResult;
|
||||||
const src = build.src;
|
const src = build.src;
|
||||||
if (typeof src !== 'string') {
|
if (typeof src !== 'string') {
|
||||||
throw new Error(`Expected "build.src" to be a 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 ext = extname(src);
|
||||||
const path = build.config?.zeroConfig
|
const path = stripDuplicateSlashes(
|
||||||
? src.substring(0, src.length - ext.length)
|
build.config?.zeroConfig ? src.substring(0, src.length - ext.length) : src
|
||||||
: src;
|
);
|
||||||
if (isLambda(output)) {
|
if (isLambda(output)) {
|
||||||
await writeLambda(outputDir, output, path);
|
await writeLambda(outputDir, output, path, functionConfiguration);
|
||||||
} else if (isEdgeFunction(output)) {
|
} else if (isEdgeFunction(output)) {
|
||||||
await writeEdgeFunction(outputDir, output, path);
|
await writeEdgeFunction(outputDir, output, path);
|
||||||
} else {
|
} else {
|
||||||
@@ -235,6 +291,7 @@ async function writeEdgeFunction(
|
|||||||
const config = {
|
const config = {
|
||||||
runtime: 'edge',
|
runtime: 'edge',
|
||||||
...edgeFunction,
|
...edgeFunction,
|
||||||
|
entrypoint: normalizePath(edgeFunction.entrypoint),
|
||||||
files: undefined,
|
files: undefined,
|
||||||
type: undefined,
|
type: undefined,
|
||||||
};
|
};
|
||||||
@@ -258,6 +315,7 @@ async function writeLambda(
|
|||||||
outputDir: string,
|
outputDir: string,
|
||||||
lambda: Lambda,
|
lambda: Lambda,
|
||||||
path: string,
|
path: string,
|
||||||
|
functionConfiguration?: FunctionConfiguration,
|
||||||
lambdas?: Map<Lambda, string>
|
lambdas?: Map<Lambda, string>
|
||||||
) {
|
) {
|
||||||
const dest = join(outputDir, 'functions', `${path}.func`);
|
const dest = join(outputDir, 'functions', `${path}.func`);
|
||||||
@@ -292,8 +350,14 @@ async function writeLambda(
|
|||||||
throw new Error('Malformed `Lambda` - no "files" present');
|
throw new Error('Malformed `Lambda` - no "files" present');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const memory = functionConfiguration?.memory ?? lambda.memory;
|
||||||
|
const maxDuration = functionConfiguration?.maxDuration ?? lambda.maxDuration;
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
...lambda,
|
...lambda,
|
||||||
|
handler: normalizePath(lambda.handler),
|
||||||
|
memory,
|
||||||
|
maxDuration,
|
||||||
type: undefined,
|
type: undefined,
|
||||||
files: undefined,
|
files: undefined,
|
||||||
zipBuffer: undefined,
|
zipBuffer: undefined,
|
||||||
|
|||||||
@@ -15,17 +15,19 @@ export default async function createCertForCns(
|
|||||||
try {
|
try {
|
||||||
const certificate = await issueCert(client, cns);
|
const certificate = await issueCert(client, cns);
|
||||||
return certificate;
|
return certificate;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'forbidden') {
|
if (ERRORS.isAPIError(err)) {
|
||||||
return new ERRORS.DomainPermissionDenied(error.domain, context);
|
if (err.code === 'forbidden') {
|
||||||
|
return new ERRORS.DomainPermissionDenied(err.domain, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappedError = mapCertError(err, cns);
|
||||||
|
if (mappedError) {
|
||||||
|
return mappedError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappedError = mapCertError(error, cns);
|
throw err;
|
||||||
if (mappedError) {
|
|
||||||
return mappedError;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
} finally {
|
} finally {
|
||||||
output.stopSpinner();
|
output.stopSpinner();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { readFileSync } from 'fs';
|
|||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import Client from '../client';
|
import Client from '../client';
|
||||||
import { Cert } from '../../types';
|
import { Cert } from '../../types';
|
||||||
|
import { isErrnoException } from '../is-error';
|
||||||
|
import { isAPIError } from '../errors-ts';
|
||||||
|
|
||||||
export default async function createCertFromFile(
|
export default async function createCertFromFile(
|
||||||
client: Client,
|
client: Client,
|
||||||
@@ -25,15 +27,15 @@ export default async function createCertFromFile(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
return certificate;
|
return certificate;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'ENOENT') {
|
if (isErrnoException(err) && err.code === 'ENOENT') {
|
||||||
return new Error(`The specified file "${error.path}" doesn't exist.`);
|
return new Error(`The specified file "${err.path}" doesn't exist.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.status < 500) {
|
if (isAPIError(err) && err.status < 500) {
|
||||||
return error;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,16 +22,18 @@ export default async function startCertOrder(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
return cert;
|
return cert;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'cert_order_not_found') {
|
if (ERRORS.isAPIError(err)) {
|
||||||
return new ERRORS.CertOrderNotFound(cns);
|
if (err.code === 'cert_order_not_found') {
|
||||||
|
return new ERRORS.CertOrderNotFound(cns);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappedError = mapCertError(err, cns);
|
||||||
|
if (mappedError) {
|
||||||
|
return mappedError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappedError = mapCertError(error, cns);
|
throw err;
|
||||||
if (mappedError) {
|
|
||||||
return mappedError;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import * as ERRORS from '../errors-ts';
|
|||||||
export default async function getCertById(client: Client, id: string) {
|
export default async function getCertById(client: Client, id: string) {
|
||||||
try {
|
try {
|
||||||
return await client.fetch<Cert>(`/v6/now/certs/${id}`);
|
return await client.fetch<Cert>(`/v6/now/certs/${id}`);
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'cert_not_found') {
|
if (ERRORS.isAPIError(err) && err.code === 'cert_not_found') {
|
||||||
return new ERRORS.CertNotFound(id);
|
return new ERRORS.CertNotFound(id);
|
||||||
}
|
}
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ export async function getCustomCertsForDomain(
|
|||||||
`/v5/now/certs?${stringify({ domain, custom: true })}`
|
`/v5/now/certs?${stringify({ domain, custom: true })}`
|
||||||
);
|
);
|
||||||
return certs;
|
return certs;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'forbidden') {
|
if (ERRORS.isAPIError(err) && err.code === 'forbidden') {
|
||||||
return new ERRORS.CertsPermissionDenied(context, domain);
|
return new ERRORS.CertsPermissionDenied(context, domain);
|
||||||
}
|
}
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import retry from 'async-retry';
|
import retry from 'async-retry';
|
||||||
import { Cert } from '../../types';
|
import { Cert } from '../../types';
|
||||||
import Client from '../client';
|
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
|
// 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
|
// otherwise we bail to handle the error in the upper level
|
||||||
@@ -10,13 +12,15 @@ export default async function issueCert(client: Client, cns: string[]) {
|
|||||||
try {
|
try {
|
||||||
return await client.fetch<Cert>('/v3/now/certs', {
|
return await client.fetch<Cert>('/v3/now/certs', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { domains: cns }
|
body: { domains: cns },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'configuration_error') {
|
if (isAPIError(err) && err.code === 'configuration_error') {
|
||||||
throw error;
|
throw err;
|
||||||
|
} else if (isError(err)) {
|
||||||
|
bail(err);
|
||||||
} else {
|
} else {
|
||||||
bail(error);
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import * as ERRORS from '../errors-ts';
|
import * as ERRORS from '../errors-ts';
|
||||||
|
|
||||||
export default function mapCertError(error: any, cns?: string[]) {
|
export default function mapCertError(error: ERRORS.APIError, cns?: string[]) {
|
||||||
const errorCode: string = error.code;
|
const errorCode: string = error.code;
|
||||||
if (errorCode === 'too_many_requests') {
|
if (errorCode === 'too_many_requests') {
|
||||||
return new ERRORS.TooManyRequests('certificates', error.retryAfter);
|
const retryAfter =
|
||||||
|
typeof error.retryAfter === 'number' ? error.retryAfter : 0;
|
||||||
|
return new ERRORS.TooManyRequests('certificates', retryAfter);
|
||||||
}
|
}
|
||||||
if (errorCode === 'not_found') {
|
if (errorCode === 'not_found') {
|
||||||
return new ERRORS.DomainNotFound(error.domain);
|
return new ERRORS.DomainNotFound(error.domain);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import error from '../output/error';
|
|||||||
import highlight from '../output/highlight';
|
import highlight from '../output/highlight';
|
||||||
import { VercelConfig } from '../dev/types';
|
import { VercelConfig } from '../dev/types';
|
||||||
import { AuthConfig, GlobalConfig } from '../../types';
|
import { AuthConfig, GlobalConfig } from '../../types';
|
||||||
|
import { isErrnoException, isError } from '../is-error';
|
||||||
|
|
||||||
const VERCEL_DIR = getGlobalPathConfig();
|
const VERCEL_DIR = getGlobalPathConfig();
|
||||||
const CONFIG_FILE_PATH = join(VERCEL_DIR, 'config.json');
|
const CONFIG_FILE_PATH = join(VERCEL_DIR, 'config.json');
|
||||||
@@ -25,25 +26,27 @@ export const readConfigFile = (): GlobalConfig => {
|
|||||||
export const writeToConfigFile = (stuff: GlobalConfig): void => {
|
export const writeToConfigFile = (stuff: GlobalConfig): void => {
|
||||||
try {
|
try {
|
||||||
return writeJSON.sync(CONFIG_FILE_PATH, stuff, { indent: 2 });
|
return writeJSON.sync(CONFIG_FILE_PATH, stuff, { indent: 2 });
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code === 'EPERM') {
|
if (isErrnoException(err)) {
|
||||||
console.error(
|
if (isErrnoException(err) && err.code === 'EPERM') {
|
||||||
error(
|
console.error(
|
||||||
`Not able to create ${highlight(
|
error(
|
||||||
CONFIG_FILE_PATH
|
`Not able to create ${highlight(
|
||||||
)} (operation not permitted).`
|
CONFIG_FILE_PATH
|
||||||
)
|
)} (operation not permitted).`
|
||||||
);
|
)
|
||||||
process.exit(1);
|
);
|
||||||
} else if (err.code === 'EBADF') {
|
process.exit(1);
|
||||||
console.error(
|
} else if (err.code === 'EBADF') {
|
||||||
error(
|
console.error(
|
||||||
`Not able to create ${highlight(
|
error(
|
||||||
CONFIG_FILE_PATH
|
`Not able to create ${highlight(
|
||||||
)} (bad file descriptor).`
|
CONFIG_FILE_PATH
|
||||||
)
|
)} (bad file descriptor).`
|
||||||
);
|
)
|
||||||
process.exit(1);
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err;
|
throw err;
|
||||||
@@ -65,25 +68,27 @@ export const writeToAuthConfigFile = (authConfig: AuthConfig) => {
|
|||||||
indent: 2,
|
indent: 2,
|
||||||
mode: 0o600,
|
mode: 0o600,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code === 'EPERM') {
|
if (isErrnoException(err)) {
|
||||||
console.error(
|
if (err.code === 'EPERM') {
|
||||||
error(
|
console.error(
|
||||||
`Not able to create ${highlight(
|
error(
|
||||||
AUTH_CONFIG_FILE_PATH
|
`Not able to create ${highlight(
|
||||||
)} (operation not permitted).`
|
AUTH_CONFIG_FILE_PATH
|
||||||
)
|
)} (operation not permitted).`
|
||||||
);
|
)
|
||||||
process.exit(1);
|
);
|
||||||
} else if (err.code === 'EBADF') {
|
process.exit(1);
|
||||||
console.error(
|
} else if (err.code === 'EBADF') {
|
||||||
error(
|
console.error(
|
||||||
`Not able to create ${highlight(
|
error(
|
||||||
AUTH_CONFIG_FILE_PATH
|
`Not able to create ${highlight(
|
||||||
)} (bad file descriptor).`
|
AUTH_CONFIG_FILE_PATH
|
||||||
)
|
)} (bad file descriptor).`
|
||||||
);
|
)
|
||||||
process.exit(1);
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err;
|
throw err;
|
||||||
@@ -123,12 +128,14 @@ export function readLocalConfig(
|
|||||||
if (existsSync(target)) {
|
if (existsSync(target)) {
|
||||||
config = loadJSON.sync(target);
|
config = loadJSON.sync(target);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.name === 'JSONError') {
|
if (isError(err) && err.name === 'JSONError') {
|
||||||
console.error(error(err.message));
|
console.error(error(err.message));
|
||||||
} else {
|
} else if (isErrnoException(err)) {
|
||||||
const code = err.code ? ` (${err.code})` : '';
|
const code = err.code ? ` (${err.code})` : '';
|
||||||
console.error(error(`Failed to read config file: ${target}${code}`));
|
console.error(error(`Failed to read config file: ${target}${code}`));
|
||||||
|
} else {
|
||||||
|
console.error(err);
|
||||||
}
|
}
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
159
packages/cli/src/util/create-git-meta.ts
Normal file
159
packages/cli/src/util/create-git-meta.ts
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -20,103 +20,105 @@ export default async function createDeploy(
|
|||||||
): Promise<any | DeploymentError> {
|
): Promise<any | DeploymentError> {
|
||||||
try {
|
try {
|
||||||
return await now.create(paths, createArgs, org, isSettingUpProject, cwd);
|
return await now.create(paths, createArgs, org, isSettingUpProject, cwd);
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'rate_limited') {
|
if (ERRORS_TS.isAPIError(err)) {
|
||||||
throw new ERRORS_TS.DeploymentsRateLimited(error.message);
|
if (err.code === 'rate_limited') {
|
||||||
}
|
throw new ERRORS_TS.DeploymentsRateLimited(err.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return createDeploy(
|
// Means that the domain used as a suffix no longer exists
|
||||||
client,
|
if (err.code === 'domain_missing') {
|
||||||
now,
|
throw new ERRORS_TS.DomainNotFound(err.value);
|
||||||
contextName,
|
}
|
||||||
paths,
|
|
||||||
createArgs,
|
|
||||||
org,
|
|
||||||
isSettingUpProject
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error.code === 'not_found') {
|
if (err.code === 'domain_not_found' && err.domain) {
|
||||||
throw new ERRORS_TS.DeploymentNotFound({ context: contextName });
|
throw new ERRORS_TS.DomainNotFound(err.domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
const certError = mapCertError(error);
|
// This error occures when a domain used in the `alias`
|
||||||
if (certError) {
|
// is not yet verified
|
||||||
return certError;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the error is unknown, we just throw
|
// If the error is unknown, we just throw
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +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 } 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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
DeploymentNotFound,
|
DeploymentNotFound,
|
||||||
DeploymentPermissionDenied,
|
DeploymentPermissionDenied,
|
||||||
InvalidDeploymentId,
|
InvalidDeploymentId,
|
||||||
|
isAPIError,
|
||||||
} from '../errors-ts';
|
} from '../errors-ts';
|
||||||
import mapCertError from '../certs/map-cert-error';
|
import mapCertError from '../certs/map-cert-error';
|
||||||
|
|
||||||
@@ -26,23 +27,25 @@ export default async function getDeploymentByIdOrHost(
|
|||||||
)
|
)
|
||||||
: await getDeploymentById(client, idOrHost, apiVersion);
|
: await getDeploymentById(client, idOrHost, apiVersion);
|
||||||
return deployment;
|
return deployment;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.status === 404) {
|
if (isAPIError(err)) {
|
||||||
return new DeploymentNotFound({ id: idOrHost, context: contextName });
|
if (err.status === 404) {
|
||||||
}
|
return new DeploymentNotFound({ id: idOrHost, context: contextName });
|
||||||
if (error.status === 403) {
|
}
|
||||||
return new DeploymentPermissionDenied(idOrHost, contextName);
|
if (err.status === 403) {
|
||||||
}
|
return new DeploymentPermissionDenied(idOrHost, contextName);
|
||||||
if (error.status === 400 && error.message.includes('`id`')) {
|
}
|
||||||
return new InvalidDeploymentId(idOrHost);
|
if (err.status === 400 && err.message.includes('`id`')) {
|
||||||
|
return new InvalidDeploymentId(idOrHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
const certError = mapCertError(err);
|
||||||
|
if (certError) {
|
||||||
|
return certError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const certError = mapCertError(error);
|
throw err;
|
||||||
if (certError) {
|
|
||||||
return certError;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
import { BuildsManifest } from '../../commands/build';
|
||||||
|
|
||||||
export default async function getPrebuiltJson(directory: string) {
|
export default async function getPrebuiltJson(
|
||||||
|
directory: string
|
||||||
|
): Promise<BuildsManifest | null> {
|
||||||
try {
|
try {
|
||||||
return await fs.readJSON(join(directory, '.vercel/output/builds.json'));
|
return await fs.readJSON(join(directory, '.vercel/output/builds.json'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -11,13 +11,11 @@ import cliPkg from '../pkg';
|
|||||||
|
|
||||||
import cmd from '../output/cmd';
|
import cmd from '../output/cmd';
|
||||||
import { Output } from '../output';
|
import { Output } from '../output';
|
||||||
import { getDistTag } from '../get-dist-tag';
|
|
||||||
import { NoBuilderCacheError } from '../errors-ts';
|
import { NoBuilderCacheError } from '../errors-ts';
|
||||||
|
|
||||||
import * as staticBuilder from './static-builder';
|
import * as staticBuilder from './static-builder';
|
||||||
import { BuilderWithPackage } from './types';
|
import { BuilderWithPackage } from './types';
|
||||||
|
import { isErrnoException } from '../is-error';
|
||||||
type CliPackageJson = typeof cliPkg;
|
|
||||||
|
|
||||||
const require_: typeof require = eval('require');
|
const require_: typeof require = eval('require');
|
||||||
|
|
||||||
@@ -37,8 +35,6 @@ const localBuilders: { [key: string]: BuilderWithPackage } = {
|
|||||||
'@vercel/static': createStaticBuilder('vercel'),
|
'@vercel/static': createStaticBuilder('vercel'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const distTag = getDistTag(cliPkg.version);
|
|
||||||
|
|
||||||
export const cacheDirPromise = prepareCacheDir();
|
export const cacheDirPromise = prepareCacheDir();
|
||||||
export const builderDirPromise = prepareBuilderDir();
|
export const builderDirPromise = prepareBuilderDir();
|
||||||
|
|
||||||
@@ -65,8 +61,8 @@ export async function prepareBuilderDir() {
|
|||||||
try {
|
try {
|
||||||
const buildersPkg = join(builderDir, 'package.json');
|
const buildersPkg = join(builderDir, 'package.json');
|
||||||
await writeJSON(buildersPkg, { private: true }, { flag: 'wx' });
|
await writeJSON(buildersPkg, { private: true }, { flag: 'wx' });
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code !== 'EEXIST') {
|
if (!isErrnoException(err) || err.code !== 'EEXIST') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,9 +98,8 @@ function parseVersionSafe(rawSpec: string) {
|
|||||||
|
|
||||||
export function filterPackage(
|
export function filterPackage(
|
||||||
builderSpec: string,
|
builderSpec: string,
|
||||||
distTag: string,
|
|
||||||
buildersPkg: PackageJson,
|
buildersPkg: PackageJson,
|
||||||
cliPkg: Partial<CliPackageJson>
|
cliPkg: Partial<PackageJson>
|
||||||
) {
|
) {
|
||||||
if (builderSpec in localBuilders) return false;
|
if (builderSpec in localBuilders) return false;
|
||||||
const parsed = npa(builderSpec);
|
const parsed = npa(builderSpec);
|
||||||
@@ -126,31 +121,6 @@ export function filterPackage(
|
|||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +153,7 @@ export async function installBuilders(
|
|||||||
|
|
||||||
// Filter out any packages that come packaged with Vercel CLI
|
// Filter out any packages that come packaged with Vercel CLI
|
||||||
const packagesToInstall = packages.filter(p =>
|
const packagesToInstall = packages.filter(p =>
|
||||||
filterPackage(p, distTag, buildersPkgBefore, cliPkg)
|
filterPackage(p, buildersPkgBefore, cliPkg)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (packagesToInstall.length === 0) {
|
if (packagesToInstall.length === 0) {
|
||||||
@@ -362,8 +332,12 @@ export async function getBuilder(
|
|||||||
builder: Object.freeze(mod),
|
builder: Object.freeze(mod),
|
||||||
package: Object.freeze(pkg),
|
package: Object.freeze(pkg),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code === 'MODULE_NOT_FOUND' && !isRetry) {
|
if (
|
||||||
|
isErrnoException(err) &&
|
||||||
|
err.code === 'MODULE_NOT_FOUND' &&
|
||||||
|
!isRetry
|
||||||
|
) {
|
||||||
output.debug(
|
output.debug(
|
||||||
`Attempted to require ${requirePath}, but it is not installed`
|
`Attempted to require ${requirePath}, but it is not installed`
|
||||||
);
|
);
|
||||||
@@ -392,20 +366,13 @@ export function isBundledBuilder(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bundledVersion = dependencies[parsed.name];
|
const inCliDependencyList = !!dependencies[parsed.name];
|
||||||
if (bundledVersion) {
|
const inScope = parsed.scope === '@vercel';
|
||||||
if (parsed.type === 'tag') {
|
const isVersionedReference = ['tag', 'version', 'range'].includes(
|
||||||
if (parsed.fetchSpec === 'canary') {
|
parsed.type
|
||||||
return bundledVersion.includes('canary');
|
);
|
||||||
} else if (parsed.fetchSpec === 'latest') {
|
|
||||||
return !bundledVersion.includes('canary');
|
|
||||||
}
|
|
||||||
} else if (parsed.type === 'version') {
|
|
||||||
return parsed.fetchSpec === bundledVersion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return inCliDependencyList && inScope && isVersionedReference;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPackageName(
|
function getPackageName(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import ms from 'ms';
|
|||||||
import bytes from 'bytes';
|
import bytes from 'bytes';
|
||||||
import { delimiter, dirname, join } from 'path';
|
import { delimiter, dirname, join } from 'path';
|
||||||
import { fork, ChildProcess } from 'child_process';
|
import { fork, ChildProcess } from 'child_process';
|
||||||
import { createFunction } from '@zeit/fun';
|
import { createFunction } from '@vercel/fun';
|
||||||
import {
|
import {
|
||||||
Builder,
|
Builder,
|
||||||
BuildOptions,
|
BuildOptions,
|
||||||
|
|||||||
@@ -94,6 +94,12 @@ import { ProjectEnvVariable, ProjectSettings } from '../../types';
|
|||||||
import exposeSystemEnvs from './expose-system-envs';
|
import exposeSystemEnvs from './expose-system-envs';
|
||||||
import { treeKill } from '../tree-kill';
|
import { treeKill } from '../tree-kill';
|
||||||
import { nodeHeadersToFetchHeaders } from './headers';
|
import { nodeHeadersToFetchHeaders } from './headers';
|
||||||
|
import {
|
||||||
|
errorToString,
|
||||||
|
isErrnoException,
|
||||||
|
isError,
|
||||||
|
isSpawnError,
|
||||||
|
} from '../is-error';
|
||||||
|
|
||||||
const frontendRuntimeSet = new Set(
|
const frontendRuntimeSet = new Set(
|
||||||
frameworkList.map(f => f.useRuntime?.use || '@vercel/static-build')
|
frameworkList.map(f => f.useRuntime?.use || '@vercel/static-build')
|
||||||
@@ -340,8 +346,8 @@ export default class DevServer {
|
|||||||
}
|
}
|
||||||
fileChanged(name, changed, removed);
|
fileChanged(name, changed, removed);
|
||||||
this.output.debug(`File created: ${name}`);
|
this.output.debug(`File created: ${name}`);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code === 'ENOENT') {
|
if (isErrnoException(err) && err.code === 'ENOENT') {
|
||||||
this.output.debug(`File created, but has since been deleted: ${name}`);
|
this.output.debug(`File created, but has since been deleted: ${name}`);
|
||||||
fileRemoved(name, this.files, changed, removed);
|
fileRemoved(name, this.files, changed, removed);
|
||||||
} else {
|
} else {
|
||||||
@@ -375,8 +381,8 @@ export default class DevServer {
|
|||||||
this.files[name] = await FileFsRef.fromFsPath({ fsPath });
|
this.files[name] = await FileFsRef.fromFsPath({ fsPath });
|
||||||
fileChanged(name, changed, removed);
|
fileChanged(name, changed, removed);
|
||||||
this.output.debug(`File modified: ${name}`);
|
this.output.debug(`File modified: ${name}`);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code === 'ENOENT') {
|
if (isErrnoException(err) && err.code === 'ENOENT') {
|
||||||
this.output.debug(`File modified, but has since been deleted: ${name}`);
|
this.output.debug(`File modified, but has since been deleted: ${name}`);
|
||||||
fileRemoved(name, this.files, changed, removed);
|
fileRemoved(name, this.files, changed, removed);
|
||||||
} else {
|
} else {
|
||||||
@@ -507,8 +513,8 @@ export default class DevServer {
|
|||||||
this.output.debug(`Using local env: ${filePath}`);
|
this.output.debug(`Using local env: ${filePath}`);
|
||||||
env = parseDotenv(dotenv);
|
env = parseDotenv(dotenv);
|
||||||
env = this.injectSystemValuesInDotenv(env);
|
env = this.injectSystemValuesInDotenv(env);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code !== 'ENOENT') {
|
if (!isErrnoException(err) || err.code !== 'ENOENT') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -558,9 +564,8 @@ export default class DevServer {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
await this.validateVercelConfig(vercelConfig);
|
await this.validateVercelConfig(vercelConfig);
|
||||||
const { error: routeError, routes: maybeRoutes } = getTransformedRoutes({
|
const { error: routeError, routes: maybeRoutes } =
|
||||||
nowConfig: vercelConfig,
|
getTransformedRoutes(vercelConfig);
|
||||||
});
|
|
||||||
if (routeError) {
|
if (routeError) {
|
||||||
this.output.prettyError(routeError);
|
this.output.prettyError(routeError);
|
||||||
await this.exit();
|
await this.exit();
|
||||||
@@ -720,13 +725,15 @@ export default class DevServer {
|
|||||||
const parsed: WithFileNameSymbol<T> = JSON.parse(raw);
|
const parsed: WithFileNameSymbol<T> = JSON.parse(raw);
|
||||||
parsed[fileNameSymbol] = rel;
|
parsed[fileNameSymbol] = rel;
|
||||||
return parsed;
|
return parsed;
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code === 'ENOENT') {
|
if (isError(err)) {
|
||||||
this.output.debug(`No \`${rel}\` file present`);
|
if (isErrnoException(err) && err.code === 'ENOENT') {
|
||||||
} else if (err.name === 'SyntaxError') {
|
this.output.debug(`No \`${rel}\` file present`);
|
||||||
this.output.warn(
|
} else if (err.name === 'SyntaxError') {
|
||||||
`There is a syntax error in the \`${rel}\` file: ${err.message}`
|
this.output.warn(
|
||||||
);
|
`There is a syntax error in the \`${rel}\` file: ${err.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@@ -852,22 +859,26 @@ export default class DevServer {
|
|||||||
while (typeof address !== 'string') {
|
while (typeof address !== 'string') {
|
||||||
try {
|
try {
|
||||||
address = await listen(this.server, ...listenSpec);
|
address = await listen(this.server, ...listenSpec);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
this.output.debug(`Got listen error: ${err.code}`);
|
if (isErrnoException(err)) {
|
||||||
if (err.code === 'EADDRINUSE') {
|
this.output.debug(`Got listen error: ${err.code}`);
|
||||||
if (typeof listenSpec[0] === 'number') {
|
if (err.code === 'EADDRINUSE') {
|
||||||
// Increase port and try again
|
if (typeof listenSpec[0] === 'number') {
|
||||||
this.output.note(
|
// Increase port and try again
|
||||||
`Requested port ${chalk.yellow(
|
this.output.note(
|
||||||
String(listenSpec[0])
|
`Requested port ${chalk.yellow(
|
||||||
)} is already in use`
|
String(listenSpec[0])
|
||||||
);
|
)} is already in use`
|
||||||
listenSpec[0]++;
|
);
|
||||||
} else {
|
listenSpec[0]++;
|
||||||
this.output.error(
|
} else {
|
||||||
`Requested socket ${chalk.cyan(listenSpec[0])} is already in use`
|
this.output.error(
|
||||||
);
|
`Requested socket ${chalk.cyan(
|
||||||
process.exit(1);
|
listenSpec[0]
|
||||||
|
)} is already in use`
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
@@ -1029,12 +1040,8 @@ export default class DevServer {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(ops);
|
await Promise.all(ops);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
// Node 8 doesn't have a code for that error
|
if (isErrnoException(err) && err.code === 'ERR_SERVER_NOT_RUNNING') {
|
||||||
if (
|
|
||||||
err.code === 'ERR_SERVER_NOT_RUNNING' ||
|
|
||||||
err.message === 'Not running'
|
|
||||||
) {
|
|
||||||
process.exit(exitCode || 0);
|
process.exit(exitCode || 0);
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
@@ -1304,13 +1311,16 @@ export default class DevServer {
|
|||||||
try {
|
try {
|
||||||
const vercelConfig = await this.getVercelConfig();
|
const vercelConfig = await this.getVercelConfig();
|
||||||
await this.serveProjectAsNowV2(req, res, requestId, vercelConfig);
|
await this.serveProjectAsNowV2(req, res, requestId, vercelConfig);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
this.output.debug(err.stack);
|
|
||||||
|
if (isError(err) && typeof err.stack === 'string') {
|
||||||
|
this.output.debug(err.stack);
|
||||||
|
}
|
||||||
|
|
||||||
if (!res.finished) {
|
if (!res.finished) {
|
||||||
res.statusCode = 500;
|
res.statusCode = 500;
|
||||||
res.end(err.message);
|
res.end(errorToString(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1534,16 +1544,16 @@ export default class DevServer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
// `startDevServer()` threw an error. Most likely this means the dev
|
// `startDevServer()` threw an error. Most likely this means the dev
|
||||||
// server process exited before sending the port information message
|
// server process exited before sending the port information message
|
||||||
// (missing dependency at runtime, for example).
|
// (missing dependency at runtime, for example).
|
||||||
if (err.code === 'ENOENT') {
|
if (isSpawnError(err) && err.code === 'ENOENT') {
|
||||||
err.message = `Command not found: ${chalk.cyan(
|
err.message = `Command not found: ${chalk.cyan(
|
||||||
err.path,
|
err.path,
|
||||||
...err.spawnargs
|
...err.spawnargs
|
||||||
)}\nPlease ensure that ${cmd(err.path)} is properly installed`;
|
)}\nPlease ensure that ${cmd(err.path!)} is properly installed`;
|
||||||
err.link = 'https://vercel.link/command-not-found';
|
(err as any).link = 'https://vercel.link/command-not-found';
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.sendError(
|
await this.sendError(
|
||||||
@@ -1846,16 +1856,16 @@ export default class DevServer {
|
|||||||
buildEnv: { ...envConfigs.buildEnv },
|
buildEnv: { ...envConfigs.buildEnv },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
// `startDevServer()` threw an error. Most likely this means the dev
|
// `startDevServer()` threw an error. Most likely this means the dev
|
||||||
// server process exited before sending the port information message
|
// server process exited before sending the port information message
|
||||||
// (missing dependency at runtime, for example).
|
// (missing dependency at runtime, for example).
|
||||||
if (err.code === 'ENOENT') {
|
if (isSpawnError(err) && err.code === 'ENOENT') {
|
||||||
err.message = `Command not found: ${chalk.cyan(
|
err.message = `Command not found: ${chalk.cyan(
|
||||||
err.path,
|
err.path,
|
||||||
...err.spawnargs
|
...err.spawnargs
|
||||||
)}\nPlease ensure that ${cmd(err.path)} is properly installed`;
|
)}\nPlease ensure that ${cmd(err.path!)} is properly installed`;
|
||||||
err.link = 'https://vercel.link/command-not-found';
|
(err as any).link = 'https://vercel.link/command-not-found';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.output.prettyError(err);
|
this.output.prettyError(err);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import http from 'http';
|
import http from 'http';
|
||||||
import { ChildProcess } from 'child_process';
|
import { ChildProcess } from 'child_process';
|
||||||
import { Lambda as FunLambda } from '@zeit/fun';
|
import { Lambda as FunLambda } from '@vercel/fun';
|
||||||
import {
|
import {
|
||||||
Builder as BuildConfig,
|
Builder as BuildConfig,
|
||||||
BuildOptions,
|
BuildOptions,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
DNSInvalidPort,
|
DNSInvalidPort,
|
||||||
DNSInvalidType,
|
DNSInvalidType,
|
||||||
DNSConflictingRecord,
|
DNSConflictingRecord,
|
||||||
|
isAPIError,
|
||||||
} from '../errors-ts';
|
} from '../errors-ts';
|
||||||
import { DNSRecordData } from '../../types';
|
import { DNSRecordData } from '../../types';
|
||||||
|
|
||||||
@@ -26,32 +27,34 @@ export default async function addDNSRecord(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
return record;
|
return record;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.status === 400 && error.code === 'invalid_type') {
|
if (isAPIError(err)) {
|
||||||
return new DNSInvalidType(recordData.type);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.status === 400 && error.message.includes('port')) {
|
throw err;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DNSRecord, PaginationOptions } from '../../types';
|
import { DNSRecord, PaginationOptions } from '../../types';
|
||||||
import { DomainNotFound } from '../errors-ts';
|
import { DomainNotFound, isAPIError } from '../errors-ts';
|
||||||
import { Output } from '../output';
|
import { Output } from '../output';
|
||||||
import Client from '../client';
|
import Client from '../client';
|
||||||
|
|
||||||
@@ -27,10 +27,10 @@ export default async function getDomainDNSRecords(
|
|||||||
|
|
||||||
const data = await client.fetch<Response>(url);
|
const data = await client.fetch<Response>(url);
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'not_found') {
|
if (isAPIError(err) && err.code === 'not_found') {
|
||||||
return new DomainNotFound(domain);
|
return new DomainNotFound(domain);
|
||||||
}
|
}
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { Response } from 'node-fetch';
|
import { Response } from 'node-fetch';
|
||||||
import { DomainNotFound, InvalidDomain } from '../errors-ts';
|
import { DomainNotFound, InvalidDomain, isAPIError } from '../errors-ts';
|
||||||
import Client from '../client';
|
import Client from '../client';
|
||||||
|
|
||||||
type JSONResponse = {
|
type JSONResponse = {
|
||||||
@@ -33,15 +33,17 @@ export default async function importZonefile(
|
|||||||
|
|
||||||
const { recordIds } = (await res.json()) as JSONResponse;
|
const { recordIds } = (await res.json()) as JSONResponse;
|
||||||
return recordIds;
|
return recordIds;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'not_found') {
|
if (isAPIError(err)) {
|
||||||
return new DomainNotFound(domain, contextName);
|
if (err.code === 'not_found') {
|
||||||
|
return new DomainNotFound(domain, contextName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.code === 'invalid_domain') {
|
||||||
|
return new InvalidDomain(domain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.code === 'invalid_domain') {
|
throw err;
|
||||||
return new InvalidDomain(domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import retry from 'async-retry';
|
import retry from 'async-retry';
|
||||||
import { DomainAlreadyExists, InvalidDomain } from '../errors-ts';
|
import { DomainAlreadyExists, InvalidDomain, isAPIError } from '../errors-ts';
|
||||||
import { Domain } from '../../types';
|
import { Domain } from '../../types';
|
||||||
import Client from '../client';
|
import Client from '../client';
|
||||||
|
|
||||||
@@ -29,16 +29,18 @@ async function performAddRequest(client: Client, domainName: string) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
return domain;
|
return domain;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'invalid_name') {
|
if (isAPIError(err)) {
|
||||||
return new InvalidDomain(domainName);
|
if (err.code === 'invalid_name') {
|
||||||
|
return new InvalidDomain(domainName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.code === 'domain_already_exists') {
|
||||||
|
return new DomainAlreadyExists(domainName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.code === 'domain_already_exists') {
|
throw err;
|
||||||
return new DomainAlreadyExists(domainName);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ retries: 5, maxTimeout: 8000 }
|
{ retries: 5, maxTimeout: 8000 }
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import Client from '../client';
|
import Client from '../client';
|
||||||
import { Domain } from '../../types';
|
import { Domain } from '../../types';
|
||||||
import { DomainPermissionDenied, DomainNotFound } from '../errors-ts';
|
import {
|
||||||
|
DomainPermissionDenied,
|
||||||
|
DomainNotFound,
|
||||||
|
isAPIError,
|
||||||
|
} from '../errors-ts';
|
||||||
|
|
||||||
type Response = {
|
type Response = {
|
||||||
domain: Domain;
|
domain: Domain;
|
||||||
@@ -25,15 +29,17 @@ export default async function getDomainByName(
|
|||||||
`/v4/domains/${encodeURIComponent(domainName)}`
|
`/v4/domains/${encodeURIComponent(domainName)}`
|
||||||
);
|
);
|
||||||
return domain;
|
return domain;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.status === 404) {
|
if (isAPIError(err)) {
|
||||||
return new DomainNotFound(domainName, contextName);
|
if (err.status === 404) {
|
||||||
|
return new DomainNotFound(domainName, contextName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.status === 403) {
|
||||||
|
return new DomainPermissionDenied(domainName, contextName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.status === 403) {
|
throw err;
|
||||||
return new DomainPermissionDenied(domainName, contextName);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Client from '../client';
|
import Client from '../client';
|
||||||
import { DomainConfig } from '../../types';
|
import { DomainConfig } from '../../types';
|
||||||
|
import { isAPIError } from '../errors-ts';
|
||||||
|
|
||||||
export async function getDomainConfig(client: Client, domainName: string) {
|
export async function getDomainConfig(client: Client, domainName: string) {
|
||||||
try {
|
try {
|
||||||
@@ -8,11 +9,11 @@ export async function getDomainConfig(client: Client, domainName: string) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.status < 500) {
|
if (isAPIError(err) && err.status < 500) {
|
||||||
return error;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { stringify } from 'querystring';
|
import { stringify } from 'querystring';
|
||||||
import { UnsupportedTLD } from '../errors-ts';
|
import { isAPIError, UnsupportedTLD } from '../errors-ts';
|
||||||
import Client from '../client';
|
import Client from '../client';
|
||||||
|
|
||||||
type Response = {
|
type Response = {
|
||||||
@@ -15,15 +15,17 @@ export default async function getDomainPrice(
|
|||||||
try {
|
try {
|
||||||
const querystr = type ? stringify({ name, type }) : stringify({ name });
|
const querystr = type ? stringify({ name, type }) : stringify({ name });
|
||||||
return await client.fetch<Response>(`/v3/domains/price?${querystr}`);
|
return await client.fetch<Response>(`/v3/domains/price?${querystr}`);
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'unsupported_tld') {
|
if (isAPIError(err)) {
|
||||||
return new UnsupportedTLD(name);
|
if (err.code === 'unsupported_tld') {
|
||||||
|
return new UnsupportedTLD(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.status < 500) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.status < 500) {
|
throw err;
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import Client from '../client';
|
import Client from '../client';
|
||||||
import { Domain } from '../../types';
|
import { Domain } from '../../types';
|
||||||
|
import { isAPIError } from '../errors-ts';
|
||||||
|
|
||||||
type Response = {
|
type Response = {
|
||||||
domain: Domain;
|
domain: Domain;
|
||||||
@@ -20,11 +21,11 @@ export async function getDomain(
|
|||||||
);
|
);
|
||||||
|
|
||||||
return domain;
|
return domain;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.status < 500) {
|
if (isAPIError(err) && err.status < 500) {
|
||||||
return error;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,25 +20,27 @@ export default async function moveOutDomain(
|
|||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'forbidden') {
|
if (ERRORS.isAPIError(err)) {
|
||||||
return new ERRORS.DomainPermissionDenied(name, contextName);
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (error.code === 'not_found') {
|
throw err;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,28 +20,30 @@ export default async function purchaseDomain(
|
|||||||
body: { name, expectedPrice, renew },
|
body: { name, expectedPrice, renew },
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'invalid_domain') {
|
if (ERRORS.isAPIError(err)) {
|
||||||
return new ERRORS.InvalidDomain(name);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (error.code === 'not_available') {
|
throw err;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,24 +10,26 @@ export default async function removeDomainByName(
|
|||||||
return await now.fetch(`/v3/domains/${encodeURIComponent(domain)}`, {
|
return await now.fetch(`/v3/domains/${encodeURIComponent(domain)}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'not_found') {
|
if (ERRORS.isAPIError(err)) {
|
||||||
return new ERRORS.DomainNotFound(domain);
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (error.code === 'forbidden') {
|
throw err;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,25 +14,27 @@ export default async function setCustomSuffix(
|
|||||||
suffix,
|
suffix,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'forbidden') {
|
if (ERRORS.isAPIError(err)) {
|
||||||
return new ERRORS.DomainPermissionDenied(domain, contextName);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (error.code === 'domain_external') {
|
throw err;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,25 +17,27 @@ export default async function transferInDomain(
|
|||||||
body: { method: 'transfer-in', name, authCode, expectedPrice },
|
body: { method: 'transfer-in', name, authCode, expectedPrice },
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
if (error.code === 'invalid_name') {
|
if (ERRORS.isAPIError(err)) {
|
||||||
return new ERRORS.InvalidDomain(name);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (error.code === 'domain_already_exists') {
|
throw err;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
packages/cli/src/util/env/add-env-record.ts
vendored
2
packages/cli/src/util/env/add-env-record.ts
vendored
@@ -29,6 +29,6 @@ export default async function addEnvRecord(
|
|||||||
const url = `/v8/projects/${projectId}/env`;
|
const url = `/v8/projects/${projectId}/env`;
|
||||||
await client.fetch(url, {
|
await client.fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(body),
|
body,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
80
packages/cli/src/util/env/diff-env-files.ts
vendored
Normal file
80
packages/cli/src/util/env/diff-env-files.ts
vendored
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
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'
|
||||||
|
);
|
||||||
|
}
|
||||||
7
packages/cli/src/util/env/known-error.ts
vendored
7
packages/cli/src/util/env/known-error.ts
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
import { isErrnoException } from '../is-error';
|
||||||
|
|
||||||
const knownErrorsCodes = new Set([
|
const knownErrorsCodes = new Set([
|
||||||
'PAYMENT_REQUIRED',
|
'PAYMENT_REQUIRED',
|
||||||
'BAD_REQUEST',
|
'BAD_REQUEST',
|
||||||
@@ -7,7 +9,8 @@ const knownErrorsCodes = new Set([
|
|||||||
'ENV_SHOULD_BE_A_SECRET',
|
'ENV_SHOULD_BE_A_SECRET',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function isKnownError(error: { code?: string }) {
|
export function isKnownError(error: unknown) {
|
||||||
const code = error && typeof error.code === 'string' ? error.code : '';
|
const code = isErrnoException(error) ? error.code : null;
|
||||||
|
if (!code) return false;
|
||||||
return knownErrorsCodes.has(code.toUpperCase());
|
return knownErrorsCodes.has(code.toUpperCase());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,3 +87,18 @@ export async function responseErrorMessage(
|
|||||||
|
|
||||||
return `${message} (${res.status})`;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { NowError } from './now-error';
|
|||||||
import code from './output/code';
|
import code from './output/code';
|
||||||
import { getCommandName } from './pkg-name';
|
import { getCommandName } from './pkg-name';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
import { isError } from './is-error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This error is thrown when there is an API error with a payload. The error
|
* This error is thrown when there is an API error with a payload. The error
|
||||||
@@ -45,6 +46,10 @@ 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
|
* 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
|
* retrieve information. This means that the team was probably deleted or the
|
||||||
|
|||||||
@@ -81,21 +81,21 @@ async function printEvents(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
poller = startPoller();
|
poller = startPoller();
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
stream.end();
|
stream.end();
|
||||||
finish(error);
|
finish(err);
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
let finishCalled = false;
|
let finishCalled = false;
|
||||||
function finish(error?: Error) {
|
function finish(err?: unknown) {
|
||||||
if (finishCalled) return;
|
if (finishCalled) return;
|
||||||
finishCalled = true;
|
finishCalled = true;
|
||||||
clearTimeout(poller);
|
clearTimeout(poller);
|
||||||
if (error) {
|
if (err) {
|
||||||
reject(error);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user