mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-12 04:22:14 +00:00
Compare commits
45 Commits
docusaurus
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9178f14c40 | ||
|
|
9a790f5352 | ||
|
|
33d8be7f8f | ||
|
|
82aa86b786 | ||
|
|
6288d5ef43 | ||
|
|
00a97b7ae2 | ||
|
|
a19fb3aa0f | ||
|
|
93fae76f9c | ||
|
|
e5131d5557 | ||
|
|
930063638c | ||
|
|
7b509ae672 | ||
|
|
711da37771 | ||
|
|
5e50b96994 | ||
|
|
35d2f5950f | ||
|
|
37f969416c | ||
|
|
75630e0982 | ||
|
|
038c228af4 | ||
|
|
a17aaebba3 | ||
|
|
c39ba31b4b | ||
|
|
91414d6f38 | ||
|
|
00bb723db3 | ||
|
|
65a97720b3 | ||
|
|
88eaf6efab | ||
|
|
abf159d5e0 | ||
|
|
a23879b507 | ||
|
|
a713b9170b | ||
|
|
8fd57459a1 | ||
|
|
9a3dc8ed5f | ||
|
|
c46dd8c556 | ||
|
|
e28edc5c93 | ||
|
|
3fc4f64824 | ||
|
|
90e51287e9 | ||
|
|
6d8b10802c | ||
|
|
1dd2ffd895 | ||
|
|
e4d422a2d7 | ||
|
|
bd55ad8212 | ||
|
|
6591dae152 | ||
|
|
a490faa496 | ||
|
|
8fe16f1b0a | ||
|
|
bac19b66af | ||
|
|
b58090a181 | ||
|
|
8c1b707f38 | ||
|
|
4b4caccb3d | ||
|
|
102dc04221 | ||
|
|
8d96a57117 |
2
.github/EXAMPLE_README_TEMPLATE.md
vendored
2
.github/EXAMPLE_README_TEMPLATE.md
vendored
@@ -6,7 +6,7 @@ This directory is a brief example of a [Name](site-link) site that can be deploy
|
||||
|
||||
Deploy your own [Name] project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/example-directory)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/example-directory)
|
||||
|
||||
### How We Created This Example
|
||||
|
||||
|
||||
2
.github/workflows/cancel.yml
vendored
2
.github/workflows/cancel.yml
vendored
@@ -3,7 +3,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
- '!master'
|
||||
- '!main'
|
||||
|
||||
jobs:
|
||||
cancel:
|
||||
|
||||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -3,7 +3,7 @@ name: Publish
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
tags:
|
||||
- '!*'
|
||||
|
||||
|
||||
6
.github/workflows/test-integration-cli.yml
vendored
6
.github/workflows/test-integration-cli.yml
vendored
@@ -3,7 +3,7 @@ name: CLI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
tags:
|
||||
- '!*'
|
||||
pull_request:
|
||||
@@ -29,9 +29,9 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 100
|
||||
- run: git --version
|
||||
- run: git fetch origin master --depth=100
|
||||
- run: git fetch origin main --depth=100
|
||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
||||
- run: git diff origin/master...HEAD --name-only
|
||||
- run: git diff origin/main...HEAD --name-only
|
||||
- run: yarn install --network-timeout 1000000
|
||||
- run: yarn run build
|
||||
- run: yarn test-integration-cli --clean false
|
||||
|
||||
6
.github/workflows/test-integration-dev.yml
vendored
6
.github/workflows/test-integration-dev.yml
vendored
@@ -3,7 +3,7 @@ name: Dev
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
tags:
|
||||
- '!*'
|
||||
pull_request:
|
||||
@@ -29,9 +29,9 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 100
|
||||
- run: git --version
|
||||
- run: git fetch origin master --depth=100
|
||||
- run: git fetch origin main --depth=100
|
||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
||||
- run: git diff origin/master...HEAD --name-only
|
||||
- run: git diff origin/main...HEAD --name-only
|
||||
- name: Install Hugo
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/cli/test/dev/fixtures/08-hugo/
|
||||
|
||||
6
.github/workflows/test-integration-once.yml
vendored
6
.github/workflows/test-integration-once.yml
vendored
@@ -3,7 +3,7 @@ name: E2E
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
tags:
|
||||
- '!*'
|
||||
pull_request:
|
||||
@@ -24,9 +24,9 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 100
|
||||
- run: git --version
|
||||
- run: git fetch origin master --depth=100
|
||||
- run: git fetch origin main --depth=100
|
||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
||||
- run: git diff origin/master...HEAD --name-only
|
||||
- run: git diff origin/main...HEAD --name-only
|
||||
- run: yarn install --network-timeout 1000000
|
||||
- run: yarn run build
|
||||
- run: yarn test-integration-once --clean false
|
||||
|
||||
6
.github/workflows/test-unit.yml
vendored
6
.github/workflows/test-unit.yml
vendored
@@ -3,7 +3,7 @@ name: Unit
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
tags:
|
||||
- '!*'
|
||||
pull_request:
|
||||
@@ -29,9 +29,9 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 100
|
||||
- run: git --version
|
||||
- run: git fetch origin master --depth=100
|
||||
- run: git fetch origin main --depth=100
|
||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
||||
- run: git diff origin/master...HEAD --name-only
|
||||
- run: git diff origin/main...HEAD --name-only
|
||||
- run: yarn install --network-timeout 1000000
|
||||
- run: yarn run build
|
||||
- run: yarn run lint
|
||||
|
||||
1
.husky/.gitignore
vendored
Normal file
1
.husky/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_
|
||||
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn pre-commit
|
||||
@@ -6,7 +6,7 @@
|
||||
<p align="center">Develop. Preview. Ship.</p>
|
||||
</p>
|
||||
|
||||
[](https://github.com/vercel/vercel/actions?workflow=CI)
|
||||
[](https://github.com/vercel/vercel/actions/workflows/test-unit.yml)
|
||||
[](https://github.com/vercel/vercel/discussions)
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -48,8 +48,8 @@ export default withApiHandler(async function (
|
||||
|
||||
const example = segment.slice(0, -ext.length);
|
||||
|
||||
await extract('https://github.com/vercel/vercel/archive/master.zip', TMP_DIR);
|
||||
const directory = `${TMP_DIR}/vercel-master/examples/${example}`;
|
||||
await extract('https://github.com/vercel/vercel/archive/main.zip', TMP_DIR);
|
||||
const directory = `${TMP_DIR}/vercel-main/examples/${example}`;
|
||||
|
||||
if (!isDirectory(directory)) {
|
||||
return notFound(res, `Example '${example}' was not found.`);
|
||||
|
||||
@@ -8,8 +8,8 @@ export default withApiHandler(async function (
|
||||
req: VercelRequest,
|
||||
res: VercelResponse
|
||||
) {
|
||||
await extract('https://github.com/vercel/vercel/archive/master.zip', '/tmp');
|
||||
const exampleList = summary('/tmp/vercel-master/examples');
|
||||
await extract('https://github.com/vercel/vercel/archive/main.zip', '/tmp');
|
||||
const exampleList = summary('/tmp/vercel-main/examples');
|
||||
|
||||
const existingExamples = Array.from(exampleList).map(key => ({
|
||||
name: key,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"dependencies": {
|
||||
"@sentry/node": "5.11.1",
|
||||
"got": "10.2.1",
|
||||
"node-fetch": "2.6.0",
|
||||
"node-fetch": "2.6.1",
|
||||
"parse-github-url": "1.0.2",
|
||||
"tar-fs": "2.0.0",
|
||||
"unzip-stream": "0.3.0"
|
||||
|
||||
@@ -362,10 +362,10 @@ ms@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
node-fetch@2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
||||
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
||||
node-fetch@2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
normalize-url@^4.1.0:
|
||||
version "4.5.0"
|
||||
|
||||
14
examples/README.md
vendored
14
examples/README.md
vendored
@@ -44,13 +44,13 @@ We are continuously improving our examples based on best practices and feedback
|
||||
|
||||
For example, the previous `nodejs` example showed a static frontend with a Node.js API. This is illustrated in the `svelte` example. Below is a table that lists some of the most popular previous examples and the equivalent replacement:
|
||||
|
||||
| Previous Example | New Example |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------ |
|
||||
| **monorepo** | [gatsby-functions](https://github.com/vercel/vercel/tree/master/examples/gatsby) |
|
||||
| **nodejs** | [svelte-functions](https://github.com/vercel/vercel/tree/master/examples/svelte) |
|
||||
| **nextjs-static** | [nextjs](https://github.com/vercel/vercel/tree/master/examples/nextjs) |
|
||||
| **vanilla-go** | [create-react-app](https://github.com/vercel/vercel/tree/master/examples/create-react-app) |
|
||||
| **typescript** | [gatsby-functions](https://github.com/vercel/vercel/tree/master/examples/gatsby) |
|
||||
| Previous Example | New Example |
|
||||
| ----------------- | ---------------------------------------------------------------------------------------- |
|
||||
| **monorepo** | [gatsby-functions](https://github.com/vercel/vercel/tree/main/examples/gatsby) |
|
||||
| **nodejs** | [svelte-functions](https://github.com/vercel/vercel/tree/main/examples/svelte) |
|
||||
| **nextjs-static** | [nextjs](https://github.com/vercel/vercel/tree/main/examples/nextjs) |
|
||||
| **vanilla-go** | [create-react-app](https://github.com/vercel/vercel/tree/main/examples/create-react-app) |
|
||||
| **typescript** | [gatsby-functions](https://github.com/vercel/vercel/tree/main/examples/gatsby) |
|
||||
|
||||
## Migrating and Upgrading
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of an [AMP](https://amp.dev/) site that can be
|
||||
|
||||
Deploy your own AMP project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/amp)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/amp)
|
||||
|
||||
_Live Example: https://amp.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of an [Angular](https://angular.io/) app that
|
||||
|
||||
Deploy your own Angular project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/angular)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/angular)
|
||||
|
||||
_Live Example: https://angular.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -3073,9 +3073,9 @@ hmac-drbg@^1.0.1:
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hosted-git-info@^2.1.4, hosted-git-info@^2.6.0, hosted-git-info@^2.7.1:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
||||
version "2.8.9"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||
|
||||
hpack.js@^2.1.6:
|
||||
version "2.1.6"
|
||||
@@ -6204,9 +6204,9 @@ sshpk@^1.7.0:
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
ssri@^6.0.0, ssri@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
|
||||
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
|
||||
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
|
||||
dependencies:
|
||||
figgy-pudding "^3.5.1"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Blitz.js](https://blitzjs.com/) project
|
||||
|
||||
Deploy your own Blitz.js project with Vercel by viewing the [documentation on deploying to Vercel](https://blitzjs.com/docs/deploy-vercel)
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/blitzjs)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/blitzjs)
|
||||
|
||||
### How We Created This Example
|
||||
|
||||
|
||||
@@ -4681,9 +4681,9 @@ hmac-drbg@^1.0.1:
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
||||
version "2.8.9"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||
|
||||
hsl-regex@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -8195,9 +8195,9 @@ sprintf-js@~1.0.2:
|
||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||
|
||||
ssri@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
|
||||
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
|
||||
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
|
||||
dependencies:
|
||||
figgy-pudding "^3.5.1"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Brunch](https://brunch.io/) site that ca
|
||||
|
||||
Deploy your own Brunch project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/brunch)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/brunch)
|
||||
|
||||
_Live Example: https://brunch.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [React](https://reactjs.org/) app with [S
|
||||
|
||||
Deploy your own React project, along with Serverless Functions, with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/create-react-app-functions)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/create-react-app-functions)
|
||||
|
||||
_Live Example: https://create-react-app.now-examples.now.sh/_
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ function App() {
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
href="https://github.com/vercel/vercel/tree/master/examples/create-react-app"
|
||||
href="https://github.com/vercel/vercel/tree/main/examples/create-react-app"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of using a Custom Build script that can be dep
|
||||
|
||||
Deploy your own Custom Built project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/custom-build)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/custom-build)
|
||||
|
||||
_Live Example: https://custom-build.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Docusaurus](https://v2.docusaurus.io) si
|
||||
|
||||
Deploy your own Docusaurus project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/docusaurus-2)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/docusaurus-2)
|
||||
|
||||
### How We Created This Example
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Docusaurus](https://docusaurus.io/) site
|
||||
|
||||
Deploy your own Docusaurus project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/docusaurus)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/docusaurus)
|
||||
|
||||
_Live Example: https://docusaurus.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Dojo](https://dojo.io) site that can be
|
||||
|
||||
Deploy your own Dojo project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/dojo)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/dojo)
|
||||
|
||||
### How We Created This Example
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Eleventy](https://www.11ty.io/) site tha
|
||||
|
||||
Deploy your own Eleventy project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/eleventy)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/eleventy)
|
||||
|
||||
_Live Example: https://eleventy.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of an [Ember](https://emberjs.com/) app that c
|
||||
|
||||
Deploy your own Ember project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/ember)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/ember)
|
||||
|
||||
_Live Example: https://ember.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Gatsby](https://www.gatsbyjs.org/) app w
|
||||
|
||||
Deploy your own Gatsby project, along with Serverless Functions, with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/gatsby)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/gatsby)
|
||||
|
||||
_Live Example: https://gatsby.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ function Index() {
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
href="https://github.com/vercel/vercel/tree/master/examples/gatsby"
|
||||
href="https://github.com/vercel/vercel/tree/main/examples/gatsby"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Gridsome](https://gridsome.org/) app tha
|
||||
|
||||
Deploy your own Gridsome project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/gridsome)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/gridsome)
|
||||
|
||||
_Live Example: https://gridsome.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Hexo](https://hexo.io/) site that can be
|
||||
|
||||
Deploy your own Hexo project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/hexo)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/hexo)
|
||||
|
||||
_Live Example: https://hexo.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Hugo](https://gohugo.io/) app that can b
|
||||
|
||||
Deploy your own Hugo project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/hugo)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/hugo)
|
||||
|
||||
_Live Example: https://hugo.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of an [Ionic Angular](https://ionicframework.c
|
||||
|
||||
Deploy your own Ionic Angular project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/ionic-angular)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/ionic-angular)
|
||||
|
||||
_Live Example: https://ionic-angular.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of an [Ionic React](https://ionicframework.com
|
||||
|
||||
Deploy your own Ionic React project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/ionic-react)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/ionic-react)
|
||||
|
||||
_Live Example: https://ionic-react.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Jekyll](https://jekyllrb.com/) site that
|
||||
|
||||
Deploy your own Jekyll project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/jekyll)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/jekyll)
|
||||
|
||||
_Live Example: https://jekyll.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Middleman](https://middlemanapp.com/) si
|
||||
|
||||
Deploy your own Middleman project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/middleman)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/middleman)
|
||||
|
||||
_Live Example: https://middleman.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Nuxt.js](https://nuxtjs.org) app that ca
|
||||
|
||||
Deploy your own Nuxt.js project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/nuxtjs)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/nuxtjs)
|
||||
|
||||
_Live Example: https://nuxtjs.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Polymer](https://www.polymer-project.org
|
||||
|
||||
Deploy your own Polymer project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/polymer)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/polymer)
|
||||
|
||||
_Live Example: https://polymer.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Preact](https://preactjs.com/) app that
|
||||
|
||||
Deploy your own Preact project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/preact)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/preact)
|
||||
|
||||
_Live Example: https://preact.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [RedwoodJS](https://redwoodjs.com) app wi
|
||||
|
||||
Deploy your own RedwoodJS project, along with Serverless Functions, with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/redwoodjs)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/redwoodjs)
|
||||
|
||||
_Live Example: https://redwoodjs.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Saber](https://saber.land) site that can
|
||||
|
||||
Deploy your own Saber project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/saber)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/saber)
|
||||
|
||||
_Live Example: https://saber.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Sapper](https://sapper.svelte.dev/) app
|
||||
|
||||
Deploy your own Sapper project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/sapper)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/sapper)
|
||||
|
||||
_Live Example: https://sapper.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Scully](https://scully.io) site that can
|
||||
|
||||
Deploy your own Scully project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/scully)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/scully)
|
||||
|
||||
_Live Example: https://scully.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Stencil](https://stenciljs.com/) app tha
|
||||
|
||||
Deploy your own Stencil project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/stencil)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/stencil)
|
||||
|
||||
_Live Example: https://stencil.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Svelte](https://svelte.dev/) app with [S
|
||||
|
||||
Deploy your own Svelte project, along with Serverless Functions, with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/svelte)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/svelte)
|
||||
|
||||
_Live Example: https://svelte.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
href="https://github.com/vercel/vercel/tree/master/examples/svelte"
|
||||
href="https://github.com/vercel/vercel/tree/main/examples/svelte"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener">
|
||||
This project
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [UmiJS](https://umijs.org/) app that can
|
||||
|
||||
Deploy your own UmiJS project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/umijs)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/umijs)
|
||||
|
||||
_Live Example: https://umijs.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
# Realtime Pusher Whiteboard
|
||||
# Realtime Pusher Example
|
||||
|
||||
This directory is a realtime serverless whiteboard powered by Pusher. The frontend is static vanilla HTML, CSS, and JavaScript and the backend is a single Node.js function.
|
||||
This directory is a realtime serverless whiteboard powered by Pusher. The frontend is static vanilla HTML, CSS, and JavaScript and the backend is a single Node.js function both deployed with Vercel and zero configuration.
|
||||
|
||||
## Creating This Example
|
||||
## Deploy Your Own
|
||||
|
||||
To get started with a realtime whiteboard on Vercel, you can use [Vercel CLI](https://vercel.com/download) to initialize the project:
|
||||
Deploy your own Pusher project with Vercel.
|
||||
|
||||
```shell
|
||||
$ vercel init vanilla-pusher-functions
|
||||
```
|
||||
|
||||
### Deploying From Your Terminal
|
||||
|
||||
You can deploy your new project with a single command from your terminal using [Vercel CLI](https://vercel.com/download):
|
||||
|
||||
```shell
|
||||
$ vercel
|
||||
```
|
||||
[](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fvercel%2Ftree%2Fmain%2Fexamples%2Fvanilla-pusher-functions&env=APP_ID,KEY,SECRET,CLUSTER&envDescription=Pusher%20Channels&envLink=https%3A%2F%2Fpusher.com%2Fchannels)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"env": {
|
||||
"APP_ID": "@pusher-whiteboard-app-id",
|
||||
"KEY": "@pusher-whiteboard-key",
|
||||
"SECRET": "@pusher-whiteboard-secret",
|
||||
"CLUSTER": "us2"
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Vue.js](https://vuejs.org/) app that can
|
||||
|
||||
Deploy your own Vue.js project with Vercel.
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/master/examples/vue)
|
||||
[](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/vue)
|
||||
|
||||
_Live Example: https://vue.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"command": {
|
||||
"publish": {
|
||||
"npmClient": "npm",
|
||||
"allowBranch": ["master"],
|
||||
"allowBranch": ["main"],
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
}
|
||||
},
|
||||
|
||||
11
package.json
11
package.json
@@ -24,7 +24,7 @@
|
||||
"eslint": "6.2.2",
|
||||
"eslint-config-prettier": "6.1.0",
|
||||
"eslint-plugin-jest": "23.8.2",
|
||||
"husky": "3.0.4",
|
||||
"husky": "6.0.0",
|
||||
"json5": "2.1.1",
|
||||
"lint-staged": "9.2.5",
|
||||
"node-fetch": "2.6.1",
|
||||
@@ -40,11 +40,13 @@
|
||||
"changelog": "node utils/changelog.js",
|
||||
"build": "node utils/run.js build all",
|
||||
"vercel-build": "mkdir -p public && echo '<a href=\"https://vercel.com/import\">Import</a>' > public/output.html",
|
||||
"pre-commit": "lint-staged",
|
||||
"test-unit": "node utils/run.js test-unit",
|
||||
"test-integration-cli": "node utils/run.js test-integration-cli",
|
||||
"test-integration-once": "node utils/run.js test-integration-once",
|
||||
"test-integration-dev": "node utils/run.js test-integration-dev",
|
||||
"lint": "eslint . --ext .ts,.js"
|
||||
"lint": "eslint . --ext .ts,.js",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"lint-staged": {
|
||||
"./{*,{api,packages,test,utils}/**/*}.{js,ts}": [
|
||||
@@ -57,11 +59,6 @@
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "2.10.2-canary.5",
|
||||
"version": "2.10.3-canary.4",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
@@ -29,7 +29,7 @@
|
||||
"@types/node-fetch": "^2.1.6",
|
||||
"@types/semver": "6.0.0",
|
||||
"@types/yazl": "^2.4.1",
|
||||
"@vercel/frameworks": "0.3.2-canary.5",
|
||||
"@vercel/frameworks": "0.3.3-canary.3",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"aggregate-error": "3.0.1",
|
||||
"async-retry": "1.2.3",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
const scheduler = require('@google-cloud/scheduler');
|
||||
|
||||
module.exports = (_, res) => {
|
||||
if (scheduler) {
|
||||
res.end('found:RANDOMNESS_PLACEHOLDER');
|
||||
} else {
|
||||
res.end('nope:RANDOMNESS_PLACEHOLDER');
|
||||
}
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [
|
||||
{
|
||||
"src": "index.js",
|
||||
"use": "@vercel/node"
|
||||
}
|
||||
],
|
||||
"probes": [{ "path": "/", "mustContain": "found:RANDOMNESS_PLACEHOLDER" }]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "15-yarn-ignore-engines",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@google-cloud/scheduler": "0.3.0"
|
||||
}
|
||||
}
|
||||
24
packages/build-utils/test/unit.test.js
vendored
24
packages/build-utils/test/unit.test.js
vendored
@@ -97,10 +97,6 @@ it('should create zip files with symlinks properly', async () => {
|
||||
});
|
||||
|
||||
it('should only match supported node versions, otherwise throw an error', async () => {
|
||||
expect(await getSupportedNodeVersion('10.x', false)).toHaveProperty(
|
||||
'major',
|
||||
10
|
||||
);
|
||||
expect(await getSupportedNodeVersion('12.x', false)).toHaveProperty(
|
||||
'major',
|
||||
12
|
||||
@@ -121,10 +117,6 @@ it('should only match supported node versions, otherwise throw an error', async
|
||||
await expectBuilderError(getSupportedNodeVersion('foo', true), autoMessage);
|
||||
await expectBuilderError(getSupportedNodeVersion('=> 10', true), autoMessage);
|
||||
|
||||
expect(await getSupportedNodeVersion('10.x', true)).toHaveProperty(
|
||||
'major',
|
||||
10
|
||||
);
|
||||
expect(await getSupportedNodeVersion('12.x', true)).toHaveProperty(
|
||||
'major',
|
||||
12
|
||||
@@ -154,20 +146,20 @@ it('should only match supported node versions, otherwise throw an error', async
|
||||
|
||||
it('should match all semver ranges', async () => {
|
||||
// See https://docs.npmjs.com/files/package.json#engines
|
||||
expect(await getSupportedNodeVersion('10.0.0')).toHaveProperty('major', 10);
|
||||
expect(await getSupportedNodeVersion('10.x')).toHaveProperty('major', 10);
|
||||
expect(await getSupportedNodeVersion('12.0.0')).toHaveProperty('major', 12);
|
||||
expect(await getSupportedNodeVersion('12.x')).toHaveProperty('major', 12);
|
||||
expect(await getSupportedNodeVersion('>=10')).toHaveProperty('major', 14);
|
||||
expect(await getSupportedNodeVersion('>=10.3.0')).toHaveProperty('major', 14);
|
||||
expect(await getSupportedNodeVersion('8.5.0 - 10.5.0')).toHaveProperty(
|
||||
expect(await getSupportedNodeVersion('11.5.0 - 12.5.0')).toHaveProperty(
|
||||
'major',
|
||||
10
|
||||
12
|
||||
);
|
||||
expect(await getSupportedNodeVersion('>=9.5.0 <=10.5.0')).toHaveProperty(
|
||||
expect(await getSupportedNodeVersion('>=9.5.0 <=12.5.0')).toHaveProperty(
|
||||
'major',
|
||||
10
|
||||
12
|
||||
);
|
||||
expect(await getSupportedNodeVersion('~10.5.0')).toHaveProperty('major', 10);
|
||||
expect(await getSupportedNodeVersion('^10.5.0')).toHaveProperty('major', 10);
|
||||
expect(await getSupportedNodeVersion('~12.5.0')).toHaveProperty('major', 12);
|
||||
expect(await getSupportedNodeVersion('^12.5.0')).toHaveProperty('major', 12);
|
||||
});
|
||||
|
||||
it('should ignore node version in vercel dev getNodeVersion()', async () => {
|
||||
|
||||
1
packages/cli/@types/inquirer/index.d.ts
vendored
1
packages/cli/@types/inquirer/index.d.ts
vendored
@@ -1 +0,0 @@
|
||||
declare module 'inquirer';
|
||||
5
packages/cli/@types/jsonlines/index.d.ts
vendored
Normal file
5
packages/cli/@types/jsonlines/index.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module 'jsonlines' {
|
||||
import { Transform } from 'stream';
|
||||
|
||||
function parse(): Transform;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "21.3.4-canary.12",
|
||||
"version": "22.0.2-canary.6",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -61,10 +61,10 @@
|
||||
"node": ">= 12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.10.2-canary.5",
|
||||
"@vercel/build-utils": "2.10.3-canary.4",
|
||||
"@vercel/go": "1.2.2",
|
||||
"@vercel/node": "1.9.2-canary.1",
|
||||
"@vercel/python": "2.0.1",
|
||||
"@vercel/node": "1.10.1-canary.2",
|
||||
"@vercel/python": "2.0.2",
|
||||
"@vercel/ruby": "1.2.6",
|
||||
"update-notifier": "4.1.0"
|
||||
},
|
||||
@@ -82,6 +82,7 @@
|
||||
"@types/fs-extra": "5.0.5",
|
||||
"@types/glob": "7.1.1",
|
||||
"@types/http-proxy": "1.16.2",
|
||||
"@types/inquirer": "7.3.1",
|
||||
"@types/load-json-file": "2.0.7",
|
||||
"@types/mime-types": "2.1.0",
|
||||
"@types/minimatch": "3.0.3",
|
||||
@@ -99,7 +100,7 @@
|
||||
"@types/universal-analytics": "0.4.2",
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@vercel/frameworks": "0.3.2-canary.5",
|
||||
"@vercel/frameworks": "0.3.3-canary.3",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@zeit/fun": "0.11.2",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
|
||||
@@ -19,7 +19,7 @@ import link from '../../util/output/link';
|
||||
import { User } from '../../types';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import toHost from '../../util/to-host';
|
||||
import { NowConfig } from '../../util/dev/types';
|
||||
import { VercelConfig } from '../../util/dev/types';
|
||||
|
||||
type Options = {
|
||||
'--debug': boolean;
|
||||
@@ -421,7 +421,7 @@ function handleCreateAliasError<T>(
|
||||
return error;
|
||||
}
|
||||
|
||||
function getTargetsForAlias(args: string[], { alias }: NowConfig) {
|
||||
function getTargetsForAlias(args: string[], { alias }: VercelConfig) {
|
||||
if (args.length) {
|
||||
return [args[args.length - 1]]
|
||||
.map(target => (target.indexOf('.') !== -1 ? toHost(target) : target))
|
||||
|
||||
@@ -13,7 +13,6 @@ import inspect from './inspect';
|
||||
import ls from './ls';
|
||||
import rm from './rm';
|
||||
import move from './move';
|
||||
import verify from './verify';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
|
||||
const help = () => {
|
||||
@@ -81,7 +80,6 @@ const COMMAND_CONFIG = {
|
||||
move: ['move'],
|
||||
rm: ['rm', 'remove'],
|
||||
transferIn: ['transfer-in'],
|
||||
verify: ['verify'],
|
||||
};
|
||||
|
||||
export default async function main(client: Client) {
|
||||
@@ -119,8 +117,6 @@ export default async function main(client: Client) {
|
||||
return rm(client, argv, args);
|
||||
case 'transferIn':
|
||||
return transferIn(client, argv, args);
|
||||
case 'verify':
|
||||
return verify(client, argv, args);
|
||||
default:
|
||||
return ls(client, argv, args);
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import Client from '../../util/client';
|
||||
import { NowBuildError } from '@vercel/build-utils';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
|
||||
export default async function verify(
|
||||
{ output }: Client,
|
||||
_opts: {},
|
||||
args: string[]
|
||||
) {
|
||||
const [domainName] = args;
|
||||
|
||||
if (!domainName) {
|
||||
output.error(
|
||||
`${getCommandName(`domains verify <domain>`)} expects one argument`
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
const error = new NowBuildError({
|
||||
code: 'domains_verify_command_deprecated',
|
||||
message: `It's not necessary to verify Domains anymore. Instead, you can run ${getCommandName(
|
||||
`domains inspect ${domainName}`
|
||||
)} to see what you need to do in order to configure it properly.`,
|
||||
link: 'https://vercel.link/domain-verification-via-cli',
|
||||
});
|
||||
|
||||
output.prettyError(error);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -111,7 +111,6 @@ async function chooseFromDropdown(message: string, exampleList: string[]) {
|
||||
|
||||
return listInput({
|
||||
message,
|
||||
separator: false,
|
||||
choices,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ import getArgs from '../util/get-args';
|
||||
import buildsList from '../util/output/builds';
|
||||
import routesList from '../util/output/routes';
|
||||
import indent from '../util/output/indent';
|
||||
import Now from '../util';
|
||||
import logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed.ts';
|
||||
import elapsed from '../util/output/elapsed';
|
||||
import { handleError } from '../util/error';
|
||||
import getScope from '../util/get-scope.ts';
|
||||
import { getPkgName, getCommandName } from '../util/pkg-name.ts';
|
||||
import getScope from '../util/get-scope';
|
||||
import { getPkgName, getCommandName } from '../util/pkg-name';
|
||||
import Client from '../util/client';
|
||||
import { getDeployment } from '../util/get-deployment';
|
||||
import { Deployment } from '@vercel/client';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -33,15 +35,15 @@ const help = () => {
|
||||
|
||||
${chalk.gray('–')} Get information about a deployment by its unique URL
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment-ji2fjij2.now.sh`)}
|
||||
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment-ji2fjij2.vercel.app`)}
|
||||
|
||||
${chalk.gray('-')} Get information about the deployment an alias points to
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment.now.sh`)}
|
||||
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment.vercel.app`)}
|
||||
`);
|
||||
};
|
||||
|
||||
export default async function main(client) {
|
||||
export default async function main(client: Client) {
|
||||
let deployment;
|
||||
let argv;
|
||||
|
||||
@@ -57,14 +59,7 @@ export default async function main(client) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
const {
|
||||
apiUrl,
|
||||
output,
|
||||
authConfig: { token },
|
||||
config,
|
||||
} = client;
|
||||
const debugEnabled = argv['--debug'];
|
||||
const { print, log, error } = output;
|
||||
const { print, log, error } = client.output;
|
||||
|
||||
// extract the first parameter
|
||||
const [, deploymentIdOrHost] = argv._;
|
||||
@@ -75,9 +70,7 @@ export default async function main(client) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { currentTeam } = config;
|
||||
|
||||
let contextName = null;
|
||||
let contextName: string | null = null;
|
||||
|
||||
try {
|
||||
({ contextName } = await getScope(client));
|
||||
@@ -90,22 +83,14 @@ export default async function main(client) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const now = new Now({
|
||||
apiUrl,
|
||||
token,
|
||||
debug: debugEnabled,
|
||||
currentTeam,
|
||||
output,
|
||||
});
|
||||
|
||||
// resolve the deployment, since we might have been given an alias
|
||||
const depFetchStart = Date.now();
|
||||
output.spinner(
|
||||
client.output.spinner(
|
||||
`Fetching deployment "${deploymentIdOrHost}" in ${chalk.bold(contextName)}`
|
||||
);
|
||||
|
||||
try {
|
||||
deployment = await now.findDeployment(deploymentIdOrHost);
|
||||
deployment = await getDeployment(client, deploymentIdOrHost);
|
||||
} catch (err) {
|
||||
if (err.status === 404) {
|
||||
error(
|
||||
@@ -127,11 +112,11 @@ export default async function main(client) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const { id, name, url, created, routes, readyState } = deployment;
|
||||
const { id, name, url, createdAt, routes, readyState } = deployment;
|
||||
|
||||
const { builds } =
|
||||
deployment.version === 2
|
||||
? await now.fetch(`/v1/now/deployments/${id}/builds`)
|
||||
? await client.fetch(`/v1/now/deployments/${id}/builds`)
|
||||
: { builds: [] };
|
||||
|
||||
log(
|
||||
@@ -146,10 +131,10 @@ export default async function main(client) {
|
||||
print(` ${chalk.cyan('name')}\t${name}\n`);
|
||||
print(` ${chalk.cyan('readyState')}\t${stateString(readyState)}\n`);
|
||||
print(` ${chalk.cyan('url')}\t\t${url}\n`);
|
||||
if (created) {
|
||||
if (createdAt) {
|
||||
print(
|
||||
` ${chalk.cyan('createdAt')}\t${new Date(created)} ${elapsed(
|
||||
Date.now() - created,
|
||||
` ${chalk.cyan('createdAt')}\t${new Date(createdAt)} ${elapsed(
|
||||
Date.now() - createdAt,
|
||||
true
|
||||
)}\n`
|
||||
);
|
||||
@@ -157,7 +142,7 @@ export default async function main(client) {
|
||||
print('\n\n');
|
||||
|
||||
if (builds.length > 0) {
|
||||
const times = {};
|
||||
const times: { [id: string]: string | null } = {};
|
||||
|
||||
for (const build of builds) {
|
||||
const { id, createdAt, readyStateAt } = build;
|
||||
@@ -179,7 +164,7 @@ export default async function main(client) {
|
||||
}
|
||||
|
||||
// renders the state string
|
||||
function stateString(s) {
|
||||
function stateString(s: Deployment['readyState']) {
|
||||
switch (s) {
|
||||
case 'INITIALIZING':
|
||||
return chalk.yellow(s);
|
||||
@@ -1,11 +1,10 @@
|
||||
import inquirer from 'inquirer';
|
||||
import { validate as validateEmail } from 'email-validator';
|
||||
import chalk from 'chalk';
|
||||
import hp from '../util/humanize-path';
|
||||
import getArgs from '../util/get-args';
|
||||
import error from '../util/output/error';
|
||||
import handleError from '../util/handle-error';
|
||||
import logo from '../util/output/logo';
|
||||
import prompt from '../util/login/prompt';
|
||||
import doSsoLogin from '../util/login/sso';
|
||||
import doEmailLogin from '../util/login/email';
|
||||
import { prependEmoji, emoji } from '../util/emoji';
|
||||
@@ -13,6 +12,7 @@ import { getCommandName, getPkgName } from '../util/pkg-name';
|
||||
import getGlobalPathConfig from '../util/config/global-path';
|
||||
import { writeToAuthConfigFile, writeToConfigFile } from '../util/config/files';
|
||||
import Client from '../util/client';
|
||||
import { LoginParams } from '../util/login/types';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -44,35 +44,6 @@ const help = () => {
|
||||
`);
|
||||
};
|
||||
|
||||
const readInput = async () => {
|
||||
let input;
|
||||
|
||||
while (!input) {
|
||||
try {
|
||||
const { val } = await inquirer.prompt({
|
||||
type: 'input',
|
||||
name: 'val',
|
||||
message: 'Enter your email or team slug:',
|
||||
});
|
||||
input = val;
|
||||
} catch (err) {
|
||||
console.log(); // \n
|
||||
|
||||
if (err.isTtyError) {
|
||||
throw new Error(
|
||||
error(
|
||||
`Interactive mode not supported – please run ${getCommandName(
|
||||
`login you@domain.com`
|
||||
)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
};
|
||||
|
||||
export default async function login(client: Client): Promise<number> {
|
||||
let argv;
|
||||
const { apiUrl, output } = client;
|
||||
@@ -94,21 +65,21 @@ export default async function login(client: Client): Promise<number> {
|
||||
return 2;
|
||||
}
|
||||
|
||||
const input = argv._[1] || (await readInput());
|
||||
|
||||
// TODO: add proper validation
|
||||
const isValidSlug = true;
|
||||
const input = argv._[1];
|
||||
|
||||
let result: number | string = 1;
|
||||
const params: LoginParams = { output, apiUrl };
|
||||
|
||||
if (validateEmail(input)) {
|
||||
result = await doEmailLogin(input, { output, apiUrl });
|
||||
} else if (isValidSlug) {
|
||||
result = await doSsoLogin(input, { output, apiUrl });
|
||||
if (input) {
|
||||
// Email or Team slug was provided via command line
|
||||
if (validateEmail(input)) {
|
||||
result = await doEmailLogin(input, params);
|
||||
} else {
|
||||
result = await doSsoLogin(input, params);
|
||||
}
|
||||
} else {
|
||||
output.error(`Invalid input: "${input}"`);
|
||||
output.log(`Please enter a valid email address or team slug`);
|
||||
return 2;
|
||||
// Interactive mode
|
||||
result = await prompt(params);
|
||||
}
|
||||
|
||||
// The login function failed, so it returned an exit code
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import chalk from 'chalk';
|
||||
import fetch from 'node-fetch';
|
||||
import logo from '../util/output/logo';
|
||||
// @ts-ignore
|
||||
import { handleError } from '../util/error';
|
||||
@@ -48,10 +47,9 @@ export default async function main(client: Client): Promise<number> {
|
||||
return 2;
|
||||
}
|
||||
|
||||
const { authConfig, config, apiUrl, output } = client;
|
||||
const { token } = authConfig;
|
||||
const { authConfig, config, output } = client;
|
||||
|
||||
if (!token) {
|
||||
if (!authConfig.token) {
|
||||
output.note(
|
||||
`Not currently logged in, so ${getCommandName('logout')} did nothing`
|
||||
);
|
||||
@@ -59,6 +57,20 @@ export default async function main(client: Client): Promise<number> {
|
||||
}
|
||||
|
||||
output.spinner('Logging out…', 200);
|
||||
let exitCode = 0;
|
||||
|
||||
try {
|
||||
await client.fetch(`/v3/user/tokens/current`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.status === 403) {
|
||||
output.debug('Token is invalid so it cannot be revoked');
|
||||
} else if (err.status !== 200) {
|
||||
output.debug(err?.message ?? '');
|
||||
exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
delete config.currentTeam;
|
||||
|
||||
@@ -75,26 +87,15 @@ export default async function main(client: Client): Promise<number> {
|
||||
writeToAuthConfigFile(authConfig);
|
||||
output.debug('Configuration has been deleted');
|
||||
} catch (err) {
|
||||
output.error(`Couldn't remove config while logging out`);
|
||||
return 1;
|
||||
output.debug(err?.message ?? '');
|
||||
exitCode = 1;
|
||||
}
|
||||
|
||||
const res = await fetch(`${apiUrl}/v3/user/tokens/current`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status === 403) {
|
||||
output.debug('Token is invalid so it cannot be revoked');
|
||||
} else if (res.status !== 200) {
|
||||
const err = await res.json();
|
||||
output.error('Failed to revoke token');
|
||||
output.debug(err ? err.message : '');
|
||||
return 1;
|
||||
if (exitCode === 0) {
|
||||
output.log('Logged out!');
|
||||
} else {
|
||||
output.error(`Failed during logout`);
|
||||
}
|
||||
|
||||
output.log('Logged out!');
|
||||
return 0;
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import chalk from 'chalk';
|
||||
import Now from '../util';
|
||||
import logo from '../util/output/logo';
|
||||
import elapsed from '../util/output/elapsed.ts';
|
||||
import elapsed from '../util/output/elapsed';
|
||||
import { maybeURL, normalizeURL } from '../util/url';
|
||||
import printEvents from '../util/events';
|
||||
import getScope from '../util/get-scope.ts';
|
||||
import { getPkgName } from '../util/pkg-name.ts';
|
||||
import getArgs from '../util/get-args.ts';
|
||||
import handleError from '../util/handle-error.ts';
|
||||
import printEvents, { DeploymentEvent } from '../util/events';
|
||||
import getScope from '../util/get-scope';
|
||||
import { getPkgName } from '../util/pkg-name';
|
||||
import getArgs from '../util/get-args';
|
||||
import Client from '../util/client';
|
||||
import { getDeployment } from '../util/get-deployment';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -53,35 +53,25 @@ const help = () => {
|
||||
`);
|
||||
};
|
||||
|
||||
export default async function main(client) {
|
||||
let argv;
|
||||
let deploymentIdOrURL;
|
||||
|
||||
let debug;
|
||||
export default async function main(client: Client) {
|
||||
let head;
|
||||
let limit;
|
||||
let follow;
|
||||
let outputMode;
|
||||
|
||||
let since;
|
||||
let until;
|
||||
let deploymentIdOrURL;
|
||||
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--since': String,
|
||||
'--until': String,
|
||||
'--output': String,
|
||||
'--limit': Number,
|
||||
'--head': Boolean,
|
||||
'--follow': Boolean,
|
||||
'-f': '--follow',
|
||||
'-o': '--output',
|
||||
'-n': '--limit',
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
return 1;
|
||||
}
|
||||
const argv = getArgs(client.argv.slice(2), {
|
||||
'--since': String,
|
||||
'--until': String,
|
||||
'--output': String,
|
||||
'--limit': Number,
|
||||
'--head': Boolean,
|
||||
'--follow': Boolean,
|
||||
'-f': '--follow',
|
||||
'-o': '--output',
|
||||
'-n': '--limit',
|
||||
});
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
deploymentIdOrURL = argv._[0];
|
||||
@@ -91,12 +81,7 @@ export default async function main(client) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
const {
|
||||
authConfig: { token },
|
||||
apiUrl,
|
||||
output,
|
||||
config,
|
||||
} = client;
|
||||
const { output } = client;
|
||||
|
||||
try {
|
||||
since = argv['--since'] ? toTimestamp(argv['--since']) : 0;
|
||||
@@ -124,40 +109,24 @@ export default async function main(client) {
|
||||
deploymentIdOrURL = normalizedURL;
|
||||
}
|
||||
|
||||
debug = argv['--debug'];
|
||||
|
||||
head = argv['--head'];
|
||||
limit = argv['--limit'] || 100;
|
||||
follow = argv['--follow'];
|
||||
if (follow) until = 0;
|
||||
outputMode = argv['--output'] in logPrinters ? argv['--output'] : 'short';
|
||||
const logPrinter = getLogPrinter(argv['--output'], 'short');
|
||||
|
||||
const { currentTeam } = config;
|
||||
const now = new Now({ apiUrl, token, debug, currentTeam, output });
|
||||
let contextName = null;
|
||||
const { contextName } = await getScope(client);
|
||||
|
||||
try {
|
||||
({ contextName } = await getScope(client));
|
||||
} catch (err) {
|
||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
||||
output.error(err.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
let deployment;
|
||||
const id = deploymentIdOrURL;
|
||||
|
||||
const depFetchStart = Date.now();
|
||||
output.spinner(`Fetching deployment "${id}" in ${chalk.bold(contextName)}`);
|
||||
|
||||
let deployment;
|
||||
try {
|
||||
deployment = await now.findDeployment(id);
|
||||
deployment = await getDeployment(client, id);
|
||||
} catch (err) {
|
||||
output.stopSpinner();
|
||||
now.close();
|
||||
|
||||
if (err.status === 404) {
|
||||
output.error(
|
||||
@@ -183,31 +152,28 @@ export default async function main(client) {
|
||||
)} ${elapsed(Date.now() - depFetchStart)}`
|
||||
);
|
||||
|
||||
let direction = head ? 'forward' : 'backward';
|
||||
if (since && !until) direction = 'forward';
|
||||
const findOpts1 = {
|
||||
direction,
|
||||
limit,
|
||||
since,
|
||||
until,
|
||||
}; // no follow
|
||||
const storage = [];
|
||||
const storeEvent = event => storage.push(event);
|
||||
const storage: DeploymentEvent[] = [];
|
||||
|
||||
await printEvents(now, deployment.uid || deployment.id, currentTeam, {
|
||||
let direction = head ? ('forward' as const) : ('backward' as const);
|
||||
if (since && !until) direction = 'forward';
|
||||
|
||||
await printEvents(client, deployment.id, {
|
||||
mode: 'logs',
|
||||
onEvent: storeEvent,
|
||||
onEvent: event => storage.push(event),
|
||||
quiet: false,
|
||||
debug,
|
||||
findOpts: findOpts1,
|
||||
output,
|
||||
findOpts: {
|
||||
direction,
|
||||
limit,
|
||||
since,
|
||||
until,
|
||||
},
|
||||
});
|
||||
|
||||
const printedEventIds = new Set();
|
||||
const printEvent = event => {
|
||||
const printedEventIds = new Set<string>();
|
||||
const printEvent = (event: DeploymentEvent) => {
|
||||
if (printedEventIds.has(event.id)) return 0;
|
||||
printedEventIds.add(event.id);
|
||||
return logPrinters[outputMode](event);
|
||||
return logPrinter(event);
|
||||
};
|
||||
storage.sort(compareEvents).forEach(printEvent);
|
||||
|
||||
@@ -216,26 +182,22 @@ export default async function main(client) {
|
||||
// NOTE: the API ignores `since` on follow mode.
|
||||
// (but not sure if it's always true on legacy deployments)
|
||||
const since2 = lastEvent ? lastEvent.date : Date.now();
|
||||
const findOpts2 = {
|
||||
direction: 'forward',
|
||||
since: since2,
|
||||
follow: true,
|
||||
};
|
||||
await printEvents(now, deployment.uid || deployment.id, currentTeam, {
|
||||
await printEvents(client, deployment.id, {
|
||||
mode: 'logs',
|
||||
onEvent: printEvent,
|
||||
quiet: false,
|
||||
debug,
|
||||
findOpts: findOpts2,
|
||||
output,
|
||||
findOpts: {
|
||||
direction: 'forward',
|
||||
since: since2,
|
||||
follow: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
now.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
function compareEvents(d1, d2) {
|
||||
function compareEvents(d1: DeploymentEvent, d2: DeploymentEvent) {
|
||||
const c1 = d1.date || d1.created;
|
||||
const c2 = d2.date || d2.created;
|
||||
if (c1 !== c2) return c1 - c2;
|
||||
@@ -246,10 +208,10 @@ function compareEvents(d1, d2) {
|
||||
return d1.created - d2.created; // if date are equal and no serial
|
||||
}
|
||||
|
||||
function printLogShort(log) {
|
||||
function printLogShort(log: any) {
|
||||
if (!log.created) return; // keepalive
|
||||
|
||||
let data;
|
||||
let data: string;
|
||||
const obj = log.object;
|
||||
if (log.type === 'request') {
|
||||
data =
|
||||
@@ -315,7 +277,7 @@ function printLogShort(log) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function printLogRaw(log) {
|
||||
function printLogRaw(log: any) {
|
||||
if (!log.created) return; // keepalive
|
||||
|
||||
if (log.object) {
|
||||
@@ -340,7 +302,27 @@ const logPrinters = {
|
||||
raw: printLogRaw,
|
||||
};
|
||||
|
||||
function toTimestamp(datestr) {
|
||||
type OutputMode = keyof typeof logPrinters;
|
||||
|
||||
const isLogPrinter = (v: any): v is OutputMode => {
|
||||
return v && v in logPrinters;
|
||||
};
|
||||
|
||||
const getLogPrinter = (mode: string | undefined, def: OutputMode) => {
|
||||
if (mode) {
|
||||
if (isLogPrinter(mode)) {
|
||||
return logPrinters[mode];
|
||||
}
|
||||
throw new TypeError(
|
||||
`Invalid output mode "${mode}". Must be one of: ${Object.keys(
|
||||
logPrinters
|
||||
).join(', ')}`
|
||||
);
|
||||
}
|
||||
return logPrinters[def];
|
||||
};
|
||||
|
||||
function toTimestamp(datestr: string) {
|
||||
const t = Date.parse(datestr);
|
||||
if (isNaN(t)) {
|
||||
throw new TypeError('Invalid date string');
|
||||
@@ -1,14 +1,15 @@
|
||||
import chalk from 'chalk';
|
||||
import error from '../util/output/error';
|
||||
import NowTeams from '../util/teams';
|
||||
import logo from '../util/output/logo';
|
||||
import list from './teams/list';
|
||||
import add from './teams/add';
|
||||
import change from './teams/switch';
|
||||
import invite from './teams/invite';
|
||||
import { getPkgName } from '../util/pkg-name.ts';
|
||||
import getArgs from '../util/get-args.ts';
|
||||
import handleError from '../util/handle-error.ts';
|
||||
import error from '../../util/output/error';
|
||||
import NowTeams from '../../util/teams';
|
||||
import logo from '../../util/output/logo';
|
||||
import list from './list';
|
||||
import add from './add';
|
||||
import change from './switch';
|
||||
import invite from './invite';
|
||||
import { getPkgName } from '../../util/pkg-name';
|
||||
import getArgs from '../../util/get-args';
|
||||
import handleError from '../../util/handle-error';
|
||||
import Client from '../../util/client';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
@@ -65,7 +66,7 @@ let debug;
|
||||
let apiUrl;
|
||||
let subcommand;
|
||||
|
||||
const main = async client => {
|
||||
export default async (client: Client) => {
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--since': String,
|
||||
@@ -113,7 +114,7 @@ const main = async client => {
|
||||
}
|
||||
case 'switch':
|
||||
case 'change': {
|
||||
exitCode = await change(client, argv);
|
||||
exitCode = await change(client, argv._[0]);
|
||||
break;
|
||||
}
|
||||
case 'add':
|
||||
@@ -139,13 +140,3 @@ const main = async client => {
|
||||
teams.close();
|
||||
return exitCode || 0;
|
||||
};
|
||||
|
||||
export default async client => {
|
||||
try {
|
||||
return await main(client);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
handleError(err);
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
@@ -1,190 +0,0 @@
|
||||
// Packages
|
||||
import chalk from 'chalk';
|
||||
|
||||
// Utilities
|
||||
import listInput from '../../util/input/list';
|
||||
import success from '../../util/output/success';
|
||||
import info from '../../util/output/info';
|
||||
import error from '../../util/output/error';
|
||||
import param from '../../util/output/param.ts';
|
||||
import { writeToConfigFile } from '../../util/config/files';
|
||||
import getUser from '../../util/get-user.ts';
|
||||
import NowTeams from '../../util/teams';
|
||||
|
||||
const updateCurrentTeam = (config, newTeam) => {
|
||||
if (newTeam) {
|
||||
config.currentTeam = newTeam.id;
|
||||
} else {
|
||||
delete config.currentTeam;
|
||||
}
|
||||
|
||||
writeToConfigFile(config);
|
||||
};
|
||||
|
||||
export default async function change(client, argv) {
|
||||
const {
|
||||
apiUrl,
|
||||
authConfig: { token },
|
||||
debug,
|
||||
config,
|
||||
output,
|
||||
} = client;
|
||||
output.spinner('Fetching teams');
|
||||
|
||||
// We're loading the teams here without `currentTeam`, so that
|
||||
// people can use `vercel switch` in the case that their
|
||||
// current team was deleted.
|
||||
const teams = new NowTeams({ apiUrl, token, debug, output });
|
||||
const list = (await teams.ls()).teams;
|
||||
|
||||
let { currentTeam } = config;
|
||||
const accountIsCurrent = !currentTeam;
|
||||
|
||||
output.spinner('Fetching user information');
|
||||
let user;
|
||||
try {
|
||||
user = await getUser(client);
|
||||
} catch (err) {
|
||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
||||
output.error(err.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (accountIsCurrent) {
|
||||
currentTeam = {
|
||||
slug: user.username || user.email,
|
||||
};
|
||||
} else {
|
||||
currentTeam = list.find(team => team.id === currentTeam);
|
||||
|
||||
if (!currentTeam) {
|
||||
output.error(`You are not a part of the current team anymore`);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (argv._.length !== 0) {
|
||||
const desiredSlug = argv._[0];
|
||||
const newTeam = list.find(team => team.slug === desiredSlug);
|
||||
|
||||
if (newTeam) {
|
||||
updateCurrentTeam(config, newTeam);
|
||||
console.log(
|
||||
success(
|
||||
`The team ${chalk.bold(newTeam.name)} (${
|
||||
newTeam.slug
|
||||
}) is now active!`
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (desiredSlug === user.username) {
|
||||
output.spinner('Saving');
|
||||
updateCurrentTeam(config);
|
||||
|
||||
output.stopSpinner();
|
||||
console.log(
|
||||
success(`Your account (${chalk.bold(desiredSlug)}) is now active!`)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
console.error(
|
||||
error(`Could not find membership for team ${param(desiredSlug)}`)
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const choices = list.map(({ id, slug, name }) => {
|
||||
name = `${slug} (${name})`;
|
||||
|
||||
if (id === currentTeam.id) {
|
||||
name += ` ${chalk.bold('(current)')}`;
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
value: slug,
|
||||
short: slug,
|
||||
};
|
||||
});
|
||||
|
||||
const suffix = accountIsCurrent ? ` ${chalk.bold('(current)')}` : '';
|
||||
|
||||
const userEntryName = user.username
|
||||
? `${user.username} (${user.email})${suffix}`
|
||||
: user.email;
|
||||
|
||||
choices.unshift({
|
||||
name: userEntryName,
|
||||
value: user.email,
|
||||
short: user.username,
|
||||
});
|
||||
|
||||
// Let's bring the current team to the beginning of the list
|
||||
if (!accountIsCurrent) {
|
||||
const index = choices.findIndex(
|
||||
choice => choice.value === currentTeam.slug
|
||||
);
|
||||
const choice = choices.splice(index, 1)[0];
|
||||
choices.unshift(choice);
|
||||
}
|
||||
|
||||
output.stopSpinner();
|
||||
|
||||
let message;
|
||||
|
||||
if (currentTeam) {
|
||||
message = `Switch to:`;
|
||||
}
|
||||
|
||||
const choice = await listInput({
|
||||
message,
|
||||
choices,
|
||||
separator: false,
|
||||
eraseFinalAnswer: true,
|
||||
});
|
||||
|
||||
// Abort
|
||||
if (!choice) {
|
||||
console.log(info('No changes made'));
|
||||
return 0;
|
||||
}
|
||||
|
||||
const newTeam = list.find(item => item.slug === choice);
|
||||
|
||||
// Switch to account
|
||||
if (!newTeam) {
|
||||
if (currentTeam.slug === user.username || currentTeam.slug === user.email) {
|
||||
console.log(info('No changes made'));
|
||||
return 0;
|
||||
}
|
||||
|
||||
output.spinner('Saving');
|
||||
updateCurrentTeam(config);
|
||||
|
||||
output.stopSpinner();
|
||||
console.log(success(`Your account (${chalk.bold(choice)}) is now active!`));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (newTeam.slug === currentTeam.slug) {
|
||||
console.log(info('No changes made'));
|
||||
return 0;
|
||||
}
|
||||
|
||||
output.spinner('Saving');
|
||||
updateCurrentTeam(config, newTeam);
|
||||
|
||||
output.stopSpinner();
|
||||
console.log(
|
||||
success(
|
||||
`The team ${chalk.bold(newTeam.name)} (${newTeam.slug}) is now active!`
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
123
packages/cli/src/commands/teams/switch.ts
Normal file
123
packages/cli/src/commands/teams/switch.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
// Packages
|
||||
import chalk from 'chalk';
|
||||
|
||||
// Utilities
|
||||
import Client from '../../util/client';
|
||||
import listInput from '../../util/input/list';
|
||||
import getUser from '../../util/get-user';
|
||||
import getTeams from '../../util/get-teams';
|
||||
import { Team, GlobalConfig } from '../../types';
|
||||
import { writeToConfigFile } from '../../util/config/files';
|
||||
|
||||
const updateCurrentTeam = (config: GlobalConfig, team?: Team) => {
|
||||
if (team) {
|
||||
config.currentTeam = team.id;
|
||||
} else {
|
||||
delete config.currentTeam;
|
||||
}
|
||||
|
||||
writeToConfigFile(config);
|
||||
};
|
||||
|
||||
export default async function main(client: Client, desiredSlug?: string) {
|
||||
const { config, output } = client;
|
||||
const personalScopeSelected = !config.currentTeam;
|
||||
|
||||
output.spinner('Fetching teams information');
|
||||
const [user, teams] = await Promise.all([getUser(client), getTeams(client)]);
|
||||
|
||||
const currentTeam = personalScopeSelected
|
||||
? undefined
|
||||
: teams.find(team => team.id === config.currentTeam);
|
||||
|
||||
if (!personalScopeSelected && !currentTeam) {
|
||||
output.error(`You are not a part of the current team anymore.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!desiredSlug) {
|
||||
const teamChoices = teams
|
||||
.slice(0)
|
||||
.sort((a, b) => {
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
})
|
||||
.map(({ id, slug, name }) => {
|
||||
let title = `${name} (${slug})`;
|
||||
const selected = id === currentTeam?.id;
|
||||
|
||||
if (selected) {
|
||||
title += ` ${chalk.bold('(current)')}`;
|
||||
}
|
||||
|
||||
return {
|
||||
name: title,
|
||||
value: slug,
|
||||
short: slug,
|
||||
selected,
|
||||
};
|
||||
});
|
||||
|
||||
// Add the User scope entry at the top
|
||||
const suffix = personalScopeSelected ? ` ${chalk.bold('(current)')}` : '';
|
||||
|
||||
const choices = [
|
||||
{ separator: 'Personal Account' },
|
||||
{
|
||||
name: `${user.email} (${user.username})${suffix}`,
|
||||
value: user.email,
|
||||
short: user.username,
|
||||
selected: personalScopeSelected,
|
||||
},
|
||||
{ separator: 'Teams' },
|
||||
...teamChoices,
|
||||
];
|
||||
|
||||
output.stopSpinner();
|
||||
desiredSlug = await listInput({
|
||||
message: 'Switch to:',
|
||||
choices,
|
||||
eraseFinalAnswer: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Abort
|
||||
if (!desiredSlug) {
|
||||
output.log('No changes made.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (desiredSlug === user.username || desiredSlug === user.email) {
|
||||
// Switch to user's personal account
|
||||
if (personalScopeSelected) {
|
||||
output.log('No changes made');
|
||||
return 0;
|
||||
}
|
||||
|
||||
updateCurrentTeam(config);
|
||||
|
||||
output.success(`Your account (${chalk.bold(desiredSlug)}) is now active!`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Switch to selected team
|
||||
const newTeam = teams.find(team => team.slug === desiredSlug);
|
||||
|
||||
if (!newTeam) {
|
||||
output.error(
|
||||
`You do not have permission to access scope ${chalk.bold(desiredSlug)}.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (newTeam.slug === currentTeam?.slug) {
|
||||
output.log('No changes made');
|
||||
return 0;
|
||||
}
|
||||
|
||||
updateCurrentTeam(config, newTeam);
|
||||
|
||||
output.success(
|
||||
`The team ${chalk.bold(newTeam.name)} (${newTeam.slug}) is now active!`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
@@ -484,7 +484,7 @@ const main = async () => {
|
||||
return 1;
|
||||
}
|
||||
|
||||
client.authConfig = { token };
|
||||
client.authConfig = { token, skipWrite: true };
|
||||
|
||||
// Don't use team from config if `--token` was set
|
||||
if (client.config && client.config.currentTeam) {
|
||||
@@ -587,10 +587,10 @@ const main = async () => {
|
||||
const eventCategory = 'Exit Code';
|
||||
|
||||
try {
|
||||
const start = new Date();
|
||||
const start = Date.now();
|
||||
const full = require(`./commands/${targetCommand}`).default;
|
||||
exitCode = await full(client);
|
||||
const end = new Date() - start;
|
||||
const end = Date.now() - start;
|
||||
|
||||
if (shouldCollectMetrics) {
|
||||
const category = 'Command Invocation';
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { fileNameSymbol } from '@vercel/client';
|
||||
|
||||
export interface AuthConfig {
|
||||
[fileNameSymbol]?: string;
|
||||
token: string;
|
||||
skipWrite?: boolean;
|
||||
}
|
||||
|
||||
export interface GlobalConfig {
|
||||
[fileNameSymbol]?: string;
|
||||
currentTeam?: string;
|
||||
collectMetrics?: boolean;
|
||||
api?: string;
|
||||
|
||||
@@ -3,7 +3,7 @@ import chalk from 'chalk';
|
||||
import Client from '../client';
|
||||
import { Output } from '../output';
|
||||
import { User } from '../../types';
|
||||
import { NowConfig } from '../dev/types';
|
||||
import { VercelConfig } from '../dev/types';
|
||||
import getDeploymentsByAppName from '../deploy/get-deployments-by-appname';
|
||||
import getDeploymentByIdOrHost from '../deploy/get-deployment-by-id-or-host';
|
||||
|
||||
@@ -35,7 +35,7 @@ export async function getDeploymentForAlias(
|
||||
localConfigPath: string | undefined,
|
||||
user: User,
|
||||
contextName: string,
|
||||
localConfig: NowConfig
|
||||
localConfig: VercelConfig
|
||||
) {
|
||||
output.spinner(`Fetching deployment to alias in ${chalk.bold(contextName)}`);
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { URLSearchParams } from 'url';
|
||||
import { EventEmitter } from 'events';
|
||||
import { parse as parseUrl } from 'url';
|
||||
import fetch, { RequestInit } from 'node-fetch';
|
||||
import fetch, { RequestInit, Response } from 'node-fetch';
|
||||
import retry, { RetryFunction, Options as RetryOptions } from 'async-retry';
|
||||
import { Output } from './output/create-output';
|
||||
import responseError from './response-error';
|
||||
import ua from './ua';
|
||||
import printIndications from './print-indications';
|
||||
import { AuthConfig, GlobalConfig } from '../types';
|
||||
import { NowConfig } from './dev/types';
|
||||
import { VercelConfig } from './dev/types';
|
||||
import doSsoLogin from './login/sso';
|
||||
import { writeToAuthConfigFile } from './config/files';
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface ClientOptions {
|
||||
authConfig: AuthConfig;
|
||||
output: Output;
|
||||
config: GlobalConfig;
|
||||
localConfig: NowConfig;
|
||||
localConfig: VercelConfig;
|
||||
}
|
||||
|
||||
export default class Client extends EventEmitter {
|
||||
@@ -37,7 +37,7 @@ export default class Client extends EventEmitter {
|
||||
authConfig: AuthConfig;
|
||||
output: Output;
|
||||
config: GlobalConfig;
|
||||
localConfig: NowConfig;
|
||||
localConfig: VercelConfig;
|
||||
|
||||
constructor(opts: ClientOptions) {
|
||||
super();
|
||||
@@ -103,10 +103,14 @@ export default class Client extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
fetch(url: string, opts: { json: false }): Promise<Response>;
|
||||
fetch<T>(url: string, opts?: FetchOptions): Promise<T>;
|
||||
async fetch<T>(url: string, opts: FetchOptions = {}): Promise<T> {
|
||||
return this.retry(async bail => {
|
||||
const res = await this._fetch(url, opts);
|
||||
|
||||
printIndications(res);
|
||||
|
||||
if (!res.ok) {
|
||||
const error = await responseError(res);
|
||||
|
||||
@@ -140,8 +144,6 @@ export default class Client extends EventEmitter {
|
||||
return null;
|
||||
}
|
||||
|
||||
printIndications(res);
|
||||
|
||||
return res.headers.get('content-type').includes('application/json')
|
||||
? res.json()
|
||||
: res;
|
||||
|
||||
@@ -8,7 +8,7 @@ import getLocalPathConfig from './local-path';
|
||||
import { NowError } from '../now-error';
|
||||
import error from '../output/error';
|
||||
import highlight from '../output/highlight';
|
||||
import { NowConfig } from '../dev/types';
|
||||
import { VercelConfig } from '../dev/types';
|
||||
import { AuthConfig, GlobalConfig } from '../../types';
|
||||
|
||||
const VERCEL_DIR = getGlobalPathConfig();
|
||||
@@ -16,19 +16,15 @@ const CONFIG_FILE_PATH = join(VERCEL_DIR, 'config.json');
|
||||
const AUTH_CONFIG_FILE_PATH = join(VERCEL_DIR, 'auth.json');
|
||||
|
||||
// reads "global config" file atomically
|
||||
export const readConfigFile = (fileName = CONFIG_FILE_PATH): GlobalConfig => {
|
||||
const config = loadJSON.sync(fileName);
|
||||
config[fileNameSymbol] = fileName;
|
||||
export const readConfigFile = (): GlobalConfig => {
|
||||
const config = loadJSON.sync(CONFIG_FILE_PATH);
|
||||
return config;
|
||||
};
|
||||
|
||||
// writes whatever's in `stuff` to "global config" file, atomically
|
||||
export const writeToConfigFile = (stuff: GlobalConfig): void => {
|
||||
const fileName = stuff[fileNameSymbol];
|
||||
if (!fileName) return;
|
||||
|
||||
try {
|
||||
return writeJSON.sync(fileName, stuff, { indent: 2 });
|
||||
return writeJSON.sync(CONFIG_FILE_PATH, stuff, { indent: 2 });
|
||||
} catch (err) {
|
||||
if (err.code === 'EPERM') {
|
||||
console.error(
|
||||
@@ -55,21 +51,17 @@ export const writeToConfigFile = (stuff: GlobalConfig): void => {
|
||||
};
|
||||
|
||||
// reads "auth config" file atomically
|
||||
export const readAuthConfigFile = (
|
||||
fileName = AUTH_CONFIG_FILE_PATH
|
||||
): AuthConfig => {
|
||||
const config = loadJSON.sync(fileName);
|
||||
config[fileNameSymbol] = fileName;
|
||||
export const readAuthConfigFile = (): AuthConfig => {
|
||||
const config = loadJSON.sync(AUTH_CONFIG_FILE_PATH);
|
||||
return config;
|
||||
};
|
||||
|
||||
// writes whatever's in `stuff` to "auth config" file, atomically
|
||||
export const writeToAuthConfigFile = (stuff: AuthConfig) => {
|
||||
const fileName = stuff[fileNameSymbol];
|
||||
if (!fileName) return;
|
||||
|
||||
export const writeToAuthConfigFile = (authConfig: AuthConfig) => {
|
||||
if (authConfig.skipWrite) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
return writeJSON.sync(fileName, stuff, {
|
||||
return writeJSON.sync(AUTH_CONFIG_FILE_PATH, authConfig, {
|
||||
indent: 2,
|
||||
mode: 0o600,
|
||||
});
|
||||
@@ -108,8 +100,8 @@ export function getAuthConfigFilePath() {
|
||||
|
||||
export function readLocalConfig(
|
||||
prefix: string = process.cwd()
|
||||
): NowConfig | null {
|
||||
let config: NowConfig | null = null;
|
||||
): VercelConfig | null {
|
||||
let config: VercelConfig | null = null;
|
||||
let target = '';
|
||||
|
||||
try {
|
||||
|
||||
@@ -66,7 +66,7 @@ export const getDefaultAuthConfig = async existing => {
|
||||
|
||||
const config = {
|
||||
_:
|
||||
'This is your Now 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) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { join } from 'path';
|
||||
import { CantParseJSONFile } from '../errors-ts';
|
||||
import readJSONFile from '../read-json-file';
|
||||
import { NowConfig } from '../dev/types';
|
||||
import { VercelConfig } from '../dev/types';
|
||||
import getLocalConfigPath from './local-path';
|
||||
|
||||
export default async function readConfig(dir: string) {
|
||||
@@ -13,7 +13,7 @@ export default async function readConfig(dir: string) {
|
||||
}
|
||||
|
||||
if (result) {
|
||||
return result as NowConfig;
|
||||
return result as VercelConfig;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { Output } from '../output';
|
||||
// @ts-ignore
|
||||
import Now from '../../util';
|
||||
import { NowConfig } from '../dev/types';
|
||||
import { VercelConfig } from '../dev/types';
|
||||
import { Org } from '../../types';
|
||||
import ua from '../ua';
|
||||
import { linkFolderToProject } from '../projects/link';
|
||||
@@ -43,7 +43,7 @@ export default async function processDeployment({
|
||||
uploadStamp: () => string;
|
||||
deployStamp: () => string;
|
||||
quiet: boolean;
|
||||
nowConfig?: NowConfig;
|
||||
nowConfig?: VercelConfig;
|
||||
force?: boolean;
|
||||
withCache?: boolean;
|
||||
org: Org;
|
||||
|
||||
@@ -27,7 +27,7 @@ import { LambdaSizeExceededError } from '../errors-ts';
|
||||
import DevServer from './server';
|
||||
import { getBuilder } from './builder-cache';
|
||||
import {
|
||||
NowConfig,
|
||||
VercelConfig,
|
||||
BuildMatch,
|
||||
BuildResult,
|
||||
BuilderInputs,
|
||||
@@ -96,7 +96,7 @@ async function createBuildProcess(
|
||||
}
|
||||
|
||||
export async function executeBuild(
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
devServer: DevServer,
|
||||
files: BuilderInputs,
|
||||
match: BuildMatch,
|
||||
@@ -383,7 +383,7 @@ export async function executeBuild(
|
||||
}
|
||||
|
||||
export async function getBuildMatches(
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
cwd: string,
|
||||
output: Output,
|
||||
devServer: DevServer,
|
||||
|
||||
@@ -4,7 +4,7 @@ import PCRE from 'pcre-to-regexp';
|
||||
import isURL from './is-url';
|
||||
import DevServer from './server';
|
||||
|
||||
import { NowConfig, HttpHeadersConfig, RouteResult } from './types';
|
||||
import { VercelConfig, HttpHeadersConfig, RouteResult } from './types';
|
||||
import { isHandler, Route, HandleValue } from '@vercel/routing-utils';
|
||||
|
||||
export function resolveRouteParameters(
|
||||
@@ -50,7 +50,7 @@ export async function devRouter(
|
||||
reqMethod?: string,
|
||||
routes?: Route[],
|
||||
devServer?: DevServer,
|
||||
nowConfig?: NowConfig,
|
||||
nowConfig?: VercelConfig,
|
||||
previousHeaders?: HttpHeadersConfig,
|
||||
missRoutes?: Route[],
|
||||
phase?: HandleValue | null
|
||||
|
||||
@@ -49,7 +49,7 @@ import sleep from '../sleep';
|
||||
import { Output } from '../output';
|
||||
import { relative } from '../path-helpers';
|
||||
import { getDistTag } from '../get-dist-tag';
|
||||
import getNowConfigPath from '../config/local-path';
|
||||
import getVercelConfigPath from '../config/local-path';
|
||||
import { MissingDotenvVarsError } from '../errors-ts';
|
||||
import cliPkg from '../pkg';
|
||||
import { getVercelDirectory } from '../projects/link';
|
||||
@@ -73,7 +73,7 @@ import errorTemplate502 from './templates/error_502';
|
||||
import redirectTemplate from './templates/redirect';
|
||||
|
||||
import {
|
||||
NowConfig,
|
||||
VercelConfig,
|
||||
DevServerOptions,
|
||||
BuildMatch,
|
||||
BuildResult,
|
||||
@@ -145,7 +145,7 @@ export default class DevServer {
|
||||
private devServerPids: Set<number>;
|
||||
private projectSettings?: ProjectSettings;
|
||||
|
||||
private getNowConfigPromise: Promise<NowConfig> | null;
|
||||
private getVercelConfigPromise: Promise<VercelConfig> | null;
|
||||
private blockingBuildsPromise: Promise<void> | null;
|
||||
private updateBuildersPromise: Promise<void> | null;
|
||||
private updateBuildersTimeout: NodeJS.Timeout | undefined;
|
||||
@@ -181,7 +181,7 @@ export default class DevServer {
|
||||
this.inProgressBuilds = new Map();
|
||||
this.devCacheDir = join(getVercelDirectory(cwd), 'cache');
|
||||
|
||||
this.getNowConfigPromise = null;
|
||||
this.getVercelConfigPromise = null;
|
||||
this.blockingBuildsPromise = null;
|
||||
this.updateBuildersPromise = null;
|
||||
this.startPromise = null;
|
||||
@@ -244,7 +244,7 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
|
||||
const nowConfig = await this.getNowConfig();
|
||||
const nowConfig = await this.getVercelConfig();
|
||||
|
||||
// Update the build matches in case an entrypoint was created or deleted
|
||||
await this.updateBuildMatches(nowConfig);
|
||||
@@ -375,7 +375,7 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
async updateBuildMatches(
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
isInitial = false
|
||||
): Promise<void> {
|
||||
const fileList = this.resolveBuildFiles(this.files);
|
||||
@@ -460,7 +460,7 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
async invalidateBuildMatches(
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
updatedBuilders: string[]
|
||||
): Promise<void> {
|
||||
if (updatedBuilders.length === 0) {
|
||||
@@ -516,25 +516,25 @@ export default class DevServer {
|
||||
return {};
|
||||
}
|
||||
|
||||
clearNowConfigPromise = () => {
|
||||
this.getNowConfigPromise = null;
|
||||
clearVercelConfigPromise = () => {
|
||||
this.getVercelConfigPromise = null;
|
||||
};
|
||||
|
||||
getNowConfig(): Promise<NowConfig> {
|
||||
if (this.getNowConfigPromise) {
|
||||
return this.getNowConfigPromise;
|
||||
getVercelConfig(): Promise<VercelConfig> {
|
||||
if (this.getVercelConfigPromise) {
|
||||
return this.getVercelConfigPromise;
|
||||
}
|
||||
this.getNowConfigPromise = this._getNowConfig();
|
||||
this.getVercelConfigPromise = this._getVercelConfig();
|
||||
|
||||
// Clean up the promise once it has resolved
|
||||
const clear = this.clearNowConfigPromise;
|
||||
this.getNowConfigPromise.finally(clear);
|
||||
const clear = this.clearVercelConfigPromise;
|
||||
this.getVercelConfigPromise.finally(clear);
|
||||
|
||||
return this.getNowConfigPromise;
|
||||
return this.getVercelConfigPromise;
|
||||
}
|
||||
|
||||
async _getNowConfig(): Promise<NowConfig> {
|
||||
const configPath = getNowConfigPath(this.cwd);
|
||||
async _getVercelConfig(): Promise<VercelConfig> {
|
||||
const configPath = getVercelConfigPath(this.cwd);
|
||||
|
||||
const [
|
||||
pkg = null,
|
||||
@@ -543,10 +543,10 @@ export default class DevServer {
|
||||
config = { version: 2, [fileNameSymbol]: 'vercel.json' },
|
||||
] = await Promise.all([
|
||||
this.readJsonFile<PackageJson>('package.json'),
|
||||
this.readJsonFile<NowConfig>(configPath),
|
||||
this.readJsonFile<VercelConfig>(configPath),
|
||||
]);
|
||||
|
||||
await this.validateNowConfig(config);
|
||||
await this.validateVercelConfig(config);
|
||||
const { error: routeError, routes: maybeRoutes } = getTransformedRoutes({
|
||||
nowConfig: config,
|
||||
});
|
||||
@@ -634,7 +634,7 @@ export default class DevServer {
|
||||
config.builds.sort(sortBuilders);
|
||||
}
|
||||
|
||||
await this.validateNowConfig(config);
|
||||
await this.validateVercelConfig(config);
|
||||
|
||||
this.caseSensitive = hasNewRoutingProperties(config);
|
||||
this.apiDir = detectApiDirectory(config.builds || []);
|
||||
@@ -710,8 +710,8 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
async tryValidateOrExit(
|
||||
config: NowConfig,
|
||||
validate: (c: NowConfig) => string | null
|
||||
config: VercelConfig,
|
||||
validate: (c: VercelConfig) => string | null
|
||||
): Promise<void> {
|
||||
const message = validate(config);
|
||||
|
||||
@@ -721,7 +721,7 @@ export default class DevServer {
|
||||
}
|
||||
}
|
||||
|
||||
async validateNowConfig(config: NowConfig): Promise<void> {
|
||||
async validateVercelConfig(config: VercelConfig): Promise<void> {
|
||||
if (config.version === 1) {
|
||||
this.output.error('Cannot run `version: 1` projects.');
|
||||
await this.exit(1);
|
||||
@@ -855,7 +855,7 @@ export default class DevServer {
|
||||
.replace('[::]', 'localhost')
|
||||
.replace('127.0.0.1', 'localhost');
|
||||
|
||||
const nowConfig = await this.getNowConfig();
|
||||
const nowConfig = await this.getVercelConfig();
|
||||
const devCommandPromise = this.runDevCommand();
|
||||
|
||||
const files = await getFiles(this.cwd, { output: this.output });
|
||||
@@ -982,7 +982,7 @@ export default class DevServer {
|
||||
|
||||
if (devProcess) {
|
||||
ops.push(
|
||||
new Promise((resolve, reject) => {
|
||||
new Promise<void>((resolve, reject) => {
|
||||
devProcess.once('exit', () => resolve());
|
||||
try {
|
||||
process.kill(devProcess.pid);
|
||||
@@ -1201,7 +1201,7 @@ export default class DevServer {
|
||||
match: BuildMatch,
|
||||
requestPath: string | null,
|
||||
req: http.IncomingMessage | null,
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
previousBuildResult?: BuildResult,
|
||||
filesChanged?: string[],
|
||||
filesRemoved?: string[]
|
||||
@@ -1290,7 +1290,7 @@ export default class DevServer {
|
||||
this.output.debug(`${chalk.bold(method)} ${req.url}`);
|
||||
|
||||
try {
|
||||
const nowConfig = await this.getNowConfig();
|
||||
const nowConfig = await this.getVercelConfig();
|
||||
await this.serveProjectAsNowV2(req, res, nowRequestId, nowConfig);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -1339,7 +1339,7 @@ export default class DevServer {
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
nowRequestId: string,
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
routes: Route[] | undefined = nowConfig.routes,
|
||||
callLevel: number = 0
|
||||
) => {
|
||||
@@ -1992,7 +1992,7 @@ export default class DevServer {
|
||||
return true;
|
||||
}
|
||||
|
||||
async hasFilesystem(dest: string, nowConfig: NowConfig): Promise<boolean> {
|
||||
async hasFilesystem(dest: string, nowConfig: VercelConfig): Promise<boolean> {
|
||||
if (
|
||||
await findBuildMatch(
|
||||
this.buildMatches,
|
||||
@@ -2181,7 +2181,7 @@ async function findBuildMatch(
|
||||
files: BuilderInputs,
|
||||
requestPath: string,
|
||||
devServer: DevServer,
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
isFilesystem = false
|
||||
): Promise<BuildMatch | null> {
|
||||
requestPath = requestPath.replace(/^\//, '');
|
||||
@@ -2219,7 +2219,7 @@ async function shouldServe(
|
||||
files: BuilderInputs,
|
||||
requestPath: string,
|
||||
devServer: DevServer,
|
||||
nowConfig: NowConfig,
|
||||
nowConfig: VercelConfig,
|
||||
isFilesystem = false
|
||||
): Promise<boolean> {
|
||||
const {
|
||||
@@ -2284,7 +2284,7 @@ async function findMatchingRoute(
|
||||
match: BuildMatch,
|
||||
requestPath: string,
|
||||
devServer: DevServer,
|
||||
nowConfig: NowConfig
|
||||
nowConfig: VercelConfig
|
||||
): Promise<RouteResult | void> {
|
||||
const reqUrl = `/${requestPath}`;
|
||||
for (const buildResult of match.buildResults.values()) {
|
||||
@@ -2305,7 +2305,7 @@ async function findMatchingRoute(
|
||||
function findAsset(
|
||||
match: BuildMatch,
|
||||
requestPath: string,
|
||||
nowConfig: NowConfig
|
||||
nowConfig: VercelConfig
|
||||
): { asset: BuilderOutput; assetKey: string } | void {
|
||||
if (!match.buildOutput) {
|
||||
return;
|
||||
@@ -2397,7 +2397,7 @@ function filterFrontendBuilds(build: Builder) {
|
||||
return !frontendRuntimeSet.has(name || '');
|
||||
}
|
||||
|
||||
function hasNewRoutingProperties(nowConfig: NowConfig) {
|
||||
function hasNewRoutingProperties(nowConfig: VercelConfig) {
|
||||
return (
|
||||
typeof nowConfig.cleanUrls !== undefined ||
|
||||
typeof nowConfig.headers !== undefined ||
|
||||
|
||||
@@ -14,12 +14,12 @@ import {
|
||||
Lambda,
|
||||
PackageJson,
|
||||
} from '@vercel/build-utils';
|
||||
import { NowConfig } from '@vercel/client';
|
||||
import { VercelConfig } from '@vercel/client';
|
||||
import { HandleValue, Route } from '@vercel/routing-utils';
|
||||
import { Output } from '../output';
|
||||
import { ProjectEnvVariable, ProjectSettings } from '../../types';
|
||||
|
||||
export { NowConfig };
|
||||
export { VercelConfig };
|
||||
|
||||
export interface DevServerOptions {
|
||||
output: Output;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
rewritesSchema,
|
||||
trailingSlashSchema,
|
||||
} from '@vercel/routing-utils';
|
||||
import { NowConfig } from './types';
|
||||
import { VercelConfig } from './types';
|
||||
import {
|
||||
functionsSchema,
|
||||
buildsSchema,
|
||||
@@ -36,7 +36,7 @@ const vercelConfigSchema = {
|
||||
const ajv = new Ajv();
|
||||
const validate = ajv.compile(vercelConfigSchema);
|
||||
|
||||
export function validateConfig(config: NowConfig): NowBuildError | null {
|
||||
export function validateConfig(config: VercelConfig): NowBuildError | null {
|
||||
if (!validate(config)) {
|
||||
if (validate.errors && validate.errors[0]) {
|
||||
const error = validate.errors[0];
|
||||
|
||||
@@ -1,28 +1,16 @@
|
||||
export type EmojiLabel =
|
||||
| 'notice'
|
||||
| 'tip'
|
||||
| 'warning'
|
||||
| 'link'
|
||||
| 'inspect'
|
||||
| 'success';
|
||||
export const emojiLabels = {
|
||||
notice: '📝',
|
||||
tip: '💡',
|
||||
warning: '❗️',
|
||||
link: '🔗',
|
||||
inspect: '🔍',
|
||||
success: '✅',
|
||||
} as const;
|
||||
|
||||
export function emoji(label: EmojiLabel): string | undefined {
|
||||
switch (label) {
|
||||
case 'notice':
|
||||
return '📝';
|
||||
case 'tip':
|
||||
return '💡';
|
||||
case 'warning':
|
||||
return '❗️';
|
||||
case 'link':
|
||||
return '🔗';
|
||||
case 'inspect':
|
||||
return '🔍';
|
||||
case 'success':
|
||||
return '✅';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
export type EmojiLabel = keyof typeof emojiLabels;
|
||||
|
||||
export function emoji(label: EmojiLabel) {
|
||||
return emojiLabels[label];
|
||||
}
|
||||
|
||||
export function prependEmoji(message: string, emoji?: string): string {
|
||||
|
||||
@@ -2,52 +2,41 @@
|
||||
import { URLSearchParams } from 'url';
|
||||
|
||||
// Packages
|
||||
import retry from 'async-retry';
|
||||
import jsonlines from 'jsonlines';
|
||||
import { eraseLines } from 'ansi-escapes';
|
||||
|
||||
import jsonlines from 'jsonlines';
|
||||
import retry from 'async-retry';
|
||||
import Client from './client';
|
||||
import { getDeployment } from './get-deployment';
|
||||
|
||||
export interface FindOpts {
|
||||
direction: 'forward' | 'backward';
|
||||
limit?: number;
|
||||
since?: number;
|
||||
until?: number;
|
||||
follow?: boolean;
|
||||
}
|
||||
|
||||
export interface PrintEventsOptions {
|
||||
mode: string;
|
||||
onEvent: (event: DeploymentEvent) => void;
|
||||
quiet?: boolean;
|
||||
findOpts: FindOpts;
|
||||
}
|
||||
|
||||
export interface DeploymentEvent {
|
||||
id: string;
|
||||
created: number;
|
||||
date?: number;
|
||||
serial?: string;
|
||||
}
|
||||
|
||||
async function printEvents(
|
||||
now,
|
||||
deploymentIdOrURL,
|
||||
currentTeam = null,
|
||||
{
|
||||
mode,
|
||||
onOpen = () => {},
|
||||
onEvent,
|
||||
quiet,
|
||||
debugEnabled,
|
||||
findOpts,
|
||||
output,
|
||||
} = {}
|
||||
client: Client,
|
||||
deploymentIdOrURL: string,
|
||||
{ mode, onEvent, quiet, findOpts }: PrintEventsOptions
|
||||
) {
|
||||
const { log, debug } = output;
|
||||
|
||||
let onOpenCalled = false;
|
||||
function callOnOpenOnce() {
|
||||
if (onOpenCalled) return;
|
||||
onOpenCalled = true;
|
||||
onOpen();
|
||||
}
|
||||
|
||||
const query = new URLSearchParams({
|
||||
direction: findOpts.direction,
|
||||
limit: findOpts.limit,
|
||||
since: findOpts.since,
|
||||
until: findOpts.until,
|
||||
follow: findOpts.follow ? '1' : '',
|
||||
format: 'lines',
|
||||
});
|
||||
|
||||
let eventsUrl = `/v1/now/deployments/${deploymentIdOrURL}/events?${query}`;
|
||||
let pollUrl = `/v3/now/deployments/${deploymentIdOrURL}`;
|
||||
|
||||
if (currentTeam) {
|
||||
eventsUrl += `&teamId=${currentTeam.id}`;
|
||||
pollUrl += `?teamId=${currentTeam.id}`;
|
||||
}
|
||||
|
||||
debug(`Events ${eventsUrl}`);
|
||||
const { log, debug } = client.output;
|
||||
|
||||
// we keep track of how much we log in case we
|
||||
// drop the connection and have to start over
|
||||
@@ -59,29 +48,34 @@ async function printEvents(
|
||||
debug('Retrying events');
|
||||
}
|
||||
|
||||
const eventsRes = await now._fetch(eventsUrl);
|
||||
const query = new URLSearchParams({
|
||||
direction: findOpts.direction,
|
||||
follow: findOpts.follow ? '1' : '',
|
||||
format: 'lines',
|
||||
});
|
||||
if (findOpts.limit) query.set('limit', String(findOpts.limit));
|
||||
if (findOpts.since) query.set('since', String(findOpts.since));
|
||||
if (findOpts.until) query.set('until', String(findOpts.until));
|
||||
|
||||
const eventsUrl = `/v1/now/deployments/${deploymentIdOrURL}/events?${query}`;
|
||||
const eventsRes = await client.fetch(eventsUrl, { json: false });
|
||||
|
||||
if (eventsRes.ok) {
|
||||
const readable = eventsRes.readable
|
||||
? await eventsRes.readable()
|
||||
: eventsRes.body;
|
||||
const readable = eventsRes.body;
|
||||
|
||||
// handle the event stream and make the promise get rejected
|
||||
// if errors occur so we can retry
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const stream = readable.pipe(jsonlines.parse());
|
||||
|
||||
let poller;
|
||||
let poller: ReturnType<typeof setTimeout>;
|
||||
|
||||
if (mode === 'deploy') {
|
||||
poller = (function startPoller() {
|
||||
return setTimeout(async () => {
|
||||
try {
|
||||
const pollRes = await now._fetch(pollUrl);
|
||||
if (!pollRes.ok)
|
||||
throw new Error(`Response ${pollRes.status}`);
|
||||
const json = await pollRes.json();
|
||||
if (json.state === 'READY') {
|
||||
const json = await getDeployment(client, deploymentIdOrURL);
|
||||
if (json.readyState === 'READY') {
|
||||
stream.end();
|
||||
finish();
|
||||
return;
|
||||
@@ -96,10 +90,9 @@ async function printEvents(
|
||||
}
|
||||
|
||||
let finishCalled = false;
|
||||
function finish(error) {
|
||||
function finish(error?: Error) {
|
||||
if (finishCalled) return;
|
||||
finishCalled = true;
|
||||
callOnOpenOnce();
|
||||
clearTimeout(poller);
|
||||
if (error) {
|
||||
reject(error);
|
||||
@@ -110,26 +103,24 @@ async function printEvents(
|
||||
|
||||
let latestLogDate = 0;
|
||||
|
||||
const onData = data => {
|
||||
const { event } = data;
|
||||
if (event === 'state' && data.payload.value === 'READY') {
|
||||
const onData = (data: any) => {
|
||||
const { event, payload, date } = data;
|
||||
if (event === 'state' && payload.value === 'READY') {
|
||||
if (mode === 'deploy') {
|
||||
stream.end();
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
latestLogDate = Math.max(latestLogDate, data.date);
|
||||
const linesPrinted = onEvent(data, callOnOpenOnce);
|
||||
o += linesPrinted || 0;
|
||||
latestLogDate = Math.max(latestLogDate, date);
|
||||
onEvent(data);
|
||||
}
|
||||
};
|
||||
|
||||
let onErrorCalled = false;
|
||||
const onError = err => {
|
||||
const onError = (err: Error) => {
|
||||
if (finishCalled || onErrorCalled) return;
|
||||
onErrorCalled = true;
|
||||
o++;
|
||||
callOnOpenOnce();
|
||||
|
||||
const errorMessage = `Deployment event stream error: ${err.message}`;
|
||||
if (!findOpts.follow) {
|
||||
@@ -140,7 +131,6 @@ async function printEvents(
|
||||
debug(errorMessage);
|
||||
clearTimeout(poller);
|
||||
stream.destroy(err);
|
||||
readable.destroy(err);
|
||||
|
||||
const retryFindOpts = {
|
||||
...findOpts,
|
||||
@@ -149,12 +139,10 @@ async function printEvents(
|
||||
|
||||
setTimeout(() => {
|
||||
// retry without maximum amount nor clear past logs etc
|
||||
printEvents(now, deploymentIdOrURL, currentTeam, {
|
||||
printEvents(client, deploymentIdOrURL, {
|
||||
mode,
|
||||
onOpen,
|
||||
onEvent,
|
||||
quiet,
|
||||
debugEnabled,
|
||||
findOpts: retryFindOpts,
|
||||
}).then(resolve, reject);
|
||||
}, 2000);
|
||||
@@ -166,7 +154,6 @@ async function printEvents(
|
||||
readable.on('error', onError);
|
||||
});
|
||||
}
|
||||
callOnOpenOnce();
|
||||
const err = new Error(`Deployment events status ${eventsRes.status}`);
|
||||
|
||||
if (eventsRes.status < 500) {
|
||||
@@ -9,15 +9,15 @@ import {
|
||||
import humanizePath from './humanize-path';
|
||||
import readJSONFile from './read-json-file';
|
||||
import readPackage from './read-package';
|
||||
import { NowConfig } from './dev/types';
|
||||
import { VercelConfig } from './dev/types';
|
||||
import { Output } from './output';
|
||||
|
||||
let config: NowConfig;
|
||||
let config: VercelConfig;
|
||||
|
||||
export default async function getConfig(
|
||||
output: Output,
|
||||
configFile?: string
|
||||
): Promise<NowConfig | Error> {
|
||||
): Promise<VercelConfig | Error> {
|
||||
// If config was already read, just return it
|
||||
if (config) {
|
||||
return config;
|
||||
@@ -44,7 +44,7 @@ export default async function getConfig(
|
||||
return localConfig;
|
||||
}
|
||||
if (localConfig !== null) {
|
||||
config = localConfig as NowConfig;
|
||||
config = localConfig as VercelConfig;
|
||||
config[fileNameSymbol] = configFile;
|
||||
return config;
|
||||
}
|
||||
@@ -68,13 +68,13 @@ export default async function getConfig(
|
||||
}
|
||||
if (vercelConfig !== null) {
|
||||
output.debug(`Found config in file "${vercelFilePath}"`);
|
||||
config = vercelConfig as NowConfig;
|
||||
config = vercelConfig as VercelConfig;
|
||||
config[fileNameSymbol] = 'vercel.json';
|
||||
return config;
|
||||
}
|
||||
if (nowConfig !== null) {
|
||||
output.debug(`Found config in file "${nowFilePath}"`);
|
||||
config = nowConfig as NowConfig;
|
||||
config = nowConfig as VercelConfig;
|
||||
config[fileNameSymbol] = 'now.json';
|
||||
return config;
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export default async function getConfig(
|
||||
}
|
||||
if (pkgConfig) {
|
||||
output.debug(`Found config in package ${pkgFilePath}`);
|
||||
config = pkgConfig as NowConfig;
|
||||
config = pkgConfig as VercelConfig;
|
||||
config[fileNameSymbol] = 'package.json';
|
||||
return config;
|
||||
}
|
||||
|
||||
27
packages/cli/src/util/get-deployment.ts
Normal file
27
packages/cli/src/util/get-deployment.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { stringify } from 'querystring';
|
||||
import { Deployment } from '@vercel/client';
|
||||
import Client from './client';
|
||||
|
||||
export async function getDeployment(
|
||||
client: Client,
|
||||
hostOrId: string
|
||||
): Promise<Deployment> {
|
||||
let url = `/v13/deployments`;
|
||||
|
||||
if (hostOrId.includes('.')) {
|
||||
let host = hostOrId.replace(/^https:\/\//i, '');
|
||||
|
||||
if (host.slice(-1) === '/') {
|
||||
host = host.slice(0, -1);
|
||||
}
|
||||
|
||||
url += `/get?${stringify({
|
||||
url: host,
|
||||
})}`;
|
||||
} else {
|
||||
url += `/${encodeURIComponent(hostOrId)}`;
|
||||
}
|
||||
|
||||
const deployment = await client.fetch<Deployment>(url);
|
||||
return deployment;
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import Client from './client';
|
||||
import { APIError, InvalidToken } from './errors-ts';
|
||||
import { Team } from '../types';
|
||||
// @ts-ignore
|
||||
import NowTeams from './teams.js';
|
||||
import { APIError, InvalidToken } from './errors-ts';
|
||||
|
||||
let teams: Team[] | undefined;
|
||||
|
||||
@@ -10,15 +8,11 @@ export default async function getTeams(client: Client): Promise<Team[]> {
|
||||
if (teams) return teams;
|
||||
|
||||
try {
|
||||
// we're using NowTeams because `client.fetch` hangs on windows
|
||||
const teamClient = new NowTeams({
|
||||
apiUrl: client.apiUrl,
|
||||
token: client.authConfig.token,
|
||||
debug: client.output.isDebugEnabled(),
|
||||
const body = await client.fetch<{ teams: Team[] }>('/v1/teams', {
|
||||
useCurrentTeam: false,
|
||||
});
|
||||
|
||||
teams = (await teamClient.ls()).teams;
|
||||
return teams || [];
|
||||
teams = body.teams || [];
|
||||
return teams;
|
||||
} catch (error) {
|
||||
if (error instanceof APIError && error.status === 403) {
|
||||
throw new InvalidToken();
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import inquirer from 'inquirer';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import eraseLines from '../output/erase-lines';
|
||||
|
||||
function getLength(string) {
|
||||
let biggestLength = 0;
|
||||
string.split('\n').map(str => {
|
||||
str = stripAnsi(str);
|
||||
if (str.length > biggestLength) {
|
||||
biggestLength = str.length;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
return biggestLength;
|
||||
}
|
||||
|
||||
export default async function({
|
||||
message = 'the question',
|
||||
// eslint-disable-line no-unused-vars
|
||||
choices = [
|
||||
{
|
||||
name: 'something\ndescription\ndetails\netc',
|
||||
value: 'something unique',
|
||||
short: 'generally the first line of `name`',
|
||||
},
|
||||
],
|
||||
pageSize = 15, // Show 15 lines without scrolling (~4 credit cards)
|
||||
separator = true, // Puts a blank separator between each choice
|
||||
abort = 'end', // Wether the `abort` option will be at the `start` or the `end`,
|
||||
eraseFinalAnswer = false, // If true, the line with the final answee that inquirer prints will be erased before returning
|
||||
}) {
|
||||
require('./patch-inquirer-legacy');
|
||||
|
||||
let biggestLength = 0;
|
||||
|
||||
choices = choices.map(choice => {
|
||||
if (choice.name) {
|
||||
const length = getLength(choice.name);
|
||||
if (length > biggestLength) {
|
||||
biggestLength = length;
|
||||
}
|
||||
return choice;
|
||||
}
|
||||
throw new Error('Invalid choice');
|
||||
});
|
||||
|
||||
if (separator === true) {
|
||||
choices = choices.reduce(
|
||||
(prev, curr) => prev.concat(new inquirer.Separator(' '), curr),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
const abortSeparator = new inquirer.Separator('─'.repeat(biggestLength));
|
||||
const _abort = {
|
||||
name: 'Abort',
|
||||
value: undefined,
|
||||
};
|
||||
|
||||
if (abort === 'start') {
|
||||
const blankSep = choices.shift();
|
||||
choices.unshift(abortSeparator);
|
||||
choices.unshift(_abort);
|
||||
choices.unshift(blankSep);
|
||||
} else {
|
||||
choices.push(abortSeparator);
|
||||
choices.push(_abort);
|
||||
}
|
||||
|
||||
const nonce = Date.now();
|
||||
const answer = await inquirer.prompt({
|
||||
name: nonce,
|
||||
type: 'list',
|
||||
message,
|
||||
choices,
|
||||
pageSize,
|
||||
});
|
||||
if (eraseFinalAnswer === true) {
|
||||
process.stdout.write(eraseLines(2));
|
||||
}
|
||||
return answer[nonce];
|
||||
}
|
||||
123
packages/cli/src/util/input/list.ts
Normal file
123
packages/cli/src/util/input/list.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import inquirer from 'inquirer';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import eraseLines from '../output/erase-lines';
|
||||
|
||||
interface ListEntry {
|
||||
name: string;
|
||||
value: string;
|
||||
short: string;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
interface ListSeparator {
|
||||
separator: string;
|
||||
}
|
||||
|
||||
type ListChoice = ListEntry | ListSeparator | typeof inquirer.Separator;
|
||||
|
||||
interface ListOptions {
|
||||
message: string;
|
||||
choices: ListChoice[];
|
||||
pageSize?: number;
|
||||
separator?: boolean;
|
||||
abort?: 'start' | 'end';
|
||||
eraseFinalAnswer?: boolean;
|
||||
}
|
||||
|
||||
function getLength(input: string): number {
|
||||
let biggestLength = 0;
|
||||
for (const line of input.split('\n')) {
|
||||
const str = stripAnsi(line);
|
||||
if (str.length > biggestLength) {
|
||||
biggestLength = str.length;
|
||||
}
|
||||
}
|
||||
return biggestLength;
|
||||
}
|
||||
|
||||
export default async function list({
|
||||
message = 'the question',
|
||||
// eslint-disable-line no-unused-vars
|
||||
choices: _choices = [
|
||||
{
|
||||
name: 'something\ndescription\ndetails\netc',
|
||||
value: 'something unique',
|
||||
short: 'generally the first line of `name`',
|
||||
},
|
||||
],
|
||||
pageSize = 15, // Show 15 lines without scrolling (~4 credit cards)
|
||||
separator = false, // Puts a blank separator between each choice
|
||||
abort = 'end', // Whether the `abort` option will be at the `start` or the `end`,
|
||||
eraseFinalAnswer = false, // If true, the line with the final answer that inquirer prints will be erased before returning
|
||||
}: ListOptions): Promise<string> {
|
||||
require('./patch-inquirer-legacy');
|
||||
|
||||
let biggestLength = 0;
|
||||
let selected: string | undefined;
|
||||
|
||||
// First calculate the biggest length
|
||||
for (const choice of _choices) {
|
||||
if ('name' in choice) {
|
||||
const length = getLength(choice.name);
|
||||
if (length > biggestLength) {
|
||||
biggestLength = length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const choices = _choices.map(choice => {
|
||||
if (choice instanceof inquirer.Separator) {
|
||||
return choice;
|
||||
}
|
||||
|
||||
if ('separator' in choice) {
|
||||
const prefix = `── ${choice.separator} `;
|
||||
const suffix = '─'.repeat(biggestLength - getLength(prefix));
|
||||
return new inquirer.Separator(`${prefix}${suffix}`);
|
||||
}
|
||||
|
||||
if ('short' in choice) {
|
||||
if (choice.selected) {
|
||||
if (selected) throw new Error('Only one choice may be selected');
|
||||
selected = choice.short;
|
||||
}
|
||||
return choice;
|
||||
}
|
||||
|
||||
throw new Error('Invalid choice');
|
||||
});
|
||||
|
||||
if (separator) {
|
||||
for (let i = 0; i < choices.length; i += 2) {
|
||||
choices.splice(i, 0, new inquirer.Separator(' '));
|
||||
}
|
||||
}
|
||||
|
||||
const abortSeparator = new inquirer.Separator('─'.repeat(biggestLength));
|
||||
const _abort = {
|
||||
name: 'Abort',
|
||||
value: '',
|
||||
short: '',
|
||||
};
|
||||
|
||||
if (abort === 'start') {
|
||||
choices.unshift(_abort, abortSeparator);
|
||||
} else {
|
||||
choices.push(abortSeparator, _abort);
|
||||
}
|
||||
|
||||
const answer = await inquirer.prompt({
|
||||
name: 'value',
|
||||
type: 'list',
|
||||
default: selected,
|
||||
message,
|
||||
choices,
|
||||
pageSize,
|
||||
});
|
||||
|
||||
if (eraseFinalAnswer === true) {
|
||||
process.stdout.write(eraseLines(2));
|
||||
}
|
||||
|
||||
return answer.value;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { join, basename } from 'path';
|
||||
import chalk from 'chalk';
|
||||
import { remove } from 'fs-extra';
|
||||
import { ProjectLinkResult, ProjectSettings } from '../../types';
|
||||
import { NowConfig } from '../dev/types';
|
||||
import { VercelConfig } from '../dev/types';
|
||||
import {
|
||||
getLinkedProject,
|
||||
linkFolderToProject,
|
||||
@@ -134,7 +134,7 @@ export default async function setupAndLink(
|
||||
return { status: 'error', exitCode: 1 };
|
||||
}
|
||||
|
||||
let localConfig: NowConfig = {};
|
||||
let localConfig: VercelConfig = {};
|
||||
if (client.localConfig && !(client.localConfig instanceof Error)) {
|
||||
localConfig = client.localConfig;
|
||||
}
|
||||
|
||||
13
packages/cli/src/util/login/bitbucket.ts
Normal file
13
packages/cli/src/util/login/bitbucket.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { URL } from 'url';
|
||||
import { LoginParams } from './types';
|
||||
import doOauthLogin from './oauth';
|
||||
|
||||
export default function doBitbucketLogin(params: LoginParams) {
|
||||
const url = new URL(
|
||||
'/api/registration/bitbucket/connect',
|
||||
// Can't use `apiUrl` here because this URL sets a
|
||||
// cookie that the OAuth callback URL depends on
|
||||
'https://vercel.com'
|
||||
);
|
||||
return doOauthLogin(url, 'Bitbucket', params);
|
||||
}
|
||||
@@ -1,70 +1,18 @@
|
||||
import ms from 'ms';
|
||||
import { stringify as stringifyQuery } from 'querystring';
|
||||
import fetch from 'node-fetch';
|
||||
import sleep from '../sleep';
|
||||
import ua from '../ua';
|
||||
import error from '../output/error';
|
||||
import highlight from '../output/highlight';
|
||||
import eraseLines from '../output/erase-lines';
|
||||
import verify from './verify';
|
||||
import executeLogin from './login';
|
||||
import { LoginParams } from './types';
|
||||
|
||||
async function verify(
|
||||
email: string,
|
||||
verificationToken: string,
|
||||
{ apiUrl, output }: LoginParams
|
||||
): Promise<string> {
|
||||
const query = {
|
||||
email,
|
||||
token: verificationToken,
|
||||
};
|
||||
|
||||
output.debug('GET /now/registration/verify');
|
||||
|
||||
let res;
|
||||
|
||||
try {
|
||||
res = await fetch(
|
||||
`${apiUrl}/now/registration/verify?${stringifyQuery(query)}`,
|
||||
{
|
||||
headers: { 'User-Agent': ua },
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
output.debug(`error fetching /now/registration/verify: ${err.stack}`);
|
||||
|
||||
throw new Error(
|
||||
error(
|
||||
`An unexpected error occurred while trying to verify your login: ${err.message}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
output.debug('parsing response from GET /now/registration/verify');
|
||||
let body;
|
||||
|
||||
try {
|
||||
body = await res.json();
|
||||
} catch (err) {
|
||||
output.debug(
|
||||
`error parsing the response from /now/registration/verify: ${err.stack}`
|
||||
);
|
||||
throw new Error(
|
||||
error(
|
||||
`An unexpected error occurred while trying to verify your login: ${err.message}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return body.token;
|
||||
}
|
||||
|
||||
export default async function doEmailLogin(
|
||||
email: string,
|
||||
{ apiUrl, output }: LoginParams
|
||||
params: LoginParams
|
||||
): Promise<number | string> {
|
||||
let securityCode;
|
||||
let verificationToken;
|
||||
const { apiUrl, output } = params;
|
||||
|
||||
output.spinner('Sending you an email');
|
||||
|
||||
@@ -91,22 +39,18 @@ export default async function doEmailLogin(
|
||||
output.spinner('Waiting for your confirmation');
|
||||
|
||||
let token = '';
|
||||
|
||||
while (!token) {
|
||||
try {
|
||||
await sleep(ms('1s'));
|
||||
token = await verify(email, verificationToken, { apiUrl, output });
|
||||
token = await verify(email, verificationToken, params);
|
||||
} catch (err) {
|
||||
if (/invalid json response body/.test(err.message)) {
|
||||
// /now/registraton is currently returning plain text in that case
|
||||
// we just wait for the user to click on the link
|
||||
} else {
|
||||
if (err.message !== 'Confirmation incomplete') {
|
||||
output.error(err.message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.success('Email confirmed');
|
||||
output.success(`Email authentication complete for ${email}`);
|
||||
return token;
|
||||
}
|
||||
|
||||
13
packages/cli/src/util/login/github.ts
Normal file
13
packages/cli/src/util/login/github.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { URL } from 'url';
|
||||
import { LoginParams } from './types';
|
||||
import doOauthLogin from './oauth';
|
||||
|
||||
export default function doGithubLogin(params: LoginParams) {
|
||||
const url = new URL(
|
||||
'/api/registration/login-with-github',
|
||||
// Can't use `apiUrl` here because this URL sets a
|
||||
// cookie that the OAuth callback URL depends on
|
||||
'https://vercel.com'
|
||||
);
|
||||
return doOauthLogin(url, 'GitHub', params);
|
||||
}
|
||||
10
packages/cli/src/util/login/gitlab.ts
Normal file
10
packages/cli/src/util/login/gitlab.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { URL } from 'url';
|
||||
import { LoginParams } from './types';
|
||||
import doOauthLogin from './oauth';
|
||||
|
||||
export default function doGitlabLogin(params: LoginParams) {
|
||||
// Can't use `apiUrl` here because this URL sets a
|
||||
// cookie that the OAuth callback URL depends on
|
||||
const url = new URL('/api/registration/gitlab/connect', 'https://vercel.com');
|
||||
return doOauthLogin(url, 'GitLab', params);
|
||||
}
|
||||
117
packages/cli/src/util/login/oauth.ts
Normal file
117
packages/cli/src/util/login/oauth.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import http from 'http';
|
||||
import open from 'open';
|
||||
import { URL } from 'url';
|
||||
import listen from 'async-listen';
|
||||
import { hostname } from 'os';
|
||||
import { LoginParams } from './types';
|
||||
import prompt from './prompt';
|
||||
import verify from './verify';
|
||||
import { getTitleName } from '../pkg-name';
|
||||
import highlight from '../output/highlight';
|
||||
|
||||
export default async function doOauthLogin(
|
||||
url: URL,
|
||||
provider: string,
|
||||
params: LoginParams
|
||||
): Promise<number | string> {
|
||||
const { output } = params;
|
||||
|
||||
output.spinner(
|
||||
`Please complete the ${provider} authentication in your web browser`
|
||||
);
|
||||
|
||||
const server = http.createServer();
|
||||
const address = await listen(server, 0, '127.0.0.1');
|
||||
const { port } = new URL(address);
|
||||
url.searchParams.append('mode', 'login');
|
||||
url.searchParams.append('next', `http://localhost:${port}`);
|
||||
|
||||
// Append token name param
|
||||
const hyphens = new RegExp('-', 'g');
|
||||
const host = hostname().replace(hyphens, ' ').replace('.local', '');
|
||||
const tokenName = `${getTitleName()} CLI on ${host} via ${provider}`;
|
||||
url.searchParams.append('tokenName', tokenName);
|
||||
|
||||
try {
|
||||
const [query] = await Promise.all([
|
||||
new Promise<URL['searchParams']>((resolve, reject) => {
|
||||
server.once('request', (req, res) => {
|
||||
const query = new URL(req.url || '/', 'http://localhost')
|
||||
.searchParams;
|
||||
resolve(query);
|
||||
|
||||
// Redirect the user's web browser back to
|
||||
// the Vercel CLI login notification page
|
||||
const location = new URL(
|
||||
'https://vercel.com/notifications/cli-login-'
|
||||
);
|
||||
const loginError = query.get('loginError');
|
||||
const ssoEmail = query.get('ssoEmail');
|
||||
if (loginError) {
|
||||
location.pathname += 'failed';
|
||||
location.searchParams.set('loginError', loginError);
|
||||
} else if (ssoEmail) {
|
||||
location.pathname += 'incomplete';
|
||||
location.searchParams.set('ssoEmail', ssoEmail);
|
||||
const teamName = query.get('teamName');
|
||||
const ssoType = query.get('ssoType');
|
||||
if (teamName) {
|
||||
location.searchParams.set('teamName', teamName);
|
||||
}
|
||||
if (ssoType) {
|
||||
location.searchParams.set('ssoType', ssoType);
|
||||
}
|
||||
} else {
|
||||
location.pathname += 'success';
|
||||
const email = query.get('email');
|
||||
if (email) {
|
||||
location.searchParams.set('email', email);
|
||||
}
|
||||
}
|
||||
|
||||
res.statusCode = 302;
|
||||
res.setHeader('location', location.href);
|
||||
res.end();
|
||||
});
|
||||
server.once('error', reject);
|
||||
}),
|
||||
open(url.href),
|
||||
]);
|
||||
|
||||
const loginError = query.get('loginError');
|
||||
if (loginError) {
|
||||
const err = JSON.parse(loginError);
|
||||
output.prettyError(err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If an `ssoUserId` was returned, then the SAML Profile is not yet connected
|
||||
// to a Team member. Prompt the user to log in to a Vercel account now, which
|
||||
// will complete the connection to the SAML Profile.
|
||||
const ssoUserId = query.get('ssoUserId');
|
||||
if (ssoUserId) {
|
||||
output.log(
|
||||
'Please log in to your Vercel account to complete SAML connection.'
|
||||
);
|
||||
return prompt({ ...params, ssoUserId });
|
||||
}
|
||||
|
||||
const email = query.get('email');
|
||||
const verificationToken = query.get('token');
|
||||
if (!email || !verificationToken) {
|
||||
output.error(
|
||||
'Verification token was not provided. Please contact support.'
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
output.spinner('Verifying authentication token');
|
||||
const token = await verify(email, verificationToken, params);
|
||||
output.success(
|
||||
`${provider} authentication complete for ${highlight(email)}`
|
||||
);
|
||||
return token;
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
}
|
||||
77
packages/cli/src/util/login/prompt.ts
Normal file
77
packages/cli/src/util/login/prompt.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import inquirer from 'inquirer';
|
||||
import error from '../output/error';
|
||||
import listInput from '../input/list';
|
||||
import { getCommandName } from '../pkg-name';
|
||||
import { LoginParams } from './types';
|
||||
import doSsoLogin from './sso';
|
||||
import doEmailLogin from './email';
|
||||
import doGithubLogin from './github';
|
||||
import doGitlabLogin from './gitlab';
|
||||
import doBitbucketLogin from './bitbucket';
|
||||
|
||||
export default async function prompt(params: LoginParams) {
|
||||
let result: number | string = 1;
|
||||
|
||||
const choices = [
|
||||
{ name: 'Continue with GitHub', value: 'github', short: 'github' },
|
||||
{ name: 'Continue with GitLab', value: 'gitlab', short: 'gitlab' },
|
||||
{ name: 'Continue with Bitbucket', value: 'bitbucket', short: 'bitbucket' },
|
||||
{ name: 'Continue with Email', value: 'email', short: 'email' },
|
||||
{ name: 'Continue with SAML Single Sign-On', value: 'saml', short: 'saml' },
|
||||
];
|
||||
|
||||
if (params.ssoUserId) {
|
||||
// Remove SAML login option if we're connecting SAML Profile
|
||||
choices.pop();
|
||||
}
|
||||
|
||||
const choice = await listInput({
|
||||
message: 'Log in to Vercel',
|
||||
choices,
|
||||
});
|
||||
|
||||
if (choice === 'github') {
|
||||
result = await doGithubLogin(params);
|
||||
} else if (choice === 'gitlab') {
|
||||
result = await doGitlabLogin(params);
|
||||
} else if (choice === 'bitbucket') {
|
||||
result = await doBitbucketLogin(params);
|
||||
} else if (choice === 'email') {
|
||||
const email = await readInput('Enter your email address');
|
||||
result = await doEmailLogin(email, params);
|
||||
} else if (choice === 'saml') {
|
||||
const slug = await readInput('Enter your Team slug');
|
||||
result = await doSsoLogin(slug, params);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function readInput(message: string) {
|
||||
let input;
|
||||
|
||||
while (!input) {
|
||||
try {
|
||||
const { val } = await inquirer.prompt({
|
||||
type: 'input',
|
||||
name: 'val',
|
||||
message,
|
||||
});
|
||||
input = val;
|
||||
} catch (err) {
|
||||
console.log(); // \n
|
||||
|
||||
if (err.isTtyError) {
|
||||
throw new Error(
|
||||
error(
|
||||
`Interactive mode not supported – please run ${getCommandName(
|
||||
`login you@domain.com`
|
||||
)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user