Compare commits

..

48 Commits

Author SHA1 Message Date
JJ Kasper
cf827a8b91 Publish Canary
- @vercel/build-utils@2.11.2-canary.2
 - vercel@23.0.2-canary.3
 - @vercel/client@10.1.2-canary.2
 - @vercel/frameworks@0.4.2-canary.1
 - @vercel/go@1.2.3-canary.0
 - @vercel/node-bridge@2.0.1-canary.1
 - @vercel/node@1.11.2-canary.3
 - @vercel/python@2.0.5-canary.0
 - @vercel/routing-utils@1.11.3-canary.0
 - @vercel/ruby@1.2.7-canary.0
2021-07-06 22:43:13 -05:00
Steven
bed83e829c [routing-utils] Fix rewrites with query string array in destination (#6441)
Related to https://github.com/vercel/next.js/pull/26619
2021-07-06 22:24:40 +00:00
Markoz Peña
0f0ee4e845 [cli] Convert vc whoami to TypeScript (#6435) 2021-07-06 15:10:00 -04:00
Steven
2d70c6c811 [test] Update publish-canary script (#6432) 2021-07-02 11:54:53 -04:00
Nathan Rajlich
887309f0a5 [cli] Convert vc deploy to TypeScript (#6413)
This is a continuation of https://github.com/vercel/vercel/pull/6382 (thank you @markozxuu!) with merge conflicts addressed and pushed to a local branch, so that CI tests run.
2021-06-30 18:26:29 +00:00
Nathan Rajlich
8a7657c80b [cli] Use output.spinner() instead of wait() (#6414) 2021-06-29 18:48:50 -07:00
Nathan Rajlich
279d0bf988 [cli] Set current scope to proper team after SSO login (#6415) 2021-06-29 16:42:40 -07:00
Nathan Rajlich
d2b31d03da [cli] Use Client instead of LoginParams for login (#6409)
* [cli] Use `Client` instead of `LoginParams`

* Use `client.fetch()`

* Fix email verify

* Remove redundant "user-agent" header

* Fix test
2021-06-29 01:48:36 -07:00
Nathan Rajlich
16acf906fc Update to TypeScript 4.3.4 (#6397)
Also updates eslint and related packages to work with the new TypeScript version.
2021-06-25 23:45:53 +00:00
Nathan Rajlich
0127d17703 Fix Ruby E2E tests (#6401)
Currently the `06-rails` test is failing at build time with:

```
16:27:09.550  	    'Your bundle is locked to mimemagic (0.3.3) from rubygems repository\n' +
16:27:09.550  	    'https://rubygems.org/ or installed locally, but that version can no longer be\n' +
16:27:09.550  	    'found in that source. That means the author of mimemagic (0.3.3) has removed it.\n' +
16:27:09.550  	    "You'll need to update your bundle to a version other than mimemagic (0.3.3) that\n" +
16:27:09.550  	    "hasn't been removed in order to install.",
```

So I ran `bundler update` in the "06-rails" Ruby test fixture to update the deps in the Gemfile.lock file.
2021-06-25 14:41:52 -07:00
Steven
0bfc2a3693 Publish Canary
- @vercel/build-utils@2.11.2-canary.1
 - vercel@23.0.2-canary.2
 - @vercel/client@10.1.2-canary.1
 - @vercel/frameworks@0.4.2-canary.0
 - @vercel/node@1.11.2-canary.2
2021-06-25 11:22:56 -04:00
Steven
b9809b140c [node] Fix ESM when package.json type: module (#6399)
* [node] Fix ESM when package.json `type: module`

* Fix cjs and add test fixture
2021-06-25 11:14:05 -04:00
Steven
faaccc1f9f [frameworks] Add missing cachePattern to Next.js (#6396) 2021-06-24 15:31:16 -04:00
Nathan Rajlich
4b61f1dd61 [cli] Set proper currentTeam in vc deploy (#6394)
Fixes an edge case bug when the user has a current scope that is
different than the owner of the project that is being deployed.

When this was the case, the API call to get the certs for a domain at
the end of the `vc deploy` command was using the incorrect `teamId`,
potentially causing a 403 error (when the current auth token does not
have access to the selected scope).
2021-06-24 12:06:27 -07:00
Steven
cdd417455f [frameworks] Add cachePattern to API response (#6395) 2021-06-24 14:44:38 -04:00
Leonardo Merlin
fe671f56c5 chore(examples): update nuxtjs (#6383)
* chore: update nuxtjs example

* fix: remove @nuxtjs/pwa

Co-authored-by: Leonardo Merlin <lmerlin@schubergphilis.com>
2021-06-23 19:19:04 -07:00
Nathan Rajlich
ddf9e6cf3f [cli] Make vc deploy not load current "scope" (#6388)
`vc deploy` doesn't care about the current "scope" that the CLI has selected, since it uses the `.vercel` directory to determine the ownerId and projectId.

Therefore, it should not be fetching the team details of the selected scope during `vc deploy`, since it's possible that the token might not have access to the current scope and returns a 403, even though that API call is unnecessary for the `vc deploy` command to complete.
2021-06-23 12:32:43 -07:00
Steven
13b03c6abd Publish Canary
- @vercel/build-utils@2.11.2-canary.0
 - vercel@23.0.2-canary.1
 - @vercel/client@10.1.2-canary.0
 - @vercel/node@1.11.2-canary.1
2021-06-23 10:54:17 -04:00
Steven
7dd4c629ad [node] Add support for ESM to vc dev (#6385)
Follow up to #6121
2021-06-23 14:48:47 +00:00
Nathan Rajlich
81b3d7f5f1 [cli] Fix vc login hanging for a few seconds before exiting (#6387)
Closing keep-alive HTTP connections was causing the `server.close()` call
to take a few seconds before completing, so set the `Connection: close`
response header in order to make the connections close immediately,
so that `server.close()` is fast.
2021-06-22 20:57:15 -07:00
Steven
3566c32209 [build-utils] Add support for .mjs with zero config (#6386)
Follow up to #6121
2021-06-23 02:21:16 +00:00
Nathan Rajlich
267ca7b379 [cli] Print timestamp for each line for multi-line log entries (#6384) 2021-06-22 16:46:03 -07:00
Steven
7b9d9954b8 Publish Canary
- vercel@23.0.2-canary.0
 - @vercel/node-bridge@2.0.1-canary.0
 - @vercel/node@1.11.2-canary.0
2021-06-22 15:46:21 -04:00
Steven
79675db241 [node][node-bridge] Add support for ESM (#6121) 2021-06-22 15:44:30 -04:00
Nathan Rajlich
3ac8a3f67f [cli] Make vercel dev command exit quickly (#6365)
Considering that it's a development environment, it's not important to wait for ongoing HTTP request connections to complete. Sometimes it takes a long time for all the shutdown operations to complete, which makes the command feel sluggish. So let's just `process.exit()` and exit quickly.
2021-06-18 23:33:37 +00:00
Nathan Rajlich
2f19949133 [cli] Finish executing the original command after login with no credentials (#6364) 2021-06-18 13:10:30 -07:00
Paco
85fd2aed7e Update Next.js example favicon (#6367) 2021-06-18 12:35:01 -07:00
Steven
e659eecf48 Publish Stable
- @vercel/build-utils@2.11.1
 - vercel@23.0.1
 - @vercel/client@10.1.1
 - @vercel/frameworks@0.4.1
 - @vercel/node@1.11.1
 - @vercel/python@2.0.4
 - @vercel/routing-utils@1.11.2
2021-06-15 17:29:53 -04:00
Steven
b428f7ff83 Publish Canary
- vercel@23.0.1-canary.4
2021-06-15 16:37:28 -04:00
Nathan Rajlich
5eb133283d Remove utils/go (#6362)
This code now lives in its own repository, so the code in this repo is not being used.

https://github.com/vercel/go-bridge
2021-06-15 12:49:18 +00:00
Nathan Rajlich
646c29600e [cgi] Remove @vercel/cgi Runtime (#6361)
This Runtime is very old, outdated, unmaintained, and has never been documented.

Additionally, it no longer compiles with the latest version of Go. So
rather than fixing it, let's just remove it since we don't want to
invest any more time into it.
2021-06-14 20:49:05 -07:00
Nathan Rajlich
469eb4315d [cli] Update "open" to v8.2.0 (#6348)
Fixes an issue where the bundled `xdg-open` script would not be used.
2021-06-10 19:14:57 -07:00
JJ Kasper
6dc54d0d64 Publish Canary
- @vercel/build-utils@2.11.1-canary.1
 - vercel@23.0.1-canary.3
 - @vercel/client@10.1.1-canary.1
 - @vercel/frameworks@0.4.1-canary.1
 - @vercel/routing-utils@1.11.2-canary.1
2021-06-08 11:38:01 -05:00
JJ Kasper
adc84d5148 Fix check: true beforeFiles order (#6337) 2021-06-08 11:35:31 -05:00
Nathan Rajlich
88642b1ce8 [cli] Print login URL to terminal (#6336)
In some cases (i.e. when SSH'd to a remote machine) the `open` command will not work reliably. So we need to print the URL to the user as a fallback for those cases when the web browser is not automatically opened.

This also moves where `tokenName` is specified to be in the "verify" endpoint, so that it does not need to be part of the URL that gets printed to the user.

<img width="738" alt="Screen Shot 2021-06-07 at 2 12 47 PM" src="https://user-images.githubusercontent.com/71256/121089239-b5452d00-c79b-11eb-85b2-0e45b817dff0.png">
2021-06-07 22:13:58 +00:00
Steven
4b8d207533 [cli] Warn when vercel.json uses has (#6327) 2021-06-07 14:45:36 -04:00
Steven
36fe5cc4d1 [test] Fix corrupt bmp test (#6328) 2021-06-07 09:31:51 -04:00
JJ Kasper
370b0dbed2 Publish Canary
- vercel@23.0.1-canary.2
 - @vercel/node@1.11.1-canary.0
2021-06-06 15:24:50 -05:00
JJ Kasper
cc7a82fb0a [node] Update nft to 0.13.1 (#6333) 2021-06-06 16:22:56 -04:00
Markoz Peña
6eea26c39e [cli] Convert vc alias to TS (#6325)
* refactor: Remove unncessary file

* feat(cli/alias): Migration to TS

* refactor(cli/alias): Add a line break

* refactor(cli/alias): Remove unnecesary code

* feat(cli): Add `Paginationoptions` type to `pagination`

* feat(cli/commands): Rewrite th `alias ls` command to TS

* refactor: Remove unncessary code

* feat: Create helper function for `getSafeAlias`

* refactor: Remove unnecessary code

* feat: Remove parameter generic, "null" for the fetch

* feat(cli/alias): Rewritten in full TS

* feat: Add Partial to opts

* refactor: Remove comment @ts-ignore

* feat: Add Partial to opts

* feat: Only should be return `alias.uid`

* refactor: Remove `Alias` type from of the parameter `id`

* refactor: Remove destructuring from alias object

* refactor: Remove unnecessary code

* feat: Rename `created` property to `createdAt` of number type

* refactor: Move getSafeAlias function in the same file

* refactor: Simplifying code

* refactor: Intentation did not affect diff on git

* Add null back to type

Co-authored-by: Steven <steven@ceriously.com>
2021-06-04 16:43:39 -04:00
Nathan Rajlich
b8bfae7840 [cli] Fix vc logout command when using Team scope (#6322)
Fixes logout command not working when switched to a Team scope:

```
$ vc login
$ vc switch $some_team
$ vc logout
Failed during logout
```
2021-06-04 19:48:03 +00:00
Nathan Rajlich
dc6a0a1cbb [cli] Upgrade token scopes in vc switch command (#6323)
Pass the `Authorization` request header to the verify endpoint so that the current auth token will be upgraded with the new scope.

[ch22273]
2021-06-04 18:33:49 +00:00
Steven
a6807c9d21 Publish Canary
- vercel@23.0.1-canary.1
 - @vercel/python@2.0.4-canary.0
2021-06-01 14:35:09 -04:00
Steven
c628090d08 [cli] Fix vc projects rm race condition (#6306)
The call to `GET /projects/info` is used to check existence but it can cause a race condition if the project was removed before the `DELETE /v2/projects` is called.

Instead, we rely on the response from `DELETE /v2/projects` to determine if the project exists or not.

This will also allow us to remove a legacy API endpoint in the future (see related API PR)
2021-06-01 18:31:20 +00:00
Hydrophobefireman
4e0b291ed1 [python] Remove imports from werkzeug._compat (#6283) 2021-06-01 09:15:06 -04:00
JJ Kasper
ee0bc9b0c8 Publish Canary
- @vercel/build-utils@2.11.1-canary.0
 - vercel@23.0.1-canary.0
 - @vercel/client@10.1.1-canary.0
 - @vercel/frameworks@0.4.1-canary.0
 - @vercel/routing-utils@1.11.2-canary.0
2021-05-26 13:44:39 -05:00
JJ Kasper
e516c1f49f Ensure beforeFiles rewrites come after redirects when continuing (#6289) 2021-05-26 12:50:51 -05:00
JJ Kasper
01f53f36fc [routing-utils] Ensure header key value casing is normalized (#6284)
This ensures we normalize header `key` values in `has` items to be lower-case as the proxy currently only matches against the lower-case variant. Updated superstatic tests to ensure the header key is normalized correctly. 

### Related Issues

[related thread](https://vercel.slack.com/archives/C01N3RWTE5V/p1621937306006400)

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2021-05-25 22:46:28 +00:00
136 changed files with 3254 additions and 3411 deletions

View File

@@ -1,14 +1,7 @@
node_modules node_modules
dist dist
examples examples
# gatsby-plugin-now
packages/gatsby-plugin-now/test/fixtures
# build-utils
packages/build-utils/test/fixtures packages/build-utils/test/fixtures
# cli
packages/cli/@types packages/cli/@types
packages/cli/download packages/cli/download
packages/cli/dist packages/cli/dist
@@ -17,24 +10,9 @@ packages/cli/test/dev/fixtures
packages/cli/bin packages/cli/bin
packages/cli/link packages/cli/link
packages/cli/src/util/dev/templates/*.ts packages/cli/src/util/dev/templates/*.ts
# client
packages/client/tests/fixtures packages/client/tests/fixtures
packages/client/lib packages/client/lib
# next
packages/next/test/fixtures
# node
packages/node/src/bridge.ts packages/node/src/bridge.ts
packages/node/test/fixtures packages/node/test/fixtures
packages/node-bridge/bridge.js
# node-bridge packages/node-bridge/launcher.js
packages/node-bridge/bridge.*
# static-build
packages/static-build/test/fixtures
packages/static-build/test/build-fixtures
# redwood
packages/redwood/test/fixtures

View File

@@ -14,7 +14,6 @@ const frameworks = (_frameworks as Framework[])
sort: undefined, sort: undefined,
dependency: undefined, dependency: undefined,
defaultRoutes: undefined, defaultRoutes: undefined,
cachePattern: undefined,
devCommand: undefined, devCommand: undefined,
buildCommand: undefined, buildCommand: undefined,
}; };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -80,11 +80,15 @@ dist
# IDE / Editor # IDE / Editor
.idea .idea
.editorconfig
# Service worker # Service worker
sw.* sw.*
# Mac OSX # macOS
.DS_Store .DS_Store
# Vim swap files
*.swp
# Vercel
.vercel .vercel

View File

@@ -1 +1,4 @@
README.md README.md
.nuxt
node_modules
*.log

View File

@@ -8,7 +8,7 @@ Deploy your own Nuxt.js project with Vercel.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/nuxtjs) [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/vercel/vercel/tree/main/examples/nuxtjs)
_Live Example: https://nuxtjs.now-examples.now.sh_ _Live Example: https://nuxtjs.now-examples.vercel.app/_
### How We Created This Example ### How We Created This Example
@@ -17,5 +17,3 @@ To get started with Nuxt.js deployed with Vercel, you can use the [Create-Nuxt-A
```shell ```shell
$ npx create-nuxt-app my-app $ npx create-nuxt-app my-app
``` ```
> The only change made is to amend the output directory in `nuxt.config.js` to `"/public"`.

View File

@@ -1,44 +1,37 @@
export default { export default {
mode: 'spa', // Target: https://go.nuxtjs.dev/config-target
/* target: 'static',
** Headers of the page
*/ // Global page headers: https://go.nuxtjs.dev/config-head
head: { head: {
title: process.env.npm_package_name || '', title: 'nuxtjs',
meta: [ meta: [
{ charset: 'utf-8' }, { charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ {
hid: 'description', hid: 'description',
name: 'description', name: 'description',
content: process.env.npm_package_description || '', content: 'My astonishing Nuxt.js project',
}, },
], ],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }], link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
}, },
/*
** Customize the progress-bar color // Global CSS: https://go.nuxtjs.dev/config-css
*/
loading: { color: '#fff' },
/*
** Global CSS
*/
css: [], css: [],
/*
** Plugins to load before mounting the App // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
*/
plugins: [], plugins: [],
/*
** Nuxt.js modules // Auto import components: https://go.nuxtjs.dev/config-components
*/ components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [], modules: [],
/*
** Build configuration // Build Configuration: https://go.nuxtjs.dev/config-build
*/ build: {}
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {},
},
}; };

View File

@@ -10,9 +10,8 @@
"start": "nuxt start" "start": "nuxt start"
}, },
"dependencies": { "dependencies": {
"nuxt": "^2.0.0" "core-js": "^3.15.0",
"nuxt": "^2.15.7"
}, },
"devDependencies": { "devDependencies": {}
"nodemon": "^2.0.7"
}
} }

View File

@@ -12,6 +12,7 @@
<a <a
href="https://nuxtjs.org/" href="https://nuxtjs.org/"
target="_blank" target="_blank"
rel="noopener"
class="button--green" class="button--green"
> >
Documentation Documentation
@@ -19,6 +20,7 @@
<a <a
href="https://github.com/nuxt/nuxt.js" href="https://github.com/nuxt/nuxt.js"
target="_blank" target="_blank"
rel="noopener"
class="button--grey" class="button--grey"
> >
GitHub GitHub

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -15,27 +15,27 @@
"lerna": "3.16.4" "lerna": "3.16.4"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "2.0.0", "@typescript-eslint/eslint-plugin": "4.28.0",
"@typescript-eslint/parser": "2.0.0", "@typescript-eslint/parser": "4.28.0",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"async-retry": "1.2.3", "async-retry": "1.2.3",
"buffer-replace": "1.0.0", "buffer-replace": "1.0.0",
"cheerio": "1.0.0-rc.3", "cheerio": "1.0.0-rc.3",
"eslint": "6.2.2", "eslint": "7.29.0",
"eslint-config-prettier": "6.1.0", "eslint-config-prettier": "8.3.0",
"eslint-plugin-jest": "23.8.2", "eslint-plugin-jest": "24.3.6",
"husky": "6.0.0", "husky": "6.0.0",
"json5": "2.1.1", "json5": "2.1.1",
"lint-staged": "9.2.5", "lint-staged": "9.2.5",
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"npm-package-arg": "6.1.0", "npm-package-arg": "6.1.0",
"prettier": "2.0.5" "prettier": "2.3.1"
}, },
"scripts": { "scripts": {
"lerna": "lerna", "lerna": "lerna",
"bootstrap": "lerna bootstrap", "bootstrap": "lerna bootstrap",
"publish-stable": "echo 'Run `yarn changelog` for instructions'", "publish-stable": "echo 'Run `yarn changelog` for instructions'",
"publish-canary": "git pull && lerna version prerelease --preid canary --message 'Publish Canary' --exact", "publish-canary": "git checkout main && git pull && lerna version prerelease --preid canary --message 'Publish Canary' --exact",
"publish-from-github": "./utils/publish.sh", "publish-from-github": "./utils/publish.sh",
"changelog": "node utils/changelog.js", "changelog": "node utils/changelog.js",
"build": "node utils/run.js build all", "build": "node utils/run.js build all",
@@ -79,8 +79,7 @@
"eslint:recommended", "eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"prettier", "prettier"
"prettier/@typescript-eslint"
], ],
"env": { "env": {
"node": true, "node": true,
@@ -89,26 +88,19 @@
}, },
"rules": { "rules": {
"require-atomic-updates": 0, "require-atomic-updates": 0,
"@typescript-eslint/ban-ts-ignore": 0, "@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/camelcase": 0, "@typescript-eslint/camelcase": 0,
"@typescript-eslint/explicit-function-return-type": 0, "@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-empty-function": 0, "@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-non-null-assertion": 0, "@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-unused-vars": 2, "@typescript-eslint/no-unused-vars": 2,
"@typescript-eslint/no-use-before-define": 0, "@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-var-requires": 0,
"jest/no-disabled-tests": 2, "jest/no-disabled-tests": 2,
"jest/no-focused-tests": 2 "jest/no-focused-tests": 2
}, },
"overrides": [ "overrides": [
{
"files": [
"**/*.js"
],
"rules": {
"@typescript-eslint/no-var-requires": "off"
}
},
{ {
"files": [ "files": [
"packages/cli/**/*" "packages/cli/**/*"
@@ -125,8 +117,7 @@
"@typescript-eslint/member-delimiter-style": 0, "@typescript-eslint/member-delimiter-style": 0,
"@typescript-eslint/no-empty-function": 0, "@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-inferrable-types": 0, "@typescript-eslint/no-inferrable-types": 0
"@typescript-eslint/no-var-requires": 0
} }
}, },
{ {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/build-utils", "name": "@vercel/build-utils",
"version": "2.11.0", "version": "2.11.2-canary.2",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.js", "types": "./dist/index.d.js",
@@ -29,7 +29,7 @@
"@types/node-fetch": "^2.1.6", "@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@types/yazl": "^2.4.1", "@types/yazl": "^2.4.1",
"@vercel/frameworks": "0.4.0", "@vercel/frameworks": "0.4.2-canary.1",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1", "aggregate-error": "3.0.1",
"async-retry": "1.2.3", "async-retry": "1.2.3",
@@ -47,7 +47,7 @@
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"semver": "6.1.1", "semver": "6.1.1",
"ts-jest": "24.1.0", "ts-jest": "24.1.0",
"typescript": "3.9.3", "typescript": "4.3.4",
"yazl": "2.4.3" "yazl": "2.4.3"
} }
} }

View File

@@ -424,6 +424,7 @@ function getApiMatches() {
return [ return [
{ src: 'api/**/*.js', use: `@vercel/node`, config }, { src: 'api/**/*.js', use: `@vercel/node`, config },
{ src: 'api/**/*.mjs', use: `@vercel/node`, config },
{ src: 'api/**/*.ts', use: `@vercel/node`, config }, { src: 'api/**/*.ts', use: `@vercel/node`, config },
{ src: 'api/**/!(*_test).go', use: `@vercel/go`, config }, { src: 'api/**/!(*_test).go', use: `@vercel/go`, config },
{ src: 'api/**/*.py', use: `@vercel/python`, config }, { src: 'api/**/*.py', use: `@vercel/python`, config },

View File

@@ -1346,6 +1346,25 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect((errorRoutes![0] as Source).status).toBe(404); expect((errorRoutes![0] as Source).status).toBe(404);
}); });
it('api detect node mjs files', async () => {
const files = [
'api/index.mjs',
'api/users.mjs',
'api/config/staging.mjs',
'api/config/production.mjs',
'api/src/controllers/health.mjs',
'api/src/controllers/user.module.mjs',
];
const { builders, errorRoutes } = await detectBuilders(files, undefined, {
featHandleMiss,
});
expect(builders!.length).toBe(6);
expect(builders!.every(b => b.src!.endsWith('.mjs'))).toBe(true);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('just public', async () => { it('just public', async () => {
const files = ['public/index.html', 'public/favicon.ico', 'README.md']; const files = ['public/index.html', 'public/favicon.ico', 'README.md'];

View File

@@ -1 +0,0 @@
handler

View File

@@ -1,25 +0,0 @@
#!/usr/bin/env node
const execa = require('execa');
const { join } = require('path');
const { homedir } = require('os');
async function main() {
process.env.GOOS = 'linux';
process.env.GOARCH = 'amd64';
process.env.GOPATH = join(homedir(), 'go');
await execa('go', ['get', 'github.com/aws/aws-lambda-go/events'], {
stdio: 'inherit',
});
await execa('go', ['get', 'github.com/aws/aws-lambda-go/lambda'], {
stdio: 'inherit',
});
await execa('go', ['build', '-o', 'handler', 'main.go'], {
stdio: 'inherit',
});
}
main().catch(err => {
console.error(err);
process.exit(1);
});

View File

@@ -1,45 +0,0 @@
const path = require('path');
const { mkdirp, copyFile } = require('fs-extra');
const {
glob,
download,
shouldServe,
createLambda,
getWritableDirectory,
} = require('@vercel/build-utils');
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
exports.version = 3;
exports.build = async ({ workPath, files, entrypoint, meta }) => {
const outDir = await getWritableDirectory();
await download(files, workPath, meta);
const handlerPath = path.join(__dirname, 'handler');
await copyFile(handlerPath, path.join(outDir, 'handler'));
const entrypointOutDir = path.join(outDir, path.dirname(entrypoint));
await mkdirp(entrypointOutDir);
// For now only the entrypoint file is copied into the lambda
await copyFile(
path.join(workPath, entrypoint),
path.join(outDir, entrypoint)
);
const lambda = await createLambda({
files: await glob('**', outDir),
handler: 'handler',
runtime: 'go1.x',
environment: {
SCRIPT_FILENAME: entrypoint,
},
});
return { output: lambda };
};
exports.shouldServe = shouldServe;

View File

@@ -1,36 +0,0 @@
package main
import (
now "../../utils/go/bridge"
"net/http"
"net/http/cgi"
"os"
"path/filepath"
)
type CgiHandler struct {
http.Handler
Dir string
Script string
}
func (h *CgiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
handler := cgi.Handler{
Path: h.Script,
Root: "/" + h.Script,
Dir: h.Dir,
Env: []string{
"HTTPS=on",
"SERVER_PORT=443",
"SERVER_SOFTWARE=@vercel/cgi",
},
}
handler.ServeHTTP(w, r)
}
func main() {
workdir, _ := filepath.Abs(".")
script := os.Getenv("SCRIPT_FILENAME")
handler := &CgiHandler{nil, workdir, script}
now.Start(handler)
}

View File

@@ -1,24 +0,0 @@
{
"name": "@vercel/cgi",
"version": "1.0.7",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/vercel/vercel.git",
"directory": "packages/cgi"
},
"scripts": {
"build": "node build",
"prepublishOnly": "node build"
},
"files": [
"index.js",
"handler"
],
"dependencies": {
"fs-extra": "7.0.0"
},
"devDependencies": {
"rmfr": "2.0.0"
}
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "vercel", "name": "vercel",
"version": "23.0.0", "version": "23.0.2-canary.3",
"preferGlobal": true, "preferGlobal": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "The command-line interface for Vercel", "description": "The command-line interface for Vercel",
@@ -61,11 +61,11 @@
"node": ">= 12" "node": ">= 12"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "2.11.0", "@vercel/build-utils": "2.11.2-canary.2",
"@vercel/go": "1.2.2", "@vercel/go": "1.2.3-canary.0",
"@vercel/node": "1.11.0", "@vercel/node": "1.11.2-canary.3",
"@vercel/python": "2.0.3", "@vercel/python": "2.0.5-canary.0",
"@vercel/ruby": "1.2.6", "@vercel/ruby": "1.2.7-canary.0",
"update-notifier": "4.1.0" "update-notifier": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
@@ -100,7 +100,7 @@
"@types/universal-analytics": "0.4.2", "@types/universal-analytics": "0.4.2",
"@types/which": "1.3.2", "@types/which": "1.3.2",
"@types/write-json-file": "2.2.1", "@types/write-json-file": "2.2.1",
"@vercel/frameworks": "0.4.0", "@vercel/frameworks": "0.4.2-canary.1",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2", "@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2", "@zeit/source-map-support": "0.6.2",
@@ -148,7 +148,7 @@
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"npm-package-arg": "6.1.0", "npm-package-arg": "6.1.0",
"nyc": "13.2.0", "nyc": "13.2.0",
"open": "8.0.2", "open": "8.2.0",
"ora": "3.4.0", "ora": "3.4.0",
"pcre-to-regexp": "1.0.0", "pcre-to-regexp": "1.0.0",
"pluralize": "7.0.0", "pluralize": "7.0.0",
@@ -171,7 +171,7 @@
"tree-kill": "1.2.2", "tree-kill": "1.2.2",
"ts-eager": "2.0.2", "ts-eager": "2.0.2",
"ts-node": "8.3.0", "ts-node": "8.3.0",
"typescript": "3.9.3", "typescript": "4.3.4",
"universal-analytics": "0.4.20", "universal-analytics": "0.4.20",
"utility-types": "2.1.0", "utility-types": "2.1.0",
"which": "2.0.2", "which": "2.0.2",

View File

@@ -2,10 +2,11 @@ import chalk from 'chalk';
import { handleError } from '../../util/error'; import { handleError } from '../../util/error';
import Client from '../../util/client';
import getArgs from '../../util/get-args'; import getArgs from '../../util/get-args';
import getSubcommand from '../../util/get-subcommand'; import getSubcommand from '../../util/get-subcommand';
import logo from '../../util/output/logo'; import logo from '../../util/output/logo';
import { getPkgName } from '../../util/pkg-name.ts'; import { getPkgName } from '../../util/pkg-name';
import ls from './ls'; import ls from './ls';
import rm from './rm'; import rm from './rm';
@@ -36,6 +37,7 @@ const help = () => {
)} Login token )} Login token
-S, --scope Set a custom scope -S, --scope Set a custom scope
-N, --next Show next page of results -N, --next Show next page of results
${chalk.dim('Examples:')} ${chalk.dim('Examples:')}
${chalk.gray('')} Add a new alias to ${chalk.underline('my-api.vercel.app')} ${chalk.gray('')} Add a new alias to ${chalk.underline('my-api.vercel.app')}
@@ -64,13 +66,13 @@ const help = () => {
}; };
const COMMAND_CONFIG = { const COMMAND_CONFIG = {
default: 'set', default: ['set'],
ls: ['ls', 'list'], ls: ['ls', 'list'],
rm: ['rm', 'remove'], rm: ['rm', 'remove'],
set: ['set'], set: ['set'],
}; };
export default async function main(client) { export default async function main(client: Client) {
let argv; let argv;
try { try {

View File

@@ -1,21 +1,26 @@
import chalk from 'chalk'; import chalk from 'chalk';
import ms from 'ms'; import ms from 'ms';
import table from 'text-table'; import table from 'text-table';
import Now from '../../util'; import Client from '../../util/client';
import getAliases from '../../util/alias/get-aliases'; import getAliases from '../../util/alias/get-aliases';
import getScope from '../../util/get-scope.ts'; import getScope from '../../util/get-scope';
import stamp from '../../util/output/stamp.ts'; import stamp from '../../util/output/stamp';
import strlen from '../../util/strlen.ts'; import strlen from '../../util/strlen';
import getCommandFlags from '../../util/get-command-flags'; import getCommandFlags from '../../util/get-command-flags';
import { getCommandName } from '../../util/pkg-name.ts'; import { getCommandName } from '../../util/pkg-name';
export default async function ls(client, opts, args) { import { Alias } from '../../types';
const {
apiUrl, interface Options {
authConfig: { token }, '--next'?: number;
output, }
config: { currentTeam },
} = client; export default async function ls(
client: Client,
opts: Options,
args: string[]
) {
const { output } = client;
const { '--next': nextTimestamp } = opts; const { '--next': nextTimestamp } = opts;
let contextName = null; let contextName = null;
@@ -36,13 +41,6 @@ export default async function ls(client, opts, args) {
return 1; return 1;
} }
const now = new Now({
apiUrl,
token,
debug: client.output.isDebugEnabled(),
currentTeam,
output,
});
const lsStamp = stamp(); const lsStamp = stamp();
if (args.length > 0) { if (args.length > 0) {
@@ -56,8 +54,9 @@ export default async function ls(client, opts, args) {
output.spinner(`Fetching aliases under ${chalk.bold(contextName)}`); output.spinner(`Fetching aliases under ${chalk.bold(contextName)}`);
// Get the list of alias
const { aliases, pagination } = await getAliases( const { aliases, pagination } = await getAliases(
now, client,
undefined, undefined,
nextTimestamp nextTimestamp
); );
@@ -73,21 +72,20 @@ export default async function ls(client, opts, args) {
); );
} }
now.close();
return 0; return 0;
} }
function printAliasTable(aliases) { function printAliasTable(aliases: Alias[]) {
return `${table( return `${table(
[ [
['source', 'url', 'age'].map(h => chalk.gray(h)), ['source', 'url', 'age'].map(header => chalk.gray(header)),
...aliases.map(a => [ ...aliases.map(a => [
// for legacy reasons, we might have situations // for legacy reasons, we might have situations
// where the deployment was deleted and the alias // where the deployment was deleted and the alias
// not collected appropriately, and we need to handle it // not collected appropriately, and we need to handle it
a.deployment && a.deployment.url ? a.deployment.url : chalk.gray(''), a.deployment && a.deployment.url ? a.deployment.url : chalk.gray(''),
a.alias, a.alias,
ms(Date.now() - new Date(a.createdAt)), ms(Date.now() - a.createdAt),
]), ]),
], ],
{ {

View File

@@ -1,23 +1,29 @@
import chalk from 'chalk'; import chalk from 'chalk';
import ms from 'ms'; import ms from 'ms';
import table from 'text-table'; import table from 'text-table';
import Now from '../../util'; import Client from '../../util/client';
import getScope from '../../util/get-scope.ts'; import getScope from '../../util/get-scope';
import removeAliasById from '../../util/alias/remove-alias-by-id'; import removeAliasById from '../../util/alias/remove-alias-by-id';
import stamp from '../../util/output/stamp.ts'; import stamp from '../../util/output/stamp';
import strlen from '../../util/strlen.ts'; import strlen from '../../util/strlen';
import confirm from '../../util/input/confirm'; import confirm from '../../util/input/confirm';
import { isValidName } from '../../util/is-valid-name';
import findAliasByAliasOrId from '../../util/alias/find-alias-by-alias-or-id'; import findAliasByAliasOrId from '../../util/alias/find-alias-by-alias-or-id';
import { getCommandName } from '../../util/pkg-name.ts';
export default async function rm(client, opts, args) { import { Alias } from '../../types';
const { import { Output } from '../../util/output';
apiUrl, import { isValidName } from '../../util/is-valid-name';
authConfig: { token }, import { getCommandName } from '../../util/pkg-name';
output,
config: { currentTeam }, type Options = {
} = client; '--yes': boolean;
};
export default async function rm(
client: Client,
opts: Partial<Options>,
args: string[]
) {
const { output } = client;
let contextName = null; let contextName = null;
@@ -32,13 +38,6 @@ export default async function rm(client, opts, args) {
throw err; throw err;
} }
const now = new Now({
apiUrl,
token,
debug: client.output.isDebugEnabled(),
currentTeam,
output,
});
const [aliasOrId] = args; const [aliasOrId] = args;
if (args.length !== 1) { if (args.length !== 1) {
@@ -61,7 +60,8 @@ export default async function rm(client, opts, args) {
return 1; return 1;
} }
const alias = await findAliasByAliasOrId(output, now, aliasOrId); const alias = await findAliasByAliasOrId(output, client, aliasOrId);
if (!alias) { if (!alias) {
output.error( output.error(
`Alias not found by "${aliasOrId}" under ${chalk.bold(contextName)}` `Alias not found by "${aliasOrId}" under ${chalk.bold(contextName)}`
@@ -76,7 +76,7 @@ export default async function rm(client, opts, args) {
return 0; return 0;
} }
await removeAliasById(now, alias.uid); await removeAliasById(client, alias.uid);
console.log( console.log(
`${chalk.cyan('> Success!')} Alias ${chalk.bold( `${chalk.cyan('> Success!')} Alias ${chalk.bold(
alias.alias alias.alias
@@ -85,7 +85,7 @@ export default async function rm(client, opts, args) {
return 0; return 0;
} }
async function confirmAliasRemove(output, alias) { async function confirmAliasRemove(output: Output, alias: Alias) {
const srcUrl = alias.deployment const srcUrl = alias.deployment
? chalk.underline(alias.deployment.url) ? chalk.underline(alias.deployment.url)
: null; : null;
@@ -94,7 +94,7 @@ async function confirmAliasRemove(output, alias) {
[ [
...(srcUrl ? [srcUrl] : []), ...(srcUrl ? [srcUrl] : []),
chalk.underline(alias.alias), chalk.underline(alias.alias),
chalk.gray(`${ms(new Date() - new Date(alias.created))} ago`), chalk.gray(`${ms(Date.now() - alias.createdAt)} ago`),
], ],
], ],
{ {

View File

@@ -28,7 +28,7 @@ type Options = {
export default async function set( export default async function set(
client: Client, client: Client,
opts: Options, opts: Partial<Options>,
args: string[] args: string[]
) { ) {
const { output, localConfig } = client; const { output, localConfig } = client;

View File

@@ -1,6 +1,6 @@
import chalk from 'chalk'; import chalk from 'chalk';
import logo from '../../util/output/logo'; import logo from '../../util/output/logo';
import { getPkgName } from '../../util/pkg-name.ts'; import { getPkgName } from '../../util/pkg-name';
export const help = () => ` export const help = () => `
${chalk.bold(`${logo} ${getPkgName()}`)} [options] <command | path> ${chalk.bold(`${logo} ${getPkgName()}`)} [options] <command | path>
@@ -92,30 +92,3 @@ export const help = () => `
${chalk.cyan(`$ ${getPkgName()} help list`)} ${chalk.cyan(`$ ${getPkgName()} help list`)}
`; `;
export const args = {
'--force': Boolean,
'--with-cache': Boolean,
'--public': Boolean,
'--no-clipboard': Boolean,
'--env': [String],
'--build-env': [String],
'--meta': [String],
// This is not an array in favor of matching
// the config property name.
'--regions': String,
'--prod': Boolean,
'--confirm': Boolean,
'-f': '--force',
'-p': '--public',
'-e': '--env',
'-b': '--build-env',
'-C': '--no-clipboard',
'-m': '--meta',
'-c': '--confirm',
// deprecated
'--name': String,
'-n': '--name',
'--target': String,
};

View File

@@ -1,37 +1,62 @@
import fs from 'fs-extra'; import fs from 'fs-extra';
import { resolve, basename } from 'path'; import { resolve, basename } from 'path';
import { fileNameSymbol } from '@vercel/client'; import { VercelConfig, fileNameSymbol } from '@vercel/client';
import getScope from '../../util/get-scope.ts';
import code from '../../util/output/code'; import code from '../../util/output/code';
import highlight from '../../util/output/highlight'; import highlight from '../../util/output/highlight';
import { readLocalConfig } from '../../util/config/files'; import { readLocalConfig } from '../../util/config/files';
import getArgs from '../../util/get-args'; import getArgs from '../../util/get-args';
import { handleError } from '../../util/error'; import { handleError } from '../../util/error';
import { help, args } from './args'; import { help } from './args';
import deploy from './latest'; import deploy from './latest';
import Client from '../../util/client';
export default async client => { export default async (client: Client) => {
const { const { output } = client;
output,
config: { currentTeam },
} = client;
let contextName = currentTeam || 'current user';
let argv = null; let argv = null;
try { try {
argv = getArgs(client.argv.slice(2), args); argv = getArgs(client.argv.slice(2), {
'--force': Boolean,
'--with-cache': Boolean,
'--public': Boolean,
'--no-clipboard': Boolean,
'--env': [String],
'--build-env': [String],
'--meta': [String],
// This is not an array in favor of matching
// the config property name.
'--regions': String,
'--prod': Boolean,
'--confirm': Boolean,
'-f': '--force',
'-p': '--public',
'-e': '--env',
'-b': '--build-env',
'-C': '--no-clipboard',
'-m': '--meta',
'-c': '--confirm',
// deprecated
'--name': String,
'-n': '--name',
'--target': String,
});
} catch (error) { } catch (error) {
handleError(error); handleError(error);
return 1; return 1;
} }
if (argv['--help']) {
output.print(help());
return 2;
}
if (argv._[0] === 'deploy') { if (argv._[0] === 'deploy') {
argv._.shift(); argv._.shift();
} }
let paths = []; let paths;
if (argv._.length > 0) { if (argv._.length > 0) {
// If path is relative: resolve // If path is relative: resolve
// if path is absolute: clear up strange `/` etc // if path is absolute: clear up strange `/` etc
@@ -40,20 +65,14 @@ export default async client => {
paths = [process.cwd()]; paths = [process.cwd()];
} }
let { localConfig } = client; let localConfig: VercelConfig | null = client.localConfig;
if (!localConfig || localConfig instanceof Error) { if (!localConfig || localConfig instanceof Error) {
localConfig = readLocalConfig(paths[0]); localConfig = readLocalConfig(paths[0]);
} }
const stats = {};
if (argv['--help']) {
output.print(help());
return 2;
}
for (const path of paths) { for (const path of paths) {
try { try {
stats[path] = await fs.lstat(path); await fs.stat(path);
} catch (err) { } catch (err) {
output.error( output.error(
`The specified file or directory "${basename(path)}" does not exist.` `The specified file or directory "${basename(path)}" does not exist.`
@@ -62,26 +81,15 @@ export default async client => {
} }
} }
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
if (localConfig) { if (localConfig) {
const { version } = localConfig; const { version } = localConfig;
const file = highlight(localConfig[fileNameSymbol]); const file = highlight(localConfig[fileNameSymbol]!);
const prop = code('version'); const prop = code('version');
if (version) { if (version) {
if (typeof version === 'number') { if (typeof version === 'number') {
if (version !== 2) { if (version !== 2) {
const two = code(2); const two = code(String(2));
output.error( output.error(
`The value of the ${prop} property within ${file} can only be ${two}.` `The value of the ${prop} property within ${file} can only be ${two}.`
@@ -97,5 +105,5 @@ export default async client => {
} }
} }
return deploy(client, contextName, output, stats, localConfig, args); return deploy(client, paths, localConfig, argv);
}; };

View File

@@ -3,13 +3,12 @@ import bytes from 'bytes';
import { join } from 'path'; import { join } from 'path';
import { write as copy } from 'clipboardy'; import { write as copy } from 'clipboardy';
import chalk from 'chalk'; import chalk from 'chalk';
import { fileNameSymbol } from '@vercel/client'; import { Dictionary, fileNameSymbol, VercelConfig } from '@vercel/client';
import { getPrettyError } from '@vercel/build-utils'; import { getPrettyError } from '@vercel/build-utils';
import { handleError } from '../../util/error'; import { handleError } from '../../util/error';
import getArgs from '../../util/get-args';
import toHumanPath from '../../util/humanize-path'; import toHumanPath from '../../util/humanize-path';
import Now from '../../util'; import Now from '../../util';
import stamp from '../../util/output/stamp.ts'; import stamp from '../../util/output/stamp';
import createDeploy from '../../util/deploy/create-deploy'; import createDeploy from '../../util/deploy/create-deploy';
import getDeploymentByIdOrHost from '../../util/deploy/get-deployment-by-id-or-host'; import getDeploymentByIdOrHost from '../../util/deploy/get-deployment-by-id-or-host';
import parseMeta from '../../util/parse-meta'; import parseMeta from '../../util/parse-meta';
@@ -53,10 +52,15 @@ import validatePaths, {
validateRootDirectory, validateRootDirectory,
} from '../../util/validate-paths'; } from '../../util/validate-paths';
import { readLocalConfig } from '../../util/config/files'; import { readLocalConfig } from '../../util/config/files';
import { getCommandName } from '../../util/pkg-name.ts'; import { getCommandName } from '../../util/pkg-name';
import { getPreferredPreviewURL } from '../../util/deploy/get-preferred-preview-url.ts'; import { getPreferredPreviewURL } from '../../util/deploy/get-preferred-preview-url';
import { Output } from '../../util/output';
import Client from '../../util/client';
const addProcessEnv = async (log, env) => { const addProcessEnv = async (
log: (str: string) => void,
env: typeof process.env
) => {
let val; let val;
for (const key of Object.keys(env)) { for (const key of Object.keys(env)) {
@@ -85,8 +89,8 @@ const addProcessEnv = async (log, env) => {
}; };
const printDeploymentStatus = async ( const printDeploymentStatus = async (
output, output: Output,
client, client: Client,
{ {
readyState, readyState,
alias: aliasList, alias: aliasList,
@@ -94,10 +98,17 @@ const printDeploymentStatus = async (
target, target,
indications, indications,
url: deploymentUrl, url: deploymentUrl,
}: {
readyState: string;
alias: string[];
aliasError: Error;
target: string;
indications: any;
url: string;
}, },
deployStamp, deployStamp: () => string,
isClipboardEnabled, isClipboardEnabled: boolean,
isFile isFile: boolean
) => { ) => {
const isProdDeployment = target === 'production'; const isProdDeployment = target === 'production';
@@ -116,8 +127,8 @@ const printDeploymentStatus = async (
); );
} else { } else {
// print preview/production url // print preview/production url
let previewUrl; let previewUrl: string;
let isWildcard; let isWildcard: boolean;
if (!isFile && Array.isArray(aliasList) && aliasList.length > 0) { if (!isFile && Array.isArray(aliasList) && aliasList.length > 0) {
const previewUrlInfo = await getPreferredPreviewURL(client, aliasList); const previewUrlInfo = await getPreferredPreviewURL(client, aliasList);
if (previewUrlInfo) { if (previewUrlInfo) {
@@ -136,11 +147,12 @@ const printDeploymentStatus = async (
// copy to clipboard // copy to clipboard
let isCopiedToClipboard = false; let isCopiedToClipboard = false;
if (isClipboardEnabled && !isWildcard) { if (isClipboardEnabled && !isWildcard) {
await copy(previewUrl) try {
.then(() => { await copy(previewUrl);
isCopiedToClipboard = true; isCopiedToClipboard = true;
}) } catch (err) {
.catch(error => output.debug(`Error copying to clipboard: ${error}`)); output.debug(`Error copyind to clipboard: ${err}`);
}
} }
output.print( output.print(
@@ -176,7 +188,7 @@ const printDeploymentStatus = async (
}; };
// Converts `env` Arrays, Strings and Objects into env Objects. // Converts `env` Arrays, Strings and Objects into env Objects.
const parseEnv = env => { const parseEnv = (env?: string[] | Dictionary<string>) => {
if (!env) { if (!env) {
return {}; return {};
} }
@@ -201,35 +213,25 @@ const parseEnv = env => {
o[key] = value; o[key] = value;
return o; return o;
}, {}); }, {} as Dictionary<string | undefined>);
} }
// assume it's already an Object // assume it's already an Object
return env; return env;
}; };
export default async function main( export default async function main(
client, client: Client,
contextName, paths: string[],
output, localConfig: VercelConfig | null,
stats, argv: any
localConfig,
args
) { ) {
let argv = null;
try {
argv = getArgs(client.argv.slice(2), args);
} catch (error) {
handleError(error);
return 1;
}
const { const {
apiUrl, apiUrl,
output,
authConfig: { token }, authConfig: { token },
} = client; } = client;
const { log, debug, error, warn } = output; const { log, debug, error, warn } = output;
const paths = Object.keys(stats);
const debugEnabled = argv['--debug']; const debugEnabled = argv['--debug'];
const { isTTY } = process.stdout; const { isTTY } = process.stdout;
@@ -265,6 +267,7 @@ export default async function main(
} }
let { org, project, status } = link; let { org, project, status } = link;
let newProjectName = null; let newProjectName = null;
let rootDirectory = project ? project.rootDirectory : null; let rootDirectory = project ? project.rootDirectory : null;
let sourceFilesOutsideRootDirectory = true; let sourceFilesOutsideRootDirectory = true;
@@ -340,6 +343,16 @@ export default async function main(
} }
} }
// At this point `org` should be populated
if (!org) {
throw new Error(`"org" is not defined`);
}
// Set the `contextName` and `currentTeam` as specified by the
// Project Settings, so that API calls happen with the proper scope
const contextName = org.slug;
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
// if we have `sourceFilesOutsideRootDirectory` set to `true`, we use the current path // if we have `sourceFilesOutsideRootDirectory` set to `true`, we use the current path
// and upload the entire directory. // and upload the entire directory.
const sourcePath = const sourcePath =
@@ -354,7 +367,7 @@ export default async function main(
path, path,
sourcePath, sourcePath,
project project
? `To change your Project Settings, go to https://vercel.com/${org.slug}/${project.name}/settings` ? `To change your Project Settings, go to https://vercel.com/${org?.slug}/${project.name}/settings`
: '' : ''
)) === false )) === false
) { ) {
@@ -373,7 +386,7 @@ export default async function main(
output.print( output.print(
`${prependEmoji( `${prependEmoji(
`The ${highlight( `The ${highlight(
localConfig[fileNameSymbol] localConfig[fileNameSymbol]!
)} file should be inside of the provided root directory.`, )} file should be inside of the provided root directory.`,
emoji('warning') emoji('warning')
)}\n` )}\n`
@@ -387,7 +400,7 @@ export default async function main(
output.print( output.print(
`${prependEmoji( `${prependEmoji(
`The ${code('name')} property in ${highlight( `The ${code('name')} property in ${highlight(
localConfig[fileNameSymbol] localConfig[fileNameSymbol]!
)} is deprecated (https://vercel.link/name-prop)`, )} is deprecated (https://vercel.link/name-prop)`,
emoji('warning') emoji('warning')
)}\n` )}\n`
@@ -395,7 +408,7 @@ export default async function main(
} }
// build `env` // build `env`
const isObject = item => const isObject = (item: any) =>
Object.prototype.toString.call(item) === '[object Object]'; Object.prototype.toString.call(item) === '[object Object]';
// This validation needs to happen on the client side because // This validation needs to happen on the client side because
@@ -404,7 +417,7 @@ export default async function main(
if (typeof localConfig.env !== 'undefined' && !isObject(localConfig.env)) { if (typeof localConfig.env !== 'undefined' && !isObject(localConfig.env)) {
error( error(
`The ${code('env')} property in ${highlight( `The ${code('env')} property in ${highlight(
localConfig[fileNameSymbol] localConfig[fileNameSymbol]!
)} needs to be an object` )} needs to be an object`
); );
return 1; return 1;
@@ -414,7 +427,7 @@ export default async function main(
if (!isObject(localConfig.build)) { if (!isObject(localConfig.build)) {
error( error(
`The ${code('build')} property in ${highlight( `The ${code('build')} property in ${highlight(
localConfig[fileNameSymbol] localConfig[fileNameSymbol]!
)} needs to be an object` )} needs to be an object`
); );
return 1; return 1;
@@ -426,7 +439,7 @@ export default async function main(
) { ) {
error( error(
`The ${code('build.env')} property in ${highlight( `The ${code('build.env')} property in ${highlight(
localConfig[fileNameSymbol] localConfig[fileNameSymbol]!
)} needs to be an object` )} needs to be an object`
); );
return 1; return 1;
@@ -466,7 +479,7 @@ export default async function main(
// build `regions` // build `regions`
const regionFlag = (argv['--regions'] || '') const regionFlag = (argv['--regions'] || '')
.split(',') .split(',')
.map(s => s.trim()) .map((s: string) => s.trim())
.filter(Boolean); .filter(Boolean);
const regions = regionFlag.length > 0 ? regionFlag : localConfig.regions; const regions = regionFlag.length > 0 ? regionFlag : localConfig.regions;
@@ -497,13 +510,13 @@ export default async function main(
target = 'production'; target = 'production';
} }
const currentTeam = org.type === 'team' ? org.id : undefined; const currentTeam = org?.type === 'team' ? org.id : undefined;
const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam }); const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam });
let deployStamp = stamp(); let deployStamp = stamp();
let deployment = null; let deployment = null;
try { try {
const createArgs = { const createArgs: any = {
name: project ? project.name : newProjectName, name: project ? project.name : newProjectName,
env: deploymentEnv, env: deploymentEnv,
build: { env: deploymentBuildEnv }, build: { env: deploymentBuildEnv },
@@ -527,7 +540,7 @@ export default async function main(
} }
deployment = await createDeploy( deployment = await createDeploy(
output, client,
now, now,
contextName, contextName,
[sourcePath], [sourcePath],
@@ -537,18 +550,15 @@ export default async function main(
path path
); );
if ( if (deployment.code === 'missing_project_settings') {
deployment instanceof Error &&
deployment.code === 'missing_project_settings'
) {
let { projectSettings, framework } = deployment; let { projectSettings, framework } = deployment;
if (rootDirectory) { if (rootDirectory) {
projectSettings.rootDirectory = rootDirectory; projectSettings.rootDirectory = rootDirectory;
} }
if (typeof sourceFilesOutsideRootDirectory !== 'undefined') { if (typeof sourceFilesOutsideRootDirectory !== 'undefined') {
projectSettings.sourceFilesOutsideRootDirectory = sourceFilesOutsideRootDirectory; projectSettings.sourceFilesOutsideRootDirectory =
sourceFilesOutsideRootDirectory;
} }
const settings = await editProjectSettings( const settings = await editProjectSettings(
@@ -563,7 +573,7 @@ export default async function main(
deployStamp = stamp(); deployStamp = stamp();
createArgs.deployStamp = deployStamp; createArgs.deployStamp = deployStamp;
deployment = await createDeploy( deployment = await createDeploy(
output, client,
now, now,
contextName, contextName,
[sourcePath], [sourcePath],
@@ -575,7 +585,7 @@ export default async function main(
} }
if (deployment instanceof NotDomainOwner) { if (deployment instanceof NotDomainOwner) {
output.error(deployment); output.error(deployment.message);
return 1; return 1;
} }
@@ -583,7 +593,7 @@ export default async function main(
output.error( output.error(
deployment.message || deployment.message ||
'An unexpected error occurred while deploying your project', 'An unexpected error occurred while deploying your project',
null, undefined,
'https://vercel.link/help', 'https://vercel.link/help',
'Contact Support' 'Contact Support'
); );
@@ -596,7 +606,7 @@ export default async function main(
} }
const deploymentResponse = await getDeploymentByIdOrHost( const deploymentResponse = await getDeploymentByIdOrHost(
now, client,
contextName, contextName,
deployment.id, deployment.id,
'v10' 'v10'
@@ -676,12 +686,7 @@ export default async function main(
output.error(err.message || 'Build failed'); output.error(err.message || 'Build failed');
output.error( output.error(
`Check your logs at https://${now.url}/_logs or run ${getCommandName( `Check your logs at https://${now.url}/_logs or run ${getCommandName(
`logs ${now.url}`, `logs ${now.url}`
{
// Backticks are interpreted as part of the URL, causing CMD+Click
// behavior to fail in editors like VSCode.
backticks: false,
}
)}` )}`
); );
@@ -715,7 +720,11 @@ export default async function main(
); );
} }
function handleCreateDeployError(output, error, localConfig) { function handleCreateDeployError(
output: Output,
error: Error,
localConfig: VercelConfig
) {
if (error instanceof InvalidDomain) { if (error instanceof InvalidDomain) {
output.error(`The domain ${error.meta.domain} is not valid`); output.error(`The domain ${error.meta.domain} is not valid`);
return 1; return 1;

View File

@@ -108,8 +108,5 @@ export default async function dev(
systemEnvValues, systemEnvValues,
}); });
process.once('SIGINT', () => devServer.stop());
process.once('SIGTERM', () => devServer.stop());
await devServer.start(...listen); await devServer.start(...listen);
} }

View File

@@ -13,7 +13,6 @@ import getDomainPrice from '../../util/domains/get-domain-price';
import { getCommandName } from '../../util/pkg-name'; import { getCommandName } from '../../util/pkg-name';
import { getDomainConfig } from '../../util/domains/get-domain-config'; import { getDomainConfig } from '../../util/domains/get-domain-config';
import code from '../../util/output/code'; import code from '../../util/output/code';
import wait from '../../util/output/wait';
import { getDomainRegistrar } from '../../util/domains/get-domain-registrar'; import { getDomainRegistrar } from '../../util/domains/get-domain-registrar';
type Options = {}; type Options = {};
@@ -59,7 +58,7 @@ export default async function inspect(
output.debug(`Fetching domain info`); output.debug(`Fetching domain info`);
const cancelWait = wait( output.spinner(
`Fetching Domain ${domainName} under ${chalk.bold(contextName)}` `Fetching Domain ${domainName} under ${chalk.bold(contextName)}`
); );
@@ -68,9 +67,6 @@ export default async function inspect(
client, client,
contextName, contextName,
domainName, domainName,
cancelWait,
}).finally(() => {
cancelWait();
}); });
if (typeof information === 'number') { if (typeof information === 'number') {
@@ -207,13 +203,11 @@ async function fetchInformation({
client, client,
contextName, contextName,
domainName, domainName,
cancelWait,
}: { }: {
output: Output; output: Output;
client: Client; client: Client;
contextName: string; contextName: string;
domainName: string; domainName: string;
cancelWait: () => void;
}) { }) {
const [domain, renewalPrice] = await Promise.all([ const [domain, renewalPrice] = await Promise.all([
getDomainByName(client, contextName, domainName, { ignoreWait: true }), getDomainByName(client, contextName, domainName, { ignoreWait: true }),
@@ -223,13 +217,11 @@ async function fetchInformation({
]); ]);
if (domain instanceof DomainNotFound) { if (domain instanceof DomainNotFound) {
cancelWait();
output.prettyError(domain); output.prettyError(domain);
return 1; return 1;
} }
if (domain instanceof DomainPermissionDenied) { if (domain instanceof DomainPermissionDenied) {
cancelWait();
output.prettyError(domain); output.prettyError(domain);
output.log(`Run ${getCommandName(`domains ls`)} to see your domains.`); output.log(`Run ${getCommandName(`domains ls`)} to see your domains.`);
return 1; return 1;
@@ -238,7 +230,6 @@ async function fetchInformation({
const projects = await findProjectsForDomain(client, domainName); const projects = await findProjectsForDomain(client, domainName);
if (projects instanceof Error) { if (projects instanceof Error) {
cancelWait();
output.prettyError(projects); output.prettyError(projects);
return 1; return 1;
} }

View File

@@ -2,7 +2,6 @@ import ms from 'ms';
import chalk from 'chalk'; import chalk from 'chalk';
import plural from 'pluralize'; import plural from 'pluralize';
import wait from '../../util/output/wait';
import Client from '../../util/client'; import Client from '../../util/client';
import getDomains from '../../util/domains/get-domains'; import getDomains from '../../util/domains/get-domains';
import getScope from '../../util/get-scope'; import getScope from '../../util/get-scope';
@@ -55,14 +54,9 @@ export default async function ls(
return 1; return 1;
} }
const cancelWait = wait(`Fetching Domains under ${chalk.bold(contextName)}`); output.spinner(`Fetching Domains under ${chalk.bold(contextName)}`);
const { domains, pagination } = await getDomains( const { domains, pagination } = await getDomains(client, nextTimestamp);
client,
nextTimestamp
).finally(() => {
cancelWait();
});
output.log( output.log(
`${plural('Domain', domains.length, true)} found under ${chalk.bold( `${plural('Domain', domains.length, true)} found under ${chalk.bold(

View File

@@ -12,7 +12,7 @@ import { getCommandName, getPkgName } from '../util/pkg-name';
import getGlobalPathConfig from '../util/config/global-path'; import getGlobalPathConfig from '../util/config/global-path';
import { writeToAuthConfigFile, writeToConfigFile } from '../util/config/files'; import { writeToAuthConfigFile, writeToConfigFile } from '../util/config/files';
import Client from '../util/client'; import Client from '../util/client';
import { LoginParams } from '../util/login/types'; import { LoginResult } from '../util/login/types';
const help = () => { const help = () => {
console.log(` console.log(`
@@ -46,7 +46,7 @@ const help = () => {
export default async function login(client: Client): Promise<number> { export default async function login(client: Client): Promise<number> {
let argv; let argv;
const { apiUrl, output } = client; const { output } = client;
try { try {
argv = getArgs(client.argv.slice(2)); argv = getArgs(client.argv.slice(2));
@@ -67,19 +67,18 @@ export default async function login(client: Client): Promise<number> {
const input = argv._[1]; const input = argv._[1];
let result: number | string = 1; let result: LoginResult = 1;
const params: LoginParams = { output, apiUrl };
if (input) { if (input) {
// Email or Team slug was provided via command line // Email or Team slug was provided via command line
if (validateEmail(input)) { if (validateEmail(input)) {
result = await doEmailLogin(params, input); result = await doEmailLogin(client, input);
} else { } else {
result = await doSsoLogin(params, input); result = await doSsoLogin(client, input);
} }
} else { } else {
// Interactive mode // Interactive mode
result = await prompt(params); result = await prompt(client);
} }
// The login function failed, so it returned an exit code // The login function failed, so it returned an exit code
@@ -87,12 +86,20 @@ export default async function login(client: Client): Promise<number> {
return result; return result;
} }
// If the token was upgraded (not a new login), then don't modify
// the current scope.
if (!client.authConfig.token) {
if (result.teamId) {
// SSO login, so set the current scope to the appropriate Team
client.config.currentTeam = result.teamId;
} else {
delete client.config.currentTeam;
}
}
// When `result` is a string it's the user's authentication token. // When `result` is a string it's the user's authentication token.
// It needs to be saved to the configuration file. // It needs to be saved to the configuration file.
client.authConfig.token = result; client.authConfig.token = result.token;
// New user, so we can't keep the team
delete client.config.currentTeam;
writeToAuthConfigFile(client.authConfig); writeToAuthConfigFile(client.authConfig);
writeToConfigFile(client.config); writeToConfigFile(client.config);

View File

@@ -1,6 +1,5 @@
import chalk from 'chalk'; import chalk from 'chalk';
import logo from '../util/output/logo'; import logo from '../util/output/logo';
// @ts-ignore
import { handleError } from '../util/error'; import { handleError } from '../util/error';
import { writeToConfigFile, writeToAuthConfigFile } from '../util/config/files'; import { writeToConfigFile, writeToAuthConfigFile } from '../util/config/files';
import getArgs from '../util/get-args'; import getArgs from '../util/get-args';
@@ -62,6 +61,7 @@ export default async function main(client: Client): Promise<number> {
try { try {
await client.fetch(`/v3/user/tokens/current`, { await client.fetch(`/v3/user/tokens/current`, {
method: 'DELETE', method: 'DELETE',
useCurrentTeam: false,
}); });
} catch (err) { } catch (err) {
if (err.status === 403) { if (err.status === 403) {

View File

@@ -243,7 +243,7 @@ function printLogShort(log: any) {
const date = new Date(log.created).toISOString(); const date = new Date(log.created).toISOString();
data.split('\n').forEach((line, i) => { data.split('\n').forEach(line => {
if ( if (
line.includes('START RequestId:') || line.includes('START RequestId:') ||
line.includes('END RequestId:') || line.includes('END RequestId:') ||
@@ -260,18 +260,9 @@ function printLogShort(log: any) {
} }
} }
if (i === 0) { console.log(
console.log( `${chalk.dim(date)} ${line.replace('[now-builder-debug] ', '')}`
`${chalk.dim(date)} ${line.replace('[now-builder-debug] ', '')}` );
);
} else {
console.log(
`${' '.repeat(date.length)} ${line.replace(
'[now-builder-debug] ',
''
)}`
);
}
}); });
return 0; return 0;

View File

@@ -8,7 +8,6 @@ import exit from '../util/exit';
import logo from '../util/output/logo'; import logo from '../util/output/logo';
import getScope from '../util/get-scope'; import getScope from '../util/get-scope';
import getCommandFlags from '../util/get-command-flags'; import getCommandFlags from '../util/get-command-flags';
import wait from '../util/output/wait';
import { getPkgName, getCommandName } from '../util/pkg-name.ts'; import { getPkgName, getCommandName } from '../util/pkg-name.ts';
const e = encodeURIComponent; const e = encodeURIComponent;
@@ -103,6 +102,7 @@ export default async client => {
}; };
async function run({ client, contextName }) { async function run({ client, contextName }) {
const { output } = client;
const args = argv._.slice(1); const args = argv._.slice(1);
const start = Date.now(); const start = Date.now();
@@ -118,7 +118,7 @@ async function run({ client, contextName }) {
return exit(2); return exit(2);
} }
const stopSpinner = wait(`Fetching projects in ${chalk.bold(contextName)}`); output.spinner(`Fetching projects in ${chalk.bold(contextName)}`);
let projectsUrl = '/v4/projects/?limit=20'; let projectsUrl = '/v4/projects/?limit=20';
@@ -131,7 +131,7 @@ async function run({ client, contextName }) {
method: 'GET', method: 'GET',
}); });
stopSpinner(); output.stopSpinner();
const elapsed = ms(new Date() - start); const elapsed = ms(new Date() - start);
@@ -187,25 +187,22 @@ async function run({ client, contextName }) {
const name = args[0]; const name = args[0];
// Check the existence of the project
try {
await client.fetch(`/projects/info/${e(name)}`);
} catch (err) {
if (err.status === 404) {
console.error(error('No such project exists'));
return exit(1);
}
}
const yes = await readConfirmation(name); const yes = await readConfirmation(name);
if (!yes) { if (!yes) {
console.error(error('User abort')); console.error(error('User abort'));
return exit(0); return exit(0);
} }
await client.fetch(`/v2/projects/${name}`, { try {
method: 'DELETE', await client.fetch(`/v2/projects/${e(name)}`, {
}); method: 'DELETE',
});
} catch (err) {
if (err.status === 404) {
console.error(error('No such project exists'));
return exit(1);
}
}
const elapsed = ms(new Date() - start); const elapsed = ms(new Date() - start);
console.log( console.log(
`${chalk.cyan('> Success!')} Project ${chalk.bold( `${chalk.cyan('> Success!')} Project ${chalk.bold(

View File

@@ -1,12 +1,9 @@
import chalk from 'chalk'; import chalk from 'chalk';
import stamp from '../../util/output/stamp.ts'; import stamp from '../../util/output/stamp.ts';
import info from '../../util/output/info'; import info from '../../util/output/info';
import error from '../../util/output/error';
import wait from '../../util/output/wait';
import rightPad from '../../util/output/right-pad'; import rightPad from '../../util/output/right-pad';
import eraseLines from '../../util/output/erase-lines'; import eraseLines from '../../util/output/erase-lines';
import chars from '../../util/output/chars'; import chars from '../../util/output/chars';
import success from '../../util/output/success';
import note from '../../util/output/note'; import note from '../../util/output/note';
import textInput from '../../util/input/text'; import textInput from '../../util/input/text';
import invite from './invite'; import invite from './invite';
@@ -40,14 +37,12 @@ export default async function add(client, teams) {
let slug; let slug;
let team; let team;
let elapsed; let elapsed;
let stopSpinner; const { output } = client;
console.log( output.log(
info( `Pick a team identifier for its url (e.g.: ${chalk.cyan(
`Pick a team identifier for its url (e.g.: ${chalk.cyan( '`vercel.com/acme`'
'`vercel.com/acme`' )})`
)})`
)
); );
do { do {
try { try {
@@ -61,7 +56,7 @@ export default async function add(client, teams) {
}); });
} catch (err) { } catch (err) {
if (err.message === 'USER_ABORT') { if (err.message === 'USER_ABORT') {
console.log(info('Aborted')); output.log('Aborted');
return 0; return 0;
} }
@@ -69,26 +64,26 @@ export default async function add(client, teams) {
} }
elapsed = stamp(); elapsed = stamp();
stopSpinner = wait(teamUrlPrefix + slug); output.spinner(teamUrlPrefix + slug);
let res; let res;
try { try {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
res = await teams.create({ slug }); res = await teams.create({ slug });
stopSpinner();
team = res; team = res;
} catch (err) { } catch (err) {
stopSpinner(); output.stopSpinner();
process.stdout.write(eraseLines(2)); process.stdout.write(eraseLines(2));
console.error(error(err.message)); output.error(err.message);
} }
} while (!team); } while (!team);
output.stopSpinner();
process.stdout.write(eraseLines(2)); process.stdout.write(eraseLines(2));
console.log(success(`Team created ${elapsed()}`)); output.success(`Team created ${elapsed()}`);
console.log(`${chalk.cyan(`${chars.tick} `) + teamUrlPrefix + slug}\n`); output.log(`${chalk.cyan(`${chars.tick} `) + teamUrlPrefix + slug}\n`);
console.log(info('Pick a display name for your team')); output.log('Pick a display name for your team');
let name; let name;
@@ -107,17 +102,16 @@ export default async function add(client, teams) {
} }
elapsed = stamp(); elapsed = stamp();
stopSpinner = wait(teamNamePrefix + name); output.spinner(teamNamePrefix + name);
const res = await teams.edit({ id: team.id, name }); const res = await teams.edit({ id: team.id, name });
stopSpinner(); output.stopSpinner();
process.stdout.write(eraseLines(2)); process.stdout.write(eraseLines(2));
if (res.error) { if (res.error) {
console.error(error(res.error.message)); output.error(res.error.message);
console.log(`${chalk.red(`${teamNamePrefix}`)}${name}`); output.log(`${chalk.red(`${teamNamePrefix}`)}${name}`);
return 1; return 1;
// TODO: maybe we want to ask the user to retry? not sure if // TODO: maybe we want to ask the user to retry? not sure if
@@ -126,10 +120,10 @@ export default async function add(client, teams) {
team = Object.assign(team, res); team = Object.assign(team, res);
console.log(success(`Team name saved ${elapsed()}`)); output.success(`Team name saved ${elapsed()}`);
console.log(`${chalk.cyan(`${chars.tick} `) + teamNamePrefix + team.name}\n`); output.log(`${chalk.cyan(`${chars.tick} `) + teamNamePrefix + team.name}\n`);
stopSpinner = wait('Saving'); output.spinner('Saving');
// Update config file // Update config file
const configCopy = Object.assign({}, client.config); const configCopy = Object.assign({}, client.config);
@@ -142,7 +136,7 @@ export default async function add(client, teams) {
writeToConfigFile(configCopy); writeToConfigFile(configCopy);
stopSpinner(); output.stopSpinner();
await invite(client, { _: [] }, teams, { await invite(client, { _: [] }, teams, {
introMsg: 'Invite your teammates! When done, press enter on an empty field', introMsg: 'Invite your teammates! When done, press enter on an empty field',

View File

@@ -1,11 +1,3 @@
// Packages
import chalk from 'chalk';
// Utilities
import wait from '../../util/output/wait';
import info from '../../util/output/info';
import error from '../../util/output/error';
import chars from '../../util/output/chars'; import chars from '../../util/output/chars';
import table from '../../util/output/table'; import table from '../../util/output/table';
import getUser from '../../util/get-user.ts'; import getUser from '../../util/get-user.ts';
@@ -23,7 +15,7 @@ export default async function list(client, argv, teams) {
return 1; return 1;
} }
const stopSpinner = wait('Fetching teams'); output.spinner('Fetching teams');
const { teams: list, pagination } = await teams.ls({ const { teams: list, pagination } = await teams.ls({
next, next,
apiVersion: 2, apiVersion: 2,
@@ -31,23 +23,19 @@ export default async function list(client, argv, teams) {
let { currentTeam } = config; let { currentTeam } = config;
const accountIsCurrent = !currentTeam; const accountIsCurrent = !currentTeam;
stopSpinner(); output.spinner('Fetching user information');
const stopUserSpinner = wait('Fetching user information');
let user; let user;
try { try {
user = await getUser(client); user = await getUser(client);
} catch (err) { } catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') { if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
console.error(error(err.message)); output.error(err.message);
return 1; return 1;
} }
throw err; throw err;
} }
stopUserSpinner();
if (accountIsCurrent) { if (accountIsCurrent) {
currentTeam = { currentTeam = {
slug: user.username || user.email, slug: user.username || user.email,
@@ -79,12 +67,12 @@ export default async function list(client, argv, teams) {
const count = teamList.length; const count = teamList.length;
if (!count) { if (!count) {
// Maybe should not happen // Maybe should not happen
console.error(error(`No team found`)); output.error(`No teams found`);
return 1; return 1;
} }
info(`${chalk.bold(count)} team${count > 1 ? 's' : ''} found`); output.stopSpinner();
console.log(); console.log(); // empty line
table( table(
['', 'id', 'email / name'], ['', 'id', 'email / name'],

View File

@@ -1,9 +1,10 @@
import chalk from 'chalk'; import chalk from 'chalk';
import logo from '../util/output/logo'; import logo from '../util/output/logo';
import getScope from '../util/get-scope.ts'; import getScope from '../util/get-scope';
import { getPkgName } from '../util/pkg-name.ts'; import { getPkgName } from '../util/pkg-name';
import getArgs from '../util/get-args.ts'; import getArgs from '../util/get-args';
import handleError from '../util/handle-error.ts'; import handleError from '../util/handle-error';
import Client from '../util/client';
const help = () => { const help = () => {
console.log(` console.log(`
@@ -31,7 +32,7 @@ const help = () => {
`); `);
}; };
export default async client => { export default async (client: Client) => {
const { output } = client; const { output } = client;
let argv; let argv;
try { try {

View File

@@ -50,6 +50,7 @@ import { SENTRY_DSN } from './util/constants.ts';
import getUpdateCommand from './util/get-update-command'; import getUpdateCommand from './util/get-update-command';
import { metrics, shouldCollectMetrics } from './util/metrics.ts'; import { metrics, shouldCollectMetrics } from './util/metrics.ts';
import { getCommandName, getTitleName } from './util/pkg-name.ts'; import { getCommandName, getTitleName } from './util/pkg-name.ts';
import doLoginPrompt from './util/login/prompt.ts';
const isCanary = pkg.version.includes('canary'); const isCanary = pkg.version.includes('canary');
@@ -422,13 +423,24 @@ const main = async () => {
) { ) {
if (isTTY) { if (isTTY) {
output.log(info(`No existing credentials found. Please log in:`)); output.log(info(`No existing credentials found. Please log in:`));
const result = await doLoginPrompt(client);
subcommand = 'login'; // The login function failed, so it returned an exit code
client.argv[2] = 'login'; if (typeof result === 'number') {
return result;
}
// Ensure that subcommands lead to login as well, if // When `result` is a string it's the user's authentication token.
// no credentials are defined // It needs to be saved to the configuration file.
client.argv = client.argv.splice(0, 3); client.authConfig.token = result;
// New user, so we can't keep the team
delete client.config.currentTeam;
configFiles.writeToAuthConfigFile(client.authConfig);
configFiles.writeToConfigFile(client.config);
output.debug(`Saved credentials in "${hp(VERCEL_DIR)}"`);
} else { } else {
output.prettyError({ output.prettyError({
message: message:

View File

@@ -16,7 +16,7 @@ export interface JSONObject {
} }
export interface AuthConfig { export interface AuthConfig {
token: string; token?: string;
skipWrite?: boolean; skipWrite?: boolean;
} }
@@ -145,7 +145,7 @@ export type Deployment = {
export type Alias = { export type Alias = {
uid: string; uid: string;
alias: string; alias: string;
created: string; createdAt: number;
deployment: { deployment: {
id: string; id: string;
url: string; url: string;
@@ -270,6 +270,7 @@ export interface Project extends ProjectSettings {
rootDirectory?: string | null; rootDirectory?: string | null;
latestDeployments?: Partial<Deployment>[]; latestDeployments?: Partial<Deployment>[];
autoExposeSystemEnvs?: boolean; autoExposeSystemEnvs?: boolean;
sourceFilesOutsideRootDirectory: boolean;
} }
export interface Org { export interface Org {

View File

@@ -1,5 +1,6 @@
import { Output } from '../output'; import { Output } from '../output';
import { Alias } from '../../types'; import { Alias } from '../../types';
import Client from '../client'; import Client from '../client';
export default async function findAliasByAliasOrId( export default async function findAliasByAliasOrId(
@@ -11,7 +12,6 @@ export default async function findAliasByAliasOrId(
`/now/aliases/${encodeURIComponent(getSafeAlias(aliasOrId))}` `/now/aliases/${encodeURIComponent(getSafeAlias(aliasOrId))}`
); );
} }
function getSafeAlias(alias: string) { function getSafeAlias(alias: string) {
return alias return alias
.replace(/^https:\/\//i, '') .replace(/^https:\/\//i, '')

View File

@@ -1,8 +1,9 @@
import { Alias } from '../../types'; import { Alias, PaginationOptions } from '../../types';
import Client from '../client'; import Client from '../client';
type Response = { type Response = {
aliases: Alias[]; aliases: Alias[];
pagination: PaginationOptions;
}; };
export default async function getAliases( export default async function getAliases(

View File

@@ -2,6 +2,6 @@ import Client from '../client';
export default async function removeAliasById(client: Client, id: string) { export default async function removeAliasById(client: Client, id: string) {
return client.fetch(`/now/aliases/${id}`, { return client.fetch(`/now/aliases/${id}`, {
method: 'DELETE' method: 'DELETE',
}); });
} }

View File

@@ -1,6 +1,5 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { resolve } from 'path'; import { resolve } from 'path';
import wait from '../output/wait';
import Client from '../client'; import Client from '../client';
import { Cert } from '../../types'; import { Cert } from '../../types';
@@ -10,7 +9,7 @@ export default async function createCertFromFile(
certPath: string, certPath: string,
caPath: string caPath: string
) { ) {
const cancelWait = wait('Adding your custom certificate'); client.output.spinner('Adding your custom certificate');
try { try {
const cert = readFileSync(resolve(certPath), 'utf8'); const cert = readFileSync(resolve(certPath), 'utf8');
@@ -36,7 +35,5 @@ export default async function createCertFromFile(
} }
throw error; throw error;
} finally {
cancelWait();
} }
} }

View File

@@ -3,7 +3,6 @@ import chalk from 'chalk';
import { Cert } from '../../types'; import { Cert } from '../../types';
import * as ERRORS from '../errors-ts'; import * as ERRORS from '../errors-ts';
import Client from '../client'; import Client from '../client';
import wait from '../output/wait';
import mapCertError from './map-cert-error'; import mapCertError from './map-cert-error';
export default async function startCertOrder( export default async function startCertOrder(
@@ -11,7 +10,7 @@ export default async function startCertOrder(
cns: string[], cns: string[],
context: string // eslint-disable-line context: string // eslint-disable-line
) { ) {
const cancelWait = wait( client.output.spinner(
`Issuing a certificate for ${chalk.bold(cns.join(', '))}` `Issuing a certificate for ${chalk.bold(cns.join(', '))}`
); );
try { try {
@@ -22,10 +21,8 @@ export default async function startCertOrder(
domains: cns, domains: cns,
}, },
}); });
cancelWait();
return cert; return cert;
} catch (error) { } catch (error) {
cancelWait();
if (error.code === 'cert_order_not_found') { if (error.code === 'cert_order_not_found') {
return new ERRORS.CertOrderNotFound(cns); return new ERRORS.CertOrderNotFound(cns);
} }

View File

@@ -1,5 +1,4 @@
import chalk from 'chalk'; import chalk from 'chalk';
import wait from '../output/wait';
import Client from '../client'; import Client from '../client';
export type CertificateChallenge = { export type CertificateChallenge = {
@@ -24,23 +23,17 @@ export default async function startCertOrder(
cns: string[], cns: string[],
contextName: string contextName: string
) { ) {
const cancelWait = wait( client.output.spinner(
`Starting certificate issuance for ${chalk.bold( `Starting certificate issuance for ${chalk.bold(
cns.join(', ') cns.join(', ')
)} under ${chalk.bold(contextName)}` )} under ${chalk.bold(contextName)}`
); );
try { const order = await client.fetch<CertificateOrder>('/v3/now/certs', {
const order = await client.fetch<CertificateOrder>('/v3/now/certs', { method: 'PATCH',
method: 'PATCH', body: {
body: { op: 'startOrder',
op: 'startOrder', domains: cns,
domains: cns },
} });
}); return order;
cancelWait();
return order;
} catch (error) {
cancelWait();
throw error;
}
} }

View File

@@ -90,8 +90,10 @@ export default class Client extends EventEmitter {
} }
const headers = new Headers(opts.headers); const headers = new Headers(opts.headers);
headers.set('authorization', `Bearer ${this.authConfig.token}`);
headers.set('user-agent', ua); headers.set('user-agent', ua);
if (this.authConfig.token) {
headers.set('authorization', `Bearer ${this.authConfig.token}`);
}
let body; let body;
if (isJSONObject(opts.body)) { if (isJSONObject(opts.body)) {
@@ -163,7 +165,7 @@ export default class Client extends EventEmitter {
process.exit(1); process.exit(1);
} }
this.authConfig.token = result; this.authConfig.token = result.token;
writeToAuthConfigFile(this.authConfig); writeToAuthConfigFile(this.authConfig);
}); });

View File

@@ -3,17 +3,21 @@ import * as ERRORS_TS from '../errors-ts';
import * as ERRORS from '../errors'; import * as ERRORS from '../errors';
import { NowError } from '../now-error'; import { NowError } from '../now-error';
import mapCertError from '../certs/map-cert-error'; import mapCertError from '../certs/map-cert-error';
import { Org } from '../../types';
import Now from '..';
import Client from '../client';
import { DeploymentError } from '../../../../client/dist';
export default async function createDeploy( export default async function createDeploy(
output, client: Client,
now, now: Now,
contextName, contextName: string,
paths, paths: string[],
createArgs, createArgs: any,
org, org: Org | null,
isSettingUpProject, isSettingUpProject: boolean,
cwd cwd?: string
) { ): Promise<any | DeploymentError> {
try { try {
return await now.create(paths, createArgs, org, isSettingUpProject, cwd); return await now.create(paths, createArgs, org, isSettingUpProject, cwd);
} catch (error) { } catch (error) {
@@ -83,16 +87,17 @@ export default async function createDeploy(
// If the cert is missing we try to generate a new one and the retry // If the cert is missing we try to generate a new one and the retry
if (error.code === 'cert_missing') { if (error.code === 'cert_missing') {
const result = await generateCertForDeploy( const result = await generateCertForDeploy(
output, client,
now,
contextName, contextName,
error.value error.value
); );
if (result instanceof NowError) { if (result instanceof NowError) {
return result; return result;
} }
return createDeploy( return createDeploy(
output, client,
now, now,
contextName, contextName,
paths, paths,

View File

@@ -1,17 +1,16 @@
import psl from 'psl'; import psl from 'psl';
import { NowError } from '../now-error'; import { NowError } from '../now-error';
import { Output } from '../output';
import Client from '../client'; import Client from '../client';
import createCertForCns from '../certs/create-cert-for-cns'; import createCertForCns from '../certs/create-cert-for-cns';
import setupDomain from '../domains/setup-domain'; import setupDomain from '../domains/setup-domain';
import { InvalidDomain } from '../errors-ts'; import { InvalidDomain } from '../errors-ts';
export default async function generateCertForDeploy( export default async function generateCertForDeploy(
output: Output,
client: Client, client: Client,
contextName: string, contextName: string,
deployURL: string deployURL: string
) { ) {
const { output } = client;
const parsedDomain = psl.parse(deployURL); const parsedDomain = psl.parse(deployURL);
if (parsedDomain.error) { if (parsedDomain.error) {
return new InvalidDomain(deployURL, parsedDomain.error.message); return new InvalidDomain(deployURL, parsedDomain.error.message);

View File

@@ -145,6 +145,7 @@ export default class DevServer {
private devServerPids: Set<number>; private devServerPids: Set<number>;
private projectSettings?: ProjectSettings; private projectSettings?: ProjectSettings;
private vercelConfigWarning: boolean;
private getVercelConfigPromise: Promise<VercelConfig> | null; private getVercelConfigPromise: Promise<VercelConfig> | null;
private blockingBuildsPromise: Promise<void> | null; private blockingBuildsPromise: Promise<void> | null;
private updateBuildersPromise: Promise<void> | null; private updateBuildersPromise: Promise<void> | null;
@@ -181,6 +182,7 @@ export default class DevServer {
this.inProgressBuilds = new Map(); this.inProgressBuilds = new Map();
this.devCacheDir = join(getVercelDirectory(cwd), 'cache'); this.devCacheDir = join(getVercelDirectory(cwd), 'cache');
this.vercelConfigWarning = false;
this.getVercelConfigPromise = null; this.getVercelConfigPromise = null;
this.blockingBuildsPromise = null; this.blockingBuildsPromise = null;
this.updateBuildersPromise = null; this.updateBuildersPromise = null;
@@ -636,6 +638,20 @@ export default class DevServer {
await this.validateVercelConfig(config); await this.validateVercelConfig(config);
// TODO: temporarily strip and warn since `has` is not implemented yet
config.routes = (config.routes || []).filter(route => {
if ('has' in route) {
if (!this.vercelConfigWarning) {
this.vercelConfigWarning = true;
this.output.warn(
`The "has" property in ${config[fileNameSymbol]} will be ignored during development. Deployments will work as expected.`
);
}
return false;
}
return true;
});
this.caseSensitive = hasNewRoutingProperties(config); this.caseSensitive = hasNewRoutingProperties(config);
this.apiDir = detectApiDirectory(config.builds || []); this.apiDir = detectApiDirectory(config.builds || []);
this.apiExtensions = detectApiExtensions(config.builds || []); this.apiExtensions = detectApiExtensions(config.builds || []);

View File

@@ -4,7 +4,6 @@ import { Output } from '../output';
import Client from '../client'; import Client from '../client';
import getDomainDNSRecords from './get-domain-dns-records'; import getDomainDNSRecords from './get-domain-dns-records';
import getDomains from '../domains/get-domains'; import getDomains from '../domains/get-domains';
import wait from '../output/wait';
import chalk from 'chalk'; import chalk from 'chalk';
export type DomainRecordsItem = { export type DomainRecordsItem = {
@@ -60,11 +59,7 @@ async function getDomainNames(
contextName: string, contextName: string,
next?: number next?: number
) { ) {
const cancelWait = wait(`Fetching domains under ${chalk.bold(contextName)}`); client.output.spinner(`Fetching domains under ${chalk.bold(contextName)}`);
try { const { domains, pagination } = await getDomains(client, next);
const { domains, pagination } = await getDomains(client, next); return { domainNames: domains.map(domain => domain.name), pagination };
return { domainNames: domains.map(domain => domain.name), pagination };
} finally {
cancelWait();
}
} }

View File

@@ -4,7 +4,6 @@ import { resolve } from 'path';
import { Response } from 'node-fetch'; import { Response } from 'node-fetch';
import { DomainNotFound, InvalidDomain } from '../errors-ts'; import { DomainNotFound, InvalidDomain } from '../errors-ts';
import Client from '../client'; import Client from '../client';
import wait from '../output/wait';
type JSONResponse = { type JSONResponse = {
recordIds: string[]; recordIds: string[];
@@ -16,7 +15,7 @@ export default async function importZonefile(
domain: string, domain: string,
zonefilePath: string zonefilePath: string
) { ) {
const cancelWait = wait( client.output.spinner(
`Importing Zone file for domain ${domain} under ${chalk.bold(contextName)}` `Importing Zone file for domain ${domain} under ${chalk.bold(contextName)}`
); );
const zonefile = readFileSync(resolve(zonefilePath), 'utf8'); const zonefile = readFileSync(resolve(zonefilePath), 'utf8');
@@ -33,10 +32,8 @@ export default async function importZonefile(
); );
const { recordIds } = (await res.json()) as JSONResponse; const { recordIds } = (await res.json()) as JSONResponse;
cancelWait();
return recordIds; return recordIds;
} catch (error) { } catch (error) {
cancelWait();
if (error.code === 'not_found') { if (error.code === 'not_found') {
return new DomainNotFound(domain, contextName); return new DomainNotFound(domain, contextName);
} }

View File

@@ -3,7 +3,6 @@ import retry from 'async-retry';
import { DomainAlreadyExists, InvalidDomain } from '../errors-ts'; import { DomainAlreadyExists, InvalidDomain } from '../errors-ts';
import { Domain } from '../../types'; import { Domain } from '../../types';
import Client from '../client'; import Client from '../client';
import wait from '../output/wait';
type Response = { type Response = {
domain: Domain; domain: Domain;
@@ -14,17 +13,11 @@ export default async function addDomain(
domain: string, domain: string,
contextName: string contextName: string
) { ) {
const cancelWait = wait( client.output.spinner(
`Adding domain ${domain} under ${chalk.bold(contextName)}` `Adding domain ${domain} under ${chalk.bold(contextName)}`
); );
try { const addedDomain = await performAddRequest(client, domain);
const addedDomain = await performAddRequest(client, domain); return addedDomain;
cancelWait();
return addedDomain;
} catch (error) {
cancelWait();
throw error;
}
} }
async function performAddRequest(client: Client, domainName: string) { async function performAddRequest(client: Client, domainName: string) {

View File

@@ -1,6 +1,5 @@
import chalk from 'chalk'; import chalk from 'chalk';
import Client from '../client'; import Client from '../client';
import wait from '../output/wait';
import { Domain } from '../../types'; import { Domain } from '../../types';
import { DomainPermissionDenied, DomainNotFound } from '../errors-ts'; import { DomainPermissionDenied, DomainNotFound } from '../errors-ts';
@@ -16,14 +15,15 @@ export default async function getDomainByName(
ignoreWait?: boolean; ignoreWait?: boolean;
} = {} } = {}
) { ) {
const cancelWait = options.ignoreWait if (!options.ignoreWait) {
? null client.output.spinner(
: wait(`Fetching domain ${domainName} under ${chalk.bold(contextName)}`); `Fetching domain ${domainName} under ${chalk.bold(contextName)}`
);
}
try { try {
const { domain } = await client.fetch<Response>( const { domain } = await client.fetch<Response>(
`/v4/domains/${encodeURIComponent(domainName)}` `/v4/domains/${encodeURIComponent(domainName)}`
); );
return domain; return domain;
} catch (error) { } catch (error) {
if (error.status === 404) { if (error.status === 404) {
@@ -35,7 +35,5 @@ export default async function getDomainByName(
} }
throw error; throw error;
} finally {
cancelWait?.();
} }
} }

View File

@@ -1,6 +1,5 @@
import chalk from 'chalk'; import chalk from 'chalk';
import Client from '../client'; import Client from '../client';
import wait from '../output/wait';
import { Domain } from '../../types'; import { Domain } from '../../types';
type Response = { type Response = {
@@ -12,7 +11,7 @@ export async function getDomain(
contextName: string, contextName: string,
domainName: string domainName: string
) { ) {
const cancelWait = wait( client.output.spinner(
`Fetching domain ${domainName} under ${chalk.bold(contextName)}` `Fetching domain ${domainName} under ${chalk.bold(contextName)}`
); );
try { try {
@@ -27,7 +26,5 @@ export async function getDomain(
} }
throw error; throw error;
} finally {
cancelWait();
} }
} }

View File

@@ -3,22 +3,19 @@ import retry from 'async-retry';
import { Domain } from '../../types'; import { Domain } from '../../types';
import * as ERRORS from '../errors-ts'; import * as ERRORS from '../errors-ts';
import Client from '../client'; import Client from '../client';
import wait from '../output/wait';
export default async function verifyDomain( export default async function verifyDomain(
client: Client, client: Client,
domainName: string, domainName: string,
contextName: string contextName: string
) { ) {
const cancelWait = wait( client.output.spinner(
`Verifying domain ${domainName} under ${chalk.bold(contextName)}` `Verifying domain ${domainName} under ${chalk.bold(contextName)}`
); );
try { try {
const { domain } = await performVerifyDomain(client, domainName); const { domain } = await performVerifyDomain(client, domainName);
cancelWait();
return domain; return domain;
} catch (error) { } catch (error) {
cancelWait();
if (error.code === 'verification_failed') { if (error.code === 'verification_failed') {
return new ERRORS.DomainVerificationFailed({ return new ERRORS.DomainVerificationFailed({
purchased: false, purchased: false,

View File

@@ -588,7 +588,7 @@ export class DeploymentNotFound extends NowError<
'DEPLOYMENT_NOT_FOUND', 'DEPLOYMENT_NOT_FOUND',
{ id: string; context: string } { id: string; context: string }
> { > {
constructor({ context, id = '' }: { context: string; id: string }) { constructor({ context, id = '' }: { context: string; id?: string }) {
super({ super({
code: 'DEPLOYMENT_NOT_FOUND', code: 'DEPLOYMENT_NOT_FOUND',
meta: { id, context }, meta: { id, context },

View File

@@ -1,15 +0,0 @@
import { EventEmitter } from 'events';
async function* eventListenerToGenerator(event: string, emitter: EventEmitter) {
while (true) {
yield new Promise(resolve => {
const handler = (...args: any[]) => {
emitter.removeListener(event, handler);
resolve(...args);
};
emitter.on(event, handler);
});
}
}
export default eventListenerToGenerator;

View File

@@ -20,6 +20,7 @@ export default class Now extends EventEmitter {
constructor({ constructor({
apiUrl, apiUrl,
token, token,
url = null,
currentTeam = null, currentTeam = null,
forceNew = false, forceNew = false,
withCache = false, withCache = false,
@@ -28,6 +29,7 @@ export default class Now extends EventEmitter {
}) { }) {
super(); super();
this.url = url;
this._token = token; this._token = token;
this._debug = debug; this._debug = debug;
this._forceNew = forceNew; this._forceNew = forceNew;

View File

@@ -22,7 +22,7 @@ export default async function editProjectSettings(
output: Output, output: Output,
projectSettings: PartialProjectSettings | null, projectSettings: PartialProjectSettings | null,
framework: Framework | null, framework: Framework | null,
autoConfirm: boolean autoConfirm?: boolean
): Promise<ProjectSettings> { ): Promise<ProjectSettings> {
// create new settings object, missing values will be filled with `null` // create new settings object, missing values will be filled with `null`
const settings: ProjectSettings = Object.assign( const settings: ProjectSettings = Object.assign(

View File

@@ -177,7 +177,7 @@ export default async function setupAndLink(
} }
const deployment = await createDeploy( const deployment = await createDeploy(
output, client,
now, now,
config.currentTeam || 'current user', config.currentTeam || 'current user',
[sourcePath], [sourcePath],

View File

@@ -1,13 +1,13 @@
import { URL } from 'url'; import { URL } from 'url';
import { LoginParams } from './types'; import Client from '../client';
import doOauthLogin from './oauth'; import doOauthLogin from './oauth';
export default function doBitbucketLogin(params: LoginParams) { export default function doBitbucketLogin(client: Client, ssoUserId?: string) {
const url = new URL( const url = new URL(
'/api/registration/bitbucket/connect', '/api/registration/bitbucket/connect',
// Can't use `apiUrl` here because this URL sets a // Can't use `apiUrl` here because this URL sets a
// cookie that the OAuth callback URL depends on // cookie that the OAuth callback URL depends on
'https://vercel.com' 'https://vercel.com'
); );
return doOauthLogin(params, url, 'Bitbucket'); return doOauthLogin(client, url, 'Bitbucket', ssoUserId);
} }

View File

@@ -4,20 +4,22 @@ import highlight from '../output/highlight';
import eraseLines from '../output/erase-lines'; import eraseLines from '../output/erase-lines';
import verify from './verify'; import verify from './verify';
import executeLogin from './login'; import executeLogin from './login';
import { LoginParams } from './types'; import Client from '../client';
import { LoginResult } from './types';
export default async function doEmailLogin( export default async function doEmailLogin(
params: LoginParams, client: Client,
email: string email: string,
): Promise<number | string> { ssoUserId?: string
): Promise<LoginResult> {
let securityCode; let securityCode;
let verificationToken; let verificationToken;
const { apiUrl, output } = params; const { output } = client;
output.spinner('Sending you an email'); output.spinner('Sending you an email');
try { try {
const data = await executeLogin(apiUrl, email); const data = await executeLogin(client, email);
verificationToken = data.token; verificationToken = data.token;
securityCode = data.securityCode; securityCode = data.securityCode;
} catch (err) { } catch (err) {
@@ -42,9 +44,15 @@ export default async function doEmailLogin(
while (!token) { while (!token) {
try { try {
await sleep(ms('1s')); await sleep(ms('1s'));
token = await verify(email, verificationToken, params); token = await verify(
client,
email,
verificationToken,
'Email',
ssoUserId
);
} catch (err) { } catch (err) {
if (err.message !== 'Confirmation incomplete') { if (err.serverMessage !== 'Confirmation incomplete') {
output.error(err.message); output.error(err.message);
return 1; return 1;
} }
@@ -52,5 +60,5 @@ export default async function doEmailLogin(
} }
output.success(`Email authentication complete for ${email}`); output.success(`Email authentication complete for ${email}`);
return token; return { token };
} }

View File

@@ -1,13 +1,13 @@
import { URL } from 'url'; import { URL } from 'url';
import { LoginParams } from './types'; import Client from '../client';
import doOauthLogin from './oauth'; import doOauthLogin from './oauth';
export default function doGithubLogin(params: LoginParams) { export default function doGithubLogin(client: Client, ssoUserId?: string) {
const url = new URL( const url = new URL(
'/api/registration/login-with-github', '/api/registration/login-with-github',
// Can't use `apiUrl` here because this URL sets a // Can't use `apiUrl` here because this URL sets a
// cookie that the OAuth callback URL depends on // cookie that the OAuth callback URL depends on
'https://vercel.com' 'https://vercel.com'
); );
return doOauthLogin(params, url, 'GitHub'); return doOauthLogin(client, url, 'GitHub', ssoUserId);
} }

View File

@@ -1,10 +1,10 @@
import { URL } from 'url'; import { URL } from 'url';
import { LoginParams } from './types'; import Client from '../client';
import doOauthLogin from './oauth'; import doOauthLogin from './oauth';
export default function doGitlabLogin(params: LoginParams) { export default function doGitlabLogin(client: Client, ssoUserId?: string) {
// Can't use `apiUrl` here because this URL sets a // Can't use `apiUrl` here because this URL sets a
// cookie that the OAuth callback URL depends on // cookie that the OAuth callback URL depends on
const url = new URL('/api/registration/gitlab/connect', 'https://vercel.com'); const url = new URL('/api/registration/gitlab/connect', 'https://vercel.com');
return doOauthLogin(params, url, 'GitLab'); return doOauthLogin(client, url, 'GitLab', ssoUserId);
} }

View File

@@ -1,47 +1,28 @@
import fetch from 'node-fetch'; import Client from '../client';
import { hostname } from 'os';
import { InvalidEmail, AccountNotFound } from '../errors-ts'; import { InvalidEmail, AccountNotFound } from '../errors-ts';
import ua from '../ua';
import { getTitleName } from '../pkg-name';
import { LoginData } from './types'; import { LoginData } from './types';
export default async function login( export default async function login(
apiUrl: string, client: Client,
email: string, email: string
mode: 'login' | 'signup' = 'login'
): Promise<LoginData> { ): Promise<LoginData> {
const hyphens = new RegExp('-', 'g'); try {
const host = hostname().replace(hyphens, ' ').replace('.local', ''); return await client.fetch<LoginData>(`/registration?mode=login`, {
const tokenName = `${getTitleName()} CLI on ${host}`; method: 'POST',
body: { email },
const response = await fetch(`${apiUrl}/now/registration?mode=${mode}`, { });
method: 'POST', } catch (err) {
headers: { if (err.code === 'not_exists') {
'Content-Type': 'application/json',
'User-Agent': ua,
},
body: JSON.stringify({
tokenName,
email,
}),
});
const body = await response.json();
if (!response.ok) {
const { error = {} } = body;
if (error.code === 'not_exists') {
throw new AccountNotFound( throw new AccountNotFound(
email, email,
`Please sign up: https://vercel.com/signup` `Please sign up: https://vercel.com/signup`
); );
} }
if (error.code === 'invalid_email') { if (err.code === 'invalid_email') {
throw new InvalidEmail(email, error.message); throw new InvalidEmail(email, err.message);
} }
throw new Error(`Unexpected error: ${error.message}`); throw new Error(`Unexpected error: ${err.message}`);
} }
return body as LoginData;
} }

View File

@@ -2,40 +2,40 @@ import http from 'http';
import open from 'open'; import open from 'open';
import { URL } from 'url'; import { URL } from 'url';
import listen from 'async-listen'; import listen from 'async-listen';
import { hostname } from 'os'; import Client from '../client';
import { LoginParams } from './types';
import prompt from './prompt'; import prompt from './prompt';
import verify from './verify'; import verify from './verify';
import { getTitleName } from '../pkg-name';
import highlight from '../output/highlight'; import highlight from '../output/highlight';
import link from '../output/link';
import eraseLines from '../output/erase-lines';
import { LoginResult } from './types';
export default async function doOauthLogin( export default async function doOauthLogin(
params: LoginParams, client: Client,
url: URL, url: URL,
provider: string provider: string,
): Promise<number | string> { ssoUserId?: string
const { output } = params; ): Promise<LoginResult> {
const { output } = client;
output.spinner(
`Please complete the ${provider} authentication in your web browser`
);
const server = http.createServer(); const server = http.createServer();
const address = await listen(server, 0, '127.0.0.1'); const address = await listen(server, 0, '127.0.0.1');
const { port } = new URL(address); const { port } = new URL(address);
url.searchParams.append('mode', 'login'); url.searchParams.set('mode', 'login');
url.searchParams.append('next', `http://localhost:${port}`); url.searchParams.set('next', `http://localhost:${port}`);
// Append token name param output.log(`Please visit the following URL in your web browser:`);
const hyphens = new RegExp('-', 'g'); output.log(link(url.href));
const host = hostname().replace(hyphens, ' ').replace('.local', ''); output.spinner(`Waiting for ${provider} authentication to be completed`);
const tokenName = `${getTitleName()} CLI on ${host} via ${provider}`;
url.searchParams.append('tokenName', tokenName);
try { try {
const [query] = await Promise.all([ const [query] = await Promise.all([
new Promise<URL['searchParams']>((resolve, reject) => { new Promise<URL['searchParams']>((resolve, reject) => {
server.once('request', (req, res) => { server.once('request', (req, res) => {
// Close the HTTP connection to prevent
// `server.close()` from hanging
res.setHeader('connection', 'close');
const query = new URL(req.url || '/', 'http://localhost') const query = new URL(req.url || '/', 'http://localhost')
.searchParams; .searchParams;
resolve(query); resolve(query);
@@ -78,6 +78,9 @@ export default async function doOauthLogin(
open(url.href), open(url.href),
]); ]);
output.stopSpinner();
output.print(eraseLines(3));
const loginError = query.get('loginError'); const loginError = query.get('loginError');
if (loginError) { if (loginError) {
const err = JSON.parse(loginError); const err = JSON.parse(loginError);
@@ -88,16 +91,17 @@ export default async function doOauthLogin(
// If an `ssoUserId` was returned, then the SAML Profile is not yet connected // 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 // to a Team member. Prompt the user to log in to a Vercel account now, which
// will complete the connection to the SAML Profile. // will complete the connection to the SAML Profile.
const ssoUserId = query.get('ssoUserId'); const ssoUserIdParam = query.get('ssoUserId');
if (ssoUserId) { if (ssoUserIdParam) {
output.log( output.log(
'Please log in to your Vercel account to complete SAML connection.' 'Please log in to your Vercel account to complete SAML connection.'
); );
return prompt({ ...params, ssoUserId }); return prompt(client, undefined, ssoUserIdParam);
} }
const email = query.get('email'); const email = query.get('email');
const verificationToken = query.get('token'); const verificationToken = query.get('token');
const teamId = query.get('teamId');
if (!email || !verificationToken) { if (!email || !verificationToken) {
output.error( output.error(
'Verification token was not provided. Please contact support.' 'Verification token was not provided. Please contact support.'
@@ -106,11 +110,17 @@ export default async function doOauthLogin(
} }
output.spinner('Verifying authentication token'); output.spinner('Verifying authentication token');
const token = await verify(email, verificationToken, params); const token = await verify(
client,
email,
verificationToken,
provider,
ssoUserId
);
output.success( output.success(
`${provider} authentication complete for ${highlight(email)}` `${provider} authentication complete for ${highlight(email)}`
); );
return token; return { token, teamId };
} finally { } finally {
server.close(); server.close();
} }

View File

@@ -1,8 +1,9 @@
import inquirer from 'inquirer'; import inquirer from 'inquirer';
import Client from '../client';
import error from '../output/error'; import error from '../output/error';
import listInput from '../input/list'; import listInput from '../input/list';
import { getCommandName } from '../pkg-name'; import { getCommandName } from '../pkg-name';
import { LoginParams, SAMLError } from './types'; import { LoginResult, SAMLError } from './types';
import doSsoLogin from './sso'; import doSsoLogin from './sso';
import doEmailLogin from './email'; import doEmailLogin from './email';
import doGithubLogin from './github'; import doGithubLogin from './github';
@@ -10,10 +11,11 @@ import doGitlabLogin from './gitlab';
import doBitbucketLogin from './bitbucket'; import doBitbucketLogin from './bitbucket';
export default async function prompt( export default async function prompt(
params: LoginParams, client: Client,
error?: Pick<SAMLError, 'teamId'> error?: Pick<SAMLError, 'teamId'>,
ssoUserId?: string
) { ) {
let result: number | string = 1; let result: LoginResult = 1;
const choices = [ const choices = [
{ name: 'Continue with GitHub', value: 'github', short: 'github' }, { name: 'Continue with GitHub', value: 'github', short: 'github' },
@@ -23,7 +25,7 @@ export default async function prompt(
{ name: 'Continue with SAML Single Sign-On', value: 'sso', short: 'sso' }, { name: 'Continue with SAML Single Sign-On', value: 'sso', short: 'sso' },
]; ];
if (params.ssoUserId || (error && !error.teamId)) { if (ssoUserId || (error && !error.teamId)) {
// Remove SAML login option if we're connecting SAML Profile, // Remove SAML login option if we're connecting SAML Profile,
// or if this is a SAML error for a user / team without SAML // or if this is a SAML error for a user / team without SAML
choices.pop(); choices.pop();
@@ -35,17 +37,17 @@ export default async function prompt(
}); });
if (choice === 'github') { if (choice === 'github') {
result = await doGithubLogin(params); result = await doGithubLogin(client, ssoUserId);
} else if (choice === 'gitlab') { } else if (choice === 'gitlab') {
result = await doGitlabLogin(params); result = await doGitlabLogin(client, ssoUserId);
} else if (choice === 'bitbucket') { } else if (choice === 'bitbucket') {
result = await doBitbucketLogin(params); result = await doBitbucketLogin(client, ssoUserId);
} else if (choice === 'email') { } else if (choice === 'email') {
const email = await readInput('Enter your email address'); const email = await readInput('Enter your email address');
result = await doEmailLogin(params, email); result = await doEmailLogin(client, email, ssoUserId);
} else if (choice === 'sso') { } else if (choice === 'sso') {
const slug = error?.teamId || (await readInput('Enter your Team slug')); const slug = error?.teamId || (await readInput('Enter your Team slug'));
result = await doSsoLogin(params, slug); result = await doSsoLogin(client, slug, ssoUserId);
} }
return result; return result;

View File

@@ -1,28 +1,28 @@
import { bold } from 'chalk'; import { bold } from 'chalk';
import doSsoLogin from './sso'; import doSsoLogin from './sso';
import showLoginPrompt from './prompt'; import showLoginPrompt from './prompt';
import { LoginParams, SAMLError } from './types'; import { LoginResult, SAMLError } from './types';
import confirm from '../input/confirm'; import confirm from '../input/confirm';
import Client from '../client';
export default async function reauthenticate( export default async function reauthenticate(
params: LoginParams, client: Client,
error: Pick<SAMLError, 'enforced' | 'scope' | 'teamId'> error: Pick<SAMLError, 'enforced' | 'scope' | 'teamId'>
): Promise<string | number> { ): Promise<LoginResult> {
let result: string | number = 1;
if (error.teamId && error.enforced) { if (error.teamId && error.enforced) {
// If team has SAML enforced then trigger the SSO login directly // If team has SAML enforced then trigger the SSO login directly
params.output.log( client.output.log(
`You must re-authenticate with SAML to use ${bold(error.scope)} scope.` `You must re-authenticate with SAML to use ${bold(error.scope)} scope.`
); );
if (await confirm(`Log in with SAML?`, true)) { if (await confirm(`Log in with SAML?`, true)) {
result = await doSsoLogin(params, error.teamId); return doSsoLogin(client, error.teamId);
} }
} else { } else {
// Personal account, or team that does not have SAML enforced // Personal account, or team that does not have SAML enforced
params.output.log( client.output.log(
`You must re-authenticate to use ${bold(error.scope)} scope.` `You must re-authenticate to use ${bold(error.scope)} scope.`
); );
result = await showLoginPrompt(params, error); return showLoginPrompt(client, error);
} }
return result; return 1;
} }

View File

@@ -1,9 +1,13 @@
import { URL } from 'url'; import { URL } from 'url';
import { LoginParams } from './types'; import Client from '../client';
import doOauthLogin from './oauth'; import doOauthLogin from './oauth';
export default function doSsoLogin(params: LoginParams, teamIdOrSlug: string) { export default function doSsoLogin(
const url = new URL('/auth/sso', params.apiUrl); client: Client,
url.searchParams.append('teamId', teamIdOrSlug); teamIdOrSlug: string,
return doOauthLogin(params, url, 'SAML Single Sign-On'); ssoUserId?: string
) {
const url = new URL('/auth/sso', client.apiUrl);
url.searchParams.set('teamId', teamIdOrSlug);
return doOauthLogin(client, url, 'SAML Single Sign-On', ssoUserId);
} }

View File

@@ -1,16 +1,19 @@
import { Output } from '../output';
export interface LoginParams {
apiUrl: string;
output: Output;
ssoUserId?: string;
}
export interface LoginData { export interface LoginData {
token: string; token: string;
securityCode: string; securityCode: string;
} }
export type LoginResult = number | LoginResultSuccess;
export interface LoginResultSuccess {
token: string;
teamId?: string | null;
}
export interface VerifyData {
token: string;
}
export interface SAMLError { export interface SAMLError {
saml?: true; saml?: true;
teamId: string | null; teamId: string | null;

View File

@@ -1,33 +1,34 @@
import { URL } from 'url'; import { URL } from 'url';
import fetch from 'node-fetch'; import Client from '../client';
import ua from '../ua'; import { hostname } from 'os';
import { LoginParams } from './types'; import { getTitleName } from '../pkg-name';
import { VerifyData } from './types';
export default async function verify( export default async function verify(
client: Client,
email: string, email: string,
verificationToken: string, verificationToken: string,
{ apiUrl, ssoUserId }: LoginParams provider: string,
ssoUserId?: string
): Promise<string> { ): Promise<string> {
const url = new URL('/registration/verify', apiUrl); const url = new URL('/registration/verify', client.apiUrl);
url.searchParams.append('email', email); url.searchParams.set('email', email);
url.searchParams.append('token', verificationToken); url.searchParams.set('token', verificationToken);
if (!client.authConfig.token) {
// Set the "name" of the Token that will be created
const hyphens = new RegExp('-', 'g');
const host = hostname().replace(hyphens, ' ').replace('.local', '');
const tokenName = `${getTitleName()} CLI on ${host} via ${provider}`;
url.searchParams.set('tokenName', tokenName);
}
// If `ssoUserId` is defined then this verification
// will complete the SAML two-step login connection
if (ssoUserId) { if (ssoUserId) {
url.searchParams.append('ssoUserId', ssoUserId); url.searchParams.set('ssoUserId', ssoUserId);
} }
const res = await fetch(url.href, { const { token } = await client.fetch<VerifyData>(url.href);
headers: { 'User-Agent': ua }, return token;
});
const body = await res.json();
if (!res.ok) {
const err = new Error(
`Unexpected ${res.status} status code from verify API`
);
Object.assign(err, body.error);
throw err;
}
return body.token;
} }

View File

@@ -1,6 +1,5 @@
import chalk from 'chalk'; import chalk from 'chalk';
import Client from '../client'; import Client from '../client';
import wait from '../output/wait';
import { ProjectAliasTarget } from '../../types'; import { ProjectAliasTarget } from '../../types';
export async function addDomainToProject( export async function addDomainToProject(
@@ -8,7 +7,7 @@ export async function addDomainToProject(
projectNameOrId: string, projectNameOrId: string,
domain: string domain: string
) { ) {
const cancelWait = wait( client.output.spinner(
`Adding domain ${domain} to project ${chalk.bold(projectNameOrId)}` `Adding domain ${domain} to project ${chalk.bold(projectNameOrId)}`
); );
try { try {
@@ -40,7 +39,5 @@ export async function addDomainToProject(
} }
throw err; throw err;
} finally {
cancelWait();
} }
} }

View File

@@ -1,6 +1,5 @@
import chalk from 'chalk'; import chalk from 'chalk';
import Client from '../client'; import Client from '../client';
import wait from '../output/wait';
import { ProjectAliasTarget } from '../../types'; import { ProjectAliasTarget } from '../../types';
export async function removeDomainFromProject( export async function removeDomainFromProject(
@@ -8,7 +7,7 @@ export async function removeDomainFromProject(
projectNameOrId: string, projectNameOrId: string,
domain: string domain: string
) { ) {
const cancelWait = wait( client.output.spinner(
`Removing domain ${domain} from project ${chalk.bold(projectNameOrId)}` `Removing domain ${domain} from project ${chalk.bold(projectNameOrId)}`
); );
try { try {
@@ -28,7 +27,5 @@ export async function removeDomainFromProject(
} }
throw err; throw err;
} finally {
cancelWait();
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -0,0 +1,11 @@
import { dep1 } from './js/ecmascript-module';
const { dep2 } = require('./js/commonjs-module');
module.exports = (req, res) => {
if (req && typeof dep1 === 'string' && typeof dep2 === 'string') {
res.end('mixed-modules:js');
} else {
res.end('import failed');
}
};

View File

@@ -0,0 +1,12 @@
import { dep1 } from './js/em-jay-ess.mjs';
async function handler(_req, res) {
const cjs = await import('./js/commonjs-module.js');
if (dep1 === 'dep1' && cjs.default && cjs.default.dep2 === 'dep2') {
res.end('mixed-modules:mjs');
} else {
res.end('import failed');
}
};
export default handler;

View File

@@ -0,0 +1,11 @@
import { IncomingMessage, ServerResponse } from 'http';
import { dep1 } from './ts/ecmascript-module';
const { dep2 } = require('./ts/commonjs-module');
module.exports = (req: IncomingMessage, res: ServerResponse) => {
if (req && typeof dep1 === 'string' && typeof dep2 === 'string') {
res.end('mixed-modules:ts');
} else {
res.end('import failed');
}
};

View File

@@ -0,0 +1 @@
module.exports = { dep2: 'dep2' };

View File

@@ -0,0 +1,2 @@
export const dep1 = 'dep1';
export const another = 'another';

View File

@@ -0,0 +1,2 @@
export const dep1 = 'dep1';
export const another = 'another';

View File

@@ -0,0 +1,3 @@
{
"private": true
}

View File

@@ -0,0 +1 @@
module.exports = { dep2: 'dep2' };

View File

@@ -0,0 +1 @@
export const dep1 = 'dep1';

View File

@@ -0,0 +1,3 @@
{
"private": true
}

View File

@@ -0,0 +1,12 @@
import { dep1 } from '../js/em-jay-ess.mjs';
async function handler(_req, res) {
const cjs = await import('../js/commonjs-module.js');
if (dep1 === 'dep1' && cjs.default && cjs.default.dep2 === 'dep2') {
res.end('mixed-modules:auto');
} else {
res.end('import failed');
}
}
export default handler;

View File

@@ -0,0 +1,12 @@
import { dep1 } from '../../js/em-jay-ess.mjs';
async function handler(_req, res) {
const cjs = await import('../../js/commonjs-module.js');
if (dep1 === 'dep1' && cjs.default && cjs.default.dep2 === 'dep2') {
res.end('mixed-modules:also');
} else {
res.end('import failed');
}
}
export default handler;

View File

@@ -0,0 +1,4 @@
{
"private": true,
"type": "module"
}

View File

@@ -0,0 +1,7 @@
{
"version": 2,
"builds": [
{ "src": "entrypoint**", "use": "@vercel/node@canary" },
{ "src": "type-module-package-json/**/*.js", "use": "@vercel/node@canary" }
]
}

View File

@@ -1710,7 +1710,9 @@ test(
expectHeader('image/svg+xml'), expectHeader('image/svg+xml'),
fetchOpts('image/webp') fetchOpts('image/webp')
); );
// bmp should bypass: serve as-is /* Disabled bmp because `next dev` bypasses
* and production will convert. Eventually
* we can enable once `next dev` supports it.
await testPath( await testPath(
200, 200,
toUrl('/test.bmp', 64, 50), toUrl('/test.bmp', 64, 50),
@@ -1718,6 +1720,7 @@ test(
expectHeader('image/bmp'), expectHeader('image/bmp'),
fetchOpts('image/webp') fetchOpts('image/webp')
); );
*/
// animated gif should bypass: serve as-is // animated gif should bypass: serve as-is
await testPath( await testPath(
200, 200,
@@ -1729,6 +1732,25 @@ test(
}) })
); );
test(
'[vercel dev] 40-mixed-modules',
testFixtureStdio('40-mixed-modules', async testPath => {
await testPath(200, '/entrypoint.js', 'mixed-modules:js');
await testPath(200, '/entrypoint.mjs', 'mixed-modules:mjs');
await testPath(200, '/entrypoint.ts', 'mixed-modules:ts');
await testPath(
200,
'/type-module-package-json/auto.js',
'mixed-modules:auto'
);
await testPath(
200,
'/type-module-package-json/nested/also.js',
'mixed-modules:also'
);
})
);
test( test(
'[vercel dev] Use `@vercel/python` with Flask requirements.txt', '[vercel dev] Use `@vercel/python` with Flask requirements.txt',
testFixtureStdio('python-flask', async testPath => { testFixtureStdio('python-flask', async testPath => {

View File

@@ -157,9 +157,10 @@ function mockLoginApi(req, res) {
let { pathname = '/', query = {} } = parseUrl(url, true); let { pathname = '/', query = {} } = parseUrl(url, true);
console.log(`[mock-login-server] ${method} ${pathname}`); console.log(`[mock-login-server] ${method} ${pathname}`);
const securityCode = 'Bears Beets Battlestar Galactica'; const securityCode = 'Bears Beets Battlestar Galactica';
res.setHeader('content-type', 'application/json');
if ( if (
method === 'POST' && method === 'POST' &&
pathname === '/now/registration' && pathname === '/registration' &&
query.mode === 'login' query.mode === 'login'
) { ) {
res.end(JSON.stringify({ token, securityCode })); res.end(JSON.stringify({ token, securityCode }));

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/client", "name": "@vercel/client",
"version": "10.1.0", "version": "10.1.2-canary.2",
"main": "dist/index.js", "main": "dist/index.js",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",
"homepage": "https://vercel.com", "homepage": "https://vercel.com",
@@ -29,7 +29,7 @@
"@types/node": "12.0.4", "@types/node": "12.0.4",
"@types/node-fetch": "2.5.4", "@types/node-fetch": "2.5.4",
"@types/recursive-readdir": "2.2.0", "@types/recursive-readdir": "2.2.0",
"typescript": "3.9.3" "typescript": "4.3.4"
}, },
"jest": { "jest": {
"preset": "ts-jest", "preset": "ts-jest",
@@ -40,7 +40,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "2.11.0", "@vercel/build-utils": "2.11.2-canary.2",
"@zeit/fetch": "5.2.0", "@zeit/fetch": "5.2.0",
"async-retry": "1.2.3", "async-retry": "1.2.3",
"async-sema": "3.0.0", "async-sema": "3.0.0",

View File

@@ -102,6 +102,7 @@ export const fileNameSymbol = Symbol('fileName');
export interface VercelConfig { export interface VercelConfig {
[fileNameSymbol]?: string; [fileNameSymbol]?: string;
name?: string; name?: string;
meta?: string[];
version?: number; version?: number;
public?: boolean; public?: boolean;
env?: Dictionary<string>; env?: Dictionary<string>;

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/frameworks", "name": "@vercel/frameworks",
"version": "0.4.0", "version": "0.4.2-canary.1",
"main": "./dist/frameworks.js", "main": "./dist/frameworks.js",
"types": "./dist/frameworks.d.ts", "types": "./dist/frameworks.d.ts",
"files": [ "files": [
@@ -20,10 +20,10 @@
"@types/js-yaml": "3.12.1", "@types/js-yaml": "3.12.1",
"@types/node": "12.0.4", "@types/node": "12.0.4",
"@types/node-fetch": "2.5.8", "@types/node-fetch": "2.5.8",
"@vercel/routing-utils": "1.11.1", "@vercel/routing-utils": "1.11.3-canary.0",
"ajv": "6.12.2", "ajv": "6.12.2",
"jest": "24.9.0", "jest": "24.9.0",
"ts-jest": "24.1.0", "ts-jest": "24.1.0",
"typescript": "3.9.3" "typescript": "4.3.4"
} }
} }

View File

@@ -104,6 +104,7 @@ export const frameworks = [
devCommand: 'next dev --port $PORT', devCommand: 'next dev --port $PORT',
buildCommand: 'next build', buildCommand: 'next build',
getOutputDirName: async () => 'public', getOutputDirName: async () => 'public',
cachePattern: '.next/cache/**',
}, },
{ {
name: 'Gatsby.js', name: 'Gatsby.js',

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/go", "name": "@vercel/go",
"version": "1.2.2", "version": "1.2.3-canary.0",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
@@ -31,6 +31,6 @@
"node-fetch": "^2.2.1", "node-fetch": "^2.2.1",
"string-argv": "0.3.1", "string-argv": "0.3.1",
"tar": "4.4.6", "tar": "4.4.6",
"typescript": "3.9.3" "typescript": "4.3.4"
} }
} }

View File

@@ -1,2 +0,0 @@
/bridge.*
/launcher.*

18
packages/node-bridge/bridge.d.ts vendored Normal file
View File

@@ -0,0 +1,18 @@
/// <reference types="node" />
import { Server } from 'http';
import {
VercelProxyRequest,
VercelProxyResponse,
VercelProxyEvent,
ServerLike,
} from './types';
export declare class Bridge {
constructor(server?: ServerLike, shouldStoreEvents?: boolean);
setServer(server: ServerLike): void;
setStoreEvents(shouldStoreEvents: boolean): void;
listen(): void | Server;
launcher(event: VercelProxyEvent, context: any): Promise<VercelProxyResponse>;
consumeEvent(reqId: string): VercelProxyRequest;
}
export {};

View File

@@ -1,43 +1,4 @@
/// <reference types="node" /> const { request } = require('http');
import { AddressInfo } from 'net';
import { APIGatewayProxyEvent, Context } from 'aws-lambda';
import {
Server,
IncomingHttpHeaders,
OutgoingHttpHeaders,
request,
} from 'http';
interface NowProxyEvent {
Action: string;
body: string;
}
export interface NowProxyRequest {
isApiGateway?: boolean;
method: string;
path: string;
headers: IncomingHttpHeaders;
body: Buffer;
}
export interface NowProxyResponse {
statusCode: number;
headers: OutgoingHttpHeaders;
body: string;
encoding: BufferEncoding;
}
interface ServerLike {
timeout?: number;
listen: (
opts: {
host?: string;
port?: number;
},
callback: (this: Server | null) => void
) => Server | void;
}
/** /**
* If the `http.Server` handler function throws an error asynchronously, * If the `http.Server` handler function throws an error asynchronously,
@@ -51,8 +12,11 @@ process.on('unhandledRejection', err => {
process.exit(1); process.exit(1);
}); });
function normalizeNowProxyEvent(event: NowProxyEvent): NowProxyRequest { /**
let bodyBuffer: Buffer | null; * @param {import('./types').VercelProxyEvent} event
*/
function normalizeProxyEvent(event) {
let bodyBuffer;
const { method, path, headers, encoding, body } = JSON.parse(event.body); const { method, path, headers, encoding, body } = JSON.parse(event.body);
if (body) { if (body) {
@@ -70,10 +34,11 @@ function normalizeNowProxyEvent(event: NowProxyEvent): NowProxyRequest {
return { isApiGateway: false, method, path, headers, body: bodyBuffer }; return { isApiGateway: false, method, path, headers, body: bodyBuffer };
} }
function normalizeAPIGatewayProxyEvent( /**
event: APIGatewayProxyEvent * @param {import('aws-lambda').APIGatewayProxyEvent} event
): NowProxyRequest { */
let bodyBuffer: Buffer | null; function normalizeAPIGatewayProxyEvent(event) {
let bodyBuffer;
const { httpMethod: method, path, headers, body } = event; const { httpMethod: method, path, headers, body } = event;
if (body) { if (body) {
@@ -89,12 +54,13 @@ function normalizeAPIGatewayProxyEvent(
return { isApiGateway: true, method, path, headers, body: bodyBuffer }; return { isApiGateway: true, method, path, headers, body: bodyBuffer };
} }
function normalizeEvent( /**
event: NowProxyEvent | APIGatewayProxyEvent * @param {import('./types').VercelProxyEvent | import('aws-lambda').APIGatewayProxyEvent} event
): NowProxyRequest { */
function normalizeEvent(event) {
if ('Action' in event) { if ('Action' in event) {
if (event.Action === 'Invoke') { if (event.Action === 'Invoke') {
return normalizeNowProxyEvent(event); return normalizeProxyEvent(event);
} else { } else {
throw new Error(`Unexpected event.Action: ${event.Action}`); throw new Error(`Unexpected event.Action: ${event.Action}`);
} }
@@ -103,35 +69,40 @@ function normalizeEvent(
} }
} }
export class Bridge { class Bridge {
private server: ServerLike | null; /**
private listening: Promise<AddressInfo>; * @param {import('./types').ServerLike | null} server
private resolveListening: (info: AddressInfo) => void; * @param {boolean} shouldStoreEvents
private events: { [key: string]: NowProxyRequest } = {}; */
private reqIdSeed = 1; constructor(server = null, shouldStoreEvents = false) {
private shouldStoreEvents = false; this.server = server;
constructor(server?: ServerLike, shouldStoreEvents = false) {
this.server = null;
this.shouldStoreEvents = shouldStoreEvents; this.shouldStoreEvents = shouldStoreEvents;
if (server) {
this.setServer(server);
}
this.launcher = this.launcher.bind(this); this.launcher = this.launcher.bind(this);
this.reqIdSeed = 1;
// This is just to appease TypeScript strict mode, since it doesn't /**
// understand that the Promise constructor is synchronous * @type {{ [key: string]: import('./types').VercelProxyRequest }}
this.resolveListening = (_info: AddressInfo) => {}; // eslint-disable-line @typescript-eslint/no-unused-vars */
this.events = {};
this.listening = new Promise(resolve => { this.listening = new Promise(resolve => {
this.resolveListening = resolve; this.resolveListening = resolve;
}); });
} }
setServer(server: ServerLike) { /**
* @param {import('./types').ServerLike} server
*/
setServer(server) {
this.server = server; this.server = server;
} }
/**
* @param {boolean} shouldStoreEvents
*/
setStoreEvents(shouldStoreEvents) {
this.shouldStoreEvents = shouldStoreEvents;
}
listen() { listen() {
const { server, resolveListening } = this; const { server, resolveListening } = this;
if (!server) { if (!server) {
@@ -173,10 +144,13 @@ export class Bridge {
); );
} }
async launcher( /**
event: NowProxyEvent | APIGatewayProxyEvent, *
context: Pick<Context, 'callbackWaitsForEmptyEventLoop'> * @param {import('./types').VercelProxyEvent | import('aws-lambda').APIGatewayProxyEvent} event
): Promise<NowProxyResponse> { * @param {import('aws-lambda').Context} context
* @return {Promise<{statusCode: number, headers: import('http').IncomingHttpHeaders, body: string, encoding: 'base64'}>}
*/
async launcher(event, context) {
context.callbackWaitsForEmptyEventLoop = false; context.callbackWaitsForEmptyEventLoop = false;
const { port } = await this.listening; const { port } = await this.listening;
@@ -194,7 +168,10 @@ export class Bridge {
const opts = { hostname: '127.0.0.1', port, path, method }; const opts = { hostname: '127.0.0.1', port, path, method };
const req = request(opts, res => { const req = request(opts, res => {
const response = res; const response = res;
const respBodyChunks: Buffer[] = []; /**
* @type {Buffer[]}
*/
const respBodyChunks = [];
response.on('data', chunk => respBodyChunks.push(Buffer.from(chunk))); response.on('data', chunk => respBodyChunks.push(Buffer.from(chunk)));
response.on('error', reject); response.on('error', reject);
response.on('end', () => { response.on('end', () => {
@@ -227,18 +204,14 @@ export class Bridge {
for (const [name, value] of Object.entries(headers)) { for (const [name, value] of Object.entries(headers)) {
if (value === undefined) { if (value === undefined) {
console.error( console.error(
'Skipping HTTP request header %j because value is undefined', `Skipping HTTP request header "${name}" because value is undefined`
name
); );
continue; continue;
} }
try { try {
req.setHeader(name, value); req.setHeader(name, value);
} catch (err) { } catch (err) {
console.error( console.error(`Skipping HTTP request header: "${name}: ${value}"`);
'Skipping HTTP request header: %j',
`${name}: ${value}`
);
console.error(err.message); console.error(err.message);
} }
} }
@@ -248,9 +221,15 @@ export class Bridge {
}); });
} }
consumeEvent(reqId: string) { /**
* @param {string} reqId
* @return {import('./types').VercelProxyRequest}
*/
consumeEvent(reqId) {
const event = this.events[reqId]; const event = this.events[reqId];
delete this.events[reqId]; delete this.events[reqId];
return event; return event;
} }
} }
module.exports = { Bridge };

16
packages/node-bridge/launcher.d.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
import { Bridge } from './bridge';
import { LauncherConfiguration } from './types';
export declare function makeVercelLauncher(
config: LauncherConfiguration
): string;
export declare function getVercelLauncher({
entrypointPath,
helpersPath,
shouldAddHelpers,
}: LauncherConfiguration): () => Bridge;
export declare function makeAwsLauncher(config: LauncherConfiguration): string;
export declare function getAwsLauncher({
entrypointPath,
awsLambdaHandler,
}: LauncherConfiguration): (e: any, context: any, callback: any) => any;
export {};

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