Compare commits

..

29 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
58 changed files with 749 additions and 751 deletions

56
LICENSE
View File

@@ -1,10 +1,11 @@
Apache License Apache License
Version 2.0, January 2004 Version 2.0, January 2004
https://www.apache.org/licenses/ http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions. 1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, "License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document. and distribution as defined by Sections 1 through 9 of this document.
@@ -63,14 +64,14 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
on behalf of whom a Contribution has been received by Licensor and on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work. subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of 2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual, this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of, copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form. Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of 3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual, this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made, (except as stated in this section) patent license to make, have made,
@@ -86,7 +87,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
granted to You under this License for that Work shall terminate granted to You under this License for that Work shall terminate
as of the date such litigation is filed. as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the 4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You modifications, and in Source or Object form, provided that You
meet the following conditions: meet the following conditions:
@@ -127,7 +128,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
reproduction, and distribution of the Work otherwise complies with reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License. the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, 5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions. this License, without any additional terms or conditions.
@@ -135,12 +136,12 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
the terms of any separate license agreement you may have executed the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions. with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade 6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor, names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file. origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or 7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS, Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
@@ -150,7 +151,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
appropriateness of using or redistributing the Work and assume any appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License. risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, 8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise, whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be negligent acts) or agreed to in writing, shall any Contributor be
@@ -162,7 +163,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
other commercial damages or losses), even if such Contributor other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages. has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing 9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer, the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity, and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this or other liability obligations and/or rights consistent with this
@@ -173,18 +174,29 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
incurred by, or claims asserted against, such Contributor by reason incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability. of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
Copyright 2017 Vercel, Inc. APPENDIX: How to apply the Apache License to your work.
Licensed under the Apache License, Version 2.0 (the "License"); To apply the Apache License to your work, attach the following
you may not use this file except in compliance with the License. boilerplate notice, with the fields enclosed by brackets "[]"
You may obtain a copy of the License at 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.
https://www.apache.org/licenses/LICENSE-2.0 Copyright 2017 Vercel, Inc.
Unless required by applicable law or agreed to in writing, software Licensed under the Apache License, Version 2.0 (the "License");
distributed under the License is distributed on an "AS IS" BASIS, you may not use this file except in compliance with the License.
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. You may obtain a copy of the License at
See the License for the specific language governing permissions and
limitations under the License. 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

