Compare commits

..

68 Commits

Author SHA1 Message Date
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
Nathan Rajlich
f2d396caae Publish Stable
- @vercel/build-utils@2.11.0
 - vercel@23.0.0
 - @vercel/client@10.1.0
 - @vercel/frameworks@0.4.0
 - @vercel/node@1.11.0
 - @vercel/python@2.0.3
 - @vercel/routing-utils@1.11.1
2021-05-25 14:56:11 -07:00
Matheus Fernandes
001f2f60b8 Use proper Apache License format (#6189)
The existing LICENSE file was missing the Appendix, and also
used weird formatting. Now it's an exact copy of the original:

https://www.apache.org/licenses/LICENSE-2.0.txt.
2021-05-25 13:27:32 -07:00
Nathan Rajlich
78ca930287 [cli] Show user's name in vc switch command (#6288)
This more closely matches the Team picker on vercel.com.

Will still show "email" if no "name" is defined.
2021-05-25 13:18:14 -07:00
Nathan Rajlich
b03e18df12 Publish Canary
- vercel@22.0.2-canary.7
 - @vercel/python@2.0.3-canary.0
2021-05-25 11:07:16 -07:00
Nathan Rajlich
3a6b8b072c [cli] Reauthenticate scopes with limited access in vc switch (#6280)
In the `vc switch` command, if your current access token results in
"limited" Team information being returned, then show a lock emoji
next to the team/user name in the select input.

When a locked scope is selected, then pre-emptively prompt the
user to re-authenticate using a valid login method in relation to
the desired scope.

https://user-images.githubusercontent.com/71256/119441172-87abae80-bcda-11eb-801a-cb6837bae353.mov

[ch21964]
2021-05-25 11:03:06 -07:00
Steven
d480cd6bbd [cli] Bump codecov to 3.8.2 (#6279)
Closes #5814
2021-05-24 23:16:02 +00:00
Nathan Rajlich
181b624bf4 [cli] Add SAML reauthentication logic when using different scope (#6263)
When the API returns a SAML error, then show the proper reauthentication prompt depending on the scope being requested:

Team with SAML enforced, shows only SSO login option:

<img width="476" alt="Screen Shot 2021-05-24 at 1 50 29 PM" src="https://user-images.githubusercontent.com/71256/119406131-31694c00-bc97-11eb-858a-52e5fe7052d1.png">

Team with SAML enabled, but not enforced, prompts with all login methods:

<img width="352" alt="Screen Shot 2021-05-24 at 1 50 36 PM" src="https://user-images.githubusercontent.com/71256/119406134-3201e280-bc97-11eb-9166-60fbfec47ee0.png">

Team without SAML enabled, or User scope, shows prompt with SSO option removed:

<img width="366" alt="Screen Shot 2021-05-24 at 1 50 44 PM" src="https://user-images.githubusercontent.com/71256/119406137-3201e280-bc97-11eb-8c5c-b88eb9983500.png">

[ch21964]
2021-05-24 15:16:24 -07:00
Nathan Rajlich
200495e4ce [cli] Use ts-eager to execute build script (#6278)
* [cli] Use `ts-eager` to execute build script

* Use `node -r ts-eager/register` to workaround Windows issue
2021-05-24 18:14:55 -04:00
Steven
9178f14c40 Publish Stable
- @vercel/python@2.0.2
2021-05-24 16:46:49 -04:00
Nathan Rajlich
9a790f5352 [cli] Set tokenName query param for Git provider login methods (#6277)
We're not currently setting the `tokenName` when logging in via a
Git provider, so the name becomes the default "Website" one which
is confusing / incorrect.
2021-05-24 13:38:55 -07:00
Steven
33d8be7f8f [cli] Fix vc logout retry (#6276)
* [cli] Fix `vc logout` retry

* Fix logout message
2021-05-24 16:26:01 -04:00
JJ Kasper
82aa86b786 Apply dependabot changes (#6275)
* Bump elliptic in /packages/cli/test/dev/fixtures/02-angular-node

Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.2 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.2...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump node-fetch from 2.6.0 to 2.6.1 in /api

Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/bitinn/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump ssri from 6.0.1 to 6.0.2 in /examples/blitzjs

Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump hosted-git-info from 2.8.8 to 2.8.9 in /examples/angular

Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump hosted-git-info from 2.8.8 to 2.8.9 in /examples/blitzjs

Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump ssri in /packages/cli/test/dev/fixtures/02-angular-node

Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump ssri in /packages/cli/test/dev/fixtures/04-create-react-app

Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump ssri in /packages/cli/test/dev/fixtures/03-aurelia

Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump hosted-git-info in /packages/cli/test/dev/fixtures/02-angular-node

Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump url-parse in /packages/cli/test/dev/fixtures/04-create-react-app

Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.4.7 to 1.5.1.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.4.7...1.5.1)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump url-parse in /packages/cli/test/dev/fixtures/03-aurelia

Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.4.7 to 1.5.1.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.4.7...1.5.1)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump handlebars in /packages/cli/test/dev/fixtures/02-angular-node

Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.7.6 to 4.7.7.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.7.6...v4.7.7)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump lodash in /packages/cli/test/dev/fixtures/02-angular-node

Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump hosted-git-info in /packages/cli/test/dev/fixtures/03-aurelia

Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump hosted-git-info

Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump lodash in /packages/cli/test/dev/fixtures/03-aurelia

Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump url-parse in /packages/cli/test/dev/fixtures/02-angular-node

Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.4.7 to 1.5.1.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.4.7...1.5.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Steven <steven@ceriously.com>
2021-05-24 15:49:31 -04:00
Steven
6288d5ef43 Publish Canary
- vercel@22.0.2-canary.6
 - @vercel/client@10.0.1-canary.5
 - @vercel/node@1.10.1-canary.2
 - @vercel/python@2.0.2-canary.0
2021-05-24 13:37:57 -04:00
Steven
00a97b7ae2 [node] Bump nft to 0.12.2 (#6274) 2021-05-24 13:34:28 -04:00
Steven
a19fb3aa0f [python] Fix installRequirement to use explicit version (#6272)
* [python] Fix installRequirement to use explicit version

* Remove `--upgrade`
2021-05-24 11:41:57 -04:00
Nathan Rajlich
93fae76f9c [cli] Simplify emoji module (#6260) 2021-05-21 13:53:13 -07:00
Nathan Rajlich
e5131d5557 [cli] Update NowConfig references to VercelConfig (#6256)
The `NowConfig` interface is deprecated.
2021-05-21 12:21:38 -07:00
Nathan Rajlich
930063638c [cli] Refactor vc logs command (#6259) 2021-05-21 11:36:58 -07:00
Nathan Rajlich
7b509ae672 [cli] Refactor vc switch command (#6257)
Refactors the `vc switch` command to more closely match how the team picker works on the Vercel dashboard:

 * Converts to TypeScript
 * Adds separator between user scope and team scopes
 * Sorts teams by name
 * No longer places the current scope at the top of the list (but the current scope is still selected by default)
2021-05-20 11:19:38 -07:00
Nathan Rajlich
711da37771 [cli] Refactor vc inspect command (#6258) 2021-05-20 10:30:33 -07:00
Steven
5e50b96994 Publish Canary
- vercel@22.0.2-canary.5
 - @vercel/node@1.10.1-canary.1
2021-05-13 16:40:36 -04:00
Steven
35d2f5950f [node] Bump nft to 0.12.0 (#6229) 2021-05-13 16:22:58 -04:00
Steven
37f969416c Publish Canary
- @vercel/build-utils@2.10.3-canary.4
 - vercel@22.0.2-canary.4
 - @vercel/client@10.0.1-canary.4
 - @vercel/node@1.10.1-canary.0
2021-05-10 14:01:41 -04:00
Steven
75630e0982 [node] Bump nft to 0.11.2 (#6211)
* [node] Bump nft to 0.11.2

* Fix tests since node 10.x is discontinued
2021-05-10 14:01:02 -04:00
dependabot[bot]
038c228af4 Bump hosted-git-info from 2.8.8 to 2.8.9 (#6200)
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-07 16:48:18 -04:00
dependabot[bot]
a17aaebba3 Bump lodash from 4.17.19 to 4.17.21 (#6195)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-07 16:40:35 -04:00
dependabot[bot]
c39ba31b4b Bump handlebars from 4.7.6 to 4.7.7 (#6196)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.7.6 to 4.7.7.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.7.6...v4.7.7)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-07 16:31:07 -04:00
Nathan Rajlich
91414d6f38 [build-utils] Remove 15-yarn-ignore-engines test fixture (#6193)
It's failing during runtime, like here: https://github.com/vercel/vercel/runs/2520732314

With an error like so:

```
[GET] / 2021-05-06T20:02:55.411Z	undefined	ERROR	The gRPC
binary module was not installed. This may be fixed by running "npm rebuild"
Original error: Cannot find module
'/var/task/node_modules/grpc/src/node/extension_binary/node-v83-linux-x64-glibc/grpc_node.node'
Require stack:
- /var/task/node_modules/grpc/src/grpc_extension.js
- /var/task/node_modules/grpc/src/client_interceptors.js
- /var/task/node_modules/grpc/src/client.js
- /var/task/node_modules/grpc/index.js
- /var/task/node_modules/google-gax/build/src/grpc.js
- /var/task/node_modules/google-gax/build/src/index.js
-
/var/task/node_modules/@google-cloud/scheduler/src/v1beta1/cloud_scheduler_client.js
- /var/task/node_modules/@google-cloud/scheduler/src/v1beta1/index.js
- /var/task/node_modules/@google-cloud/scheduler/src/index.js
- /var/task/index.js
- /var/task/___vc_launcher.js
- /var/runtime/UserFunction.js
- /var/runtime/index.js 2021-05-06T20:02:55.411Z	undefined	ERROR
Did you forget to add it to "dependencies" in `package.json`? RequestId:
2cdba37e-4bf4-4a2f-b443-4ebcc99fe308 Error: Runtime exited with error: exit
status 1 Runtime.ExitError
```

Because grpc is failing with NFT.

But also, the test is no longer necessary because we no longer support Node 8 which was what the original test intended https://github.com/vercel/now-builders/pull/463
2021-05-06 22:43:22 +00:00
Nathan Rajlich
00bb723db3 Publish Canary
- @vercel/build-utils@2.10.3-canary.3
 - vercel@22.0.2-canary.3
 - @vercel/client@10.0.1-canary.3
 - @vercel/frameworks@0.3.3-canary.3
2021-05-05 13:09:43 -07:00
Nathan Rajlich
65a97720b3 [cli] Refactor vc login command (#6185)
Refactors the CLI `vc login` command with the following features:

* Adds GitHub, GitLab and Bitbucket as login options
* Uses a "list" input to select which login method to use (same list as `vc init`)
* Support connecting SAML Profile to Vercel user during login
2021-05-05 13:08:26 -07:00
Steven
88eaf6efab [examples] Update pusher example to use deploy button (#6183) 2021-05-04 11:55:40 -04:00
Sam Ko
abf159d5e0 Update @ionic/angular devCommand (#6158)
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>
2021-05-04 16:48:14 +02:00
luc
a23879b507 Publish Canary
- @vercel/build-utils@2.10.3-canary.2
 - vercel@22.0.2-canary.2
 - @vercel/client@10.0.1-canary.2
 - @vercel/frameworks@0.3.3-canary.2
2021-05-04 09:33:09 +02:00
Luc Leray
a713b9170b [frameworks] Add placeholder to frameworks' dev command (#6168)
* adjust type

* add placeholders

* adjust unit test
2021-05-03 17:26:27 +02:00
dependabot[bot]
8fd57459a1 Bump ssri from 6.0.1 to 6.0.2 (#6132)
Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-01 20:29:19 -04:00
Markoz Peña
9a3dc8ed5f [dependencies] Bump husky from v3 to v6 (#6157)
Co-authored-by: Steven <steven@ceriously.com>
2021-05-01 20:28:34 -04:00
dependabot[bot]
c46dd8c556 Bump ssri from 6.0.1 to 6.0.2 in /examples/angular (#6131)
Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-30 12:07:03 -04:00
JJ Kasper
e28edc5c93 Publish Canary
- @vercel/build-utils@2.10.3-canary.1
 - vercel@22.0.2-canary.1
 - @vercel/client@10.0.1-canary.1
 - @vercel/frameworks@0.3.3-canary.1
 - @vercel/routing-utils@1.11.1-canary.1
2021-04-29 11:03:01 -05:00
JJ Kasper
3fc4f64824 Replace regex lexer with minimal regex for named groups (#6171) 2021-04-29 11:01:39 -05:00
Steven
90e51287e9 [build-utils] Fix unit tests for Node.js version check (#6155)
Fixes unit tests since we throw now for Node.js 10 since April 20th.

https://vercel.com/changelog/node-js-10-is-being-deprecated
2021-04-26 18:54:56 +00:00
JJ Kasper
6d8b10802c Publish Canary
- @vercel/build-utils@2.10.3-canary.0
 - vercel@22.0.2-canary.0
 - @vercel/client@10.0.1-canary.0
 - @vercel/frameworks@0.3.3-canary.0
 - @vercel/routing-utils@1.11.1-canary.0
2021-04-23 14:48:09 -05:00
JJ Kasper
1dd2ffd895 Ensure segments from only has are replaced (#6142)
This ensures that when segments are coming from only `has` items we still replace them correctly as they currently don't get replaced if the `source` doesn't have any segments as well. 

### Related Issues

x-ref: https://vercel.slack.com/archives/CLDDX2Y0G/p1619061783470000

### 📋 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-04-23 18:20:49 +00:00
Steven
e4d422a2d7 Publish Stable
- vercel@22.0.1
2021-04-22 15:07:42 -04:00
Steven
bd55ad8212 Publish Canary
- vercel@22.0.1-canary.0
2021-04-22 14:50:59 -04:00
Nathan Rajlich
6591dae152 [cli] Update default auth config comment to say "Vercel" (#6140) 2021-04-22 14:40:11 -04:00
Steven
a490faa496 [cli] Fix SAML usage with --token (#6139) 2021-04-22 14:39:51 -04:00
Steven
8fe16f1b0a [cli] Fix vercel login command (#6138)
* Revert file symbol usage for login

* Add missing remove() to test
2021-04-22 13:35:07 -04:00
116 changed files with 2017 additions and 1831 deletions

1
.husky/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
_

4
.husky/pre-commit Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn pre-commit

332
LICENSE
View File

@@ -1,190 +1,202 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
1. Definitions.
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
1. Definitions.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
END OF TERMS AND CONDITIONS
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
Copyright 2017 Vercel, Inc.
END OF TERMS AND CONDITIONS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
APPENDIX: How to apply the Apache License to your work.
https://www.apache.org/licenses/LICENSE-2.0
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Copyright 2017 Vercel, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

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

View File

@@ -362,10 +362,10 @@ ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
node-fetch@2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
node-fetch@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
normalize-url@^4.1.0:
version "4.5.0"

View File

@@ -3073,9 +3073,9 @@ hmac-drbg@^1.0.1:
minimalistic-crypto-utils "^1.0.1"
hosted-git-info@^2.1.4, hosted-git-info@^2.6.0, hosted-git-info@^2.7.1:
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
hpack.js@^2.1.6:
version "2.1.6"
@@ -6204,9 +6204,9 @@ sshpk@^1.7.0:
tweetnacl "~0.14.0"
ssri@^6.0.0, ssri@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
version "6.0.2"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
dependencies:
figgy-pudding "^3.5.1"

View File

@@ -4681,9 +4681,9 @@ hmac-drbg@^1.0.1:
minimalistic-crypto-utils "^1.0.1"
hosted-git-info@^2.1.4:
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
hsl-regex@^1.0.0:
version "1.0.0"
@@ -8195,9 +8195,9 @@ sprintf-js@~1.0.2:
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
ssri@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
version "6.0.2"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
dependencies:
figgy-pudding "^3.5.1"

View File

@@ -1,19 +1,9 @@
# Realtime Pusher Whiteboard
# Realtime Pusher Example
This directory is a realtime serverless whiteboard powered by Pusher. The frontend is static vanilla HTML, CSS, and JavaScript and the backend is a single Node.js function.
This directory is a realtime serverless whiteboard powered by Pusher. The frontend is static vanilla HTML, CSS, and JavaScript and the backend is a single Node.js function both deployed with Vercel and zero configuration.
## Creating This Example
## Deploy Your Own
To get started with a realtime whiteboard on Vercel, you can use [Vercel CLI](https://vercel.com/download) to initialize the project:
Deploy your own Pusher project with Vercel.
```shell
$ vercel init vanilla-pusher-functions
```
### Deploying From Your Terminal
You can deploy your new project with a single command from your terminal using [Vercel CLI](https://vercel.com/download):
```shell
$ vercel
```
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fvercel%2Ftree%2Fmain%2Fexamples%2Fvanilla-pusher-functions&env=APP_ID,KEY,SECRET,CLUSTER&envDescription=Pusher%20Channels&envLink=https%3A%2F%2Fpusher.com%2Fchannels)

View File

@@ -1,9 +0,0 @@
{
"version": 2,
"env": {
"APP_ID": "@pusher-whiteboard-app-id",
"KEY": "@pusher-whiteboard-key",
"SECRET": "@pusher-whiteboard-secret",
"CLUSTER": "us2"
}
}

View File

@@ -2,7 +2,7 @@
"name": "vercel-monorepo",
"version": "0.0.0",
"private": true,
"license": "MIT",
"license": "Apache-2.0",
"workspaces": {
"packages": [
"packages/*"
@@ -24,7 +24,7 @@
"eslint": "6.2.2",
"eslint-config-prettier": "6.1.0",
"eslint-plugin-jest": "23.8.2",
"husky": "3.0.4",
"husky": "6.0.0",
"json5": "2.1.1",
"lint-staged": "9.2.5",
"node-fetch": "2.6.1",
@@ -40,11 +40,13 @@
"changelog": "node utils/changelog.js",
"build": "node utils/run.js build all",
"vercel-build": "mkdir -p public && echo '<a href=\"https://vercel.com/import\">Import</a>' > public/output.html",
"pre-commit": "lint-staged",
"test-unit": "node utils/run.js test-unit",
"test-integration-cli": "node utils/run.js test-integration-cli",
"test-integration-once": "node utils/run.js test-integration-once",
"test-integration-dev": "node utils/run.js test-integration-dev",
"lint": "eslint . --ext .ts,.js"
"lint": "eslint . --ext .ts,.js",
"prepare": "husky install"
},
"lint-staged": {
"./{*,{api,packages,test,utils}/**/*}.{js,ts}": [
@@ -57,11 +59,6 @@
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"prettier": {
"trailingComma": "es5",
"singleQuote": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "2.10.2",
"version": "2.11.1",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -29,7 +29,7 @@
"@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0",
"@types/yazl": "^2.4.1",
"@vercel/frameworks": "0.3.2",
"@vercel/frameworks": "0.4.1",
"@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1",
"async-retry": "1.2.3",

View File

@@ -1,9 +0,0 @@
const scheduler = require('@google-cloud/scheduler');
module.exports = (_, res) => {
if (scheduler) {
res.end('found:RANDOMNESS_PLACEHOLDER');
} else {
res.end('nope:RANDOMNESS_PLACEHOLDER');
}
};

View File

@@ -1,10 +0,0 @@
{
"version": 2,
"builds": [
{
"src": "index.js",
"use": "@vercel/node"
}
],
"probes": [{ "path": "/", "mustContain": "found:RANDOMNESS_PLACEHOLDER" }]
}

View File

@@ -1,8 +0,0 @@
{
"name": "15-yarn-ignore-engines",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"@google-cloud/scheduler": "0.3.0"
}
}

View File

@@ -97,10 +97,6 @@ it('should create zip files with symlinks properly', async () => {
});
it('should only match supported node versions, otherwise throw an error', async () => {
expect(await getSupportedNodeVersion('10.x', false)).toHaveProperty(
'major',
10
);
expect(await getSupportedNodeVersion('12.x', false)).toHaveProperty(
'major',
12
@@ -121,10 +117,6 @@ it('should only match supported node versions, otherwise throw an error', async
await expectBuilderError(getSupportedNodeVersion('foo', true), autoMessage);
await expectBuilderError(getSupportedNodeVersion('=> 10', true), autoMessage);
expect(await getSupportedNodeVersion('10.x', true)).toHaveProperty(
'major',
10
);
expect(await getSupportedNodeVersion('12.x', true)).toHaveProperty(
'major',
12
@@ -154,20 +146,20 @@ it('should only match supported node versions, otherwise throw an error', async
it('should match all semver ranges', async () => {
// See https://docs.npmjs.com/files/package.json#engines
expect(await getSupportedNodeVersion('10.0.0')).toHaveProperty('major', 10);
expect(await getSupportedNodeVersion('10.x')).toHaveProperty('major', 10);
expect(await getSupportedNodeVersion('12.0.0')).toHaveProperty('major', 12);
expect(await getSupportedNodeVersion('12.x')).toHaveProperty('major', 12);
expect(await getSupportedNodeVersion('>=10')).toHaveProperty('major', 14);
expect(await getSupportedNodeVersion('>=10.3.0')).toHaveProperty('major', 14);
expect(await getSupportedNodeVersion('8.5.0 - 10.5.0')).toHaveProperty(
expect(await getSupportedNodeVersion('11.5.0 - 12.5.0')).toHaveProperty(
'major',
10
12
);
expect(await getSupportedNodeVersion('>=9.5.0 <=10.5.0')).toHaveProperty(
expect(await getSupportedNodeVersion('>=9.5.0 <=12.5.0')).toHaveProperty(
'major',
10
12
);
expect(await getSupportedNodeVersion('~10.5.0')).toHaveProperty('major', 10);
expect(await getSupportedNodeVersion('^10.5.0')).toHaveProperty('major', 10);
expect(await getSupportedNodeVersion('~12.5.0')).toHaveProperty('major', 12);
expect(await getSupportedNodeVersion('^12.5.0')).toHaveProperty('major', 12);
});
it('should ignore node version in vercel dev getNodeVersion()', async () => {

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 +0,0 @@
declare module 'inquirer';

View File

@@ -0,0 +1,5 @@
declare module 'jsonlines' {
import { Transform } from 'stream';
function parse(): Transform;
}

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "22.0.0",
"version": "23.0.1",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -17,8 +17,8 @@
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
"prepublishOnly": "yarn build",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"build": "ts-node ./scripts/build.ts",
"build-dev": "ts-node ./scripts/build.ts --dev"
"build": "node -r ts-eager/register ./scripts/build.ts",
"build-dev": "node -r ts-eager/register ./scripts/build.ts --dev"
},
"nyc": {
"include": [
@@ -61,10 +61,10 @@
"node": ">= 12"
},
"dependencies": {
"@vercel/build-utils": "2.10.2",
"@vercel/build-utils": "2.11.1",
"@vercel/go": "1.2.2",
"@vercel/node": "1.10.0",
"@vercel/python": "2.0.1",
"@vercel/node": "1.11.1",
"@vercel/python": "2.0.4",
"@vercel/ruby": "1.2.6",
"update-notifier": "4.1.0"
},
@@ -82,13 +82,14 @@
"@types/fs-extra": "5.0.5",
"@types/glob": "7.1.1",
"@types/http-proxy": "1.16.2",
"@types/inquirer": "7.3.1",
"@types/load-json-file": "2.0.7",
"@types/mime-types": "2.1.0",
"@types/minimatch": "3.0.3",
"@types/mri": "1.1.0",
"@types/ms": "0.7.30",
"@types/node": "11.11.0",
"@types/node-fetch": "2.1.4",
"@types/node-fetch": "2.5.10",
"@types/npm-package-arg": "6.1.0",
"@types/pluralize": "0.0.29",
"@types/progress": "2.0.3",
@@ -99,7 +100,7 @@
"@types/universal-analytics": "0.4.2",
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@vercel/frameworks": "0.3.2",
"@vercel/frameworks": "0.4.1",
"@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2",
@@ -116,7 +117,7 @@
"chalk": "4.1.0",
"chokidar": "3.3.1",
"clipboardy": "2.1.0",
"codecov": "3.7.1",
"codecov": "3.8.2",
"cpy": "7.2.0",
"credit-card": "3.0.1",
"date-fns": "1.29.0",
@@ -147,7 +148,7 @@
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"nyc": "13.2.0",
"open": "8.0.2",
"open": "8.2.0",
"ora": "3.4.0",
"pcre-to-regexp": "1.0.0",
"pluralize": "7.0.0",
@@ -168,6 +169,7 @@
"title": "3.4.1",
"tmp-promise": "1.0.3",
"tree-kill": "1.2.2",
"ts-eager": "2.0.2",
"ts-node": "8.3.0",
"typescript": "3.9.3",
"universal-analytics": "0.4.20",

View File

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

View File

@@ -1,21 +1,26 @@
import chalk from 'chalk';
import ms from 'ms';
import table from 'text-table';
import Now from '../../util';
import Client from '../../util/client';
import getAliases from '../../util/alias/get-aliases';
import getScope from '../../util/get-scope.ts';
import stamp from '../../util/output/stamp.ts';
import strlen from '../../util/strlen.ts';
import getScope from '../../util/get-scope';
import stamp from '../../util/output/stamp';
import strlen from '../../util/strlen';
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) {
const {
apiUrl,
authConfig: { token },
output,
config: { currentTeam },
} = client;
import { Alias } from '../../types';
interface Options {
'--next'?: number;
}
export default async function ls(
client: Client,
opts: Options,
args: string[]
) {
const { output } = client;
const { '--next': nextTimestamp } = opts;
let contextName = null;
@@ -36,13 +41,6 @@ export default async function ls(client, opts, args) {
return 1;
}
const now = new Now({
apiUrl,
token,
debug: client.output.isDebugEnabled(),
currentTeam,
output,
});
const lsStamp = stamp();
if (args.length > 0) {
@@ -56,8 +54,9 @@ export default async function ls(client, opts, args) {
output.spinner(`Fetching aliases under ${chalk.bold(contextName)}`);
// Get the list of alias
const { aliases, pagination } = await getAliases(
now,
client,
undefined,
nextTimestamp
);
@@ -73,21 +72,20 @@ export default async function ls(client, opts, args) {
);
}
now.close();
return 0;
}
function printAliasTable(aliases) {
function printAliasTable(aliases: Alias[]) {
return `${table(
[
['source', 'url', 'age'].map(h => chalk.gray(h)),
['source', 'url', 'age'].map(header => chalk.gray(header)),
...aliases.map(a => [
// for legacy reasons, we might have situations
// where the deployment was deleted and the alias
// not collected appropriately, and we need to handle it
a.deployment && a.deployment.url ? a.deployment.url : chalk.gray(''),
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 ms from 'ms';
import table from 'text-table';
import Now from '../../util';
import getScope from '../../util/get-scope.ts';
import Client from '../../util/client';
import getScope from '../../util/get-scope';
import removeAliasById from '../../util/alias/remove-alias-by-id';
import stamp from '../../util/output/stamp.ts';
import strlen from '../../util/strlen.ts';
import stamp from '../../util/output/stamp';
import strlen from '../../util/strlen';
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 { getCommandName } from '../../util/pkg-name.ts';
export default async function rm(client, opts, args) {
const {
apiUrl,
authConfig: { token },
output,
config: { currentTeam },
} = client;
import { Alias } from '../../types';
import { Output } from '../../util/output';
import { isValidName } from '../../util/is-valid-name';
import { getCommandName } from '../../util/pkg-name';
type Options = {
'--yes': boolean;
};
export default async function rm(
client: Client,
opts: Partial<Options>,
args: string[]
) {
const { output } = client;
let contextName = null;
@@ -32,13 +38,6 @@ export default async function rm(client, opts, args) {
throw err;
}
const now = new Now({
apiUrl,
token,
debug: client.output.isDebugEnabled(),
currentTeam,
output,
});
const [aliasOrId] = args;
if (args.length !== 1) {
@@ -61,7 +60,8 @@ export default async function rm(client, opts, args) {
return 1;
}
const alias = await findAliasByAliasOrId(output, now, aliasOrId);
const alias = await findAliasByAliasOrId(output, client, aliasOrId);
if (!alias) {
output.error(
`Alias not found by "${aliasOrId}" under ${chalk.bold(contextName)}`
@@ -76,7 +76,7 @@ export default async function rm(client, opts, args) {
return 0;
}
await removeAliasById(now, alias.uid);
await removeAliasById(client, alias.uid);
console.log(
`${chalk.cyan('> Success!')} Alias ${chalk.bold(
alias.alias
@@ -85,7 +85,7 @@ export default async function rm(client, opts, args) {
return 0;
}
async function confirmAliasRemove(output, alias) {
async function confirmAliasRemove(output: Output, alias: Alias) {
const srcUrl = alias.deployment
? chalk.underline(alias.deployment.url)
: null;
@@ -94,7 +94,7 @@ async function confirmAliasRemove(output, alias) {
[
...(srcUrl ? [srcUrl] : []),
chalk.underline(alias.alias),
chalk.gray(`${ms(new Date() - new Date(alias.created))} ago`),
chalk.gray(`${ms(Date.now() - alias.createdAt)} ago`),
],
],
{

View File

@@ -19,7 +19,7 @@ import link from '../../util/output/link';
import { User } from '../../types';
import { getCommandName } from '../../util/pkg-name';
import toHost from '../../util/to-host';
import { NowConfig } from '../../util/dev/types';
import { VercelConfig } from '../../util/dev/types';
type Options = {
'--debug': boolean;
@@ -28,7 +28,7 @@ type Options = {
export default async function set(
client: Client,
opts: Options,
opts: Partial<Options>,
args: string[]
) {
const { output, localConfig } = client;
@@ -421,7 +421,7 @@ function handleCreateAliasError<T>(
return error;
}
function getTargetsForAlias(args: string[], { alias }: NowConfig) {
function getTargetsForAlias(args: string[], { alias }: VercelConfig) {
if (args.length) {
return [args[args.length - 1]]
.map(target => (target.indexOf('.') !== -1 ? toHost(target) : target))

View File

@@ -111,7 +111,6 @@ async function chooseFromDropdown(message: string, exampleList: string[]) {
return listInput({
message,
separator: false,
choices,
});
}

View File

@@ -3,12 +3,14 @@ import getArgs from '../util/get-args';
import buildsList from '../util/output/builds';
import routesList from '../util/output/routes';
import indent from '../util/output/indent';
import Now from '../util';
import logo from '../util/output/logo';
import elapsed from '../util/output/elapsed.ts';
import elapsed from '../util/output/elapsed';
import { handleError } from '../util/error';
import getScope from '../util/get-scope.ts';
import { getPkgName, getCommandName } from '../util/pkg-name.ts';
import getScope from '../util/get-scope';
import { getPkgName, getCommandName } from '../util/pkg-name';
import Client from '../util/client';
import { getDeployment } from '../util/get-deployment';
import { Deployment } from '@vercel/client';
const help = () => {
console.log(`
@@ -33,15 +35,15 @@ const help = () => {
${chalk.gray('')} Get information about a deployment by its unique URL
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment-ji2fjij2.now.sh`)}
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment-ji2fjij2.vercel.app`)}
${chalk.gray('-')} Get information about the deployment an alias points to
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment.now.sh`)}
${chalk.cyan(`$ ${getPkgName()} inspect my-deployment.vercel.app`)}
`);
};
export default async function main(client) {
export default async function main(client: Client) {
let deployment;
let argv;
@@ -57,14 +59,7 @@ export default async function main(client) {
return 2;
}
const {
apiUrl,
output,
authConfig: { token },
config,
} = client;
const debugEnabled = argv['--debug'];
const { print, log, error } = output;
const { print, log, error } = client.output;
// extract the first parameter
const [, deploymentIdOrHost] = argv._;
@@ -75,9 +70,7 @@ export default async function main(client) {
return 1;
}
const { currentTeam } = config;
let contextName = null;
let contextName: string | null = null;
try {
({ contextName } = await getScope(client));
@@ -90,22 +83,14 @@ export default async function main(client) {
throw err;
}
const now = new Now({
apiUrl,
token,
debug: debugEnabled,
currentTeam,
output,
});
// resolve the deployment, since we might have been given an alias
const depFetchStart = Date.now();
output.spinner(
client.output.spinner(
`Fetching deployment "${deploymentIdOrHost}" in ${chalk.bold(contextName)}`
);
try {
deployment = await now.findDeployment(deploymentIdOrHost);
deployment = await getDeployment(client, deploymentIdOrHost);
} catch (err) {
if (err.status === 404) {
error(
@@ -127,11 +112,11 @@ export default async function main(client) {
throw err;
}
const { id, name, url, created, routes, readyState } = deployment;
const { id, name, url, createdAt, routes, readyState } = deployment;
const { builds } =
deployment.version === 2
? await now.fetch(`/v1/now/deployments/${id}/builds`)
? await client.fetch(`/v1/now/deployments/${id}/builds`)
: { builds: [] };
log(
@@ -146,10 +131,10 @@ export default async function main(client) {
print(` ${chalk.cyan('name')}\t${name}\n`);
print(` ${chalk.cyan('readyState')}\t${stateString(readyState)}\n`);
print(` ${chalk.cyan('url')}\t\t${url}\n`);
if (created) {
if (createdAt) {
print(
` ${chalk.cyan('createdAt')}\t${new Date(created)} ${elapsed(
Date.now() - created,
` ${chalk.cyan('createdAt')}\t${new Date(createdAt)} ${elapsed(
Date.now() - createdAt,
true
)}\n`
);
@@ -157,7 +142,7 @@ export default async function main(client) {
print('\n\n');
if (builds.length > 0) {
const times = {};
const times: { [id: string]: string | null } = {};
for (const build of builds) {
const { id, createdAt, readyStateAt } = build;
@@ -179,7 +164,7 @@ export default async function main(client) {
}
// renders the state string
function stateString(s) {
function stateString(s: Deployment['readyState']) {
switch (s) {
case 'INITIALIZING':
return chalk.yellow(s);

View File

@@ -1,11 +1,10 @@
import inquirer from 'inquirer';
import { validate as validateEmail } from 'email-validator';
import chalk from 'chalk';
import hp from '../util/humanize-path';
import getArgs from '../util/get-args';
import error from '../util/output/error';
import handleError from '../util/handle-error';
import logo from '../util/output/logo';
import prompt from '../util/login/prompt';
import doSsoLogin from '../util/login/sso';
import doEmailLogin from '../util/login/email';
import { prependEmoji, emoji } from '../util/emoji';
@@ -44,38 +43,9 @@ const help = () => {
`);
};
const readInput = async () => {
let input;
while (!input) {
try {
const { val } = await inquirer.prompt({
type: 'input',
name: 'val',
message: 'Enter your email or team slug:',
});
input = val;
} catch (err) {
console.log(); // \n
if (err.isTtyError) {
throw new Error(
error(
`Interactive mode not supported please run ${getCommandName(
`login you@domain.com`
)}`
)
);
}
}
}
return input;
};
export default async function login(client: Client): Promise<number> {
let argv;
const { apiUrl, output } = client;
const { output } = client;
try {
argv = getArgs(client.argv.slice(2));
@@ -94,21 +64,20 @@ export default async function login(client: Client): Promise<number> {
return 2;
}
const input = argv._[1] || (await readInput());
// TODO: add proper validation
const isValidSlug = true;
const input = argv._[1];
let result: number | string = 1;
if (validateEmail(input)) {
result = await doEmailLogin(input, { output, apiUrl });
} else if (isValidSlug) {
result = await doSsoLogin(input, { output, apiUrl });
if (input) {
// Email or Team slug was provided via command line
if (validateEmail(input)) {
result = await doEmailLogin(client, input);
} else {
result = await doSsoLogin(client, input);
}
} else {
output.error(`Invalid input: "${input}"`);
output.log(`Please enter a valid email address or team slug`);
return 2;
// Interactive mode
result = await prompt(client);
}
// The login function failed, so it returned an exit code

View File

@@ -1,7 +1,5 @@
import chalk from 'chalk';
import fetch from 'node-fetch';
import logo from '../util/output/logo';
// @ts-ignore
import { handleError } from '../util/error';
import { writeToConfigFile, writeToAuthConfigFile } from '../util/config/files';
import getArgs from '../util/get-args';
@@ -48,10 +46,9 @@ export default async function main(client: Client): Promise<number> {
return 2;
}
const { authConfig, config, apiUrl, output } = client;
const { token } = authConfig;
const { authConfig, config, output } = client;
if (!token) {
if (!authConfig.token) {
output.note(
`Not currently logged in, so ${getCommandName('logout')} did nothing`
);
@@ -59,6 +56,21 @@ export default async function main(client: Client): Promise<number> {
}
output.spinner('Logging out…', 200);
let exitCode = 0;
try {
await client.fetch(`/v3/user/tokens/current`, {
method: 'DELETE',
useCurrentTeam: false,
});
} catch (err) {
if (err.status === 403) {
output.debug('Token is invalid so it cannot be revoked');
} else if (err.status !== 200) {
output.debug(err?.message ?? '');
exitCode = 1;
}
}
delete config.currentTeam;
@@ -75,26 +87,15 @@ export default async function main(client: Client): Promise<number> {
writeToAuthConfigFile(authConfig);
output.debug('Configuration has been deleted');
} catch (err) {
output.error(`Couldn't remove config while logging out`);
return 1;
output.debug(err?.message ?? '');
exitCode = 1;
}
const res = await fetch(`${apiUrl}/v3/user/tokens/current`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${token}`,
},
});
if (res.status === 403) {
output.debug('Token is invalid so it cannot be revoked');
} else if (res.status !== 200) {
const err = await res.json();
output.error('Failed to revoke token');
output.debug(err ? err.message : '');
return 1;
if (exitCode === 0) {
output.log('Logged out!');
} else {
output.error(`Failed during logout`);
}
output.log('Logged out!');
return 0;
return exitCode;
}

View File

@@ -1,13 +1,13 @@
import chalk from 'chalk';
import Now from '../util';
import logo from '../util/output/logo';
import elapsed from '../util/output/elapsed.ts';
import elapsed from '../util/output/elapsed';
import { maybeURL, normalizeURL } from '../util/url';
import printEvents from '../util/events';
import getScope from '../util/get-scope.ts';
import { getPkgName } from '../util/pkg-name.ts';
import getArgs from '../util/get-args.ts';
import handleError from '../util/handle-error.ts';
import printEvents, { DeploymentEvent } from '../util/events';
import getScope from '../util/get-scope';
import { getPkgName } from '../util/pkg-name';
import getArgs from '../util/get-args';
import Client from '../util/client';
import { getDeployment } from '../util/get-deployment';
const help = () => {
console.log(`
@@ -53,35 +53,25 @@ const help = () => {
`);
};
export default async function main(client) {
let argv;
let deploymentIdOrURL;
let debug;
export default async function main(client: Client) {
let head;
let limit;
let follow;
let outputMode;
let since;
let until;
let deploymentIdOrURL;
try {
argv = getArgs(client.argv.slice(2), {
'--since': String,
'--until': String,
'--output': String,
'--limit': Number,
'--head': Boolean,
'--follow': Boolean,
'-f': '--follow',
'-o': '--output',
'-n': '--limit',
});
} catch (error) {
handleError(error);
return 1;
}
const argv = getArgs(client.argv.slice(2), {
'--since': String,
'--until': String,
'--output': String,
'--limit': Number,
'--head': Boolean,
'--follow': Boolean,
'-f': '--follow',
'-o': '--output',
'-n': '--limit',
});
argv._ = argv._.slice(1);
deploymentIdOrURL = argv._[0];
@@ -91,12 +81,7 @@ export default async function main(client) {
return 2;
}
const {
authConfig: { token },
apiUrl,
output,
config,
} = client;
const { output } = client;
try {
since = argv['--since'] ? toTimestamp(argv['--since']) : 0;
@@ -124,40 +109,24 @@ export default async function main(client) {
deploymentIdOrURL = normalizedURL;
}
debug = argv['--debug'];
head = argv['--head'];
limit = argv['--limit'] || 100;
follow = argv['--follow'];
if (follow) until = 0;
outputMode = argv['--output'] in logPrinters ? argv['--output'] : 'short';
const logPrinter = getLogPrinter(argv['--output'], 'short');
const { currentTeam } = config;
const now = new Now({ apiUrl, token, debug, currentTeam, output });
let contextName = null;
const { contextName } = await getScope(client);
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
let deployment;
const id = deploymentIdOrURL;
const depFetchStart = Date.now();
output.spinner(`Fetching deployment "${id}" in ${chalk.bold(contextName)}`);
let deployment;
try {
deployment = await now.findDeployment(id);
deployment = await getDeployment(client, id);
} catch (err) {
output.stopSpinner();
now.close();
if (err.status === 404) {
output.error(
@@ -183,31 +152,28 @@ export default async function main(client) {
)} ${elapsed(Date.now() - depFetchStart)}`
);
let direction = head ? 'forward' : 'backward';
if (since && !until) direction = 'forward';
const findOpts1 = {
direction,
limit,
since,
until,
}; // no follow
const storage = [];
const storeEvent = event => storage.push(event);
const storage: DeploymentEvent[] = [];
await printEvents(now, deployment.uid || deployment.id, currentTeam, {
let direction = head ? ('forward' as const) : ('backward' as const);
if (since && !until) direction = 'forward';
await printEvents(client, deployment.id, {
mode: 'logs',
onEvent: storeEvent,
onEvent: event => storage.push(event),
quiet: false,
debug,
findOpts: findOpts1,
output,
findOpts: {
direction,
limit,
since,
until,
},
});
const printedEventIds = new Set();
const printEvent = event => {
const printedEventIds = new Set<string>();
const printEvent = (event: DeploymentEvent) => {
if (printedEventIds.has(event.id)) return 0;
printedEventIds.add(event.id);
return logPrinters[outputMode](event);
return logPrinter(event);
};
storage.sort(compareEvents).forEach(printEvent);
@@ -216,26 +182,22 @@ export default async function main(client) {
// NOTE: the API ignores `since` on follow mode.
// (but not sure if it's always true on legacy deployments)
const since2 = lastEvent ? lastEvent.date : Date.now();
const findOpts2 = {
direction: 'forward',
since: since2,
follow: true,
};
await printEvents(now, deployment.uid || deployment.id, currentTeam, {
await printEvents(client, deployment.id, {
mode: 'logs',
onEvent: printEvent,
quiet: false,
debug,
findOpts: findOpts2,
output,
findOpts: {
direction: 'forward',
since: since2,
follow: true,
},
});
}
now.close();
return 0;
}
function compareEvents(d1, d2) {
function compareEvents(d1: DeploymentEvent, d2: DeploymentEvent) {
const c1 = d1.date || d1.created;
const c2 = d2.date || d2.created;
if (c1 !== c2) return c1 - c2;
@@ -246,10 +208,10 @@ function compareEvents(d1, d2) {
return d1.created - d2.created; // if date are equal and no serial
}
function printLogShort(log) {
function printLogShort(log: any) {
if (!log.created) return; // keepalive
let data;
let data: string;
const obj = log.object;
if (log.type === 'request') {
data =
@@ -315,7 +277,7 @@ function printLogShort(log) {
return 0;
}
function printLogRaw(log) {
function printLogRaw(log: any) {
if (!log.created) return; // keepalive
if (log.object) {
@@ -340,7 +302,27 @@ const logPrinters = {
raw: printLogRaw,
};
function toTimestamp(datestr) {
type OutputMode = keyof typeof logPrinters;
const isLogPrinter = (v: any): v is OutputMode => {
return v && v in logPrinters;
};
const getLogPrinter = (mode: string | undefined, def: OutputMode) => {
if (mode) {
if (isLogPrinter(mode)) {
return logPrinters[mode];
}
throw new TypeError(
`Invalid output mode "${mode}". Must be one of: ${Object.keys(
logPrinters
).join(', ')}`
);
}
return logPrinters[def];
};
function toTimestamp(datestr: string) {
const t = Date.parse(datestr);
if (isNaN(t)) {
throw new TypeError('Invalid date string');

View File

@@ -187,25 +187,22 @@ async function run({ client, contextName }) {
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);
if (!yes) {
console.error(error('User abort'));
return exit(0);
}
await client.fetch(`/v2/projects/${name}`, {
method: 'DELETE',
});
try {
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);
console.log(
`${chalk.cyan('> Success!')} Project ${chalk.bold(

View File

@@ -115,8 +115,6 @@ export default async function main(client) {
try {
({ contextName } = await getScope(client));
} catch (err) {
client.close();
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
@@ -210,7 +208,6 @@ export default async function main(client) {
.map(id => chalk.bold(`"${id}"`))
.join(', ')}. Run ${getCommandName('ls')} to list.`
);
client.close();
return 1;
}
@@ -233,7 +230,6 @@ export default async function main(client) {
if (confirmation !== 'y' && confirmation !== 'yes') {
output.log('Aborted');
client.close();
return 1;
}
}
@@ -265,7 +261,6 @@ export default async function main(client) {
console.log(`${chalk.gray('-')} ${chalk.bold(project.name)}`);
});
client.close();
return 0;
}

View File

@@ -1,14 +1,15 @@
import chalk from 'chalk';
import error from '../util/output/error';
import NowTeams from '../util/teams';
import logo from '../util/output/logo';
import list from './teams/list';
import add from './teams/add';
import change from './teams/switch';
import invite from './teams/invite';
import { getPkgName } from '../util/pkg-name.ts';
import getArgs from '../util/get-args.ts';
import handleError from '../util/handle-error.ts';
import error from '../../util/output/error';
import NowTeams from '../../util/teams';
import logo from '../../util/output/logo';
import list from './list';
import add from './add';
import change from './switch';
import invite from './invite';
import { getPkgName } from '../../util/pkg-name';
import getArgs from '../../util/get-args';
import handleError from '../../util/handle-error';
import Client from '../../util/client';
const help = () => {
console.log(`
@@ -65,7 +66,7 @@ let debug;
let apiUrl;
let subcommand;
const main = async client => {
export default async (client: Client) => {
try {
argv = getArgs(client.argv.slice(2), {
'--since': String,
@@ -113,7 +114,7 @@ const main = async client => {
}
case 'switch':
case 'change': {
exitCode = await change(client, argv);
exitCode = await change(client, argv._[0]);
break;
}
case 'add':
@@ -139,13 +140,3 @@ const main = async client => {
teams.close();
return exitCode || 0;
};
export default async client => {
try {
return await main(client);
} catch (err) {
console.error(err);
handleError(err);
return 1;
}
};

View File

@@ -1,190 +0,0 @@
// Packages
import chalk from 'chalk';
// Utilities
import listInput from '../../util/input/list';
import success from '../../util/output/success';
import info from '../../util/output/info';
import error from '../../util/output/error';
import param from '../../util/output/param.ts';
import { writeToConfigFile } from '../../util/config/files';
import getUser from '../../util/get-user.ts';
import NowTeams from '../../util/teams';
const updateCurrentTeam = (config, newTeam) => {
if (newTeam) {
config.currentTeam = newTeam.id;
} else {
delete config.currentTeam;
}
writeToConfigFile(config);
};
export default async function change(client, argv) {
const {
apiUrl,
authConfig: { token },
debug,
config,
output,
} = client;
output.spinner('Fetching teams');
// We're loading the teams here without `currentTeam`, so that
// people can use `vercel switch` in the case that their
// current team was deleted.
const teams = new NowTeams({ apiUrl, token, debug, output });
const list = (await teams.ls()).teams;
let { currentTeam } = config;
const accountIsCurrent = !currentTeam;
output.spinner('Fetching user information');
let user;
try {
user = await getUser(client);
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
if (accountIsCurrent) {
currentTeam = {
slug: user.username || user.email,
};
} else {
currentTeam = list.find(team => team.id === currentTeam);
if (!currentTeam) {
output.error(`You are not a part of the current team anymore`);
return 1;
}
}
if (argv._.length !== 0) {
const desiredSlug = argv._[0];
const newTeam = list.find(team => team.slug === desiredSlug);
if (newTeam) {
updateCurrentTeam(config, newTeam);
console.log(
success(
`The team ${chalk.bold(newTeam.name)} (${
newTeam.slug
}) is now active!`
)
);
return 0;
}
if (desiredSlug === user.username) {
output.spinner('Saving');
updateCurrentTeam(config);
output.stopSpinner();
console.log(
success(`Your account (${chalk.bold(desiredSlug)}) is now active!`)
);
return 0;
}
console.error(
error(`Could not find membership for team ${param(desiredSlug)}`)
);
return 1;
}
const choices = list.map(({ id, slug, name }) => {
name = `${slug} (${name})`;
if (id === currentTeam.id) {
name += ` ${chalk.bold('(current)')}`;
}
return {
name,
value: slug,
short: slug,
};
});
const suffix = accountIsCurrent ? ` ${chalk.bold('(current)')}` : '';
const userEntryName = user.username
? `${user.username} (${user.email})${suffix}`
: user.email;
choices.unshift({
name: userEntryName,
value: user.email,
short: user.username,
});
// Let's bring the current team to the beginning of the list
if (!accountIsCurrent) {
const index = choices.findIndex(
choice => choice.value === currentTeam.slug
);
const choice = choices.splice(index, 1)[0];
choices.unshift(choice);
}
output.stopSpinner();
let message;
if (currentTeam) {
message = `Switch to:`;
}
const choice = await listInput({
message,
choices,
separator: false,
eraseFinalAnswer: true,
});
// Abort
if (!choice) {
console.log(info('No changes made'));
return 0;
}
const newTeam = list.find(item => item.slug === choice);
// Switch to account
if (!newTeam) {
if (currentTeam.slug === user.username || currentTeam.slug === user.email) {
console.log(info('No changes made'));
return 0;
}
output.spinner('Saving');
updateCurrentTeam(config);
output.stopSpinner();
console.log(success(`Your account (${chalk.bold(choice)}) is now active!`));
return 0;
}
if (newTeam.slug === currentTeam.slug) {
console.log(info('No changes made'));
return 0;
}
output.spinner('Saving');
updateCurrentTeam(config, newTeam);
output.stopSpinner();
console.log(
success(
`The team ${chalk.bold(newTeam.name)} (${newTeam.slug}) is now active!`
)
);
return 0;
}

View File

@@ -0,0 +1,151 @@
// Packages
import chalk from 'chalk';
// Utilities
import Client from '../../util/client';
import { emoji } from '../../util/emoji';
import getUser from '../../util/get-user';
import getTeams from '../../util/get-teams';
import listInput from '../../util/input/list';
import { Team, GlobalConfig } from '../../types';
import { writeToConfigFile } from '../../util/config/files';
const updateCurrentTeam = (config: GlobalConfig, team?: Team) => {
if (team) {
config.currentTeam = team.id;
} else {
delete config.currentTeam;
}
writeToConfigFile(config);
};
export default async function main(client: Client, desiredSlug?: string) {
const { config, output } = client;
const personalScopeSelected = !config.currentTeam;
output.spinner('Fetching teams information');
const [user, teams] = await Promise.all([getUser(client), getTeams(client)]);
const currentTeam = personalScopeSelected
? undefined
: teams.find(team => team.id === config.currentTeam);
if (!personalScopeSelected && !currentTeam) {
output.error(`You are not a member of the current team anymore.`);
return 1;
}
if (!desiredSlug) {
const teamChoices = teams
.slice(0)
.sort((a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
})
.map(team => {
let title = `${team.name} (${team.slug})`;
const selected = team.id === currentTeam?.id;
if (selected) {
title += ` ${chalk.bold('(current)')}`;
}
if (team.limited) {
title += ` ${emoji('locked')}`;
}
return {
name: title,
value: team.slug,
short: team.slug,
selected,
};
});
// Add the User scope entry at the top
let suffix = personalScopeSelected ? ` ${chalk.bold('(current)')}` : '';
// SAML tokens can not interact with the user scope
if (user.limited) {
suffix += ` ${emoji('locked')}`;
}
const choices = [
{ separator: 'Personal Account' },
{
name: `${user.name || user.email} (${user.username})${suffix}`,
value: user.username,
short: user.username,
selected: personalScopeSelected,
},
{ separator: 'Teams' },
...teamChoices,
];
output.stopSpinner();
desiredSlug = await listInput({
message: 'Switch to:',
choices,
eraseFinalAnswer: true,
});
}
// Abort
if (!desiredSlug) {
output.log('No changes made.');
return 0;
}
if (desiredSlug === user.username || desiredSlug === user.email) {
// Switch to user's personal account
if (personalScopeSelected) {
output.log('No changes made');
return 0;
}
if (user.limited) {
await client.reauthenticate({
scope: user.username,
teamId: null,
});
}
updateCurrentTeam(config);
output.success(
`Your account (${chalk.bold(user.username)}) is now active!`
);
return 0;
}
// Switch to selected team
const newTeam = teams.find(team => team.slug === desiredSlug);
if (!newTeam) {
output.error(
`You do not have permission to access scope ${chalk.bold(desiredSlug)}.`
);
return 1;
}
if (newTeam.slug === currentTeam?.slug) {
output.log('No changes made');
return 0;
}
if (newTeam.limited) {
const samlEnabled = newTeam.saml?.connection?.state === 'active';
await client.reauthenticate({
teamId: samlEnabled ? newTeam.id : null,
scope: newTeam.slug,
enforced: samlEnabled && newTeam.saml?.enforced === true,
});
}
updateCurrentTeam(config, newTeam);
output.success(
`The team ${chalk.bold(newTeam.name)} (${newTeam.slug}) is now active!`
);
return 0;
}

View File

@@ -484,7 +484,7 @@ const main = async () => {
return 1;
}
client.authConfig = { token };
client.authConfig = { token, skipWrite: true };
// Don't use team from config if `--token` was set
if (client.config && client.config.currentTeam) {
@@ -587,10 +587,10 @@ const main = async () => {
const eventCategory = 'Exit Code';
try {
const start = new Date();
const start = Date.now();
const full = require(`./commands/${targetCommand}`).default;
exitCode = await full(client);
const end = new Date() - start;
const end = Date.now() - start;
if (shouldCollectMetrics) {
const category = 'Command Invocation';

View File

@@ -1,12 +1,26 @@
import { fileNameSymbol } from '@vercel/client';
export type Primitive =
| bigint
| boolean
| null
| number
| string
| symbol
| undefined;
export type JSONArray = JSONValue[];
export type JSONValue = Primitive | JSONObject | JSONArray;
export interface JSONObject {
[key: string]: JSONValue;
}
export interface AuthConfig {
[fileNameSymbol]?: string;
token: string;
token?: string;
skipWrite?: boolean;
}
export interface GlobalConfig {
[fileNameSymbol]?: string;
currentTeam?: string;
collectMetrics?: boolean;
api?: string;
@@ -48,18 +62,26 @@ export type User = {
updatedAt: number;
};
name?: string;
limited?: boolean;
};
export type Team = {
export interface Team {
id: string;
avatar?: string;
avatar?: string | null;
billing: Billing;
created: string;
creatorId: string;
membership: { uid: string; role: 'MEMBER' | 'OWNER'; created: number };
name: string;
slug: string;
};
limited?: boolean;
saml?: {
enforced: boolean;
connection?: {
state: string;
};
};
}
export type Domain = {
id: string;
@@ -123,7 +145,7 @@ export type Deployment = {
export type Alias = {
uid: string;
alias: string;
created: string;
createdAt: number;
deployment: {
id: string;
url: string;
@@ -271,3 +293,13 @@ export type ProjectLinkResult =
| { status: 'linked'; org: Org; project: Project }
| { status: 'not_linked'; org: null; project: null }
| { status: 'error'; exitCode: number };
export interface Token {
id: string;
name: string;
type: string;
origin?: string;
activeAt: number;
createdAt: number;
teamId?: string;
}

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ import chalk from 'chalk';
import Client from '../client';
import { Output } from '../output';
import { User } from '../../types';
import { NowConfig } from '../dev/types';
import { VercelConfig } from '../dev/types';
import getDeploymentsByAppName from '../deploy/get-deployments-by-appname';
import getDeploymentByIdOrHost from '../deploy/get-deployment-by-id-or-host';
@@ -35,7 +35,7 @@ export async function getDeploymentForAlias(
localConfigPath: string | undefined,
user: User,
contextName: string,
localConfig: NowConfig
localConfig: VercelConfig
) {
output.spinner(`Fetching deployment to alias in ${chalk.bold(contextName)}`);

View File

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

View File

@@ -1,22 +1,28 @@
import { URLSearchParams } from 'url';
import { EventEmitter } from 'events';
import { URLSearchParams } from 'url';
import { parse as parseUrl } from 'url';
import fetch, { RequestInit } from 'node-fetch';
import { VercelConfig } from '@vercel/client';
import retry, { RetryFunction, Options as RetryOptions } from 'async-retry';
import fetch, { BodyInit, Headers, RequestInit, Response } from 'node-fetch';
import ua from './ua';
import { Output } from './output/create-output';
import responseError from './response-error';
import ua from './ua';
import printIndications from './print-indications';
import { AuthConfig, GlobalConfig } from '../types';
import { NowConfig } from './dev/types';
import doSsoLogin from './login/sso';
import reauthenticate from './login/reauthenticate';
import { SAMLError } from './login/types';
import { writeToAuthConfigFile } from './config/files';
import { AuthConfig, GlobalConfig, JSONObject } from '../types';
import { sharedPromise } from './promise';
import { APIError } from './errors-ts';
import { bold } from 'chalk';
export interface FetchOptions {
body?: NodeJS.ReadableStream | object | string;
headers?: { [key: string]: string };
const isSAMLError = (v: any): v is SAMLError => {
return v && v.saml;
};
export interface FetchOptions extends Omit<RequestInit, 'body'> {
body?: BodyInit | JSONObject;
json?: boolean;
method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
retry?: RetryOptions;
useCurrentTeam?: boolean;
accountId?: string;
@@ -28,16 +34,20 @@ export interface ClientOptions {
authConfig: AuthConfig;
output: Output;
config: GlobalConfig;
localConfig: NowConfig;
localConfig: VercelConfig;
}
const isJSONObject = (v: any): v is JSONObject => {
return v && typeof v == 'object' && v.constructor === Object;
};
export default class Client extends EventEmitter {
argv: string[];
apiUrl: string;
authConfig: AuthConfig;
output: Output;
config: GlobalConfig;
localConfig: NowConfig;
localConfig: VercelConfig;
constructor(opts: ClientOptions) {
super();
@@ -47,7 +57,6 @@ export default class Client extends EventEmitter {
this.output = opts.output;
this.config = opts.config;
this.localConfig = opts.localConfig;
this._onRetry = this._onRetry.bind(this);
}
retry<T>(fn: RetryFunction<T>, { retries = 3, maxTimeout = Infinity } = {}) {
@@ -78,51 +87,43 @@ export default class Client extends EventEmitter {
}
_url = `${apiUrl}${parsedUrl.pathname}?${query}`;
delete opts.useCurrentTeam;
delete opts.accountId;
}
if (opts.json !== false && opts.body && typeof opts.body === 'object') {
Object.assign(opts, {
body: JSON.stringify(opts.body),
headers: Object.assign({}, opts.headers, {
'Content-Type': 'application/json',
}),
});
}
const headers = new Headers(opts.headers);
headers.set('authorization', `Bearer ${this.authConfig.token}`);
headers.set('user-agent', ua);
opts.headers = opts.headers || {};
opts.headers.Authorization = `Bearer ${this.authConfig.token}`;
opts.headers['user-agent'] = ua;
let body;
if (isJSONObject(opts.body)) {
body = JSON.stringify(opts.body);
headers.set('content-type', 'application/json; charset=utf8');
} else {
body = opts.body;
}
const url = `${apiUrl ? '' : this.apiUrl}${_url}`;
return this.output.time(
`${opts.method || 'GET'} ${url} ${JSON.stringify(opts.body) || ''}`,
fetch(url, opts as RequestInit)
fetch(url, { ...opts, headers, body })
);
}
async fetch<T>(url: string, opts: FetchOptions = {}): Promise<T> {
fetch(url: string, opts: { json: false }): Promise<Response>;
fetch<T>(url: string, opts?: FetchOptions): Promise<T>;
fetch(url: string, opts: FetchOptions = {}) {
return this.retry(async bail => {
const res = await this._fetch(url, opts);
printIndications(res);
if (!res.ok) {
const error = await responseError(res);
if (error.saml && error.teamId) {
// If a SAML error is encountered then we re-trigger the SAML
// authentication flow for the team specified in the error.
const result = await doSsoLogin(error.teamId, this);
if (typeof result === 'number') {
this.output.prettyError(error);
process.exit(1);
return;
}
this.authConfig.token = result;
writeToAuthConfigFile(this.authConfig);
if (isSAMLError(error)) {
// A SAML error means the token is expired, or is not
// designated for the requested team, so the user needs
// to re-authenticate
await this.reauthenticate(error);
} else if (res.status >= 400 && res.status < 500) {
// Any other 4xx should bail without retrying
return bail(error);
@@ -136,21 +137,37 @@ export default class Client extends EventEmitter {
return res;
}
if (!res.headers.get('content-type')) {
const contentType = res.headers.get('content-type');
if (!contentType) {
return null;
}
printIndications(res);
return res.headers.get('content-type').includes('application/json')
? res.json()
: res;
return contentType.includes('application/json') ? res.json() : res;
}, opts.retry);
}
_onRetry(error: Error) {
this.output.debug(`Retrying: ${error}\n${error.stack}`);
}
reauthenticate = sharedPromise(async function (
this: Client,
error: SAMLError
) {
const result = await reauthenticate(this, error);
close() {}
if (typeof result === 'number') {
if (error instanceof APIError) {
this.output.prettyError(error);
} else {
this.output.error(
`Failed to re-authenticate for ${bold(error.scope)} scope`
);
}
process.exit(1);
}
this.authConfig.token = result;
writeToAuthConfigFile(this.authConfig);
});
_onRetry = (error: Error) => {
this.output.debug(`Retrying: ${error}\n${error.stack}`);
};
}

View File

@@ -8,7 +8,7 @@ import getLocalPathConfig from './local-path';
import { NowError } from '../now-error';
import error from '../output/error';
import highlight from '../output/highlight';
import { NowConfig } from '../dev/types';
import { VercelConfig } from '../dev/types';
import { AuthConfig, GlobalConfig } from '../../types';
const VERCEL_DIR = getGlobalPathConfig();
@@ -16,19 +16,15 @@ const CONFIG_FILE_PATH = join(VERCEL_DIR, 'config.json');
const AUTH_CONFIG_FILE_PATH = join(VERCEL_DIR, 'auth.json');
// reads "global config" file atomically
export const readConfigFile = (fileName = CONFIG_FILE_PATH): GlobalConfig => {
const config = loadJSON.sync(fileName);
config[fileNameSymbol] = fileName;
export const readConfigFile = (): GlobalConfig => {
const config = loadJSON.sync(CONFIG_FILE_PATH);
return config;
};
// writes whatever's in `stuff` to "global config" file, atomically
export const writeToConfigFile = (stuff: GlobalConfig): void => {
const fileName = stuff[fileNameSymbol];
if (!fileName) return;
try {
return writeJSON.sync(fileName, stuff, { indent: 2 });
return writeJSON.sync(CONFIG_FILE_PATH, stuff, { indent: 2 });
} catch (err) {
if (err.code === 'EPERM') {
console.error(
@@ -55,21 +51,17 @@ export const writeToConfigFile = (stuff: GlobalConfig): void => {
};
// reads "auth config" file atomically
export const readAuthConfigFile = (
fileName = AUTH_CONFIG_FILE_PATH
): AuthConfig => {
const config = loadJSON.sync(fileName);
config[fileNameSymbol] = fileName;
export const readAuthConfigFile = (): AuthConfig => {
const config = loadJSON.sync(AUTH_CONFIG_FILE_PATH);
return config;
};
// writes whatever's in `stuff` to "auth config" file, atomically
export const writeToAuthConfigFile = (stuff: AuthConfig) => {
const fileName = stuff[fileNameSymbol];
if (!fileName) return;
export const writeToAuthConfigFile = (authConfig: AuthConfig) => {
if (authConfig.skipWrite) {
return;
}
try {
return writeJSON.sync(fileName, stuff, {
return writeJSON.sync(AUTH_CONFIG_FILE_PATH, authConfig, {
indent: 2,
mode: 0o600,
});
@@ -108,8 +100,8 @@ export function getAuthConfigFilePath() {
export function readLocalConfig(
prefix: string = process.cwd()
): NowConfig | null {
let config: NowConfig | null = null;
): VercelConfig | null {
let config: VercelConfig | null = null;
let target = '';
try {

View File

@@ -66,7 +66,7 @@ export const getDefaultAuthConfig = async existing => {
const config = {
_:
'This is your Now credentials file. DO NOT SHARE! More: https://vercel.com/docs/configuration#global',
'This is your Vercel credentials file. DO NOT SHARE! More: https://vercel.com/docs/configuration#global',
};
if (existing) {

View File

@@ -1,7 +1,7 @@
import { join } from 'path';
import { CantParseJSONFile } from '../errors-ts';
import readJSONFile from '../read-json-file';
import { NowConfig } from '../dev/types';
import { VercelConfig } from '../dev/types';
import getLocalConfigPath from './local-path';
export default async function readConfig(dir: string) {
@@ -13,7 +13,7 @@ export default async function readConfig(dir: string) {
}
if (result) {
return result as NowConfig;
return result as VercelConfig;
}
return null;

View File

@@ -39,7 +39,9 @@ export default async function getDeploymentsByProjectId(
query.set('from', options.from.toString());
}
const { deployments } = await client.fetch<Response>(`/v4/now/deployments?${query}`);
const { deployments } = await client.fetch<Response>(
`/v4/now/deployments?${query}`
);
total += deployments.length;
if (options.max && total >= options.max) {
@@ -49,15 +51,15 @@ export default async function getDeploymentsByProjectId(
if (options.continue && deployments.length === limit) {
const nextFrom = deployments[deployments.length - 1].created;
const nextOptions = Object.assign({}, options, { from: nextFrom });
deployments.push(...(await getDeploymentsByProjectId(client, projectId, nextOptions, total)));
deployments.push(
...(await getDeploymentsByProjectId(
client,
projectId,
nextOptions,
total
))
);
}
return deployments;
}
export async function getAllDeploymentsByProjectId(
client: Client,
projectId: string
) {
return getDeploymentsByProjectId(client, projectId, { from: null, limit: 100, continue: true });
}

View File

@@ -9,7 +9,7 @@ import {
import { Output } from '../output';
// @ts-ignore
import Now from '../../util';
import { NowConfig } from '../dev/types';
import { VercelConfig } from '../dev/types';
import { Org } from '../../types';
import ua from '../ua';
import { linkFolderToProject } from '../projects/link';
@@ -43,7 +43,7 @@ export default async function processDeployment({
uploadStamp: () => string;
deployStamp: () => string;
quiet: boolean;
nowConfig?: NowConfig;
nowConfig?: VercelConfig;
force?: boolean;
withCache?: boolean;
org: Org;

View File

@@ -27,7 +27,7 @@ import { LambdaSizeExceededError } from '../errors-ts';
import DevServer from './server';
import { getBuilder } from './builder-cache';
import {
NowConfig,
VercelConfig,
BuildMatch,
BuildResult,
BuilderInputs,
@@ -96,7 +96,7 @@ async function createBuildProcess(
}
export async function executeBuild(
nowConfig: NowConfig,
nowConfig: VercelConfig,
devServer: DevServer,
files: BuilderInputs,
match: BuildMatch,
@@ -383,7 +383,7 @@ export async function executeBuild(
}
export async function getBuildMatches(
nowConfig: NowConfig,
nowConfig: VercelConfig,
cwd: string,
output: Output,
devServer: DevServer,

View File

@@ -4,7 +4,7 @@ import PCRE from 'pcre-to-regexp';
import isURL from './is-url';
import DevServer from './server';
import { NowConfig, HttpHeadersConfig, RouteResult } from './types';
import { VercelConfig, HttpHeadersConfig, RouteResult } from './types';
import { isHandler, Route, HandleValue } from '@vercel/routing-utils';
export function resolveRouteParameters(
@@ -50,7 +50,7 @@ export async function devRouter(
reqMethod?: string,
routes?: Route[],
devServer?: DevServer,
nowConfig?: NowConfig,
nowConfig?: VercelConfig,
previousHeaders?: HttpHeadersConfig,
missRoutes?: Route[],
phase?: HandleValue | null

View File

@@ -49,7 +49,7 @@ import sleep from '../sleep';
import { Output } from '../output';
import { relative } from '../path-helpers';
import { getDistTag } from '../get-dist-tag';
import getNowConfigPath from '../config/local-path';
import getVercelConfigPath from '../config/local-path';
import { MissingDotenvVarsError } from '../errors-ts';
import cliPkg from '../pkg';
import { getVercelDirectory } from '../projects/link';
@@ -73,7 +73,7 @@ import errorTemplate502 from './templates/error_502';
import redirectTemplate from './templates/redirect';
import {
NowConfig,
VercelConfig,
DevServerOptions,
BuildMatch,
BuildResult,
@@ -145,7 +145,8 @@ export default class DevServer {
private devServerPids: Set<number>;
private projectSettings?: ProjectSettings;
private getNowConfigPromise: Promise<NowConfig> | null;
private vercelConfigWarning: boolean;
private getVercelConfigPromise: Promise<VercelConfig> | null;
private blockingBuildsPromise: Promise<void> | null;
private updateBuildersPromise: Promise<void> | null;
private updateBuildersTimeout: NodeJS.Timeout | undefined;
@@ -181,7 +182,8 @@ export default class DevServer {
this.inProgressBuilds = new Map();
this.devCacheDir = join(getVercelDirectory(cwd), 'cache');
this.getNowConfigPromise = null;
this.vercelConfigWarning = false;
this.getVercelConfigPromise = null;
this.blockingBuildsPromise = null;
this.updateBuildersPromise = null;
this.startPromise = null;
@@ -244,7 +246,7 @@ export default class DevServer {
}
}
const nowConfig = await this.getNowConfig();
const nowConfig = await this.getVercelConfig();
// Update the build matches in case an entrypoint was created or deleted
await this.updateBuildMatches(nowConfig);
@@ -375,7 +377,7 @@ export default class DevServer {
}
async updateBuildMatches(
nowConfig: NowConfig,
nowConfig: VercelConfig,
isInitial = false
): Promise<void> {
const fileList = this.resolveBuildFiles(this.files);
@@ -460,7 +462,7 @@ export default class DevServer {
}
async invalidateBuildMatches(
nowConfig: NowConfig,
nowConfig: VercelConfig,
updatedBuilders: string[]
): Promise<void> {
if (updatedBuilders.length === 0) {
@@ -516,25 +518,25 @@ export default class DevServer {
return {};
}
clearNowConfigPromise = () => {
this.getNowConfigPromise = null;
clearVercelConfigPromise = () => {
this.getVercelConfigPromise = null;
};
getNowConfig(): Promise<NowConfig> {
if (this.getNowConfigPromise) {
return this.getNowConfigPromise;
getVercelConfig(): Promise<VercelConfig> {
if (this.getVercelConfigPromise) {
return this.getVercelConfigPromise;
}
this.getNowConfigPromise = this._getNowConfig();
this.getVercelConfigPromise = this._getVercelConfig();
// Clean up the promise once it has resolved
const clear = this.clearNowConfigPromise;
this.getNowConfigPromise.finally(clear);
const clear = this.clearVercelConfigPromise;
this.getVercelConfigPromise.finally(clear);
return this.getNowConfigPromise;
return this.getVercelConfigPromise;
}
async _getNowConfig(): Promise<NowConfig> {
const configPath = getNowConfigPath(this.cwd);
async _getVercelConfig(): Promise<VercelConfig> {
const configPath = getVercelConfigPath(this.cwd);
const [
pkg = null,
@@ -543,10 +545,10 @@ export default class DevServer {
config = { version: 2, [fileNameSymbol]: 'vercel.json' },
] = await Promise.all([
this.readJsonFile<PackageJson>('package.json'),
this.readJsonFile<NowConfig>(configPath),
this.readJsonFile<VercelConfig>(configPath),
]);
await this.validateNowConfig(config);
await this.validateVercelConfig(config);
const { error: routeError, routes: maybeRoutes } = getTransformedRoutes({
nowConfig: config,
});
@@ -634,7 +636,21 @@ export default class DevServer {
config.builds.sort(sortBuilders);
}
await this.validateNowConfig(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.apiDir = detectApiDirectory(config.builds || []);
@@ -710,8 +726,8 @@ export default class DevServer {
}
async tryValidateOrExit(
config: NowConfig,
validate: (c: NowConfig) => string | null
config: VercelConfig,
validate: (c: VercelConfig) => string | null
): Promise<void> {
const message = validate(config);
@@ -721,7 +737,7 @@ export default class DevServer {
}
}
async validateNowConfig(config: NowConfig): Promise<void> {
async validateVercelConfig(config: VercelConfig): Promise<void> {
if (config.version === 1) {
this.output.error('Cannot run `version: 1` projects.');
await this.exit(1);
@@ -855,7 +871,7 @@ export default class DevServer {
.replace('[::]', 'localhost')
.replace('127.0.0.1', 'localhost');
const nowConfig = await this.getNowConfig();
const nowConfig = await this.getVercelConfig();
const devCommandPromise = this.runDevCommand();
const files = await getFiles(this.cwd, { output: this.output });
@@ -982,7 +998,7 @@ export default class DevServer {
if (devProcess) {
ops.push(
new Promise((resolve, reject) => {
new Promise<void>((resolve, reject) => {
devProcess.once('exit', () => resolve());
try {
process.kill(devProcess.pid);
@@ -1201,7 +1217,7 @@ export default class DevServer {
match: BuildMatch,
requestPath: string | null,
req: http.IncomingMessage | null,
nowConfig: NowConfig,
nowConfig: VercelConfig,
previousBuildResult?: BuildResult,
filesChanged?: string[],
filesRemoved?: string[]
@@ -1290,7 +1306,7 @@ export default class DevServer {
this.output.debug(`${chalk.bold(method)} ${req.url}`);
try {
const nowConfig = await this.getNowConfig();
const nowConfig = await this.getVercelConfig();
await this.serveProjectAsNowV2(req, res, nowRequestId, nowConfig);
} catch (err) {
console.error(err);
@@ -1339,7 +1355,7 @@ export default class DevServer {
req: http.IncomingMessage,
res: http.ServerResponse,
nowRequestId: string,
nowConfig: NowConfig,
nowConfig: VercelConfig,
routes: Route[] | undefined = nowConfig.routes,
callLevel: number = 0
) => {
@@ -1992,7 +2008,7 @@ export default class DevServer {
return true;
}
async hasFilesystem(dest: string, nowConfig: NowConfig): Promise<boolean> {
async hasFilesystem(dest: string, nowConfig: VercelConfig): Promise<boolean> {
if (
await findBuildMatch(
this.buildMatches,
@@ -2181,7 +2197,7 @@ async function findBuildMatch(
files: BuilderInputs,
requestPath: string,
devServer: DevServer,
nowConfig: NowConfig,
nowConfig: VercelConfig,
isFilesystem = false
): Promise<BuildMatch | null> {
requestPath = requestPath.replace(/^\//, '');
@@ -2219,7 +2235,7 @@ async function shouldServe(
files: BuilderInputs,
requestPath: string,
devServer: DevServer,
nowConfig: NowConfig,
nowConfig: VercelConfig,
isFilesystem = false
): Promise<boolean> {
const {
@@ -2284,7 +2300,7 @@ async function findMatchingRoute(
match: BuildMatch,
requestPath: string,
devServer: DevServer,
nowConfig: NowConfig
nowConfig: VercelConfig
): Promise<RouteResult | void> {
const reqUrl = `/${requestPath}`;
for (const buildResult of match.buildResults.values()) {
@@ -2305,7 +2321,7 @@ async function findMatchingRoute(
function findAsset(
match: BuildMatch,
requestPath: string,
nowConfig: NowConfig
nowConfig: VercelConfig
): { asset: BuilderOutput; assetKey: string } | void {
if (!match.buildOutput) {
return;
@@ -2397,7 +2413,7 @@ function filterFrontendBuilds(build: Builder) {
return !frontendRuntimeSet.has(name || '');
}
function hasNewRoutingProperties(nowConfig: NowConfig) {
function hasNewRoutingProperties(nowConfig: VercelConfig) {
return (
typeof nowConfig.cleanUrls !== undefined ||
typeof nowConfig.headers !== undefined ||

View File

@@ -14,12 +14,12 @@ import {
Lambda,
PackageJson,
} from '@vercel/build-utils';
import { NowConfig } from '@vercel/client';
import { VercelConfig } from '@vercel/client';
import { HandleValue, Route } from '@vercel/routing-utils';
import { Output } from '../output';
import { ProjectEnvVariable, ProjectSettings } from '../../types';
export { NowConfig };
export { VercelConfig };
export interface DevServerOptions {
output: Output;

View File

@@ -7,7 +7,7 @@ import {
rewritesSchema,
trailingSlashSchema,
} from '@vercel/routing-utils';
import { NowConfig } from './types';
import { VercelConfig } from './types';
import {
functionsSchema,
buildsSchema,
@@ -36,7 +36,7 @@ const vercelConfigSchema = {
const ajv = new Ajv();
const validate = ajv.compile(vercelConfigSchema);
export function validateConfig(config: NowConfig): NowBuildError | null {
export function validateConfig(config: VercelConfig): NowBuildError | null {
if (!validate(config)) {
if (validate.errors && validate.errors[0]) {
const error = validate.errors[0];

View File

@@ -1,28 +1,17 @@
export type EmojiLabel =
| 'notice'
| 'tip'
| 'warning'
| 'link'
| 'inspect'
| 'success';
export const emojiLabels = {
notice: '📝',
tip: '💡',
warning: '❗️',
link: '🔗',
inspect: '🔍',
success: '✅',
locked: '🔒',
} as const;
export function emoji(label: EmojiLabel): string | undefined {
switch (label) {
case 'notice':
return '📝';
case 'tip':
return '💡';
case 'warning':
return '❗️';
case 'link':
return '🔗';
case 'inspect':
return '🔍';
case 'success':
return '✅';
default:
return undefined;
}
export type EmojiLabel = keyof typeof emojiLabels;
export function emoji(label: EmojiLabel) {
return emojiLabels[label];
}
export function prependEmoji(message: string, emoji?: string): string {

View File

@@ -2,52 +2,41 @@
import { URLSearchParams } from 'url';
// Packages
import retry from 'async-retry';
import jsonlines from 'jsonlines';
import { eraseLines } from 'ansi-escapes';
import jsonlines from 'jsonlines';
import retry from 'async-retry';
import Client from './client';
import { getDeployment } from './get-deployment';
export interface FindOpts {
direction: 'forward' | 'backward';
limit?: number;
since?: number;
until?: number;
follow?: boolean;
}
export interface PrintEventsOptions {
mode: string;
onEvent: (event: DeploymentEvent) => void;
quiet?: boolean;
findOpts: FindOpts;
}
export interface DeploymentEvent {
id: string;
created: number;
date?: number;
serial?: string;
}
async function printEvents(
now,
deploymentIdOrURL,
currentTeam = null,
{
mode,
onOpen = () => {},
onEvent,
quiet,
debugEnabled,
findOpts,
output,
} = {}
client: Client,
deploymentIdOrURL: string,
{ mode, onEvent, quiet, findOpts }: PrintEventsOptions
) {
const { log, debug } = output;
let onOpenCalled = false;
function callOnOpenOnce() {
if (onOpenCalled) return;
onOpenCalled = true;
onOpen();
}
const query = new URLSearchParams({
direction: findOpts.direction,
limit: findOpts.limit,
since: findOpts.since,
until: findOpts.until,
follow: findOpts.follow ? '1' : '',
format: 'lines',
});
let eventsUrl = `/v1/now/deployments/${deploymentIdOrURL}/events?${query}`;
let pollUrl = `/v3/now/deployments/${deploymentIdOrURL}`;
if (currentTeam) {
eventsUrl += `&teamId=${currentTeam.id}`;
pollUrl += `?teamId=${currentTeam.id}`;
}
debug(`Events ${eventsUrl}`);
const { log, debug } = client.output;
// we keep track of how much we log in case we
// drop the connection and have to start over
@@ -59,29 +48,34 @@ async function printEvents(
debug('Retrying events');
}
const eventsRes = await now._fetch(eventsUrl);
const query = new URLSearchParams({
direction: findOpts.direction,
follow: findOpts.follow ? '1' : '',
format: 'lines',
});
if (findOpts.limit) query.set('limit', String(findOpts.limit));
if (findOpts.since) query.set('since', String(findOpts.since));
if (findOpts.until) query.set('until', String(findOpts.until));
const eventsUrl = `/v1/now/deployments/${deploymentIdOrURL}/events?${query}`;
const eventsRes = await client.fetch(eventsUrl, { json: false });
if (eventsRes.ok) {
const readable = eventsRes.readable
? await eventsRes.readable()
: eventsRes.body;
const readable = eventsRes.body;
// handle the event stream and make the promise get rejected
// if errors occur so we can retry
return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
const stream = readable.pipe(jsonlines.parse());
let poller;
let poller: ReturnType<typeof setTimeout>;
if (mode === 'deploy') {
poller = (function startPoller() {
return setTimeout(async () => {
try {
const pollRes = await now._fetch(pollUrl);
if (!pollRes.ok)
throw new Error(`Response ${pollRes.status}`);
const json = await pollRes.json();
if (json.state === 'READY') {
const json = await getDeployment(client, deploymentIdOrURL);
if (json.readyState === 'READY') {
stream.end();
finish();
return;
@@ -96,10 +90,9 @@ async function printEvents(
}
let finishCalled = false;
function finish(error) {
function finish(error?: Error) {
if (finishCalled) return;
finishCalled = true;
callOnOpenOnce();
clearTimeout(poller);
if (error) {
reject(error);
@@ -110,26 +103,24 @@ async function printEvents(
let latestLogDate = 0;
const onData = data => {
const { event } = data;
if (event === 'state' && data.payload.value === 'READY') {
const onData = (data: any) => {
const { event, payload, date } = data;
if (event === 'state' && payload.value === 'READY') {
if (mode === 'deploy') {
stream.end();
finish();
}
} else {
latestLogDate = Math.max(latestLogDate, data.date);
const linesPrinted = onEvent(data, callOnOpenOnce);
o += linesPrinted || 0;
latestLogDate = Math.max(latestLogDate, date);
onEvent(data);
}
};
let onErrorCalled = false;
const onError = err => {
const onError = (err: Error) => {
if (finishCalled || onErrorCalled) return;
onErrorCalled = true;
o++;
callOnOpenOnce();
const errorMessage = `Deployment event stream error: ${err.message}`;
if (!findOpts.follow) {
@@ -140,7 +131,6 @@ async function printEvents(
debug(errorMessage);
clearTimeout(poller);
stream.destroy(err);
readable.destroy(err);
const retryFindOpts = {
...findOpts,
@@ -149,12 +139,10 @@ async function printEvents(
setTimeout(() => {
// retry without maximum amount nor clear past logs etc
printEvents(now, deploymentIdOrURL, currentTeam, {
printEvents(client, deploymentIdOrURL, {
mode,
onOpen,
onEvent,
quiet,
debugEnabled,
findOpts: retryFindOpts,
}).then(resolve, reject);
}, 2000);
@@ -166,7 +154,6 @@ async function printEvents(
readable.on('error', onError);
});
}
callOnOpenOnce();
const err = new Error(`Deployment events status ${eventsRes.status}`);
if (eventsRes.status < 500) {

View File

@@ -9,15 +9,15 @@ import {
import humanizePath from './humanize-path';
import readJSONFile from './read-json-file';
import readPackage from './read-package';
import { NowConfig } from './dev/types';
import { VercelConfig } from './dev/types';
import { Output } from './output';
let config: NowConfig;
let config: VercelConfig;
export default async function getConfig(
output: Output,
configFile?: string
): Promise<NowConfig | Error> {
): Promise<VercelConfig | Error> {
// If config was already read, just return it
if (config) {
return config;
@@ -44,7 +44,7 @@ export default async function getConfig(
return localConfig;
}
if (localConfig !== null) {
config = localConfig as NowConfig;
config = localConfig as VercelConfig;
config[fileNameSymbol] = configFile;
return config;
}
@@ -68,13 +68,13 @@ export default async function getConfig(
}
if (vercelConfig !== null) {
output.debug(`Found config in file "${vercelFilePath}"`);
config = vercelConfig as NowConfig;
config = vercelConfig as VercelConfig;
config[fileNameSymbol] = 'vercel.json';
return config;
}
if (nowConfig !== null) {
output.debug(`Found config in file "${nowFilePath}"`);
config = nowConfig as NowConfig;
config = nowConfig as VercelConfig;
config[fileNameSymbol] = 'now.json';
return config;
}
@@ -87,7 +87,7 @@ export default async function getConfig(
}
if (pkgConfig) {
output.debug(`Found config in package ${pkgFilePath}`);
config = pkgConfig as NowConfig;
config = pkgConfig as VercelConfig;
config[fileNameSymbol] = 'package.json';
return config;
}

View File

@@ -0,0 +1,27 @@
import { stringify } from 'querystring';
import { Deployment } from '@vercel/client';
import Client from './client';
export async function getDeployment(
client: Client,
hostOrId: string
): Promise<Deployment> {
let url = `/v13/deployments`;
if (hostOrId.includes('.')) {
let host = hostOrId.replace(/^https:\/\//i, '');
if (host.slice(-1) === '/') {
host = host.slice(0, -1);
}
url += `/get?${stringify({
url: host,
})}`;
} else {
url += `/${encodeURIComponent(hostOrId)}`;
}
const deployment = await client.fetch<Deployment>(url);
return deployment;
}

View File

@@ -1,8 +1,6 @@
import Client from './client';
import { APIError, InvalidToken } from './errors-ts';
import { Team } from '../types';
// @ts-ignore
import NowTeams from './teams.js';
import { APIError, InvalidToken } from './errors-ts';
let teams: Team[] | undefined;
@@ -10,15 +8,11 @@ export default async function getTeams(client: Client): Promise<Team[]> {
if (teams) return teams;
try {
// we're using NowTeams because `client.fetch` hangs on windows
const teamClient = new NowTeams({
apiUrl: client.apiUrl,
token: client.authConfig.token,
debug: client.output.isDebugEnabled(),
const body = await client.fetch<{ teams: Team[] }>('/v1/teams', {
useCurrentTeam: false,
});
teams = (await teamClient.ls()).teams;
return teams || [];
teams = body.teams || [];
return teams;
} catch (error) {
if (error instanceof APIError && error.status === 403) {
throw new InvalidToken();

View File

@@ -6,15 +6,12 @@ export default async function confirm(
): Promise<boolean> {
require('./patch-inquirer');
const name = `${Date.now()}`;
const answers = await inquirer.prompt({
type: 'confirm',
name,
name: 'value',
message,
default: preferred,
});
const answer = answers[name] as boolean;
return answer;
return answers.value;
}

View File

@@ -1,82 +0,0 @@
import inquirer from 'inquirer';
import stripAnsi from 'strip-ansi';
import eraseLines from '../output/erase-lines';
function getLength(string) {
let biggestLength = 0;
string.split('\n').map(str => {
str = stripAnsi(str);
if (str.length > biggestLength) {
biggestLength = str.length;
}
return undefined;
});
return biggestLength;
}
export default async function({
message = 'the question',
// eslint-disable-line no-unused-vars
choices = [
{
name: 'something\ndescription\ndetails\netc',
value: 'something unique',
short: 'generally the first line of `name`',
},
],
pageSize = 15, // Show 15 lines without scrolling (~4 credit cards)
separator = true, // Puts a blank separator between each choice
abort = 'end', // Wether the `abort` option will be at the `start` or the `end`,
eraseFinalAnswer = false, // If true, the line with the final answee that inquirer prints will be erased before returning
}) {
require('./patch-inquirer-legacy');
let biggestLength = 0;
choices = choices.map(choice => {
if (choice.name) {
const length = getLength(choice.name);
if (length > biggestLength) {
biggestLength = length;
}
return choice;
}
throw new Error('Invalid choice');
});
if (separator === true) {
choices = choices.reduce(
(prev, curr) => prev.concat(new inquirer.Separator(' '), curr),
[]
);
}
const abortSeparator = new inquirer.Separator('─'.repeat(biggestLength));
const _abort = {
name: 'Abort',
value: undefined,
};
if (abort === 'start') {
const blankSep = choices.shift();
choices.unshift(abortSeparator);
choices.unshift(_abort);
choices.unshift(blankSep);
} else {
choices.push(abortSeparator);
choices.push(_abort);
}
const nonce = Date.now();
const answer = await inquirer.prompt({
name: nonce,
type: 'list',
message,
choices,
pageSize,
});
if (eraseFinalAnswer === true) {
process.stdout.write(eraseLines(2));
}
return answer[nonce];
}

View File

@@ -0,0 +1,123 @@
import inquirer from 'inquirer';
import stripAnsi from 'strip-ansi';
import eraseLines from '../output/erase-lines';
interface ListEntry {
name: string;
value: string;
short: string;
selected?: boolean;
}
interface ListSeparator {
separator: string;
}
type ListChoice = ListEntry | ListSeparator | typeof inquirer.Separator;
interface ListOptions {
message: string;
choices: ListChoice[];
pageSize?: number;
separator?: boolean;
abort?: 'start' | 'end';
eraseFinalAnswer?: boolean;
}
function getLength(input: string): number {
let biggestLength = 0;
for (const line of input.split('\n')) {
const str = stripAnsi(line);
if (str.length > biggestLength) {
biggestLength = str.length;
}
}
return biggestLength;
}
export default async function list({
message = 'the question',
// eslint-disable-line no-unused-vars
choices: _choices = [
{
name: 'something\ndescription\ndetails\netc',
value: 'something unique',
short: 'generally the first line of `name`',
},
],
pageSize = 15, // Show 15 lines without scrolling (~4 credit cards)
separator = false, // Puts a blank separator between each choice
abort = 'end', // Whether the `abort` option will be at the `start` or the `end`,
eraseFinalAnswer = false, // If true, the line with the final answer that inquirer prints will be erased before returning
}: ListOptions): Promise<string> {
require('./patch-inquirer-legacy');
let biggestLength = 0;
let selected: string | undefined;
// First calculate the biggest length
for (const choice of _choices) {
if ('name' in choice) {
const length = getLength(choice.name);
if (length > biggestLength) {
biggestLength = length;
}
}
}
const choices = _choices.map(choice => {
if (choice instanceof inquirer.Separator) {
return choice;
}
if ('separator' in choice) {
const prefix = `── ${choice.separator} `;
const suffix = '─'.repeat(biggestLength - getLength(prefix));
return new inquirer.Separator(`${prefix}${suffix}`);
}
if ('short' in choice) {
if (choice.selected) {
if (selected) throw new Error('Only one choice may be selected');
selected = choice.short;
}
return choice;
}
throw new Error('Invalid choice');
});
if (separator) {
for (let i = 0; i < choices.length; i += 2) {
choices.splice(i, 0, new inquirer.Separator(' '));
}
}
const abortSeparator = new inquirer.Separator('─'.repeat(biggestLength));
const _abort = {
name: 'Abort',
value: '',
short: '',
};
if (abort === 'start') {
choices.unshift(_abort, abortSeparator);
} else {
choices.push(abortSeparator, _abort);
}
const answer = await inquirer.prompt({
name: 'value',
type: 'list',
default: selected,
message,
choices,
pageSize,
});
if (eraseFinalAnswer === true) {
process.stdout.write(eraseLines(2));
}
return answer.value;
}

View File

@@ -2,7 +2,7 @@ import { join, basename } from 'path';
import chalk from 'chalk';
import { remove } from 'fs-extra';
import { ProjectLinkResult, ProjectSettings } from '../../types';
import { NowConfig } from '../dev/types';
import { VercelConfig } from '../dev/types';
import {
getLinkedProject,
linkFolderToProject,
@@ -134,7 +134,7 @@ export default async function setupAndLink(
return { status: 'error', exitCode: 1 };
}
let localConfig: NowConfig = {};
let localConfig: VercelConfig = {};
if (client.localConfig && !(client.localConfig instanceof Error)) {
localConfig = client.localConfig;
}

View File

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

View File

@@ -1,70 +1,18 @@
import ms from 'ms';
import { stringify as stringifyQuery } from 'querystring';
import fetch from 'node-fetch';
import sleep from '../sleep';
import ua from '../ua';
import error from '../output/error';
import highlight from '../output/highlight';
import eraseLines from '../output/erase-lines';
import verify from './verify';
import executeLogin from './login';
import { LoginParams } from './types';
async function verify(
email: string,
verificationToken: string,
{ apiUrl, output }: LoginParams
): Promise<string> {
const query = {
email,
token: verificationToken,
};
output.debug('GET /now/registration/verify');
let res;
try {
res = await fetch(
`${apiUrl}/now/registration/verify?${stringifyQuery(query)}`,
{
headers: { 'User-Agent': ua },
}
);
} catch (err) {
output.debug(`error fetching /now/registration/verify: ${err.stack}`);
throw new Error(
error(
`An unexpected error occurred while trying to verify your login: ${err.message}`
)
);
}
output.debug('parsing response from GET /now/registration/verify');
let body;
try {
body = await res.json();
} catch (err) {
output.debug(
`error parsing the response from /now/registration/verify: ${err.stack}`
);
throw new Error(
error(
`An unexpected error occurred while trying to verify your login: ${err.message}`
)
);
}
return body.token;
}
export default async function doEmailLogin(
email: string,
{ apiUrl, output }: LoginParams
params: LoginParams,
email: string
): Promise<number | string> {
let securityCode;
let verificationToken;
const { apiUrl, output } = params;
output.spinner('Sending you an email');
@@ -91,22 +39,18 @@ export default async function doEmailLogin(
output.spinner('Waiting for your confirmation');
let token = '';
while (!token) {
try {
await sleep(ms('1s'));
token = await verify(email, verificationToken, { apiUrl, output });
token = await verify(email, verificationToken, 'Email', params);
} catch (err) {
if (/invalid json response body/.test(err.message)) {
// /now/registraton is currently returning plain text in that case
// we just wait for the user to click on the link
} else {
if (err.message !== 'Confirmation incomplete') {
output.error(err.message);
return 1;
}
}
}
output.success('Email confirmed');
output.success(`Email authentication complete for ${email}`);
return token;
}

View File

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

View File

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

View File

@@ -1,8 +1,6 @@
import fetch from 'node-fetch';
import { hostname } from 'os';
import { InvalidEmail, AccountNotFound } from '../errors-ts';
import ua from '../ua';
import { getTitleName } from '../pkg-name';
import { LoginData } from './types';
export default async function login(
@@ -10,20 +8,13 @@ export default async function login(
email: string,
mode: 'login' | 'signup' = 'login'
): Promise<LoginData> {
const hyphens = new RegExp('-', 'g');
const host = hostname().replace(hyphens, ' ').replace('.local', '');
const tokenName = `${getTitleName()} CLI on ${host}`;
const response = await fetch(`${apiUrl}/now/registration?mode=${mode}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': ua,
},
body: JSON.stringify({
tokenName,
email,
}),
body: JSON.stringify({ email }),
});
const body = await response.json();

View File

@@ -0,0 +1,114 @@
import http from 'http';
import open from 'open';
import { URL } from 'url';
import listen from 'async-listen';
import { LoginParams } from './types';
import prompt from './prompt';
import verify from './verify';
import highlight from '../output/highlight';
import link from '../output/link';
import eraseLines from '../output/erase-lines';
export default async function doOauthLogin(
params: LoginParams,
url: URL,
provider: string
): Promise<number | string> {
const { output } = params;
const server = http.createServer();
const address = await listen(server, 0, '127.0.0.1');
const { port } = new URL(address);
url.searchParams.set('mode', 'login');
url.searchParams.set('next', `http://localhost:${port}`);
output.log(`Please visit the following URL in your web browser:`);
output.log(link(url.href));
output.spinner(`Waiting for ${provider} authentication to be completed`);
try {
const [query] = await Promise.all([
new Promise<URL['searchParams']>((resolve, reject) => {
server.once('request', (req, res) => {
const query = new URL(req.url || '/', 'http://localhost')
.searchParams;
resolve(query);
// Redirect the user's web browser back to
// the Vercel CLI login notification page
const location = new URL(
'https://vercel.com/notifications/cli-login-'
);
const loginError = query.get('loginError');
const ssoEmail = query.get('ssoEmail');
if (loginError) {
location.pathname += 'failed';
location.searchParams.set('loginError', loginError);
} else if (ssoEmail) {
location.pathname += 'incomplete';
location.searchParams.set('ssoEmail', ssoEmail);
const teamName = query.get('teamName');
const ssoType = query.get('ssoType');
if (teamName) {
location.searchParams.set('teamName', teamName);
}
if (ssoType) {
location.searchParams.set('ssoType', ssoType);
}
} else {
location.pathname += 'success';
const email = query.get('email');
if (email) {
location.searchParams.set('email', email);
}
}
res.statusCode = 302;
res.setHeader('location', location.href);
res.end();
});
server.once('error', reject);
}),
open(url.href),
]);
output.stopSpinner();
output.print(eraseLines(3));
const loginError = query.get('loginError');
if (loginError) {
const err = JSON.parse(loginError);
output.prettyError(err);
return 1;
}
// If an `ssoUserId` was returned, then the SAML Profile is not yet connected
// to a Team member. Prompt the user to log in to a Vercel account now, which
// will complete the connection to the SAML Profile.
const ssoUserId = query.get('ssoUserId');
if (ssoUserId) {
output.log(
'Please log in to your Vercel account to complete SAML connection.'
);
return prompt({ ...params, ssoUserId });
}
const email = query.get('email');
const verificationToken = query.get('token');
if (!email || !verificationToken) {
output.error(
'Verification token was not provided. Please contact support.'
);
return 1;
}
output.spinner('Verifying authentication token');
const token = await verify(email, verificationToken, provider, params);
output.success(
`${provider} authentication complete for ${highlight(email)}`
);
return token;
} finally {
server.close();
}
}

View File

@@ -0,0 +1,81 @@
import inquirer from 'inquirer';
import error from '../output/error';
import listInput from '../input/list';
import { getCommandName } from '../pkg-name';
import { LoginParams, SAMLError } from './types';
import doSsoLogin from './sso';
import doEmailLogin from './email';
import doGithubLogin from './github';
import doGitlabLogin from './gitlab';
import doBitbucketLogin from './bitbucket';
export default async function prompt(
params: LoginParams,
error?: Pick<SAMLError, 'teamId'>
) {
let result: number | string = 1;
const choices = [
{ name: 'Continue with GitHub', value: 'github', short: 'github' },
{ name: 'Continue with GitLab', value: 'gitlab', short: 'gitlab' },
{ name: 'Continue with Bitbucket', value: 'bitbucket', short: 'bitbucket' },
{ name: 'Continue with Email', value: 'email', short: 'email' },
{ name: 'Continue with SAML Single Sign-On', value: 'sso', short: 'sso' },
];
if (params.ssoUserId || (error && !error.teamId)) {
// Remove SAML login option if we're connecting SAML Profile,
// or if this is a SAML error for a user / team without SAML
choices.pop();
}
const choice = await listInput({
message: 'Log in to Vercel',
choices,
});
if (choice === 'github') {
result = await doGithubLogin(params);
} else if (choice === 'gitlab') {
result = await doGitlabLogin(params);
} else if (choice === 'bitbucket') {
result = await doBitbucketLogin(params);
} else if (choice === 'email') {
const email = await readInput('Enter your email address');
result = await doEmailLogin(params, email);
} else if (choice === 'sso') {
const slug = error?.teamId || (await readInput('Enter your Team slug'));
result = await doSsoLogin(params, slug);
}
return result;
}
async function readInput(message: string) {
let input;
while (!input) {
try {
const { val } = await inquirer.prompt({
type: 'input',
name: 'val',
message,
});
input = val;
} catch (err) {
console.log(); // \n
if (err.isTtyError) {
throw new Error(
error(
`Interactive mode not supported please run ${getCommandName(
`login you@domain.com`
)}`
)
);
}
}
}
return input;
}

View File

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

View File

@@ -1,105 +1,9 @@
import http from 'http';
import open from 'open';
import fetch from 'node-fetch';
import { hostname } from 'os';
import { URL } from 'url';
import listen from 'async-listen';
import { getTitleName } from '../pkg-name';
import highlight from '../output/highlight';
import { LoginParams } from './types';
import doOauthLogin from './oauth';
export default async function doSsoLogin(
teamIdOrSlug: string,
{ apiUrl, output }: LoginParams
): Promise<number | string> {
output.print(`Logging in to team "${teamIdOrSlug}"`);
const hyphens = new RegExp('-', 'g');
const host = hostname().replace(hyphens, ' ').replace('.local', '');
const tokenName = `${getTitleName()} CLI on ${host}`;
const server = http.createServer();
const address = await listen(server, 0, '127.0.0.1');
const { port } = new URL(address);
try {
const url = new URL('/auth/sso', apiUrl);
url.searchParams.append('mode', 'login');
url.searchParams.append('next', `http://localhost:${port}`);
url.searchParams.append('teamId', teamIdOrSlug);
url.searchParams.append('tokenName', tokenName);
output.spinner(
'Please complete the SAML Single Sign-On authentication in your web browser'
);
const [query] = await Promise.all([
new Promise<URL['searchParams']>((resolve, reject) => {
server.once('request', (req, res) => {
const query = new URL(req.url || '/', 'http://localhost')
.searchParams;
resolve(query);
// Redirect the user's web browser back to
// the Vercel CLI login notification page
const location = new URL(
'https://vercel.com/notifications/cli-login-'
);
const loginError = query.get('loginError');
if (loginError) {
location.pathname += 'failed';
location.searchParams.set('loginError', loginError);
} else {
location.pathname += 'success';
const email = query.get('email');
if (email) {
location.searchParams.set('email', email);
}
}
res.statusCode = 302;
res.setHeader('location', location.href);
res.end();
});
server.once('error', reject);
}),
open(url.href),
]);
const loginError = query.get('loginError');
if (loginError) {
const err = JSON.parse(loginError);
output.prettyError(err);
return 1;
}
const email = query.get('email');
const verificationToken = query.get('token');
if (!email || !verificationToken) {
output.error(
'Verification token was not provided. Please contact support.'
);
return 1;
}
output.spinner('Verifying authentication token');
const verifyUrl = new URL('/registration/verify', apiUrl);
verifyUrl.searchParams.append('email', email);
verifyUrl.searchParams.append('token', verificationToken);
const verifyRes = await fetch(verifyUrl.href);
if (!verifyRes.ok) {
output.error(
`Unexpected ${verifyRes.status} status code from verify API`
);
output.debug(await verifyRes.text());
return 1;
}
output.success(`SAML authentication complete for ${highlight(email)}`);
const body = await verifyRes.json();
return body.token;
} finally {
server.close();
}
export default function doSsoLogin(params: LoginParams, teamIdOrSlug: string) {
const url = new URL('/auth/sso', params.apiUrl);
url.searchParams.set('teamId', teamIdOrSlug);
return doOauthLogin(params, url, 'SAML Single Sign-On');
}

View File

@@ -1,11 +1,21 @@
import { AuthConfig } from '../../types';
import { Output } from '../output';
export interface LoginParams {
authConfig: AuthConfig;
apiUrl: string;
output: Output;
ssoUserId?: string;
}
export interface LoginData {
token: string;
securityCode: string;
}
export interface SAMLError {
saml?: true;
teamId: string | null;
scope: string;
enforced?: boolean;
}

View File

@@ -0,0 +1,50 @@
import { URL } from 'url';
import fetch, { Headers } from 'node-fetch';
import ua from '../ua';
import { LoginParams } from './types';
import { hostname } from 'os';
import { getTitleName } from '../pkg-name';
export default async function verify(
email: string,
verificationToken: string,
provider: string,
{ authConfig, apiUrl, ssoUserId }: LoginParams
): Promise<string> {
const url = new URL('/registration/verify', apiUrl);
url.searchParams.set('email', email);
url.searchParams.set('token', verificationToken);
const headers = new Headers({ 'User-Agent': ua });
if (authConfig.token) {
// If there is already an auth token then it will be
// upgraded, rather than a new token being created
headers.set('Authorization', `Bearer ${authConfig.token}`);
} else {
// 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) {
url.searchParams.set('ssoUserId', ssoUserId);
}
const res = await fetch(url.href, { headers });
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

@@ -0,0 +1,24 @@
/**
* Wraps a function such that only one in-flight invocation is active at a time.
*
* That is, if the returned function is invoked more that one time before the
* promise returned from the initial invocation resolves, then the same promise
* is returned for subsequent invocations.
*
* Once the promise has resolved, the next invocation of the returned function
* will re-invoke the original function again.
*/
export function sharedPromise<P extends any[], V, T>(
fn: (this: T, ...args: P) => Promise<V>
) {
let promise: Promise<V> | null = null;
return function (this: T, ...args: P) {
if (!promise) {
promise = fn.apply(this, args);
promise.finally(() => {
promise = null;
});
}
return promise;
};
}

View File

@@ -1,11 +1,11 @@
import path from 'path';
import { CantParseJSONFile } from './errors-ts';
import readJSONFile from './read-json-file';
import { NowConfig } from './dev/types';
import { VercelConfig } from './dev/types';
import { PackageJson } from '@vercel/build-utils';
interface CustomPackage extends PackageJson {
now?: NowConfig;
now?: VercelConfig;
}
export default async function readPackage(file?: string) {

View File

@@ -7,9 +7,9 @@ import { parse } from 'url';
* google.com => google.com
*/
function toHost(url: string) {
function toHost(url: string): string {
if (/^https?:\/\//.test(url)) {
return parse(url).host;
return parse(url).host!;
}
// Remove any path if present

View File

@@ -9,9 +9,6 @@
"lint": "ng lint",
"e2e": "ng e2e"
},
"engines": {
"node": "10.x"
},
"private": true,
"dependencies": {
"@angular/animations": "8.1.0",

View File

@@ -997,10 +997,10 @@ bluebird@^3.3.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
version "4.12.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
bn.js@^5.1.1:
version "5.1.1"
@@ -1066,7 +1066,7 @@ braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
brorand@^1.0.1:
brorand@^1.0.1, brorand@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
@@ -1529,7 +1529,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
commander@^2.11.0, commander@^2.12.1, commander@^2.20.0, commander@~2.20.3:
commander@^2.11.0, commander@^2.12.1, commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -2088,17 +2088,17 @@ electron-to-chromium@^1.3.164, electron-to-chromium@^1.3.413:
integrity sha512-FtEGg/wRKT765QehmpRqMDaUbRam03Y3brE+6nUfoxCr9XgyEl+zAgN4+EraOJeyTGTGh27u7Mdx5Hl9qAhJPQ==
elliptic@^6.0.0, elliptic@^6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762"
integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==
version "6.5.4"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
dependencies:
bn.js "^4.4.0"
brorand "^1.0.1"
bn.js "^4.11.9"
brorand "^1.1.0"
hash.js "^1.0.0"
hmac-drbg "^1.0.0"
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0"
hmac-drbg "^1.0.1"
inherits "^2.0.4"
minimalistic-assert "^1.0.1"
minimalistic-crypto-utils "^1.0.1"
emoji-regex@^7.0.1:
version "7.0.3"
@@ -2842,9 +2842,9 @@ handle-thing@^2.0.0:
integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==
handlebars@^4.0.3:
version "4.7.6"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e"
integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==
version "4.7.7"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
dependencies:
minimist "^1.2.5"
neo-async "^2.6.0"
@@ -2955,7 +2955,7 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
hmac-drbg@^1.0.0:
hmac-drbg@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
@@ -2965,9 +2965,9 @@ hmac-drbg@^1.0.0:
minimalistic-crypto-utils "^1.0.1"
hosted-git-info@^2.1.4, hosted-git-info@^2.6.0, hosted-git-info@^2.7.1:
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
hpack.js@^2.1.6:
version "2.1.6"
@@ -3948,9 +3948,9 @@ lodash.tail@^4.1.1:
integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=
lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.4, lodash@^4.17.5:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
log4js@^4.0.0:
version "4.5.1"
@@ -4181,7 +4181,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
minimalistic-crypto-utils@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
@@ -4330,9 +4330,9 @@ negotiator@0.6.2:
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
neo-async@^2.5.0, neo-async@^2.6.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
nice-try@^1.0.4:
version "1.0.5"
@@ -5192,9 +5192,9 @@ querystring@0.2.0:
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
querystringify@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
version "2.2.0"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
version "2.1.0"
@@ -6109,9 +6109,9 @@ sshpk@^1.7.0:
tweetnacl "~0.14.0"
ssri@^6.0.0, ssri@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
version "6.0.2"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
dependencies:
figgy-pudding "^3.5.1"
@@ -6601,11 +6601,9 @@ typescript@3.4.5:
integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==
uglify-js@^3.1.4:
version "3.9.3"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.3.tgz#4a285d1658b8a2ebaef9e51366b3a0f7acd79ec2"
integrity sha512-r5ImcL6QyzQGVimQoov3aL2ZScywrOgBXGndbWrdehKoSvGe/RmiE5Jpw/v+GvxODt6l2tpBXwA7n+qZVlHBMA==
dependencies:
commander "~2.20.3"
version "3.13.7"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.7.tgz#25468a3b39b1c875df03f0937b2b7036a93f3fee"
integrity sha512-1Psi2MmnZJbnEsgJJIlfnd7tFlJfitusmR7zDI8lXlFI0ACD4/Rm/xdrU8bh6zF0i74aiVoBtkRiFulkrmh3AA==
ultron@~1.1.0:
version "1.1.1"
@@ -6704,9 +6702,9 @@ urix@^0.1.0:
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
url-parse@^1.4.3:
version "1.4.7"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
version "1.5.1"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b"
integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"

View File

@@ -4571,9 +4571,9 @@ hoopy@^0.1.4:
integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==
hosted-git-info@^2.1.4:
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
hpack.js@^2.1.6:
version "2.1.6"
@@ -6056,9 +6056,9 @@ lodash.sortby@^4.7.0:
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.3, lodash@^4.17.4:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loglevel@^1.6.3:
version "1.6.8"
@@ -7324,9 +7324,9 @@ querystring@0.2.0:
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
querystringify@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
version "2.2.0"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
version "2.1.0"
@@ -8207,9 +8207,9 @@ sshpk@^1.7.0:
tweetnacl "~0.14.0"
ssri@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
version "6.0.2"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
dependencies:
figgy-pudding "^3.5.1"
@@ -8903,9 +8903,9 @@ url-loader@1.1.2:
schema-utils "^1.0.0"
url-parse@^1.4.3:
version "1.4.7"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
version "1.5.1"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b"
integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"

View File

@@ -4580,9 +4580,9 @@ hmac-drbg@^1.0.1:
minimalistic-crypto-utils "^1.0.1"
hosted-git-info@^2.1.4:
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
hpack.js@^2.1.6:
version "2.1.6"
@@ -7896,9 +7896,9 @@ querystring@0.2.0:
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
querystringify@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
version "2.2.0"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
raf@^3.4.1:
version "3.4.1"
@@ -8898,9 +8898,9 @@ sshpk@^1.7.0:
tweetnacl "~0.14.0"
ssri@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
version "6.0.2"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
dependencies:
figgy-pudding "^3.5.1"
@@ -9529,9 +9529,9 @@ url-loader@1.1.2:
schema-utils "^1.0.0"
url-parse@^1.4.3:
version "1.4.7"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
version "1.5.1"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b"
integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -1710,7 +1710,9 @@ test(
expectHeader('image/svg+xml'),
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(
200,
toUrl('/test.bmp', 64, 50),
@@ -1718,6 +1720,7 @@ test(
expectHeader('image/bmp'),
fetchOpts('image/webp')
);
*/
// animated gif should bypass: serve as-is
await testPath(
200,

View File

@@ -165,7 +165,7 @@ function mockLoginApi(req, res) {
res.end(JSON.stringify({ token, securityCode }));
} else if (
method === 'GET' &&
pathname === '/now/registration/verify' &&
pathname === '/registration/verify' &&
query.email === email
) {
res.end(JSON.stringify({ token }));
@@ -295,6 +295,7 @@ test('default command should prompt login with empty auth.json', async t => {
test('login', async t => {
t.timeout(ms('1m'));
await fs.remove(getConfigAuthPath());
const loginOutput = await execa(binaryPath, [
'login',
email,

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "10.0.0",
"version": "10.1.1",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -40,7 +40,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "2.10.2",
"@vercel/build-utils": "2.11.1",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

@@ -55,7 +55,7 @@ export interface Deployment {
| 'DEPLOYING'
| 'READY'
| 'ERROR';
createdAt: string;
createdAt: number;
createdIn: string;
env: Dictionary<string>;
build: {

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/frameworks",
"version": "0.3.2",
"version": "0.4.1",
"main": "./dist/frameworks.js",
"types": "./dist/frameworks.d.ts",
"files": [
@@ -20,7 +20,7 @@
"@types/js-yaml": "3.12.1",
"@types/node": "12.0.4",
"@types/node-fetch": "2.5.8",
"@vercel/routing-utils": "1.11.0",
"@vercel/routing-utils": "1.11.2",
"ajv": "6.12.2",
"jest": "24.9.0",
"ts-jest": "24.1.0",

View File

@@ -89,6 +89,7 @@ export const frameworks = [
},
devCommand: {
value: 'next dev --port $PORT',
placeholder: 'next',
},
outputDirectory: {
placeholder: 'Next.js default',
@@ -135,6 +136,7 @@ export const frameworks = [
},
devCommand: {
value: 'gatsby develop --port $PORT',
placeholder: 'gatsby develop',
},
outputDirectory: {
value: 'public',
@@ -222,6 +224,7 @@ export const frameworks = [
},
devCommand: {
value: 'hexo server --port $PORT',
placeholder: 'hexo server',
},
outputDirectory: {
value: 'public',
@@ -261,6 +264,7 @@ export const frameworks = [
},
devCommand: {
value: 'npx @11ty/eleventy --serve --watch --port $PORT',
placeholder: 'npx @11ty/eleventy --serve',
},
outputDirectory: {
value: '_site',
@@ -301,6 +305,7 @@ export const frameworks = [
},
devCommand: {
value: 'docusaurus start --port $PORT',
placeholder: 'docusaurus start',
},
outputDirectory: {
value: 'build',
@@ -393,6 +398,7 @@ export const frameworks = [
},
devCommand: {
value: 'docusaurus-start --port $PORT',
placeholder: 'docusaurus-start',
},
outputDirectory: {
value: 'build',
@@ -445,6 +451,7 @@ export const frameworks = [
},
devCommand: {
value: 'preact watch --port $PORT',
placeholder: 'preact watch',
},
outputDirectory: {
value: 'build',
@@ -495,6 +502,7 @@ export const frameworks = [
},
devCommand: {
value: 'dojo build -m dev -w -s -p $PORT',
placeholder: 'dojo build -m dev -w -s',
},
outputDirectory: {
value: 'output/dist',
@@ -547,6 +555,7 @@ export const frameworks = [
},
devCommand: {
value: 'ember serve --port $PORT',
placeholder: 'ember serve',
},
outputDirectory: {
value: 'dist',
@@ -595,6 +604,7 @@ export const frameworks = [
},
devCommand: {
value: 'vue-cli-service serve --port $PORT',
placeholder: 'vue-cli-service serve',
},
outputDirectory: {
value: 'dist',
@@ -651,6 +661,7 @@ export const frameworks = [
},
devCommand: {
value: 'ng serve --port $PORT',
placeholder: 'ng serve',
},
outputDirectory: {
value: 'dist',
@@ -688,14 +699,14 @@ export const frameworks = [
placeholder: '`npm run build` or `ng build`',
},
devCommand: {
value: 'ng start',
value: 'ng serve',
},
outputDirectory: {
value: 'www',
},
},
dependency: '@ionic/angular',
devCommand: 'ng start',
devCommand: 'ng serve',
buildCommand: 'ng build',
getOutputDirName: async () => 'www',
defaultRoutes: [
@@ -736,6 +747,7 @@ export const frameworks = [
},
devCommand: {
value: 'ng serve --port $PORT',
placeholder: 'ng serve',
},
outputDirectory: {
value: 'dist',
@@ -797,6 +809,7 @@ export const frameworks = [
},
devCommand: {
value: 'polymer serve --port $PORT',
placeholder: 'polymer serve',
},
outputDirectory: {
value: 'build',
@@ -1040,6 +1053,7 @@ export const frameworks = [
},
devCommand: {
value: 'gridsome develop -p $PORT',
placeholder: 'gridsome develop',
},
outputDirectory: {
value: 'dist',
@@ -1078,6 +1092,7 @@ export const frameworks = [
},
devCommand: {
value: 'umi dev --port $PORT',
placeholder: 'umi dev',
},
outputDirectory: {
value: 'dist',
@@ -1125,6 +1140,7 @@ export const frameworks = [
},
devCommand: {
value: 'sapper dev --port $PORT',
placeholder: 'sapper dev',
},
outputDirectory: {
value: '__sapper__/export',
@@ -1163,6 +1179,7 @@ export const frameworks = [
},
devCommand: {
value: 'saber --port $PORT',
placeholder: 'saber',
},
outputDirectory: {
value: 'public',
@@ -1215,6 +1232,7 @@ export const frameworks = [
},
devCommand: {
value: 'stencil build --dev --watch --serve --port $PORT',
placeholder: 'stencil build --dev --watch --serve',
},
outputDirectory: {
value: 'www',
@@ -1338,6 +1356,7 @@ export const frameworks = [
},
devCommand: {
value: 'yarn rw dev --fwd="--port=$PORT --open=false"',
placeholder: 'yarn rw dev',
},
outputDirectory: {
placeholder: 'RedwoodJS default',
@@ -1383,6 +1402,7 @@ export const frameworks = [
},
devCommand: {
value: 'hugo server -D -w -p $PORT',
placeholder: 'hugo server -D',
},
outputDirectory: {
placeholder: '`public` or `publishDir` from the `config` file',
@@ -1428,6 +1448,7 @@ export const frameworks = [
},
devCommand: {
value: 'bundle exec jekyll serve --watch --port $PORT',
placeholder: 'bundle exec jekyll serve',
},
outputDirectory: {
placeholder: '`_site` or `destination` from `_config.yml`',
@@ -1470,6 +1491,7 @@ export const frameworks = [
},
devCommand: {
value: 'brunch watch --server --port $PORT',
placeholder: 'brunch watch --server',
},
outputDirectory: {
value: 'public',
@@ -1505,6 +1527,7 @@ export const frameworks = [
},
devCommand: {
value: 'bundle exec middleman server -p $PORT',
placeholder: 'bundle exec middleman server',
},
outputDirectory: {
value: 'build',
@@ -1541,6 +1564,7 @@ export const frameworks = [
},
devCommand: {
value: 'zola serve --port $PORT',
placeholder: 'zola serve',
},
outputDirectory: {
value: 'public',

View File

@@ -27,6 +27,7 @@ export interface SettingValue {
* @example "next dev --port $PORT"
*/
value: string;
placeholder?: string;
}
export type Setting = SettingValue | SettingPlaceholder;

View File

@@ -36,6 +36,9 @@ const SchemaSettings = {
value: {
type: 'string',
},
placeholder: {
type: 'string',
},
},
},
{

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "1.10.0",
"version": "1.11.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -33,7 +33,7 @@
"@types/etag": "1.8.0",
"@types/test-listen": "1.1.0",
"@vercel/ncc": "0.24.0",
"@vercel/nft": "0.10.0",
"@vercel/nft": "0.13.1",
"content-type": "1.0.4",
"cookie": "0.4.0",
"etag": "1.8.1",

View File

@@ -1,5 +1,3 @@
{
"engines": {
"node": "10.x"
}
"private": true
}

View File

@@ -1,5 +1,3 @@
{
"engines": {
"node": "10.x"
}
"private": true
}

View File

@@ -26,7 +26,7 @@ async function getResult(url) {
}
module.exports = async (req, res) => {
const result = await getResult('https://vercel.com/about');
const result = await getResult('https://vercel.com/docs');
if (req && result && result.lhr && result.lhr.categories) {
res.end('lighthouse:RANDOMNESS_PLACEHOLDER');
} else {

View File

@@ -1,10 +1,10 @@
{
"engines": {
"node": "10.x"
"node": "14.x"
},
"dependencies": {
"chrome-aws-lambda": "1.20.4",
"lighthouse": "5.6.0",
"puppeteer-core": "1.20.0"
"chrome-aws-lambda": "7.0.0",
"lighthouse": "7.4.0",
"puppeteer-core": "7.0.0"
}
}

View File

@@ -7,7 +7,8 @@
"config": {
"functions": {
"**/*.js": {
"memory": 3008
"memory": 3008,
"maxDuration": 30
}
}
}

View File

@@ -3,8 +3,5 @@
"dependencies": {
"@builders-transpiled-test/lib": "*"
},
"engines": {
"node": "10.x"
},
"version": "1.0.0"
}

View File

@@ -3,8 +3,5 @@
"dependencies": {
"@builders-typescript-test/lib": "*"
},
"engines": {
"node": "10.x"
},
"version": "1.0.0"
}

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