mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-12 12:57:47 +00:00
Compare commits
153 Commits
vercel@23.
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d7f3df980 | ||
|
|
5cf0c316e9 | ||
|
|
f4501433c8 | ||
|
|
19831593ce | ||
|
|
5d85bb1426 | ||
|
|
f194d54b0c | ||
|
|
6542086843 | ||
|
|
2721b3449d | ||
|
|
adb284519a | ||
|
|
b2d91f3121 | ||
|
|
32664cd13b | ||
|
|
db468c489a | ||
|
|
edd9bb506c | ||
|
|
a72549a290 | ||
|
|
4aa6a13912 | ||
|
|
81ea0082f1 | ||
|
|
6dff0875f5 | ||
|
|
30aa392c0a | ||
|
|
c4fc060030 | ||
|
|
3fa08bf64f | ||
|
|
43056bde1f | ||
|
|
a49966b9b4 | ||
|
|
7f55de71bb | ||
|
|
db8e36e04c | ||
|
|
82924bb5c4 | ||
|
|
18b5fac93e | ||
|
|
a6012e600b | ||
|
|
c3abf73f58 | ||
|
|
4873b8b379 | ||
|
|
6248139281 | ||
|
|
507a5de3cd | ||
|
|
be1c78e72f | ||
|
|
c277c649c6 | ||
|
|
ed1dacd276 | ||
|
|
144e890bfa | ||
|
|
af097c2c06 | ||
|
|
873a582986 | ||
|
|
986b4c0b1a | ||
|
|
14071819ac | ||
|
|
2a8588a0c5 | ||
|
|
0f7e89f76c | ||
|
|
e68ed33a88 | ||
|
|
d3e98cdb73 | ||
|
|
bf4e77110f | ||
|
|
5b5197d2c5 | ||
|
|
a6ccf6c180 | ||
|
|
8d848ebe8b | ||
|
|
6ef2c16d63 | ||
|
|
6c71ceaaeb | ||
|
|
1dcb6dfc6f | ||
|
|
4fd24575e5 | ||
|
|
8714f1905e | ||
|
|
2e69f2513d | ||
|
|
979e4b674a | ||
|
|
07fa47bcfb | ||
|
|
307c4fc377 | ||
|
|
44868d79b6 | ||
|
|
df9a4afa5c | ||
|
|
8a6869bae2 | ||
|
|
a3fc3c1ca7 | ||
|
|
44037c58be | ||
|
|
1a9419b690 | ||
|
|
93d0e5966c | ||
|
|
306f3a1312 | ||
|
|
9c67e8115e | ||
|
|
b890ac1e44 | ||
|
|
28e71ff109 | ||
|
|
2bf060c708 | ||
|
|
c8ef1d71d1 | ||
|
|
3d2efc7dcd | ||
|
|
cd7185a872 | ||
|
|
981a76fbe2 | ||
|
|
c35c05446b | ||
|
|
b88e65c4ad | ||
|
|
9e16ce750b | ||
|
|
2105d31730 | ||
|
|
3d7e01ebf0 | ||
|
|
dccacc4ca0 | ||
|
|
be5c0da521 | ||
|
|
f985d953ed | ||
|
|
b8f8289afe | ||
|
|
e01a1cebfd | ||
|
|
2515d522a3 | ||
|
|
32e935d632 | ||
|
|
e6818dd3f9 | ||
|
|
e510415a66 | ||
|
|
1be75712e0 | ||
|
|
f682aefc9d | ||
|
|
cd485c1866 | ||
|
|
f7de7227b4 | ||
|
|
abea217177 | ||
|
|
4e52f8532b | ||
|
|
702cb9e29c | ||
|
|
d3d5555d79 | ||
|
|
2fd3fc73e5 | ||
|
|
de0b13a46e | ||
|
|
d0fe85db92 | ||
|
|
bfbd927320 | ||
|
|
90bacf88b8 | ||
|
|
07c369c542 | ||
|
|
a2e4186ccb | ||
|
|
6e1d708e3f | ||
|
|
38503103c3 | ||
|
|
e8fec4b69c | ||
|
|
b3ffcdf80d | ||
|
|
43c1a93c1d | ||
|
|
5b118fd4e6 | ||
|
|
8916b674af | ||
|
|
1807f83c69 | ||
|
|
74e8ec7c64 | ||
|
|
2644e3127b | ||
|
|
d77ac04b0c | ||
|
|
0ef9c8df4d | ||
|
|
dfc4c98820 | ||
|
|
0e51884725 | ||
|
|
1b264fe60e | ||
|
|
f18bca9718 | ||
|
|
c23dc73f41 | ||
|
|
273718e0b7 | ||
|
|
230b88bf9b | ||
|
|
676a3d2568 | ||
|
|
f221f041d0 | ||
|
|
aca42b2aac | ||
|
|
cf11a8efb5 | ||
|
|
be09349daf | ||
|
|
a01372bcbb | ||
|
|
b941715d7b | ||
|
|
ee9a8a0415 | ||
|
|
2ad27eefb0 | ||
|
|
578fe8a930 | ||
|
|
04ea3bb88d | ||
|
|
46116022b7 | ||
|
|
f80539df82 | ||
|
|
daf06307b4 | ||
|
|
0bd028cd84 | ||
|
|
1c48030e1e | ||
|
|
1dc05428d7 | ||
|
|
288dca045c | ||
|
|
8c5bc04fde | ||
|
|
a07e6fc103 | ||
|
|
9af3054d41 | ||
|
|
9fb254e14a | ||
|
|
3616bdf17a | ||
|
|
52a89fd4b7 | ||
|
|
d4db6635f1 | ||
|
|
f1009a80cd | ||
|
|
2756d1e323 | ||
|
|
5b61b16bd1 | ||
|
|
41c61f8f8b | ||
|
|
6c52e1fad7 | ||
|
|
d2e82fdc3a | ||
|
|
a60b1b225b | ||
|
|
18bec983ae |
@@ -1,11 +1,10 @@
|
|||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
examples
|
examples
|
||||||
packages/build-utils/test/fixtures
|
packages/*/test/fixtures
|
||||||
packages/cli/@types
|
packages/cli/@types
|
||||||
packages/cli/download
|
packages/cli/download
|
||||||
packages/cli/dist
|
packages/cli/dist
|
||||||
packages/cli/test/fixtures
|
|
||||||
packages/cli/test/dev/fixtures
|
packages/cli/test/dev/fixtures
|
||||||
packages/cli/bin
|
packages/cli/bin
|
||||||
packages/cli/link
|
packages/cli/link
|
||||||
@@ -13,6 +12,6 @@ packages/cli/src/util/dev/templates/*.ts
|
|||||||
packages/client/tests/fixtures
|
packages/client/tests/fixtures
|
||||||
packages/client/lib
|
packages/client/lib
|
||||||
packages/node/src/bridge.ts
|
packages/node/src/bridge.ts
|
||||||
packages/node/test/fixtures
|
|
||||||
packages/node-bridge/bridge.js
|
packages/node-bridge/bridge.js
|
||||||
packages/node-bridge/launcher.js
|
packages/node-bridge/launcher.js
|
||||||
|
packages/middleware/src/entries.js
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: true
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Bug Report
|
- name: Bug Report
|
||||||
url: https://vercel.com/support/request
|
url: https://vercel.com/support/request
|
||||||
|
|||||||
2
.github/workflows/cancel.yml
vendored
2
.github/workflows/cancel.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 2
|
timeout-minutes: 2
|
||||||
steps:
|
steps:
|
||||||
- uses: styfle/cancel-workflow-action@0.4.1
|
- uses: styfle/cancel-workflow-action@0.9.1
|
||||||
with:
|
with:
|
||||||
workflow_id: 849295, 849296, 849297, 849298
|
workflow_id: 849295, 849296, 849297, 849298
|
||||||
access_token: ${{ github.token }}
|
access_token: ${{ github.token }}
|
||||||
|
|||||||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -29,6 +29,6 @@ jobs:
|
|||||||
- name: Publish
|
- name: Publish
|
||||||
run: yarn publish-from-github
|
run: yarn publish-from-github
|
||||||
env:
|
env:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}
|
||||||
GA_TRACKING_ID: ${{ secrets.GA_TRACKING_ID }}
|
GA_TRACKING_ID: ${{ secrets.GA_TRACKING_ID }}
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
|
|||||||
2
.github/workflows/test-integration-dev.yml
vendored
2
.github/workflows/test-integration-dev.yml
vendored
@@ -11,7 +11,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Dev
|
name: Dev
|
||||||
timeout-minutes: 60
|
timeout-minutes: 75
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -27,3 +27,4 @@ test/lib/deployment/failed-page.txt
|
|||||||
/public
|
/public
|
||||||
__pycache__
|
__pycache__
|
||||||
.vercel
|
.vercel
|
||||||
|
.output
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ This is an abstract enumeration type that is implemented by one of the following
|
|||||||
- `nodejs10.x`
|
- `nodejs10.x`
|
||||||
- `go1.x`
|
- `go1.x`
|
||||||
- `java11`
|
- `java11`
|
||||||
- `python3.8`
|
- `python3.9`
|
||||||
- `python3.6`
|
- `python3.6`
|
||||||
- `dotnetcore2.1`
|
- `dotnetcore2.1`
|
||||||
- `ruby2.5`
|
- `ruby2.5`
|
||||||
@@ -398,12 +398,12 @@ This utility allows you to _scan_ the filesystem and return a [`Files`](#files)
|
|||||||
The following trivial example downloads everything to the filesystem, only to return it back (therefore just re-creating the passed-in [`Files`](#files)):
|
The following trivial example downloads everything to the filesystem, only to return it back (therefore just re-creating the passed-in [`Files`](#files)):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const { glob, download } = require('@vercel/build-utils')
|
const { glob, download } = require('@vercel/build-utils');
|
||||||
|
|
||||||
exports.build = ({ files, workPath }) => {
|
exports.build = ({ files, workPath }) => {
|
||||||
await download(files, workPath)
|
await download(files, workPath);
|
||||||
return glob('**', workPath)
|
return glob('**', workPath);
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### `getWritableDirectory()`
|
### `getWritableDirectory()`
|
||||||
|
|||||||
@@ -11,7 +11,11 @@
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Vercel is the optimal workflow for frontend teams. All-in-one: Static and Jamstack deployment, Serverless Functions, and Global CDN.
|
Vercel is a platform for **static sites and frontend frameworks**, built to integrate with your headless content, commerce, or database.
|
||||||
|
|
||||||
|
We provide a **frictionless developer experience** to take care of the hard things: deploy instantly, scale automatically, and serve personalized content around the globe.
|
||||||
|
|
||||||
|
We make it easy for frontend teams to **develop, preview, and ship** delightful user experiences, where performance is the default.
|
||||||
|
|
||||||
Get started by [Importing a Git Project](https://vercel.com/new) and use `git push` to deploy. Alternatively, you can [install Vercel CLI](https://vercel.com/cli).
|
Get started by [Importing a Git Project](https://vercel.com/new) and use `git push` to deploy. Alternatively, you can [install Vercel CLI](https://vercel.com/cli).
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,19 @@
|
|||||||
* Get example list from extracted folder
|
* Get example list from extracted folder
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { join } from 'path';
|
|
||||||
import { lstatSync, existsSync, readdirSync } from 'fs';
|
import { lstatSync, existsSync, readdirSync } from 'fs';
|
||||||
|
|
||||||
const exists = (path: string) => existsSync(path);
|
const exists = (path: string) => existsSync(path);
|
||||||
const isDotFile = (name: string) => name.startsWith('.');
|
const isDotFile = (name: string) => name.startsWith('.');
|
||||||
const isDirectory = (path: string) => lstatSync(path).isDirectory();
|
const isDirectory = (path: string) => lstatSync(path).isDirectory();
|
||||||
|
|
||||||
export function summary(source: string) {
|
export function summary(source: string): string[] {
|
||||||
if (!exists(source) || !isDirectory(source)) {
|
if (!exists(source) || !isDirectory(source)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return readdirSync(source)
|
return readdirSync(source, { withFileTypes: true })
|
||||||
.filter(name => !isDotFile(name))
|
.filter(d => !isDotFile(d.name))
|
||||||
.filter(name => isDirectory(join(source, name)));
|
.filter(d => d.isDirectory())
|
||||||
|
.map(d => d.name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ const frameworks = (_frameworks as Framework[])
|
|||||||
sort: undefined,
|
sort: undefined,
|
||||||
dependency: undefined,
|
dependency: undefined,
|
||||||
defaultRoutes: undefined,
|
defaultRoutes: undefined,
|
||||||
devCommand: undefined,
|
|
||||||
buildCommand: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (framework.logo) {
|
if (framework.logo) {
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ coverage:
|
|||||||
project: off
|
project: off
|
||||||
patch: off
|
patch: off
|
||||||
|
|
||||||
|
fixes:
|
||||||
|
- "::packages/cli/" # move root e.g., "path/" => "after/path/"
|
||||||
|
|||||||
13218
examples/angular/package-lock.json
generated
Normal file
13218
examples/angular/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,37 +12,38 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "~8.1.0",
|
"@angular/animations": "^8.1.0",
|
||||||
"@angular/common": "~8.1.0",
|
"@angular/common": "^8.1.0",
|
||||||
"@angular/compiler": "~8.1.0",
|
"@angular/core": "^8.1.0",
|
||||||
"@angular/core": "~8.1.0",
|
"@angular/forms": "^8.1.0",
|
||||||
"@angular/forms": "~8.1.0",
|
"@angular/platform-browser": "^8.1.0",
|
||||||
"@angular/platform-browser": "~8.1.0",
|
"@angular/platform-browser-dynamic": "^8.1.0",
|
||||||
"@angular/platform-browser-dynamic": "~8.1.0",
|
"@angular/router": "^8.1.0",
|
||||||
"@angular/router": "~8.1.0",
|
|
||||||
"rxjs": "~6.4.0",
|
"rxjs": "~6.4.0",
|
||||||
"tslib": "^1.9.0",
|
"tslib": "^1.9.0",
|
||||||
"zone.js": "~0.9.1"
|
"zone.js": "~0.9.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "~0.801.0",
|
"@angular-devkit/build-angular": "^12.2.2",
|
||||||
"@angular/cli": "~8.1.0",
|
"@angular/cli": "^12.2.2",
|
||||||
"@angular/compiler-cli": "~8.1.0",
|
"@angular/compiler": "^12.2.2",
|
||||||
|
"@angular/compiler-cli": "^12.2.2",
|
||||||
"@angular/language-service": "~8.1.0",
|
"@angular/language-service": "~8.1.0",
|
||||||
"@types/node": "~8.9.4",
|
|
||||||
"@types/jasmine": "~3.3.8",
|
"@types/jasmine": "~3.3.8",
|
||||||
"@types/jasminewd2": "~2.0.3",
|
"@types/jasminewd2": "~2.0.3",
|
||||||
|
"@types/node": "~8.9.4",
|
||||||
"codelyzer": "^5.0.0",
|
"codelyzer": "^5.0.0",
|
||||||
"jasmine-core": "~3.4.0",
|
"glob-parent": "^5.1.2",
|
||||||
|
"jasmine-core": "^3.4.0",
|
||||||
"jasmine-spec-reporter": "~4.2.1",
|
"jasmine-spec-reporter": "~4.2.1",
|
||||||
"karma": "~4.1.0",
|
"karma": "^6.3.4",
|
||||||
"karma-chrome-launcher": "~2.2.0",
|
"karma-chrome-launcher": "~2.2.0",
|
||||||
"karma-coverage-istanbul-reporter": "~2.0.1",
|
"karma-coverage-istanbul-reporter": "~2.0.1",
|
||||||
"karma-jasmine": "~2.0.1",
|
"karma-jasmine": "~2.0.1",
|
||||||
"karma-jasmine-html-reporter": "^1.4.0",
|
"karma-jasmine-html-reporter": "^1.4.0",
|
||||||
"protractor": "~5.4.0",
|
"protractor": "^7.0.0",
|
||||||
"ts-node": "~7.0.0",
|
"ts-node": "~7.0.0",
|
||||||
"tslint": "~5.15.0",
|
"tslint": "~5.15.0",
|
||||||
"typescript": "~3.4.3"
|
"typescript": "^4.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^16.6.1",
|
"react": "^16.6.1",
|
||||||
"react-dom": "^16.6.1",
|
"react-dom": "^16.6.1",
|
||||||
"react-scripts": "2.1.1"
|
"react-scripts": "^4.0.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "BROWSER=none react-scripts start",
|
"dev": "BROWSER=none react-scripts start",
|
||||||
|
|||||||
3
examples/nextjs/.eslintrc.json
Normal file
3
examples/nextjs/.eslintrc.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
||||||
@@ -29,6 +29,6 @@ You can check out [the Next.js GitHub repository](https://github.com/vercel/next
|
|||||||
|
|
||||||
## Deploy on Vercel
|
## Deploy on Vercel
|
||||||
|
|
||||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||||
|
|||||||
3
examples/nextjs/next.config.js
Normal file
3
examples/nextjs/next.config.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
}
|
||||||
@@ -1,15 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "nextjs",
|
"name": "nextjs",
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start"
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "10.x",
|
"next": "12.0.1",
|
||||||
"react": "17.x",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.x"
|
"react-dom": "17.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "7.32.0",
|
||||||
|
"eslint-config-next": "11.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
|
||||||
export default (req, res) => {
|
export default function handler(req, res) {
|
||||||
res.statusCode = 200
|
res.status(200).json({ name: 'John Doe' })
|
||||||
res.json({ name: 'John Doe' })
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
|
import Image from 'next/image'
|
||||||
import styles from '../styles/Home.module.css'
|
import styles from '../styles/Home.module.css'
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
@@ -6,6 +7,7 @@ export default function Home() {
|
|||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Create Next App</title>
|
<title>Create Next App</title>
|
||||||
|
<meta name="description" content="Generated by create next app" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
@@ -21,12 +23,12 @@ export default function Home() {
|
|||||||
|
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
<a href="https://nextjs.org/docs" className={styles.card}>
|
<a href="https://nextjs.org/docs" className={styles.card}>
|
||||||
<h3>Documentation →</h3>
|
<h2>Documentation →</h2>
|
||||||
<p>Find in-depth information about Next.js features and API.</p>
|
<p>Find in-depth information about Next.js features and API.</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="https://nextjs.org/learn" className={styles.card}>
|
<a href="https://nextjs.org/learn" className={styles.card}>
|
||||||
<h3>Learn →</h3>
|
<h2>Learn →</h2>
|
||||||
<p>Learn about Next.js in an interactive course with quizzes!</p>
|
<p>Learn about Next.js in an interactive course with quizzes!</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -34,15 +36,15 @@ export default function Home() {
|
|||||||
href="https://github.com/vercel/next.js/tree/master/examples"
|
href="https://github.com/vercel/next.js/tree/master/examples"
|
||||||
className={styles.card}
|
className={styles.card}
|
||||||
>
|
>
|
||||||
<h3>Examples →</h3>
|
<h2>Examples →</h2>
|
||||||
<p>Discover and deploy boilerplate example Next.js projects.</p>
|
<p>Discover and deploy boilerplate example Next.js projects.</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="https://vercel.com/import?filter=next.js&utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||||
className={styles.card}
|
className={styles.card}
|
||||||
>
|
>
|
||||||
<h3>Deploy →</h3>
|
<h2>Deploy →</h2>
|
||||||
<p>
|
<p>
|
||||||
Instantly deploy your Next.js site to a public URL with Vercel.
|
Instantly deploy your Next.js site to a public URL with Vercel.
|
||||||
</p>
|
</p>
|
||||||
@@ -57,7 +59,9 @@ export default function Home() {
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
Powered by{' '}
|
Powered by{' '}
|
||||||
<img src="/vercel.svg" alt="Vercel Logo" className={styles.logo} />
|
<span className={styles.logo}>
|
||||||
|
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
@@ -25,14 +26,11 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer img {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer a {
|
.footer a {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title a {
|
.title a {
|
||||||
@@ -82,7 +80,6 @@
|
|||||||
|
|
||||||
.card {
|
.card {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
flex-basis: 45%;
|
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
@@ -90,6 +87,7 @@
|
|||||||
border: 1px solid #eaeaea;
|
border: 1px solid #eaeaea;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
transition: color 0.15s ease, border-color 0.15s ease;
|
transition: color 0.15s ease, border-color 0.15s ease;
|
||||||
|
width: 45%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card:hover,
|
.card:hover,
|
||||||
@@ -99,7 +97,7 @@
|
|||||||
border-color: #0070f3;
|
border-color: #0070f3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card h3 {
|
.card h2 {
|
||||||
margin: 0 0 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
@@ -112,6 +110,7 @@
|
|||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
height: 1em;
|
height: 1em;
|
||||||
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
7
examples/parcel/.gitignore
vendored
Normal file
7
examples/parcel/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
.parcel-cache
|
||||||
|
dist
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
.DS_Store
|
||||||
|
.vercel
|
||||||
34
examples/parcel/README.md
Normal file
34
examples/parcel/README.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
This is a vanilla web app built with [Parcel](https://parceljs.org).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Run the dev server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn start
|
||||||
|
# or
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
And open [localhost:1234](http://localhost:1234) in your browser!
|
||||||
|
|
||||||
|
As you make changes, you should see your app automatically update in the browser without even refreshing the page!
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
- [Getting started guide](https://parceljs.org/getting-started/webapp/)
|
||||||
|
- [Documentation](https://parceljs.org/docs/)
|
||||||
|
|
||||||
|
## Deploy Your Own
|
||||||
|
|
||||||
|
[](https://vercel.com/new/clone?repository-url=https://github.com/vercel/vercel/tree/main/examples/parcel&template=parcel)
|
||||||
|
|
||||||
|
_Live Example: https://parcel.examples.vercel.com_
|
||||||
|
|
||||||
|
### Deploying From Your Terminal
|
||||||
|
|
||||||
|
You can deploy your new Parcel project with a single command from your terminal using [Vercel CLI](https://vercel.com/download):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ vercel
|
||||||
|
```
|
||||||
12
examples/parcel/package.json
Normal file
12
examples/parcel/package.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "parcel-app",
|
||||||
|
"private": true,
|
||||||
|
"source": "src/index.html",
|
||||||
|
"scripts": {
|
||||||
|
"start": "parcel",
|
||||||
|
"build": "parcel build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"parcel": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
examples/parcel/src/app.js
Normal file
1
examples/parcel/src/app.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
console.log('Hello world!');
|
||||||
13
examples/parcel/src/index.html
Normal file
13
examples/parcel/src/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>My First Parcel App</title>
|
||||||
|
<link rel="stylesheet" href="styles.css" />
|
||||||
|
<script type="module" src="app.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hello, Parcel!</h1>
|
||||||
|
<p>This is a vanilla web app built with Parcel 2!<br>Check out the <a href="https://parceljs.org/getting-started/webapp/" target="_blank">getting started guide</a> and the <a href="https://parceljs.org/docs/" target="_blank">documentation</a> to learn more!</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
18
examples/parcel/src/styles.css
Normal file
18
examples/parcel/src/styles.css
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
body {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: hotpink;
|
||||||
|
font-family: cursive;
|
||||||
|
font-size: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-family: Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: dodgerblue;
|
||||||
|
}
|
||||||
4541
examples/parcel/yarn.lock
Normal file
4541
examples/parcel/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "4.28.0",
|
"@typescript-eslint/eslint-plugin": "4.28.0",
|
||||||
"@typescript-eslint/parser": "4.28.0",
|
"@typescript-eslint/parser": "4.28.0",
|
||||||
"@vercel/ncc": "0.24.0",
|
|
||||||
"async-retry": "1.2.3",
|
"async-retry": "1.2.3",
|
||||||
"buffer-replace": "1.0.0",
|
"buffer-replace": "1.0.0",
|
||||||
"cheerio": "1.0.0-rc.3",
|
"cheerio": "1.0.0-rc.3",
|
||||||
@@ -25,7 +24,7 @@
|
|||||||
"eslint-config-prettier": "8.3.0",
|
"eslint-config-prettier": "8.3.0",
|
||||||
"eslint-plugin-jest": "24.3.6",
|
"eslint-plugin-jest": "24.3.6",
|
||||||
"husky": "6.0.0",
|
"husky": "6.0.0",
|
||||||
"jest": "27.0.6",
|
"jest": "27.3.1",
|
||||||
"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.1",
|
||||||
@@ -37,7 +36,7 @@
|
|||||||
"lerna": "lerna",
|
"lerna": "lerna",
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"publish-stable": "echo 'Run `yarn changelog` for instructions'",
|
"publish-stable": "echo 'Run `yarn changelog` for instructions'",
|
||||||
"publish-canary": "git checkout main && git pull && lerna version prerelease --preid canary --message 'Publish Canary' --exact",
|
"publish-canary": "git checkout main && git pull && lerna version prerelease --preid canary --message \"Publish Canary\" --exact",
|
||||||
"publish-from-github": "./utils/publish.sh",
|
"publish-from-github": "./utils/publish.sh",
|
||||||
"changelog": "node utils/changelog.js",
|
"changelog": "node utils/changelog.js",
|
||||||
"build": "node utils/run.js build all",
|
"build": "node utils/run.js build all",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@vercel/build-utils",
|
"name": "@vercel/build-utils",
|
||||||
"version": "2.12.1",
|
"version": "2.12.3-canary.20",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.js",
|
"types": "./dist/index.d.js",
|
||||||
@@ -21,16 +21,16 @@
|
|||||||
"@types/async-retry": "^1.2.1",
|
"@types/async-retry": "^1.2.1",
|
||||||
"@types/cross-spawn": "6.0.0",
|
"@types/cross-spawn": "6.0.0",
|
||||||
"@types/end-of-stream": "^1.4.0",
|
"@types/end-of-stream": "^1.4.0",
|
||||||
"@types/fs-extra": "^5.0.5",
|
"@types/fs-extra": "9.0.13",
|
||||||
"@types/glob": "^7.1.1",
|
"@types/glob": "^7.1.1",
|
||||||
"@types/jest": "26.0.24",
|
"@types/jest": "27.0.1",
|
||||||
"@types/js-yaml": "3.12.1",
|
"@types/js-yaml": "3.12.1",
|
||||||
"@types/ms": "0.7.31",
|
"@types/ms": "0.7.31",
|
||||||
"@types/multistream": "2.1.1",
|
"@types/multistream": "2.1.1",
|
||||||
"@types/node-fetch": "^2.1.6",
|
"@types/node-fetch": "^2.1.6",
|
||||||
"@types/semver": "6.0.0",
|
"@types/semver": "6.0.0",
|
||||||
"@types/yazl": "^2.4.1",
|
"@types/yazl": "^2.4.1",
|
||||||
"@vercel/frameworks": "0.5.1-canary.0",
|
"@vercel/frameworks": "0.5.1-canary.12",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
"aggregate-error": "3.0.1",
|
"aggregate-error": "3.0.1",
|
||||||
"async-retry": "1.2.3",
|
"async-retry": "1.2.3",
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"boxen": "4.2.0",
|
"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": "7.0.0",
|
"fs-extra": "10.0.0",
|
||||||
"glob": "7.1.3",
|
"glob": "7.1.3",
|
||||||
"into-stream": "5.0.0",
|
"into-stream": "5.0.0",
|
||||||
"js-yaml": "3.13.1",
|
"js-yaml": "3.13.1",
|
||||||
|
|||||||
202
packages/build-utils/src/convert-runtime-to-plugin.ts
Normal file
202
packages/build-utils/src/convert-runtime-to-plugin.ts
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
import fs from 'fs-extra';
|
||||||
|
import { join, dirname, relative } from 'path';
|
||||||
|
import glob from './fs/glob';
|
||||||
|
import { normalizePath } from './fs/normalize-path';
|
||||||
|
import { FILES_SYMBOL, getLambdaOptionsFromFunction, Lambda } from './lambda';
|
||||||
|
import type FileBlob from './file-blob';
|
||||||
|
import type { BuilderFunctions, BuildOptions, Files } from './types';
|
||||||
|
import minimatch from 'minimatch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert legacy Runtime to a Plugin.
|
||||||
|
* @param buildRuntime - a legacy build() function from a Runtime
|
||||||
|
* @param ext - the file extension, for example `.py`
|
||||||
|
*/
|
||||||
|
export function convertRuntimeToPlugin(
|
||||||
|
buildRuntime: (options: BuildOptions) => Promise<{ output: Lambda }>,
|
||||||
|
ext: string
|
||||||
|
) {
|
||||||
|
return async function build({ workPath }: { workPath: string }) {
|
||||||
|
const opts = { cwd: workPath };
|
||||||
|
const files = await glob('**', opts);
|
||||||
|
delete files['vercel.json']; // Builders/Runtimes didn't have vercel.json
|
||||||
|
const entrypoints = await glob(`api/**/*${ext}`, opts);
|
||||||
|
const pages: { [key: string]: any } = {};
|
||||||
|
const { functions = {} } = await readVercelConfig(workPath);
|
||||||
|
const traceDir = join(workPath, '.output', 'runtime-traced-files');
|
||||||
|
await fs.ensureDir(traceDir);
|
||||||
|
|
||||||
|
for (const entrypoint of Object.keys(entrypoints)) {
|
||||||
|
const key =
|
||||||
|
Object.keys(functions).find(
|
||||||
|
src => src === entrypoint || minimatch(entrypoint, src)
|
||||||
|
) || '';
|
||||||
|
const config = functions[key] || {};
|
||||||
|
|
||||||
|
const { output } = await buildRuntime({
|
||||||
|
files,
|
||||||
|
entrypoint,
|
||||||
|
workPath,
|
||||||
|
config: {
|
||||||
|
zeroConfig: true,
|
||||||
|
includeFiles: config.includeFiles,
|
||||||
|
excludeFiles: config.excludeFiles,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
pages[entrypoint] = {
|
||||||
|
handler: output.handler,
|
||||||
|
runtime: output.runtime,
|
||||||
|
memory: output.memory,
|
||||||
|
maxDuration: output.maxDuration,
|
||||||
|
environment: output.environment,
|
||||||
|
allowQuery: output.allowQuery,
|
||||||
|
regions: output.regions,
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-ignore This symbol is a private API
|
||||||
|
const lambdaFiles: Files = output[FILES_SYMBOL];
|
||||||
|
|
||||||
|
const entry = join(workPath, '.output', 'server', 'pages', entrypoint);
|
||||||
|
await fs.ensureDir(dirname(entry));
|
||||||
|
await linkOrCopy(files[entrypoint].fsPath, entry);
|
||||||
|
|
||||||
|
const tracedFiles: {
|
||||||
|
absolutePath: string;
|
||||||
|
relativePath: string;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
Object.entries(lambdaFiles).forEach(async ([relPath, file]) => {
|
||||||
|
const newPath = join(traceDir, relPath);
|
||||||
|
tracedFiles.push({ absolutePath: newPath, relativePath: relPath });
|
||||||
|
if (file.fsPath) {
|
||||||
|
await linkOrCopy(file.fsPath, newPath);
|
||||||
|
} else if (file.type === 'FileBlob') {
|
||||||
|
const { data, mode } = file as FileBlob;
|
||||||
|
await fs.writeFile(newPath, data, { mode });
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown file type: ${file.type}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const nft = join(
|
||||||
|
workPath,
|
||||||
|
'.output',
|
||||||
|
'server',
|
||||||
|
'pages',
|
||||||
|
`${entrypoint}.nft.json`
|
||||||
|
);
|
||||||
|
const json = JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
files: tracedFiles.map(f => ({
|
||||||
|
input: normalizePath(relative(nft, f.absolutePath)),
|
||||||
|
output: normalizePath(f.relativePath),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
await fs.ensureDir(dirname(nft));
|
||||||
|
await fs.writeFile(nft, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateFunctionsManifest({ workPath, pages });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function linkOrCopy(existingPath: string, newPath: string) {
|
||||||
|
try {
|
||||||
|
await fs.createLink(existingPath, newPath);
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.code !== 'EEXIST') {
|
||||||
|
await fs.copyFile(existingPath, newPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readJson(filePath: string): Promise<{ [key: string]: any }> {
|
||||||
|
try {
|
||||||
|
const str = await fs.readFile(filePath, 'utf8');
|
||||||
|
return JSON.parse(str);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'ENOENT') {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readVercelConfig(
|
||||||
|
workPath: string
|
||||||
|
): Promise<{ functions?: BuilderFunctions; regions?: string[] }> {
|
||||||
|
const vercelJsonPath = join(workPath, 'vercel.json');
|
||||||
|
return readJson(vercelJsonPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `.output/functions-manifest.json` exists, append to the pages
|
||||||
|
* property. Otherwise write a new file. This will also read `vercel.json`
|
||||||
|
* and apply relevant `functions` property config.
|
||||||
|
*/
|
||||||
|
export async function updateFunctionsManifest({
|
||||||
|
workPath,
|
||||||
|
pages,
|
||||||
|
}: {
|
||||||
|
workPath: string;
|
||||||
|
pages: { [key: string]: any };
|
||||||
|
}) {
|
||||||
|
const functionsManifestPath = join(
|
||||||
|
workPath,
|
||||||
|
'.output',
|
||||||
|
'functions-manifest.json'
|
||||||
|
);
|
||||||
|
const vercelConfig = await readVercelConfig(workPath);
|
||||||
|
const functionsManifest = await readJson(functionsManifestPath);
|
||||||
|
|
||||||
|
if (!functionsManifest.version) functionsManifest.version = 1;
|
||||||
|
if (!functionsManifest.pages) functionsManifest.pages = {};
|
||||||
|
|
||||||
|
for (const [pageKey, pageConfig] of Object.entries(pages)) {
|
||||||
|
const fnConfig = await getLambdaOptionsFromFunction({
|
||||||
|
sourceFile: pageKey,
|
||||||
|
config: vercelConfig,
|
||||||
|
});
|
||||||
|
functionsManifest.pages[pageKey] = {
|
||||||
|
...pageConfig,
|
||||||
|
memory: fnConfig.memory || pageConfig.memory,
|
||||||
|
maxDuration: fnConfig.maxDuration || pageConfig.maxDuration,
|
||||||
|
regions: vercelConfig.regions || pageConfig.regions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.writeFile(functionsManifestPath, JSON.stringify(functionsManifest));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will append routes to the `routes-manifest.json` file.
|
||||||
|
* If the file does not exist, it'll be created.
|
||||||
|
*/
|
||||||
|
export async function updateRoutesManifest({
|
||||||
|
workPath,
|
||||||
|
dynamicRoutes,
|
||||||
|
}: {
|
||||||
|
workPath: string;
|
||||||
|
dynamicRoutes?: {
|
||||||
|
page: string;
|
||||||
|
regex: string;
|
||||||
|
namedRegex?: string;
|
||||||
|
routeKeys?: { [named: string]: string };
|
||||||
|
}[];
|
||||||
|
}) {
|
||||||
|
const routesManifestPath = join(workPath, '.output', 'routes-manifest.json');
|
||||||
|
|
||||||
|
const routesManifest = await readJson(routesManifestPath);
|
||||||
|
|
||||||
|
if (!routesManifest.version) routesManifest.version = 1;
|
||||||
|
if (routesManifest.pages404 === undefined) routesManifest.pages404 = true;
|
||||||
|
|
||||||
|
if (dynamicRoutes) {
|
||||||
|
if (!routesManifest.dynamicRoutes) routesManifest.dynamicRoutes = [];
|
||||||
|
routesManifest.dynamicRoutes.push(...dynamicRoutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.writeFile(routesManifestPath, JSON.stringify(routesManifest));
|
||||||
|
}
|
||||||
@@ -1030,7 +1030,7 @@ function getRouteResult(
|
|||||||
// https://nextjs.org/docs/advanced-features/custom-error-page
|
// https://nextjs.org/docs/advanced-features/custom-error-page
|
||||||
errorRoutes.push({
|
errorRoutes.push({
|
||||||
status: 404,
|
status: 404,
|
||||||
src: '^/(?!.*api).*$',
|
src: '^(?!/api).*$',
|
||||||
dest: options.cleanUrls ? '/404' : '/404.html',
|
dest: options.cleanUrls ? '/404' : '/404.html',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,4 +100,4 @@ class FileFsRef implements File {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = FileFsRef;
|
export default FileFsRef;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import assert from 'assert';
|
|||||||
import vanillaGlob_ from 'glob';
|
import vanillaGlob_ from 'glob';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { lstat, Stats } from 'fs-extra';
|
import { lstat, Stats } from 'fs-extra';
|
||||||
|
import { normalizePath } from './normalize-path';
|
||||||
import FileFsRef from '../file-fs-ref';
|
import FileFsRef from '../file-fs-ref';
|
||||||
|
|
||||||
export type GlobOptions = vanillaGlob_.IOptions;
|
export type GlobOptions = vanillaGlob_.IOptions;
|
||||||
@@ -45,7 +46,7 @@ export default async function glob(
|
|||||||
const files = await vanillaGlob(pattern, options);
|
const files = await vanillaGlob(pattern, options);
|
||||||
|
|
||||||
for (const relativePath of files) {
|
for (const relativePath of files) {
|
||||||
const fsPath = path.join(options.cwd!, relativePath).replace(/\\/g, '/');
|
const fsPath = normalizePath(path.join(options.cwd!, relativePath));
|
||||||
let stat: Stats = options.statCache![fsPath] as Stats;
|
let stat: Stats = options.statCache![fsPath] as Stats;
|
||||||
assert(
|
assert(
|
||||||
stat,
|
stat,
|
||||||
|
|||||||
8
packages/build-utils/src/fs/normalize-path.ts
Normal file
8
packages/build-utils/src/fs/normalize-path.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const isWin = process.platform === 'win32';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Windows separators to Unix separators.
|
||||||
|
*/
|
||||||
|
export function normalizePath(p: string): string {
|
||||||
|
return isWin ? p.replace(/\\/g, '/') : p;
|
||||||
|
}
|
||||||
@@ -313,7 +313,8 @@ export async function runNpmInstall(
|
|||||||
destPath: string,
|
destPath: string,
|
||||||
args: string[] = [],
|
args: string[] = [],
|
||||||
spawnOpts?: SpawnOptions,
|
spawnOpts?: SpawnOptions,
|
||||||
meta?: Meta
|
meta?: Meta,
|
||||||
|
nodeVersion?: NodeVersion
|
||||||
) {
|
) {
|
||||||
if (meta?.isDev) {
|
if (meta?.isDev) {
|
||||||
debug('Skipping dependency installation because dev mode is enabled');
|
debug('Skipping dependency installation because dev mode is enabled');
|
||||||
@@ -337,7 +338,12 @@ export async function runNpmInstall(
|
|||||||
.filter(a => a !== '--prefer-offline')
|
.filter(a => a !== '--prefer-offline')
|
||||||
.concat(['install', '--no-audit', '--unsafe-perm']);
|
.concat(['install', '--no-audit', '--unsafe-perm']);
|
||||||
|
|
||||||
if (typeof lockfileVersion === 'number' && lockfileVersion >= 2) {
|
// If the lockfile version is 2 or greater and the node version is less than 16 than we will force npm7 to be used
|
||||||
|
if (
|
||||||
|
typeof lockfileVersion === 'number' &&
|
||||||
|
lockfileVersion >= 2 &&
|
||||||
|
(nodeVersion?.major || 0) < 16
|
||||||
|
) {
|
||||||
// Ensure that npm 7 is at the beginning of the `$PATH`
|
// Ensure that npm 7 is at the beginning of the `$PATH`
|
||||||
env.PATH = `/node16/bin-npm7:${env.PATH}`;
|
env.PATH = `/node16/bin-npm7:${env.PATH}`;
|
||||||
console.log('Detected `package-lock.json` generated by npm 7...');
|
console.log('Detected `package-lock.json` generated by npm 7...');
|
||||||
|
|||||||
@@ -81,6 +81,12 @@ export {
|
|||||||
export { detectFramework } from './detect-framework';
|
export { detectFramework } from './detect-framework';
|
||||||
export { DetectorFilesystem } from './detectors/filesystem';
|
export { DetectorFilesystem } from './detectors/filesystem';
|
||||||
export { readConfigFile } from './fs/read-config-file';
|
export { readConfigFile } from './fs/read-config-file';
|
||||||
|
export { normalizePath } from './fs/normalize-path';
|
||||||
|
export {
|
||||||
|
convertRuntimeToPlugin,
|
||||||
|
updateFunctionsManifest,
|
||||||
|
updateRoutesManifest,
|
||||||
|
} from './convert-runtime-to-plugin';
|
||||||
|
|
||||||
export * from './schemas';
|
export * from './schemas';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ interface LambdaOptions {
|
|||||||
memory?: number;
|
memory?: number;
|
||||||
maxDuration?: number;
|
maxDuration?: number;
|
||||||
environment: Environment;
|
environment: Environment;
|
||||||
|
allowQuery?: string[];
|
||||||
|
regions?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateLambdaOptions {
|
interface CreateLambdaOptions {
|
||||||
@@ -28,13 +30,17 @@ interface CreateLambdaOptions {
|
|||||||
memory?: number;
|
memory?: number;
|
||||||
maxDuration?: number;
|
maxDuration?: number;
|
||||||
environment?: Environment;
|
environment?: Environment;
|
||||||
|
allowQuery?: string[];
|
||||||
|
regions?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetLambdaOptionsFromFunctionOptions {
|
interface GetLambdaOptionsFromFunctionOptions {
|
||||||
sourceFile: string;
|
sourceFile: string;
|
||||||
config?: Config;
|
config?: Pick<Config, 'functions'>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const FILES_SYMBOL = Symbol('files');
|
||||||
|
|
||||||
export class Lambda {
|
export class Lambda {
|
||||||
public type: 'Lambda';
|
public type: 'Lambda';
|
||||||
public zipBuffer: Buffer;
|
public zipBuffer: Buffer;
|
||||||
@@ -43,6 +49,8 @@ export class Lambda {
|
|||||||
public memory?: number;
|
public memory?: number;
|
||||||
public maxDuration?: number;
|
public maxDuration?: number;
|
||||||
public environment: Environment;
|
public environment: Environment;
|
||||||
|
public allowQuery?: string[];
|
||||||
|
public regions?: string[];
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
zipBuffer,
|
zipBuffer,
|
||||||
@@ -51,6 +59,8 @@ export class Lambda {
|
|||||||
maxDuration,
|
maxDuration,
|
||||||
memory,
|
memory,
|
||||||
environment,
|
environment,
|
||||||
|
allowQuery,
|
||||||
|
regions,
|
||||||
}: LambdaOptions) {
|
}: LambdaOptions) {
|
||||||
this.type = 'Lambda';
|
this.type = 'Lambda';
|
||||||
this.zipBuffer = zipBuffer;
|
this.zipBuffer = zipBuffer;
|
||||||
@@ -59,6 +69,8 @@ export class Lambda {
|
|||||||
this.memory = memory;
|
this.memory = memory;
|
||||||
this.maxDuration = maxDuration;
|
this.maxDuration = maxDuration;
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
|
this.allowQuery = allowQuery;
|
||||||
|
this.regions = regions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +84,8 @@ export async function createLambda({
|
|||||||
memory,
|
memory,
|
||||||
maxDuration,
|
maxDuration,
|
||||||
environment = {},
|
environment = {},
|
||||||
|
allowQuery,
|
||||||
|
regions,
|
||||||
}: CreateLambdaOptions): Promise<Lambda> {
|
}: CreateLambdaOptions): Promise<Lambda> {
|
||||||
assert(typeof files === 'object', '"files" must be an object');
|
assert(typeof files === 'object', '"files" must be an object');
|
||||||
assert(typeof handler === 'string', '"handler" is not a string');
|
assert(typeof handler === 'string', '"handler" is not a string');
|
||||||
@@ -86,18 +100,38 @@ export async function createLambda({
|
|||||||
assert(typeof maxDuration === 'number', '"maxDuration" is not a number');
|
assert(typeof maxDuration === 'number', '"maxDuration" is not a number');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (allowQuery !== undefined) {
|
||||||
|
assert(Array.isArray(allowQuery), '"allowQuery" is not an Array');
|
||||||
|
assert(
|
||||||
|
allowQuery.every(q => typeof q === 'string'),
|
||||||
|
'"allowQuery" is not a string Array'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regions !== undefined) {
|
||||||
|
assert(Array.isArray(regions), '"regions" is not an Array');
|
||||||
|
assert(
|
||||||
|
regions.every(r => typeof r === 'string'),
|
||||||
|
'"regions" is not a string Array'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await sema.acquire();
|
await sema.acquire();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const zipBuffer = await createZip(files);
|
const zipBuffer = await createZip(files);
|
||||||
return new Lambda({
|
const lambda = new Lambda({
|
||||||
zipBuffer,
|
zipBuffer,
|
||||||
handler,
|
handler,
|
||||||
runtime,
|
runtime,
|
||||||
memory,
|
memory,
|
||||||
maxDuration,
|
maxDuration,
|
||||||
environment,
|
environment,
|
||||||
|
regions,
|
||||||
});
|
});
|
||||||
|
// @ts-ignore This symbol is a private API
|
||||||
|
lambda[FILES_SYMBOL] = files;
|
||||||
|
return lambda;
|
||||||
} finally {
|
} finally {
|
||||||
sema.release();
|
sema.release();
|
||||||
}
|
}
|
||||||
@@ -131,9 +165,7 @@ export async function createZip(files: Files): Promise<Buffer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
zipFile.end();
|
zipFile.end();
|
||||||
streamToBuffer(zipFile.outputStream)
|
streamToBuffer(zipFile.outputStream).then(resolve).catch(reject);
|
||||||
.then(resolve)
|
|
||||||
.catch(reject);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return zipBuffer;
|
return zipBuffer;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ interface PrerenderOptions {
|
|||||||
fallback: FileBlob | FileFsRef | FileRef | null;
|
fallback: FileBlob | FileFsRef | FileRef | null;
|
||||||
group?: number;
|
group?: number;
|
||||||
bypassToken?: string | null /* optional to be non-breaking change */;
|
bypassToken?: string | null /* optional to be non-breaking change */;
|
||||||
|
allowQuery?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Prerender {
|
export class Prerender {
|
||||||
@@ -18,6 +19,7 @@ export class Prerender {
|
|||||||
public fallback: FileBlob | FileFsRef | FileRef | null;
|
public fallback: FileBlob | FileFsRef | FileRef | null;
|
||||||
public group?: number;
|
public group?: number;
|
||||||
public bypassToken: string | null;
|
public bypassToken: string | null;
|
||||||
|
public allowQuery?: string[];
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
expiration,
|
expiration,
|
||||||
@@ -25,6 +27,7 @@ export class Prerender {
|
|||||||
fallback,
|
fallback,
|
||||||
group,
|
group,
|
||||||
bypassToken,
|
bypassToken,
|
||||||
|
allowQuery,
|
||||||
}: PrerenderOptions) {
|
}: PrerenderOptions) {
|
||||||
this.type = 'Prerender';
|
this.type = 'Prerender';
|
||||||
this.expiration = expiration;
|
this.expiration = expiration;
|
||||||
@@ -62,5 +65,19 @@ export class Prerender {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.fallback = fallback;
|
this.fallback = fallback;
|
||||||
|
|
||||||
|
if (allowQuery !== undefined) {
|
||||||
|
if (!Array.isArray(allowQuery)) {
|
||||||
|
throw new Error(
|
||||||
|
'The `allowQuery` argument for `Prerender` must be Array.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!allowQuery.every(q => typeof q === 'string')) {
|
||||||
|
throw new Error(
|
||||||
|
'The `allowQuery` argument for `Prerender` must be Array of strings.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.allowQuery = allowQuery;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface File {
|
|||||||
mode: number;
|
mode: number;
|
||||||
contentType?: string;
|
contentType?: string;
|
||||||
toStream: () => NodeJS.ReadableStream;
|
toStream: () => NodeJS.ReadableStream;
|
||||||
|
toStreamAsync?: () => Promise<NodeJS.ReadableStream>;
|
||||||
/**
|
/**
|
||||||
* The absolute path to the file in the filesystem
|
* The absolute path to the file in the filesystem
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"builds": [{ "src": "package.json", "use": "@vercel/static-build" }],
|
"builds": [{ "src": "package.json", "use": "@vercel/static-build" }],
|
||||||
"probes": [{ "path": "/", "mustContain": "npm version: 7" }]
|
"probes": [{ "path": "/", "mustContain": "npm version: 8" }]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2013,15 +2013,11 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
framework: 'redwoodjs',
|
framework: 'redwoodjs',
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const { builders, defaultRoutes, rewriteRoutes, errorRoutes } =
|
||||||
builders,
|
await detectBuilders(files, null, {
|
||||||
defaultRoutes,
|
projectSettings,
|
||||||
rewriteRoutes,
|
featHandleMiss,
|
||||||
errorRoutes,
|
});
|
||||||
} = await detectBuilders(files, null, {
|
|
||||||
projectSettings,
|
|
||||||
featHandleMiss,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(builders).toStrictEqual([
|
expect(builders).toStrictEqual([
|
||||||
{
|
{
|
||||||
@@ -2038,7 +2034,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(errorRoutes).toStrictEqual([
|
expect(errorRoutes).toStrictEqual([
|
||||||
{
|
{
|
||||||
status: 404,
|
status: 404,
|
||||||
src: '^/(?!.*api).*$',
|
src: '^(?!/api).*$',
|
||||||
dest: '/404.html',
|
dest: '/404.html',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -2050,15 +2046,11 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
framework: 'redwoodjs',
|
framework: 'redwoodjs',
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const { builders, defaultRoutes, rewriteRoutes, errorRoutes } =
|
||||||
builders,
|
await detectBuilders(files, null, {
|
||||||
defaultRoutes,
|
projectSettings,
|
||||||
rewriteRoutes,
|
featHandleMiss,
|
||||||
errorRoutes,
|
});
|
||||||
} = await detectBuilders(files, null, {
|
|
||||||
projectSettings,
|
|
||||||
featHandleMiss,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(builders).toStrictEqual([
|
expect(builders).toStrictEqual([
|
||||||
{
|
{
|
||||||
@@ -2096,7 +2088,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
|
|||||||
expect(errorRoutes).toStrictEqual([
|
expect(errorRoutes).toStrictEqual([
|
||||||
{
|
{
|
||||||
status: 404,
|
status: 404,
|
||||||
src: '^/(?!.*api).*$',
|
src: '^(?!/api).*$',
|
||||||
dest: '/404.html',
|
dest: '/404.html',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -2417,7 +2409,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
|||||||
expect(errorRoutes).toStrictEqual([
|
expect(errorRoutes).toStrictEqual([
|
||||||
{
|
{
|
||||||
status: 404,
|
status: 404,
|
||||||
src: '^/(?!.*api).*$',
|
src: '^(?!/api).*$',
|
||||||
dest: '/404.html',
|
dest: '/404.html',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -2435,6 +2427,11 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
|||||||
'/another/sub/index.html',
|
'/another/sub/index.html',
|
||||||
'/another/sub/page.html',
|
'/another/sub/page.html',
|
||||||
'/another/sub/page',
|
'/another/sub/page',
|
||||||
|
'/another/api',
|
||||||
|
'/another/api/page.html',
|
||||||
|
'/rapid',
|
||||||
|
'/rapid/page.html',
|
||||||
|
'/health-api.html',
|
||||||
].forEach(file => {
|
].forEach(file => {
|
||||||
expect(file).toMatch(pattern);
|
expect(file).toMatch(pattern);
|
||||||
});
|
});
|
||||||
@@ -2443,12 +2440,12 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
|||||||
'/api',
|
'/api',
|
||||||
'/api/',
|
'/api/',
|
||||||
'/api/index.html',
|
'/api/index.html',
|
||||||
'/api/page.html',
|
'/api/users.js',
|
||||||
'/api/page',
|
'/api/users',
|
||||||
'/api/sub',
|
'/api/sub',
|
||||||
'/api/sub/index.html',
|
'/api/sub/index.html',
|
||||||
'/api/sub/page.html',
|
'/api/sub/users.js',
|
||||||
'/api/sub/page',
|
'/api/sub/users',
|
||||||
].forEach(file => {
|
].forEach(file => {
|
||||||
expect(file).not.toMatch(pattern);
|
expect(file).not.toMatch(pattern);
|
||||||
});
|
});
|
||||||
@@ -2819,12 +2816,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
{
|
{
|
||||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes, errorRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
errorRoutes,
|
|
||||||
} = await detectBuilders(files, null, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -2836,7 +2829,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
expect(errorRoutes).toStrictEqual([
|
expect(errorRoutes).toStrictEqual([
|
||||||
{
|
{
|
||||||
status: 404,
|
status: 404,
|
||||||
src: '^/(?!.*api).*$',
|
src: '^(?!/api).*$',
|
||||||
dest: '/404',
|
dest: '/404',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -2904,11 +2897,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
{
|
{
|
||||||
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, null, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -2936,11 +2926,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
'api/[endpoint]/[id].js',
|
'api/[endpoint]/[id].js',
|
||||||
];
|
];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, null, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -2974,11 +2961,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
|
|
||||||
const files = ['public/index.html', 'api/[endpoint].js'];
|
const files = ['public/index.html', 'api/[endpoint].js'];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, pkg, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, pkg, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -3004,11 +2988,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
{
|
{
|
||||||
const files = ['api/date/index.js', 'api/date.js'];
|
const files = ['api/date/index.js', 'api/date.js'];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, null, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -3022,11 +3003,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
{
|
{
|
||||||
const files = ['api/date.js', 'api/[date]/index.js'];
|
const files = ['api/date.js', 'api/[date]/index.js'];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, null, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -3051,11 +3029,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
'api/food.ts',
|
'api/food.ts',
|
||||||
'api/ts/gold.ts',
|
'api/ts/gold.ts',
|
||||||
];
|
];
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, null, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -3071,11 +3046,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
|||||||
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
|
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
|
||||||
const files = ['api/user.php'];
|
const files = ['api/user.php'];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, { functions, ...options });
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, null, { functions, ...options });
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -3105,11 +3077,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
|||||||
{
|
{
|
||||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, null, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -3152,11 +3121,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
|||||||
{
|
{
|
||||||
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, null, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -3184,11 +3150,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
|||||||
'api/[endpoint]/[id].js',
|
'api/[endpoint]/[id].js',
|
||||||
];
|
];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, null, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -3222,11 +3185,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
|||||||
|
|
||||||
const files = ['public/index.html', 'api/[endpoint].js'];
|
const files = ['public/index.html', 'api/[endpoint].js'];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, pkg, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, pkg, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -3245,11 +3205,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
|||||||
{
|
{
|
||||||
const files = ['api/date/index.js', 'api/date.js'];
|
const files = ['api/date/index.js', 'api/date.js'];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, null, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -3263,11 +3220,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
|||||||
{
|
{
|
||||||
const files = ['api/date.js', 'api/[date]/index.js'];
|
const files = ['api/date.js', 'api/[date]/index.js'];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, null, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -3292,11 +3246,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
|||||||
'api/food.ts',
|
'api/food.ts',
|
||||||
'api/ts/gold.ts',
|
'api/ts/gold.ts',
|
||||||
];
|
];
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, options);
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, null, options);
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
@@ -3312,11 +3263,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
|||||||
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
|
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
|
||||||
const files = ['api/user.php'];
|
const files = ['api/user.php'];
|
||||||
|
|
||||||
const {
|
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
|
||||||
defaultRoutes,
|
await detectBuilders(files, null, { functions, ...options });
|
||||||
redirectRoutes,
|
|
||||||
rewriteRoutes,
|
|
||||||
} = await detectBuilders(files, null, { functions, ...options });
|
|
||||||
testHeaders(redirectRoutes);
|
testHeaders(redirectRoutes);
|
||||||
expect(defaultRoutes).toStrictEqual([]);
|
expect(defaultRoutes).toStrictEqual([]);
|
||||||
expect(rewriteRoutes).toStrictEqual([
|
expect(rewriteRoutes).toStrictEqual([
|
||||||
|
|||||||
182
packages/build-utils/test/unit.convert-runtime-to-plugin.test.ts
vendored
Normal file
182
packages/build-utils/test/unit.convert-runtime-to-plugin.test.ts
vendored
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
import { join } from 'path';
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import { BuildOptions, createLambda } from '../src';
|
||||||
|
import { convertRuntimeToPlugin } from '../src/convert-runtime-to-plugin';
|
||||||
|
|
||||||
|
async function fsToJson(dir: string, output: Record<string, any> = {}) {
|
||||||
|
const files = await fs.readdir(dir);
|
||||||
|
for (const file of files) {
|
||||||
|
const fsPath = join(dir, file);
|
||||||
|
const stat = await fs.stat(fsPath);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
output[file] = {};
|
||||||
|
await fsToJson(fsPath, output[file]);
|
||||||
|
} else {
|
||||||
|
output[file] = await fs.readFile(fsPath, 'utf8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
const workPath = join(__dirname, 'walk', 'python-api');
|
||||||
|
|
||||||
|
describe('convert-runtime-to-plugin', () => {
|
||||||
|
afterEach(async () => {
|
||||||
|
await fs.remove(join(workPath, '.output'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create correct fileystem for python', async () => {
|
||||||
|
const lambdaOptions = {
|
||||||
|
handler: 'index.handler',
|
||||||
|
runtime: 'python3.9',
|
||||||
|
memory: 512,
|
||||||
|
maxDuration: 5,
|
||||||
|
environment: {},
|
||||||
|
regions: ['sfo1'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildRuntime = async (opts: BuildOptions) => {
|
||||||
|
const lambda = await createLambda({
|
||||||
|
files: opts.files,
|
||||||
|
...lambdaOptions,
|
||||||
|
});
|
||||||
|
return { output: lambda };
|
||||||
|
};
|
||||||
|
|
||||||
|
const lambdaFiles = await fsToJson(workPath);
|
||||||
|
delete lambdaFiles['vercel.json'];
|
||||||
|
const build = await convertRuntimeToPlugin(buildRuntime, '.py');
|
||||||
|
|
||||||
|
await build({ workPath });
|
||||||
|
|
||||||
|
const output = await fsToJson(join(workPath, '.output'));
|
||||||
|
expect(output).toMatchObject({
|
||||||
|
'functions-manifest.json': expect.stringContaining('{'),
|
||||||
|
'runtime-traced-files': lambdaFiles,
|
||||||
|
server: {
|
||||||
|
pages: {
|
||||||
|
api: {
|
||||||
|
'index.py': expect.stringContaining('index'),
|
||||||
|
'index.py.nft.json': expect.stringContaining('{'),
|
||||||
|
users: {
|
||||||
|
'get.py': expect.stringContaining('get'),
|
||||||
|
'get.py.nft.json': expect.stringContaining('{'),
|
||||||
|
'post.py': expect.stringContaining('post'),
|
||||||
|
'post.py.nft.json': expect.stringContaining('{'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const funcManifest = JSON.parse(output['functions-manifest.json']);
|
||||||
|
expect(funcManifest).toMatchObject({
|
||||||
|
version: 1,
|
||||||
|
pages: {
|
||||||
|
'api/index.py': lambdaOptions,
|
||||||
|
'api/users/get.py': lambdaOptions,
|
||||||
|
'api/users/post.py': { ...lambdaOptions, memory: 3008 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const indexJson = JSON.parse(output.server.pages.api['index.py.nft.json']);
|
||||||
|
expect(indexJson).toMatchObject({
|
||||||
|
version: 1,
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
input: '../../../../runtime-traced-files/api/index.py',
|
||||||
|
output: 'api/index.py',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../runtime-traced-files/api/users/get.py',
|
||||||
|
output: 'api/users/get.py',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../runtime-traced-files/api/users/post.py',
|
||||||
|
output: 'api/users/post.py',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../runtime-traced-files/file.txt',
|
||||||
|
output: 'file.txt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../runtime-traced-files/util/date.py',
|
||||||
|
output: 'util/date.py',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../runtime-traced-files/util/math.py',
|
||||||
|
output: 'util/math.py',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getJson = JSON.parse(
|
||||||
|
output.server.pages.api.users['get.py.nft.json']
|
||||||
|
);
|
||||||
|
expect(getJson).toMatchObject({
|
||||||
|
version: 1,
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/api/index.py',
|
||||||
|
output: 'api/index.py',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/api/users/get.py',
|
||||||
|
output: 'api/users/get.py',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/api/users/post.py',
|
||||||
|
output: 'api/users/post.py',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/file.txt',
|
||||||
|
output: 'file.txt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/util/date.py',
|
||||||
|
output: 'util/date.py',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/util/math.py',
|
||||||
|
output: 'util/math.py',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const postJson = JSON.parse(
|
||||||
|
output.server.pages.api.users['post.py.nft.json']
|
||||||
|
);
|
||||||
|
expect(postJson).toMatchObject({
|
||||||
|
version: 1,
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/api/index.py',
|
||||||
|
output: 'api/index.py',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/api/users/get.py',
|
||||||
|
output: 'api/users/get.py',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/api/users/post.py',
|
||||||
|
output: 'api/users/post.py',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/file.txt',
|
||||||
|
output: 'file.txt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/util/date.py',
|
||||||
|
output: 'util/date.py',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: '../../../../../runtime-traced-files/util/math.py',
|
||||||
|
output: 'util/math.py',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(output.server.pages['file.txt']).toBeUndefined();
|
||||||
|
expect(output.server.pages.api['file.txt']).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
1
packages/build-utils/test/walk/python-api/api/index.py
Normal file
1
packages/build-utils/test/walk/python-api/api/index.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# index
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# get
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# post
|
||||||
1
packages/build-utils/test/walk/python-api/file.txt
Normal file
1
packages/build-utils/test/walk/python-api/file.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This file should also be included
|
||||||
1
packages/build-utils/test/walk/python-api/util/date.py
Normal file
1
packages/build-utils/test/walk/python-api/util/date.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# date
|
||||||
1
packages/build-utils/test/walk/python-api/util/math.py
Normal file
1
packages/build-utils/test/walk/python-api/util/math.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# math
|
||||||
10
packages/build-utils/test/walk/python-api/vercel.json
Normal file
10
packages/build-utils/test/walk/python-api/vercel.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"functions": {
|
||||||
|
"api/users/post.py": {
|
||||||
|
"memory": 3008
|
||||||
|
},
|
||||||
|
"api/not-matching-anything.py": {
|
||||||
|
"memory": 768
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
packages/cli/@types/intercept-stdout/index.d.ts
vendored
11
packages/cli/@types/intercept-stdout/index.d.ts
vendored
@@ -1,11 +0,0 @@
|
|||||||
declare module 'intercept-stdout' {
|
|
||||||
export default function (fn?: InterceptFn): UnhookIntercept
|
|
||||||
}
|
|
||||||
|
|
||||||
interface InterceptFn {
|
|
||||||
(text: string): string | void
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UnhookIntercept {
|
|
||||||
(): void
|
|
||||||
}
|
|
||||||
5
packages/cli/@types/promisepipe/index.d.ts
vendored
5
packages/cli/@types/promisepipe/index.d.ts
vendored
@@ -1,5 +0,0 @@
|
|||||||
declare module 'promisepipe' {
|
|
||||||
export default function (
|
|
||||||
...streams: Array<NodeJS.ReadableStream | NodeJS.WritableStream | NodeJS.ReadWriteStream>
|
|
||||||
): Promise<void>
|
|
||||||
}
|
|
||||||
@@ -10,9 +10,13 @@
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Vercel is the optimal workflow for frontend teams. All-in-one: Static and Jamstack deployment, Serverless Functions, and Global CDN.
|
Vercel is a platform for **static sites and frontend frameworks**, built to integrate with your headless content, commerce, or database.
|
||||||
|
|
||||||
To install the latest version of Vercel CLI, visit [vercel.com/download](https://vercel.com/download) or run this command:
|
We provide a **frictionless developer experience** to take care of the hard things: deploy instantly, scale automatically, and serve personalized content around the globe.
|
||||||
|
|
||||||
|
We make it easy for frontend teams to **develop, preview, and ship** delightful user experiences, where performance is the default.
|
||||||
|
|
||||||
|
To install the latest version of Vercel CLI, run this command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm i -g vercel
|
npm i -g vercel
|
||||||
@@ -26,6 +30,8 @@ cd <PROJECT> # Change directory to the new project
|
|||||||
vercel # Deploy to the cloud
|
vercel # Deploy to the cloud
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Finally, [connect your Git repository to Vercel](https://vercel.com/docs/git) and deploy with `git push`.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
For details on how to use Vercel CLI, check out our [documentation](https://vercel.com/docs).
|
For details on how to use Vercel CLI, check out our [documentation](https://vercel.com/docs).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vercel",
|
"name": "vercel",
|
||||||
"version": "23.1.1",
|
"version": "23.1.3-canary.38",
|
||||||
"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",
|
||||||
@@ -12,33 +12,15 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "node ./scripts/preinstall.js",
|
"preinstall": "node ./scripts/preinstall.js",
|
||||||
"test-unit": "nyc ava test/unit.js test/dev-builder.unit.js test/dev-router.unit.js test/dev-server.unit.js test/dev-validate.unit.js --serial --fail-fast --verbose",
|
"test": "jest",
|
||||||
|
"test-unit": "jest --coverage --verbose",
|
||||||
"test-integration-cli": "rimraf test/fixtures/integration && ava test/integration.js --serial --fail-fast --verbose",
|
"test-integration-cli": "rimraf test/fixtures/integration && ava test/integration.js --serial --fail-fast --verbose",
|
||||||
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
|
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
|
||||||
"prepublishOnly": "yarn build",
|
"prepublishOnly": "yarn build",
|
||||||
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
|
"coverage": "codecov",
|
||||||
"build": "node -r ts-eager/register ./scripts/build.ts",
|
"build": "node -r ts-eager/register ./scripts/build.ts",
|
||||||
"build-dev": "node -r ts-eager/register ./scripts/build.ts --dev"
|
"build-dev": "node -r ts-eager/register ./scripts/build.ts --dev"
|
||||||
},
|
},
|
||||||
"nyc": {
|
|
||||||
"include": [
|
|
||||||
"src/**"
|
|
||||||
],
|
|
||||||
"extension": [
|
|
||||||
".js",
|
|
||||||
".ts"
|
|
||||||
],
|
|
||||||
"require": [
|
|
||||||
"ts-node/register"
|
|
||||||
],
|
|
||||||
"reporter": [
|
|
||||||
"text",
|
|
||||||
"html"
|
|
||||||
],
|
|
||||||
"sourceMap": true,
|
|
||||||
"instrument": true,
|
|
||||||
"all": true
|
|
||||||
},
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"vc": "./dist/index.js",
|
"vc": "./dist/index.js",
|
||||||
"vercel": "./dist/index.js"
|
"vercel": "./dist/index.js"
|
||||||
@@ -61,14 +43,17 @@
|
|||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vercel/build-utils": "2.12.1",
|
"@vercel/build-utils": "2.12.3-canary.20",
|
||||||
"@vercel/go": "1.2.3",
|
"@vercel/go": "1.2.4-canary.4",
|
||||||
"@vercel/node": "1.12.0",
|
"@vercel/node": "1.12.2-canary.7",
|
||||||
"@vercel/python": "2.0.5",
|
"@vercel/python": "2.1.1",
|
||||||
"@vercel/ruby": "1.2.7",
|
"@vercel/ruby": "1.2.8-canary.4",
|
||||||
"update-notifier": "4.1.0"
|
"update-notifier": "4.1.0",
|
||||||
|
"vercel-plugin-middleware": "0.0.0-canary.7",
|
||||||
|
"vercel-plugin-node": "1.12.2-canary.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@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",
|
||||||
"@tootallnate/once": "1.1.2",
|
"@tootallnate/once": "1.1.2",
|
||||||
@@ -76,13 +61,16 @@
|
|||||||
"@types/ansi-regex": "4.0.0",
|
"@types/ansi-regex": "4.0.0",
|
||||||
"@types/async-retry": "1.2.1",
|
"@types/async-retry": "1.2.1",
|
||||||
"@types/bytes": "3.0.0",
|
"@types/bytes": "3.0.0",
|
||||||
|
"@types/chance": "1.1.3",
|
||||||
"@types/debug": "0.0.31",
|
"@types/debug": "0.0.31",
|
||||||
"@types/dotenv": "6.1.1",
|
"@types/dotenv": "6.1.1",
|
||||||
"@types/escape-html": "0.0.20",
|
"@types/escape-html": "0.0.20",
|
||||||
"@types/fs-extra": "5.0.5",
|
"@types/express": "4.17.13",
|
||||||
|
"@types/fs-extra": "9.0.13",
|
||||||
"@types/glob": "7.1.1",
|
"@types/glob": "7.1.1",
|
||||||
"@types/http-proxy": "1.16.2",
|
"@types/http-proxy": "1.16.2",
|
||||||
"@types/inquirer": "7.3.1",
|
"@types/inquirer": "7.3.1",
|
||||||
|
"@types/jest": "27.0.1",
|
||||||
"@types/load-json-file": "2.0.7",
|
"@types/load-json-file": "2.0.7",
|
||||||
"@types/mime-types": "2.1.0",
|
"@types/mime-types": "2.1.0",
|
||||||
"@types/minimatch": "3.0.3",
|
"@types/minimatch": "3.0.3",
|
||||||
@@ -97,11 +85,14 @@
|
|||||||
"@types/semver": "6.0.1",
|
"@types/semver": "6.0.1",
|
||||||
"@types/tar-fs": "1.16.1",
|
"@types/tar-fs": "1.16.1",
|
||||||
"@types/text-table": "0.2.0",
|
"@types/text-table": "0.2.0",
|
||||||
|
"@types/title": "3.4.1",
|
||||||
"@types/universal-analytics": "0.4.2",
|
"@types/universal-analytics": "0.4.2",
|
||||||
|
"@types/update-notifier": "5.1.0",
|
||||||
"@types/which": "1.3.2",
|
"@types/which": "1.3.2",
|
||||||
"@types/write-json-file": "2.2.1",
|
"@types/write-json-file": "2.2.1",
|
||||||
"@vercel/frameworks": "0.5.1-canary.0",
|
"@vercel/frameworks": "0.5.1-canary.12",
|
||||||
"@vercel/ncc": "0.24.0",
|
"@vercel/ncc": "0.24.0",
|
||||||
|
"@vercel/nft": "0.17.0",
|
||||||
"@zeit/fun": "0.11.2",
|
"@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",
|
||||||
@@ -115,6 +106,7 @@
|
|||||||
"ava": "2.2.0",
|
"ava": "2.2.0",
|
||||||
"bytes": "3.0.0",
|
"bytes": "3.0.0",
|
||||||
"chalk": "4.1.0",
|
"chalk": "4.1.0",
|
||||||
|
"chance": "1.1.7",
|
||||||
"chokidar": "3.3.1",
|
"chokidar": "3.3.1",
|
||||||
"clipboardy": "2.1.0",
|
"clipboardy": "2.1.0",
|
||||||
"codecov": "3.8.2",
|
"codecov": "3.8.2",
|
||||||
@@ -130,8 +122,9 @@
|
|||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
"esm": "3.1.4",
|
"esm": "3.1.4",
|
||||||
"execa": "3.2.0",
|
"execa": "3.2.0",
|
||||||
|
"express": "4.17.1",
|
||||||
"fast-deep-equal": "3.1.3",
|
"fast-deep-equal": "3.1.3",
|
||||||
"fs-extra": "7.0.1",
|
"fs-extra": "10.0.0",
|
||||||
"get-port": "5.1.1",
|
"get-port": "5.1.1",
|
||||||
"glob": "7.1.2",
|
"glob": "7.1.2",
|
||||||
"http-proxy": "1.18.1",
|
"http-proxy": "1.18.1",
|
||||||
@@ -148,7 +141,6 @@
|
|||||||
"ms": "2.1.2",
|
"ms": "2.1.2",
|
||||||
"node-fetch": "2.6.1",
|
"node-fetch": "2.6.1",
|
||||||
"npm-package-arg": "6.1.0",
|
"npm-package-arg": "6.1.0",
|
||||||
"nyc": "13.2.0",
|
|
||||||
"open": "8.2.0",
|
"open": "8.2.0",
|
||||||
"ora": "3.4.0",
|
"ora": "3.4.0",
|
||||||
"pcre-to-regexp": "1.0.0",
|
"pcre-to-regexp": "1.0.0",
|
||||||
@@ -161,7 +153,6 @@
|
|||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"semver": "5.5.0",
|
"semver": "5.5.0",
|
||||||
"serve-handler": "6.1.1",
|
"serve-handler": "6.1.1",
|
||||||
"sinon": "4.4.2",
|
|
||||||
"strip-ansi": "5.2.0",
|
"strip-ansi": "5.2.0",
|
||||||
"stripe": "5.1.0",
|
"stripe": "5.1.0",
|
||||||
"tar-fs": "1.16.3",
|
"tar-fs": "1.16.3",
|
||||||
@@ -178,5 +169,19 @@
|
|||||||
"which": "2.0.2",
|
"which": "2.0.2",
|
||||||
"write-json-file": "2.2.0",
|
"write-json-file": "2.2.0",
|
||||||
"xdg-app-paths": "5.1.0"
|
"xdg-app-paths": "5.1.0"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"preset": "ts-jest",
|
||||||
|
"globals": {
|
||||||
|
"ts-jest": {
|
||||||
|
"diagnostics": false,
|
||||||
|
"isolatedModules": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"verbose": false,
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"testMatch": [
|
||||||
|
"<rootDir>/test/**/*.test.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { join } from 'path';
|
|||||||
import { remove, writeFile } from 'fs-extra';
|
import { remove, writeFile } from 'fs-extra';
|
||||||
|
|
||||||
const dirRoot = join(__dirname, '..');
|
const dirRoot = join(__dirname, '..');
|
||||||
|
const distRoot = join(dirRoot, 'dist');
|
||||||
|
|
||||||
async function createConstants() {
|
async function createConstants() {
|
||||||
console.log('Creating constants.ts');
|
console.log('Creating constants.ts');
|
||||||
@@ -48,13 +49,12 @@ async function main() {
|
|||||||
|
|
||||||
// Do the initial `ncc` build
|
// Do the initial `ncc` build
|
||||||
console.log();
|
console.log();
|
||||||
const src = join(dirRoot, 'src');
|
|
||||||
const args = ['ncc', 'build', '--external', 'update-notifier'];
|
const args = ['ncc', 'build', '--external', 'update-notifier'];
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
args.push('--source-map');
|
args.push('--source-map');
|
||||||
}
|
}
|
||||||
args.push(src);
|
args.push('src/index.ts');
|
||||||
await execa('yarn', args, { stdio: 'inherit' });
|
await execa('yarn', args, { stdio: 'inherit', cwd: dirRoot });
|
||||||
|
|
||||||
// `ncc` has some issues with `@zeit/fun`'s runtime files:
|
// `ncc` has some issues with `@zeit/fun`'s runtime files:
|
||||||
// - Executable bits on the `bootstrap` files appear to be lost:
|
// - Executable bits on the `bootstrap` files appear to be lost:
|
||||||
@@ -72,19 +72,13 @@ async function main() {
|
|||||||
dirRoot,
|
dirRoot,
|
||||||
'../../node_modules/@zeit/fun/dist/src/runtimes'
|
'../../node_modules/@zeit/fun/dist/src/runtimes'
|
||||||
);
|
);
|
||||||
const dest = join(dirRoot, 'dist/runtimes');
|
await cpy('**/*', join(distRoot, 'runtimes'), {
|
||||||
await cpy('**/*', dest, { parents: true, cwd: runtimes });
|
parents: true,
|
||||||
|
cwd: runtimes,
|
||||||
|
});
|
||||||
|
|
||||||
// Band-aid to delete stuff that `ncc` bundles, but it shouldn't:
|
// Band-aid to bundle stuff that `ncc` neglects to bundle
|
||||||
|
await cpy(join(dirRoot, 'src/util/projects/VERCEL_DIR_README.txt'), distRoot);
|
||||||
// TypeScript definition files from `@vercel/build-utils`
|
|
||||||
await remove(join(dirRoot, 'dist', 'dist'));
|
|
||||||
|
|
||||||
// The Readme and `package.json` from "config-chain" module
|
|
||||||
await remove(join(dirRoot, 'dist', 'config-chain'));
|
|
||||||
|
|
||||||
// A bunch of source `.ts` files from CLI's `util` directory
|
|
||||||
await remove(join(dirRoot, 'dist', 'util'));
|
|
||||||
|
|
||||||
console.log('Finished building Vercel CLI');
|
console.log('Finished building Vercel CLI');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ function handleCreateAliasError<T>(
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTargetsForAlias(args: string[], { alias }: VercelConfig) {
|
function getTargetsForAlias(args: string[], { alias }: VercelConfig = {}) {
|
||||||
if (args.length) {
|
if (args.length) {
|
||||||
return [args[args.length - 1]]
|
return [args[args.length - 1]]
|
||||||
.map(target => (target.indexOf('.') !== -1 ? toHost(target) : target))
|
.map(target => (target.indexOf('.') !== -1 ? toHost(target) : target))
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import cardBrands from '../../util/billing/card-brands';
|
|||||||
import success from '../../util/output/success';
|
import success from '../../util/output/success';
|
||||||
import wait from '../../util/output/wait';
|
import wait from '../../util/output/wait';
|
||||||
import chars from '../../util/output/chars';
|
import chars from '../../util/output/chars';
|
||||||
import rightPad from '../../util/output/right-pad';
|
|
||||||
import error from '../../util/output/error';
|
import error from '../../util/output/error';
|
||||||
|
|
||||||
const expDateMiddleware = data => data;
|
const expDateMiddleware = data => data;
|
||||||
|
|
||||||
export default async function({ creditCards, clear = false, contextName }) {
|
export default async function ({ creditCards, clear = false, contextName }) {
|
||||||
const state = {
|
const state = {
|
||||||
error: undefined,
|
error: undefined,
|
||||||
cardGroupLabel: `> ${chalk.bold(
|
cardGroupLabel: `> ${chalk.bold(
|
||||||
@@ -19,13 +18,13 @@ export default async function({ creditCards, clear = false, contextName }) {
|
|||||||
)}`,
|
)}`,
|
||||||
|
|
||||||
name: {
|
name: {
|
||||||
label: rightPad('Full Name', 12),
|
label: 'Full Name'.padEnd(12),
|
||||||
placeholder: 'John Appleseed',
|
placeholder: 'John Appleseed',
|
||||||
validateValue: data => data.trim().length > 0,
|
validateValue: data => data.trim().length > 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
cardNumber: {
|
cardNumber: {
|
||||||
label: rightPad('Number', 12),
|
label: 'Number'.padEnd(12),
|
||||||
mask: 'cc',
|
mask: 'cc',
|
||||||
placeholder: '#### #### #### ####',
|
placeholder: '#### #### #### ####',
|
||||||
validateKeypress: (data, value) => /\d/.test(data) && value.length < 19,
|
validateKeypress: (data, value) => /\d/.test(data) && value.length < 19,
|
||||||
@@ -40,7 +39,7 @@ export default async function({ creditCards, clear = false, contextName }) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
ccv: {
|
ccv: {
|
||||||
label: rightPad('CCV', 12),
|
label: 'CCV'.padEnd(12),
|
||||||
mask: 'ccv',
|
mask: 'ccv',
|
||||||
placeholder: '###',
|
placeholder: '###',
|
||||||
validateValue: data => {
|
validateValue: data => {
|
||||||
@@ -50,7 +49,7 @@ export default async function({ creditCards, clear = false, contextName }) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
expDate: {
|
expDate: {
|
||||||
label: rightPad('Exp. Date', 12),
|
label: 'Exp. Date'.padEnd(12),
|
||||||
mask: 'expDate',
|
mask: 'expDate',
|
||||||
placeholder: 'mm / yyyy',
|
placeholder: 'mm / yyyy',
|
||||||
middleware: expDateMiddleware,
|
middleware: expDateMiddleware,
|
||||||
@@ -147,9 +146,9 @@ export default async function({ creditCards, clear = false, contextName }) {
|
|||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
success(
|
success(
|
||||||
`${state.cardNumber.brand ||
|
`${state.cardNumber.brand || state.cardNumber.card.brand} ending in ${
|
||||||
state.cardNumber.card.brand} ending in ${res.last4 ||
|
res.last4 || res.card.last4
|
||||||
res.card.last4} was added to ${chalk.bold(contextName)}`
|
} was added to ${chalk.bold(contextName)}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import ms from 'ms';
|
|||||||
import plural from 'pluralize';
|
import plural from 'pluralize';
|
||||||
import { error } from '../../util/error';
|
import { error } from '../../util/error';
|
||||||
import NowCreditCards from '../../util/credit-cards';
|
import NowCreditCards from '../../util/credit-cards';
|
||||||
import indent from '../../util/indent';
|
import indent from '../../util/output/indent';
|
||||||
import listInput from '../../util/input/list';
|
import listInput from '../../util/input/list';
|
||||||
import success from '../../util/output/success';
|
import success from '../../util/output/success';
|
||||||
import promptBool from '../../util/input/prompt-bool';
|
import promptBool from '../../util/input/prompt-bool';
|
||||||
@@ -51,8 +51,6 @@ const help = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let argv;
|
let argv;
|
||||||
let debug;
|
|
||||||
let apiUrl;
|
|
||||||
let subcommand;
|
let subcommand;
|
||||||
|
|
||||||
export default async client => {
|
export default async client => {
|
||||||
@@ -65,8 +63,6 @@ export default async client => {
|
|||||||
|
|
||||||
argv._ = argv._.slice(1);
|
argv._ = argv._.slice(1);
|
||||||
|
|
||||||
debug = argv['--debug'];
|
|
||||||
apiUrl = client.apiUrl;
|
|
||||||
subcommand = argv._[0];
|
subcommand = argv._[0];
|
||||||
|
|
||||||
if (argv['--help'] || !subcommand) {
|
if (argv['--help'] || !subcommand) {
|
||||||
@@ -76,17 +72,13 @@ export default async client => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
output,
|
output,
|
||||||
authConfig: { token },
|
|
||||||
config: { currentTeam },
|
config: { currentTeam },
|
||||||
} = client;
|
} = client;
|
||||||
|
|
||||||
const start = new Date();
|
const start = new Date();
|
||||||
const creditCards = new NowCreditCards({
|
const creditCards = new NowCreditCards({
|
||||||
apiUrl,
|
client,
|
||||||
token,
|
|
||||||
debug,
|
|
||||||
currentTeam,
|
currentTeam,
|
||||||
output,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let contextName = null;
|
let contextName = null;
|
||||||
|
|||||||
791
packages/cli/src/commands/build.ts
Normal file
791
packages/cli/src/commands/build.ts
Normal file
@@ -0,0 +1,791 @@
|
|||||||
|
import { loadEnvConfig, processEnv } from '@next/env';
|
||||||
|
import {
|
||||||
|
execCommand,
|
||||||
|
getScriptName,
|
||||||
|
GlobOptions,
|
||||||
|
scanParentDirs,
|
||||||
|
spawnAsync,
|
||||||
|
} from '@vercel/build-utils';
|
||||||
|
import { nodeFileTrace } from '@vercel/nft';
|
||||||
|
import Sema from 'async-sema';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { SpawnOptions } from 'child_process';
|
||||||
|
import { assert } from 'console';
|
||||||
|
import { createHash } from 'crypto';
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import ogGlob from 'glob';
|
||||||
|
import { isAbsolute, join, parse, relative, resolve } from 'path';
|
||||||
|
import pluralize from 'pluralize';
|
||||||
|
import Client from '../util/client';
|
||||||
|
import { emoji, prependEmoji } from '../util/emoji';
|
||||||
|
import getArgs from '../util/get-args';
|
||||||
|
import handleError from '../util/handle-error';
|
||||||
|
import confirm from '../util/input/confirm';
|
||||||
|
import { isSettingValue } from '../util/is-setting-value';
|
||||||
|
import cmd from '../util/output/cmd';
|
||||||
|
import logo from '../util/output/logo';
|
||||||
|
import param from '../util/output/param';
|
||||||
|
import stamp from '../util/output/stamp';
|
||||||
|
import { getCommandName, getPkgName } from '../util/pkg-name';
|
||||||
|
import { loadCliPlugins } from '../util/plugins';
|
||||||
|
import { findFramework } from '../util/projects/find-framework';
|
||||||
|
import { VERCEL_DIR } from '../util/projects/link';
|
||||||
|
import { readProjectSettings } from '../util/projects/project-settings';
|
||||||
|
import pull from './pull';
|
||||||
|
|
||||||
|
const sema = new Sema(16, {
|
||||||
|
capacity: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
const help = () => {
|
||||||
|
return console.log(`
|
||||||
|
${chalk.bold(`${logo} ${getPkgName()} build`)}
|
||||||
|
|
||||||
|
${chalk.dim('Options:')}
|
||||||
|
|
||||||
|
-h, --help Output usage information
|
||||||
|
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||||
|
'FILE'
|
||||||
|
)} Path to the local ${'`vercel.json`'} file
|
||||||
|
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
|
||||||
|
'DIR'
|
||||||
|
)} Path to the global ${'`.vercel`'} directory
|
||||||
|
--cwd [path] The current working directory
|
||||||
|
-d, --debug Debug mode [off]
|
||||||
|
-y, --yes Skip the confirmation prompt
|
||||||
|
|
||||||
|
${chalk.dim('Examples:')}
|
||||||
|
|
||||||
|
${chalk.gray('–')} Build the project
|
||||||
|
|
||||||
|
${chalk.cyan(`$ ${getPkgName()} build`)}
|
||||||
|
${chalk.cyan(`$ ${getPkgName()} build --cwd ./path-to-project`)}
|
||||||
|
`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const OUTPUT_DIR = '.output';
|
||||||
|
|
||||||
|
export default async function main(client: Client) {
|
||||||
|
if (process.env.__VERCEL_BUILD_RUNNING) {
|
||||||
|
client.output.error(
|
||||||
|
`${cmd(
|
||||||
|
`${getPkgName()} build`
|
||||||
|
)} must not recursively invoke itself. Check the Build Command in the Project Settings or the ${cmd(
|
||||||
|
'build'
|
||||||
|
)} script in ${cmd('package.json')}`
|
||||||
|
);
|
||||||
|
client.output.error(
|
||||||
|
`Learn More: https://vercel.link/recursive-invocation-of-commands`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
process.env.__VERCEL_BUILD_RUNNING = '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
let argv;
|
||||||
|
const buildStamp = stamp();
|
||||||
|
try {
|
||||||
|
argv = getArgs(client.argv.slice(2), {
|
||||||
|
'--debug': Boolean,
|
||||||
|
'--cwd': String,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argv['--help']) {
|
||||||
|
help();
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cwd = argv['--cwd'] || process.cwd();
|
||||||
|
|
||||||
|
let project = await readProjectSettings(join(cwd, VERCEL_DIR));
|
||||||
|
// If there are no project settings, only then do we pull them down
|
||||||
|
while (!project?.settings) {
|
||||||
|
const confirmed = await confirm(
|
||||||
|
`No Project Settings found locally. Run ${getCommandName(
|
||||||
|
'pull'
|
||||||
|
)} for retrieving them?`,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
if (!confirmed) {
|
||||||
|
client.output.print(`Aborted. No Project Settings retrieved.\n`);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const result = await pull(client);
|
||||||
|
if (result !== 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
project = await readProjectSettings(join(cwd, VERCEL_DIR));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `rootDirectory` exists, then `baseDir` will be the repo's root directory.
|
||||||
|
const baseDir = cwd;
|
||||||
|
|
||||||
|
cwd = project.settings.rootDirectory
|
||||||
|
? join(cwd, project.settings.rootDirectory)
|
||||||
|
: cwd;
|
||||||
|
|
||||||
|
// Load the environment
|
||||||
|
const { combinedEnv, loadedEnvFiles } = loadEnvConfig(cwd, false, {
|
||||||
|
info: () => ({}), // we don't want to log this yet.
|
||||||
|
error: (...args: any[]) => client.output.error(args.join(' ')),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set process.env with loaded environment variables
|
||||||
|
await processEnv(loadedEnvFiles);
|
||||||
|
|
||||||
|
const spawnOpts = {
|
||||||
|
env: { ...combinedEnv, VERCEL: '1' },
|
||||||
|
};
|
||||||
|
|
||||||
|
process.chdir(cwd);
|
||||||
|
|
||||||
|
const framework = findFramework(project.settings.framework);
|
||||||
|
// If this is undefined, we bail. If it is null, then findFramework should return "Other",
|
||||||
|
// so this should really never happen, but just in case....
|
||||||
|
if (framework === undefined) {
|
||||||
|
client.output.error(
|
||||||
|
`Framework detection failed or is malformed. Please run ${getCommandName(
|
||||||
|
'pull'
|
||||||
|
)} again.`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildState = { ...project.settings };
|
||||||
|
const formatSetting = (
|
||||||
|
name: string,
|
||||||
|
override: string | null | undefined,
|
||||||
|
defaults: typeof framework.settings.outputDirectory
|
||||||
|
) =>
|
||||||
|
` - ${chalk.bold(`${name}:`)} ${`${
|
||||||
|
override
|
||||||
|
? override + ` (override)`
|
||||||
|
: 'placeholder' in defaults
|
||||||
|
? chalk.italic(`${defaults.placeholder}`)
|
||||||
|
: defaults.value
|
||||||
|
}`}`;
|
||||||
|
console.log(`Retrieved Project Settings:`);
|
||||||
|
console.log(
|
||||||
|
chalk.dim(` - ${chalk.bold(`Framework Preset:`)} ${framework.name}`)
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
chalk.dim(
|
||||||
|
formatSetting(
|
||||||
|
'Build Command',
|
||||||
|
project.settings.buildCommand,
|
||||||
|
framework.settings.buildCommand
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
chalk.dim(
|
||||||
|
formatSetting(
|
||||||
|
'Output Directory',
|
||||||
|
project.settings.outputDirectory,
|
||||||
|
framework.settings.outputDirectory
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
buildState.outputDirectory =
|
||||||
|
project.settings.outputDirectory ||
|
||||||
|
(isSettingValue(framework.settings.outputDirectory)
|
||||||
|
? framework.settings.outputDirectory.value
|
||||||
|
: null);
|
||||||
|
buildState.rootDirectory = project.settings.rootDirectory;
|
||||||
|
|
||||||
|
if (loadedEnvFiles.length > 0) {
|
||||||
|
console.log(
|
||||||
|
`Loaded Environment Variables from ${loadedEnvFiles.length} ${pluralize(
|
||||||
|
'file',
|
||||||
|
loadedEnvFiles.length
|
||||||
|
)}:`
|
||||||
|
);
|
||||||
|
for (let envFile of loadedEnvFiles) {
|
||||||
|
console.log(chalk.dim(` - ${envFile.path}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load plugins
|
||||||
|
const debug = argv['--debug'];
|
||||||
|
let plugins;
|
||||||
|
try {
|
||||||
|
plugins = await loadCliPlugins(cwd, client.output);
|
||||||
|
} catch (error) {
|
||||||
|
client.output.error('Failed to load CLI Plugins');
|
||||||
|
handleError(error, { debug });
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const origLog = console.log;
|
||||||
|
const origErr = console.error;
|
||||||
|
const prefixedLog = (
|
||||||
|
prefix: string,
|
||||||
|
args: any[],
|
||||||
|
logger: (...args: any[]) => void
|
||||||
|
) => {
|
||||||
|
if (typeof args[0] === 'string') {
|
||||||
|
args[0] = `${prefix} ${args[0]}`;
|
||||||
|
} else {
|
||||||
|
args.unshift(prefix);
|
||||||
|
}
|
||||||
|
return logger(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (plugins?.pluginCount && plugins?.pluginCount > 0) {
|
||||||
|
console.log(
|
||||||
|
`Loaded ${plugins.pluginCount} CLI ${pluralize(
|
||||||
|
'Plugin',
|
||||||
|
plugins.pluginCount
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
// preBuild Plugins
|
||||||
|
if (plugins.preBuildPlugins.length > 0) {
|
||||||
|
console.log(
|
||||||
|
`Running ${plugins.pluginCount} CLI ${pluralize(
|
||||||
|
'Plugin',
|
||||||
|
plugins.pluginCount
|
||||||
|
)} before Build Command:`
|
||||||
|
);
|
||||||
|
for (let item of plugins.preBuildPlugins) {
|
||||||
|
const { name, plugin, color } = item;
|
||||||
|
if (typeof plugin.preBuild === 'function') {
|
||||||
|
const pluginStamp = stamp();
|
||||||
|
const fullName = name + '.preBuild';
|
||||||
|
const prefix = chalk.gray(' > ') + color(fullName + ':');
|
||||||
|
client.output.debug(`Running ${fullName}:`);
|
||||||
|
try {
|
||||||
|
console.log = (...args: any[]) =>
|
||||||
|
prefixedLog(prefix, args, origLog);
|
||||||
|
console.error = (...args: any[]) =>
|
||||||
|
prefixedLog(prefix, args, origErr);
|
||||||
|
await plugin.preBuild();
|
||||||
|
client.output.debug(
|
||||||
|
`Completed ${fullName} ${chalk.dim(`${pluginStamp()}`)}`
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
client.output.error(`${prefix} failed`);
|
||||||
|
handleError(error, { debug });
|
||||||
|
return 1;
|
||||||
|
} finally {
|
||||||
|
console.log = origLog;
|
||||||
|
console.error = origErr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean the output directory
|
||||||
|
fs.removeSync(join(cwd, OUTPUT_DIR));
|
||||||
|
|
||||||
|
// Yarn v2 PnP mode may be activated, so force
|
||||||
|
// "node-modules" linker style
|
||||||
|
const env = {
|
||||||
|
YARN_NODE_LINKER: 'node-modules',
|
||||||
|
...spawnOpts.env,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof buildState.buildCommand === 'string') {
|
||||||
|
console.log(`Running Build Command: ${cmd(buildState.buildCommand)}`);
|
||||||
|
await execCommand(buildState.buildCommand, {
|
||||||
|
...spawnOpts,
|
||||||
|
env,
|
||||||
|
cwd,
|
||||||
|
});
|
||||||
|
} else if (fs.existsSync(join(cwd, 'package.json'))) {
|
||||||
|
await runPackageJsonScript(
|
||||||
|
client,
|
||||||
|
cwd,
|
||||||
|
['vercel-build', 'now-build', 'build'],
|
||||||
|
spawnOpts
|
||||||
|
);
|
||||||
|
} else if (typeof framework.settings.buildCommand.value === 'string') {
|
||||||
|
console.log(
|
||||||
|
`Running Build Command: ${cmd(framework.settings.buildCommand.value)}`
|
||||||
|
);
|
||||||
|
await execCommand(framework.settings.buildCommand.value, {
|
||||||
|
...spawnOpts,
|
||||||
|
env,
|
||||||
|
cwd,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(join(cwd, OUTPUT_DIR))) {
|
||||||
|
let outputDir = join(OUTPUT_DIR, 'static');
|
||||||
|
let distDir = await framework.getFsOutputDir(cwd);
|
||||||
|
if (framework.slug === 'nextjs') {
|
||||||
|
outputDir = OUTPUT_DIR;
|
||||||
|
}
|
||||||
|
const copyStamp = stamp();
|
||||||
|
await fs.ensureDir(join(cwd, outputDir));
|
||||||
|
const relativeDistDir = relative(cwd, distDir);
|
||||||
|
client.output.spinner(
|
||||||
|
`Copying files from ${param(distDir)} to ${param(outputDir)}`
|
||||||
|
);
|
||||||
|
const files = await glob(join(relativeDistDir, '**'), {
|
||||||
|
ignore: [
|
||||||
|
'node_modules/**',
|
||||||
|
'.vercel/**',
|
||||||
|
'.env',
|
||||||
|
'.env.*',
|
||||||
|
'.*ignore',
|
||||||
|
'_middleware.ts',
|
||||||
|
'_middleware.mts',
|
||||||
|
'_middleware.cts',
|
||||||
|
'_middleware.mjs',
|
||||||
|
'_middleware.cjs',
|
||||||
|
'_middleware.js',
|
||||||
|
'api/**',
|
||||||
|
'.git/**',
|
||||||
|
'.next/cache/**',
|
||||||
|
],
|
||||||
|
nodir: true,
|
||||||
|
dot: true,
|
||||||
|
cwd,
|
||||||
|
absolute: true,
|
||||||
|
});
|
||||||
|
await Promise.all(
|
||||||
|
files.map(f =>
|
||||||
|
smartCopy(
|
||||||
|
client,
|
||||||
|
f,
|
||||||
|
distDir === '.'
|
||||||
|
? join(cwd, outputDir, relative(cwd, f))
|
||||||
|
: f.replace(distDir, outputDir)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
client.output.stopSpinner();
|
||||||
|
console.log(
|
||||||
|
`Copied ${files.length.toLocaleString()} files from ${param(
|
||||||
|
distDir
|
||||||
|
)} to ${param(outputDir)} ${copyStamp()}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const buildManifestPath = join(cwd, OUTPUT_DIR, 'build-manifest.json');
|
||||||
|
const routesManifestPath = join(cwd, OUTPUT_DIR, 'routes-manifest.json');
|
||||||
|
|
||||||
|
if (!fs.existsSync(buildManifestPath)) {
|
||||||
|
client.output.debug(
|
||||||
|
`Generating build manifest: ${param(buildManifestPath)}`
|
||||||
|
);
|
||||||
|
const buildManifest = {
|
||||||
|
cache: framework.cachePattern ? [framework.cachePattern] : [],
|
||||||
|
};
|
||||||
|
await fs.writeJSON(buildManifestPath, buildManifest, { spaces: 2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(routesManifestPath)) {
|
||||||
|
client.output.debug(
|
||||||
|
`Generating routes manifest: ${param(routesManifestPath)}`
|
||||||
|
);
|
||||||
|
const routesManifest = {
|
||||||
|
version: 3,
|
||||||
|
pages404: true,
|
||||||
|
basePath: '',
|
||||||
|
redirects: framework.defaultRedirects ?? [],
|
||||||
|
headers: framework.defaultHeaders ?? [],
|
||||||
|
dynamicRoutes: [],
|
||||||
|
dataRoutes: [],
|
||||||
|
rewrites: framework.defaultRewrites ?? [],
|
||||||
|
};
|
||||||
|
await fs.writeJSON(
|
||||||
|
join(cwd, OUTPUT_DIR, 'routes-manifest.json'),
|
||||||
|
routesManifest,
|
||||||
|
{ spaces: 2 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special Next.js processing.
|
||||||
|
if (framework.slug === 'nextjs') {
|
||||||
|
// The contents of `.output/static` should be placed inside of `.output/static/_next/static`
|
||||||
|
const tempStatic = '___static';
|
||||||
|
await fs.rename(
|
||||||
|
join(cwd, OUTPUT_DIR, 'static'),
|
||||||
|
join(cwd, OUTPUT_DIR, tempStatic)
|
||||||
|
);
|
||||||
|
await fs.mkdirp(join(cwd, OUTPUT_DIR, 'static', '_next', 'static'));
|
||||||
|
await fs.rename(
|
||||||
|
join(cwd, OUTPUT_DIR, tempStatic),
|
||||||
|
join(cwd, OUTPUT_DIR, 'static', '_next', 'static')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Next.js might reference files from the `static` directory in `middleware-manifest.json`.
|
||||||
|
// Since we move all files from `static` to `static/_next/static`, we'll need to change
|
||||||
|
// those references as well and update the manifest file.
|
||||||
|
const middlewareManifest = join(
|
||||||
|
cwd,
|
||||||
|
OUTPUT_DIR,
|
||||||
|
'server',
|
||||||
|
'middleware-manifest.json'
|
||||||
|
);
|
||||||
|
if (fs.existsSync(middlewareManifest)) {
|
||||||
|
const manifest = await fs.readJSON(middlewareManifest);
|
||||||
|
Object.keys(manifest.middleware).forEach(key => {
|
||||||
|
const files = manifest.middleware[key].files.map((f: string) => {
|
||||||
|
if (f.startsWith('static/')) {
|
||||||
|
const next = f.replace(/^static\//gm, 'static/_next/static/');
|
||||||
|
client.output.debug(
|
||||||
|
`Replacing file in \`middleware-manifest.json\`: ${f} => ${next}`
|
||||||
|
);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return f;
|
||||||
|
});
|
||||||
|
|
||||||
|
manifest.middleware[key].files = files;
|
||||||
|
});
|
||||||
|
|
||||||
|
await fs.writeJSON(middlewareManifest, manifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to pick up directories for user-provided static files into `.`output/static`.
|
||||||
|
// More specifically, the static directory contents would then be mounted to `output/static/static`,
|
||||||
|
// and the public directory contents would be mounted to `output/static`. Old Next.js versions
|
||||||
|
// allow `static`, and newer ones allow both, but since there's nobody that actually uses both,
|
||||||
|
// we can check for the existence of both and pick the first match that we find (first
|
||||||
|
// `public`, then`static`). We can't read both at the same time because that would mean we'd
|
||||||
|
// read public for old Next.js versions that don't support it, which might be breaking (and
|
||||||
|
// we don't want to make vercel build specific framework versions).
|
||||||
|
const publicFiles = await glob('public/**', {
|
||||||
|
nodir: true,
|
||||||
|
dot: true,
|
||||||
|
cwd,
|
||||||
|
absolute: true,
|
||||||
|
});
|
||||||
|
if (publicFiles.length > 0) {
|
||||||
|
await Promise.all(
|
||||||
|
publicFiles.map(f =>
|
||||||
|
smartCopy(
|
||||||
|
client,
|
||||||
|
f,
|
||||||
|
f.replace('public', join(OUTPUT_DIR, 'static'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const staticFiles = await glob('static/**', {
|
||||||
|
nodir: true,
|
||||||
|
dot: true,
|
||||||
|
cwd,
|
||||||
|
absolute: true,
|
||||||
|
});
|
||||||
|
await Promise.all(
|
||||||
|
staticFiles.map(f =>
|
||||||
|
smartCopy(
|
||||||
|
client,
|
||||||
|
f,
|
||||||
|
f.replace('static', join(OUTPUT_DIR, 'static', 'static'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regardless of the Next.js version, we make sure that it is compatible with
|
||||||
|
// the Filesystem API. We get there by moving all the files needed
|
||||||
|
// into the outputs directory `inputs` folder. Next.js is > 12, we can
|
||||||
|
// read the .nft.json files directly. If there aren't .nft.json files
|
||||||
|
// we trace and create them. We then resolve the files in each nft file list
|
||||||
|
// and move them into the "inputs" directory. We rename them with hashes to
|
||||||
|
// prevent collisions and then update the related .nft files accordingly
|
||||||
|
// to point to the newly named input files. Again, all of this is so that Next.js
|
||||||
|
// works with the Filesystem API (and so .output contains all inputs
|
||||||
|
// needed to run Next.js) and `vc --prebuilt`.
|
||||||
|
const nftFiles = await glob(join(OUTPUT_DIR, '**', '*.nft.json'), {
|
||||||
|
nodir: true,
|
||||||
|
dot: true,
|
||||||
|
cwd,
|
||||||
|
absolute: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there are no .nft.json files, we know that Next.js < 12. We then
|
||||||
|
// execute the tracing on our own.
|
||||||
|
if (nftFiles.length === 0) {
|
||||||
|
const serverFiles = await glob(
|
||||||
|
join(OUTPUT_DIR, 'server', 'pages', '**', '*.js'),
|
||||||
|
{
|
||||||
|
nodir: true,
|
||||||
|
dot: true,
|
||||||
|
cwd,
|
||||||
|
ignore: ['webpack-runtime.js'],
|
||||||
|
absolute: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
for (let f of serverFiles) {
|
||||||
|
const { ext, dir } = parse(f);
|
||||||
|
const { fileList } = await nodeFileTrace([f], {
|
||||||
|
ignore: [
|
||||||
|
relative(cwd, f),
|
||||||
|
'node_modules/next/dist/pages/**/*',
|
||||||
|
'node_modules/next/dist/compiled/webpack/(bundle4|bundle5).js',
|
||||||
|
'node_modules/react/**/*.development.js',
|
||||||
|
'node_modules/react-dom/**/*.development.js',
|
||||||
|
'node_modules/use-subscription/**/*.development.js',
|
||||||
|
'node_modules/sharp/**/*',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
fileList.delete(relative(cwd, f));
|
||||||
|
await resolveNftToOutput({
|
||||||
|
client,
|
||||||
|
baseDir,
|
||||||
|
outputDir: OUTPUT_DIR,
|
||||||
|
nftFileName: f.replace(ext, '.js.nft.json'),
|
||||||
|
nft: {
|
||||||
|
version: 1,
|
||||||
|
files: Array.from(fileList).map(fileListEntry =>
|
||||||
|
relative(dir, fileListEntry)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let f of nftFiles) {
|
||||||
|
const json = await fs.readJson(f);
|
||||||
|
await resolveNftToOutput({
|
||||||
|
client,
|
||||||
|
baseDir,
|
||||||
|
outputDir: OUTPUT_DIR,
|
||||||
|
nftFileName: f,
|
||||||
|
nft: json,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const requiredServerFilesPath = join(
|
||||||
|
OUTPUT_DIR,
|
||||||
|
'required-server-files.json'
|
||||||
|
);
|
||||||
|
const requiredServerFilesJson = await fs.readJSON(
|
||||||
|
requiredServerFilesPath
|
||||||
|
);
|
||||||
|
await fs.writeJSON(requiredServerFilesPath, {
|
||||||
|
...requiredServerFilesJson,
|
||||||
|
appDir: '.',
|
||||||
|
files: requiredServerFilesJson.files.map((i: string) => {
|
||||||
|
const absolutePath = join(cwd, i.replace('.next', '.output'));
|
||||||
|
const output = relative(baseDir, absolutePath);
|
||||||
|
|
||||||
|
return {
|
||||||
|
input: i.replace('.next', '.output'),
|
||||||
|
output,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build Plugins
|
||||||
|
if (plugins?.buildPlugins && plugins.buildPlugins.length > 0) {
|
||||||
|
console.log(
|
||||||
|
`Running ${plugins.pluginCount} CLI ${pluralize(
|
||||||
|
'Plugin',
|
||||||
|
plugins.pluginCount
|
||||||
|
)} after Build Command:`
|
||||||
|
);
|
||||||
|
for (let item of plugins.buildPlugins) {
|
||||||
|
const { name, plugin, color } = item;
|
||||||
|
if (typeof plugin.build === 'function') {
|
||||||
|
const pluginStamp = stamp();
|
||||||
|
const fullName = name + '.build';
|
||||||
|
const prefix = chalk.gray(' > ') + color(fullName + ':');
|
||||||
|
client.output.debug(`Running ${fullName}:`);
|
||||||
|
try {
|
||||||
|
console.log = (...args: any[]) => prefixedLog(prefix, args, origLog);
|
||||||
|
console.error = (...args: any[]) =>
|
||||||
|
prefixedLog(prefix, args, origErr);
|
||||||
|
await plugin.build({
|
||||||
|
workPath: cwd,
|
||||||
|
});
|
||||||
|
client.output.debug(
|
||||||
|
`Completed ${fullName} ${chalk.dim(`${pluginStamp()}`)}`
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
client.output.error(`${prefix} failed`);
|
||||||
|
handleError(error, { debug });
|
||||||
|
return 1;
|
||||||
|
} finally {
|
||||||
|
console.log = origLog;
|
||||||
|
console.error = origLog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`${prependEmoji(
|
||||||
|
`Build Completed in ${chalk.bold(OUTPUT_DIR)} ${chalk.gray(
|
||||||
|
buildStamp()
|
||||||
|
)}`,
|
||||||
|
emoji('success')
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runPackageJsonScript(
|
||||||
|
client: Client,
|
||||||
|
destPath: string,
|
||||||
|
scriptNames: string | Iterable<string>,
|
||||||
|
spawnOpts?: SpawnOptions
|
||||||
|
) {
|
||||||
|
assert(isAbsolute(destPath));
|
||||||
|
|
||||||
|
const { packageJson, cliType, lockfileVersion } = await scanParentDirs(
|
||||||
|
destPath,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const scriptName = getScriptName(
|
||||||
|
packageJson,
|
||||||
|
typeof scriptNames === 'string' ? [scriptNames] : scriptNames
|
||||||
|
);
|
||||||
|
if (!scriptName) return false;
|
||||||
|
|
||||||
|
client.output.debug('Running user script...');
|
||||||
|
const runScriptTime = Date.now();
|
||||||
|
|
||||||
|
const opts: any = { cwd: destPath, ...spawnOpts };
|
||||||
|
const env = (opts.env = { ...process.env, ...opts.env });
|
||||||
|
|
||||||
|
if (cliType === 'npm') {
|
||||||
|
opts.prettyCommand = `npm run ${scriptName}`;
|
||||||
|
|
||||||
|
if (typeof lockfileVersion === 'number' && lockfileVersion >= 2) {
|
||||||
|
// Ensure that npm 7 is at the beginning of the `$PATH`
|
||||||
|
env.PATH = `/node16/bin-npm7:${env.PATH}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opts.prettyCommand = `yarn run ${scriptName}`;
|
||||||
|
|
||||||
|
// Yarn v2 PnP mode may be activated, so force "node-modules" linker style
|
||||||
|
if (!env.YARN_NODE_LINKER) {
|
||||||
|
env.YARN_NODE_LINKER = 'node-modules';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Running Build Command: ${cmd(opts.prettyCommand)}\n`);
|
||||||
|
await spawnAsync(cliType, ['run', scriptName], opts);
|
||||||
|
console.log(); // give it some room
|
||||||
|
client.output.debug(`Script complete [${Date.now() - runScriptTime}ms]`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function linkOrCopy(existingPath: string, newPath: string) {
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
newPath.endsWith('.nft.json') ||
|
||||||
|
newPath.endsWith('middleware-manifest.json') ||
|
||||||
|
newPath.endsWith('required-server-files.json')
|
||||||
|
) {
|
||||||
|
await fs.copy(existingPath, newPath, {
|
||||||
|
overwrite: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await fs.createLink(existingPath, newPath);
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
// eslint-disable-line
|
||||||
|
// If a symlink to the same file already exists
|
||||||
|
// then trying to copy it will make an empty file from it.
|
||||||
|
if (err['code'] === 'EEXIST') return;
|
||||||
|
// In some VERY rare cases (1 in a thousand), symlink creation fails on Windows.
|
||||||
|
// In that case, we just fall back to copying.
|
||||||
|
// This issue is reproducible with "pnpm add @material-ui/icons@4.9.1"
|
||||||
|
await fs.copy(existingPath, newPath, {
|
||||||
|
overwrite: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function smartCopy(client: Client, from: string, to: string) {
|
||||||
|
sema.acquire();
|
||||||
|
try {
|
||||||
|
client.output.debug(`Copying from ${from} to ${to}`);
|
||||||
|
await linkOrCopy(from, to);
|
||||||
|
} finally {
|
||||||
|
sema.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function glob(pattern: string, options: GlobOptions): Promise<string[]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
ogGlob(pattern, options, (err, files) => {
|
||||||
|
err ? reject(err) : resolve(files);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes a hash for the given buf.
|
||||||
|
*
|
||||||
|
* @param {Buffer} file data
|
||||||
|
* @return {String} hex digest
|
||||||
|
*/
|
||||||
|
function hash(buf: Buffer): string {
|
||||||
|
return createHash('sha1').update(buf).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NftFile {
|
||||||
|
version: number;
|
||||||
|
files: (string | { input: string; output: string })[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveNftToOutput takes nft file and moves all of its trace files
|
||||||
|
// into the specified directory + `inputs`, (renaming them to their hash + ext) and
|
||||||
|
// subsequently updating the original nft file accordingly. This is done
|
||||||
|
// to make the `.output` directory be self-contained, so that it works
|
||||||
|
// properly with `vc --prebuilt`.
|
||||||
|
async function resolveNftToOutput({
|
||||||
|
client,
|
||||||
|
baseDir,
|
||||||
|
outputDir,
|
||||||
|
nftFileName,
|
||||||
|
nft,
|
||||||
|
}: {
|
||||||
|
client: Client;
|
||||||
|
baseDir: string;
|
||||||
|
outputDir: string;
|
||||||
|
nftFileName: string;
|
||||||
|
nft: NftFile;
|
||||||
|
}) {
|
||||||
|
client.output.debug(`Processing and resolving ${nftFileName}`);
|
||||||
|
await fs.ensureDir(join(outputDir, 'inputs'));
|
||||||
|
const newFilesList: NftFile['files'] = [];
|
||||||
|
for (let fileEntity of nft.files) {
|
||||||
|
const relativeInput: string =
|
||||||
|
typeof fileEntity === 'string' ? fileEntity : fileEntity.input;
|
||||||
|
const fullInput = resolve(join(parse(nftFileName).dir, relativeInput));
|
||||||
|
|
||||||
|
// if the resolved path is NOT in the .output directory we move in it there
|
||||||
|
if (!fullInput.includes(outputDir)) {
|
||||||
|
const { ext } = parse(fullInput);
|
||||||
|
const raw = await fs.readFile(fullInput);
|
||||||
|
const newFilePath = join(outputDir, 'inputs', hash(raw) + ext);
|
||||||
|
smartCopy(client, fullInput, newFilePath);
|
||||||
|
|
||||||
|
// We have to use `baseDir` instead of `cwd`, because we want to
|
||||||
|
// mount everything from there (especially `node_modules`).
|
||||||
|
// This is important for NPM Workspaces where `node_modules` is not
|
||||||
|
// in the directory of the workspace.
|
||||||
|
const output = relative(baseDir, fullInput).replace('.output', '.next');
|
||||||
|
|
||||||
|
newFilesList.push({
|
||||||
|
input: relative(parse(nftFileName).dir, newFilePath),
|
||||||
|
output,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
newFilesList.push(relativeInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update the .nft.json with new input and output mapping
|
||||||
|
await fs.writeJSON(nftFileName, {
|
||||||
|
...nft,
|
||||||
|
files: newFilesList,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -18,9 +18,10 @@ export const help = () => `
|
|||||||
init [example] Initialize an example project
|
init [example] Initialize an example project
|
||||||
ls | list [app] Lists deployments
|
ls | list [app] Lists deployments
|
||||||
inspect [id] Displays information related to a deployment
|
inspect [id] Displays information related to a deployment
|
||||||
link Link local directory to a Vercel Project
|
link [path] Link local directory to a Vercel Project
|
||||||
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
|
||||||
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]
|
help [cmd] Displays complete help for [cmd]
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,66 @@
|
|||||||
|
import ms from 'ms';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { resolve, basename } from 'path';
|
import bytes from 'bytes';
|
||||||
import { VercelConfig, fileNameSymbol } from '@vercel/client';
|
import chalk from 'chalk';
|
||||||
|
import { join, resolve, basename } from 'path';
|
||||||
|
import { Dictionary, 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';
|
||||||
import getArgs from '../../util/get-args';
|
import getArgs from '../../util/get-args';
|
||||||
import { handleError } from '../../util/error';
|
import { handleError } from '../../util/error';
|
||||||
import { help } from './args';
|
|
||||||
import deploy from './latest';
|
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
|
import { write as copy } from 'clipboardy';
|
||||||
|
import { getPrettyError } from '@vercel/build-utils';
|
||||||
|
import toHumanPath from '../../util/humanize-path';
|
||||||
|
import Now from '../../util';
|
||||||
|
import stamp from '../../util/output/stamp';
|
||||||
|
import createDeploy from '../../util/deploy/create-deploy';
|
||||||
|
import getDeploymentByIdOrHost from '../../util/deploy/get-deployment-by-id-or-host';
|
||||||
|
import parseMeta from '../../util/parse-meta';
|
||||||
|
import linkStyle from '../../util/output/link';
|
||||||
|
import param from '../../util/output/param';
|
||||||
|
import {
|
||||||
|
BuildsRateLimited,
|
||||||
|
DeploymentNotFound,
|
||||||
|
DeploymentPermissionDenied,
|
||||||
|
InvalidDeploymentId,
|
||||||
|
DomainNotFound,
|
||||||
|
DomainNotVerified,
|
||||||
|
DomainPermissionDenied,
|
||||||
|
DomainVerificationFailed,
|
||||||
|
InvalidDomain,
|
||||||
|
TooManyRequests,
|
||||||
|
UserAborted,
|
||||||
|
DeploymentsRateLimited,
|
||||||
|
AliasDomainConfigured,
|
||||||
|
MissingBuildScript,
|
||||||
|
ConflictingFilePath,
|
||||||
|
ConflictingPathSegment,
|
||||||
|
BuildError,
|
||||||
|
NotDomainOwner,
|
||||||
|
} from '../../util/errors-ts';
|
||||||
|
import { SchemaValidationFailed } from '../../util/errors';
|
||||||
|
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||||
|
import confirm from '../../util/input/confirm';
|
||||||
|
import editProjectSettings from '../../util/input/edit-project-settings';
|
||||||
|
import {
|
||||||
|
getLinkedProject,
|
||||||
|
linkFolderToProject,
|
||||||
|
} from '../../util/projects/link';
|
||||||
|
import getProjectName from '../../util/get-project-name';
|
||||||
|
import selectOrg from '../../util/input/select-org';
|
||||||
|
import inputProject from '../../util/input/input-project';
|
||||||
|
import { prependEmoji, emoji } from '../../util/emoji';
|
||||||
|
import { inputRootDirectory } from '../../util/input/input-root-directory';
|
||||||
|
import validatePaths, {
|
||||||
|
validateRootDirectory,
|
||||||
|
} from '../../util/validate-paths';
|
||||||
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
|
import { getPreferredPreviewURL } from '../../util/deploy/get-preferred-preview-url';
|
||||||
|
import { Output } from '../../util/output';
|
||||||
|
import { help } from './args';
|
||||||
|
import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
|
||||||
|
|
||||||
export default async (client: Client) => {
|
export default async (client: Client) => {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
@@ -27,6 +79,7 @@ export default async (client: Client) => {
|
|||||||
// This is not an array in favor of matching
|
// This is not an array in favor of matching
|
||||||
// the config property name.
|
// the config property name.
|
||||||
'--regions': String,
|
'--regions': String,
|
||||||
|
'--prebuilt': Boolean,
|
||||||
'--prod': Boolean,
|
'--prod': Boolean,
|
||||||
'--confirm': Boolean,
|
'--confirm': Boolean,
|
||||||
'-f': '--force',
|
'-f': '--force',
|
||||||
@@ -65,10 +118,7 @@ export default async (client: Client) => {
|
|||||||
paths = [process.cwd()];
|
paths = [process.cwd()];
|
||||||
}
|
}
|
||||||
|
|
||||||
let localConfig: VercelConfig | null = client.localConfig;
|
let localConfig = client.localConfig || readLocalConfig(paths[0]);
|
||||||
if (!localConfig || localConfig instanceof Error) {
|
|
||||||
localConfig = readLocalConfig(paths[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
try {
|
try {
|
||||||
@@ -105,5 +155,755 @@ export default async (client: Client) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return deploy(client, paths, localConfig, argv);
|
const { log, debug, error, warn, isTTY } = output;
|
||||||
|
|
||||||
|
const quiet = !isTTY;
|
||||||
|
|
||||||
|
// check paths
|
||||||
|
const pathValidation = await validatePaths(output, paths);
|
||||||
|
|
||||||
|
if (!pathValidation.valid) {
|
||||||
|
return pathValidation.exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { isFile, path } = pathValidation;
|
||||||
|
const autoConfirm = argv['--confirm'] || isFile;
|
||||||
|
|
||||||
|
// deprecate --name
|
||||||
|
if (argv['--name']) {
|
||||||
|
output.print(
|
||||||
|
`${prependEmoji(
|
||||||
|
`The ${param(
|
||||||
|
'--name'
|
||||||
|
)} option is deprecated (https://vercel.link/name-flag)`,
|
||||||
|
emoji('warning')
|
||||||
|
)}\n`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve `project` and `org` from .vercel
|
||||||
|
const link = await getLinkedProject(client, path);
|
||||||
|
|
||||||
|
if (link.status === 'error') {
|
||||||
|
return link.exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { org, project, status } = link;
|
||||||
|
|
||||||
|
let newProjectName = null;
|
||||||
|
let rootDirectory = project ? project.rootDirectory : null;
|
||||||
|
let sourceFilesOutsideRootDirectory = true;
|
||||||
|
|
||||||
|
if (status === 'not_linked') {
|
||||||
|
const shouldStartSetup =
|
||||||
|
autoConfirm ||
|
||||||
|
(await confirm(
|
||||||
|
`Set up and deploy ${chalk.cyan(`“${toHumanPath(path)}”`)}?`,
|
||||||
|
true
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!shouldStartSetup) {
|
||||||
|
output.print(`Aborted. Project not set up.\n`);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
org = await selectOrg(
|
||||||
|
client,
|
||||||
|
'Which scope do you want to deploy to?',
|
||||||
|
autoConfirm
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
||||||
|
output.error(err.message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use `localConfig` here to read the name
|
||||||
|
// even though the `vercel.json` file can change
|
||||||
|
// afterwards, this is fine since the property
|
||||||
|
// will be deprecated and can be replaced with
|
||||||
|
// user input.
|
||||||
|
const detectedProjectName = getProjectName({
|
||||||
|
argv,
|
||||||
|
nowConfig: localConfig || {},
|
||||||
|
isFile,
|
||||||
|
paths,
|
||||||
|
});
|
||||||
|
|
||||||
|
const projectOrNewProjectName = await inputProject(
|
||||||
|
client,
|
||||||
|
org,
|
||||||
|
detectedProjectName,
|
||||||
|
autoConfirm
|
||||||
|
);
|
||||||
|
|
||||||
|
if (typeof projectOrNewProjectName === 'string') {
|
||||||
|
newProjectName = projectOrNewProjectName;
|
||||||
|
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
|
||||||
|
} else {
|
||||||
|
project = projectOrNewProjectName;
|
||||||
|
rootDirectory = project.rootDirectory;
|
||||||
|
sourceFilesOutsideRootDirectory = project.sourceFilesOutsideRootDirectory;
|
||||||
|
|
||||||
|
// we can already link the project
|
||||||
|
await linkFolderToProject(
|
||||||
|
output,
|
||||||
|
path,
|
||||||
|
{
|
||||||
|
projectId: project.id,
|
||||||
|
orgId: org.id,
|
||||||
|
},
|
||||||
|
project.name,
|
||||||
|
org.slug
|
||||||
|
);
|
||||||
|
status = 'linked';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point `org` should be populated
|
||||||
|
if (!org) {
|
||||||
|
throw new Error(`"org" is not defined`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the `contextName` and `currentTeam` as specified by the
|
||||||
|
// Project Settings, so that API calls happen with the proper scope
|
||||||
|
const contextName = org.slug;
|
||||||
|
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
|
||||||
|
|
||||||
|
// if we have `sourceFilesOutsideRootDirectory` set to `true`, we use the current path
|
||||||
|
// and upload the entire directory.
|
||||||
|
const sourcePath =
|
||||||
|
rootDirectory && !sourceFilesOutsideRootDirectory
|
||||||
|
? join(path, rootDirectory)
|
||||||
|
: path;
|
||||||
|
|
||||||
|
if (
|
||||||
|
rootDirectory &&
|
||||||
|
(await validateRootDirectory(
|
||||||
|
output,
|
||||||
|
path,
|
||||||
|
sourcePath,
|
||||||
|
project
|
||||||
|
? `To change your Project Settings, go to https://vercel.com/${org?.slug}/${project.name}/settings`
|
||||||
|
: ''
|
||||||
|
)) === false
|
||||||
|
) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If Root Directory is used we'll try to read the config
|
||||||
|
// from there instead and use it if it exists.
|
||||||
|
if (rootDirectory) {
|
||||||
|
const rootDirectoryConfig = readLocalConfig(join(path, rootDirectory));
|
||||||
|
|
||||||
|
if (rootDirectoryConfig) {
|
||||||
|
debug(`Read local config from root directory (${rootDirectory})`);
|
||||||
|
localConfig = rootDirectoryConfig;
|
||||||
|
} else if (localConfig) {
|
||||||
|
output.print(
|
||||||
|
`${prependEmoji(
|
||||||
|
`The ${highlight(
|
||||||
|
localConfig[fileNameSymbol]!
|
||||||
|
)} file should be inside of the provided root directory.`,
|
||||||
|
emoji('warning')
|
||||||
|
)}\n`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localConfig = localConfig || {};
|
||||||
|
|
||||||
|
if (localConfig.name) {
|
||||||
|
output.print(
|
||||||
|
`${prependEmoji(
|
||||||
|
`The ${code('name')} property in ${highlight(
|
||||||
|
localConfig[fileNameSymbol]!
|
||||||
|
)} is deprecated (https://vercel.link/name-prop)`,
|
||||||
|
emoji('warning')
|
||||||
|
)}\n`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// build `env`
|
||||||
|
const isObject = (item: any) =>
|
||||||
|
Object.prototype.toString.call(item) === '[object Object]';
|
||||||
|
|
||||||
|
// This validation needs to happen on the client side because
|
||||||
|
// the data is merged with other data before it is passed to the API (which
|
||||||
|
// also does schema validation).
|
||||||
|
if (typeof localConfig.env !== 'undefined' && !isObject(localConfig.env)) {
|
||||||
|
error(
|
||||||
|
`The ${code('env')} property in ${highlight(
|
||||||
|
localConfig[fileNameSymbol]!
|
||||||
|
)} needs to be an object`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof localConfig.build !== 'undefined') {
|
||||||
|
if (!isObject(localConfig.build)) {
|
||||||
|
error(
|
||||||
|
`The ${code('build')} property in ${highlight(
|
||||||
|
localConfig[fileNameSymbol]!
|
||||||
|
)} needs to be an object`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof localConfig.build.env !== 'undefined' &&
|
||||||
|
!isObject(localConfig.build.env)
|
||||||
|
) {
|
||||||
|
error(
|
||||||
|
`The ${code('build.env')} property in ${highlight(
|
||||||
|
localConfig[fileNameSymbol]!
|
||||||
|
)} needs to be an object`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build `meta`
|
||||||
|
const meta = Object.assign(
|
||||||
|
{},
|
||||||
|
parseMeta(localConfig.meta),
|
||||||
|
parseMeta(argv['--meta'])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments
|
||||||
|
const deploymentEnv = Object.assign(
|
||||||
|
{},
|
||||||
|
parseEnv(localConfig.env),
|
||||||
|
parseEnv(argv['--env'])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Merge build env out of `build.env` from vercel.json, and `--build-env` args
|
||||||
|
const deploymentBuildEnv = Object.assign(
|
||||||
|
{},
|
||||||
|
parseEnv(localConfig.build && localConfig.build.env),
|
||||||
|
parseEnv(argv['--build-env'])
|
||||||
|
);
|
||||||
|
|
||||||
|
// If there's any undefined values, then inherit them from this process
|
||||||
|
try {
|
||||||
|
await addProcessEnv(log, deploymentEnv);
|
||||||
|
await addProcessEnv(log, deploymentBuildEnv);
|
||||||
|
} catch (err) {
|
||||||
|
error(err.message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build `regions`
|
||||||
|
const regionFlag = (argv['--regions'] || '')
|
||||||
|
.split(',')
|
||||||
|
.map((s: string) => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const regions = regionFlag.length > 0 ? regionFlag : localConfig.regions;
|
||||||
|
|
||||||
|
// build `target`
|
||||||
|
let target;
|
||||||
|
if (argv['--target']) {
|
||||||
|
const deprecatedTarget = argv['--target'];
|
||||||
|
|
||||||
|
if (!['staging', 'production'].includes(deprecatedTarget)) {
|
||||||
|
error(
|
||||||
|
`The specified ${param('--target')} ${code(
|
||||||
|
deprecatedTarget
|
||||||
|
)} is not valid`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deprecatedTarget === 'production') {
|
||||||
|
warn(
|
||||||
|
'We recommend using the much shorter `--prod` option instead of `--target production` (deprecated)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.debug(`Setting target to ${deprecatedTarget}`);
|
||||||
|
target = deprecatedTarget;
|
||||||
|
} else if (argv['--prod']) {
|
||||||
|
output.debug('Setting target to production');
|
||||||
|
target = 'production';
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTeam = org?.type === 'team' ? org.id : undefined;
|
||||||
|
const now = new Now({
|
||||||
|
client,
|
||||||
|
currentTeam,
|
||||||
|
});
|
||||||
|
let deployStamp = stamp();
|
||||||
|
let deployment = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const createArgs: any = {
|
||||||
|
name: project ? project.name : newProjectName,
|
||||||
|
env: deploymentEnv,
|
||||||
|
build: { env: deploymentBuildEnv },
|
||||||
|
forceNew: argv['--force'],
|
||||||
|
withCache: argv['--with-cache'],
|
||||||
|
prebuilt: argv['--prebuilt'],
|
||||||
|
quiet,
|
||||||
|
wantsPublic: argv['--public'] || localConfig.public,
|
||||||
|
isFile,
|
||||||
|
type: null,
|
||||||
|
nowConfig: localConfig,
|
||||||
|
regions,
|
||||||
|
meta,
|
||||||
|
deployStamp,
|
||||||
|
target,
|
||||||
|
skipAutoDetectionConfirmation: autoConfirm,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!localConfig.builds || localConfig.builds.length === 0) {
|
||||||
|
// Only add projectSettings for zero config deployments
|
||||||
|
createArgs.projectSettings = { sourceFilesOutsideRootDirectory };
|
||||||
|
}
|
||||||
|
|
||||||
|
deployment = await createDeploy(
|
||||||
|
client,
|
||||||
|
now,
|
||||||
|
contextName,
|
||||||
|
[sourcePath],
|
||||||
|
createArgs,
|
||||||
|
org,
|
||||||
|
!project && !isFile,
|
||||||
|
path
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deployment.code === 'missing_project_settings') {
|
||||||
|
let { projectSettings, framework } = deployment;
|
||||||
|
if (rootDirectory) {
|
||||||
|
projectSettings.rootDirectory = rootDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof sourceFilesOutsideRootDirectory !== 'undefined') {
|
||||||
|
projectSettings.sourceFilesOutsideRootDirectory =
|
||||||
|
sourceFilesOutsideRootDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = await editProjectSettings(
|
||||||
|
output,
|
||||||
|
projectSettings,
|
||||||
|
framework
|
||||||
|
);
|
||||||
|
|
||||||
|
// deploy again, but send projectSettings this time
|
||||||
|
createArgs.projectSettings = settings;
|
||||||
|
|
||||||
|
deployStamp = stamp();
|
||||||
|
createArgs.deployStamp = deployStamp;
|
||||||
|
deployment = await createDeploy(
|
||||||
|
client,
|
||||||
|
now,
|
||||||
|
contextName,
|
||||||
|
[sourcePath],
|
||||||
|
createArgs,
|
||||||
|
org,
|
||||||
|
false,
|
||||||
|
path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deployment instanceof NotDomainOwner) {
|
||||||
|
output.error(deployment.message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deployment instanceof Error) {
|
||||||
|
output.error(
|
||||||
|
deployment.message ||
|
||||||
|
'An unexpected error occurred while deploying your project',
|
||||||
|
undefined,
|
||||||
|
'https://vercel.link/help',
|
||||||
|
'Contact Support'
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deployment.readyState === 'CANCELED') {
|
||||||
|
output.print('The deployment has been canceled.\n');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deployment.checksConclusion === 'failed') {
|
||||||
|
const { checks } = await getDeploymentChecks(client, deployment.id);
|
||||||
|
const counters = new Map<string, number>();
|
||||||
|
checks.forEach(c => {
|
||||||
|
counters.set(c.conclusion, (counters.get(c.conclusion) ?? 0) + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const counterList = Array.from(counters)
|
||||||
|
.map(([name, no]) => `${no} ${name}`)
|
||||||
|
.join(', ');
|
||||||
|
output.error(`Running Checks: ${counterList}`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deploymentResponse = await getDeploymentByIdOrHost(
|
||||||
|
client,
|
||||||
|
contextName,
|
||||||
|
deployment.id,
|
||||||
|
'v10'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
deploymentResponse instanceof DeploymentNotFound ||
|
||||||
|
deploymentResponse instanceof DeploymentPermissionDenied ||
|
||||||
|
deploymentResponse instanceof InvalidDeploymentId
|
||||||
|
) {
|
||||||
|
output.error(deploymentResponse.message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deployment === null) {
|
||||||
|
error('Uploading failed. Please try again.');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
debug(`Error: ${err}\n${err.stack}`);
|
||||||
|
|
||||||
|
if (err instanceof NotDomainOwner) {
|
||||||
|
output.error(err.message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err instanceof DomainNotFound && err.meta && err.meta.domain) {
|
||||||
|
output.debug(
|
||||||
|
`The domain ${err.meta.domain} was not found, trying to purchase it`
|
||||||
|
);
|
||||||
|
|
||||||
|
const purchase = await purchaseDomainIfAvailable(
|
||||||
|
output,
|
||||||
|
client,
|
||||||
|
err.meta.domain,
|
||||||
|
contextName
|
||||||
|
);
|
||||||
|
|
||||||
|
if (purchase === true) {
|
||||||
|
output.success(`Successfully purchased the domain ${err.meta.domain}!`);
|
||||||
|
|
||||||
|
// We exit if the purchase is completed since
|
||||||
|
// the domain verification can take some time
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (purchase === false || purchase instanceof UserAborted) {
|
||||||
|
handleCreateDeployError(output, deployment, localConfig);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCreateDeployError(output, purchase, localConfig);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
err instanceof DomainNotFound ||
|
||||||
|
err instanceof DomainNotVerified ||
|
||||||
|
err instanceof NotDomainOwner ||
|
||||||
|
err instanceof DomainPermissionDenied ||
|
||||||
|
err instanceof DomainVerificationFailed ||
|
||||||
|
err instanceof SchemaValidationFailed ||
|
||||||
|
err instanceof InvalidDomain ||
|
||||||
|
err instanceof DeploymentNotFound ||
|
||||||
|
err instanceof BuildsRateLimited ||
|
||||||
|
err instanceof DeploymentsRateLimited ||
|
||||||
|
err instanceof AliasDomainConfigured ||
|
||||||
|
err instanceof MissingBuildScript ||
|
||||||
|
err instanceof ConflictingFilePath ||
|
||||||
|
err instanceof ConflictingPathSegment
|
||||||
|
) {
|
||||||
|
handleCreateDeployError(output, err, localConfig);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err instanceof BuildError) {
|
||||||
|
output.error(err.message || 'Build failed');
|
||||||
|
output.error(
|
||||||
|
`Check your logs at https://${now.url}/_logs or run ${getCommandName(
|
||||||
|
`logs ${now.url}`
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.keyword === 'additionalProperties' && err.dataPath === '.scale') {
|
||||||
|
const { additionalProperty = '' } = err.params || {};
|
||||||
|
const message = `Invalid DC name for the scale option: ${additionalProperty}`;
|
||||||
|
error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.code === 'size_limit_exceeded') {
|
||||||
|
const { sizeLimit = 0 } = err;
|
||||||
|
const message = `File size limit exceeded (${bytes(sizeLimit)})`;
|
||||||
|
error(message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleError(err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return printDeploymentStatus(
|
||||||
|
output,
|
||||||
|
client,
|
||||||
|
deployment,
|
||||||
|
deployStamp,
|
||||||
|
!argv['--no-clipboard'],
|
||||||
|
isFile
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleCreateDeployError(
|
||||||
|
output: Output,
|
||||||
|
error: Error,
|
||||||
|
localConfig: VercelConfig
|
||||||
|
) {
|
||||||
|
if (error instanceof InvalidDomain) {
|
||||||
|
output.error(`The domain ${error.meta.domain} is not valid`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (error instanceof DomainVerificationFailed) {
|
||||||
|
output.error(
|
||||||
|
`The domain used as a suffix ${chalk.underline(
|
||||||
|
error.meta.domain
|
||||||
|
)} is not verified and can't be used as custom suffix.`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (error instanceof DomainPermissionDenied) {
|
||||||
|
output.error(
|
||||||
|
`You don't have permissions to access the domain used as a suffix ${chalk.underline(
|
||||||
|
error.meta.domain
|
||||||
|
)}.`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (error instanceof SchemaValidationFailed) {
|
||||||
|
const niceError = getPrettyError(error.meta);
|
||||||
|
const fileName = localConfig[fileNameSymbol] || 'vercel.json';
|
||||||
|
niceError.message = `Invalid ${fileName} - ${niceError.message}`;
|
||||||
|
output.prettyError(niceError);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (error instanceof TooManyRequests) {
|
||||||
|
output.error(
|
||||||
|
`Too many requests detected for ${error.meta.api} API. Try again in ${ms(
|
||||||
|
error.meta.retryAfter * 1000,
|
||||||
|
{
|
||||||
|
long: true,
|
||||||
|
}
|
||||||
|
)}.`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (error instanceof DomainNotVerified) {
|
||||||
|
output.error(
|
||||||
|
`The domain used as an alias ${chalk.underline(
|
||||||
|
error.meta.domain
|
||||||
|
)} is not verified yet. Please verify it.`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (error instanceof BuildsRateLimited) {
|
||||||
|
output.error(error.message);
|
||||||
|
output.note(
|
||||||
|
`Run ${getCommandName('upgrade')} to increase your builds limit.`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
error instanceof DeploymentNotFound ||
|
||||||
|
error instanceof NotDomainOwner ||
|
||||||
|
error instanceof DeploymentsRateLimited ||
|
||||||
|
error instanceof AliasDomainConfigured ||
|
||||||
|
error instanceof MissingBuildScript ||
|
||||||
|
error instanceof ConflictingFilePath ||
|
||||||
|
error instanceof ConflictingPathSegment
|
||||||
|
) {
|
||||||
|
output.error(error.message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addProcessEnv = async (
|
||||||
|
log: (str: string) => void,
|
||||||
|
env: typeof process.env
|
||||||
|
) => {
|
||||||
|
let val;
|
||||||
|
|
||||||
|
for (const key of Object.keys(env)) {
|
||||||
|
if (typeof env[key] !== 'undefined') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
val = process.env[key];
|
||||||
|
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
log(
|
||||||
|
`Reading ${chalk.bold(
|
||||||
|
`"${chalk.bold(key)}"`
|
||||||
|
)} from your env (as no value was specified)`
|
||||||
|
);
|
||||||
|
// Escape value if it begins with @
|
||||||
|
env[key] = val.replace(/^@/, '\\@');
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`No value specified for env ${chalk.bold(
|
||||||
|
`"${chalk.bold(key)}"`
|
||||||
|
)} and it was not found in your env.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const printDeploymentStatus = async (
|
||||||
|
output: Output,
|
||||||
|
client: Client,
|
||||||
|
{
|
||||||
|
readyState,
|
||||||
|
alias: aliasList,
|
||||||
|
aliasError,
|
||||||
|
target,
|
||||||
|
indications,
|
||||||
|
url: deploymentUrl,
|
||||||
|
aliasWarning,
|
||||||
|
}: {
|
||||||
|
readyState: string;
|
||||||
|
alias: string[];
|
||||||
|
aliasError: Error;
|
||||||
|
target: string;
|
||||||
|
indications: any;
|
||||||
|
url: string;
|
||||||
|
aliasWarning?: {
|
||||||
|
code: string;
|
||||||
|
message: string;
|
||||||
|
link?: string;
|
||||||
|
action?: string;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
deployStamp: () => string,
|
||||||
|
isClipboardEnabled: boolean,
|
||||||
|
isFile: boolean
|
||||||
|
) => {
|
||||||
|
indications = indications || [];
|
||||||
|
const isProdDeployment = target === 'production';
|
||||||
|
|
||||||
|
if (readyState !== 'READY') {
|
||||||
|
output.error(
|
||||||
|
`Your deployment failed. Please retry later. More: https://err.sh/vercel/deployment-error`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aliasError) {
|
||||||
|
output.warn(
|
||||||
|
`Failed to assign aliases${
|
||||||
|
aliasError.message ? `: ${aliasError.message}` : ''
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// print preview/production url
|
||||||
|
let previewUrl: string;
|
||||||
|
let isWildcard: boolean;
|
||||||
|
if (!isFile && Array.isArray(aliasList) && aliasList.length > 0) {
|
||||||
|
const previewUrlInfo = await getPreferredPreviewURL(client, aliasList);
|
||||||
|
if (previewUrlInfo) {
|
||||||
|
isWildcard = previewUrlInfo.isWildcard;
|
||||||
|
previewUrl = previewUrlInfo.previewUrl;
|
||||||
|
} else {
|
||||||
|
isWildcard = false;
|
||||||
|
previewUrl = `https://${deploymentUrl}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// fallback to deployment url
|
||||||
|
isWildcard = false;
|
||||||
|
previewUrl = `https://${deploymentUrl}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy to clipboard
|
||||||
|
let isCopiedToClipboard = false;
|
||||||
|
if (isClipboardEnabled && !isWildcard) {
|
||||||
|
try {
|
||||||
|
await copy(previewUrl);
|
||||||
|
isCopiedToClipboard = true;
|
||||||
|
} catch (err) {
|
||||||
|
output.debug(`Error copyind to clipboard: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.print(
|
||||||
|
prependEmoji(
|
||||||
|
`${isProdDeployment ? 'Production' : 'Preview'}: ${chalk.bold(
|
||||||
|
previewUrl
|
||||||
|
)}${
|
||||||
|
isCopiedToClipboard ? chalk.gray(` [copied to clipboard]`) : ''
|
||||||
|
} ${deployStamp()}`,
|
||||||
|
emoji('success')
|
||||||
|
) + `\n`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aliasWarning?.message) {
|
||||||
|
indications.push({
|
||||||
|
type: 'warning',
|
||||||
|
payload: aliasWarning.message,
|
||||||
|
link: aliasWarning.link,
|
||||||
|
action: aliasWarning.action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newline = '\n';
|
||||||
|
for (let indication of indications) {
|
||||||
|
const message =
|
||||||
|
prependEmoji(chalk.dim(indication.payload), emoji(indication.type)) +
|
||||||
|
newline;
|
||||||
|
let link = '';
|
||||||
|
if (indication.link)
|
||||||
|
link =
|
||||||
|
chalk.dim(
|
||||||
|
`${indication.action || 'Learn More'}: ${linkStyle(indication.link)}`
|
||||||
|
) + newline;
|
||||||
|
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.substr(0, equalsSign);
|
||||||
|
value = e.substr(equalsSign + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
o[key] = value;
|
||||||
|
return o;
|
||||||
|
}, {} as Dictionary<string | undefined>);
|
||||||
|
}
|
||||||
|
|
||||||
|
// assume it's already an Object
|
||||||
|
return env;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,808 +0,0 @@
|
|||||||
import ms from 'ms';
|
|
||||||
import bytes from 'bytes';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { write as copy } from 'clipboardy';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { Dictionary, fileNameSymbol, VercelConfig } from '@vercel/client';
|
|
||||||
import { getPrettyError } from '@vercel/build-utils';
|
|
||||||
import { handleError } from '../../util/error';
|
|
||||||
import toHumanPath from '../../util/humanize-path';
|
|
||||||
import Now from '../../util';
|
|
||||||
import stamp from '../../util/output/stamp';
|
|
||||||
import createDeploy from '../../util/deploy/create-deploy';
|
|
||||||
import getDeploymentByIdOrHost from '../../util/deploy/get-deployment-by-id-or-host';
|
|
||||||
import parseMeta from '../../util/parse-meta';
|
|
||||||
import code from '../../util/output/code';
|
|
||||||
import linkStyle from '../../util/output/link';
|
|
||||||
import param from '../../util/output/param';
|
|
||||||
import highlight from '../../util/output/highlight';
|
|
||||||
import {
|
|
||||||
BuildsRateLimited,
|
|
||||||
DeploymentNotFound,
|
|
||||||
DeploymentPermissionDenied,
|
|
||||||
InvalidDeploymentId,
|
|
||||||
DomainNotFound,
|
|
||||||
DomainNotVerified,
|
|
||||||
DomainPermissionDenied,
|
|
||||||
DomainVerificationFailed,
|
|
||||||
InvalidDomain,
|
|
||||||
TooManyRequests,
|
|
||||||
UserAborted,
|
|
||||||
DeploymentsRateLimited,
|
|
||||||
AliasDomainConfigured,
|
|
||||||
MissingBuildScript,
|
|
||||||
ConflictingFilePath,
|
|
||||||
ConflictingPathSegment,
|
|
||||||
BuildError,
|
|
||||||
NotDomainOwner,
|
|
||||||
} from '../../util/errors-ts';
|
|
||||||
import { SchemaValidationFailed } from '../../util/errors';
|
|
||||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
|
||||||
import confirm from '../../util/input/confirm';
|
|
||||||
import editProjectSettings from '../../util/input/edit-project-settings';
|
|
||||||
import {
|
|
||||||
getLinkedProject,
|
|
||||||
linkFolderToProject,
|
|
||||||
} from '../../util/projects/link';
|
|
||||||
import getProjectName from '../../util/get-project-name';
|
|
||||||
import selectOrg from '../../util/input/select-org';
|
|
||||||
import inputProject from '../../util/input/input-project';
|
|
||||||
import { prependEmoji, emoji } from '../../util/emoji';
|
|
||||||
import { inputRootDirectory } from '../../util/input/input-root-directory';
|
|
||||||
import validatePaths, {
|
|
||||||
validateRootDirectory,
|
|
||||||
} from '../../util/validate-paths';
|
|
||||||
import { readLocalConfig } from '../../util/config/files';
|
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
|
||||||
import { getPreferredPreviewURL } from '../../util/deploy/get-preferred-preview-url';
|
|
||||||
import { Output } from '../../util/output';
|
|
||||||
import Client from '../../util/client';
|
|
||||||
|
|
||||||
const addProcessEnv = async (
|
|
||||||
log: (str: string) => void,
|
|
||||||
env: typeof process.env
|
|
||||||
) => {
|
|
||||||
let val;
|
|
||||||
|
|
||||||
for (const key of Object.keys(env)) {
|
|
||||||
if (typeof env[key] !== 'undefined') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
val = process.env[key];
|
|
||||||
|
|
||||||
if (typeof val === 'string') {
|
|
||||||
log(
|
|
||||||
`Reading ${chalk.bold(
|
|
||||||
`"${chalk.bold(key)}"`
|
|
||||||
)} from your env (as no value was specified)`
|
|
||||||
);
|
|
||||||
// Escape value if it begins with @
|
|
||||||
env[key] = val.replace(/^@/, '\\@');
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
`No value specified for env ${chalk.bold(
|
|
||||||
`"${chalk.bold(key)}"`
|
|
||||||
)} and it was not found in your env.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const printDeploymentStatus = async (
|
|
||||||
output: Output,
|
|
||||||
client: Client,
|
|
||||||
{
|
|
||||||
readyState,
|
|
||||||
alias: aliasList,
|
|
||||||
aliasError,
|
|
||||||
target,
|
|
||||||
indications,
|
|
||||||
url: deploymentUrl,
|
|
||||||
aliasWarning,
|
|
||||||
}: {
|
|
||||||
readyState: string;
|
|
||||||
alias: string[];
|
|
||||||
aliasError: Error;
|
|
||||||
target: string;
|
|
||||||
indications: any;
|
|
||||||
url: string;
|
|
||||||
aliasWarning?: {
|
|
||||||
code: string;
|
|
||||||
message: string;
|
|
||||||
link?: string;
|
|
||||||
action?: string;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
deployStamp: () => string,
|
|
||||||
isClipboardEnabled: boolean,
|
|
||||||
isFile: boolean
|
|
||||||
) => {
|
|
||||||
indications = indications || [];
|
|
||||||
const isProdDeployment = target === 'production';
|
|
||||||
|
|
||||||
if (readyState !== 'READY') {
|
|
||||||
output.error(
|
|
||||||
`Your deployment failed. Please retry later. More: https://err.sh/vercel/deployment-error`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aliasError) {
|
|
||||||
output.warn(
|
|
||||||
`Failed to assign aliases${
|
|
||||||
aliasError.message ? `: ${aliasError.message}` : ''
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// print preview/production url
|
|
||||||
let previewUrl: string;
|
|
||||||
let isWildcard: boolean;
|
|
||||||
if (!isFile && Array.isArray(aliasList) && aliasList.length > 0) {
|
|
||||||
const previewUrlInfo = await getPreferredPreviewURL(client, aliasList);
|
|
||||||
if (previewUrlInfo) {
|
|
||||||
isWildcard = previewUrlInfo.isWildcard;
|
|
||||||
previewUrl = previewUrlInfo.previewUrl;
|
|
||||||
} else {
|
|
||||||
isWildcard = false;
|
|
||||||
previewUrl = `https://${deploymentUrl}`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// fallback to deployment url
|
|
||||||
isWildcard = false;
|
|
||||||
previewUrl = `https://${deploymentUrl}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy to clipboard
|
|
||||||
let isCopiedToClipboard = false;
|
|
||||||
if (isClipboardEnabled && !isWildcard) {
|
|
||||||
try {
|
|
||||||
await copy(previewUrl);
|
|
||||||
isCopiedToClipboard = true;
|
|
||||||
} catch (err) {
|
|
||||||
output.debug(`Error copyind to clipboard: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output.print(
|
|
||||||
prependEmoji(
|
|
||||||
`${isProdDeployment ? 'Production' : 'Preview'}: ${chalk.bold(
|
|
||||||
previewUrl
|
|
||||||
)}${
|
|
||||||
isCopiedToClipboard ? chalk.gray(` [copied to clipboard]`) : ''
|
|
||||||
} ${deployStamp()}`,
|
|
||||||
emoji('success')
|
|
||||||
) + `\n`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aliasWarning?.message) {
|
|
||||||
indications.push({
|
|
||||||
type: 'warning',
|
|
||||||
payload: aliasWarning.message,
|
|
||||||
link: aliasWarning.link,
|
|
||||||
action: aliasWarning.action,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newline = '\n';
|
|
||||||
for (let indication of indications) {
|
|
||||||
const message =
|
|
||||||
prependEmoji(chalk.dim(indication.payload), emoji(indication.type)) +
|
|
||||||
newline;
|
|
||||||
let link = '';
|
|
||||||
if (indication.link)
|
|
||||||
link =
|
|
||||||
chalk.dim(
|
|
||||||
`${indication.action || 'Learn More'}: ${linkStyle(indication.link)}`
|
|
||||||
) + newline;
|
|
||||||
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.substr(0, equalsSign);
|
|
||||||
value = e.substr(equalsSign + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
o[key] = value;
|
|
||||||
return o;
|
|
||||||
}, {} as Dictionary<string | undefined>);
|
|
||||||
}
|
|
||||||
|
|
||||||
// assume it's already an Object
|
|
||||||
return env;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function main(
|
|
||||||
client: Client,
|
|
||||||
paths: string[],
|
|
||||||
localConfig: VercelConfig | null,
|
|
||||||
argv: any
|
|
||||||
) {
|
|
||||||
const {
|
|
||||||
apiUrl,
|
|
||||||
output,
|
|
||||||
authConfig: { token },
|
|
||||||
} = client;
|
|
||||||
const { log, debug, error, warn } = output;
|
|
||||||
const debugEnabled = argv['--debug'];
|
|
||||||
|
|
||||||
const { isTTY } = process.stdout;
|
|
||||||
const quiet = !isTTY;
|
|
||||||
|
|
||||||
// check paths
|
|
||||||
const pathValidation = await validatePaths(output, paths);
|
|
||||||
|
|
||||||
if (!pathValidation.valid) {
|
|
||||||
return pathValidation.exitCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { isFile, path } = pathValidation;
|
|
||||||
const autoConfirm = argv['--confirm'] || isFile;
|
|
||||||
|
|
||||||
// deprecate --name
|
|
||||||
if (argv['--name']) {
|
|
||||||
output.print(
|
|
||||||
`${prependEmoji(
|
|
||||||
`The ${param(
|
|
||||||
'--name'
|
|
||||||
)} option is deprecated (https://vercel.link/name-flag)`,
|
|
||||||
emoji('warning')
|
|
||||||
)}\n`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve `project` and `org` from .vercel
|
|
||||||
const link = await getLinkedProject(client, path);
|
|
||||||
|
|
||||||
if (link.status === 'error') {
|
|
||||||
return link.exitCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { org, project, status } = link;
|
|
||||||
|
|
||||||
let newProjectName = null;
|
|
||||||
let rootDirectory = project ? project.rootDirectory : null;
|
|
||||||
let sourceFilesOutsideRootDirectory = true;
|
|
||||||
|
|
||||||
if (status === 'not_linked') {
|
|
||||||
const shouldStartSetup =
|
|
||||||
autoConfirm ||
|
|
||||||
(await confirm(
|
|
||||||
`Set up and deploy ${chalk.cyan(`“${toHumanPath(path)}”`)}?`,
|
|
||||||
true
|
|
||||||
));
|
|
||||||
|
|
||||||
if (!shouldStartSetup) {
|
|
||||||
output.print(`Aborted. Project not set up.\n`);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
org = await selectOrg(
|
|
||||||
client,
|
|
||||||
'Which scope do you want to deploy to?',
|
|
||||||
autoConfirm
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
|
||||||
output.error(err.message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use `localConfig` here to read the name
|
|
||||||
// even though the `vercel.json` file can change
|
|
||||||
// afterwards, this is fine since the property
|
|
||||||
// will be deprecated and can be replaced with
|
|
||||||
// user input.
|
|
||||||
const detectedProjectName = getProjectName({
|
|
||||||
argv,
|
|
||||||
nowConfig: localConfig || {},
|
|
||||||
isFile,
|
|
||||||
paths,
|
|
||||||
});
|
|
||||||
|
|
||||||
const projectOrNewProjectName = await inputProject(
|
|
||||||
output,
|
|
||||||
client,
|
|
||||||
org,
|
|
||||||
detectedProjectName,
|
|
||||||
autoConfirm
|
|
||||||
);
|
|
||||||
|
|
||||||
if (typeof projectOrNewProjectName === 'string') {
|
|
||||||
newProjectName = projectOrNewProjectName;
|
|
||||||
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
|
|
||||||
} else {
|
|
||||||
project = projectOrNewProjectName;
|
|
||||||
rootDirectory = project.rootDirectory;
|
|
||||||
sourceFilesOutsideRootDirectory = project.sourceFilesOutsideRootDirectory;
|
|
||||||
|
|
||||||
// we can already link the project
|
|
||||||
await linkFolderToProject(
|
|
||||||
output,
|
|
||||||
path,
|
|
||||||
{
|
|
||||||
projectId: project.id,
|
|
||||||
orgId: org.id,
|
|
||||||
},
|
|
||||||
project.name,
|
|
||||||
org.slug
|
|
||||||
);
|
|
||||||
status = 'linked';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point `org` should be populated
|
|
||||||
if (!org) {
|
|
||||||
throw new Error(`"org" is not defined`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the `contextName` and `currentTeam` as specified by the
|
|
||||||
// Project Settings, so that API calls happen with the proper scope
|
|
||||||
const contextName = org.slug;
|
|
||||||
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
|
|
||||||
|
|
||||||
// if we have `sourceFilesOutsideRootDirectory` set to `true`, we use the current path
|
|
||||||
// and upload the entire directory.
|
|
||||||
const sourcePath =
|
|
||||||
rootDirectory && !sourceFilesOutsideRootDirectory
|
|
||||||
? join(path, rootDirectory)
|
|
||||||
: path;
|
|
||||||
|
|
||||||
if (
|
|
||||||
rootDirectory &&
|
|
||||||
(await validateRootDirectory(
|
|
||||||
output,
|
|
||||||
path,
|
|
||||||
sourcePath,
|
|
||||||
project
|
|
||||||
? `To change your Project Settings, go to https://vercel.com/${org?.slug}/${project.name}/settings`
|
|
||||||
: ''
|
|
||||||
)) === false
|
|
||||||
) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If Root Directory is used we'll try to read the config
|
|
||||||
// from there instead and use it if it exists.
|
|
||||||
if (rootDirectory) {
|
|
||||||
const rootDirectoryConfig = readLocalConfig(join(path, rootDirectory));
|
|
||||||
|
|
||||||
if (rootDirectoryConfig) {
|
|
||||||
debug(`Read local config from root directory (${rootDirectory})`);
|
|
||||||
localConfig = rootDirectoryConfig;
|
|
||||||
} else if (localConfig) {
|
|
||||||
output.print(
|
|
||||||
`${prependEmoji(
|
|
||||||
`The ${highlight(
|
|
||||||
localConfig[fileNameSymbol]!
|
|
||||||
)} file should be inside of the provided root directory.`,
|
|
||||||
emoji('warning')
|
|
||||||
)}\n`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
localConfig = localConfig || {};
|
|
||||||
|
|
||||||
if (localConfig.name) {
|
|
||||||
output.print(
|
|
||||||
`${prependEmoji(
|
|
||||||
`The ${code('name')} property in ${highlight(
|
|
||||||
localConfig[fileNameSymbol]!
|
|
||||||
)} is deprecated (https://vercel.link/name-prop)`,
|
|
||||||
emoji('warning')
|
|
||||||
)}\n`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// build `env`
|
|
||||||
const isObject = (item: any) =>
|
|
||||||
Object.prototype.toString.call(item) === '[object Object]';
|
|
||||||
|
|
||||||
// This validation needs to happen on the client side because
|
|
||||||
// the data is merged with other data before it is passed to the API (which
|
|
||||||
// also does schema validation).
|
|
||||||
if (typeof localConfig.env !== 'undefined' && !isObject(localConfig.env)) {
|
|
||||||
error(
|
|
||||||
`The ${code('env')} property in ${highlight(
|
|
||||||
localConfig[fileNameSymbol]!
|
|
||||||
)} needs to be an object`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof localConfig.build !== 'undefined') {
|
|
||||||
if (!isObject(localConfig.build)) {
|
|
||||||
error(
|
|
||||||
`The ${code('build')} property in ${highlight(
|
|
||||||
localConfig[fileNameSymbol]!
|
|
||||||
)} needs to be an object`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof localConfig.build.env !== 'undefined' &&
|
|
||||||
!isObject(localConfig.build.env)
|
|
||||||
) {
|
|
||||||
error(
|
|
||||||
`The ${code('build.env')} property in ${highlight(
|
|
||||||
localConfig[fileNameSymbol]!
|
|
||||||
)} needs to be an object`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// build `meta`
|
|
||||||
const meta = Object.assign(
|
|
||||||
{},
|
|
||||||
parseMeta(localConfig.meta),
|
|
||||||
parseMeta(argv['--meta'])
|
|
||||||
);
|
|
||||||
|
|
||||||
// Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments
|
|
||||||
const deploymentEnv = Object.assign(
|
|
||||||
{},
|
|
||||||
parseEnv(localConfig.env),
|
|
||||||
parseEnv(argv['--env'])
|
|
||||||
);
|
|
||||||
|
|
||||||
// Merge build env out of `build.env` from vercel.json, and `--build-env` args
|
|
||||||
const deploymentBuildEnv = Object.assign(
|
|
||||||
{},
|
|
||||||
parseEnv(localConfig.build && localConfig.build.env),
|
|
||||||
parseEnv(argv['--build-env'])
|
|
||||||
);
|
|
||||||
|
|
||||||
// If there's any undefined values, then inherit them from this process
|
|
||||||
try {
|
|
||||||
await addProcessEnv(log, deploymentEnv);
|
|
||||||
await addProcessEnv(log, deploymentBuildEnv);
|
|
||||||
} catch (err) {
|
|
||||||
error(err.message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// build `regions`
|
|
||||||
const regionFlag = (argv['--regions'] || '')
|
|
||||||
.split(',')
|
|
||||||
.map((s: string) => s.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
const regions = regionFlag.length > 0 ? regionFlag : localConfig.regions;
|
|
||||||
|
|
||||||
// build `target`
|
|
||||||
let target;
|
|
||||||
if (argv['--target']) {
|
|
||||||
const deprecatedTarget = argv['--target'];
|
|
||||||
|
|
||||||
if (!['staging', 'production'].includes(deprecatedTarget)) {
|
|
||||||
error(
|
|
||||||
`The specified ${param('--target')} ${code(
|
|
||||||
deprecatedTarget
|
|
||||||
)} is not valid`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deprecatedTarget === 'production') {
|
|
||||||
warn(
|
|
||||||
'We recommend using the much shorter `--prod` option instead of `--target production` (deprecated)'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.debug(`Setting target to ${deprecatedTarget}`);
|
|
||||||
target = deprecatedTarget;
|
|
||||||
} else if (argv['--prod']) {
|
|
||||||
output.debug('Setting target to production');
|
|
||||||
target = 'production';
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentTeam = org?.type === 'team' ? org.id : undefined;
|
|
||||||
const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam });
|
|
||||||
let deployStamp = stamp();
|
|
||||||
let deployment = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const createArgs: any = {
|
|
||||||
name: project ? project.name : newProjectName,
|
|
||||||
env: deploymentEnv,
|
|
||||||
build: { env: deploymentBuildEnv },
|
|
||||||
forceNew: argv['--force'],
|
|
||||||
withCache: argv['--with-cache'],
|
|
||||||
quiet,
|
|
||||||
wantsPublic: argv['--public'] || localConfig.public,
|
|
||||||
isFile,
|
|
||||||
type: null,
|
|
||||||
nowConfig: localConfig,
|
|
||||||
regions,
|
|
||||||
meta,
|
|
||||||
deployStamp,
|
|
||||||
target,
|
|
||||||
skipAutoDetectionConfirmation: autoConfirm,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!localConfig.builds || localConfig.builds.length === 0) {
|
|
||||||
// Only add projectSettings for zero config deployments
|
|
||||||
createArgs.projectSettings = { sourceFilesOutsideRootDirectory };
|
|
||||||
}
|
|
||||||
|
|
||||||
deployment = await createDeploy(
|
|
||||||
client,
|
|
||||||
now,
|
|
||||||
contextName,
|
|
||||||
[sourcePath],
|
|
||||||
createArgs,
|
|
||||||
org,
|
|
||||||
!project && !isFile,
|
|
||||||
path
|
|
||||||
);
|
|
||||||
|
|
||||||
if (deployment.code === 'missing_project_settings') {
|
|
||||||
let { projectSettings, framework } = deployment;
|
|
||||||
if (rootDirectory) {
|
|
||||||
projectSettings.rootDirectory = rootDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof sourceFilesOutsideRootDirectory !== 'undefined') {
|
|
||||||
projectSettings.sourceFilesOutsideRootDirectory =
|
|
||||||
sourceFilesOutsideRootDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings = await editProjectSettings(
|
|
||||||
output,
|
|
||||||
projectSettings,
|
|
||||||
framework
|
|
||||||
);
|
|
||||||
|
|
||||||
// deploy again, but send projectSettings this time
|
|
||||||
createArgs.projectSettings = settings;
|
|
||||||
|
|
||||||
deployStamp = stamp();
|
|
||||||
createArgs.deployStamp = deployStamp;
|
|
||||||
deployment = await createDeploy(
|
|
||||||
client,
|
|
||||||
now,
|
|
||||||
contextName,
|
|
||||||
[sourcePath],
|
|
||||||
createArgs,
|
|
||||||
org,
|
|
||||||
false,
|
|
||||||
path
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deployment instanceof NotDomainOwner) {
|
|
||||||
output.error(deployment.message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deployment instanceof Error) {
|
|
||||||
output.error(
|
|
||||||
deployment.message ||
|
|
||||||
'An unexpected error occurred while deploying your project',
|
|
||||||
undefined,
|
|
||||||
'https://vercel.link/help',
|
|
||||||
'Contact Support'
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deployment.readyState === 'CANCELED') {
|
|
||||||
output.print('The deployment has been canceled.\n');
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const deploymentResponse = await getDeploymentByIdOrHost(
|
|
||||||
client,
|
|
||||||
contextName,
|
|
||||||
deployment.id,
|
|
||||||
'v10'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
deploymentResponse instanceof DeploymentNotFound ||
|
|
||||||
deploymentResponse instanceof DeploymentPermissionDenied ||
|
|
||||||
deploymentResponse instanceof InvalidDeploymentId
|
|
||||||
) {
|
|
||||||
output.error(deploymentResponse.message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deployment === null) {
|
|
||||||
error('Uploading failed. Please try again.');
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
debug(`Error: ${err}\n${err.stack}`);
|
|
||||||
|
|
||||||
if (err instanceof NotDomainOwner) {
|
|
||||||
output.error(err.message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err instanceof DomainNotFound && err.meta && err.meta.domain) {
|
|
||||||
output.debug(
|
|
||||||
`The domain ${err.meta.domain} was not found, trying to purchase it`
|
|
||||||
);
|
|
||||||
|
|
||||||
const purchase = await purchaseDomainIfAvailable(
|
|
||||||
output,
|
|
||||||
client,
|
|
||||||
err.meta.domain,
|
|
||||||
contextName
|
|
||||||
);
|
|
||||||
|
|
||||||
if (purchase === true) {
|
|
||||||
output.success(`Successfully purchased the domain ${err.meta.domain}!`);
|
|
||||||
|
|
||||||
// We exit if the purchase is completed since
|
|
||||||
// the domain verification can take some time
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (purchase === false || purchase instanceof UserAborted) {
|
|
||||||
handleCreateDeployError(output, deployment, localConfig);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCreateDeployError(output, purchase, localConfig);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
err instanceof DomainNotFound ||
|
|
||||||
err instanceof DomainNotVerified ||
|
|
||||||
err instanceof NotDomainOwner ||
|
|
||||||
err instanceof DomainPermissionDenied ||
|
|
||||||
err instanceof DomainVerificationFailed ||
|
|
||||||
err instanceof SchemaValidationFailed ||
|
|
||||||
err instanceof InvalidDomain ||
|
|
||||||
err instanceof DeploymentNotFound ||
|
|
||||||
err instanceof BuildsRateLimited ||
|
|
||||||
err instanceof DeploymentsRateLimited ||
|
|
||||||
err instanceof AliasDomainConfigured ||
|
|
||||||
err instanceof MissingBuildScript ||
|
|
||||||
err instanceof ConflictingFilePath ||
|
|
||||||
err instanceof ConflictingPathSegment
|
|
||||||
) {
|
|
||||||
handleCreateDeployError(output, err, localConfig);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err instanceof BuildError) {
|
|
||||||
output.error(err.message || 'Build failed');
|
|
||||||
output.error(
|
|
||||||
`Check your logs at https://${now.url}/_logs or run ${getCommandName(
|
|
||||||
`logs ${now.url}`
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.keyword === 'additionalProperties' && err.dataPath === '.scale') {
|
|
||||||
const { additionalProperty = '' } = err.params || {};
|
|
||||||
const message = `Invalid DC name for the scale option: ${additionalProperty}`;
|
|
||||||
error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.code === 'size_limit_exceeded') {
|
|
||||||
const { sizeLimit = 0 } = err;
|
|
||||||
const message = `File size limit exceeded (${bytes(sizeLimit)})`;
|
|
||||||
error(message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleError(err);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return printDeploymentStatus(
|
|
||||||
output,
|
|
||||||
client,
|
|
||||||
deployment,
|
|
||||||
deployStamp,
|
|
||||||
!argv['--no-clipboard'],
|
|
||||||
isFile
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCreateDeployError(
|
|
||||||
output: Output,
|
|
||||||
error: Error,
|
|
||||||
localConfig: VercelConfig
|
|
||||||
) {
|
|
||||||
if (error instanceof InvalidDomain) {
|
|
||||||
output.error(`The domain ${error.meta.domain} is not valid`);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (error instanceof DomainVerificationFailed) {
|
|
||||||
output.error(
|
|
||||||
`The domain used as a suffix ${chalk.underline(
|
|
||||||
error.meta.domain
|
|
||||||
)} is not verified and can't be used as custom suffix.`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (error instanceof DomainPermissionDenied) {
|
|
||||||
output.error(
|
|
||||||
`You don't have permissions to access the domain used as a suffix ${chalk.underline(
|
|
||||||
error.meta.domain
|
|
||||||
)}.`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (error instanceof SchemaValidationFailed) {
|
|
||||||
const niceError = getPrettyError(error.meta);
|
|
||||||
const fileName = localConfig[fileNameSymbol] || 'vercel.json';
|
|
||||||
niceError.message = `Invalid ${fileName} - ${niceError.message}`;
|
|
||||||
output.prettyError(niceError);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (error instanceof TooManyRequests) {
|
|
||||||
output.error(
|
|
||||||
`Too many requests detected for ${error.meta.api} API. Try again in ${ms(
|
|
||||||
error.meta.retryAfter * 1000,
|
|
||||||
{
|
|
||||||
long: true,
|
|
||||||
}
|
|
||||||
)}.`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (error instanceof DomainNotVerified) {
|
|
||||||
output.error(
|
|
||||||
`The domain used as an alias ${chalk.underline(
|
|
||||||
error.meta.domain
|
|
||||||
)} is not verified yet. Please verify it.`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (error instanceof BuildsRateLimited) {
|
|
||||||
output.error(error.message);
|
|
||||||
output.note(
|
|
||||||
`Run ${getCommandName('upgrade')} to increase your builds limit.`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
error instanceof DeploymentNotFound ||
|
|
||||||
error instanceof NotDomainOwner ||
|
|
||||||
error instanceof DeploymentsRateLimited ||
|
|
||||||
error instanceof AliasDomainConfigured ||
|
|
||||||
error instanceof MissingBuildScript ||
|
|
||||||
error instanceof ConflictingFilePath ||
|
|
||||||
error instanceof ConflictingPathSegment
|
|
||||||
) {
|
|
||||||
output.error(error.message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
@@ -6,15 +6,13 @@ import { ProjectEnvVariable } from '../../types';
|
|||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import { getLinkedProject } from '../../util/projects/link';
|
import { getLinkedProject } from '../../util/projects/link';
|
||||||
import { getFrameworks } from '../../util/get-frameworks';
|
import { getFrameworks } from '../../util/get-frameworks';
|
||||||
import { isSettingValue } from '../../util/is-setting-value';
|
|
||||||
import { ProjectSettings } from '../../types';
|
import { ProjectSettings } from '../../types';
|
||||||
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
||||||
import setupAndLink from '../../util/link/setup-and-link';
|
import setupAndLink from '../../util/link/setup-and-link';
|
||||||
import getSystemEnvValues from '../../util/env/get-system-env-values';
|
import getSystemEnvValues from '../../util/env/get-system-env-values';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
'--debug'?: boolean;
|
'--listen': string;
|
||||||
'--listen'?: string;
|
|
||||||
'--confirm': boolean;
|
'--confirm': boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,7 +25,6 @@ export default async function dev(
|
|||||||
const [dir = '.'] = args;
|
const [dir = '.'] = args;
|
||||||
let cwd = resolve(dir);
|
let cwd = resolve(dir);
|
||||||
const listen = parseListen(opts['--listen'] || '3000');
|
const listen = parseListen(opts['--listen'] || '3000');
|
||||||
const debug = opts['--debug'] || false;
|
|
||||||
|
|
||||||
// retrieve dev command
|
// retrieve dev command
|
||||||
let [link, frameworks] = await Promise.all([
|
let [link, frameworks] = await Promise.all([
|
||||||
@@ -36,17 +33,11 @@ export default async function dev(
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
|
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
|
||||||
const autoConfirm = opts['--confirm'] || false;
|
link = await setupAndLink(client, cwd, {
|
||||||
const forceDelete = false;
|
autoConfirm: opts['--confirm'],
|
||||||
|
successEmoji: 'link',
|
||||||
link = await setupAndLink(
|
setupMsg: 'Set up and develop',
|
||||||
client,
|
});
|
||||||
cwd,
|
|
||||||
forceDelete,
|
|
||||||
autoConfirm,
|
|
||||||
'link',
|
|
||||||
'Set up and develop'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (link.status === 'not_linked') {
|
if (link.status === 'not_linked') {
|
||||||
// User aborted project linking questions
|
// User aborted project linking questions
|
||||||
@@ -79,9 +70,9 @@ export default async function dev(
|
|||||||
frameworkSlug = framework.slug;
|
frameworkSlug = framework.slug;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaults = framework.settings.devCommand;
|
const defaults = framework.settings.devCommand.value;
|
||||||
if (isSettingValue(defaults)) {
|
if (defaults) {
|
||||||
devCommand = defaults.value;
|
devCommand = defaults;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +91,6 @@ export default async function dev(
|
|||||||
|
|
||||||
const devServer = new DevServer(cwd, {
|
const devServer = new DevServer(cwd, {
|
||||||
output,
|
output,
|
||||||
debug,
|
|
||||||
devCommand,
|
devCommand,
|
||||||
frameworkSlug,
|
frameworkSlug,
|
||||||
projectSettings,
|
projectSettings,
|
||||||
|
|||||||
@@ -48,6 +48,22 @@ const help = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default async function main(client: Client) {
|
export default async function main(client: Client) {
|
||||||
|
if (process.env.__VERCEL_DEV_RUNNING) {
|
||||||
|
client.output.error(
|
||||||
|
`${cmd(
|
||||||
|
`${getPkgName()} dev`
|
||||||
|
)} must not recursively invoke itself. Check the Development Command in the Project Settings or the ${cmd(
|
||||||
|
'dev'
|
||||||
|
)} script in ${cmd('package.json')}`
|
||||||
|
);
|
||||||
|
client.output.error(
|
||||||
|
`Learn More: https://vercel.link/recursive-invocation-of-commands`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
process.env.__VERCEL_DEV_RUNNING = '1';
|
||||||
|
}
|
||||||
|
|
||||||
let argv;
|
let argv;
|
||||||
let args;
|
let args;
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
@@ -90,22 +106,21 @@ export default async function main(client: Client) {
|
|||||||
if (pkg) {
|
if (pkg) {
|
||||||
const { scripts } = pkg as PackageJson;
|
const { scripts } = pkg as PackageJson;
|
||||||
|
|
||||||
if (scripts && scripts.dev && /\bnow\b\W+\bdev\b/.test(scripts.dev)) {
|
if (
|
||||||
output.error(
|
scripts &&
|
||||||
`The ${cmd('dev')} script in ${cmd(
|
scripts.dev &&
|
||||||
'package.json'
|
/\b(now|vercel)\b\W+\bdev\b/.test(scripts.dev)
|
||||||
)} must not contain ${cmd('now dev')}`
|
) {
|
||||||
|
client.output.error(
|
||||||
|
`${cmd(
|
||||||
|
`${getPkgName()} dev`
|
||||||
|
)} must not recursively invoke itself. Check the Development Command in the Project Settings or the ${cmd(
|
||||||
|
'dev'
|
||||||
|
)} script in ${cmd('package.json')}`
|
||||||
);
|
);
|
||||||
output.error(`Learn More: http://err.sh/vercel/now-dev-as-dev-script`);
|
client.output.error(
|
||||||
return 1;
|
`Learn More: https://vercel.link/recursive-invocation-of-commands`
|
||||||
}
|
|
||||||
if (scripts && scripts.dev && /\bvercel\b\W+\bdev\b/.test(scripts.dev)) {
|
|
||||||
output.error(
|
|
||||||
`The ${cmd('dev')} script in ${cmd(
|
|
||||||
'package.json'
|
|
||||||
)} must not contain ${cmd('vercel dev')}`
|
|
||||||
);
|
);
|
||||||
output.error(`Learn More: http://err.sh/vercel/now-dev-as-dev-script`);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,7 +136,7 @@ export default async function main(client: Client) {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ENOTFOUND') {
|
if (err.code === 'ENOTFOUND') {
|
||||||
// Error message will look like the following:
|
// Error message will look like the following:
|
||||||
// "request to https://api.vercel.com/www/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 || '');
|
||||||
if (matches && matches[1]) {
|
if (matches && matches[1]) {
|
||||||
const hostname = matches[1];
|
const hostname = matches[1];
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { User, Team } from '../../types';
|
|||||||
import * as ERRORS from '../../util/errors-ts';
|
import * as ERRORS from '../../util/errors-ts';
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import getScope from '../../util/get-scope';
|
import getScope from '../../util/get-scope';
|
||||||
import withSpinner from '../../util/with-spinner';
|
|
||||||
import moveOutDomain from '../../util/domains/move-out-domain';
|
import moveOutDomain from '../../util/domains/move-out-domain';
|
||||||
import isRootDomain from '../../util/is-root-domain';
|
import isRootDomain from '../../util/is-root-domain';
|
||||||
import textInput from '../../util/input/text';
|
import textInput from '../../util/input/text';
|
||||||
@@ -13,7 +12,7 @@ import param from '../../util/output/param';
|
|||||||
import getDomainAliases from '../../util/alias/get-domain-aliases';
|
import getDomainAliases from '../../util/alias/get-domain-aliases';
|
||||||
import getDomainByName from '../../util/domains/get-domain-by-name';
|
import getDomainByName from '../../util/domains/get-domain-by-name';
|
||||||
import promptBool from '../../util/input/prompt-bool';
|
import promptBool from '../../util/input/prompt-bool';
|
||||||
import getTeams from '../../util/get-teams';
|
import getTeams from '../../util/teams/get-teams';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
@@ -106,9 +105,14 @@ export default async function move(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const context = contextName;
|
const context = contextName;
|
||||||
const moveTokenResult = await withSpinner('Moving', () => {
|
output.spinner('Moving');
|
||||||
return moveOutDomain(client, context, domainName, matchId || destination);
|
const moveTokenResult = await moveOutDomain(
|
||||||
});
|
client,
|
||||||
|
context,
|
||||||
|
domainName,
|
||||||
|
matchId || destination
|
||||||
|
);
|
||||||
|
|
||||||
if (moveTokenResult instanceof ERRORS.DomainMoveConflict) {
|
if (moveTokenResult instanceof ERRORS.DomainMoveConflict) {
|
||||||
const { suffix, pendingAsyncPurchase } = moveTokenResult.meta;
|
const { suffix, pendingAsyncPurchase } = moveTokenResult.meta;
|
||||||
if (suffix) {
|
if (suffix) {
|
||||||
@@ -190,8 +194,8 @@ async function findDestinationMatch(
|
|||||||
user: User,
|
user: User,
|
||||||
teams: Team[]
|
teams: Team[]
|
||||||
) {
|
) {
|
||||||
if (user.uid === destination || user.username === destination) {
|
if (user.id === destination || user.username === destination) {
|
||||||
return user.uid;
|
return user.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const team of teams) {
|
for (const team of teams) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import param from '../../util/output/param';
|
|||||||
import transferInDomain from '../../util/domains/transfer-in-domain';
|
import transferInDomain from '../../util/domains/transfer-in-domain';
|
||||||
import stamp from '../../util/output/stamp';
|
import stamp from '../../util/output/stamp';
|
||||||
import getAuthCode from '../../util/domains/get-auth-code';
|
import getAuthCode from '../../util/domains/get-auth-code';
|
||||||
import withSpinner from '../../util/with-spinner';
|
|
||||||
import getDomainPrice from '../../util/domains/get-domain-price';
|
import getDomainPrice from '../../util/domains/get-domain-price';
|
||||||
import checkTransfer from '../../util/domains/check-transfer';
|
import checkTransfer from '../../util/domains/check-transfer';
|
||||||
import promptBool from '../../util/input/prompt-bool';
|
import promptBool from '../../util/input/prompt-bool';
|
||||||
@@ -89,9 +88,13 @@ export default async function transferIn(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const transferStamp = stamp();
|
const transferStamp = stamp();
|
||||||
const transferInResult = await withSpinner(
|
output.spinner(`Initiating transfer for domain ${domainName}`);
|
||||||
`Initiating transfer for domain ${domainName}`,
|
|
||||||
() => transferInDomain(client, domainName, authCode, price)
|
const transferInResult = await transferInDomain(
|
||||||
|
client,
|
||||||
|
domainName,
|
||||||
|
authCode,
|
||||||
|
price
|
||||||
);
|
);
|
||||||
|
|
||||||
if (transferInResult instanceof ERRORS.InvalidDomain) {
|
if (transferInResult instanceof ERRORS.InvalidDomain) {
|
||||||
|
|||||||
22
packages/cli/src/commands/env/add.ts
vendored
22
packages/cli/src/commands/env/add.ts
vendored
@@ -13,7 +13,6 @@ import {
|
|||||||
} from '../../util/env/env-target';
|
} from '../../util/env/env-target';
|
||||||
import readStandardInput from '../../util/input/read-standard-input';
|
import readStandardInput from '../../util/input/read-standard-input';
|
||||||
import param from '../../util/output/param';
|
import param from '../../util/output/param';
|
||||||
import withSpinner from '../../util/with-spinner';
|
|
||||||
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';
|
||||||
@@ -142,17 +141,16 @@ export default async function add(
|
|||||||
|
|
||||||
const addStamp = stamp();
|
const addStamp = stamp();
|
||||||
try {
|
try {
|
||||||
await withSpinner('Saving', () =>
|
output.spinner('Saving');
|
||||||
addEnvRecord(
|
await addEnvRecord(
|
||||||
output,
|
output,
|
||||||
client,
|
client,
|
||||||
project.id,
|
project.id,
|
||||||
ProjectEnvType.Encrypted,
|
ProjectEnvType.Encrypted,
|
||||||
envName,
|
envName,
|
||||||
envValue,
|
envValue,
|
||||||
envTargets,
|
envTargets,
|
||||||
envGitBranch
|
envGitBranch
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isKnownError(error) && error.serverMessage) {
|
if (isKnownError(error) && error.serverMessage) {
|
||||||
|
|||||||
21
packages/cli/src/commands/env/index.ts
vendored
21
packages/cli/src/commands/env/index.ts
vendored
@@ -1,18 +1,16 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
import getArgs from '../../util/get-args';
|
|
||||||
import getSubcommand from '../../util/get-subcommand';
|
|
||||||
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
|
|
||||||
import { getEnvTargetPlaceholder } from '../../util/env/env-target';
|
|
||||||
import { getLinkedProject } from '../../util/projects/link';
|
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
|
import { getEnvTargetPlaceholder } from '../../util/env/env-target';
|
||||||
|
import getArgs from '../../util/get-args';
|
||||||
|
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
|
||||||
|
import getSubcommand from '../../util/get-subcommand';
|
||||||
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 { getCommandName, getPkgName } from '../../util/pkg-name';
|
import { getCommandName, getPkgName } from '../../util/pkg-name';
|
||||||
|
import { getLinkedProject } from '../../util/projects/link';
|
||||||
import add from './add';
|
import add from './add';
|
||||||
import pull from './pull';
|
|
||||||
import ls from './ls';
|
import ls from './ls';
|
||||||
|
import pull from './pull';
|
||||||
import rm from './rm';
|
import rm from './rm';
|
||||||
|
|
||||||
const help = () => {
|
const help = () => {
|
||||||
@@ -139,6 +137,13 @@ export default async function main(client: Client) {
|
|||||||
case 'rm':
|
case 'rm':
|
||||||
return rm(client, project, argv, args, output);
|
return rm(client, project, argv, args, output);
|
||||||
case 'pull':
|
case 'pull':
|
||||||
|
output.warn(
|
||||||
|
`${getCommandName(
|
||||||
|
'env pull'
|
||||||
|
)} is deprecated and will be removed in future releases. Run ${getCommandName(
|
||||||
|
'pull'
|
||||||
|
)} instead.`
|
||||||
|
);
|
||||||
return pull(client, project, argv, args, output);
|
return pull(client, project, argv, args, output);
|
||||||
default:
|
default:
|
||||||
output.error(getInvalidSubcommand(COMMAND_CONFIG));
|
output.error(getInvalidSubcommand(COMMAND_CONFIG));
|
||||||
|
|||||||
44
packages/cli/src/commands/env/pull.ts
vendored
44
packages/cli/src/commands/env/pull.ts
vendored
@@ -1,19 +1,18 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
import { closeSync, openSync, promises, readSync } from 'fs';
|
||||||
|
import { resolve } from 'path';
|
||||||
import { Project } from '../../types';
|
import { Project } from '../../types';
|
||||||
import { Output } from '../../util/output';
|
|
||||||
import confirm from '../../util/input/confirm';
|
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import stamp from '../../util/output/stamp';
|
import exposeSystemEnvs from '../../util/dev/expose-system-envs';
|
||||||
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
|
||||||
import param from '../../util/output/param';
|
|
||||||
import withSpinner from '../../util/with-spinner';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { promises, openSync, closeSync, readSync } from 'fs';
|
|
||||||
import { emoji, prependEmoji } from '../../util/emoji';
|
import { emoji, prependEmoji } from '../../util/emoji';
|
||||||
|
import getSystemEnvValues from '../../util/env/get-system-env-values';
|
||||||
|
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
|
||||||
|
import confirm from '../../util/input/confirm';
|
||||||
|
import { Output } from '../../util/output';
|
||||||
|
import param from '../../util/output/param';
|
||||||
|
import stamp from '../../util/output/stamp';
|
||||||
import { getCommandName } from '../../util/pkg-name';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
const { writeFile } = promises;
|
const { writeFile } = promises;
|
||||||
import exposeSystemEnvs from '../../util/dev/expose-system-envs';
|
|
||||||
import getSystemEnvValues from '../../util/env/get-system-env-values';
|
|
||||||
|
|
||||||
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
|
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
|
||||||
|
|
||||||
@@ -57,8 +56,9 @@ export default async function pull(
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle relative or absolute filename
|
||||||
const [filename = '.env'] = args;
|
const [filename = '.env'] = args;
|
||||||
const fullPath = join(process.cwd(), filename);
|
const fullPath = resolve(filename);
|
||||||
const skipConfirmation = opts['--yes'];
|
const skipConfirmation = opts['--yes'];
|
||||||
|
|
||||||
const head = tryReadHeadSync(fullPath, Buffer.byteLength(CONTENTS_PREFIX));
|
const head = tryReadHeadSync(fullPath, Buffer.byteLength(CONTENTS_PREFIX));
|
||||||
@@ -83,19 +83,16 @@ export default async function pull(
|
|||||||
project.name
|
project.name
|
||||||
)}\n`
|
)}\n`
|
||||||
);
|
);
|
||||||
const pullStamp = stamp();
|
|
||||||
|
|
||||||
const [
|
const pullStamp = stamp();
|
||||||
{ envs: projectEnvs },
|
output.spinner('Downloading');
|
||||||
{ systemEnvValues },
|
|
||||||
] = await withSpinner('Downloading', () =>
|
const [{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([
|
||||||
Promise.all([
|
getDecryptedEnvRecords(output, client, project.id),
|
||||||
getDecryptedEnvRecords(output, client, project.id),
|
project.autoExposeSystemEnvs
|
||||||
project.autoExposeSystemEnvs
|
? getSystemEnvValues(output, client, project.id)
|
||||||
? getSystemEnvValues(output, client, project.id)
|
: { systemEnvValues: [] },
|
||||||
: { systemEnvValues: [] },
|
]);
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
const records = exposeSystemEnvs(
|
const records = exposeSystemEnvs(
|
||||||
projectEnvs,
|
projectEnvs,
|
||||||
@@ -120,6 +117,7 @@ export default async function pull(
|
|||||||
emoji('success')
|
emoji('success')
|
||||||
)}\n`
|
)}\n`
|
||||||
);
|
);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
packages/cli/src/commands/env/rm.ts
vendored
6
packages/cli/src/commands/env/rm.ts
vendored
@@ -13,7 +13,6 @@ import {
|
|||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import stamp from '../../util/output/stamp';
|
import stamp from '../../util/output/stamp';
|
||||||
import param from '../../util/output/param';
|
import param from '../../util/output/param';
|
||||||
import withSpinner from '../../util/with-spinner';
|
|
||||||
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';
|
||||||
@@ -112,9 +111,8 @@ export default async function rm(
|
|||||||
const rmStamp = stamp();
|
const rmStamp = stamp();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await withSpinner('Removing', async () => {
|
output.spinner('Removing');
|
||||||
await removeEnvRecord(output, client, project.id, env);
|
await removeEnvRecord(output, client, project.id, env);
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isKnownError(error) && error.serverMessage) {
|
if (isKnownError(error) && error.serverMessage) {
|
||||||
output.error(error.serverMessage);
|
output.error(error.serverMessage);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export default new Map([
|
|||||||
['alias', 'alias'],
|
['alias', 'alias'],
|
||||||
['aliases', 'alias'],
|
['aliases', 'alias'],
|
||||||
['billing', 'billing'],
|
['billing', 'billing'],
|
||||||
|
['build', 'build'],
|
||||||
['cc', 'billing'],
|
['cc', 'billing'],
|
||||||
['cert', 'certs'],
|
['cert', 'certs'],
|
||||||
['certs', 'certs'],
|
['certs', 'certs'],
|
||||||
@@ -10,7 +11,6 @@ export default new Map([
|
|||||||
['dns', 'dns'],
|
['dns', 'dns'],
|
||||||
['domain', 'domains'],
|
['domain', 'domains'],
|
||||||
['domains', 'domains'],
|
['domains', 'domains'],
|
||||||
['downgrade', 'upgrade'],
|
|
||||||
['env', 'env'],
|
['env', 'env'],
|
||||||
['help', 'help'],
|
['help', 'help'],
|
||||||
['init', 'init'],
|
['init', 'init'],
|
||||||
@@ -25,6 +25,7 @@ export default new Map([
|
|||||||
['ls', 'list'],
|
['ls', 'list'],
|
||||||
['project', 'projects'],
|
['project', 'projects'],
|
||||||
['projects', 'projects'],
|
['projects', 'projects'],
|
||||||
|
['pull', 'pull'],
|
||||||
['remove', 'remove'],
|
['remove', 'remove'],
|
||||||
['rm', 'remove'],
|
['rm', 'remove'],
|
||||||
['secret', 'secrets'],
|
['secret', 'secrets'],
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import getSubcommand from '../../util/get-subcommand';
|
|||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
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 error from '../../util/output/error';
|
|
||||||
import init from './init';
|
import init from './init';
|
||||||
import { getPkgName } from '../../util/pkg-name';
|
import { getPkgName } from '../../util/pkg-name';
|
||||||
|
|
||||||
@@ -44,6 +43,7 @@ const help = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default async function main(client: Client) {
|
export default async function main(client: Client) {
|
||||||
|
const { output } = client;
|
||||||
let argv;
|
let argv;
|
||||||
let args;
|
let args;
|
||||||
|
|
||||||
@@ -64,15 +64,15 @@ export default async function main(client: Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (argv._.length > 3) {
|
if (argv._.length > 3) {
|
||||||
client.output.error('Too much arguments.');
|
output.error('Too much arguments.');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await init(client, argv, args);
|
return await init(client, argv, args);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(error(err.message));
|
output.prettyError(err);
|
||||||
client.output.debug(err.stack);
|
output.debug(err.stack);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,13 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import tar from 'tar-fs';
|
import tar from 'tar-fs';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import fetch from 'node-fetch';
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import listInput from '../../util/input/list';
|
import listInput from '../../util/input/list';
|
||||||
import listItem from '../../util/output/list-item';
|
import listItem from '../../util/output/list-item';
|
||||||
import promptBool from '../../util/input/prompt-bool';
|
import promptBool from '../../util/input/prompt-bool';
|
||||||
import toHumanPath from '../../util/humanize-path';
|
import toHumanPath from '../../util/humanize-path';
|
||||||
import { Output } from '../../util/output';
|
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import success from '../../util/output/success';
|
|
||||||
import info from '../../util/output/info';
|
import info from '../../util/output/info';
|
||||||
import cmd from '../../util/output/cmd';
|
import cmd from '../../util/output/cmd';
|
||||||
import didYouMean from '../../util/init/did-you-mean';
|
import didYouMean from '../../util/init/did-you-mean';
|
||||||
@@ -40,7 +37,7 @@ export default async function init(
|
|||||||
const [name, dir] = args;
|
const [name, dir] = args;
|
||||||
const force = opts['-f'] || opts['--force'];
|
const force = opts['-f'] || opts['--force'];
|
||||||
|
|
||||||
const examples = await fetchExampleList(output);
|
const examples = await fetchExampleList(client);
|
||||||
|
|
||||||
if (!examples) {
|
if (!examples) {
|
||||||
throw new Error(`Could not fetch example list.`);
|
throw new Error(`Could not fetch example list.`);
|
||||||
@@ -56,47 +53,37 @@ export default async function init(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return extractExample(output, chosen, dir, force);
|
return extractExample(client, chosen, dir, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exampleList.includes(name)) {
|
if (exampleList.includes(name)) {
|
||||||
return extractExample(output, name, dir, force);
|
return extractExample(client, name, dir, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldExample = examples.find(x => !x.visible && x.name === name);
|
const oldExample = examples.find(x => !x.visible && x.name === name);
|
||||||
if (oldExample) {
|
if (oldExample) {
|
||||||
return extractExample(output, name, dir, force, 'v1');
|
return extractExample(client, name, dir, force, 'v1');
|
||||||
}
|
}
|
||||||
|
|
||||||
const found = await guess(exampleList, name);
|
const found = await guess(exampleList, name);
|
||||||
|
|
||||||
if (typeof found === 'string') {
|
if (typeof found === 'string') {
|
||||||
return extractExample(output, found, dir, force);
|
return extractExample(client, found, dir, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(info('No changes made.'));
|
output.log(info('No changes made.'));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch example list json
|
* Fetch example list json
|
||||||
*/
|
*/
|
||||||
async function fetchExampleList(output: Output) {
|
async function fetchExampleList(client: Client) {
|
||||||
output.spinner('Fetching examples');
|
client.output.spinner('Fetching examples');
|
||||||
const url = `${EXAMPLE_API}/v2/list.json`;
|
const url = `${EXAMPLE_API}/v2/list.json`;
|
||||||
|
|
||||||
try {
|
const body = await client.fetch<Example[]>(url);
|
||||||
const resp = await fetch(url);
|
return body;
|
||||||
output.stopSpinner();
|
|
||||||
|
|
||||||
if (resp.status !== 200) {
|
|
||||||
throw new Error(`Failed fetching list.json (${resp.statusText}).`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (await resp.json()) as Example[];
|
|
||||||
} catch (e) {
|
|
||||||
output.stopSpinner();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,31 +106,33 @@ async function chooseFromDropdown(message: string, exampleList: string[]) {
|
|||||||
* Extract example to directory
|
* Extract example to directory
|
||||||
*/
|
*/
|
||||||
async function extractExample(
|
async function extractExample(
|
||||||
output: Output,
|
client: Client,
|
||||||
name: string,
|
name: string,
|
||||||
dir: string,
|
dir: string,
|
||||||
force?: boolean,
|
force?: boolean,
|
||||||
ver: string = 'v2'
|
ver: string = 'v2'
|
||||||
) {
|
) {
|
||||||
|
const { output } = client;
|
||||||
const folder = prepareFolder(process.cwd(), dir || name, force);
|
const folder = prepareFolder(process.cwd(), dir || name, force);
|
||||||
output.spinner(`Fetching ${name}`);
|
output.spinner(`Fetching ${name}`);
|
||||||
|
|
||||||
const url = `${EXAMPLE_API}/${ver}/download/${name}.tar.gz`;
|
const url = `${EXAMPLE_API}/${ver}/download/${name}.tar.gz`;
|
||||||
|
|
||||||
return fetch(url)
|
return client
|
||||||
.then(async resp => {
|
.fetch(url, { json: false })
|
||||||
|
.then(async res => {
|
||||||
output.stopSpinner();
|
output.stopSpinner();
|
||||||
|
|
||||||
if (resp.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new Error(`Could not get ${name}.tar.gz`);
|
throw new Error(`Could not get ${name}.tar.gz`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
const extractor = tar.extract(folder);
|
const extractor = tar.extract(folder);
|
||||||
resp.body.on('error', reject);
|
res.body.on('error', reject);
|
||||||
extractor.on('error', reject);
|
extractor.on('error', reject);
|
||||||
extractor.on('finish', resolve);
|
extractor.on('finish', resolve);
|
||||||
resp.body.pipe(extractor);
|
res.body.pipe(extractor);
|
||||||
});
|
});
|
||||||
|
|
||||||
const successLog = `Initialized "${chalk.bold(
|
const successLog = `Initialized "${chalk.bold(
|
||||||
@@ -158,7 +147,7 @@ async function extractExample(
|
|||||||
`cd ${folderRel}`
|
`cd ${folderRel}`
|
||||||
)} and run ${getCommandName()}.`
|
)} and run ${getCommandName()}.`
|
||||||
);
|
);
|
||||||
console.log(success(`${successLog}\n${deployHint}`));
|
output.success(`${successLog}\n${deployHint}`);
|
||||||
return 0;
|
return 0;
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { getPkgName, getCommandName } from '../util/pkg-name';
|
|||||||
import Client from '../util/client';
|
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';
|
||||||
|
|
||||||
const help = () => {
|
const help = () => {
|
||||||
console.log(`
|
console.log(`
|
||||||
@@ -116,7 +117,7 @@ export default async function main(client: Client) {
|
|||||||
|
|
||||||
const { builds } =
|
const { builds } =
|
||||||
deployment.version === 2
|
deployment.version === 2
|
||||||
? await client.fetch(`/v1/now/deployments/${id}/builds`)
|
? await client.fetch<{ builds: Build[] }>(`/v1/deployments/${id}/builds`)
|
||||||
: { builds: [] };
|
: { builds: [] };
|
||||||
|
|
||||||
log(
|
log(
|
||||||
@@ -146,7 +147,8 @@ export default async function main(client: Client) {
|
|||||||
|
|
||||||
for (const build of builds) {
|
for (const build of builds) {
|
||||||
const { id, createdAt, readyStateAt } = build;
|
const { id, createdAt, readyStateAt } = build;
|
||||||
times[id] = createdAt ? elapsed(readyStateAt - createdAt) : null;
|
times[id] =
|
||||||
|
createdAt && readyStateAt ? elapsed(readyStateAt - createdAt) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
print(chalk.bold(' Builds\n\n'));
|
print(chalk.bold(' Builds\n\n'));
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import getArgs from '../../util/get-args';
|
import getArgs from '../../util/get-args';
|
||||||
import getSubcommand from '../../util/get-subcommand';
|
|
||||||
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 setupAndLink from '../../util/link/setup-and-link';
|
import setupAndLink from '../../util/link/setup-and-link';
|
||||||
@@ -24,6 +22,9 @@ const help = () => {
|
|||||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||||
'TOKEN'
|
'TOKEN'
|
||||||
)} Login token
|
)} Login token
|
||||||
|
-p ${chalk.bold.underline('NAME')}, --project=${chalk.bold.underline(
|
||||||
|
'NAME'
|
||||||
|
)} Project name
|
||||||
--confirm Confirm default options and skip questions
|
--confirm Confirm default options and skip questions
|
||||||
|
|
||||||
${chalk.dim('Examples:')}
|
${chalk.dim('Examples:')}
|
||||||
@@ -44,40 +45,26 @@ const help = () => {
|
|||||||
`);
|
`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const COMMAND_CONFIG = {
|
|
||||||
// No subcommands yet
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function main(client: Client) {
|
export default async function main(client: Client) {
|
||||||
let argv;
|
const argv = getArgs(client.argv.slice(2), {
|
||||||
|
'--confirm': Boolean,
|
||||||
try {
|
'--project': String,
|
||||||
argv = getArgs(client.argv.slice(2), {
|
'-p': '--project',
|
||||||
'--confirm': Boolean,
|
});
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argv['--help']) {
|
if (argv['--help']) {
|
||||||
help();
|
help();
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { args } = getSubcommand(argv._.slice(1), COMMAND_CONFIG);
|
const cwd = argv._[1] || process.cwd();
|
||||||
const path = args[0] || process.cwd();
|
const link = await setupAndLink(client, cwd, {
|
||||||
const autoConfirm = argv['--confirm'] || false;
|
forceDelete: true,
|
||||||
const forceDelete = true;
|
autoConfirm: argv['--confirm'],
|
||||||
|
projectName: argv['--project'],
|
||||||
const link = await setupAndLink(
|
successEmoji: 'success',
|
||||||
client,
|
setupMsg: 'Set up',
|
||||||
path,
|
});
|
||||||
forceDelete,
|
|
||||||
autoConfirm,
|
|
||||||
'success',
|
|
||||||
'Set up'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (link.status === 'error') {
|
if (link.status === 'error') {
|
||||||
return link.exitCode;
|
return link.exitCode;
|
||||||
|
|||||||
@@ -77,14 +77,8 @@ export default async function main(client: Client) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { output, config } = client;
|
||||||
authConfig: { token },
|
|
||||||
output,
|
|
||||||
apiUrl,
|
|
||||||
config,
|
|
||||||
} = client;
|
|
||||||
|
|
||||||
const debugEnabled = argv['--debug'];
|
|
||||||
const { print, log, error, note, debug, spinner } = output;
|
const { print, log, error, note, debug, spinner } = output;
|
||||||
|
|
||||||
if (argv._.length > 2) {
|
if (argv._.length > 2) {
|
||||||
@@ -92,8 +86,8 @@ export default async function main(client: Client) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let app: string | null = argv._[1];
|
let app: string | undefined = argv._[1];
|
||||||
let host: string | null = null;
|
let host: string | undefined = undefined;
|
||||||
|
|
||||||
if (argv['--help']) {
|
if (argv['--help']) {
|
||||||
help();
|
help();
|
||||||
@@ -126,10 +120,7 @@ export default async function main(client: Client) {
|
|||||||
spinner(`Fetching deployments in ${chalk.bold(contextName)}`);
|
spinner(`Fetching deployments in ${chalk.bold(contextName)}`);
|
||||||
|
|
||||||
const now = new Now({
|
const now = new Now({
|
||||||
apiUrl,
|
client,
|
||||||
token,
|
|
||||||
debug: debugEnabled,
|
|
||||||
output,
|
|
||||||
currentTeam,
|
currentTeam,
|
||||||
});
|
});
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
@@ -156,7 +147,7 @@ export default async function main(client: Client) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
app = null;
|
app = undefined;
|
||||||
host = asHost;
|
host = asHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { validate as validateEmail } from 'email-validator';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import hp from '../util/humanize-path';
|
import hp from '../util/humanize-path';
|
||||||
import getArgs from '../util/get-args';
|
import getArgs from '../util/get-args';
|
||||||
import handleError from '../util/handle-error';
|
|
||||||
import logo from '../util/output/logo';
|
import logo from '../util/output/logo';
|
||||||
import prompt from '../util/login/prompt';
|
import prompt from '../util/login/prompt';
|
||||||
import doSamlLogin from '../util/login/saml';
|
import doSamlLogin from '../util/login/saml';
|
||||||
@@ -52,20 +51,14 @@ const help = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default async function login(client: Client): Promise<number> {
|
export default async function login(client: Client): Promise<number> {
|
||||||
let argv;
|
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
|
|
||||||
try {
|
const argv = getArgs(client.argv.slice(2), {
|
||||||
argv = getArgs(client.argv.slice(2), {
|
'--oob': Boolean,
|
||||||
'--oob': Boolean,
|
'--github': Boolean,
|
||||||
'--github': Boolean,
|
'--gitlab': Boolean,
|
||||||
'--gitlab': Boolean,
|
'--bitbucket': Boolean,
|
||||||
'--bitbucket': Boolean,
|
});
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
handleError(err);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argv['--help']) {
|
if (argv['--help']) {
|
||||||
help();
|
help();
|
||||||
@@ -115,8 +108,7 @@ export default async function login(client: Client): Promise<number> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When `result` is a string it's the user's authentication token.
|
// Save the user's authentication token to the configuration file.
|
||||||
// It needs to be saved to the configuration file.
|
|
||||||
client.authConfig.token = result.token;
|
client.authConfig.token = result.token;
|
||||||
|
|
||||||
writeToAuthConfigFile(client.authConfig);
|
writeToAuthConfigFile(client.authConfig);
|
||||||
@@ -124,9 +116,9 @@ export default async function login(client: Client): Promise<number> {
|
|||||||
|
|
||||||
output.debug(`Saved credentials in "${hp(getGlobalPathConfig())}"`);
|
output.debug(`Saved credentials in "${hp(getGlobalPathConfig())}"`);
|
||||||
|
|
||||||
console.log(
|
output.print(
|
||||||
`${chalk.cyan('Congratulations!')} ` +
|
`${chalk.cyan('Congratulations!')} ` +
|
||||||
`You are now logged in. In order to deploy something, run ${getCommandName()}.`
|
`You are now logged in. In order to deploy something, run ${getCommandName()}.\n`
|
||||||
);
|
);
|
||||||
|
|
||||||
output.print(
|
output.print(
|
||||||
|
|||||||
116
packages/cli/src/commands/pull.ts
Normal file
116
packages/cli/src/commands/pull.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import chalk from 'chalk';
|
||||||
|
import { join } from 'path';
|
||||||
|
import Client from '../util/client';
|
||||||
|
import { emoji, prependEmoji } from '../util/emoji';
|
||||||
|
import getArgs from '../util/get-args';
|
||||||
|
import handleError from '../util/handle-error';
|
||||||
|
import setupAndLink from '../util/link/setup-and-link';
|
||||||
|
import logo from '../util/output/logo';
|
||||||
|
import stamp from '../util/output/stamp';
|
||||||
|
import { getPkgName } from '../util/pkg-name';
|
||||||
|
import {
|
||||||
|
getLinkedProject,
|
||||||
|
VERCEL_DIR,
|
||||||
|
VERCEL_DIR_PROJECT,
|
||||||
|
} from '../util/projects/link';
|
||||||
|
import { writeProjectSettings } from '../util/projects/project-settings';
|
||||||
|
import pull from './env/pull';
|
||||||
|
|
||||||
|
const help = () => {
|
||||||
|
return console.log(`
|
||||||
|
${chalk.bold(`${logo} ${getPkgName()} pull`)} [path]
|
||||||
|
|
||||||
|
${chalk.dim('Options:')}
|
||||||
|
|
||||||
|
-h, --help Output usage information
|
||||||
|
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
|
||||||
|
'FILE'
|
||||||
|
)} Path to the local ${'`vercel.json`'} file
|
||||||
|
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
|
||||||
|
'DIR'
|
||||||
|
)} Path to the global ${'`.vercel`'} directory
|
||||||
|
-d, --debug Debug mode [off]
|
||||||
|
--env [filename] The file to write Development Environment Variables to [.env]
|
||||||
|
-y, --yes Skip the confirmation prompt
|
||||||
|
|
||||||
|
${chalk.dim('Examples:')}
|
||||||
|
|
||||||
|
${chalk.gray('–')} Pull the latest Project Settings from the cloud
|
||||||
|
|
||||||
|
${chalk.cyan(`$ ${getPkgName()} pull`)}
|
||||||
|
${chalk.cyan(`$ ${getPkgName()} pull ./path-to-project`)}
|
||||||
|
${chalk.cyan(`$ ${getPkgName()} pull --env .env.local`)}
|
||||||
|
${chalk.cyan(`$ ${getPkgName()} pull ./path-to-project --env .env.local`)}
|
||||||
|
`);
|
||||||
|
};
|
||||||
|
export default async function main(client: Client) {
|
||||||
|
let argv;
|
||||||
|
try {
|
||||||
|
argv = getArgs(client.argv.slice(2), {
|
||||||
|
'--yes': Boolean,
|
||||||
|
'--env': String,
|
||||||
|
'--debug': Boolean,
|
||||||
|
'-d': '--debug',
|
||||||
|
'-y': '--yes',
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argv['--help']) {
|
||||||
|
help();
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cwd = argv._[1] || process.cwd();
|
||||||
|
const yes = argv['--yes'];
|
||||||
|
const env = argv['--env'] ?? '.env';
|
||||||
|
const settingsStamp = stamp();
|
||||||
|
let link = await getLinkedProject(client, cwd);
|
||||||
|
if (link.status === 'not_linked') {
|
||||||
|
link = await setupAndLink(client, cwd, {
|
||||||
|
autoConfirm: yes,
|
||||||
|
successEmoji: 'link',
|
||||||
|
setupMsg: 'Set up',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (link.status === 'not_linked') {
|
||||||
|
// User aborted project linking questions
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link.status === 'error') {
|
||||||
|
return link.exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { project, org } = link;
|
||||||
|
|
||||||
|
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
|
||||||
|
|
||||||
|
const result = await pull(
|
||||||
|
client,
|
||||||
|
project,
|
||||||
|
argv,
|
||||||
|
[join(cwd, env)],
|
||||||
|
client.output
|
||||||
|
);
|
||||||
|
if (result !== 0) {
|
||||||
|
// an error happened
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeProjectSettings(cwd, project, org);
|
||||||
|
|
||||||
|
client.output.print(
|
||||||
|
`${prependEmoji(
|
||||||
|
`Downloaded project settings to ${chalk.bold(
|
||||||
|
join(VERCEL_DIR, VERCEL_DIR_PROJECT)
|
||||||
|
)} ${chalk.gray(settingsStamp())}`,
|
||||||
|
emoji('success')
|
||||||
|
)}\n`
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -85,8 +85,6 @@ export default async function main(client: Client) {
|
|||||||
argv._ = argv._.slice(1);
|
argv._ = argv._.slice(1);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
apiUrl,
|
|
||||||
authConfig: { token },
|
|
||||||
output,
|
output,
|
||||||
config: { currentTeam },
|
config: { currentTeam },
|
||||||
} = client;
|
} = client;
|
||||||
@@ -245,11 +243,8 @@ export default async function main(client: Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const now = new Now({
|
const now = new Now({
|
||||||
apiUrl,
|
client,
|
||||||
token,
|
|
||||||
debug: argv['--debug'],
|
|
||||||
currentTeam,
|
currentTeam,
|
||||||
output,
|
|
||||||
});
|
});
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
|
||||||
|
|||||||
@@ -72,8 +72,6 @@ const help = () => {
|
|||||||
|
|
||||||
// Options
|
// Options
|
||||||
let argv;
|
let argv;
|
||||||
let debug;
|
|
||||||
let apiUrl;
|
|
||||||
let subcommand;
|
let subcommand;
|
||||||
let nextTimestamp;
|
let nextTimestamp;
|
||||||
|
|
||||||
@@ -90,8 +88,6 @@ const main = async client => {
|
|||||||
|
|
||||||
argv._ = argv._.slice(1);
|
argv._ = argv._.slice(1);
|
||||||
|
|
||||||
debug = argv.debug;
|
|
||||||
apiUrl = client.apiUrl;
|
|
||||||
subcommand = argv._[0];
|
subcommand = argv._[0];
|
||||||
nextTimestamp = argv.next;
|
nextTimestamp = argv.next;
|
||||||
|
|
||||||
@@ -101,7 +97,6 @@ const main = async client => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
authConfig: { token },
|
|
||||||
output,
|
output,
|
||||||
config: { currentTeam },
|
config: { currentTeam },
|
||||||
} = client;
|
} = client;
|
||||||
@@ -118,7 +113,7 @@ const main = async client => {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
return run({ output, token, contextName, currentTeam, client });
|
return run({ output, contextName, currentTeam, client });
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async client => {
|
export default async client => {
|
||||||
@@ -130,8 +125,8 @@ export default async client => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function run({ output, token, contextName, currentTeam, client }) {
|
async function run({ output, contextName, currentTeam, client }) {
|
||||||
const secrets = new NowSecrets({ apiUrl, token, debug, currentTeam, output });
|
const secrets = new NowSecrets({ client, currentTeam });
|
||||||
const args = argv._.slice(1);
|
const args = argv._.slice(1);
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const { 'test-warning': testWarningFlag } = argv;
|
const { 'test-warning': testWarningFlag } = argv;
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import stamp from '../../util/output/stamp.ts';
|
import stamp from '../../util/output/stamp';
|
||||||
import info from '../../util/output/info';
|
import info from '../../util/output/info';
|
||||||
import rightPad from '../../util/output/right-pad';
|
|
||||||
import eraseLines from '../../util/output/erase-lines';
|
import eraseLines from '../../util/output/erase-lines';
|
||||||
import chars from '../../util/output/chars';
|
import chars from '../../util/output/chars';
|
||||||
import note from '../../util/output/note';
|
import note from '../../util/output/note';
|
||||||
import textInput from '../../util/input/text';
|
import textInput from '../../util/input/text';
|
||||||
import invite from './invite';
|
import invite from './invite';
|
||||||
import { writeToConfigFile } from '../../util/config/files';
|
import { writeToConfigFile } from '../../util/config/files';
|
||||||
import { getPkgName, getCommandName } from '../../util/pkg-name.ts';
|
import { getPkgName, getCommandName } from '../../util/pkg-name';
|
||||||
|
import Client from '../../util/client';
|
||||||
|
import createTeam from '../../util/teams/create-team';
|
||||||
|
import patchTeam from '../../util/teams/patch-team';
|
||||||
|
|
||||||
const validateSlugKeypress = (data, value) =>
|
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
|
||||||
// should be fixed on utils/input/text.js
|
// should be fixed on utils/input/text.js
|
||||||
/^[a-zA-Z]+[a-zA-Z0-9_-]*$/.test(value + data);
|
/^[a-zA-Z]+[a-zA-Z0-9_-]*$/.test(value + data);
|
||||||
|
|
||||||
const validateNameKeypress = (data, value) =>
|
const validateNameKeypress = (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
|
||||||
// should be fixed on utils/input/text.js
|
// should be fixed on utils/input/text.js
|
||||||
/^[ a-zA-Z0-9_-]+$/.test(value + data);
|
/^[ a-zA-Z0-9_-]+$/.test(value + data);
|
||||||
@@ -30,17 +32,17 @@ const gracefulExit = () => {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const teamUrlPrefix = rightPad('Team URL', 14) + chalk.gray('vercel.com/');
|
const teamUrlPrefix = 'Team URL'.padEnd(14) + chalk.gray('vercel.com/');
|
||||||
const teamNamePrefix = rightPad('Team Name', 14);
|
const teamNamePrefix = 'Team Name'.padEnd(14);
|
||||||
|
|
||||||
export default async function add(client, teams) {
|
export default async function add(client: Client): Promise<number> {
|
||||||
let slug;
|
let slug;
|
||||||
let team;
|
let team;
|
||||||
let elapsed;
|
let elapsed;
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
|
|
||||||
output.log(
|
output.log(
|
||||||
`Pick a team identifier for its url (e.g.: ${chalk.cyan(
|
`Pick a team identifier for its URL (e.g.: ${chalk.cyan(
|
||||||
'`vercel.com/acme`'
|
'`vercel.com/acme`'
|
||||||
)})`
|
)})`
|
||||||
);
|
);
|
||||||
@@ -66,14 +68,12 @@ export default async function add(client, teams) {
|
|||||||
elapsed = stamp();
|
elapsed = stamp();
|
||||||
output.spinner(teamUrlPrefix + slug);
|
output.spinner(teamUrlPrefix + slug);
|
||||||
|
|
||||||
let res;
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
res = await teams.create({ slug });
|
team = await createTeam(client, { slug });
|
||||||
team = res;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
output.stopSpinner();
|
output.stopSpinner();
|
||||||
process.stdout.write(eraseLines(2));
|
output.print(eraseLines(2));
|
||||||
output.error(err.message);
|
output.error(err.message);
|
||||||
}
|
}
|
||||||
} while (!team);
|
} while (!team);
|
||||||
@@ -104,11 +104,12 @@ export default async function add(client, teams) {
|
|||||||
elapsed = stamp();
|
elapsed = stamp();
|
||||||
output.spinner(teamNamePrefix + name);
|
output.spinner(teamNamePrefix + name);
|
||||||
|
|
||||||
const res = await teams.edit({ id: team.id, name });
|
const res = await patchTeam(client, team.id, { name });
|
||||||
|
|
||||||
output.stopSpinner();
|
output.stopSpinner();
|
||||||
process.stdout.write(eraseLines(2));
|
process.stdout.write(eraseLines(2));
|
||||||
|
|
||||||
|
/*
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
output.error(res.error.message);
|
output.error(res.error.message);
|
||||||
output.log(`${chalk.red(`✖ ${teamNamePrefix}`)}${name}`);
|
output.log(`${chalk.red(`✖ ${teamNamePrefix}`)}${name}`);
|
||||||
@@ -117,33 +118,25 @@ export default async function add(client, teams) {
|
|||||||
// TODO: maybe we want to ask the user to retry? not sure if
|
// TODO: maybe we want to ask the user to retry? not sure if
|
||||||
// there's a scenario where that would be wanted
|
// there's a scenario where that would be wanted
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
team = Object.assign(team, res);
|
team = Object.assign(team, res);
|
||||||
|
|
||||||
output.success(`Team name saved ${elapsed()}`);
|
output.success(`Team name saved ${elapsed()}`);
|
||||||
output.log(`${chalk.cyan(`${chars.tick} `) + teamNamePrefix + team.name}\n`);
|
output.log(`${chalk.cyan(`${chars.tick} `) + teamNamePrefix + team.name}\n`);
|
||||||
|
|
||||||
output.spinner('Saving');
|
|
||||||
|
|
||||||
// Update config file
|
// Update config file
|
||||||
const configCopy = Object.assign({}, client.config);
|
output.spinner('Saving');
|
||||||
|
client.config.currentTeam = team.id;
|
||||||
if (configCopy.sh) {
|
writeToConfigFile(client.config);
|
||||||
configCopy.sh.currentTeam = team;
|
|
||||||
} else {
|
|
||||||
configCopy.currentTeam = team.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeToConfigFile(configCopy);
|
|
||||||
|
|
||||||
output.stopSpinner();
|
output.stopSpinner();
|
||||||
|
|
||||||
await invite(client, { _: [] }, teams, {
|
await invite(client, [], {
|
||||||
introMsg: 'Invite your teammates! When done, press enter on an empty field',
|
introMsg: 'Invite your teammates! When done, press enter on an empty field',
|
||||||
noopMsg: `You can invite teammates later by running ${getCommandName(
|
noopMsg: `You can invite teammates later by running ${getCommandName(
|
||||||
`teams invite`
|
`teams invite`
|
||||||
)}`,
|
)}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
gracefulExit();
|
return gracefulExit();
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import error from '../../util/output/error';
|
import error from '../../util/output/error';
|
||||||
import NowTeams from '../../util/teams';
|
|
||||||
import logo from '../../util/output/logo';
|
import logo from '../../util/output/logo';
|
||||||
import list from './list';
|
import list from './list';
|
||||||
import add from './add';
|
import add from './add';
|
||||||
@@ -8,7 +7,6 @@ import change from './switch';
|
|||||||
import invite from './invite';
|
import invite from './invite';
|
||||||
import { getPkgName } from '../../util/pkg-name';
|
import { getPkgName } from '../../util/pkg-name';
|
||||||
import getArgs from '../../util/get-args';
|
import getArgs from '../../util/get-args';
|
||||||
import handleError from '../../util/handle-error';
|
|
||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
|
|
||||||
const help = () => {
|
const help = () => {
|
||||||
@@ -61,28 +59,11 @@ const help = () => {
|
|||||||
`);
|
`);
|
||||||
};
|
};
|
||||||
|
|
||||||
let argv;
|
|
||||||
let debug;
|
|
||||||
let apiUrl;
|
|
||||||
let subcommand;
|
|
||||||
|
|
||||||
export default async (client: Client) => {
|
export default async (client: Client) => {
|
||||||
try {
|
let subcommand;
|
||||||
argv = getArgs(client.argv.slice(2), {
|
|
||||||
'--since': String,
|
|
||||||
'--until': String,
|
|
||||||
'--next': Number,
|
|
||||||
'-N': '--next',
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug = argv['--debug'];
|
const argv = getArgs(client.argv.slice(2), undefined, { permissive: true });
|
||||||
apiUrl = client.apiUrl;
|
const isSwitch = argv._[0] === 'switch';
|
||||||
|
|
||||||
const isSwitch = argv._[0] && argv._[0] === 'switch';
|
|
||||||
|
|
||||||
argv._ = argv._.slice(1);
|
argv._ = argv._.slice(1);
|
||||||
|
|
||||||
@@ -97,19 +78,11 @@ export default async (client: Client) => {
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
let exitCode = 0;
|
||||||
authConfig: { token },
|
|
||||||
config,
|
|
||||||
} = client;
|
|
||||||
|
|
||||||
const { currentTeam } = config;
|
|
||||||
const teams = new NowTeams({ apiUrl, token, debug, currentTeam });
|
|
||||||
|
|
||||||
let exitCode;
|
|
||||||
switch (subcommand) {
|
switch (subcommand) {
|
||||||
case 'list':
|
case 'list':
|
||||||
case 'ls': {
|
case 'ls': {
|
||||||
exitCode = await list(client, argv, teams);
|
exitCode = await list(client);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'switch':
|
case 'switch':
|
||||||
@@ -119,12 +92,12 @@ export default async (client: Client) => {
|
|||||||
}
|
}
|
||||||
case 'add':
|
case 'add':
|
||||||
case 'create': {
|
case 'create': {
|
||||||
exitCode = await add(client, teams);
|
exitCode = await add(client);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'invite': {
|
case 'invite': {
|
||||||
exitCode = await invite(client, argv, teams);
|
exitCode = await invite(client, argv._);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@@ -137,6 +110,5 @@ export default async (client: Client) => {
|
|||||||
help();
|
help();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
teams.close();
|
return exitCode;
|
||||||
return exitCode || 0;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { email as regexEmail } from '../../util/input/regexes';
|
import Client from '../../util/client';
|
||||||
import cmd from '../../util/output/cmd.ts';
|
import cmd from '../../util/output/cmd';
|
||||||
import stamp from '../../util/output/stamp.ts';
|
import stamp from '../../util/output/stamp';
|
||||||
import param from '../../util/output/param.ts';
|
import param from '../../util/output/param';
|
||||||
import chars from '../../util/output/chars';
|
import chars from '../../util/output/chars';
|
||||||
import rightPad from '../../util/output/right-pad';
|
|
||||||
import textInput from '../../util/input/text';
|
import textInput from '../../util/input/text';
|
||||||
import eraseLines from '../../util/output/erase-lines';
|
import eraseLines from '../../util/output/erase-lines';
|
||||||
import getUser from '../../util/get-user.ts';
|
import getUser from '../../util/get-user';
|
||||||
import { getCommandName } from '../../util/pkg-name.ts';
|
import { getCommandName } from '../../util/pkg-name';
|
||||||
|
import { email as regexEmail } from '../../util/input/regexes';
|
||||||
|
import getTeams from '../../util/teams/get-teams';
|
||||||
|
import inviteUserToTeam from '../../util/teams/invite-user-to-team';
|
||||||
|
|
||||||
const validateEmail = data => regexEmail.test(data.trim()) || data.length === 0;
|
const validateEmail = (data: string) =>
|
||||||
|
regexEmail.test(data.trim()) || data.length === 0;
|
||||||
|
|
||||||
const domains = Array.from(
|
const domains = Array.from(
|
||||||
new Set([
|
new Set([
|
||||||
@@ -29,12 +32,12 @@ const domains = Array.from(
|
|||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
const emailAutoComplete = (value, teamSlug) => {
|
const emailAutoComplete = (value: string, teamSlug: string) => {
|
||||||
const parts = value.split('@');
|
const parts = value.split('@');
|
||||||
|
|
||||||
if (parts.length === 2 && parts[1].length > 0) {
|
if (parts.length === 2 && parts[1].length > 0) {
|
||||||
const [, host] = parts;
|
const [, host] = parts;
|
||||||
let suggestion = false;
|
let suggestion: string | false = false;
|
||||||
|
|
||||||
domains.unshift(teamSlug);
|
domains.unshift(teamSlug);
|
||||||
for (const domain of domains) {
|
for (const domain of domains) {
|
||||||
@@ -52,17 +55,16 @@ const emailAutoComplete = (value, teamSlug) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default async function invite(
|
export default async function invite(
|
||||||
client,
|
client: Client,
|
||||||
argv,
|
emails: string[] = [],
|
||||||
teams,
|
{ introMsg = '', noopMsg = 'No changes made' } = {}
|
||||||
{ introMsg, noopMsg = 'No changes made' } = {}
|
): Promise<number> {
|
||||||
) {
|
|
||||||
const { config, output } = client;
|
const { config, output } = client;
|
||||||
const { currentTeam: currentTeamId } = config;
|
const { currentTeam: currentTeamId } = config;
|
||||||
|
|
||||||
output.spinner('Fetching teams');
|
output.spinner('Fetching teams');
|
||||||
const list = (await teams.ls()).teams;
|
const teams = await getTeams(client);
|
||||||
const currentTeam = list.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;
|
let user;
|
||||||
@@ -94,8 +96,8 @@ export default async function invite(
|
|||||||
introMsg || `Inviting team members to ${chalk.bold(currentTeam.name)}`
|
introMsg || `Inviting team members to ${chalk.bold(currentTeam.name)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (argv._.length > 0) {
|
if (emails.length > 0) {
|
||||||
for (const email of argv._) {
|
for (const email of emails) {
|
||||||
if (regexEmail.test(email)) {
|
if (regexEmail.test(email)) {
|
||||||
output.spinner(email);
|
output.spinner(email);
|
||||||
const elapsed = stamp();
|
const elapsed = stamp();
|
||||||
@@ -103,8 +105,8 @@ export default async function invite(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
const res = await teams.inviteUser({ teamId: currentTeam.id, email });
|
const res = await inviteUserToTeam(client, currentTeam.id, email);
|
||||||
userInfo = res.name || res.username;
|
userInfo = res.username;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'user_not_found') {
|
if (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}".`);
|
||||||
@@ -123,12 +125,11 @@ export default async function invite(
|
|||||||
output.log(`${chalk.red(`✖ ${email}`)} ${chalk.gray('[invalid]')}`);
|
output.log(`${chalk.red(`✖ ${email}`)} ${chalk.gray('[invalid]')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inviteUserPrefix = rightPad('Invite User', 14);
|
const inviteUserPrefix = 'Invite User'.padEnd(14);
|
||||||
const sentEmailPrefix = rightPad('Sent Email', 14);
|
const sentEmailPrefix = 'Sent Email'.padEnd(14);
|
||||||
const emails = [];
|
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
let email;
|
let email;
|
||||||
do {
|
do {
|
||||||
@@ -151,12 +152,12 @@ export default async function invite(
|
|||||||
output.spinner(inviteUserPrefix + email);
|
output.spinner(inviteUserPrefix + email);
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
const { name, username } = await teams.inviteUser({
|
const { username } = await inviteUserToTeam(
|
||||||
teamId: currentTeam.id,
|
client,
|
||||||
email,
|
currentTeam.id,
|
||||||
});
|
email
|
||||||
const userInfo = name || username;
|
);
|
||||||
email = `${email}${userInfo ? ` (${userInfo})` : ''} ${elapsed()}`;
|
email = `${email}${username ? ` (${username})` : ''} ${elapsed()}`;
|
||||||
emails.push(email);
|
emails.push(email);
|
||||||
output.log(`${chalk.cyan(chars.tick)} ${sentEmailPrefix}${email}`);
|
output.log(`${chalk.cyan(chars.tick)} ${sentEmailPrefix}${email}`);
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
@@ -194,4 +195,6 @@ export default async function invite(
|
|||||||
output.log(`${chalk.cyan(chars.tick)} ${inviteUserPrefix}${email}`);
|
output.log(`${chalk.cyan(chars.tick)} ${inviteUserPrefix}${email}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,41 @@
|
|||||||
import chars from '../../util/output/chars';
|
import chars from '../../util/output/chars';
|
||||||
import table from '../../util/output/table';
|
import table from '../../util/output/table';
|
||||||
import getUser from '../../util/get-user.ts';
|
import getUser from '../../util/get-user';
|
||||||
|
import getTeams from '../../util/teams/get-teams';
|
||||||
import getPrefixedFlags from '../../util/get-prefixed-flags';
|
import getPrefixedFlags from '../../util/get-prefixed-flags';
|
||||||
import { getPkgName } from '../../util/pkg-name.ts';
|
import { getPkgName } from '../../util/pkg-name';
|
||||||
import getCommandFlags from '../../util/get-command-flags';
|
import getCommandFlags from '../../util/get-command-flags';
|
||||||
import cmd from '../../util/output/cmd.ts';
|
import cmd from '../../util/output/cmd';
|
||||||
|
import Client from '../../util/client';
|
||||||
|
import getArgs from '../../util/get-args';
|
||||||
|
|
||||||
export default async function list(client, argv, teams) {
|
export default async function list(client: Client): Promise<number> {
|
||||||
const { config, output } = client;
|
const { config, output } = client;
|
||||||
const { next } = argv;
|
|
||||||
|
const argv = getArgs(client.argv.slice(2), {
|
||||||
|
'--since': String,
|
||||||
|
'--until': String,
|
||||||
|
'--count': Number,
|
||||||
|
'--next': Number,
|
||||||
|
'-C': '--count',
|
||||||
|
'-N': '--next',
|
||||||
|
});
|
||||||
|
|
||||||
|
const next = argv['--next'];
|
||||||
|
const count = argv['--count'];
|
||||||
|
|
||||||
if (typeof next !== 'undefined' && !Number.isInteger(next)) {
|
if (typeof next !== 'undefined' && !Number.isInteger(next)) {
|
||||||
output.error('Please provide a number for flag --next');
|
output.error('Please provide a number for flag `--next`');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof count !== 'undefined' && !Number.isInteger(next)) {
|
||||||
|
output.error('Please provide a number for flag `--count`');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.spinner('Fetching teams');
|
output.spinner('Fetching teams');
|
||||||
const { teams: list, pagination } = await teams.ls({
|
const { teams, pagination } = await getTeams(client, {
|
||||||
next,
|
next,
|
||||||
apiVersion: 2,
|
apiVersion: 2,
|
||||||
});
|
});
|
||||||
@@ -37,40 +56,31 @@ export default async function list(client, argv, teams) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (accountIsCurrent) {
|
if (accountIsCurrent) {
|
||||||
currentTeam = {
|
currentTeam = user.id;
|
||||||
slug: user.username || user.email,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const teamList = list.map(({ slug, name }) => ({
|
const teamList = teams.map(({ id, slug, name }) => ({
|
||||||
|
id,
|
||||||
name,
|
name,
|
||||||
value: slug,
|
value: slug,
|
||||||
current: slug === currentTeam.slug ? chars.tick : '',
|
current: id === currentTeam ? chars.tick : '',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
teamList.unshift({
|
teamList.unshift({
|
||||||
|
id: user.id,
|
||||||
name: user.email,
|
name: user.email,
|
||||||
value: user.username || user.email,
|
value: user.username || user.email,
|
||||||
current: (accountIsCurrent && chars.tick) || '',
|
current: accountIsCurrent ? chars.tick : '',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Let's bring the current team to the beginning of the list
|
// Bring the current Team to the beginning of the list
|
||||||
if (!accountIsCurrent) {
|
if (!accountIsCurrent) {
|
||||||
const index = teamList.findIndex(
|
const index = teamList.findIndex(choice => choice.id === currentTeam);
|
||||||
choice => choice.value === currentTeam.slug
|
|
||||||
);
|
|
||||||
const choice = teamList.splice(index, 1)[0];
|
const choice = teamList.splice(index, 1)[0];
|
||||||
teamList.unshift(choice);
|
teamList.unshift(choice);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Printing
|
// Printing
|
||||||
const count = teamList.length;
|
|
||||||
if (!count) {
|
|
||||||
// Maybe should not happen
|
|
||||||
output.error(`No teams found`);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
output.stopSpinner();
|
output.stopSpinner();
|
||||||
console.log(); // empty line
|
console.log(); // empty line
|
||||||
|
|
||||||
@@ -80,7 +90,7 @@ export default async function list(client, argv, teams) {
|
|||||||
[1, 5]
|
[1, 5]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (pagination && pagination.count === 20) {
|
if (pagination?.count === 20) {
|
||||||
const prefixedArgs = getPrefixedFlags(argv);
|
const prefixedArgs = getPrefixedFlags(argv);
|
||||||
const flags = getCommandFlags(prefixedArgs, ['_', '--next', '-N', '-d']);
|
const flags = getCommandFlags(prefixedArgs, ['_', '--next', '-N', '-d']);
|
||||||
const nextCmd = `${getPkgName()} teams ls${flags} --next ${
|
const nextCmd = `${getPkgName()} teams ls${flags} --next ${
|
||||||
@@ -89,4 +99,6 @@ export default async function list(client, argv, teams) {
|
|||||||
console.log(); // empty line
|
console.log(); // empty line
|
||||||
output.log(`To display the next page run ${cmd(nextCmd)}`);
|
output.log(`To display the next page run ${cmd(nextCmd)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ import chalk from 'chalk';
|
|||||||
import Client from '../../util/client';
|
import Client from '../../util/client';
|
||||||
import { emoji } from '../../util/emoji';
|
import { emoji } from '../../util/emoji';
|
||||||
import getUser from '../../util/get-user';
|
import getUser from '../../util/get-user';
|
||||||
import getTeams from '../../util/get-teams';
|
import getTeams from '../../util/teams/get-teams';
|
||||||
import listInput from '../../util/input/list';
|
import listInput from '../../util/input/list';
|
||||||
import { Team, GlobalConfig } from '../../types';
|
import { Team, GlobalConfig } from '../../types';
|
||||||
import { writeToConfigFile } from '../../util/config/files';
|
import { writeToConfigFile } from '../../util/config/files';
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import logo from '../util/output/logo';
|
|||||||
import getScope from '../util/get-scope';
|
import getScope from '../util/get-scope';
|
||||||
import { getPkgName } from '../util/pkg-name';
|
import { getPkgName } from '../util/pkg-name';
|
||||||
import getArgs from '../util/get-args';
|
import getArgs from '../util/get-args';
|
||||||
import handleError from '../util/handle-error';
|
|
||||||
import Client from '../util/client';
|
import Client from '../util/client';
|
||||||
|
|
||||||
const help = () => {
|
const help = () => {
|
||||||
@@ -32,16 +31,9 @@ const help = () => {
|
|||||||
`);
|
`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async (client: Client) => {
|
export default async (client: Client): Promise<number> => {
|
||||||
const { output } = client;
|
const { output } = client;
|
||||||
let argv;
|
const argv = getArgs(client.argv.slice(2), {});
|
||||||
try {
|
|
||||||
argv = getArgs(client.argv.slice(2), {});
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
argv._ = argv._.slice(1);
|
argv._ = argv._.slice(1);
|
||||||
|
|
||||||
if (argv['--help'] || argv._[0] === 'help') {
|
if (argv['--help'] || argv._[0] === 'help') {
|
||||||
@@ -62,9 +54,13 @@ export default async (client: Client) => {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.stdout.isTTY) {
|
if (output.isTTY) {
|
||||||
process.stdout.write('> ');
|
output.log(contextName);
|
||||||
|
} else {
|
||||||
|
// If stdout is not a TTY, then only print the username
|
||||||
|
// to support piping the output to another file / exe
|
||||||
|
output.print(`${contextName}\n`, { w: process.stdout });
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(contextName);
|
return 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,10 +20,9 @@ import epipebomb from 'epipebomb';
|
|||||||
import updateNotifier from 'update-notifier';
|
import updateNotifier from 'update-notifier';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { NowBuildError } from '@vercel/build-utils';
|
|
||||||
import hp from './util/humanize-path';
|
import hp from './util/humanize-path';
|
||||||
import commands from './commands/index.ts';
|
import commands from './commands';
|
||||||
import pkg from './util/pkg.ts';
|
import pkg from './util/pkg';
|
||||||
import createOutput from './util/output';
|
import createOutput from './util/output';
|
||||||
import cmd from './util/output/cmd';
|
import cmd from './util/output/cmd';
|
||||||
import info from './util/output/info';
|
import info from './util/output/info';
|
||||||
@@ -31,9 +30,9 @@ import error from './util/output/error';
|
|||||||
import param from './util/output/param';
|
import param from './util/output/param';
|
||||||
import highlight from './util/output/highlight';
|
import highlight from './util/output/highlight';
|
||||||
import getArgs from './util/get-args';
|
import getArgs from './util/get-args';
|
||||||
import getUser from './util/get-user.ts';
|
import getUser from './util/get-user';
|
||||||
import Client from './util/client.ts';
|
import getTeams from './util/teams/get-teams';
|
||||||
import NowTeams from './util/teams';
|
import Client from './util/client';
|
||||||
import { handleError } from './util/error';
|
import { handleError } from './util/error';
|
||||||
import reportError from './util/report-error';
|
import reportError from './util/report-error';
|
||||||
import getConfig from './util/get-config';
|
import getConfig from './util/get-config';
|
||||||
@@ -44,13 +43,14 @@ import {
|
|||||||
getDefaultAuthConfig,
|
getDefaultAuthConfig,
|
||||||
} from './util/config/get-default';
|
} from './util/config/get-default';
|
||||||
import * as ERRORS from './util/errors-ts';
|
import * as ERRORS from './util/errors-ts';
|
||||||
import { NowError } from './util/now-error';
|
import { APIError } from './util/errors-ts';
|
||||||
import { APIError } from './util/errors-ts.ts';
|
import { SENTRY_DSN } from './util/constants';
|
||||||
import { SENTRY_DSN } from './util/constants.ts';
|
|
||||||
import getUpdateCommand from './util/get-update-command';
|
import getUpdateCommand from './util/get-update-command';
|
||||||
import { metrics, shouldCollectMetrics } from './util/metrics.ts';
|
import { metrics, shouldCollectMetrics } from './util/metrics';
|
||||||
import { getCommandName, getTitleName } from './util/pkg-name.ts';
|
import { getCommandName, getTitleName } from './util/pkg-name';
|
||||||
import doLoginPrompt from './util/login/prompt.ts';
|
import doLoginPrompt from './util/login/prompt';
|
||||||
|
import { GlobalConfig } from './types';
|
||||||
|
import { VercelConfig } from '@vercel/client';
|
||||||
|
|
||||||
const isCanary = pkg.version.includes('canary');
|
const isCanary = pkg.version.includes('canary');
|
||||||
|
|
||||||
@@ -77,8 +77,8 @@ Sentry.init({
|
|||||||
environment: isCanary ? 'canary' : 'stable',
|
environment: isCanary ? 'canary' : 'stable',
|
||||||
});
|
});
|
||||||
|
|
||||||
let client;
|
let client: Client;
|
||||||
let debug = () => {};
|
let debug: (s: string) => void = () => {};
|
||||||
let apiUrl = 'https://api.vercel.com';
|
let apiUrl = 'https://api.vercel.com';
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
@@ -108,26 +108,30 @@ const main = async () => {
|
|||||||
debug = output.debug;
|
debug = output.debug;
|
||||||
|
|
||||||
const localConfigPath = argv['--local-config'];
|
const localConfigPath = argv['--local-config'];
|
||||||
const localConfig = await getConfig(output, localConfigPath);
|
let localConfig: VercelConfig | Error | undefined = await getConfig(
|
||||||
|
output,
|
||||||
if (localConfigPath && localConfig instanceof ERRORS.CantFindConfig) {
|
localConfigPath
|
||||||
output.error(
|
);
|
||||||
`Couldn't find a project configuration file at \n ${localConfig.meta.paths.join(
|
|
||||||
' or\n '
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localConfig instanceof ERRORS.CantParseJSONFile) {
|
if (localConfig instanceof ERRORS.CantParseJSONFile) {
|
||||||
output.error(`Couldn't parse JSON file ${localConfig.meta.file}.`);
|
output.error(`Couldn't parse JSON file ${localConfig.meta.file}.`);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (localConfig instanceof ERRORS.CantFindConfig) {
|
||||||
(localConfig instanceof NowError || localConfig instanceof NowBuildError) &&
|
if (localConfigPath) {
|
||||||
!(localConfig instanceof ERRORS.CantFindConfig)
|
output.error(
|
||||||
) {
|
`Couldn't find a project configuration file at \n ${localConfig.meta.paths.join(
|
||||||
|
' or\n '
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
localConfig = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localConfig instanceof Error) {
|
||||||
output.prettyError(localConfig);
|
output.prettyError(localConfig);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -156,18 +160,26 @@ const main = async () => {
|
|||||||
// * a path to deploy (as in: `vercel path/`)
|
// * a path to deploy (as in: `vercel path/`)
|
||||||
// * a subcommand (as in: `vercel ls`)
|
// * a subcommand (as in: `vercel ls`)
|
||||||
const targetOrSubcommand = argv._[2];
|
const targetOrSubcommand = argv._[2];
|
||||||
|
const isBuildOrDev =
|
||||||
|
targetOrSubcommand === 'build' || targetOrSubcommand === 'dev';
|
||||||
|
|
||||||
output.print(
|
if (isBuildOrDev) {
|
||||||
`${chalk.grey(
|
console.log(
|
||||||
`${getTitleName()} CLI ${pkg.version}${
|
`${chalk.grey(
|
||||||
targetOrSubcommand === 'dev' ? ' dev (beta)' : ''
|
`${getTitleName()} CLI ${
|
||||||
}${
|
pkg.version
|
||||||
isCanary || targetOrSubcommand === 'dev'
|
} ${targetOrSubcommand} (beta) — https://vercel.com/feedback`
|
||||||
? ' — https://vercel.com/feedback'
|
)}`
|
||||||
: ''
|
);
|
||||||
}`
|
} else {
|
||||||
)}\n`
|
output.print(
|
||||||
);
|
`${chalk.grey(
|
||||||
|
`${getTitleName()} CLI ${pkg.version}${
|
||||||
|
isCanary ? ' — https://vercel.com/feedback' : ''
|
||||||
|
}`
|
||||||
|
)}\n`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle `--version` directly
|
// Handle `--version` directly
|
||||||
if (!targetOrSubcommand && argv['--version']) {
|
if (!targetOrSubcommand && argv['--version']) {
|
||||||
@@ -207,7 +219,7 @@ const main = async () => {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let config;
|
let config: GlobalConfig | null = null;
|
||||||
|
|
||||||
if (configExists) {
|
if (configExists) {
|
||||||
try {
|
try {
|
||||||
@@ -229,8 +241,11 @@ const main = async () => {
|
|||||||
// multiple providers. In that case, we really
|
// multiple providers. In that case, we really
|
||||||
// need to migrate.
|
// need to migrate.
|
||||||
if (
|
if (
|
||||||
|
// @ts-ignore
|
||||||
config.sh ||
|
config.sh ||
|
||||||
|
// @ts-ignore
|
||||||
config.user ||
|
config.user ||
|
||||||
|
// @ts-ignore
|
||||||
typeof config.user === 'object' ||
|
typeof config.user === 'object' ||
|
||||||
typeof config.currentTeam === 'object'
|
typeof config.currentTeam === 'object'
|
||||||
) {
|
) {
|
||||||
@@ -279,7 +294,14 @@ const main = async () => {
|
|||||||
|
|
||||||
let authConfig = null;
|
let authConfig = null;
|
||||||
|
|
||||||
const subcommandsWithoutToken = ['login', 'logout', 'help', 'init', 'update'];
|
const subcommandsWithoutToken = [
|
||||||
|
'login',
|
||||||
|
'logout',
|
||||||
|
'help',
|
||||||
|
'init',
|
||||||
|
'update',
|
||||||
|
'build',
|
||||||
|
];
|
||||||
|
|
||||||
if (authConfigExists) {
|
if (authConfigExists) {
|
||||||
try {
|
try {
|
||||||
@@ -300,6 +322,7 @@ const main = async () => {
|
|||||||
// This is from when Vercel CLI supported
|
// This is from when Vercel CLI supported
|
||||||
// multiple providers. In that case, we really
|
// multiple providers. In that case, we really
|
||||||
// need to migrate.
|
// need to migrate.
|
||||||
|
// @ts-ignore
|
||||||
if (authConfig.credentials) {
|
if (authConfig.credentials) {
|
||||||
authConfigExists = false;
|
authConfigExists = false;
|
||||||
}
|
}
|
||||||
@@ -346,6 +369,11 @@ const main = async () => {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
output.error(`Vercel global config was not loaded.`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Shared API `Client` instance for all sub-commands to utilize
|
// Shared API `Client` instance for all sub-commands to utilize
|
||||||
client = new Client({
|
client = new Client({
|
||||||
apiUrl,
|
apiUrl,
|
||||||
@@ -380,24 +408,37 @@ const main = async () => {
|
|||||||
} else if (commands.has(singular)) {
|
} else if (commands.has(singular)) {
|
||||||
alternative = singular;
|
alternative = singular;
|
||||||
}
|
}
|
||||||
console.error(
|
if (targetOrSubcommand === 'build') {
|
||||||
error(
|
output.note(
|
||||||
`The supplied argument ${param(targetOrSubcommand)} is ambiguous.` +
|
`If you wish to deploy the ${fileType} ${param(
|
||||||
`\nIf you wish to deploy the ${fileType} ${param(
|
targetOrSubcommand
|
||||||
targetOrSubcommand
|
)}, run ${getCommandName('deploy build')}.` +
|
||||||
)}, first run "cd ${targetOrSubcommand}". ` +
|
|
||||||
(alternative
|
(alternative
|
||||||
? `\nIf you wish to use the subcommand ${param(
|
? `\nIf you wish to use the subcommand ${param(
|
||||||
targetOrSubcommand
|
targetOrSubcommand
|
||||||
)}, use ${param(alternative)} instead.`
|
)}, use ${param(alternative)} instead.`
|
||||||
: '')
|
: '')
|
||||||
)
|
);
|
||||||
);
|
} else {
|
||||||
return 1;
|
console.error(
|
||||||
|
error(
|
||||||
|
`The supplied argument ${param(targetOrSubcommand)} is ambiguous.` +
|
||||||
|
`\nIf you wish to deploy the ${fileType} ${param(
|
||||||
|
targetOrSubcommand
|
||||||
|
)}, first run "cd ${targetOrSubcommand}". ` +
|
||||||
|
(alternative
|
||||||
|
? `\nIf you wish to use the subcommand ${param(
|
||||||
|
targetOrSubcommand
|
||||||
|
)}, use ${param(alternative)} instead.`
|
||||||
|
: '')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subcommandExists) {
|
if (subcommandExists) {
|
||||||
debug('user supplied known subcommand', targetOrSubcommand);
|
debug(`user supplied known subcommand: "${targetOrSubcommand}"`);
|
||||||
subcommand = targetOrSubcommand;
|
subcommand = targetOrSubcommand;
|
||||||
} else {
|
} else {
|
||||||
debug('user supplied a possible target for deployment');
|
debug('user supplied a possible target for deployment');
|
||||||
@@ -430,12 +471,16 @@ const main = async () => {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.teamId) {
|
||||||
|
// SSO login, so set the current scope to the appropriate Team
|
||||||
|
client.config.currentTeam = result.teamId;
|
||||||
|
} else {
|
||||||
|
delete client.config.currentTeam;
|
||||||
|
}
|
||||||
|
|
||||||
// When `result` is a string it's the user's authentication token.
|
// When `result` is a string it's the user's authentication token.
|
||||||
// It needs to be saved to the configuration file.
|
// It needs to be saved to the configuration file.
|
||||||
client.authConfig.token = result;
|
client.authConfig.token = result.token;
|
||||||
|
|
||||||
// New user, so we can't keep the team
|
|
||||||
delete client.config.currentTeam;
|
|
||||||
|
|
||||||
configFiles.writeToAuthConfigFile(client.authConfig);
|
configFiles.writeToAuthConfigFile(client.authConfig);
|
||||||
configFiles.writeToConfigFile(client.config);
|
configFiles.writeToConfigFile(client.config);
|
||||||
@@ -453,14 +498,12 @@ const main = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof argv['--token'] === 'string' && subcommand === 'switch') {
|
if (typeof argv['--token'] === 'string' && subcommand === 'switch') {
|
||||||
console.error(
|
output.prettyError({
|
||||||
error({
|
message: `This command doesn't work with ${param(
|
||||||
message: `This command doesn't work with ${param(
|
'--token'
|
||||||
'--token'
|
)}. Please use ${param('--scope')}.`,
|
||||||
)}. Please use ${param('--scope')}.`,
|
link: 'https://err.sh/vercel/no-token-allowed',
|
||||||
slug: 'no-token-allowed',
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -469,12 +512,10 @@ const main = async () => {
|
|||||||
const token = argv['--token'];
|
const token = argv['--token'];
|
||||||
|
|
||||||
if (token.length === 0) {
|
if (token.length === 0) {
|
||||||
console.error(
|
output.prettyError({
|
||||||
error({
|
message: `You defined ${param('--token')}, but it's missing a value`,
|
||||||
message: `You defined ${param('--token')}, but it's missing a value`,
|
link: 'https://err.sh/vercel/missing-token-value',
|
||||||
slug: 'missing-token-value',
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -482,16 +523,14 @@ const main = async () => {
|
|||||||
const invalid = token.match(/(\W)/g);
|
const invalid = token.match(/(\W)/g);
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
const notContain = Array.from(new Set(invalid)).sort();
|
const notContain = Array.from(new Set(invalid)).sort();
|
||||||
console.error(
|
output.prettyError({
|
||||||
error({
|
message: `You defined ${param(
|
||||||
message: `You defined ${param(
|
'--token'
|
||||||
'--token'
|
)}, but its contents are invalid. Must not contain: ${notContain
|
||||||
)}, but its contents are invalid. Must not contain: ${notContain
|
.map(c => JSON.stringify(c))
|
||||||
.map(c => JSON.stringify(c))
|
.join(', ')}`,
|
||||||
.join(', ')}`,
|
link: 'https://err.sh/vercel/invalid-token-value',
|
||||||
slug: 'invalid-token-value',
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -512,13 +551,8 @@ const main = async () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
|
||||||
authConfig: { token },
|
|
||||||
} = client;
|
|
||||||
|
|
||||||
let scope = argv['--scope'] || argv['--team'] || localConfig.scope;
|
|
||||||
|
|
||||||
const targetCommand = commands.get(subcommand);
|
const targetCommand = commands.get(subcommand);
|
||||||
|
const scope = argv['--scope'] || argv['--team'] || localConfig?.scope;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof scope === 'string' &&
|
typeof scope === 'string' &&
|
||||||
@@ -532,12 +566,10 @@ const main = async () => {
|
|||||||
user = await getUser(client);
|
user = await getUser(client);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'NOT_AUTHORIZED') {
|
if (err.code === 'NOT_AUTHORIZED') {
|
||||||
console.error(
|
output.prettyError({
|
||||||
error({
|
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',
|
||||||
slug: 'scope-not-accessible',
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -546,22 +578,19 @@ const main = async () => {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.uid === scope || user.email === scope || user.username === scope) {
|
if (user.id === scope || user.email === scope || user.username === scope) {
|
||||||
delete client.config.currentTeam;
|
delete client.config.currentTeam;
|
||||||
} else {
|
} else {
|
||||||
let list = [];
|
let teams = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const teams = new NowTeams({ apiUrl, token, debug: isDebugging });
|
teams = await getTeams(client);
|
||||||
list = (await teams.ls()).teams;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'not_authorized') {
|
if (err.code === 'not_authorized') {
|
||||||
console.error(
|
output.prettyError({
|
||||||
error({
|
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',
|
||||||
slug: 'scope-not-accessible',
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -571,15 +600,13 @@ const main = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const related =
|
const related =
|
||||||
list && list.find(item => item.id === scope || item.slug === scope);
|
teams && teams.find(team => team.id === scope || team.slug === scope);
|
||||||
|
|
||||||
if (!related) {
|
if (!related) {
|
||||||
console.error(
|
output.prettyError({
|
||||||
error({
|
message: 'The specified scope does not exist',
|
||||||
message: 'The specified scope does not exist',
|
link: 'https://err.sh/vercel/scope-not-existent',
|
||||||
slug: 'scope-not-existent',
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -588,20 +615,99 @@ const main = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetCommand) {
|
|
||||||
const sub = param(subcommand);
|
|
||||||
console.error(error(`The ${sub} subcommand does not exist`));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const metric = metrics();
|
const metric = metrics();
|
||||||
let exitCode;
|
let exitCode;
|
||||||
const eventCategory = 'Exit Code';
|
const eventCategory = 'Exit Code';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const full = require(`./commands/${targetCommand}`).default;
|
let func: any;
|
||||||
exitCode = await full(client);
|
switch (targetCommand) {
|
||||||
|
case 'alias':
|
||||||
|
func = await import('./commands/alias');
|
||||||
|
break;
|
||||||
|
case 'billing':
|
||||||
|
func = await import('./commands/billing');
|
||||||
|
break;
|
||||||
|
case 'build':
|
||||||
|
func = await import('./commands/build');
|
||||||
|
break;
|
||||||
|
case 'certs':
|
||||||
|
func = await import('./commands/certs');
|
||||||
|
break;
|
||||||
|
case 'deploy':
|
||||||
|
func = await import('./commands/deploy');
|
||||||
|
break;
|
||||||
|
case 'dev':
|
||||||
|
func = await import('./commands/dev');
|
||||||
|
break;
|
||||||
|
case 'dns':
|
||||||
|
func = await import('./commands/dns');
|
||||||
|
break;
|
||||||
|
case 'domains':
|
||||||
|
func = await import('./commands/domains');
|
||||||
|
break;
|
||||||
|
case 'env':
|
||||||
|
func = await import('./commands/env');
|
||||||
|
break;
|
||||||
|
case 'init':
|
||||||
|
func = await import('./commands/init');
|
||||||
|
break;
|
||||||
|
case 'inspect':
|
||||||
|
func = await import('./commands/inspect');
|
||||||
|
break;
|
||||||
|
case 'link':
|
||||||
|
func = await import('./commands/link');
|
||||||
|
break;
|
||||||
|
case 'list':
|
||||||
|
func = await import('./commands/list');
|
||||||
|
break;
|
||||||
|
case 'logs':
|
||||||
|
func = await import('./commands/logs');
|
||||||
|
break;
|
||||||
|
case 'login':
|
||||||
|
func = await import('./commands/login');
|
||||||
|
break;
|
||||||
|
case 'logout':
|
||||||
|
func = await import('./commands/logout');
|
||||||
|
break;
|
||||||
|
case 'projects':
|
||||||
|
func = await import('./commands/projects');
|
||||||
|
break;
|
||||||
|
case 'pull':
|
||||||
|
func = await import('./commands/pull');
|
||||||
|
break;
|
||||||
|
case 'remove':
|
||||||
|
func = await import('./commands/remove');
|
||||||
|
break;
|
||||||
|
case 'secrets':
|
||||||
|
func = await import('./commands/secrets');
|
||||||
|
break;
|
||||||
|
case 'teams':
|
||||||
|
func = await import('./commands/teams');
|
||||||
|
break;
|
||||||
|
case 'update':
|
||||||
|
func = await import('./commands/update');
|
||||||
|
break;
|
||||||
|
case 'whoami':
|
||||||
|
func = await import('./commands/whoami');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
func = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!func || !targetCommand) {
|
||||||
|
const sub = param(subcommand);
|
||||||
|
output.error(`The ${sub} subcommand does not exist`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (func.default) {
|
||||||
|
func = func.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode = await func(client);
|
||||||
const end = Date.now() - start;
|
const end = Date.now() - start;
|
||||||
|
|
||||||
if (shouldCollectMetrics) {
|
if (shouldCollectMetrics) {
|
||||||
@@ -615,7 +721,7 @@ const main = async () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ENOTFOUND') {
|
if (err.code === 'ENOTFOUND') {
|
||||||
// Error message will look like the following:
|
// Error message will look like the following:
|
||||||
// "request to https://api.vercel.com/www/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 || '');
|
||||||
if (matches && matches[1]) {
|
if (matches && matches[1]) {
|
||||||
const hostname = matches[1];
|
const hostname = matches[1];
|
||||||
@@ -674,7 +780,7 @@ const main = async () => {
|
|||||||
return exitCode;
|
return exitCode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRejection = async err => {
|
const handleRejection = async (err: any) => {
|
||||||
debug('handling rejection');
|
debug('handling rejection');
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -691,7 +797,7 @@ const handleRejection = async err => {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUnexpected = async err => {
|
const handleUnexpected = async (err: Error) => {
|
||||||
const { message } = err;
|
const { message } = err;
|
||||||
|
|
||||||
// We do not want to render errors about Sentry not being reachable
|
// We do not want to render errors about Sentry not being reachable
|
||||||
@@ -700,9 +806,8 @@ const handleUnexpected = async err => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await reportError(Sentry, client, err);
|
|
||||||
|
|
||||||
console.error(error(`An unexpected error occurred!\n${err.stack}`));
|
console.error(error(`An unexpected error occurred!\n${err.stack}`));
|
||||||
|
await reportError(Sentry, client, err);
|
||||||
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
};
|
};
|
||||||
@@ -713,6 +818,7 @@ process.on('uncaughtException', handleUnexpected);
|
|||||||
main()
|
main()
|
||||||
.then(exitCode => {
|
.then(exitCode => {
|
||||||
process.exitCode = exitCode;
|
process.exitCode = exitCode;
|
||||||
|
// @ts-ignore - "nowExit" is a non-standard event name
|
||||||
process.emit('nowExit');
|
process.emit('nowExit');
|
||||||
})
|
})
|
||||||
.catch(handleUnexpected);
|
.catch(handleUnexpected);
|
||||||
@@ -16,11 +16,13 @@ export interface JSONObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthConfig {
|
export interface AuthConfig {
|
||||||
|
_?: string;
|
||||||
token?: string;
|
token?: string;
|
||||||
skipWrite?: boolean;
|
skipWrite?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GlobalConfig {
|
export interface GlobalConfig {
|
||||||
|
_?: string;
|
||||||
currentTeam?: string;
|
currentTeam?: string;
|
||||||
includeScheme?: string;
|
includeScheme?: string;
|
||||||
collectMetrics?: boolean;
|
collectMetrics?: boolean;
|
||||||
@@ -43,25 +45,12 @@ type Billing = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
uid: string;
|
id: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
bio?: string;
|
createdAt: number;
|
||||||
date: number;
|
|
||||||
email: string;
|
email: string;
|
||||||
username: string;
|
username: string;
|
||||||
website?: string;
|
|
||||||
billingChecked: boolean;
|
|
||||||
billing: Billing;
|
billing: Billing;
|
||||||
github?: {
|
|
||||||
email: string;
|
|
||||||
installation: {
|
|
||||||
id: string;
|
|
||||||
login: string;
|
|
||||||
loginType: string;
|
|
||||||
};
|
|
||||||
login: string;
|
|
||||||
updatedAt: number;
|
|
||||||
};
|
|
||||||
name?: string;
|
name?: string;
|
||||||
limited?: boolean;
|
limited?: boolean;
|
||||||
};
|
};
|
||||||
@@ -306,3 +295,141 @@ export interface Token {
|
|||||||
createdAt: number;
|
createdAt: number;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object representing a Build on Vercel
|
||||||
|
*/
|
||||||
|
export interface Build {
|
||||||
|
/**
|
||||||
|
* The unique identifier of the Build
|
||||||
|
* @example "bld_q5fj68jh7eewfe8"
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unique identifier of the deployment
|
||||||
|
* @example "dpl_BRGyoU2Jzzwx7myBnqv3xjRDD2GnHTwUWyFybnrUvjDD"
|
||||||
|
*/
|
||||||
|
deploymentId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entrypoint of the deployment
|
||||||
|
* @example "api/index.js"
|
||||||
|
*/
|
||||||
|
entrypoint: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state of the deployment depending on the process of deploying,
|
||||||
|
* or if it is ready or in an error state
|
||||||
|
* @example "READY"
|
||||||
|
*/
|
||||||
|
readyState:
|
||||||
|
| 'INITIALIZING'
|
||||||
|
| 'BUILDING'
|
||||||
|
| 'UPLOADING'
|
||||||
|
| 'DEPLOYING'
|
||||||
|
| 'READY'
|
||||||
|
| 'ARCHIVED'
|
||||||
|
| 'ERROR'
|
||||||
|
| 'QUEUED'
|
||||||
|
| 'CANCELED';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time at which the Build state was last modified
|
||||||
|
* @example 1567024758130
|
||||||
|
*/
|
||||||
|
readyStateAt?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time at which the Build was scheduled to be built
|
||||||
|
* @example 1567024756543
|
||||||
|
*/
|
||||||
|
scheduledAt?: number | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time at which the Build was created
|
||||||
|
* @example 1567071524208
|
||||||
|
*/
|
||||||
|
createdAt?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time at which the Build was deployed
|
||||||
|
* @example 1567071598563
|
||||||
|
*/
|
||||||
|
deployedAt?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The region where the Build was first created
|
||||||
|
* @example "sfo1"
|
||||||
|
*/
|
||||||
|
createdIn?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Runtime the Build used to generate the output
|
||||||
|
* @example "@vercel/node"
|
||||||
|
*/
|
||||||
|
use?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that contains the Build's configuration
|
||||||
|
* @example {"zeroConfig": true}
|
||||||
|
*/
|
||||||
|
config?: {
|
||||||
|
distDir?: string | undefined;
|
||||||
|
forceBuildIn?: string | undefined;
|
||||||
|
reuseWorkPathFrom?: string | undefined;
|
||||||
|
zeroConfig?: boolean | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of outputs for the Build that can be either Serverless Functions or static files
|
||||||
|
*/
|
||||||
|
output: BuildOutput[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the Build uses the `@vercel/static` Runtime, it contains a hashed string of all outputs
|
||||||
|
* @example null
|
||||||
|
*/
|
||||||
|
fingerprint?: string | null;
|
||||||
|
|
||||||
|
copiedFrom?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BuildOutput {
|
||||||
|
/**
|
||||||
|
* The type of the output
|
||||||
|
*/
|
||||||
|
type?: 'lambda' | 'file';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The absolute path of the file or Serverless Function
|
||||||
|
*/
|
||||||
|
path: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SHA1 of the file
|
||||||
|
*/
|
||||||
|
digest: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The POSIX file permissions
|
||||||
|
*/
|
||||||
|
mode: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of the file in bytes
|
||||||
|
*/
|
||||||
|
size?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the output is a Serverless Function, an object
|
||||||
|
* containing the name, location and memory size of the function
|
||||||
|
*/
|
||||||
|
lambda?: {
|
||||||
|
functionName: string;
|
||||||
|
deployedTo: string[];
|
||||||
|
memorySize?: number;
|
||||||
|
timeout?: number;
|
||||||
|
layers?: string[];
|
||||||
|
} | null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ async function getAppLastDeployment(
|
|||||||
const deployments = await getDeploymentsByAppName(client, appName);
|
const deployments = await getDeploymentsByAppName(client, appName);
|
||||||
const deploymentItem = deployments
|
const deploymentItem = deployments
|
||||||
.sort((a, b) => b.created - a.created)
|
.sort((a, b) => b.created - a.created)
|
||||||
.filter(dep => dep.state === 'READY' && dep.creator.uid === user.uid)[0];
|
.filter(dep => dep.state === 'READY' && dep.creator.uid === user.id)[0];
|
||||||
|
|
||||||
// Try to fetch deployment details
|
// Try to fetch deployment details
|
||||||
if (deploymentItem) {
|
if (deploymentItem) {
|
||||||
@@ -35,7 +35,7 @@ export async function getDeploymentForAlias(
|
|||||||
localConfigPath: string | undefined,
|
localConfigPath: string | undefined,
|
||||||
user: User,
|
user: User,
|
||||||
contextName: string,
|
contextName: string,
|
||||||
localConfig: VercelConfig
|
localConfig?: VercelConfig
|
||||||
) {
|
) {
|
||||||
output.spinner(`Fetching deployment to alias in ${chalk.bold(contextName)}`);
|
output.spinner(`Fetching deployment to alias in ${chalk.bold(contextName)}`);
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ export async function getDeploymentForAlias(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const appName =
|
const appName =
|
||||||
(localConfig && localConfig.name) ||
|
localConfig?.name ||
|
||||||
path.basename(path.resolve(process.cwd(), localConfigPath || ''));
|
path.basename(path.resolve(process.cwd(), localConfigPath || ''));
|
||||||
|
|
||||||
if (!appName) {
|
if (!appName) {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
export const isReady = ({ readyState }) => readyState === 'READY';
|
|
||||||
export const isFailed = ({ readyState }) =>
|
|
||||||
readyState.endsWith('_ERROR') || readyState === 'ERROR';
|
|
||||||
export const isDone = ({ readyState }) =>
|
|
||||||
isReady({ readyState }) || isFailed({ readyState });
|
|
||||||
7
packages/cli/src/util/build-state.ts
Normal file
7
packages/cli/src/util/build-state.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Build } from '../types';
|
||||||
|
|
||||||
|
export const isReady = ({ readyState }: Pick<Build, 'readyState'>) =>
|
||||||
|
readyState === 'READY';
|
||||||
|
|
||||||
|
export const isFailed = ({ readyState }: Pick<Build, 'readyState'>) =>
|
||||||
|
readyState.endsWith('_ERROR') || readyState === 'ERROR';
|
||||||
@@ -34,10 +34,10 @@ export interface ClientOptions {
|
|||||||
authConfig: AuthConfig;
|
authConfig: AuthConfig;
|
||||||
output: Output;
|
output: Output;
|
||||||
config: GlobalConfig;
|
config: GlobalConfig;
|
||||||
localConfig: VercelConfig;
|
localConfig?: VercelConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isJSONObject = (v: any): v is JSONObject => {
|
export const isJSONObject = (v: any): v is JSONObject => {
|
||||||
return v && typeof v == 'object' && v.constructor === Object;
|
return v && typeof v == 'object' && v.constructor === Object;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ export default class Client extends EventEmitter {
|
|||||||
authConfig: AuthConfig;
|
authConfig: AuthConfig;
|
||||||
output: Output;
|
output: Output;
|
||||||
config: GlobalConfig;
|
config: GlobalConfig;
|
||||||
localConfig: VercelConfig;
|
localConfig?: VercelConfig;
|
||||||
private requestIdCounter: number;
|
private requestIdCounter: number;
|
||||||
|
|
||||||
constructor(opts: ClientOptions) {
|
constructor(opts: ClientOptions) {
|
||||||
@@ -69,7 +69,7 @@ export default class Client extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_fetch(_url: string, opts: FetchOptions = {}) {
|
private _fetch(_url: string, opts: FetchOptions = {}) {
|
||||||
const parsedUrl = parseUrl(_url, true);
|
const parsedUrl = parseUrl(_url, true);
|
||||||
const apiUrl = parsedUrl.host
|
const apiUrl = parsedUrl.host
|
||||||
? `${parsedUrl.protocol}//${parsedUrl.host}`
|
? `${parsedUrl.protocol}//${parsedUrl.host}`
|
||||||
@@ -100,7 +100,7 @@ export default class Client extends EventEmitter {
|
|||||||
let body;
|
let body;
|
||||||
if (isJSONObject(opts.body)) {
|
if (isJSONObject(opts.body)) {
|
||||||
body = JSON.stringify(opts.body);
|
body = JSON.stringify(opts.body);
|
||||||
headers.set('content-type', 'application/json; charset=utf8');
|
headers.set('content-type', 'application/json; charset=utf-8');
|
||||||
} else {
|
} else {
|
||||||
body = opts.body;
|
body = opts.body;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ export function getAuthConfigFilePath() {
|
|||||||
|
|
||||||
export function readLocalConfig(
|
export function readLocalConfig(
|
||||||
prefix: string = process.cwd()
|
prefix: string = process.cwd()
|
||||||
): VercelConfig | null {
|
): VercelConfig | undefined {
|
||||||
let config: VercelConfig | null = null;
|
let config: VercelConfig | undefined = undefined;
|
||||||
let target = '';
|
let target = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -116,7 +116,7 @@ export function readLocalConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!target) {
|
if (!target) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -134,7 +134,7 @@ export function readLocalConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
config[fileNameSymbol] = basename(target);
|
config[fileNameSymbol] = basename(target);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
export const getDefaultConfig = async existingCopy => {
|
import { AuthConfig, GlobalConfig } from '../../types';
|
||||||
|
|
||||||
|
export const getDefaultConfig = async (existingCopy?: GlobalConfig | null) => {
|
||||||
let migrated = false;
|
let migrated = false;
|
||||||
|
|
||||||
const config = {
|
const config: GlobalConfig = {
|
||||||
_:
|
_: 'This is your Vercel config file. For more information see the global configuration documentation: https://vercel.com/docs/configuration#global',
|
||||||
'This is your Vercel config file. For more information see the global configuration documentation: https://vercel.com/docs/configuration#global',
|
|
||||||
collectMetrics: true,
|
collectMetrics: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -16,44 +17,33 @@ export const getDefaultConfig = async existingCopy => {
|
|||||||
'collectMetrics',
|
'collectMetrics',
|
||||||
'api',
|
'api',
|
||||||
// This is deleted later in the code
|
// This is deleted later in the code
|
||||||
'shownTips',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const existing = Object.assign({}, existingCopy);
|
const existing = Object.assign({}, existingCopy);
|
||||||
|
// @ts-ignore
|
||||||
const sh = Object.assign({}, existing.sh || {});
|
const sh = Object.assign({}, existing.sh || {});
|
||||||
|
|
||||||
Object.assign(config, existing, sh);
|
Object.assign(config, existing, sh);
|
||||||
|
|
||||||
for (const key of Object.keys(config)) {
|
for (const key of Object.keys(config)) {
|
||||||
if (!keep.includes(key)) {
|
if (!keep.includes(key)) {
|
||||||
|
// @ts-ignore
|
||||||
delete config[key];
|
delete config[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof config.currentTeam === 'object') {
|
if (typeof config.currentTeam === 'object') {
|
||||||
|
// @ts-ignore
|
||||||
config.currentTeam = config.currentTeam.id;
|
config.currentTeam = config.currentTeam.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
if (typeof config.user === 'object') {
|
if (typeof config.user === 'object') {
|
||||||
|
// @ts-ignore
|
||||||
config.user = config.user.uid || config.user.id;
|
config.user = config.user.uid || config.user.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure Now Desktop users don't see any tips
|
|
||||||
// again that they already dismissed
|
|
||||||
if (config.shownTips) {
|
|
||||||
if (config.desktop) {
|
|
||||||
config.desktop.shownTips = config.shownTips;
|
|
||||||
} else {
|
|
||||||
config.desktop = {
|
|
||||||
shownTips: config.shownTips,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up the old property
|
|
||||||
delete config.shownTips;
|
|
||||||
}
|
|
||||||
|
|
||||||
migrated = true;
|
migrated = true;
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
}
|
}
|
||||||
@@ -61,16 +51,16 @@ export const getDefaultConfig = async existingCopy => {
|
|||||||
return { config, migrated };
|
return { config, migrated };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDefaultAuthConfig = async existing => {
|
export const getDefaultAuthConfig = async (existing?: AuthConfig | null) => {
|
||||||
let migrated = false;
|
let migrated = false;
|
||||||
|
|
||||||
const config = {
|
const config: AuthConfig = {
|
||||||
_:
|
_: 'This is your Vercel credentials file. DO NOT SHARE! More: https://vercel.com/docs/configuration#global',
|
||||||
'This is your Vercel credentials file. DO NOT SHARE! More: https://vercel.com/docs/configuration#global',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
try {
|
try {
|
||||||
|
// @ts-ignore
|
||||||
const sh = existing.credentials.find(item => item.provider === 'sh');
|
const sh = existing.credentials.find(item => item.provider === 'sh');
|
||||||
|
|
||||||
if (sh) {
|
if (sh) {
|
||||||
@@ -18,12 +18,8 @@ export const isDirectory = (path: string): boolean => {
|
|||||||
const getGlobalPathConfig = (): string => {
|
const getGlobalPathConfig = (): string => {
|
||||||
let customPath: string | undefined;
|
let customPath: string | undefined;
|
||||||
|
|
||||||
try {
|
const argv = getArgs(process.argv.slice(2), {}, { permissive: true });
|
||||||
const argv = getArgs(process.argv.slice(2), {});
|
customPath = argv['--global-config'];
|
||||||
customPath = argv['--global-config'];
|
|
||||||
} catch (_error) {
|
|
||||||
// args are optional so consume error
|
|
||||||
}
|
|
||||||
|
|
||||||
const vercelDirectories = XDGAppPaths('com.vercel.cli').dataDirs();
|
const vercelDirectories = XDGAppPaths('com.vercel.cli').dataDirs();
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,8 @@ import getArgs from '../../util/get-args';
|
|||||||
export default function getLocalPathConfig(prefix: string) {
|
export default function getLocalPathConfig(prefix: string) {
|
||||||
let customPath: string | undefined;
|
let customPath: string | undefined;
|
||||||
|
|
||||||
try {
|
const argv = getArgs(process.argv.slice(2), {}, { permissive: true });
|
||||||
const argv = getArgs(process.argv.slice(2), {});
|
customPath = argv['--local-config'];
|
||||||
customPath = argv['--local-config'];
|
|
||||||
} catch (_error) {
|
|
||||||
// args are optional so consume error
|
|
||||||
}
|
|
||||||
|
|
||||||
// If `--local-config` flag was specified, then that takes priority
|
// If `--local-config` flag was specified, then that takes priority
|
||||||
if (customPath) {
|
if (customPath) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as ERRORS from '../errors';
|
|||||||
import { NowError } from '../now-error';
|
import { NowError } from '../now-error';
|
||||||
import mapCertError from '../certs/map-cert-error';
|
import mapCertError from '../certs/map-cert-error';
|
||||||
import { Org } from '../../types';
|
import { Org } from '../../types';
|
||||||
import Now from '..';
|
import Now, { CreateOptions } from '..';
|
||||||
import Client from '../client';
|
import Client from '../client';
|
||||||
import { DeploymentError } from '../../../../client/dist';
|
import { DeploymentError } from '../../../../client/dist';
|
||||||
|
|
||||||
@@ -13,8 +13,8 @@ export default async function createDeploy(
|
|||||||
now: Now,
|
now: Now,
|
||||||
contextName: string,
|
contextName: string,
|
||||||
paths: string[],
|
paths: string[],
|
||||||
createArgs: any,
|
createArgs: CreateOptions,
|
||||||
org: Org | null,
|
org: Org,
|
||||||
isSettingUpProject: boolean,
|
isSettingUpProject: boolean,
|
||||||
cwd?: string
|
cwd?: string
|
||||||
): Promise<any | DeploymentError> {
|
): Promise<any | DeploymentError> {
|
||||||
|
|||||||
37
packages/cli/src/util/deploy/get-deployment-checks.ts
Normal file
37
packages/cli/src/util/deploy/get-deployment-checks.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import Client from '../client';
|
||||||
|
|
||||||
|
type CheckStatus = 'registered' | 'running' | 'completed';
|
||||||
|
type CheckConclusion =
|
||||||
|
| 'canceled'
|
||||||
|
| 'failed'
|
||||||
|
| 'neutral'
|
||||||
|
| 'succeeded'
|
||||||
|
| 'skipped'
|
||||||
|
| 'stale';
|
||||||
|
|
||||||
|
export interface DeploymentCheck {
|
||||||
|
id: string;
|
||||||
|
status: CheckStatus;
|
||||||
|
conclusion: CheckConclusion;
|
||||||
|
name: string;
|
||||||
|
startedAt: number;
|
||||||
|
completedAt: number;
|
||||||
|
createdAt: number;
|
||||||
|
updatedAt: number;
|
||||||
|
integrationId: string;
|
||||||
|
rerequestable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeploymentChecksResponse {
|
||||||
|
checks: DeploymentCheck[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDeploymentChecks(
|
||||||
|
client: Client,
|
||||||
|
deploymentId: string
|
||||||
|
) {
|
||||||
|
const checksResponse = await client.fetch<DeploymentChecksResponse>(
|
||||||
|
`/v1/deployments/${encodeURIComponent(deploymentId)}/checks`
|
||||||
|
);
|
||||||
|
return checksResponse;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user