@@ -2,7 +2,7 @@
"name": "vercel-monorepo", "name": "vercel-monorepo",
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"license": "MIT", "license": "Apache-2.0",
"workspaces": { "workspaces": {
"packages": [ "packages": [
"packages/*" "packages/*"

View File

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

View File

@@ -1 +0,0 @@
handler

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "vercel", "name": "vercel",
"version": "22.0.2-canary.6", "version": "23.0.1",
"preferGlobal": true, "preferGlobal": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "The command-line interface for Vercel", "description": "The command-line interface for Vercel",
@@ -17,8 +17,8 @@
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose", "test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
"prepublishOnly": "yarn build", "prepublishOnly": "yarn build",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov", "coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"build": "ts-node ./scripts/build.ts", "build": "node -r ts-eager/register ./scripts/build.ts",
"build-dev": "ts-node ./scripts/build.ts --dev" "build-dev": "node -r ts-eager/register ./scripts/build.ts --dev"
}, },
"nyc": { "nyc": {
"include": [ "include": [
@@ -61,10 +61,10 @@
"node": ">= 12" "node": ">= 12"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "2.10.3-canary.4", "@vercel/build-utils": "2.11.1",
"@vercel/go": "1.2.2", "@vercel/go": "1.2.2",
"@vercel/node": "1.10.1-canary.2", "@vercel/node": "1.11.1",
"@vercel/python": "2.0.2", "@vercel/python": "2.0.4",
"@vercel/ruby": "1.2.6", "@vercel/ruby": "1.2.6",
"update-notifier": "4.1.0" "update-notifier": "4.1.0"
}, },
@@ -89,7 +89,7 @@
"@types/mri": "1.1.0", "@types/mri": "1.1.0",
"@types/ms": "0.7.30", "@types/ms": "0.7.30",
"@types/node": "11.11.0", "@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/npm-package-arg": "6.1.0",
"@types/pluralize": "0.0.29", "@types/pluralize": "0.0.29",
"@types/progress": "2.0.3", "@types/progress": "2.0.3",
@@ -100,7 +100,7 @@
"@types/universal-analytics": "0.4.2", "@types/universal-analytics": "0.4.2",
"@types/which": "1.3.2", "@types/which": "1.3.2",
"@types/write-json-file": "2.2.1", "@types/write-json-file": "2.2.1",
"@vercel/frameworks": "0.3.3-canary.3", "@vercel/frameworks": "0.4.1",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2", "@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2", "@zeit/source-map-support": "0.6.2",
@@ -117,7 +117,7 @@
"chalk": "4.1.0", "chalk": "4.1.0",
"chokidar": "3.3.1", "chokidar": "3.3.1",
"clipboardy": "2.1.0", "clipboardy": "2.1.0",
"codecov": "3.7.1", "codecov": "3.8.2",
"cpy": "7.2.0", "cpy": "7.2.0",
"credit-card": "3.0.1", "credit-card": "3.0.1",
"date-fns": "1.29.0", "date-fns": "1.29.0",
@@ -148,7 +148,7 @@
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"npm-package-arg": "6.1.0", "npm-package-arg": "6.1.0",
"nyc": "13.2.0", "nyc": "13.2.0",
"open": "8.0.2", "open": "8.2.0",
"ora": "3.4.0", "ora": "3.4.0",
"pcre-to-regexp": "1.0.0", "pcre-to-regexp": "1.0.0",
"pluralize": "7.0.0", "pluralize": "7.0.0",
@@ -169,6 +169,7 @@
"title": "3.4.1", "title": "3.4.1",
"tmp-promise": "1.0.3", "tmp-promise": "1.0.3",
"tree-kill": "1.2.2", "tree-kill": "1.2.2",
"ts-eager": "2.0.2",
"ts-node": "8.3.0", "ts-node": "8.3.0",
"typescript": "3.9.3", "typescript": "3.9.3",
"universal-analytics": "0.4.20", "universal-analytics": "0.4.20",

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,7 +12,6 @@ import { getCommandName, getPkgName } from '../util/pkg-name';
import getGlobalPathConfig from '../util/config/global-path'; import getGlobalPathConfig from '../util/config/global-path';
import { writeToAuthConfigFile, writeToConfigFile } from '../util/config/files'; import { writeToAuthConfigFile, writeToConfigFile } from '../util/config/files';
import Client from '../util/client'; import Client from '../util/client';
import { LoginParams } from '../util/login/types';
const help = () => { const help = () => {
console.log(` console.log(`
@@ -46,7 +45,7 @@ const help = () => {
export default async function login(client: Client): Promise<number> { export default async function login(client: Client): Promise<number> {
let argv; let argv;
const { apiUrl, output } = client; const { output } = client;
try { try {
argv = getArgs(client.argv.slice(2)); argv = getArgs(client.argv.slice(2));
@@ -68,18 +67,17 @@ export default async function login(client: Client): Promise<number> {
const input = argv._[1]; const input = argv._[1];
let result: number | string = 1; let result: number | string = 1;
const params: LoginParams = { output, apiUrl };
if (input) { if (input) {
// Email or Team slug was provided via command line // Email or Team slug was provided via command line
if (validateEmail(input)) { if (validateEmail(input)) {
result = await doEmailLogin(input, params); result = await doEmailLogin(client, input);
} else { } else {
result = await doSsoLogin(input, params); result = await doSsoLogin(client, input);
} }
} else { } else {
// Interactive mode // Interactive mode
result = await prompt(params); result = await prompt(client);
} }
// The login function failed, so it returned an exit code // The login function failed, so it returned an exit code

View File

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

View File

@@ -187,25 +187,22 @@ async function run({ client, contextName }) {
const name = args[0]; const name = args[0];
// Check the existence of the project
try {
await client.fetch(`/projects/info/${e(name)}`);
} catch (err) {
if (err.status === 404) {
console.error(error('No such project exists'));
return exit(1);
}
}
const yes = await readConfirmation(name); const yes = await readConfirmation(name);
if (!yes) { if (!yes) {
console.error(error('User abort')); console.error(error('User abort'));
return exit(0); return exit(0);
} }
await client.fetch(`/v2/projects/${name}`, { try {
await client.fetch(`/v2/projects/${e(name)}`, {
method: 'DELETE', method: 'DELETE',
}); });
} catch (err) {
if (err.status === 404) {
console.error(error('No such project exists'));
return exit(1);
}
}
const elapsed = ms(new Date() - start); const elapsed = ms(new Date() - start);
console.log( console.log(
`${chalk.cyan('> Success!')} Project ${chalk.bold( `${chalk.cyan('> Success!')} Project ${chalk.bold(

View File

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

View File

@@ -3,9 +3,10 @@ import chalk from 'chalk';
// Utilities // Utilities
import Client from '../../util/client'; import Client from '../../util/client';
import listInput from '../../util/input/list'; import { emoji } from '../../util/emoji';
import getUser from '../../util/get-user'; import getUser from '../../util/get-user';
import getTeams from '../../util/get-teams'; import getTeams from '../../util/get-teams';
import listInput from '../../util/input/list';
import { Team, GlobalConfig } from '../../types'; import { Team, GlobalConfig } from '../../types';
import { writeToConfigFile } from '../../util/config/files'; import { writeToConfigFile } from '../../util/config/files';
@@ -31,7 +32,7 @@ export default async function main(client: Client, desiredSlug?: string) {
: teams.find(team => team.id === config.currentTeam); : teams.find(team => team.id === config.currentTeam);
if (!personalScopeSelected && !currentTeam) { if (!personalScopeSelected && !currentTeam) {
output.error(`You are not a part of the current team anymore.`); output.error(`You are not a member of the current team anymore.`);
return 1; return 1;
} }
@@ -41,30 +42,39 @@ export default async function main(client: Client, desiredSlug?: string) {
.sort((a, b) => { .sort((a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase()); return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
}) })
.map(({ id, slug, name }) => { .map(team => {
let title = `${name} (${slug})`; let title = `${team.name} (${team.slug})`;
const selected = id === currentTeam?.id; const selected = team.id === currentTeam?.id;
if (selected) { if (selected) {
title += ` ${chalk.bold('(current)')}`; title += ` ${chalk.bold('(current)')}`;
} }
if (team.limited) {
title += ` ${emoji('locked')}`;
}
return { return {
name: title, name: title,
value: slug, value: team.slug,
short: slug, short: team.slug,
selected, selected,
}; };
}); });
// Add the User scope entry at the top // Add the User scope entry at the top
const suffix = personalScopeSelected ? ` ${chalk.bold('(current)')}` : ''; let suffix = personalScopeSelected ? ` ${chalk.bold('(current)')}` : '';
// SAML tokens can not interact with the user scope
if (user.limited) {
suffix += ` ${emoji('locked')}`;
}
const choices = [ const choices = [
{ separator: 'Personal Account' }, { separator: 'Personal Account' },
{ {
name: `${user.email} (${user.username})${suffix}`, name: `${user.name || user.email} (${user.username})${suffix}`,
value: user.email, value: user.username,
short: user.username, short: user.username,
selected: personalScopeSelected, selected: personalScopeSelected,
}, },
@@ -93,9 +103,18 @@ export default async function main(client: Client, desiredSlug?: string) {
return 0; return 0;
} }
if (user.limited) {
await client.reauthenticate({
scope: user.username,
teamId: null,
});
}
updateCurrentTeam(config); updateCurrentTeam(config);
output.success(`Your account (${chalk.bold(desiredSlug)}) is now active!`); output.success(
`Your account (${chalk.bold(user.username)}) is now active!`
);
return 0; return 0;
} }
@@ -114,6 +133,15 @@ export default async function main(client: Client, desiredSlug?: string) {
return 0; 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); updateCurrentTeam(config, newTeam);
output.success( output.success(

View File

@@ -1,5 +1,22 @@
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 { export interface AuthConfig {
token: string; token?: string;
skipWrite?: boolean; skipWrite?: boolean;
} }
@@ -45,18 +62,26 @@ export type User = {
updatedAt: number; updatedAt: number;
}; };
name?: string; name?: string;
limited?: boolean;
}; };
export type Team = { export interface Team {
id: string; id: string;
avatar?: string; avatar?: string | null;
billing: Billing; billing: Billing;
created: string; created: string;
creatorId: string; creatorId: string;
membership: { uid: string; role: 'MEMBER' | 'OWNER'; created: number }; membership: { uid: string; role: 'MEMBER' | 'OWNER'; created: number };
name: string; name: string;
slug: string; slug: string;
}; limited?: boolean;
saml?: {
enforced: boolean;
connection?: {
state: string;
};
};
}
export type Domain = { export type Domain = {
id: string; id: string;
@@ -120,7 +145,7 @@ export type Deployment = {
export type Alias = { export type Alias = {
uid: string; uid: string;
alias: string; alias: string;
created: string; createdAt: number;
deployment: { deployment: {
id: string; id: string;
url: string; url: string;
@@ -268,3 +293,13 @@ export type ProjectLinkResult =
| { status: 'linked'; org: Org; project: Project } | { status: 'linked'; org: Org; project: Project }
| { status: 'not_linked'; org: null; project: null } | { status: 'not_linked'; org: null; project: null }
| { status: 'error'; exitCode: number }; | { 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 { Output } from '../output';
import { Alias } from '../../types'; import { Alias } from '../../types';
import Client from '../client'; import Client from '../client';
export default async function findAliasByAliasOrId( export default async function findAliasByAliasOrId(
@@ -11,7 +12,6 @@ export default async function findAliasByAliasOrId(
`/now/aliases/${encodeURIComponent(getSafeAlias(aliasOrId))}` `/now/aliases/${encodeURIComponent(getSafeAlias(aliasOrId))}`
); );
} }
function getSafeAlias(alias: string) { function getSafeAlias(alias: string) {
return alias return alias
.replace(/^https:\/\//i, '') .replace(/^https:\/\//i, '')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ export const emojiLabels = {
link: '🔗', link: '🔗',
inspect: '🔍', inspect: '🔍',
success: '✅', success: '✅',
locked: '🔒',
} as const; } as const;
export type EmojiLabel = keyof typeof emojiLabels; export type EmojiLabel = keyof typeof emojiLabels;

View File

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

View File

@@ -9,5 +9,5 @@ export default function doBitbucketLogin(params: LoginParams) {
// cookie that the OAuth callback URL depends on // cookie that the OAuth callback URL depends on
'https://vercel.com' 'https://vercel.com'
); );
return doOauthLogin(url, 'Bitbucket', params); return doOauthLogin(params, url, 'Bitbucket');
} }

View File

@@ -7,8 +7,8 @@ import executeLogin from './login';
import { LoginParams } from './types'; import { LoginParams } from './types';
export default async function doEmailLogin( export default async function doEmailLogin(
email: string, params: LoginParams,
params: LoginParams email: string
): Promise<number | string> { ): Promise<number | string> {
let securityCode; let securityCode;
let verificationToken; let verificationToken;
@@ -42,7 +42,7 @@ export default async function doEmailLogin(
while (!token) { while (!token) {
try { try {
await sleep(ms('1s')); await sleep(ms('1s'));
token = await verify(email, verificationToken, params); token = await verify(email, verificationToken, 'Email', params);
} catch (err) { } catch (err) {
if (err.message !== 'Confirmation incomplete') { if (err.message !== 'Confirmation incomplete') {
output.error(err.message); output.error(err.message);

View File

@@ -9,5 +9,5 @@ export default function doGithubLogin(params: LoginParams) {
// cookie that the OAuth callback URL depends on // cookie that the OAuth callback URL depends on
'https://vercel.com' 'https://vercel.com'
); );
return doOauthLogin(url, 'GitHub', params); return doOauthLogin(params, url, 'GitHub');
} }

View File

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

View File

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

View File

@@ -2,35 +2,29 @@ import http from 'http';
import open from 'open'; import open from 'open';
import { URL } from 'url'; import { URL } from 'url';
import listen from 'async-listen'; import listen from 'async-listen';
import { hostname } from 'os';
import { LoginParams } from './types'; import { LoginParams } from './types';
import prompt from './prompt'; import prompt from './prompt';
import verify from './verify'; import verify from './verify';
import { getTitleName } from '../pkg-name';
import highlight from '../output/highlight'; import highlight from '../output/highlight';
import link from '../output/link';
import eraseLines from '../output/erase-lines';
export default async function doOauthLogin( export default async function doOauthLogin(
params: LoginParams,
url: URL, url: URL,
provider: string, provider: string
params: LoginParams
): Promise<number | string> { ): Promise<number | string> {
const { output } = params; const { output } = params;
output.spinner(
`Please complete the ${provider} authentication in your web browser`
);
const server = http.createServer(); const server = http.createServer();
const address = await listen(server, 0, '127.0.0.1'); const address = await listen(server, 0, '127.0.0.1');
const { port } = new URL(address); const { port } = new URL(address);
url.searchParams.append('mode', 'login'); url.searchParams.set('mode', 'login');
url.searchParams.append('next', `http://localhost:${port}`); url.searchParams.set('next', `http://localhost:${port}`);
// Append token name param output.log(`Please visit the following URL in your web browser:`);
const hyphens = new RegExp('-', 'g'); output.log(link(url.href));
const host = hostname().replace(hyphens, ' ').replace('.local', ''); output.spinner(`Waiting for ${provider} authentication to be completed`);
const tokenName = `${getTitleName()} CLI on ${host} via ${provider}`;
url.searchParams.append('tokenName', tokenName);
try { try {
const [query] = await Promise.all([ const [query] = await Promise.all([
@@ -78,6 +72,9 @@ export default async function doOauthLogin(
open(url.href), open(url.href),
]); ]);
output.stopSpinner();
output.print(eraseLines(3));
const loginError = query.get('loginError'); const loginError = query.get('loginError');
if (loginError) { if (loginError) {
const err = JSON.parse(loginError); const err = JSON.parse(loginError);
@@ -106,7 +103,7 @@ export default async function doOauthLogin(
} }
output.spinner('Verifying authentication token'); output.spinner('Verifying authentication token');
const token = await verify(email, verificationToken, params); const token = await verify(email, verificationToken, provider, params);
output.success( output.success(
`${provider} authentication complete for ${highlight(email)}` `${provider} authentication complete for ${highlight(email)}`
); );

View File

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

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

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

View File

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

View File

@@ -1,24 +1,41 @@
import { URL } from 'url'; import { URL } from 'url';
import fetch from 'node-fetch'; import fetch, { Headers } from 'node-fetch';
import ua from '../ua'; import ua from '../ua';
import { LoginParams } from './types'; import { LoginParams } from './types';
import { hostname } from 'os';
import { getTitleName } from '../pkg-name';
export default async function verify( export default async function verify(
email: string, email: string,
verificationToken: string, verificationToken: string,
{ apiUrl, ssoUserId }: LoginParams provider: string,
{ authConfig, apiUrl, ssoUserId }: LoginParams
): Promise<string> { ): Promise<string> {
const url = new URL('/registration/verify', apiUrl); const url = new URL('/registration/verify', apiUrl);
url.searchParams.append('email', email); url.searchParams.set('email', email);
url.searchParams.append('token', verificationToken); url.searchParams.set('token', verificationToken);
if (ssoUserId) {
url.searchParams.append('ssoUserId', ssoUserId); 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);
} }
const res = await fetch(url.href, { // If `ssoUserId` is defined then this verification
headers: { 'User-Agent': ua }, // 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(); const body = await res.json();
if (!res.ok) { if (!res.ok) {

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

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

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'), expectHeader('image/svg+xml'),
fetchOpts('image/webp') fetchOpts('image/webp')
); );
// bmp should bypass: serve as-is /* Disabled bmp because `next dev` bypasses
* and production will convert. Eventually
* we can enable once `next dev` supports it.
await testPath( await testPath(
200, 200,
toUrl('/test.bmp', 64, 50), toUrl('/test.bmp', 64, 50),
@@ -1718,6 +1720,7 @@ test(
expectHeader('image/bmp'), expectHeader('image/bmp'),
fetchOpts('image/webp') fetchOpts('image/webp')
); );
*/
// animated gif should bypass: serve as-is // animated gif should bypass: serve as-is
await testPath( await testPath(
200, 200,

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/python", "name": "@vercel/python",
"version": "2.0.2", "version": "2.0.4",
"main": "./dist/index.js", "main": "./dist/index.js",
"license": "MIT", "license": "MIT",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",

View File

@@ -82,14 +82,27 @@ elif 'app' in __vc_variables:
not inspect.iscoroutinefunction(__vc_module.app.__call__) not inspect.iscoroutinefunction(__vc_module.app.__call__)
): ):
print('using Web Server Gateway Interface (WSGI)') print('using Web Server Gateway Interface (WSGI)')
from io import BytesIO
from urllib.parse import urlparse from urllib.parse import urlparse
from werkzeug._compat import BytesIO
from werkzeug._compat import string_types
from werkzeug._compat import to_bytes
from werkzeug._compat import wsgi_encoding_dance
from werkzeug.datastructures import Headers from werkzeug.datastructures import Headers
from werkzeug.wrappers import Response from werkzeug.wrappers import Response
string_types = (str,)
def to_bytes(x, charset=sys.getdefaultencoding(), errors="strict"):
if x is None:
return None
if isinstance(x, (bytes, bytearray, memoryview)):
return bytes(x)
if isinstance(x, str):
return x.encode(charset, errors)
raise TypeError("Expected bytes")
def wsgi_encoding_dance(s, charset="utf-8", errors="replace"):
if isinstance(s, str):
s = s.encode(charset)
return s.decode("latin1", errors)
def vc_handler(event, context): def vc_handler(event, context):
payload = json.loads(event['body']) payload = json.loads(event['body'])

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/routing-utils", "name": "@vercel/routing-utils",
"version": "1.11.1-canary.1", "version": "1.11.2",
"description": "Vercel routing utilities", "description": "Vercel routing utilities",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",

View File

@@ -37,9 +37,9 @@ function getCheckAndContinue(
route route
)}` )}`
); );
} else if (route.check) { } else if (route.check && !route.override) {
checks.push(route); checks.push(route);
} else if (route.continue) { } else if (route.continue && !route.override) {
continues.push(route); continues.push(route);
} else { } else {
others.push(route); others.push(route);

View File

@@ -104,6 +104,9 @@ export const routesSchema = {
continue: { continue: {
type: 'boolean', type: 'boolean',
}, },
override: {
type: 'boolean',
},
check: { check: {
type: 'boolean', type: 'boolean',
}, },

View File

@@ -187,6 +187,10 @@ function collectHasSegments(has?: HasField) {
const hasSegments = new Set<string>(); const hasSegments = new Set<string>();
for (const hasItem of has || []) { for (const hasItem of has || []) {
if ('key' in hasItem && hasItem.type === 'header') {
hasItem.key = hasItem.key.toLowerCase();
}
if (!hasItem.value && 'key' in hasItem) { if (!hasItem.value && 'key' in hasItem) {
hasSegments.add(hasItem.key); hasSegments.add(hasItem.key);
} }

View File

@@ -27,6 +27,7 @@ export type Source = {
headers?: { [name: string]: string }; headers?: { [name: string]: string };
methods?: string[]; methods?: string[];
continue?: boolean; continue?: boolean;
override?: boolean;
check?: boolean; check?: boolean;
important?: boolean; important?: boolean;
status?: number; status?: number;

View File

@@ -419,3 +419,97 @@ test('mergeRoutes ensure `handle: error` comes last', () => {
]; ];
deepStrictEqual(actual, expected); deepStrictEqual(actual, expected);
}); });
test('mergeRoutes ensure beforeFiles comes after redirects (continue)', () => {
const userRoutes = [];
const builds = [
{
use: '@vercel/next',
entrypoint: 'package.json',
routes: [
{
src: '^/home$',
status: 301,
headers: {
Location: '/',
},
},
{
src: '^/hello$',
dest: '/somewhere',
continue: true,
override: true,
},
{
handle: 'filesystem',
},
{
src: '^/404$',
dest: '/404',
status: 404,
check: true,
},
],
},
];
const actual = mergeRoutes({ userRoutes, builds });
const expected = [
{ src: '^/home$', status: 301, headers: { Location: '/' } },
{
src: '^/hello$',
dest: '/somewhere',
continue: true,
override: true,
},
{ handle: 'filesystem' },
{ src: '^/404$', dest: '/404', status: 404, check: true },
];
deepStrictEqual(actual, expected);
});
test('mergeRoutes ensure beforeFiles comes after redirects (check)', () => {
const userRoutes = [];
const builds = [
{
use: '@vercel/next',
entrypoint: 'package.json',
routes: [
{
src: '^/home$',
status: 301,
headers: {
Location: '/',
},
},
{
src: '^/hello$',
dest: '/somewhere',
check: true,
override: true,
},
{
handle: 'filesystem',
},
{
src: '^/404$',
dest: '/404',
status: 404,
check: true,
},
],
},
];
const actual = mergeRoutes({ userRoutes, builds });
const expected = [
{ src: '^/home$', status: 301, headers: { Location: '/' } },
{
src: '^/hello$',
dest: '/somewhere',
check: true,
override: true,
},
{ handle: 'filesystem' },
{ src: '^/404$', dest: '/404', status: 404, check: true },
];
deepStrictEqual(actual, expected);
});

View File

@@ -247,7 +247,7 @@ test('convertRedirects', () => {
}, },
{ {
type: 'header', type: 'header',
key: 'x-pathname', key: 'X-Pathname',
value: '(?<another>hello|world)', value: '(?<another>hello|world)',
}, },
], ],
@@ -549,7 +549,7 @@ test('convertRewrites', () => {
}, },
{ {
type: 'header', type: 'header',
key: 'x-pathname', key: 'X-Pathname',
value: '(?<another>hello|world)', value: '(?<another>hello|world)',
}, },
], ],
@@ -893,7 +893,7 @@ test('convertHeaders', () => {
}, },
{ {
type: 'header', type: 'header',
key: 'x-pathname', key: 'X-Pathname',
value: '(?<another>hello|world)', value: '(?<another>hello|world)',
}, },
], ],

View File

@@ -1,127 +0,0 @@
package bridge
import (
"bytes"
"encoding/base64"
"encoding/json"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"net/http"
"strconv"
"strings"
)
type Request struct {
Host string `json:"host"`
Path string `json:"path"`
Method string `json:"method"`
Headers map[string]string `json:"headers"`
Encoding string `json:"encoding,omitempty"`
Body string `json:"body"`
}
type Response struct {
StatusCode int `json:"statusCode"`
Headers map[string][]string `json:"headers"`
Encoding string `json:"encoding,omitemtpy"`
Body string `json:"body"`
}
type ResponseWriter struct {
http.ResponseWriter
statusCode int
headers http.Header
body *bytes.Buffer
}
func (w *ResponseWriter) Header() http.Header {
return w.headers
}
func (w *ResponseWriter) Write(p []byte) (n int, err error) {
n, err = w.body.Write(p)
return
}
func (w *ResponseWriter) WriteHeader(statusCode int) {
w.statusCode = statusCode
}
var userHandler http.Handler
func Serve(handler http.Handler, req *Request) (res Response, err error) {
var body []byte
if req.Encoding == "base64" {
body, err = base64.StdEncoding.DecodeString(req.Body)
if err != nil {
return
}
} else {
body = []byte(req.Body)
}
r, err := http.NewRequest(req.Method, req.Path, bytes.NewReader(body))
if err != nil {
return
}
for k, v := range req.Headers {
r.Header.Add(k, v)
switch strings.ToLower(k) {
case "host":
// we need to set `Host` in the request
// because Go likes to ignore the `Host` header
// see https://github.com/golang/go/issues/7682
r.Host = v
case "content-length":
contentLength, _ := strconv.ParseInt(v, 10, 64)
r.ContentLength = contentLength
case "x-forwarded-for":
case "x-real-ip":
r.RemoteAddr = v
}
}
var bodyBuf bytes.Buffer
w := &ResponseWriter{
nil,
http.StatusOK,
make(http.Header),
&bodyBuf,
}
handler.ServeHTTP(w, r)
defer r.Body.Close()
headers := make(map[string][]string)
for k, v := range w.headers {
for _, s := range v {
headers[k] = append(headers[k], s)
}
}
res = Response{
StatusCode: w.statusCode,
Headers: headers,
Encoding: "base64",
Body: base64.StdEncoding.EncodeToString(bodyBuf.Bytes()),
}
return
}
// Maps the `APIGatewayProxyRequest` to a `Request` instance and invokes `Serve()`
func handler(event events.APIGatewayProxyRequest) (res Response, err error) {
var req Request
err = json.Unmarshal([]byte(event.Body), &req)
if err != nil {
return
}
res, err = Serve(userHandler, &req)
return
}
// Starts the Lambda
func Start(h http.Handler) {
userHandler = h
lambda.Start(handler)
}

View File

@@ -1,43 +0,0 @@
package bridge
import (
"encoding/base64"
"fmt"
"net/http"
"testing"
)
type HttpHandler struct {
http.Handler
t *testing.T
}
func (h *HttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-Foo", "bar")
w.WriteHeader(404)
w.Write([]byte("test"))
}
func TestServe(t *testing.T) {
h := &HttpHandler{nil, t}
req := &Request{
"test.com",
"/path?foo=bar",
"POST",
map[string]string{"Content-Length": "1", "X-Foo": "bar"},
"",
"a",
}
res, err := Serve(h, req)
if err != nil {
t.Fail()
}
if res.StatusCode != 404 {
t.Fail()
}
fmt.Printf("status code: %d\n", res.StatusCode)
fmt.Printf("header: %v\n", res.Headers)
fmt.Printf("base64 body: %s\n", res.Body)
body, err := base64.StdEncoding.DecodeString(res.Body)
fmt.Printf("body: %s\n", body)
}

1
utils/run.js vendored
View File

@@ -6,7 +6,6 @@ const allPackages = [
'routing-utils', 'routing-utils',
'frameworks', 'frameworks',
'build-utils', 'build-utils',
'cgi',
'client', 'client',
'node-bridge', 'node-bridge',
'node', 'node',

104
yarn.lock
View File

@@ -1860,12 +1860,13 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/node-fetch@2.1.4": "@types/node-fetch@2.5.10":
version "2.1.4" version "2.5.10"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.1.4.tgz#093d1beae11541aef25999d70aa09286fd025b1a" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.10.tgz#9b4d4a0425562f9fcea70b12cb3fcdd946ca8132"
integrity sha512-tR1ekaXUGpmzOcDXWU9BW73YfA2/VW1DF1FH+wlJ82BbCSnWTbdX+JkqWQXWKIGsFPnPsYadbXfNgz28g+ccWg== integrity sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ==
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
form-data "^3.0.0"
"@types/node-fetch@2.5.4": "@types/node-fetch@2.5.4":
version "2.5.4" version "2.5.4"
@@ -2115,13 +2116,13 @@
resolved "https://registry.yarnpkg.com/@vercel/ncc/-/ncc-0.24.0.tgz#a2e8783a185caa99b5d8961a57dfc9665de16296" resolved "https://registry.yarnpkg.com/@vercel/ncc/-/ncc-0.24.0.tgz#a2e8783a185caa99b5d8961a57dfc9665de16296"
integrity sha512-crqItMcIwCkvdXY/V3/TzrHJQx6nbIaRqE1cOopJhgGX6izvNov40SmD//nS5flfEvdK54YGjwVVq+zG6crjOg== integrity sha512-crqItMcIwCkvdXY/V3/TzrHJQx6nbIaRqE1cOopJhgGX6izvNov40SmD//nS5flfEvdK54YGjwVVq+zG6crjOg==
"@vercel/nft@0.12.2": "@vercel/nft@0.13.1":
version "0.12.2" version "0.13.1"
resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.12.2.tgz#67ea9f231d24639b3783e3e69bef173659972d3b" resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.13.1.tgz#98df07e04620069ba63fff92af490c5842a2f31f"
integrity sha512-H8n44GboVnJaVVX4+WfuOTAaNLDnUIYH4KpMZcXll7KMNIcg0JTd0IFRsIBe/uvuXisqm6nEANp8Tr3/1dlRQw== integrity sha512-7pBTfSkwhhcPAeGVsFml5YX7LCZgtocP+zTAknnRK2u/RsV3GGqOD5yw7CtbgTpfjY8NfXWzwoxF1zOUEVsbww==
dependencies: dependencies:
"@mapbox/node-pre-gyp" "^1.0.5" "@mapbox/node-pre-gyp" "^1.0.5"
acorn "^8.1.0" acorn "^8.3.0"
acorn-class-fields "^1.0.0" acorn-class-fields "^1.0.0"
acorn-static-class-features "^1.0.0" acorn-static-class-features "^1.0.0"
bindings "^1.4.0" bindings "^1.4.0"
@@ -2282,10 +2283,10 @@ acorn@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe"
integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ== integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==
acorn@^8.1.0: acorn@^8.3.0:
version "8.1.1" version "8.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.1.1.tgz#fb0026885b9ac9f48bac1e185e4af472971149ff" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.3.0.tgz#1193f9b96c4e8232f00b11a9edff81b2c8b98b88"
integrity sha512-xYiIVjNuqtKXMxlRMDc6mZUhXehod4a3gbZ1qRlM7icK4EbxUFNLhWoPblCvFtB2Y9CIqHP3CF/rdxLItaQv8g== integrity sha512-tqPKHZ5CaBJw0Xmy0ZZvLs1qTV+BNFSyvn77ASXkpBNfIRk8ev26fKrD9iLGwGA9zedPao52GSHzq8lyZG0NUw==
agent-base@4, agent-base@^4.1.0, agent-base@^4.3.0: agent-base@4, agent-base@^4.1.0, agent-base@^4.3.0:
version "4.3.0" version "4.3.0"
@@ -2294,11 +2295,6 @@ agent-base@4, agent-base@^4.1.0, agent-base@^4.3.0:
dependencies: dependencies:
es6-promisify "^5.0.0" es6-promisify "^5.0.0"
agent-base@5:
version "5.1.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
agent-base@6: agent-base@6:
version "6.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.0.tgz#5d0101f19bbfaed39980b22ae866de153b93f09a" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.0.tgz#5d0101f19bbfaed39980b22ae866de153b93f09a"
@@ -3479,15 +3475,15 @@ code-point-at@^1.0.0:
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
codecov@3.7.1: codecov@3.8.2:
version "3.7.1" version "3.8.2"
resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.7.1.tgz#434cb8d55f18ef01672e5739d3d266696bebc202" resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.8.2.tgz#ab24f18783998c39e809ea210af899f8dbcc790e"
integrity sha512-JHWxyPTkMLLJn9SmKJnwAnvY09kg2Os2+Ux+GG7LwZ9g8gzDDISpIN5wAsH1UBaafA/yGcd3KofMaorE8qd6Lw== integrity sha512-6w/kt/xvmPsWMfDFPE/T054txA9RTgcJEw36PNa6MYX+YV29jCHCRFXwbQ3QZBTOgnex1J2WP8bo2AT8TWWz9g==
dependencies: dependencies:
argv "0.0.2" argv "0.0.2"
ignore-walk "3.0.3" ignore-walk "3.0.3"
js-yaml "3.13.1" js-yaml "3.14.1"
teeny-request "6.0.1" teeny-request "7.0.1"
urlgrey "0.4.4" urlgrey "0.4.4"
collection-visit@^1.0.0: collection-visit@^1.0.0:
@@ -4474,6 +4470,11 @@ es6-promisify@^5.0.0:
dependencies: dependencies:
es6-promise "^4.0.3" es6-promise "^4.0.3"
esbuild@^0.11.20:
version "0.11.23"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.23.tgz#c42534f632e165120671d64db67883634333b4b8"
integrity sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q==
escape-goat@^2.0.0: escape-goat@^2.0.0:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
@@ -5736,14 +5737,6 @@ https-proxy-agent@^2.2.3:
agent-base "^4.3.0" agent-base "^4.3.0"
debug "^3.1.0" debug "^3.1.0"
https-proxy-agent@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b"
integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==
dependencies:
agent-base "5"
debug "4"
https-proxy-agent@^5.0.0: https-proxy-agent@^5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
@@ -6830,6 +6823,14 @@ js-yaml@3.13.1:
argparse "^1.0.7" argparse "^1.0.7"
esprima "^4.0.0" esprima "^4.0.0"
js-yaml@3.14.1:
version "3.14.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@^3.10.0, js-yaml@^3.13.1: js-yaml@^3.10.0, js-yaml@^3.13.1:
version "3.14.0" version "3.14.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
@@ -7929,7 +7930,7 @@ node-fetch@2.6.0:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
node-fetch@2.6.1, node-fetch@^2.2.0, node-fetch@^2.2.1, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.1: node-fetch@2.6.1, node-fetch@^2.2.1, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.1:
version "2.6.1" version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
@@ -8292,10 +8293,10 @@ onetime@^5.1.0:
dependencies: dependencies:
mimic-fn "^2.1.0" mimic-fn "^2.1.0"
open@8.0.2: open@8.2.0:
version "8.0.2" version "8.2.0"
resolved "https://registry.yarnpkg.com/open/-/open-8.0.2.tgz#8c3e95cce93ba2fc8d99968ee8bfefecdb50b84f" resolved "https://registry.yarnpkg.com/open/-/open-8.2.0.tgz#d6a4788b00009a9d60df471ecb89842a15fdcfc1"
integrity sha512-NV5QmWJrTaNBLHABJyrb+nd5dXI5zfea/suWawBhkHzAbVhLLiJdrqMgxMypGK9Eznp2Ltoh7SAVkQ3XAucX7Q== integrity sha512-O8uInONB4asyY3qUcEytpgwxQG3O0fJ/hlssoUHsBboOIRVZzT6Wq+Rwj5nffbeUhOdMjpXeISpDDzHCMRDuOQ==
dependencies: dependencies:
define-lazy-prop "^2.0.0" define-lazy-prop "^2.0.0"
is-docker "^2.1.1" is-docker "^2.1.1"
@@ -9858,7 +9859,7 @@ source-map-support@0.5.12:
buffer-from "^1.0.0" buffer-from "^1.0.0"
source-map "^0.6.0" source-map "^0.6.0"
source-map-support@^0.5.12, source-map-support@^0.5.17, source-map-support@^0.5.6: source-map-support@^0.5.12, source-map-support@^0.5.17, source-map-support@^0.5.19, source-map-support@^0.5.6:
version "0.5.19" version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
@@ -10384,16 +10385,16 @@ tar@^6.1.0:
mkdirp "^1.0.3" mkdirp "^1.0.3"
yallist "^4.0.0" yallist "^4.0.0"
teeny-request@6.0.1: teeny-request@7.0.1:
version "6.0.1" version "7.0.1"
resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-6.0.1.tgz#9b1f512cef152945827ba7e34f62523a4ce2c5b0" resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.0.1.tgz#bdd41fdffea5f8fbc0d29392cb47bec4f66b2b4c"
integrity sha512-TAK0c9a00ELOqLrZ49cFxvPVogMUFaWY8dUsQc/0CuQPGF+BOxOQzXfE413BAk2kLomwNplvdtMpeaeGWmoc2g== integrity sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==
dependencies: dependencies:
http-proxy-agent "^4.0.0" http-proxy-agent "^4.0.0"
https-proxy-agent "^4.0.0" https-proxy-agent "^5.0.0"
node-fetch "^2.2.0" node-fetch "^2.6.1"
stream-events "^1.0.5" stream-events "^1.0.5"
uuid "^3.3.2" uuid "^8.0.0"
temp-dir@^1.0.0: temp-dir@^1.0.0:
version "1.0.0" version "1.0.0"
@@ -10637,6 +10638,14 @@ trim-right@^1.0.1:
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
ts-eager@2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/ts-eager/-/ts-eager-2.0.2.tgz#2c1a4d37529effa321f3438793650a84028a87d5"
integrity sha512-xzFPL2z7mgLs0brZXaIHTm91Pjl/Cuu9AMKprgSuK+kIS2LjiG8fqqg4eqz3tgBy9OIdupb9w55pr7ea3JBB+Q==
dependencies:
esbuild "^0.11.20"
source-map-support "^0.5.19"
ts-jest@24.1.0: ts-jest@24.1.0:
version "24.1.0" version "24.1.0"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.1.0.tgz#2eaa813271a2987b7e6c3fefbda196301c131734" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.1.0.tgz#2eaa813271a2987b7e6c3fefbda196301c131734"
@@ -10992,6 +11001,11 @@ uuid@^3.0.0, uuid@^3.0.1, uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^8.0.0:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-compile-cache@^2.0.3: v8-compile-cache@^2.0.3:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"