Compare commits

...

67 Commits

Author SHA1 Message Date
Steven
4e52f8532b Publish Canary
- @vercel/build-utils@2.12.3-canary.7
 - vercel@23.1.3-canary.9
 - @vercel/client@10.2.3-canary.7
 - @vercel/go@1.2.4-canary.3
 - @vercel/node@1.12.2-canary.4
 - @vercel/python@2.0.6-canary.4
 - @vercel/ruby@1.2.8-canary.3
2021-09-21 22:20:00 -04:00
Steven
702cb9e29c [all] Revert to @vercel/ncc@0.24.0 (#6749) 2021-09-21 22:17:06 -04:00
jj@jjsweb.site
d3d5555d79 Publish Canary
- @vercel/build-utils@2.12.3-canary.6
 - vercel@23.1.3-canary.8
 - @vercel/client@10.2.3-canary.6
 - @vercel/frameworks@0.5.1-canary.5
 - @vercel/routing-utils@1.11.4-canary.2
2021-09-20 19:35:05 -05:00
JJ Kasper
2fd3fc73e5 [routing-utils] Allow passing internal params to convertRewrites (#6742)
This adds an argument to allow passing internal param names that should be ignored when considering whether params should be auto-added to a rewrite's destination query. After adding this we should be able to resolve https://github.com/vercel/next.js/issues/27563 in the runtime where `convertRewrites` is called. 

This matches Next.js' handling for internal params which can be seen [here](e90825ad88/packages/next/shared/lib/router/utils/prepare-destination.ts (L203))

### Related Issues

x-ref: https://github.com/vercel/next.js/issues/27563

### 📋 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-09-21 00:15:29 +00:00
Steven
de0b13a46e Publish Canary
- vercel@23.1.3-canary.7
 - @vercel/node@1.12.2-canary.3
2021-09-16 12:54:47 -04:00
Steven
d0fe85db92 [node] Bump nft to 0.14.0 (#6727) 2021-09-16 12:49:04 -04:00
jj@jjsweb.site
bfbd927320 Publish Canary
- @vercel/build-utils@2.12.3-canary.5
 - vercel@23.1.3-canary.6
 - @vercel/client@10.2.3-canary.5
 - @vercel/frameworks@0.5.1-canary.4
 - @vercel/routing-utils@1.11.4-canary.1
2021-09-14 15:02:32 -05:00
JJ Kasper
90bacf88b8 [routing-utils] Fix host segment replacing (#6713)
* Fix host segment replacing

* Add additional check
2021-09-14 13:20:35 -05:00
Steven
07c369c542 Publish Canary
- @vercel/build-utils@2.12.3-canary.4
 - vercel@23.1.3-canary.5
 - @vercel/client@10.2.3-canary.4
 - @vercel/go@1.2.4-canary.2
 - @vercel/node@1.12.2-canary.2
 - @vercel/python@2.0.6-canary.3
 - @vercel/ruby@1.2.8-canary.2
2021-09-13 16:32:45 -04:00
Steven
a2e4186ccb [all] Bump ncc to 0.31.1 (#6700)
https://github.com/vercel/ncc/releases/tag/0.31.0

https://github.com/vercel/ncc/releases/tag/0.31.1
2021-09-13 20:20:48 +00:00
Steven
6e1d708e3f Publish Canary
- vercel@23.1.3-canary.4
 - @vercel/python@2.0.6-canary.2
2021-09-08 13:56:20 -04:00
Noa
38503103c3 [python] Use pip --upgrade when installing function dependencies (#6683)
Co-authored-by: Steven <steven@ceriously.com>
2021-09-08 13:54:03 -04:00
jj@jjsweb.site
e8fec4b69c Publish Canary
- @vercel/build-utils@2.12.3-canary.3
 - vercel@23.1.3-canary.3
 - @vercel/client@10.2.3-canary.3
 - @vercel/frameworks@0.5.1-canary.3
 - @vercel/routing-utils@1.11.4-canary.0
2021-09-07 11:52:01 -05:00
JJ Kasper
b3ffcdf80d [routing-utils] Ensure headers with only has items are replaced correctly (#6686)
This ensures we replace header values correctly when no named segments are used and only has items are used. 

### Related Issues

Fixes: https://vercel.slack.com/archives/CHTTGQYQ4/p1631023974185700

### 📋 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-09-07 16:17:01 +00:00
Leo Lamprecht
43c1a93c1d Enabled blank issues temporarily 2021-09-06 18:24:53 +02:00
Leo Lamprecht
5b118fd4e6 Disable blank issues again 2021-09-06 14:47:15 +02:00
Leo Lamprecht
8916b674af Temporarily enable blank issues 2021-09-06 14:41:32 +02:00
Brody McKee
1807f83c69 Update NPM publish token (#6672) 2021-09-03 19:44:59 +03:00
Steven
74e8ec7c64 Fix lerna publish for automation token (#6666)
Related to https://github.com/lerna/lerna/issues/2788
2021-09-03 00:20:20 +00:00
Steven
2644e3127b Publish Canary
- @vercel/build-utils@2.12.3-canary.2
 - vercel@23.1.3-canary.2
 - @vercel/client@10.2.3-canary.2
2021-09-02 08:42:33 -04:00
Leo Lamprecht
d77ac04b0c Disable blank issues again 2021-09-02 13:52:18 +02:00
Leo Lamprecht
0ef9c8df4d Temporarily enable issues 2021-09-02 13:45:20 +02:00
Steven
dfc4c98820 [build-utils] Fix custom 404 route (#6657)
The Custom 404 feature was originally implemented in #4563 but was matching too broadly (see [gist](https://gist.github.com/kjk/4dc57da62d7e715c687cc7914847ffb2))

This PR fixes the custom 404 route and updates the tests.
2021-09-02 00:12:45 +00:00
Steven
0e51884725 Publish Canary
- @vercel/build-utils@2.12.3-canary.1
 - vercel@23.1.3-canary.1
 - @vercel/client@10.2.3-canary.1
 - @vercel/frameworks@0.5.1-canary.2
 - @vercel/go@1.2.4-canary.1
 - @vercel/node-bridge@2.1.1-canary.1
 - @vercel/node@1.12.2-canary.1
 - @vercel/python@2.0.6-canary.1
 - @vercel/ruby@1.2.8-canary.1
2021-08-31 17:18:35 -04:00
Steven
1b264fe60e [build-utils] Add allowQuery to Lambda (#6651) 2021-08-31 17:17:31 -04:00
Nathan Rajlich
f18bca9718 [cli] Refactor legacy Now client to TypeScript (#6650) 2021-08-31 12:47:17 -07:00
Steven
c23dc73f41 [examples] Bump Next.js to 11.1.2 (#6648)
* Bump Next.js to 11.1.1

* Bump Next.js to 11.1.2
2021-08-31 10:53:18 -04:00
Nathan Rajlich
273718e0b7 [cli] Rewrite Unit tests to TypeScript + Jest (#6638)
* Rewrites the CLI unit tests to be TypeScript and use Jest (consistent with the unit tests in the other packages in this repo).
* The file structure of the new test files mirrors that of the `src` directory, so that it's easy to find the relevant tests.
* Code coverage is also properly set up through Jest - you can already see a big increase in coverage from this PR.
* Adds a mock API server framework, with the intention of making it possible to write granular tests of the CLI commands. Using mocks also has the benefit of not requiring `VERCEL_TOKEN` env var to be set, so 3rd party PRs will be able to have their tests run. Ultimately this will also help with test coverage, since we will be writing unit tests that test the commands' code directly.
* Converts `Output` into a proper class (which is subclassed for the mocks).
2021-08-30 22:07:09 +00:00
Nathan Rajlich
230b88bf9b [cli] Remove broken vc downgrade command (#6643)
It maps to an "upgrade" command, which does not exist.

Fixes:

```
$ vc downgrade
Vercel CLI 23.1.2
Error! Cannot find module './upgrade'
```
2021-08-28 18:01:31 +00:00
Nathan Rajlich
676a3d2568 [cli] Refactor src/index to TypeScript (#6602)
Refactors the CLI entrypoint `src/index.js` to TypeScript.
2021-08-27 19:48:31 +00:00
Nathan Rajlich
f221f041d0 Update @vercel/ncc to v0.29.2 (#6605) 2021-08-27 10:03:36 -07:00
Kaitlyn
aca42b2aac [examples] Update angular example to npm 7 (#6636) 2021-08-25 17:24:37 -07:00
Kaitlyn Carter
cf11a8efb5 Revert "delete yarn.lock"
This reverts commit b941715d7b.
2021-08-25 18:43:29 -04:00
Kaitlyn Carter
be09349daf Revert "update npm peer dependencies"
This reverts commit a01372bcbb.
2021-08-25 18:34:21 -04:00
Kaitlyn Carter
a01372bcbb update npm peer dependencies 2021-08-25 17:51:22 -04:00
Kaitlyn Carter
b941715d7b delete yarn.lock 2021-08-25 17:48:44 -04:00
Nathan Rajlich
ee9a8a0415 [cli] Refactor vc teams to TypeScript (#6610) 2021-08-24 11:08:30 -07:00
jj@jjsweb.site
2ad27eefb0 Publish Canary
- @vercel/build-utils@2.12.3-canary.0
 - vercel@23.1.3-canary.0
 - @vercel/client@10.2.3-canary.0
 - @vercel/frameworks@0.5.1-canary.1
 - @vercel/go@1.2.4-canary.0
 - @vercel/node-bridge@2.1.1-canary.0
 - @vercel/node@1.12.2-canary.0
 - @vercel/python@2.0.6-canary.0
 - @vercel/ruby@1.2.8-canary.0
2021-08-24 09:36:40 -05:00
Nathan Rajlich
578fe8a930 [cli] Support --project flag in vc link command (#6614)
To make setting up local dev README instructions easier for new users being introduced to a Vercel project, support flags to make the `vc link` command be non-interactive, in the case where the project name does not match the name of the directory where the code is located:

```
$ vc link --scope acme --project docs
```

Related to https://github.com/vercel/front/pull/10732.
2021-08-23 23:22:57 +00:00
JJ Kasper
04ea3bb88d [node-bridge] Fix error in node-bridge from decoded path (#6583)
* Fix error in node-bridge from decoded path

* Apply suggestions from code review

Co-authored-by: Steven <steven@ceriously.com>
2021-08-23 14:50:18 -05:00
Steven
46116022b7 [cli] Update readme (#6622) 2021-08-23 12:04:38 -04:00
Lee Robinson
f80539df82 Update README (#6618) 2021-08-22 23:39:04 +00:00
Johan Eliasson
daf06307b4 Fix deprecation message for NowRequestBody (#6613) 2021-08-20 12:58:47 -07:00
Nathan Rajlich
0bd028cd84 [cli] Show server-side errorMessage upon deployment UNEXPECTED_ERROR (#6606)
Before:

```
Error! Unexpected error. Please try again later. ()
```

After:

```
Error! An unexpected error happened when running this build. We have been notified of the problem. If you have any questions, please contact support@vercel.com.
```
2021-08-17 18:28:56 -07:00
Nathan Rajlich
1c48030e1e [cli] Stop installing @now/build-utils for vc dev (#6604)
Users should not be using any legacy Runtimes that depend on `@now/build-utils` at this point, since those have not received updates in a long time.
2021-08-17 17:35:55 +00:00
Nathan Rajlich
1dc05428d7 [go][node][python][ruby] Remove @now/build-utils backwards compat hack (#6603)
`@now/build-utils` has not been published in a long time, so let's remove this logic.
2021-08-17 15:29:52 +00:00
Nathan Rajlich
288dca045c [cli] Refactor a handful of util modules to TypeScript (#6584) 2021-08-13 20:26:02 +00:00
Steven
8c5bc04fde Fix publish script for Windows (#6591)
* Fix publish script for Windows

* Update changelog.js
2021-08-13 12:03:54 -04:00
Steven
a07e6fc103 [examples] Bump Next.js to 11.1.0 (#6582) 2021-08-11 15:41:38 -04:00
Nathan Rajlich
9af3054d41 [cli] Use client.fetch() in vc init (#6575)
Gives us debug logging and retries, etc.
2021-08-11 19:04:01 +00:00
Nathan Rajlich
9fb254e14a [cli] Attempt to fix the "lambda-with-3-second-timeout" E2E test (#6579) 2021-08-11 00:50:57 -07:00
William Li
3616bdf17a bump react-scripts to @latest (#6577) 2021-08-11 11:11:43 +08:00
Nathan Rajlich
52a89fd4b7 [cli] Remove withSpinner() helper (#6576)
It's not really necessary and doesn't use the common `Output` instance.
2021-08-10 10:41:44 -07:00
Nathan Rajlich
d4db6635f1 [cli] Remove unnecessary await in Dev tests (#6572)
This is the result of VS Code's "remove all unneeded await calls" formatter.
2021-08-10 02:01:56 -07:00
Nathan Rajlich
f1009a80cd [api] Fix GET /api/examples/list output (#6574)
A regression from #6554 caused the return value to contain a nested
object with a `name` property for the `name` key of the response in the
list.
2021-08-09 22:24:17 -07:00
Steven
2756d1e323 [client][frameworks][api] Update readdir() call with withFileTypes (#6554)
Avoids running unnecessary code such as `stat().isDirectory()`.
2021-08-09 14:27:50 -07:00
Nathan Rajlich
5b61b16bd1 Publish Stable
- @vercel/build-utils@2.12.2
 - vercel@23.1.2
 - @vercel/client@10.2.2
 - @vercel/node@1.12.1
2021-08-09 11:21:40 -07:00
Nathan Rajlich
41c61f8f8b Publish Canary
- vercel@23.1.2-canary.1
2021-08-09 10:00:43 -07:00
Nathan Rajlich
6c52e1fad7 [cli] Add "outDir" to tsconfig.json (#6566)
Fixes TypeScript errors.
2021-08-09 09:58:30 -07:00
Nathan Rajlich
d2e82fdc3a [cli] Fix in-flight re-login when there are no existing credentials (#6565) 2021-08-08 00:57:23 -07:00
Nathan Rajlich
a60b1b225b Publish Canary
- @vercel/build-utils@2.12.2-canary.0
 - vercel@23.1.2-canary.0
 - @vercel/client@10.2.2-canary.0
 - @vercel/node@1.12.1-canary.0
2021-08-06 17:15:13 -07:00
Kaitlyn
18bec983ae [build-utils] check to see that the node version is less then 16 befo… (#6553)
### Related Issues

Check the project's nodeVersion setting for when we release Node 16 and later

### 📋 Checklist

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

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [ ] 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-08-07 00:14:21 +00:00
Nathan Rajlich
e6fb2ffe05 Publish Stable
- @vercel/build-utils@2.12.1
 - vercel@23.1.1
 - @vercel/client@10.2.1
2021-08-06 13:00:16 -07:00
Nathan Rajlich
0533cfd566 [build-utils] Fix test name 2021-08-06 12:48:58 -07:00
Nathan Rajlich
3db8618885 Publish Canary
- @vercel/build-utils@2.12.1-canary.0
 - vercel@23.1.1-canary.0
 - @vercel/client@10.2.1-canary.0
 - @vercel/frameworks@0.5.1-canary.0
2021-08-06 11:52:56 -07:00
Nathan Rajlich
4722ea5ad6 [build-utils] Make scanParentDirs() work with npm Workspaces (#6559)
Currently, when `scanParentDirs()` finds the closest `package.json` file, it stops iteration regardless of whether or not a lockfile was found. For npm Workspaces, this is problematic because if you are deploying a subdirectory of a monorepo, then there will be no lockfile in the subdirectory (the `package-lock.json` file is at the root of the project). So instead of stopping once the `package.json` file is found, instead stop when the first lockfile is found.
2021-08-06 18:48:23 +00:00
Nathan Rajlich
c7bd6f3266 [tests] Define jest/ts-jest deps at the root level (#6556)
And remove `jest`/`ts-jest` from the individual packages. It is important to have a consistent version of `jest` being used throughout the monorepo, otherwise bizarre errors surface that cause jest to crash, such as [here](https://github.com/vercel/vercel/runs/3255999061?check_suite_focus=true):

```
TypeError: this._environment.runScript is not a function

  at Runtime._execModule (../../node_modules/jest-runner/node_modules/jest-runtime/build/index.js:856:41)
```
2021-08-05 21:06:18 +00:00
196 changed files with 21672 additions and 13532 deletions

View File

@@ -1,4 +1,4 @@
blank_issues_enabled: false blank_issues_enabled: true
contact_links: contact_links:
- name: Bug Report - name: Bug Report
url: https://vercel.com/support/request url: https://vercel.com/support/request

View File

@@ -29,6 +29,6 @@ jobs:
- name: Publish - name: Publish
run: yarn publish-from-github run: yarn publish-from-github
env: env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}
GA_TRACKING_ID: ${{ secrets.GA_TRACKING_ID }} GA_TRACKING_ID: ${{ secrets.GA_TRACKING_ID }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}

View File

@@ -11,7 +11,11 @@
## Usage ## Usage
Vercel is the optimal workflow for frontend teams. All-in-one: Static and Jamstack deployment, Serverless Functions, and Global CDN. Vercel is a platform for **static sites and frontend frameworks**, built to integrate with your headless content, commerce, or database.
We provide a **frictionless developer experience** to take care of the hard things: deploy instantly, scale automatically, and serve personalized content around the globe.
We make it easy for frontend teams to **develop, preview, and ship** delightful user experiences, where performance is the default.
Get started by [Importing a Git Project](https://vercel.com/new) and use `git push` to deploy. Alternatively, you can [install Vercel CLI](https://vercel.com/cli). Get started by [Importing a Git Project](https://vercel.com/new) and use `git push` to deploy. Alternatively, you can [install Vercel CLI](https://vercel.com/cli).

View File

@@ -2,19 +2,19 @@
* Get example list from extracted folder * Get example list from extracted folder
*/ */
import { join } from 'path';
import { lstatSync, existsSync, readdirSync } from 'fs'; import { lstatSync, existsSync, readdirSync } from 'fs';
const exists = (path: string) => existsSync(path); const exists = (path: string) => existsSync(path);
const isDotFile = (name: string) => name.startsWith('.'); const isDotFile = (name: string) => name.startsWith('.');
const isDirectory = (path: string) => lstatSync(path).isDirectory(); const isDirectory = (path: string) => lstatSync(path).isDirectory();
export function summary(source: string) { export function summary(source: string): string[] {
if (!exists(source) || !isDirectory(source)) { if (!exists(source) || !isDirectory(source)) {
return []; return [];
} }
return readdirSync(source) return readdirSync(source, { withFileTypes: true })
.filter(name => !isDotFile(name)) .filter(d => !isDotFile(d.name))
.filter(name => isDirectory(join(source, name))); .filter(d => d.isDirectory())
.map(d => d.name);
} }

View File

@@ -6,3 +6,5 @@ coverage:
project: off project: off
patch: off patch: off
fixes:
- "::packages/cli/" # move root e.g., "path/" => "after/path/"

13218
examples/angular/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -12,37 +12,38 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "~8.1.0", "@angular/animations": "^8.1.0",
"@angular/common": "~8.1.0", "@angular/common": "^8.1.0",
"@angular/compiler": "~8.1.0", "@angular/core": "^8.1.0",
"@angular/core": "~8.1.0", "@angular/forms": "^8.1.0",
"@angular/forms": "~8.1.0", "@angular/platform-browser": "^8.1.0",
"@angular/platform-browser": "~8.1.0", "@angular/platform-browser-dynamic": "^8.1.0",
"@angular/platform-browser-dynamic": "~8.1.0", "@angular/router": "^8.1.0",
"@angular/router": "~8.1.0",
"rxjs": "~6.4.0", "rxjs": "~6.4.0",
"tslib": "^1.9.0", "tslib": "^1.9.0",
"zone.js": "~0.9.1" "zone.js": "~0.9.1"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~0.801.0", "@angular-devkit/build-angular": "^12.2.2",
"@angular/cli": "~8.1.0", "@angular/cli": "^12.2.2",
"@angular/compiler-cli": "~8.1.0", "@angular/compiler": "^12.2.2",
"@angular/compiler-cli": "^12.2.2",
"@angular/language-service": "~8.1.0", "@angular/language-service": "~8.1.0",
"@types/node": "~8.9.4",
"@types/jasmine": "~3.3.8", "@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3", "@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4",
"codelyzer": "^5.0.0", "codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0", "glob-parent": "^5.1.2",
"jasmine-core": "^3.4.0",
"jasmine-spec-reporter": "~4.2.1", "jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0", "karma": "^6.3.4",
"karma-chrome-launcher": "~2.2.0", "karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1", "karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1", "karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0", "karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.0", "protractor": "^7.0.0",
"ts-node": "~7.0.0", "ts-node": "~7.0.0",
"tslint": "~5.15.0", "tslint": "~5.15.0",
"typescript": "~3.4.3" "typescript": "^4.2.4"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"dependencies": { "dependencies": {
"react": "^16.6.1", "react": "^16.6.1",
"react-dom": "^16.6.1", "react-dom": "^16.6.1",
"react-scripts": "2.1.1" "react-scripts": "^4.0.3"
}, },
"scripts": { "scripts": {
"dev": "BROWSER=none react-scripts start", "dev": "BROWSER=none react-scripts start",

View File

@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

View File

@@ -29,6 +29,6 @@ You can check out [the Next.js GitHub repository](https://github.com/vercel/next
## Deploy on Vercel ## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

@@ -0,0 +1,3 @@
module.exports = {
reactStrictMode: true,
}

View File

@@ -1,15 +1,20 @@
{ {
"name": "nextjs", "name": "nextjs",
"version": "0.1.0", "version": "1.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start" "start": "next start",
"lint": "next lint"
}, },
"dependencies": { "dependencies": {
"next": "10.x", "next": "11.1.2",
"react": "17.x", "react": "17.0.2",
"react-dom": "17.x" "react-dom": "17.0.2"
},
"devDependencies": {
"eslint": "7.32.0",
"eslint-config-next": "11.1.0"
} }
} }

View File

@@ -1,6 +1,5 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default (req, res) => { export default function handler(req, res) {
res.statusCode = 200 res.status(200).json({ name: 'John Doe' })
res.json({ name: 'John Doe' })
} }

View File

@@ -1,4 +1,5 @@
import Head from 'next/head' import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css' import styles from '../styles/Home.module.css'
export default function Home() { export default function Home() {
@@ -6,6 +7,7 @@ export default function Home() {
<div className={styles.container}> <div className={styles.container}>
<Head> <Head>
<title>Create Next App</title> <title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
@@ -21,12 +23,12 @@ export default function Home() {
<div className={styles.grid}> <div className={styles.grid}>
<a href="https://nextjs.org/docs" className={styles.card}> <a href="https://nextjs.org/docs" className={styles.card}>
<h3>Documentation &rarr;</h3> <h2>Documentation &rarr;</h2>
<p>Find in-depth information about Next.js features and API.</p> <p>Find in-depth information about Next.js features and API.</p>
</a> </a>
<a href="https://nextjs.org/learn" className={styles.card}> <a href="https://nextjs.org/learn" className={styles.card}>
<h3>Learn &rarr;</h3> <h2>Learn &rarr;</h2>
<p>Learn about Next.js in an interactive course with quizzes!</p> <p>Learn about Next.js in an interactive course with quizzes!</p>
</a> </a>
@@ -34,15 +36,15 @@ export default function Home() {
href="https://github.com/vercel/next.js/tree/master/examples" href="https://github.com/vercel/next.js/tree/master/examples"
className={styles.card} className={styles.card}
> >
<h3>Examples &rarr;</h3> <h2>Examples &rarr;</h2>
<p>Discover and deploy boilerplate example Next.js projects.</p> <p>Discover and deploy boilerplate example Next.js projects.</p>
</a> </a>
<a <a
href="https://vercel.com/import?filter=next.js&utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app" href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className={styles.card} className={styles.card}
> >
<h3>Deploy &rarr;</h3> <h2>Deploy &rarr;</h2>
<p> <p>
Instantly deploy your Next.js site to a public URL with Vercel. Instantly deploy your Next.js site to a public URL with Vercel.
</p> </p>
@@ -57,7 +59,9 @@ export default function Home() {
rel="noopener noreferrer" rel="noopener noreferrer"
> >
Powered by{' '} Powered by{' '}
<img src="/vercel.svg" alt="Vercel Logo" className={styles.logo} /> <span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a> </a>
</footer> </footer>
</div> </div>

View File

@@ -5,6 +5,7 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100vh;
} }
.main { .main {
@@ -25,14 +26,11 @@
align-items: center; align-items: center;
} }
.footer img {
margin-left: 0.5rem;
}
.footer a { .footer a {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-grow: 1;
} }
.title a { .title a {
@@ -82,7 +80,6 @@
.card { .card {
margin: 1rem; margin: 1rem;
flex-basis: 45%;
padding: 1.5rem; padding: 1.5rem;
text-align: left; text-align: left;
color: inherit; color: inherit;
@@ -90,6 +87,7 @@
border: 1px solid #eaeaea; border: 1px solid #eaeaea;
border-radius: 10px; border-radius: 10px;
transition: color 0.15s ease, border-color 0.15s ease; transition: color 0.15s ease, border-color 0.15s ease;
width: 45%;
} }
.card:hover, .card:hover,
@@ -99,7 +97,7 @@
border-color: #0070f3; border-color: #0070f3;
} }
.card h3 { .card h2 {
margin: 0 0 1rem 0; margin: 0 0 1rem 0;
font-size: 1.5rem; font-size: 1.5rem;
} }
@@ -112,6 +110,7 @@
.logo { .logo {
height: 1em; height: 1em;
margin-left: 0.5rem;
} }
@media (max-width: 600px) { @media (max-width: 600px) {

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,6 @@
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "4.28.0", "@typescript-eslint/eslint-plugin": "4.28.0",
"@typescript-eslint/parser": "4.28.0", "@typescript-eslint/parser": "4.28.0",
"@vercel/ncc": "0.24.0",
"async-retry": "1.2.3", "async-retry": "1.2.3",
"buffer-replace": "1.0.0", "buffer-replace": "1.0.0",
"cheerio": "1.0.0-rc.3", "cheerio": "1.0.0-rc.3",
@@ -25,17 +24,19 @@
"eslint-config-prettier": "8.3.0", "eslint-config-prettier": "8.3.0",
"eslint-plugin-jest": "24.3.6", "eslint-plugin-jest": "24.3.6",
"husky": "6.0.0", "husky": "6.0.0",
"jest": "27.0.6",
"json5": "2.1.1", "json5": "2.1.1",
"lint-staged": "9.2.5", "lint-staged": "9.2.5",
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"npm-package-arg": "6.1.0", "npm-package-arg": "6.1.0",
"prettier": "2.3.1" "prettier": "2.3.1",
"ts-jest": "27.0.4"
}, },
"scripts": { "scripts": {
"lerna": "lerna", "lerna": "lerna",
"bootstrap": "lerna bootstrap", "bootstrap": "lerna bootstrap",
"publish-stable": "echo 'Run `yarn changelog` for instructions'", "publish-stable": "echo 'Run `yarn changelog` for instructions'",
"publish-canary": "git checkout main && git pull && lerna version prerelease --preid canary --message 'Publish Canary' --exact", "publish-canary": "git checkout main && git pull && lerna version prerelease --preid canary --message \"Publish Canary\" --exact",
"publish-from-github": "./utils/publish.sh", "publish-from-github": "./utils/publish.sh",
"changelog": "node utils/changelog.js", "changelog": "node utils/changelog.js",
"build": "node utils/run.js build all", "build": "node utils/run.js build all",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/build-utils", "name": "@vercel/build-utils",
"version": "2.12.0", "version": "2.12.3-canary.7",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.js", "types": "./dist/index.d.js",
@@ -23,14 +23,14 @@
"@types/end-of-stream": "^1.4.0", "@types/end-of-stream": "^1.4.0",
"@types/fs-extra": "^5.0.5", "@types/fs-extra": "^5.0.5",
"@types/glob": "^7.1.1", "@types/glob": "^7.1.1",
"@types/jest": "26.0.24", "@types/jest": "27.0.1",
"@types/js-yaml": "3.12.1", "@types/js-yaml": "3.12.1",
"@types/ms": "0.7.31", "@types/ms": "0.7.31",
"@types/multistream": "2.1.1", "@types/multistream": "2.1.1",
"@types/node-fetch": "^2.1.6", "@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@types/yazl": "^2.4.1", "@types/yazl": "^2.4.1",
"@vercel/frameworks": "0.5.0", "@vercel/frameworks": "0.5.1-canary.5",
"@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",
@@ -41,13 +41,11 @@
"fs-extra": "7.0.0", "fs-extra": "7.0.0",
"glob": "7.1.3", "glob": "7.1.3",
"into-stream": "5.0.0", "into-stream": "5.0.0",
"jest": "27.0.6",
"js-yaml": "3.13.1", "js-yaml": "3.13.1",
"minimatch": "3.0.4", "minimatch": "3.0.4",
"multistream": "2.1.1", "multistream": "2.1.1",
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"semver": "6.1.1", "semver": "6.1.1",
"ts-jest": "27.0.4",
"typescript": "4.3.4", "typescript": "4.3.4",
"yazl": "2.4.3" "yazl": "2.4.3"
} }

View File

@@ -1030,7 +1030,7 @@ function getRouteResult(
// https://nextjs.org/docs/advanced-features/custom-error-page // https://nextjs.org/docs/advanced-features/custom-error-page
errorRoutes.push({ errorRoutes.push({
status: 404, status: 404,
src: '^/(?!.*api).*$', src: '^(?!/api).*$',
dest: options.cleanUrls ? '/404' : '/404.html', dest: options.cleanUrls ? '/404' : '/404.html',
}); });
} }

View File

@@ -100,4 +100,4 @@ class FileFsRef implements File {
} }
} }
export = FileFsRef; export default FileFsRef;

View File

@@ -244,10 +244,13 @@ export async function scanParentDirs(
const packageJsonPath = path.join(currentDestPath, 'package.json'); const packageJsonPath = path.join(currentDestPath, 'package.json');
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
if (await fs.pathExists(packageJsonPath)) { if (await fs.pathExists(packageJsonPath)) {
// eslint-disable-next-line no-await-in-loop // Only read the contents of the *first* `package.json` file found,
if (readPackageJson) { // since that's the one related to this installation.
if (readPackageJson && !packageJson) {
// eslint-disable-next-line no-await-in-loop
packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')); packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
} }
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const [packageLockJson, hasYarnLock] = await Promise.all([ const [packageLockJson, hasYarnLock] = await Promise.all([
fs fs
@@ -256,9 +259,8 @@ export async function scanParentDirs(
// If the file doesn't exist, fail gracefully otherwise error // If the file doesn't exist, fail gracefully otherwise error
if (error.code === 'ENOENT') { if (error.code === 'ENOENT') {
return null; return null;
} else {
throw error;
} }
throw error;
}), }),
fs.pathExists(path.join(currentDestPath, 'yarn.lock')), fs.pathExists(path.join(currentDestPath, 'yarn.lock')),
]); ]);
@@ -267,7 +269,13 @@ export async function scanParentDirs(
cliType = 'npm'; cliType = 'npm';
lockfileVersion = packageLockJson.lockfileVersion; lockfileVersion = packageLockJson.lockfileVersion;
} }
break;
// Only stop iterating if a lockfile was found, because it's possible
// that the lockfile is in a higher path than where the `package.json`
// file was found.
if (packageLockJson || hasYarnLock) {
break;
}
} }
const newDestPath = path.dirname(currentDestPath); const newDestPath = path.dirname(currentDestPath);
@@ -305,7 +313,8 @@ export async function runNpmInstall(
destPath: string, destPath: string,
args: string[] = [], args: string[] = [],
spawnOpts?: SpawnOptions, spawnOpts?: SpawnOptions,
meta?: Meta meta?: Meta,
nodeVersion?: NodeVersion
) { ) {
if (meta?.isDev) { if (meta?.isDev) {
debug('Skipping dependency installation because dev mode is enabled'); debug('Skipping dependency installation because dev mode is enabled');
@@ -329,7 +338,12 @@ export async function runNpmInstall(
.filter(a => a !== '--prefer-offline') .filter(a => a !== '--prefer-offline')
.concat(['install', '--no-audit', '--unsafe-perm']); .concat(['install', '--no-audit', '--unsafe-perm']);
if (typeof lockfileVersion === 'number' && lockfileVersion >= 2) { // If the lockfile version is 2 or greater and the node version is less than 16 than we will force npm7 to be used
if (
typeof lockfileVersion === 'number' &&
lockfileVersion >= 2 &&
(nodeVersion?.major || 0) < 16
) {
// Ensure that npm 7 is at the beginning of the `$PATH` // Ensure that npm 7 is at the beginning of the `$PATH`
env.PATH = `/node16/bin-npm7:${env.PATH}`; env.PATH = `/node16/bin-npm7:${env.PATH}`;
console.log('Detected `package-lock.json` generated by npm 7...'); console.log('Detected `package-lock.json` generated by npm 7...');

View File

@@ -19,6 +19,7 @@ interface LambdaOptions {
memory?: number; memory?: number;
maxDuration?: number; maxDuration?: number;
environment: Environment; environment: Environment;
allowQuery?: string[];
} }
interface CreateLambdaOptions { interface CreateLambdaOptions {
@@ -28,6 +29,7 @@ interface CreateLambdaOptions {
memory?: number; memory?: number;
maxDuration?: number; maxDuration?: number;
environment?: Environment; environment?: Environment;
allowQuery?: string[];
} }
interface GetLambdaOptionsFromFunctionOptions { interface GetLambdaOptionsFromFunctionOptions {
@@ -43,6 +45,7 @@ export class Lambda {
public memory?: number; public memory?: number;
public maxDuration?: number; public maxDuration?: number;
public environment: Environment; public environment: Environment;
public allowQuery?: string[];
constructor({ constructor({
zipBuffer, zipBuffer,
@@ -51,6 +54,7 @@ export class Lambda {
maxDuration, maxDuration,
memory, memory,
environment, environment,
allowQuery,
}: LambdaOptions) { }: LambdaOptions) {
this.type = 'Lambda'; this.type = 'Lambda';
this.zipBuffer = zipBuffer; this.zipBuffer = zipBuffer;
@@ -59,6 +63,7 @@ export class Lambda {
this.memory = memory; this.memory = memory;
this.maxDuration = maxDuration; this.maxDuration = maxDuration;
this.environment = environment; this.environment = environment;
this.allowQuery = allowQuery;
} }
} }
@@ -72,6 +77,7 @@ export async function createLambda({
memory, memory,
maxDuration, maxDuration,
environment = {}, environment = {},
allowQuery,
}: CreateLambdaOptions): Promise<Lambda> { }: CreateLambdaOptions): Promise<Lambda> {
assert(typeof files === 'object', '"files" must be an object'); assert(typeof files === 'object', '"files" must be an object');
assert(typeof handler === 'string', '"handler" is not a string'); assert(typeof handler === 'string', '"handler" is not a string');
@@ -86,6 +92,14 @@ export async function createLambda({
assert(typeof maxDuration === 'number', '"maxDuration" is not a number'); assert(typeof maxDuration === 'number', '"maxDuration" is not a number');
} }
if (allowQuery !== undefined) {
assert(Array.isArray(allowQuery), '"allowQuery" is not an Array');
assert(
allowQuery.every(q => typeof q === 'string'),
'"allowQuery" is not a string Array'
);
}
await sema.acquire(); await sema.acquire();
try { try {
@@ -131,9 +145,7 @@ export async function createZip(files: Files): Promise<Buffer> {
} }
zipFile.end(); zipFile.end();
streamToBuffer(zipFile.outputStream) streamToBuffer(zipFile.outputStream).then(resolve).catch(reject);
.then(resolve)
.catch(reject);
}); });
return zipBuffer; return zipBuffer;

View File

@@ -0,0 +1,15 @@
{
"name": "a",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"debug": "^4.3.2"
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

@@ -0,0 +1,782 @@
{
"name": "21-npm-workspaces",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "1.0.0",
"workspaces": [
"a",
"b"
]
},
"a": {
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"debug": "^4.3.2"
}
},
"b": {
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
},
"node_modules/a": {
"resolved": "a",
"link": true
},
"node_modules/ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"engines": {
"node": ">=4"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/b": {
"resolved": "b",
"link": true
},
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"engines": {
"node": ">=6"
}
},
"node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/cliui/node_modules/ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dependencies": {
"ansi-regex": "^5.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/cowsay": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/cowsay/-/cowsay-1.5.0.tgz",
"integrity": "sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==",
"dependencies": {
"get-stdin": "8.0.0",
"string-width": "~2.1.1",
"strip-final-newline": "2.0.0",
"yargs": "15.4.1"
},
"bin": {
"cowsay": "cli.js",
"cowthink": "cli.js"
},
"engines": {
"node": ">= 4"
}
},
"node_modules/debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-stdin": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
"integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"engines": {
"node": ">=4"
}
},
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"engines": {
"node": ">=8"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"node_modules/string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dependencies": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dependencies": {
"ansi-regex": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"engines": {
"node": ">=6"
}
},
"node_modules/which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi/node_modules/ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi/node_modules/string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi/node_modules/strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dependencies": {
"ansi-regex": "^5.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
"node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/yargs/node_modules/ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dependencies": {
"ansi-regex": "^5.0.0"
},
"engines": {
"node": ">=8"
}
}
},
"dependencies": {
"a": {
"version": "file:a",
"requires": {
"debug": "^4.3.2"
}
},
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"b": {
"version": "file:b",
"requires": {
"cowsay": "^1.5.0"
}
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"cowsay": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/cowsay/-/cowsay-1.5.0.tgz",
"integrity": "sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==",
"requires": {
"get-stdin": "8.0.0",
"string-width": "~2.1.1",
"strip-final-newline": "2.0.0",
"yargs": "15.4.1"
}
},
"debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
"requires": {
"ms": "2.1.2"
}
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-stdin": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
"integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg=="
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"requires": {
"p-locate": "^4.1.0"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"requires": {
"p-limit": "^2.2.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
}
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"requires": {
"ansi-regex": "^3.0.0"
}
},
"strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
"y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
"yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"requires": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
"yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
}

View File

@@ -0,0 +1,8 @@
{
"name": "21-npm-workspaces",
"version": "1.0.0",
"workspaces": [
"a",
"b"
]
}

View File

@@ -30,6 +30,7 @@ const skipFixtures: string[] = [
'06-zero-config-hugo', '06-zero-config-hugo',
'07-zero-config-jekyll', '07-zero-config-jekyll',
'08-zero-config-middleman', '08-zero-config-middleman',
'21-npm-workspaces',
]; ];
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax

View File

@@ -2013,15 +2013,11 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
framework: 'redwoodjs', framework: 'redwoodjs',
}; };
const { const { builders, defaultRoutes, rewriteRoutes, errorRoutes } =
builders, await detectBuilders(files, null, {
defaultRoutes, projectSettings,
rewriteRoutes, featHandleMiss,
errorRoutes, });
} = await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
});
expect(builders).toStrictEqual([ expect(builders).toStrictEqual([
{ {
@@ -2038,7 +2034,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(errorRoutes).toStrictEqual([ expect(errorRoutes).toStrictEqual([
{ {
status: 404, status: 404,
src: '^/(?!.*api).*$', src: '^(?!/api).*$',
dest: '/404.html', dest: '/404.html',
}, },
]); ]);
@@ -2050,15 +2046,11 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
framework: 'redwoodjs', framework: 'redwoodjs',
}; };
const { const { builders, defaultRoutes, rewriteRoutes, errorRoutes } =
builders, await detectBuilders(files, null, {
defaultRoutes, projectSettings,
rewriteRoutes, featHandleMiss,
errorRoutes, });
} = await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
});
expect(builders).toStrictEqual([ expect(builders).toStrictEqual([
{ {
@@ -2096,7 +2088,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(errorRoutes).toStrictEqual([ expect(errorRoutes).toStrictEqual([
{ {
status: 404, status: 404,
src: '^/(?!.*api).*$', src: '^(?!/api).*$',
dest: '/404.html', dest: '/404.html',
}, },
]); ]);
@@ -2417,7 +2409,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
expect(errorRoutes).toStrictEqual([ expect(errorRoutes).toStrictEqual([
{ {
status: 404, status: 404,
src: '^/(?!.*api).*$', src: '^(?!/api).*$',
dest: '/404.html', dest: '/404.html',
}, },
]); ]);
@@ -2435,6 +2427,11 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
'/another/sub/index.html', '/another/sub/index.html',
'/another/sub/page.html', '/another/sub/page.html',
'/another/sub/page', '/another/sub/page',
'/another/api',
'/another/api/page.html',
'/rapid',
'/rapid/page.html',
'/health-api.html',
].forEach(file => { ].forEach(file => {
expect(file).toMatch(pattern); expect(file).toMatch(pattern);
}); });
@@ -2443,12 +2440,12 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
'/api', '/api',
'/api/', '/api/',
'/api/index.html', '/api/index.html',
'/api/page.html', '/api/users.js',
'/api/page', '/api/users',
'/api/sub', '/api/sub',
'/api/sub/index.html', '/api/sub/index.html',
'/api/sub/page.html', '/api/sub/users.js',
'/api/sub/page', '/api/sub/users',
].forEach(file => { ].forEach(file => {
expect(file).not.toMatch(pattern); expect(file).not.toMatch(pattern);
}); });
@@ -2819,12 +2816,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
{ {
const files = ['api/user.go', 'api/team.js', 'api/package.json']; const files = ['api/user.go', 'api/team.js', 'api/package.json'];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes, errorRoutes } =
defaultRoutes, await detectBuilders(files, null, options);
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -2836,7 +2829,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
expect(errorRoutes).toStrictEqual([ expect(errorRoutes).toStrictEqual([
{ {
status: 404, status: 404,
src: '^/(?!.*api).*$', src: '^(?!/api).*$',
dest: '/404', dest: '/404',
}, },
]); ]);
@@ -2904,11 +2897,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
{ {
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js']; const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, null, options);
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -2936,11 +2926,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
'api/[endpoint]/[id].js', 'api/[endpoint]/[id].js',
]; ];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, null, options);
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -2974,11 +2961,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
const files = ['public/index.html', 'api/[endpoint].js']; const files = ['public/index.html', 'api/[endpoint].js'];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, pkg, options);
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, pkg, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -3004,11 +2988,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
{ {
const files = ['api/date/index.js', 'api/date.js']; const files = ['api/date/index.js', 'api/date.js'];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, null, options);
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -3022,11 +3003,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
{ {
const files = ['api/date.js', 'api/[date]/index.js']; const files = ['api/date.js', 'api/[date]/index.js'];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, null, options);
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -3051,11 +3029,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
'api/food.ts', 'api/food.ts',
'api/ts/gold.ts', 'api/ts/gold.ts',
]; ];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, null, options);
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -3071,11 +3046,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } }; const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
const files = ['api/user.php']; const files = ['api/user.php'];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, null, { functions, ...options });
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, { functions, ...options });
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -3105,11 +3077,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
{ {
const files = ['api/user.go', 'api/team.js', 'api/package.json']; const files = ['api/user.go', 'api/team.js', 'api/package.json'];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, null, options);
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -3152,11 +3121,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
{ {
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js']; const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, null, options);
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -3184,11 +3150,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
'api/[endpoint]/[id].js', 'api/[endpoint]/[id].js',
]; ];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, null, options);
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -3222,11 +3185,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
const files = ['public/index.html', 'api/[endpoint].js']; const files = ['public/index.html', 'api/[endpoint].js'];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, pkg, options);
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, pkg, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -3245,11 +3205,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
{ {
const files = ['api/date/index.js', 'api/date.js']; const files = ['api/date/index.js', 'api/date.js'];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, null, options);
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -3263,11 +3220,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
{ {
const files = ['api/date.js', 'api/[date]/index.js']; const files = ['api/date.js', 'api/[date]/index.js'];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, null, options);
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -3292,11 +3246,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
'api/food.ts', 'api/food.ts',
'api/ts/gold.ts', 'api/ts/gold.ts',
]; ];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, null, options);
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([
@@ -3312,11 +3263,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } }; const functions = { 'api/user.php': { runtime: 'vercel-php@0.1.0' } };
const files = ['api/user.php']; const files = ['api/user.php'];
const { const { defaultRoutes, redirectRoutes, rewriteRoutes } =
defaultRoutes, await detectBuilders(files, null, { functions, ...options });
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, { functions, ...options });
testHeaders(redirectRoutes); testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]); expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([ expect(rewriteRoutes).toStrictEqual([

View File

@@ -298,23 +298,29 @@ it(
); );
it('should return lockfileVersion 2 with npm7', async () => { it('should return lockfileVersion 2 with npm7', async () => {
const packageLockJsonPath = path.join(__dirname, 'fixtures', '20-npm-7'); const fixture = path.join(__dirname, 'fixtures', '20-npm-7');
const result = await scanParentDirs(packageLockJsonPath); const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('npm');
expect(result.lockfileVersion).toEqual(2); expect(result.lockfileVersion).toEqual(2);
}); });
it('should not return lockfileVersion with yarn', async () => { it('should not return lockfileVersion with yarn', async () => {
const packageLockJsonPath = path.join(__dirname, 'fixtures', '19-yarn-v2'); const fixture = path.join(__dirname, 'fixtures', '19-yarn-v2');
const result = await scanParentDirs(packageLockJsonPath); const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('yarn');
expect(result.lockfileVersion).toEqual(undefined); expect(result.lockfileVersion).toEqual(undefined);
}); });
it('should return lockfileVersion 1 with older versions of npm', async () => { it('should return lockfileVersion 1 with older versions of npm', async () => {
const packageLockJsonPath = path.join( const fixture = path.join(__dirname, 'fixtures', '08-yarn-npm/with-npm');
__dirname, const result = await scanParentDirs(fixture);
'fixtures', expect(result.cliType).toEqual('npm');
'08-yarn-npm/with-npm'
);
const result = await scanParentDirs(packageLockJsonPath);
expect(result.lockfileVersion).toEqual(1); expect(result.lockfileVersion).toEqual(1);
}); });
it('should detect npm Workspaces', async () => {
const fixture = path.join(__dirname, 'fixtures', '21-npm-workspaces/a');
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('npm');
expect(result.lockfileVersion).toEqual(2);
});

View File

@@ -1,11 +0,0 @@
declare module 'intercept-stdout' {
export default function (fn?: InterceptFn): UnhookIntercept
}
interface InterceptFn {
(text: string): string | void
}
interface UnhookIntercept {
(): void
}

View File

@@ -1,5 +0,0 @@
declare module 'promisepipe' {
export default function (
...streams: Array<NodeJS.ReadableStream | NodeJS.WritableStream | NodeJS.ReadWriteStream>
): Promise<void>
}

View File

@@ -10,9 +10,13 @@
## Usage ## Usage
Vercel is the optimal workflow for frontend teams. All-in-one: Static and Jamstack deployment, Serverless Functions, and Global CDN. Vercel is a platform for **static sites and frontend frameworks**, built to integrate with your headless content, commerce, or database.
To install the latest version of Vercel CLI, visit [vercel.com/download](https://vercel.com/download) or run this command: We provide a **frictionless developer experience** to take care of the hard things: deploy instantly, scale automatically, and serve personalized content around the globe.
We make it easy for frontend teams to **develop, preview, and ship** delightful user experiences, where performance is the default.
To install the latest version of Vercel CLI, run this command:
```bash ```bash
npm i -g vercel npm i -g vercel
@@ -26,6 +30,8 @@ cd <PROJECT> # Change directory to the new project
vercel # Deploy to the cloud vercel # Deploy to the cloud
``` ```
Finally, [connect your Git repository to Vercel](https://vercel.com/docs/git) and deploy with `git push`.
## Documentation ## Documentation
For details on how to use Vercel CLI, check out our [documentation](https://vercel.com/docs). For details on how to use Vercel CLI, check out our [documentation](https://vercel.com/docs).

View File

@@ -1,6 +1,6 @@
{ {
"name": "vercel", "name": "vercel",
"version": "23.1.0", "version": "23.1.3-canary.9",
"preferGlobal": true, "preferGlobal": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "The command-line interface for Vercel", "description": "The command-line interface for Vercel",
@@ -12,33 +12,15 @@
}, },
"scripts": { "scripts": {
"preinstall": "node ./scripts/preinstall.js", "preinstall": "node ./scripts/preinstall.js",
"test-unit": "nyc ava test/unit.js test/dev-builder.unit.js test/dev-router.unit.js test/dev-server.unit.js test/dev-validate.unit.js --serial --fail-fast --verbose", "test": "jest",
"test-unit": "jest --coverage --verbose",
"test-integration-cli": "rimraf test/fixtures/integration && ava test/integration.js --serial --fail-fast --verbose", "test-integration-cli": "rimraf test/fixtures/integration && ava test/integration.js --serial --fail-fast --verbose",
"test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose", "test-integration-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
"prepublishOnly": "yarn build", "prepublishOnly": "yarn build",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov", "coverage": "codecov",
"build": "node -r ts-eager/register ./scripts/build.ts", "build": "node -r ts-eager/register ./scripts/build.ts",
"build-dev": "node -r ts-eager/register ./scripts/build.ts --dev" "build-dev": "node -r ts-eager/register ./scripts/build.ts --dev"
}, },
"nyc": {
"include": [
"src/**"
],
"extension": [
".js",
".ts"
],
"require": [
"ts-node/register"
],
"reporter": [
"text",
"html"
],
"sourceMap": true,
"instrument": true,
"all": true
},
"bin": { "bin": {
"vc": "./dist/index.js", "vc": "./dist/index.js",
"vercel": "./dist/index.js" "vercel": "./dist/index.js"
@@ -61,11 +43,11 @@
"node": ">= 12" "node": ">= 12"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "2.12.0", "@vercel/build-utils": "2.12.3-canary.7",
"@vercel/go": "1.2.3", "@vercel/go": "1.2.4-canary.3",
"@vercel/node": "1.12.0", "@vercel/node": "1.12.2-canary.4",
"@vercel/python": "2.0.5", "@vercel/python": "2.0.6-canary.4",
"@vercel/ruby": "1.2.7", "@vercel/ruby": "1.2.8-canary.3",
"update-notifier": "4.1.0" "update-notifier": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
@@ -76,13 +58,16 @@
"@types/ansi-regex": "4.0.0", "@types/ansi-regex": "4.0.0",
"@types/async-retry": "1.2.1", "@types/async-retry": "1.2.1",
"@types/bytes": "3.0.0", "@types/bytes": "3.0.0",
"@types/chance": "1.1.3",
"@types/debug": "0.0.31", "@types/debug": "0.0.31",
"@types/dotenv": "6.1.1", "@types/dotenv": "6.1.1",
"@types/escape-html": "0.0.20", "@types/escape-html": "0.0.20",
"@types/express": "4.17.13",
"@types/fs-extra": "5.0.5", "@types/fs-extra": "5.0.5",
"@types/glob": "7.1.1", "@types/glob": "7.1.1",
"@types/http-proxy": "1.16.2", "@types/http-proxy": "1.16.2",
"@types/inquirer": "7.3.1", "@types/inquirer": "7.3.1",
"@types/jest": "27.0.1",
"@types/load-json-file": "2.0.7", "@types/load-json-file": "2.0.7",
"@types/mime-types": "2.1.0", "@types/mime-types": "2.1.0",
"@types/minimatch": "3.0.3", "@types/minimatch": "3.0.3",
@@ -97,10 +82,12 @@
"@types/semver": "6.0.1", "@types/semver": "6.0.1",
"@types/tar-fs": "1.16.1", "@types/tar-fs": "1.16.1",
"@types/text-table": "0.2.0", "@types/text-table": "0.2.0",
"@types/title": "3.4.1",
"@types/universal-analytics": "0.4.2", "@types/universal-analytics": "0.4.2",
"@types/update-notifier": "5.1.0",
"@types/which": "1.3.2", "@types/which": "1.3.2",
"@types/write-json-file": "2.2.1", "@types/write-json-file": "2.2.1",
"@vercel/frameworks": "0.5.0", "@vercel/frameworks": "0.5.1-canary.5",
"@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",
@@ -115,6 +102,7 @@
"ava": "2.2.0", "ava": "2.2.0",
"bytes": "3.0.0", "bytes": "3.0.0",
"chalk": "4.1.0", "chalk": "4.1.0",
"chance": "1.1.7",
"chokidar": "3.3.1", "chokidar": "3.3.1",
"clipboardy": "2.1.0", "clipboardy": "2.1.0",
"codecov": "3.8.2", "codecov": "3.8.2",
@@ -130,6 +118,7 @@
"escape-html": "1.0.3", "escape-html": "1.0.3",
"esm": "3.1.4", "esm": "3.1.4",
"execa": "3.2.0", "execa": "3.2.0",
"express": "4.17.1",
"fast-deep-equal": "3.1.3", "fast-deep-equal": "3.1.3",
"fs-extra": "7.0.1", "fs-extra": "7.0.1",
"get-port": "5.1.1", "get-port": "5.1.1",
@@ -148,7 +137,6 @@
"ms": "2.1.2", "ms": "2.1.2",
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"npm-package-arg": "6.1.0", "npm-package-arg": "6.1.0",
"nyc": "13.2.0",
"open": "8.2.0", "open": "8.2.0",
"ora": "3.4.0", "ora": "3.4.0",
"pcre-to-regexp": "1.0.0", "pcre-to-regexp": "1.0.0",
@@ -161,7 +149,6 @@
"rimraf": "3.0.2", "rimraf": "3.0.2",
"semver": "5.5.0", "semver": "5.5.0",
"serve-handler": "6.1.1", "serve-handler": "6.1.1",
"sinon": "4.4.2",
"strip-ansi": "5.2.0", "strip-ansi": "5.2.0",
"stripe": "5.1.0", "stripe": "5.1.0",
"tar-fs": "1.16.3", "tar-fs": "1.16.3",
@@ -178,5 +165,19 @@
"which": "2.0.2", "which": "2.0.2",
"write-json-file": "2.2.0", "write-json-file": "2.2.0",
"xdg-app-paths": "5.1.0" "xdg-app-paths": "5.1.0"
},
"jest": {
"preset": "ts-jest",
"globals": {
"ts-jest": {
"diagnostics": false,
"isolatedModules": true
}
},
"verbose": false,
"testEnvironment": "node",
"testMatch": [
"<rootDir>/test/**/*.test.ts"
]
} }
} }

View File

@@ -4,6 +4,7 @@ import { join } from 'path';
import { remove, writeFile } from 'fs-extra'; import { remove, writeFile } from 'fs-extra';
const dirRoot = join(__dirname, '..'); const dirRoot = join(__dirname, '..');
const distRoot = join(dirRoot, 'dist');
async function createConstants() { async function createConstants() {
console.log('Creating constants.ts'); console.log('Creating constants.ts');
@@ -48,13 +49,12 @@ async function main() {
// Do the initial `ncc` build // Do the initial `ncc` build
console.log(); console.log();
const src = join(dirRoot, 'src');
const args = ['ncc', 'build', '--external', 'update-notifier']; const args = ['ncc', 'build', '--external', 'update-notifier'];
if (isDev) { if (isDev) {
args.push('--source-map'); args.push('--source-map');
} }
args.push(src); args.push('src/index.ts');
await execa('yarn', args, { stdio: 'inherit' }); await execa('yarn', args, { stdio: 'inherit', cwd: dirRoot });
// `ncc` has some issues with `@zeit/fun`'s runtime files: // `ncc` has some issues with `@zeit/fun`'s runtime files:
// - Executable bits on the `bootstrap` files appear to be lost: // - Executable bits on the `bootstrap` files appear to be lost:
@@ -72,19 +72,13 @@ async function main() {
dirRoot, dirRoot,
'../../node_modules/@zeit/fun/dist/src/runtimes' '../../node_modules/@zeit/fun/dist/src/runtimes'
); );
const dest = join(dirRoot, 'dist/runtimes'); await cpy('**/*', join(distRoot, 'runtimes'), {
await cpy('**/*', dest, { parents: true, cwd: runtimes }); parents: true,
cwd: runtimes,
});
// Band-aid to delete stuff that `ncc` bundles, but it shouldn't: // Band-aid to bundle stuff that `ncc` neglects to bundle
await cpy(join(dirRoot, 'src/util/projects/VERCEL_DIR_README.txt'), distRoot);
// TypeScript definition files from `@vercel/build-utils`
await remove(join(dirRoot, 'dist', 'dist'));
// The Readme and `package.json` from "config-chain" module
await remove(join(dirRoot, 'dist', 'config-chain'));
// A bunch of source `.ts` files from CLI's `util` directory
await remove(join(dirRoot, 'dist', 'util'));
console.log('Finished building Vercel CLI'); console.log('Finished building Vercel CLI');
} }

View File

@@ -421,7 +421,7 @@ function handleCreateAliasError<T>(
return error; return error;
} }
function getTargetsForAlias(args: string[], { alias }: VercelConfig) { function getTargetsForAlias(args: string[], { alias }: VercelConfig = {}) {
if (args.length) { if (args.length) {
return [args[args.length - 1]] return [args[args.length - 1]]
.map(target => (target.indexOf('.') !== -1 ? toHost(target) : target)) .map(target => (target.indexOf('.') !== -1 ? toHost(target) : target))

View File

@@ -6,12 +6,11 @@ import cardBrands from '../../util/billing/card-brands';
import success from '../../util/output/success'; import success from '../../util/output/success';
import wait from '../../util/output/wait'; import wait from '../../util/output/wait';
import chars from '../../util/output/chars'; import chars from '../../util/output/chars';
import rightPad from '../../util/output/right-pad';
import error from '../../util/output/error'; import error from '../../util/output/error';
const expDateMiddleware = data => data; const expDateMiddleware = data => data;
export default async function({ creditCards, clear = false, contextName }) { export default async function ({ creditCards, clear = false, contextName }) {
const state = { const state = {
error: undefined, error: undefined,
cardGroupLabel: `> ${chalk.bold( cardGroupLabel: `> ${chalk.bold(
@@ -19,13 +18,13 @@ export default async function({ creditCards, clear = false, contextName }) {
)}`, )}`,
name: { name: {
label: rightPad('Full Name', 12), label: 'Full Name'.padEnd(12),
placeholder: 'John Appleseed', placeholder: 'John Appleseed',
validateValue: data => data.trim().length > 0, validateValue: data => data.trim().length > 0,
}, },
cardNumber: { cardNumber: {
label: rightPad('Number', 12), label: 'Number'.padEnd(12),
mask: 'cc', mask: 'cc',
placeholder: '#### #### #### ####', placeholder: '#### #### #### ####',
validateKeypress: (data, value) => /\d/.test(data) && value.length < 19, validateKeypress: (data, value) => /\d/.test(data) && value.length < 19,
@@ -40,7 +39,7 @@ export default async function({ creditCards, clear = false, contextName }) {
}, },
ccv: { ccv: {
label: rightPad('CCV', 12), label: 'CCV'.padEnd(12),
mask: 'ccv', mask: 'ccv',
placeholder: '###', placeholder: '###',
validateValue: data => { validateValue: data => {
@@ -50,7 +49,7 @@ export default async function({ creditCards, clear = false, contextName }) {
}, },
expDate: { expDate: {
label: rightPad('Exp. Date', 12), label: 'Exp. Date'.padEnd(12),
mask: 'expDate', mask: 'expDate',
placeholder: 'mm / yyyy', placeholder: 'mm / yyyy',
middleware: expDateMiddleware, middleware: expDateMiddleware,
@@ -147,9 +146,9 @@ export default async function({ creditCards, clear = false, contextName }) {
console.log( console.log(
success( success(
`${state.cardNumber.brand || `${state.cardNumber.brand || state.cardNumber.card.brand} ending in ${
state.cardNumber.card.brand} ending in ${res.last4 || res.last4 || res.card.last4
res.card.last4} was added to ${chalk.bold(contextName)}` } was added to ${chalk.bold(contextName)}`
) )
); );
} catch (err) { } catch (err) {

View File

@@ -3,7 +3,7 @@ import ms from 'ms';
import plural from 'pluralize'; import plural from 'pluralize';
import { error } from '../../util/error'; import { error } from '../../util/error';
import NowCreditCards from '../../util/credit-cards'; import NowCreditCards from '../../util/credit-cards';
import indent from '../../util/indent'; import indent from '../../util/output/indent';
import listInput from '../../util/input/list'; import listInput from '../../util/input/list';
import success from '../../util/output/success'; import success from '../../util/output/success';
import promptBool from '../../util/input/prompt-bool'; import promptBool from '../../util/input/prompt-bool';

View File

@@ -1,17 +1,72 @@
import ms from 'ms';
import fs from 'fs-extra'; import fs from 'fs-extra';
import { resolve, basename } from 'path'; import bytes from 'bytes';
import { VercelConfig, fileNameSymbol } from '@vercel/client'; import chalk from 'chalk';
import { join, resolve, basename } from 'path';
import { Dictionary, fileNameSymbol, VercelConfig } from '@vercel/client';
import code from '../../util/output/code'; import code from '../../util/output/code';
import highlight from '../../util/output/highlight'; import highlight from '../../util/output/highlight';
import { readLocalConfig } from '../../util/config/files'; import { readLocalConfig } from '../../util/config/files';
import getArgs from '../../util/get-args'; import getArgs from '../../util/get-args';
import { handleError } from '../../util/error'; import { handleError } from '../../util/error';
import { help } from './args';
import deploy from './latest';
import Client from '../../util/client'; import Client from '../../util/client';
import { write as copy } from 'clipboardy';
import { getPrettyError } from '@vercel/build-utils';
import toHumanPath from '../../util/humanize-path';
import Now from '../../util';
import stamp from '../../util/output/stamp';
import createDeploy from '../../util/deploy/create-deploy';
import getDeploymentByIdOrHost from '../../util/deploy/get-deployment-by-id-or-host';
import parseMeta from '../../util/parse-meta';
import linkStyle from '../../util/output/link';
import param from '../../util/output/param';
import {
BuildsRateLimited,
DeploymentNotFound,
DeploymentPermissionDenied,
InvalidDeploymentId,
DomainNotFound,
DomainNotVerified,
DomainPermissionDenied,
DomainVerificationFailed,
InvalidDomain,
TooManyRequests,
UserAborted,
DeploymentsRateLimited,
AliasDomainConfigured,
MissingBuildScript,
ConflictingFilePath,
ConflictingPathSegment,
BuildError,
NotDomainOwner,
} from '../../util/errors-ts';
import { SchemaValidationFailed } from '../../util/errors';
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
import confirm from '../../util/input/confirm';
import editProjectSettings from '../../util/input/edit-project-settings';
import {
getLinkedProject,
linkFolderToProject,
} from '../../util/projects/link';
import getProjectName from '../../util/get-project-name';
import selectOrg from '../../util/input/select-org';
import inputProject from '../../util/input/input-project';
import { prependEmoji, emoji } from '../../util/emoji';
import { inputRootDirectory } from '../../util/input/input-root-directory';
import validatePaths, {
validateRootDirectory,
} from '../../util/validate-paths';
import { getCommandName } from '../../util/pkg-name';
import { getPreferredPreviewURL } from '../../util/deploy/get-preferred-preview-url';
import { Output } from '../../util/output';
import { help } from './args';
export default async (client: Client) => { export default async (client: Client) => {
const { output } = client; const {
apiUrl,
output,
authConfig: { token },
} = client;
let argv = null; let argv = null;
@@ -65,10 +120,7 @@ export default async (client: Client) => {
paths = [process.cwd()]; paths = [process.cwd()];
} }
let localConfig: VercelConfig | null = client.localConfig; let localConfig = client.localConfig || readLocalConfig(paths[0]);
if (!localConfig || localConfig instanceof Error) {
localConfig = readLocalConfig(paths[0]);
}
for (const path of paths) { for (const path of paths) {
try { try {
@@ -105,5 +157,745 @@ export default async (client: Client) => {
} }
} }
return deploy(client, paths, localConfig, argv); const { log, debug, error, warn } = output;
const debugEnabled = argv['--debug'];
const { isTTY } = process.stdout;
const quiet = !isTTY;
// check paths
const pathValidation = await validatePaths(output, paths);
if (!pathValidation.valid) {
return pathValidation.exitCode;
}
const { isFile, path } = pathValidation;
const autoConfirm = argv['--confirm'] || isFile;
// deprecate --name
if (argv['--name']) {
output.print(
`${prependEmoji(
`The ${param(
'--name'
)} option is deprecated (https://vercel.link/name-flag)`,
emoji('warning')
)}\n`
);
}
// retrieve `project` and `org` from .vercel
const link = await getLinkedProject(client, path);
if (link.status === 'error') {
return link.exitCode;
}
let { org, project, status } = link;
let newProjectName = null;
let rootDirectory = project ? project.rootDirectory : null;
let sourceFilesOutsideRootDirectory = true;
if (status === 'not_linked') {
const shouldStartSetup =
autoConfirm ||
(await confirm(
`Set up and deploy ${chalk.cyan(`${toHumanPath(path)}`)}?`,
true
));
if (!shouldStartSetup) {
output.print(`Aborted. Project not set up.\n`);
return 0;
}
try {
org = await selectOrg(
client,
'Which scope do you want to deploy to?',
autoConfirm
);
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
// We use `localConfig` here to read the name
// even though the `vercel.json` file can change
// afterwards, this is fine since the property
// will be deprecated and can be replaced with
// user input.
const detectedProjectName = getProjectName({
argv,
nowConfig: localConfig || {},
isFile,
paths,
});
const projectOrNewProjectName = await inputProject(
client,
org,
detectedProjectName,
autoConfirm
);
if (typeof projectOrNewProjectName === 'string') {
newProjectName = projectOrNewProjectName;
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
} else {
project = projectOrNewProjectName;
rootDirectory = project.rootDirectory;
sourceFilesOutsideRootDirectory = project.sourceFilesOutsideRootDirectory;
// we can already link the project
await linkFolderToProject(
output,
path,
{
projectId: project.id,
orgId: org.id,
},
project.name,
org.slug
);
status = 'linked';
}
}
// At this point `org` should be populated
if (!org) {
throw new Error(`"org" is not defined`);
}
// Set the `contextName` and `currentTeam` as specified by the
// Project Settings, so that API calls happen with the proper scope
const contextName = org.slug;
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
// if we have `sourceFilesOutsideRootDirectory` set to `true`, we use the current path
// and upload the entire directory.
const sourcePath =
rootDirectory && !sourceFilesOutsideRootDirectory
? join(path, rootDirectory)
: path;
if (
rootDirectory &&
(await validateRootDirectory(
output,
path,
sourcePath,
project
? `To change your Project Settings, go to https://vercel.com/${org?.slug}/${project.name}/settings`
: ''
)) === false
) {
return 1;
}
// If Root Directory is used we'll try to read the config
// from there instead and use it if it exists.
if (rootDirectory) {
const rootDirectoryConfig = readLocalConfig(join(path, rootDirectory));
if (rootDirectoryConfig) {
debug(`Read local config from root directory (${rootDirectory})`);
localConfig = rootDirectoryConfig;
} else if (localConfig) {
output.print(
`${prependEmoji(
`The ${highlight(
localConfig[fileNameSymbol]!
)} file should be inside of the provided root directory.`,
emoji('warning')
)}\n`
);
}
}
localConfig = localConfig || {};
if (localConfig.name) {
output.print(
`${prependEmoji(
`The ${code('name')} property in ${highlight(
localConfig[fileNameSymbol]!
)} is deprecated (https://vercel.link/name-prop)`,
emoji('warning')
)}\n`
);
}
// build `env`
const isObject = (item: any) =>
Object.prototype.toString.call(item) === '[object Object]';
// This validation needs to happen on the client side because
// the data is merged with other data before it is passed to the API (which
// also does schema validation).
if (typeof localConfig.env !== 'undefined' && !isObject(localConfig.env)) {
error(
`The ${code('env')} property in ${highlight(
localConfig[fileNameSymbol]!
)} needs to be an object`
);
return 1;
}
if (typeof localConfig.build !== 'undefined') {
if (!isObject(localConfig.build)) {
error(
`The ${code('build')} property in ${highlight(
localConfig[fileNameSymbol]!
)} needs to be an object`
);
return 1;
}
if (
typeof localConfig.build.env !== 'undefined' &&
!isObject(localConfig.build.env)
) {
error(
`The ${code('build.env')} property in ${highlight(
localConfig[fileNameSymbol]!
)} needs to be an object`
);
return 1;
}
}
// build `meta`
const meta = Object.assign(
{},
parseMeta(localConfig.meta),
parseMeta(argv['--meta'])
);
// Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments
const deploymentEnv = Object.assign(
{},
parseEnv(localConfig.env),
parseEnv(argv['--env'])
);
// Merge build env out of `build.env` from vercel.json, and `--build-env` args
const deploymentBuildEnv = Object.assign(
{},
parseEnv(localConfig.build && localConfig.build.env),
parseEnv(argv['--build-env'])
);
// If there's any undefined values, then inherit them from this process
try {
await addProcessEnv(log, deploymentEnv);
await addProcessEnv(log, deploymentBuildEnv);
} catch (err) {
error(err.message);
return 1;
}
// build `regions`
const regionFlag = (argv['--regions'] || '')
.split(',')
.map((s: string) => s.trim())
.filter(Boolean);
const regions = regionFlag.length > 0 ? regionFlag : localConfig.regions;
// build `target`
let target;
if (argv['--target']) {
const deprecatedTarget = argv['--target'];
if (!['staging', 'production'].includes(deprecatedTarget)) {
error(
`The specified ${param('--target')} ${code(
deprecatedTarget
)} is not valid`
);
return 1;
}
if (deprecatedTarget === 'production') {
warn(
'We recommend using the much shorter `--prod` option instead of `--target production` (deprecated)'
);
}
output.debug(`Setting target to ${deprecatedTarget}`);
target = deprecatedTarget;
} else if (argv['--prod']) {
output.debug('Setting target to production');
target = 'production';
}
const currentTeam = org?.type === 'team' ? org.id : undefined;
const now = new Now({
apiUrl,
token,
debug: debugEnabled,
currentTeam,
output,
});
let deployStamp = stamp();
let deployment = null;
try {
const createArgs: any = {
name: project ? project.name : newProjectName,
env: deploymentEnv,
build: { env: deploymentBuildEnv },
forceNew: argv['--force'],
withCache: argv['--with-cache'],
quiet,
wantsPublic: argv['--public'] || localConfig.public,
isFile,
type: null,
nowConfig: localConfig,
regions,
meta,
deployStamp,
target,
skipAutoDetectionConfirmation: autoConfirm,
};
if (!localConfig.builds || localConfig.builds.length === 0) {
// Only add projectSettings for zero config deployments
createArgs.projectSettings = { sourceFilesOutsideRootDirectory };
}
deployment = await createDeploy(
client,
now,
contextName,
[sourcePath],
createArgs,
org,
!project && !isFile,
path
);
if (deployment.code === 'missing_project_settings') {
let { projectSettings, framework } = deployment;
if (rootDirectory) {
projectSettings.rootDirectory = rootDirectory;
}
if (typeof sourceFilesOutsideRootDirectory !== 'undefined') {
projectSettings.sourceFilesOutsideRootDirectory =
sourceFilesOutsideRootDirectory;
}
const settings = await editProjectSettings(
output,
projectSettings,
framework
);
// deploy again, but send projectSettings this time
createArgs.projectSettings = settings;
deployStamp = stamp();
createArgs.deployStamp = deployStamp;
deployment = await createDeploy(
client,
now,
contextName,
[sourcePath],
createArgs,
org,
false,
path
);
}
if (deployment instanceof NotDomainOwner) {
output.error(deployment.message);
return 1;
}
if (deployment instanceof Error) {
output.error(
deployment.message ||
'An unexpected error occurred while deploying your project',
undefined,
'https://vercel.link/help',
'Contact Support'
);
return 1;
}
if (deployment.readyState === 'CANCELED') {
output.print('The deployment has been canceled.\n');
return 1;
}
const deploymentResponse = await getDeploymentByIdOrHost(
client,
contextName,
deployment.id,
'v10'
);
if (
deploymentResponse instanceof DeploymentNotFound ||
deploymentResponse instanceof DeploymentPermissionDenied ||
deploymentResponse instanceof InvalidDeploymentId
) {
output.error(deploymentResponse.message);
return 1;
}
if (deployment === null) {
error('Uploading failed. Please try again.');
return 1;
}
} catch (err) {
debug(`Error: ${err}\n${err.stack}`);
if (err instanceof NotDomainOwner) {
output.error(err.message);
return 1;
}
if (err instanceof DomainNotFound && err.meta && err.meta.domain) {
output.debug(
`The domain ${err.meta.domain} was not found, trying to purchase it`
);
const purchase = await purchaseDomainIfAvailable(
output,
client,
err.meta.domain,
contextName
);
if (purchase === true) {
output.success(`Successfully purchased the domain ${err.meta.domain}!`);
// We exit if the purchase is completed since
// the domain verification can take some time
return 0;
}
if (purchase === false || purchase instanceof UserAborted) {
handleCreateDeployError(output, deployment, localConfig);
return 1;
}
handleCreateDeployError(output, purchase, localConfig);
return 1;
}
if (
err instanceof DomainNotFound ||
err instanceof DomainNotVerified ||
err instanceof NotDomainOwner ||
err instanceof DomainPermissionDenied ||
err instanceof DomainVerificationFailed ||
err instanceof SchemaValidationFailed ||
err instanceof InvalidDomain ||
err instanceof DeploymentNotFound ||
err instanceof BuildsRateLimited ||
err instanceof DeploymentsRateLimited ||
err instanceof AliasDomainConfigured ||
err instanceof MissingBuildScript ||
err instanceof ConflictingFilePath ||
err instanceof ConflictingPathSegment
) {
handleCreateDeployError(output, err, localConfig);
return 1;
}
if (err instanceof BuildError) {
output.error(err.message || 'Build failed');
output.error(
`Check your logs at https://${now.url}/_logs or run ${getCommandName(
`logs ${now.url}`
)}`
);
return 1;
}
if (err.keyword === 'additionalProperties' && err.dataPath === '.scale') {
const { additionalProperty = '' } = err.params || {};
const message = `Invalid DC name for the scale option: ${additionalProperty}`;
error(message);
}
if (err.code === 'size_limit_exceeded') {
const { sizeLimit = 0 } = err;
const message = `File size limit exceeded (${bytes(sizeLimit)})`;
error(message);
return 1;
}
handleError(err);
return 1;
}
return printDeploymentStatus(
output,
client,
deployment,
deployStamp,
!argv['--no-clipboard'],
isFile
);
};
function handleCreateDeployError(
output: Output,
error: Error,
localConfig: VercelConfig
) {
if (error instanceof InvalidDomain) {
output.error(`The domain ${error.meta.domain} is not valid`);
return 1;
}
if (error instanceof DomainVerificationFailed) {
output.error(
`The domain used as a suffix ${chalk.underline(
error.meta.domain
)} is not verified and can't be used as custom suffix.`
);
return 1;
}
if (error instanceof DomainPermissionDenied) {
output.error(
`You don't have permissions to access the domain used as a suffix ${chalk.underline(
error.meta.domain
)}.`
);
return 1;
}
if (error instanceof SchemaValidationFailed) {
const niceError = getPrettyError(error.meta);
const fileName = localConfig[fileNameSymbol] || 'vercel.json';
niceError.message = `Invalid ${fileName} - ${niceError.message}`;
output.prettyError(niceError);
return 1;
}
if (error instanceof TooManyRequests) {
output.error(
`Too many requests detected for ${error.meta.api} API. Try again in ${ms(
error.meta.retryAfter * 1000,
{
long: true,
}
)}.`
);
return 1;
}
if (error instanceof DomainNotVerified) {
output.error(
`The domain used as an alias ${chalk.underline(
error.meta.domain
)} is not verified yet. Please verify it.`
);
return 1;
}
if (error instanceof BuildsRateLimited) {
output.error(error.message);
output.note(
`Run ${getCommandName('upgrade')} to increase your builds limit.`
);
return 1;
}
if (
error instanceof DeploymentNotFound ||
error instanceof NotDomainOwner ||
error instanceof DeploymentsRateLimited ||
error instanceof AliasDomainConfigured ||
error instanceof MissingBuildScript ||
error instanceof ConflictingFilePath ||
error instanceof ConflictingPathSegment
) {
output.error(error.message);
return 1;
}
return error;
}
const addProcessEnv = async (
log: (str: string) => void,
env: typeof process.env
) => {
let val;
for (const key of Object.keys(env)) {
if (typeof env[key] !== 'undefined') {
continue;
}
val = process.env[key];
if (typeof val === 'string') {
log(
`Reading ${chalk.bold(
`"${chalk.bold(key)}"`
)} from your env (as no value was specified)`
);
// Escape value if it begins with @
env[key] = val.replace(/^@/, '\\@');
} else {
throw new Error(
`No value specified for env ${chalk.bold(
`"${chalk.bold(key)}"`
)} and it was not found in your env.`
);
}
}
};
const printDeploymentStatus = async (
output: Output,
client: Client,
{
readyState,
alias: aliasList,
aliasError,
target,
indications,
url: deploymentUrl,
aliasWarning,
}: {
readyState: string;
alias: string[];
aliasError: Error;
target: string;
indications: any;
url: string;
aliasWarning?: {
code: string;
message: string;
link?: string;
action?: string;
};
},
deployStamp: () => string,
isClipboardEnabled: boolean,
isFile: boolean
) => {
indications = indications || [];
const isProdDeployment = target === 'production';
if (readyState !== 'READY') {
output.error(
`Your deployment failed. Please retry later. More: https://err.sh/vercel/deployment-error`
);
return 1;
}
if (aliasError) {
output.warn(
`Failed to assign aliases${
aliasError.message ? `: ${aliasError.message}` : ''
}`
);
} else {
// print preview/production url
let previewUrl: string;
let isWildcard: boolean;
if (!isFile && Array.isArray(aliasList) && aliasList.length > 0) {
const previewUrlInfo = await getPreferredPreviewURL(client, aliasList);
if (previewUrlInfo) {
isWildcard = previewUrlInfo.isWildcard;
previewUrl = previewUrlInfo.previewUrl;
} else {
isWildcard = false;
previewUrl = `https://${deploymentUrl}`;
}
} else {
// fallback to deployment url
isWildcard = false;
previewUrl = `https://${deploymentUrl}`;
}
// copy to clipboard
let isCopiedToClipboard = false;
if (isClipboardEnabled && !isWildcard) {
try {
await copy(previewUrl);
isCopiedToClipboard = true;
} catch (err) {
output.debug(`Error copyind to clipboard: ${err}`);
}
}
output.print(
prependEmoji(
`${isProdDeployment ? 'Production' : 'Preview'}: ${chalk.bold(
previewUrl
)}${
isCopiedToClipboard ? chalk.gray(` [copied to clipboard]`) : ''
} ${deployStamp()}`,
emoji('success')
) + `\n`
);
}
if (aliasWarning?.message) {
indications.push({
type: 'warning',
payload: aliasWarning.message,
link: aliasWarning.link,
action: aliasWarning.action,
});
}
const newline = '\n';
for (let indication of indications) {
const message =
prependEmoji(chalk.dim(indication.payload), emoji(indication.type)) +
newline;
let link = '';
if (indication.link)
link =
chalk.dim(
`${indication.action || 'Learn More'}: ${linkStyle(indication.link)}`
) + newline;
output.print(message + link);
}
};
// Converts `env` Arrays, Strings and Objects into env Objects.
const parseEnv = (env?: string[] | Dictionary<string>) => {
if (!env) {
return {};
}
if (typeof env === 'string') {
// a single `--env` arg comes in as a String
env = [env];
}
if (Array.isArray(env)) {
return env.reduce((o, e) => {
let key;
let value;
const equalsSign = e.indexOf('=');
if (equalsSign === -1) {
key = e;
} else {
key = e.substr(0, equalsSign);
value = e.substr(equalsSign + 1);
}
o[key] = value;
return o;
}, {} as Dictionary<string | undefined>);
}
// assume it's already an Object
return env;
}; };

View File

@@ -1,808 +0,0 @@
import ms from 'ms';
import bytes from 'bytes';
import { join } from 'path';
import { write as copy } from 'clipboardy';
import chalk from 'chalk';
import { Dictionary, fileNameSymbol, VercelConfig } from '@vercel/client';
import { getPrettyError } from '@vercel/build-utils';
import { handleError } from '../../util/error';
import toHumanPath from '../../util/humanize-path';
import Now from '../../util';
import stamp from '../../util/output/stamp';
import createDeploy from '../../util/deploy/create-deploy';
import getDeploymentByIdOrHost from '../../util/deploy/get-deployment-by-id-or-host';
import parseMeta from '../../util/parse-meta';
import code from '../../util/output/code';
import linkStyle from '../../util/output/link';
import param from '../../util/output/param';
import highlight from '../../util/output/highlight';
import {
BuildsRateLimited,
DeploymentNotFound,
DeploymentPermissionDenied,
InvalidDeploymentId,
DomainNotFound,
DomainNotVerified,
DomainPermissionDenied,
DomainVerificationFailed,
InvalidDomain,
TooManyRequests,
UserAborted,
DeploymentsRateLimited,
AliasDomainConfigured,
MissingBuildScript,
ConflictingFilePath,
ConflictingPathSegment,
BuildError,
NotDomainOwner,
} from '../../util/errors-ts';
import { SchemaValidationFailed } from '../../util/errors';
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
import confirm from '../../util/input/confirm';
import editProjectSettings from '../../util/input/edit-project-settings';
import {
getLinkedProject,
linkFolderToProject,
} from '../../util/projects/link';
import getProjectName from '../../util/get-project-name';
import selectOrg from '../../util/input/select-org';
import inputProject from '../../util/input/input-project';
import { prependEmoji, emoji } from '../../util/emoji';
import { inputRootDirectory } from '../../util/input/input-root-directory';
import validatePaths, {
validateRootDirectory,
} from '../../util/validate-paths';
import { readLocalConfig } from '../../util/config/files';
import { getCommandName } from '../../util/pkg-name';
import { getPreferredPreviewURL } from '../../util/deploy/get-preferred-preview-url';
import { Output } from '../../util/output';
import Client from '../../util/client';
const addProcessEnv = async (
log: (str: string) => void,
env: typeof process.env
) => {
let val;
for (const key of Object.keys(env)) {
if (typeof env[key] !== 'undefined') {
continue;
}
val = process.env[key];
if (typeof val === 'string') {
log(
`Reading ${chalk.bold(
`"${chalk.bold(key)}"`
)} from your env (as no value was specified)`
);
// Escape value if it begins with @
env[key] = val.replace(/^@/, '\\@');
} else {
throw new Error(
`No value specified for env ${chalk.bold(
`"${chalk.bold(key)}"`
)} and it was not found in your env.`
);
}
}
};
const printDeploymentStatus = async (
output: Output,
client: Client,
{
readyState,
alias: aliasList,
aliasError,
target,
indications,
url: deploymentUrl,
aliasWarning,
}: {
readyState: string;
alias: string[];
aliasError: Error;
target: string;
indications: any;
url: string;
aliasWarning?: {
code: string;
message: string;
link?: string;
action?: string;
};
},
deployStamp: () => string,
isClipboardEnabled: boolean,
isFile: boolean
) => {
indications = indications || [];
const isProdDeployment = target === 'production';
if (readyState !== 'READY') {
output.error(
`Your deployment failed. Please retry later. More: https://err.sh/vercel/deployment-error`
);
return 1;
}
if (aliasError) {
output.warn(
`Failed to assign aliases${
aliasError.message ? `: ${aliasError.message}` : ''
}`
);
} else {
// print preview/production url
let previewUrl: string;
let isWildcard: boolean;
if (!isFile && Array.isArray(aliasList) && aliasList.length > 0) {
const previewUrlInfo = await getPreferredPreviewURL(client, aliasList);
if (previewUrlInfo) {
isWildcard = previewUrlInfo.isWildcard;
previewUrl = previewUrlInfo.previewUrl;
} else {
isWildcard = false;
previewUrl = `https://${deploymentUrl}`;
}
} else {
// fallback to deployment url
isWildcard = false;
previewUrl = `https://${deploymentUrl}`;
}
// copy to clipboard
let isCopiedToClipboard = false;
if (isClipboardEnabled && !isWildcard) {
try {
await copy(previewUrl);
isCopiedToClipboard = true;
} catch (err) {
output.debug(`Error copyind to clipboard: ${err}`);
}
}
output.print(
prependEmoji(
`${isProdDeployment ? 'Production' : 'Preview'}: ${chalk.bold(
previewUrl
)}${
isCopiedToClipboard ? chalk.gray(` [copied to clipboard]`) : ''
} ${deployStamp()}`,
emoji('success')
) + `\n`
);
}
if (aliasWarning?.message) {
indications.push({
type: 'warning',
payload: aliasWarning.message,
link: aliasWarning.link,
action: aliasWarning.action,
});
}
const newline = '\n';
for (let indication of indications) {
const message =
prependEmoji(chalk.dim(indication.payload), emoji(indication.type)) +
newline;
let link = '';
if (indication.link)
link =
chalk.dim(
`${indication.action || 'Learn More'}: ${linkStyle(indication.link)}`
) + newline;
output.print(message + link);
}
};
// Converts `env` Arrays, Strings and Objects into env Objects.
const parseEnv = (env?: string[] | Dictionary<string>) => {
if (!env) {
return {};
}
if (typeof env === 'string') {
// a single `--env` arg comes in as a String
env = [env];
}
if (Array.isArray(env)) {
return env.reduce((o, e) => {
let key;
let value;
const equalsSign = e.indexOf('=');
if (equalsSign === -1) {
key = e;
} else {
key = e.substr(0, equalsSign);
value = e.substr(equalsSign + 1);
}
o[key] = value;
return o;
}, {} as Dictionary<string | undefined>);
}
// assume it's already an Object
return env;
};
export default async function main(
client: Client,
paths: string[],
localConfig: VercelConfig | null,
argv: any
) {
const {
apiUrl,
output,
authConfig: { token },
} = client;
const { log, debug, error, warn } = output;
const debugEnabled = argv['--debug'];
const { isTTY } = process.stdout;
const quiet = !isTTY;
// check paths
const pathValidation = await validatePaths(output, paths);
if (!pathValidation.valid) {
return pathValidation.exitCode;
}
const { isFile, path } = pathValidation;
const autoConfirm = argv['--confirm'] || isFile;
// deprecate --name
if (argv['--name']) {
output.print(
`${prependEmoji(
`The ${param(
'--name'
)} option is deprecated (https://vercel.link/name-flag)`,
emoji('warning')
)}\n`
);
}
// retrieve `project` and `org` from .vercel
const link = await getLinkedProject(client, path);
if (link.status === 'error') {
return link.exitCode;
}
let { org, project, status } = link;
let newProjectName = null;
let rootDirectory = project ? project.rootDirectory : null;
let sourceFilesOutsideRootDirectory = true;
if (status === 'not_linked') {
const shouldStartSetup =
autoConfirm ||
(await confirm(
`Set up and deploy ${chalk.cyan(`${toHumanPath(path)}`)}?`,
true
));
if (!shouldStartSetup) {
output.print(`Aborted. Project not set up.\n`);
return 0;
}
try {
org = await selectOrg(
client,
'Which scope do you want to deploy to?',
autoConfirm
);
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
// We use `localConfig` here to read the name
// even though the `vercel.json` file can change
// afterwards, this is fine since the property
// will be deprecated and can be replaced with
// user input.
const detectedProjectName = getProjectName({
argv,
nowConfig: localConfig || {},
isFile,
paths,
});
const projectOrNewProjectName = await inputProject(
output,
client,
org,
detectedProjectName,
autoConfirm
);
if (typeof projectOrNewProjectName === 'string') {
newProjectName = projectOrNewProjectName;
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
} else {
project = projectOrNewProjectName;
rootDirectory = project.rootDirectory;
sourceFilesOutsideRootDirectory = project.sourceFilesOutsideRootDirectory;
// we can already link the project
await linkFolderToProject(
output,
path,
{
projectId: project.id,
orgId: org.id,
},
project.name,
org.slug
);
status = 'linked';
}
}
// At this point `org` should be populated
if (!org) {
throw new Error(`"org" is not defined`);
}
// Set the `contextName` and `currentTeam` as specified by the
// Project Settings, so that API calls happen with the proper scope
const contextName = org.slug;
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
// if we have `sourceFilesOutsideRootDirectory` set to `true`, we use the current path
// and upload the entire directory.
const sourcePath =
rootDirectory && !sourceFilesOutsideRootDirectory
? join(path, rootDirectory)
: path;
if (
rootDirectory &&
(await validateRootDirectory(
output,
path,
sourcePath,
project
? `To change your Project Settings, go to https://vercel.com/${org?.slug}/${project.name}/settings`
: ''
)) === false
) {
return 1;
}
// If Root Directory is used we'll try to read the config
// from there instead and use it if it exists.
if (rootDirectory) {
const rootDirectoryConfig = readLocalConfig(join(path, rootDirectory));
if (rootDirectoryConfig) {
debug(`Read local config from root directory (${rootDirectory})`);
localConfig = rootDirectoryConfig;
} else if (localConfig) {
output.print(
`${prependEmoji(
`The ${highlight(
localConfig[fileNameSymbol]!
)} file should be inside of the provided root directory.`,
emoji('warning')
)}\n`
);
}
}
localConfig = localConfig || {};
if (localConfig.name) {
output.print(
`${prependEmoji(
`The ${code('name')} property in ${highlight(
localConfig[fileNameSymbol]!
)} is deprecated (https://vercel.link/name-prop)`,
emoji('warning')
)}\n`
);
}
// build `env`
const isObject = (item: any) =>
Object.prototype.toString.call(item) === '[object Object]';
// This validation needs to happen on the client side because
// the data is merged with other data before it is passed to the API (which
// also does schema validation).
if (typeof localConfig.env !== 'undefined' && !isObject(localConfig.env)) {
error(
`The ${code('env')} property in ${highlight(
localConfig[fileNameSymbol]!
)} needs to be an object`
);
return 1;
}
if (typeof localConfig.build !== 'undefined') {
if (!isObject(localConfig.build)) {
error(
`The ${code('build')} property in ${highlight(
localConfig[fileNameSymbol]!
)} needs to be an object`
);
return 1;
}
if (
typeof localConfig.build.env !== 'undefined' &&
!isObject(localConfig.build.env)
) {
error(
`The ${code('build.env')} property in ${highlight(
localConfig[fileNameSymbol]!
)} needs to be an object`
);
return 1;
}
}
// build `meta`
const meta = Object.assign(
{},
parseMeta(localConfig.meta),
parseMeta(argv['--meta'])
);
// Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments
const deploymentEnv = Object.assign(
{},
parseEnv(localConfig.env),
parseEnv(argv['--env'])
);
// Merge build env out of `build.env` from vercel.json, and `--build-env` args
const deploymentBuildEnv = Object.assign(
{},
parseEnv(localConfig.build && localConfig.build.env),
parseEnv(argv['--build-env'])
);
// If there's any undefined values, then inherit them from this process
try {
await addProcessEnv(log, deploymentEnv);
await addProcessEnv(log, deploymentBuildEnv);
} catch (err) {
error(err.message);
return 1;
}
// build `regions`
const regionFlag = (argv['--regions'] || '')
.split(',')
.map((s: string) => s.trim())
.filter(Boolean);
const regions = regionFlag.length > 0 ? regionFlag : localConfig.regions;
// build `target`
let target;
if (argv['--target']) {
const deprecatedTarget = argv['--target'];
if (!['staging', 'production'].includes(deprecatedTarget)) {
error(
`The specified ${param('--target')} ${code(
deprecatedTarget
)} is not valid`
);
return 1;
}
if (deprecatedTarget === 'production') {
warn(
'We recommend using the much shorter `--prod` option instead of `--target production` (deprecated)'
);
}
output.debug(`Setting target to ${deprecatedTarget}`);
target = deprecatedTarget;
} else if (argv['--prod']) {
output.debug('Setting target to production');
target = 'production';
}
const currentTeam = org?.type === 'team' ? org.id : undefined;
const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam });
let deployStamp = stamp();
let deployment = null;
try {
const createArgs: any = {
name: project ? project.name : newProjectName,
env: deploymentEnv,
build: { env: deploymentBuildEnv },
forceNew: argv['--force'],
withCache: argv['--with-cache'],
quiet,
wantsPublic: argv['--public'] || localConfig.public,
isFile,
type: null,
nowConfig: localConfig,
regions,
meta,
deployStamp,
target,
skipAutoDetectionConfirmation: autoConfirm,
};
if (!localConfig.builds || localConfig.builds.length === 0) {
// Only add projectSettings for zero config deployments
createArgs.projectSettings = { sourceFilesOutsideRootDirectory };
}
deployment = await createDeploy(
client,
now,
contextName,
[sourcePath],
createArgs,
org,
!project && !isFile,
path
);
if (deployment.code === 'missing_project_settings') {
let { projectSettings, framework } = deployment;
if (rootDirectory) {
projectSettings.rootDirectory = rootDirectory;
}
if (typeof sourceFilesOutsideRootDirectory !== 'undefined') {
projectSettings.sourceFilesOutsideRootDirectory =
sourceFilesOutsideRootDirectory;
}
const settings = await editProjectSettings(
output,
projectSettings,
framework
);
// deploy again, but send projectSettings this time
createArgs.projectSettings = settings;
deployStamp = stamp();
createArgs.deployStamp = deployStamp;
deployment = await createDeploy(
client,
now,
contextName,
[sourcePath],
createArgs,
org,
false,
path
);
}
if (deployment instanceof NotDomainOwner) {
output.error(deployment.message);
return 1;
}
if (deployment instanceof Error) {
output.error(
deployment.message ||
'An unexpected error occurred while deploying your project',
undefined,
'https://vercel.link/help',
'Contact Support'
);
return 1;
}
if (deployment.readyState === 'CANCELED') {
output.print('The deployment has been canceled.\n');
return 1;
}
const deploymentResponse = await getDeploymentByIdOrHost(
client,
contextName,
deployment.id,
'v10'
);
if (
deploymentResponse instanceof DeploymentNotFound ||
deploymentResponse instanceof DeploymentPermissionDenied ||
deploymentResponse instanceof InvalidDeploymentId
) {
output.error(deploymentResponse.message);
return 1;
}
if (deployment === null) {
error('Uploading failed. Please try again.');
return 1;
}
} catch (err) {
debug(`Error: ${err}\n${err.stack}`);
if (err instanceof NotDomainOwner) {
output.error(err.message);
return 1;
}
if (err instanceof DomainNotFound && err.meta && err.meta.domain) {
output.debug(
`The domain ${err.meta.domain} was not found, trying to purchase it`
);
const purchase = await purchaseDomainIfAvailable(
output,
client,
err.meta.domain,
contextName
);
if (purchase === true) {
output.success(`Successfully purchased the domain ${err.meta.domain}!`);
// We exit if the purchase is completed since
// the domain verification can take some time
return 0;
}
if (purchase === false || purchase instanceof UserAborted) {
handleCreateDeployError(output, deployment, localConfig);
return 1;
}
handleCreateDeployError(output, purchase, localConfig);
return 1;
}
if (
err instanceof DomainNotFound ||
err instanceof DomainNotVerified ||
err instanceof NotDomainOwner ||
err instanceof DomainPermissionDenied ||
err instanceof DomainVerificationFailed ||
err instanceof SchemaValidationFailed ||
err instanceof InvalidDomain ||
err instanceof DeploymentNotFound ||
err instanceof BuildsRateLimited ||
err instanceof DeploymentsRateLimited ||
err instanceof AliasDomainConfigured ||
err instanceof MissingBuildScript ||
err instanceof ConflictingFilePath ||
err instanceof ConflictingPathSegment
) {
handleCreateDeployError(output, err, localConfig);
return 1;
}
if (err instanceof BuildError) {
output.error(err.message || 'Build failed');
output.error(
`Check your logs at https://${now.url}/_logs or run ${getCommandName(
`logs ${now.url}`
)}`
);
return 1;
}
if (err.keyword === 'additionalProperties' && err.dataPath === '.scale') {
const { additionalProperty = '' } = err.params || {};
const message = `Invalid DC name for the scale option: ${additionalProperty}`;
error(message);
}
if (err.code === 'size_limit_exceeded') {
const { sizeLimit = 0 } = err;
const message = `File size limit exceeded (${bytes(sizeLimit)})`;
error(message);
return 1;
}
handleError(err);
return 1;
}
return printDeploymentStatus(
output,
client,
deployment,
deployStamp,
!argv['--no-clipboard'],
isFile
);
}
function handleCreateDeployError(
output: Output,
error: Error,
localConfig: VercelConfig
) {
if (error instanceof InvalidDomain) {
output.error(`The domain ${error.meta.domain} is not valid`);
return 1;
}
if (error instanceof DomainVerificationFailed) {
output.error(
`The domain used as a suffix ${chalk.underline(
error.meta.domain
)} is not verified and can't be used as custom suffix.`
);
return 1;
}
if (error instanceof DomainPermissionDenied) {
output.error(
`You don't have permissions to access the domain used as a suffix ${chalk.underline(
error.meta.domain
)}.`
);
return 1;
}
if (error instanceof SchemaValidationFailed) {
const niceError = getPrettyError(error.meta);
const fileName = localConfig[fileNameSymbol] || 'vercel.json';
niceError.message = `Invalid ${fileName} - ${niceError.message}`;
output.prettyError(niceError);
return 1;
}
if (error instanceof TooManyRequests) {
output.error(
`Too many requests detected for ${error.meta.api} API. Try again in ${ms(
error.meta.retryAfter * 1000,
{
long: true,
}
)}.`
);
return 1;
}
if (error instanceof DomainNotVerified) {
output.error(
`The domain used as an alias ${chalk.underline(
error.meta.domain
)} is not verified yet. Please verify it.`
);
return 1;
}
if (error instanceof BuildsRateLimited) {
output.error(error.message);
output.note(
`Run ${getCommandName('upgrade')} to increase your builds limit.`
);
return 1;
}
if (
error instanceof DeploymentNotFound ||
error instanceof NotDomainOwner ||
error instanceof DeploymentsRateLimited ||
error instanceof AliasDomainConfigured ||
error instanceof MissingBuildScript ||
error instanceof ConflictingFilePath ||
error instanceof ConflictingPathSegment
) {
output.error(error.message);
return 1;
}
return error;
}

View File

@@ -13,8 +13,7 @@ import setupAndLink from '../../util/link/setup-and-link';
import getSystemEnvValues from '../../util/env/get-system-env-values'; import getSystemEnvValues from '../../util/env/get-system-env-values';
type Options = { type Options = {
'--debug'?: boolean; '--listen': string;
'--listen'?: string;
'--confirm': boolean; '--confirm': boolean;
}; };
@@ -27,7 +26,6 @@ export default async function dev(
const [dir = '.'] = args; const [dir = '.'] = args;
let cwd = resolve(dir); let cwd = resolve(dir);
const listen = parseListen(opts['--listen'] || '3000'); const listen = parseListen(opts['--listen'] || '3000');
const debug = opts['--debug'] || false;
// retrieve dev command // retrieve dev command
let [link, frameworks] = await Promise.all([ let [link, frameworks] = await Promise.all([
@@ -36,17 +34,11 @@ export default async function dev(
]); ]);
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) { if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
const autoConfirm = opts['--confirm'] || false; link = await setupAndLink(client, cwd, {
const forceDelete = false; autoConfirm: opts['--confirm'],
successEmoji: 'link',
link = await setupAndLink( setupMsg: 'Set up and develop',
client, });
cwd,
forceDelete,
autoConfirm,
'link',
'Set up and develop'
);
if (link.status === 'not_linked') { if (link.status === 'not_linked') {
// User aborted project linking questions // User aborted project linking questions
@@ -100,7 +92,6 @@ export default async function dev(
const devServer = new DevServer(cwd, { const devServer = new DevServer(cwd, {
output, output,
debug,
devCommand, devCommand,
frameworkSlug, frameworkSlug,
projectSettings, projectSettings,

View File

@@ -5,7 +5,6 @@ import { User, Team } from '../../types';
import * as ERRORS from '../../util/errors-ts'; import * as ERRORS from '../../util/errors-ts';
import Client from '../../util/client'; import Client from '../../util/client';
import getScope from '../../util/get-scope'; import getScope from '../../util/get-scope';
import withSpinner from '../../util/with-spinner';
import moveOutDomain from '../../util/domains/move-out-domain'; import moveOutDomain from '../../util/domains/move-out-domain';
import isRootDomain from '../../util/is-root-domain'; import isRootDomain from '../../util/is-root-domain';
import textInput from '../../util/input/text'; import textInput from '../../util/input/text';
@@ -13,7 +12,7 @@ import param from '../../util/output/param';
import getDomainAliases from '../../util/alias/get-domain-aliases'; import getDomainAliases from '../../util/alias/get-domain-aliases';
import getDomainByName from '../../util/domains/get-domain-by-name'; import getDomainByName from '../../util/domains/get-domain-by-name';
import promptBool from '../../util/input/prompt-bool'; import promptBool from '../../util/input/prompt-bool';
import getTeams from '../../util/get-teams'; import getTeams from '../../util/teams/get-teams';
import { getCommandName } from '../../util/pkg-name'; import { getCommandName } from '../../util/pkg-name';
type Options = { type Options = {
@@ -106,9 +105,14 @@ export default async function move(
} }
const context = contextName; const context = contextName;
const moveTokenResult = await withSpinner('Moving', () => { output.spinner('Moving');
return moveOutDomain(client, context, domainName, matchId || destination); const moveTokenResult = await moveOutDomain(
}); client,
context,
domainName,
matchId || destination
);
if (moveTokenResult instanceof ERRORS.DomainMoveConflict) { if (moveTokenResult instanceof ERRORS.DomainMoveConflict) {
const { suffix, pendingAsyncPurchase } = moveTokenResult.meta; const { suffix, pendingAsyncPurchase } = moveTokenResult.meta;
if (suffix) { if (suffix) {

View File

@@ -7,7 +7,6 @@ import param from '../../util/output/param';
import transferInDomain from '../../util/domains/transfer-in-domain'; import transferInDomain from '../../util/domains/transfer-in-domain';
import stamp from '../../util/output/stamp'; import stamp from '../../util/output/stamp';
import getAuthCode from '../../util/domains/get-auth-code'; import getAuthCode from '../../util/domains/get-auth-code';
import withSpinner from '../../util/with-spinner';
import getDomainPrice from '../../util/domains/get-domain-price'; import getDomainPrice from '../../util/domains/get-domain-price';
import checkTransfer from '../../util/domains/check-transfer'; import checkTransfer from '../../util/domains/check-transfer';
import promptBool from '../../util/input/prompt-bool'; import promptBool from '../../util/input/prompt-bool';
@@ -89,9 +88,13 @@ export default async function transferIn(
} }
const transferStamp = stamp(); const transferStamp = stamp();
const transferInResult = await withSpinner( output.spinner(`Initiating transfer for domain ${domainName}`);
`Initiating transfer for domain ${domainName}`,
() => transferInDomain(client, domainName, authCode, price) const transferInResult = await transferInDomain(
client,
domainName,
authCode,
price
); );
if (transferInResult instanceof ERRORS.InvalidDomain) { if (transferInResult instanceof ERRORS.InvalidDomain) {

View File

@@ -13,7 +13,6 @@ import {
} from '../../util/env/env-target'; } from '../../util/env/env-target';
import readStandardInput from '../../util/input/read-standard-input'; import readStandardInput from '../../util/input/read-standard-input';
import param from '../../util/output/param'; import param from '../../util/output/param';
import withSpinner from '../../util/with-spinner';
import { emoji, prependEmoji } from '../../util/emoji'; import { emoji, prependEmoji } from '../../util/emoji';
import { isKnownError } from '../../util/env/known-error'; import { isKnownError } from '../../util/env/known-error';
import { getCommandName } from '../../util/pkg-name'; import { getCommandName } from '../../util/pkg-name';
@@ -142,17 +141,16 @@ export default async function add(
const addStamp = stamp(); const addStamp = stamp();
try { try {
await withSpinner('Saving', () => output.spinner('Saving');
addEnvRecord( await addEnvRecord(
output, output,
client, client,
project.id, project.id,
ProjectEnvType.Encrypted, ProjectEnvType.Encrypted,
envName, envName,
envValue, envValue,
envTargets, envTargets,
envGitBranch envGitBranch
)
); );
} catch (error) { } catch (error) {
if (isKnownError(error) && error.serverMessage) { if (isKnownError(error) && error.serverMessage) {

View File

@@ -6,7 +6,6 @@ import Client from '../../util/client';
import stamp from '../../util/output/stamp'; import stamp from '../../util/output/stamp';
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records'; import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
import param from '../../util/output/param'; import param from '../../util/output/param';
import withSpinner from '../../util/with-spinner';
import { join } from 'path'; import { join } from 'path';
import { promises, openSync, closeSync, readSync } from 'fs'; import { promises, openSync, closeSync, readSync } from 'fs';
import { emoji, prependEmoji } from '../../util/emoji'; import { emoji, prependEmoji } from '../../util/emoji';
@@ -83,19 +82,16 @@ export default async function pull(
project.name project.name
)}\n` )}\n`
); );
const pullStamp = stamp();
const [ const pullStamp = stamp();
{ envs: projectEnvs }, output.spinner('Downloading');
{ systemEnvValues },
] = await withSpinner('Downloading', () => const [{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([
Promise.all([ getDecryptedEnvRecords(output, client, project.id),
getDecryptedEnvRecords(output, client, project.id), project.autoExposeSystemEnvs
project.autoExposeSystemEnvs ? getSystemEnvValues(output, client, project.id)
? getSystemEnvValues(output, client, project.id) : { systemEnvValues: [] },
: { systemEnvValues: [] }, ]);
])
);
const records = exposeSystemEnvs( const records = exposeSystemEnvs(
projectEnvs, projectEnvs,
@@ -120,6 +116,7 @@ export default async function pull(
emoji('success') emoji('success')
)}\n` )}\n`
); );
return 0; return 0;
} }

View File

@@ -13,7 +13,6 @@ import {
import Client from '../../util/client'; import Client from '../../util/client';
import stamp from '../../util/output/stamp'; import stamp from '../../util/output/stamp';
import param from '../../util/output/param'; import param from '../../util/output/param';
import withSpinner from '../../util/with-spinner';
import { emoji, prependEmoji } from '../../util/emoji'; import { emoji, prependEmoji } from '../../util/emoji';
import { isKnownError } from '../../util/env/known-error'; import { isKnownError } from '../../util/env/known-error';
import { getCommandName } from '../../util/pkg-name'; import { getCommandName } from '../../util/pkg-name';
@@ -112,9 +111,8 @@ export default async function rm(
const rmStamp = stamp(); const rmStamp = stamp();
try { try {
await withSpinner('Removing', async () => { output.spinner('Removing');
await removeEnvRecord(output, client, project.id, env); await removeEnvRecord(output, client, project.id, env);
});
} catch (error) { } catch (error) {
if (isKnownError(error) && error.serverMessage) { if (isKnownError(error) && error.serverMessage) {
output.error(error.serverMessage); output.error(error.serverMessage);

View File

@@ -10,7 +10,6 @@ export default new Map([
['dns', 'dns'], ['dns', 'dns'],
['domain', 'domains'], ['domain', 'domains'],
['domains', 'domains'], ['domains', 'domains'],
['downgrade', 'upgrade'],
['env', 'env'], ['env', 'env'],
['help', 'help'], ['help', 'help'],
['init', 'init'], ['init', 'init'],

View File

@@ -5,7 +5,6 @@ import getSubcommand from '../../util/get-subcommand';
import Client from '../../util/client'; import Client from '../../util/client';
import handleError from '../../util/handle-error'; import handleError from '../../util/handle-error';
import logo from '../../util/output/logo'; import logo from '../../util/output/logo';
import error from '../../util/output/error';
import init from './init'; import init from './init';
import { getPkgName } from '../../util/pkg-name'; import { getPkgName } from '../../util/pkg-name';
@@ -44,6 +43,7 @@ const help = () => {
}; };
export default async function main(client: Client) { export default async function main(client: Client) {
const { output } = client;
let argv; let argv;
let args; let args;
@@ -64,15 +64,15 @@ export default async function main(client: Client) {
} }
if (argv._.length > 3) { if (argv._.length > 3) {
client.output.error('Too much arguments.'); output.error('Too much arguments.');
return 1; return 1;
} }
try { try {
return await init(client, argv, args); return await init(client, argv, args);
} catch (err) { } catch (err) {
console.log(error(err.message)); output.prettyError(err);
client.output.debug(err.stack); output.debug(err.stack);
return 1; return 1;
} }
} }

View File

@@ -2,16 +2,13 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import tar from 'tar-fs'; import tar from 'tar-fs';
import chalk from 'chalk'; import chalk from 'chalk';
import fetch from 'node-fetch';
// @ts-ignore // @ts-ignore
import listInput from '../../util/input/list'; import listInput from '../../util/input/list';
import listItem from '../../util/output/list-item'; import listItem from '../../util/output/list-item';
import promptBool from '../../util/input/prompt-bool'; import promptBool from '../../util/input/prompt-bool';
import toHumanPath from '../../util/humanize-path'; import toHumanPath from '../../util/humanize-path';
import { Output } from '../../util/output';
import Client from '../../util/client'; import Client from '../../util/client';
import success from '../../util/output/success';
import info from '../../util/output/info'; import info from '../../util/output/info';
import cmd from '../../util/output/cmd'; import cmd from '../../util/output/cmd';
import didYouMean from '../../util/init/did-you-mean'; import didYouMean from '../../util/init/did-you-mean';
@@ -40,7 +37,7 @@ export default async function init(
const [name, dir] = args; const [name, dir] = args;
const force = opts['-f'] || opts['--force']; const force = opts['-f'] || opts['--force'];
const examples = await fetchExampleList(output); const examples = await fetchExampleList(client);
if (!examples) { if (!examples) {
throw new Error(`Could not fetch example list.`); throw new Error(`Could not fetch example list.`);
@@ -56,47 +53,37 @@ export default async function init(
return 0; return 0;
} }
return extractExample(output, chosen, dir, force); return extractExample(client, chosen, dir, force);
} }
if (exampleList.includes(name)) { if (exampleList.includes(name)) {
return extractExample(output, name, dir, force); return extractExample(client, name, dir, force);
} }
const oldExample = examples.find(x => !x.visible && x.name === name); const oldExample = examples.find(x => !x.visible && x.name === name);
if (oldExample) { if (oldExample) {
return extractExample(output, name, dir, force, 'v1'); return extractExample(client, name, dir, force, 'v1');
} }
const found = await guess(exampleList, name); const found = await guess(exampleList, name);
if (typeof found === 'string') { if (typeof found === 'string') {
return extractExample(output, found, dir, force); return extractExample(client, found, dir, force);
} }
console.log(info('No changes made.')); output.log(info('No changes made.'));
return 0; return 0;
} }
/** /**
* Fetch example list json * Fetch example list json
*/ */
async function fetchExampleList(output: Output) { async function fetchExampleList(client: Client) {
output.spinner('Fetching examples'); client.output.spinner('Fetching examples');
const url = `${EXAMPLE_API}/v2/list.json`; const url = `${EXAMPLE_API}/v2/list.json`;
try { const body = await client.fetch<Example[]>(url);
const resp = await fetch(url); return body;
output.stopSpinner();
if (resp.status !== 200) {
throw new Error(`Failed fetching list.json (${resp.statusText}).`);
}
return (await resp.json()) as Example[];
} catch (e) {
output.stopSpinner();
}
} }
/** /**
@@ -119,31 +106,33 @@ async function chooseFromDropdown(message: string, exampleList: string[]) {
* Extract example to directory * Extract example to directory
*/ */
async function extractExample( async function extractExample(
output: Output, client: Client,
name: string, name: string,
dir: string, dir: string,
force?: boolean, force?: boolean,
ver: string = 'v2' ver: string = 'v2'
) { ) {
const { output } = client;
const folder = prepareFolder(process.cwd(), dir || name, force); const folder = prepareFolder(process.cwd(), dir || name, force);
output.spinner(`Fetching ${name}`); output.spinner(`Fetching ${name}`);
const url = `${EXAMPLE_API}/${ver}/download/${name}.tar.gz`; const url = `${EXAMPLE_API}/${ver}/download/${name}.tar.gz`;
return fetch(url) return client
.then(async resp => { .fetch(url, { json: false })
.then(async res => {
output.stopSpinner(); output.stopSpinner();
if (resp.status !== 200) { if (res.status !== 200) {
throw new Error(`Could not get ${name}.tar.gz`); throw new Error(`Could not get ${name}.tar.gz`);
} }
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
const extractor = tar.extract(folder); const extractor = tar.extract(folder);
resp.body.on('error', reject); res.body.on('error', reject);
extractor.on('error', reject); extractor.on('error', reject);
extractor.on('finish', resolve); extractor.on('finish', resolve);
resp.body.pipe(extractor); res.body.pipe(extractor);
}); });
const successLog = `Initialized "${chalk.bold( const successLog = `Initialized "${chalk.bold(
@@ -158,7 +147,7 @@ async function extractExample(
`cd ${folderRel}` `cd ${folderRel}`
)} and run ${getCommandName()}.` )} and run ${getCommandName()}.`
); );
console.log(success(`${successLog}\n${deployHint}`)); output.success(`${successLog}\n${deployHint}`);
return 0; return 0;
}) })
.catch(e => { .catch(e => {

View File

@@ -11,6 +11,7 @@ import { getPkgName, getCommandName } from '../util/pkg-name';
import Client from '../util/client'; import Client from '../util/client';
import { getDeployment } from '../util/get-deployment'; import { getDeployment } from '../util/get-deployment';
import { Deployment } from '@vercel/client'; import { Deployment } from '@vercel/client';
import { Build } from '../types';
const help = () => { const help = () => {
console.log(` console.log(`
@@ -116,7 +117,7 @@ export default async function main(client: Client) {
const { builds } = const { builds } =
deployment.version === 2 deployment.version === 2
? await client.fetch(`/v1/now/deployments/${id}/builds`) ? await client.fetch<{ builds: Build[] }>(`/v1/deployments/${id}/builds`)
: { builds: [] }; : { builds: [] };
log( log(
@@ -146,7 +147,8 @@ export default async function main(client: Client) {
for (const build of builds) { for (const build of builds) {
const { id, createdAt, readyStateAt } = build; const { id, createdAt, readyStateAt } = build;
times[id] = createdAt ? elapsed(readyStateAt - createdAt) : null; times[id] =
createdAt && readyStateAt ? elapsed(readyStateAt - createdAt) : null;
} }
print(chalk.bold(' Builds\n\n')); print(chalk.bold(' Builds\n\n'));

View File

@@ -1,8 +1,6 @@
import chalk from 'chalk'; import chalk from 'chalk';
import Client from '../../util/client'; import Client from '../../util/client';
import getArgs from '../../util/get-args'; import getArgs from '../../util/get-args';
import getSubcommand from '../../util/get-subcommand';
import handleError from '../../util/handle-error';
import logo from '../../util/output/logo'; import logo from '../../util/output/logo';
import { getPkgName } from '../../util/pkg-name'; import { getPkgName } from '../../util/pkg-name';
import setupAndLink from '../../util/link/setup-and-link'; import setupAndLink from '../../util/link/setup-and-link';
@@ -24,6 +22,9 @@ const help = () => {
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( -t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN' 'TOKEN'
)} Login token )} Login token
-p ${chalk.bold.underline('NAME')}, --project=${chalk.bold.underline(
'NAME'
)} Project name
--confirm Confirm default options and skip questions --confirm Confirm default options and skip questions
${chalk.dim('Examples:')} ${chalk.dim('Examples:')}
@@ -44,40 +45,26 @@ const help = () => {
`); `);
}; };
const COMMAND_CONFIG = {
// No subcommands yet
};
export default async function main(client: Client) { export default async function main(client: Client) {
let argv; const argv = getArgs(client.argv.slice(2), {
'--confirm': Boolean,
try { '--project': String,
argv = getArgs(client.argv.slice(2), { '-p': '--project',
'--confirm': Boolean, });
});
} catch (error) {
handleError(error);
return 1;
}
if (argv['--help']) { if (argv['--help']) {
help(); help();
return 2; return 2;
} }
const { args } = getSubcommand(argv._.slice(1), COMMAND_CONFIG); const cwd = argv._[1] || process.cwd();
const path = args[0] || process.cwd(); const link = await setupAndLink(client, cwd, {
const autoConfirm = argv['--confirm'] || false; forceDelete: true,
const forceDelete = true; autoConfirm: argv['--confirm'],
projectName: argv['--project'],
const link = await setupAndLink( successEmoji: 'success',
client, setupMsg: 'Set up',
path, });
forceDelete,
autoConfirm,
'success',
'Set up'
);
if (link.status === 'error') { if (link.status === 'error') {
return link.exitCode; return link.exitCode;

View File

@@ -92,8 +92,8 @@ export default async function main(client: Client) {
return 1; return 1;
} }
let app: string | null = argv._[1]; let app: string | undefined = argv._[1];
let host: string | null = null; let host: string | undefined = undefined;
if (argv['--help']) { if (argv['--help']) {
help(); help();
@@ -156,7 +156,7 @@ export default async function main(client: Client) {
return 1; return 1;
} }
app = null; app = undefined;
host = asHost; host = asHost;
} }

View File

@@ -2,7 +2,6 @@ import { validate as validateEmail } from 'email-validator';
import chalk from 'chalk'; import chalk from 'chalk';
import hp from '../util/humanize-path'; import hp from '../util/humanize-path';
import getArgs from '../util/get-args'; import getArgs from '../util/get-args';
import handleError from '../util/handle-error';
import logo from '../util/output/logo'; import logo from '../util/output/logo';
import prompt from '../util/login/prompt'; import prompt from '../util/login/prompt';
import doSamlLogin from '../util/login/saml'; import doSamlLogin from '../util/login/saml';
@@ -52,20 +51,14 @@ const help = () => {
}; };
export default async function login(client: Client): Promise<number> { export default async function login(client: Client): Promise<number> {
let argv;
const { output } = client; const { output } = client;
try { const argv = getArgs(client.argv.slice(2), {
argv = getArgs(client.argv.slice(2), { '--oob': Boolean,
'--oob': Boolean, '--github': Boolean,
'--github': Boolean, '--gitlab': Boolean,
'--gitlab': Boolean, '--bitbucket': Boolean,
'--bitbucket': Boolean, });
});
} catch (err) {
handleError(err);
return 1;
}
if (argv['--help']) { if (argv['--help']) {
help(); help();
@@ -115,8 +108,7 @@ export default async function login(client: Client): Promise<number> {
} }
} }
// When `result` is a string it's the user's authentication token. // Save the user's authentication token to the configuration file.
// It needs to be saved to the configuration file.
client.authConfig.token = result.token; client.authConfig.token = result.token;
writeToAuthConfigFile(client.authConfig); writeToAuthConfigFile(client.authConfig);
@@ -124,9 +116,9 @@ export default async function login(client: Client): Promise<number> {
output.debug(`Saved credentials in "${hp(getGlobalPathConfig())}"`); output.debug(`Saved credentials in "${hp(getGlobalPathConfig())}"`);
console.log( output.print(
`${chalk.cyan('Congratulations!')} ` + `${chalk.cyan('Congratulations!')} ` +
`You are now logged in. In order to deploy something, run ${getCommandName()}.` `You are now logged in. In order to deploy something, run ${getCommandName()}.\n`
); );
output.print( output.print(

View File

@@ -1,21 +1,23 @@
import chalk from 'chalk'; import chalk from 'chalk';
import stamp from '../../util/output/stamp.ts'; import stamp from '../../util/output/stamp';
import info from '../../util/output/info'; import info from '../../util/output/info';
import rightPad from '../../util/output/right-pad';
import eraseLines from '../../util/output/erase-lines'; import eraseLines from '../../util/output/erase-lines';
import chars from '../../util/output/chars'; import chars from '../../util/output/chars';
import note from '../../util/output/note'; import note from '../../util/output/note';
import textInput from '../../util/input/text'; import textInput from '../../util/input/text';
import invite from './invite'; import invite from './invite';
import { writeToConfigFile } from '../../util/config/files'; import { writeToConfigFile } from '../../util/config/files';
import { getPkgName, getCommandName } from '../../util/pkg-name.ts'; import { getPkgName, getCommandName } from '../../util/pkg-name';
import Client from '../../util/client';
import createTeam from '../../util/teams/create-team';
import patchTeam from '../../util/teams/patch-team';
const validateSlugKeypress = (data, value) => const validateSlugKeypress = (data: string, value: string) =>
// TODO: the `value` here should contain the current value + the keypress // TODO: the `value` here should contain the current value + the keypress
// should be fixed on utils/input/text.js // should be fixed on utils/input/text.js
/^[a-zA-Z]+[a-zA-Z0-9_-]*$/.test(value + data); /^[a-zA-Z]+[a-zA-Z0-9_-]*$/.test(value + data);
const validateNameKeypress = (data, value) => const validateNameKeypress = (data: string, value: string) =>
// TODO: the `value` here should contain the current value + the keypress // TODO: the `value` here should contain the current value + the keypress
// should be fixed on utils/input/text.js // should be fixed on utils/input/text.js
/^[ a-zA-Z0-9_-]+$/.test(value + data); /^[ a-zA-Z0-9_-]+$/.test(value + data);
@@ -30,17 +32,17 @@ const gracefulExit = () => {
return 0; return 0;
}; };
const teamUrlPrefix = rightPad('Team URL', 14) + chalk.gray('vercel.com/'); const teamUrlPrefix = 'Team URL'.padEnd(14) + chalk.gray('vercel.com/');
const teamNamePrefix = rightPad('Team Name', 14); const teamNamePrefix = 'Team Name'.padEnd(14);
export default async function add(client, teams) { export default async function add(client: Client): Promise<number> {
let slug; let slug;
let team; let team;
let elapsed; let elapsed;
const { output } = client; const { output } = client;
output.log( output.log(
`Pick a team identifier for its url (e.g.: ${chalk.cyan( `Pick a team identifier for its URL (e.g.: ${chalk.cyan(
'`vercel.com/acme`' '`vercel.com/acme`'
)})` )})`
); );
@@ -66,14 +68,12 @@ export default async function add(client, teams) {
elapsed = stamp(); elapsed = stamp();
output.spinner(teamUrlPrefix + slug); output.spinner(teamUrlPrefix + slug);
let res;
try { try {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
res = await teams.create({ slug }); team = await createTeam(client, { slug });
team = res;
} catch (err) { } catch (err) {
output.stopSpinner(); output.stopSpinner();
process.stdout.write(eraseLines(2)); output.print(eraseLines(2));
output.error(err.message); output.error(err.message);
} }
} while (!team); } while (!team);
@@ -104,11 +104,12 @@ export default async function add(client, teams) {
elapsed = stamp(); elapsed = stamp();
output.spinner(teamNamePrefix + name); output.spinner(teamNamePrefix + name);
const res = await teams.edit({ id: team.id, name }); const res = await patchTeam(client, team.id, { name });
output.stopSpinner(); output.stopSpinner();
process.stdout.write(eraseLines(2)); process.stdout.write(eraseLines(2));
/*
if (res.error) { if (res.error) {
output.error(res.error.message); output.error(res.error.message);
output.log(`${chalk.red(`${teamNamePrefix}`)}${name}`); output.log(`${chalk.red(`${teamNamePrefix}`)}${name}`);
@@ -117,33 +118,25 @@ export default async function add(client, teams) {
// TODO: maybe we want to ask the user to retry? not sure if // TODO: maybe we want to ask the user to retry? not sure if
// there's a scenario where that would be wanted // there's a scenario where that would be wanted
} }
*/
team = Object.assign(team, res); team = Object.assign(team, res);
output.success(`Team name saved ${elapsed()}`); output.success(`Team name saved ${elapsed()}`);
output.log(`${chalk.cyan(`${chars.tick} `) + teamNamePrefix + team.name}\n`); output.log(`${chalk.cyan(`${chars.tick} `) + teamNamePrefix + team.name}\n`);
output.spinner('Saving');
// Update config file // Update config file
const configCopy = Object.assign({}, client.config); output.spinner('Saving');
client.config.currentTeam = team.id;
if (configCopy.sh) { writeToConfigFile(client.config);
configCopy.sh.currentTeam = team;
} else {
configCopy.currentTeam = team.id;
}
writeToConfigFile(configCopy);
output.stopSpinner(); output.stopSpinner();
await invite(client, { _: [] }, teams, { await invite(client, [], {
introMsg: 'Invite your teammates! When done, press enter on an empty field', introMsg: 'Invite your teammates! When done, press enter on an empty field',
noopMsg: `You can invite teammates later by running ${getCommandName( noopMsg: `You can invite teammates later by running ${getCommandName(
`teams invite` `teams invite`
)}`, )}`,
}); });
gracefulExit(); return gracefulExit();
} }

View File

@@ -1,6 +1,5 @@
import chalk from 'chalk'; import chalk from 'chalk';
import error from '../../util/output/error'; import error from '../../util/output/error';
import NowTeams from '../../util/teams';
import logo from '../../util/output/logo'; import logo from '../../util/output/logo';
import list from './list'; import list from './list';
import add from './add'; import add from './add';
@@ -8,7 +7,6 @@ import change from './switch';
import invite from './invite'; import invite from './invite';
import { getPkgName } from '../../util/pkg-name'; import { getPkgName } from '../../util/pkg-name';
import getArgs from '../../util/get-args'; import getArgs from '../../util/get-args';
import handleError from '../../util/handle-error';
import Client from '../../util/client'; import Client from '../../util/client';
const help = () => { const help = () => {
@@ -61,28 +59,11 @@ const help = () => {
`); `);
}; };
let argv;
let debug;
let apiUrl;
let subcommand;
export default async (client: Client) => { export default async (client: Client) => {
try { let subcommand;
argv = getArgs(client.argv.slice(2), {
'--since': String,
'--until': String,
'--next': Number,
'-N': '--next',
});
} catch (error) {
handleError(error);
return 1;
}
debug = argv['--debug']; const argv = getArgs(client.argv.slice(2), undefined, { permissive: true });
apiUrl = client.apiUrl; const isSwitch = argv._[0] === 'switch';
const isSwitch = argv._[0] && argv._[0] === 'switch';
argv._ = argv._.slice(1); argv._ = argv._.slice(1);
@@ -97,19 +78,11 @@ export default async (client: Client) => {
return 2; return 2;
} }
const { let exitCode = 0;
authConfig: { token },
config,
} = client;
const { currentTeam } = config;
const teams = new NowTeams({ apiUrl, token, debug, currentTeam });
let exitCode;
switch (subcommand) { switch (subcommand) {
case 'list': case 'list':
case 'ls': { case 'ls': {
exitCode = await list(client, argv, teams); exitCode = await list(client);
break; break;
} }
case 'switch': case 'switch':
@@ -119,12 +92,12 @@ export default async (client: Client) => {
} }
case 'add': case 'add':
case 'create': { case 'create': {
exitCode = await add(client, teams); exitCode = await add(client);
break; break;
} }
case 'invite': { case 'invite': {
exitCode = await invite(client, argv, teams); exitCode = await invite(client, argv._);
break; break;
} }
default: { default: {
@@ -137,6 +110,5 @@ export default async (client: Client) => {
help(); help();
} }
} }
teams.close(); return exitCode;
return exitCode || 0;
}; };

View File

@@ -1,16 +1,19 @@
import chalk from 'chalk'; import chalk from 'chalk';
import { email as regexEmail } from '../../util/input/regexes'; import Client from '../../util/client';
import cmd from '../../util/output/cmd.ts'; import cmd from '../../util/output/cmd';
import stamp from '../../util/output/stamp.ts'; import stamp from '../../util/output/stamp';
import param from '../../util/output/param.ts'; import param from '../../util/output/param';
import chars from '../../util/output/chars'; import chars from '../../util/output/chars';
import rightPad from '../../util/output/right-pad';
import textInput from '../../util/input/text'; import textInput from '../../util/input/text';
import eraseLines from '../../util/output/erase-lines'; import eraseLines from '../../util/output/erase-lines';
import getUser from '../../util/get-user.ts'; import getUser from '../../util/get-user';
import { getCommandName } from '../../util/pkg-name.ts'; import { getCommandName } from '../../util/pkg-name';
import { email as regexEmail } from '../../util/input/regexes';
import getTeams from '../../util/teams/get-teams';
import inviteUserToTeam from '../../util/teams/invite-user-to-team';
const validateEmail = data => regexEmail.test(data.trim()) || data.length === 0; const validateEmail = (data: string) =>
regexEmail.test(data.trim()) || data.length === 0;
const domains = Array.from( const domains = Array.from(
new Set([ new Set([
@@ -29,12 +32,12 @@ const domains = Array.from(
]) ])
); );
const emailAutoComplete = (value, teamSlug) => { const emailAutoComplete = (value: string, teamSlug: string) => {
const parts = value.split('@'); const parts = value.split('@');
if (parts.length === 2 && parts[1].length > 0) { if (parts.length === 2 && parts[1].length > 0) {
const [, host] = parts; const [, host] = parts;
let suggestion = false; let suggestion: string | false = false;
domains.unshift(teamSlug); domains.unshift(teamSlug);
for (const domain of domains) { for (const domain of domains) {
@@ -52,17 +55,16 @@ const emailAutoComplete = (value, teamSlug) => {
}; };
export default async function invite( export default async function invite(
client, client: Client,
argv, emails: string[] = [],
teams, { introMsg = '', noopMsg = 'No changes made' } = {}
{ introMsg, noopMsg = 'No changes made' } = {} ): Promise<number> {
) {
const { config, output } = client; const { config, output } = client;
const { currentTeam: currentTeamId } = config; const { currentTeam: currentTeamId } = config;
output.spinner('Fetching teams'); output.spinner('Fetching teams');
const list = (await teams.ls()).teams; const teams = await getTeams(client);
const currentTeam = list.find(team => team.id === currentTeamId); const currentTeam = teams.find(team => team.id === currentTeamId);
output.spinner('Fetching user information'); output.spinner('Fetching user information');
let user; let user;
@@ -94,8 +96,8 @@ export default async function invite(
introMsg || `Inviting team members to ${chalk.bold(currentTeam.name)}` introMsg || `Inviting team members to ${chalk.bold(currentTeam.name)}`
); );
if (argv._.length > 0) { if (emails.length > 0) {
for (const email of argv._) { for (const email of emails) {
if (regexEmail.test(email)) { if (regexEmail.test(email)) {
output.spinner(email); output.spinner(email);
const elapsed = stamp(); const elapsed = stamp();
@@ -103,8 +105,8 @@ export default async function invite(
try { try {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const res = await teams.inviteUser({ teamId: currentTeam.id, email }); const res = await inviteUserToTeam(client, currentTeam.id, email);
userInfo = res.name || res.username; userInfo = res.username;
} catch (err) { } catch (err) {
if (err.code === 'user_not_found') { if (err.code === 'user_not_found') {
output.error(`No user exists with the email address "${email}".`); output.error(`No user exists with the email address "${email}".`);
@@ -123,12 +125,11 @@ export default async function invite(
output.log(`${chalk.red(`${email}`)} ${chalk.gray('[invalid]')}`); output.log(`${chalk.red(`${email}`)} ${chalk.gray('[invalid]')}`);
} }
} }
return; return 0;
} }
const inviteUserPrefix = rightPad('Invite User', 14); const inviteUserPrefix = 'Invite User'.padEnd(14);
const sentEmailPrefix = rightPad('Sent Email', 14); const sentEmailPrefix = 'Sent Email'.padEnd(14);
const emails = [];
let hasError = false; let hasError = false;
let email; let email;
do { do {
@@ -151,12 +152,12 @@ export default async function invite(
output.spinner(inviteUserPrefix + email); output.spinner(inviteUserPrefix + email);
try { try {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const { name, username } = await teams.inviteUser({ const { username } = await inviteUserToTeam(
teamId: currentTeam.id, client,
email, currentTeam.id,
}); email
const userInfo = name || username; );
email = `${email}${userInfo ? ` (${userInfo})` : ''} ${elapsed()}`; email = `${email}${username ? ` (${username})` : ''} ${elapsed()}`;
emails.push(email); emails.push(email);
output.log(`${chalk.cyan(chars.tick)} ${sentEmailPrefix}${email}`); output.log(`${chalk.cyan(chars.tick)} ${sentEmailPrefix}${email}`);
if (hasError) { if (hasError) {
@@ -194,4 +195,6 @@ export default async function invite(
output.log(`${chalk.cyan(chars.tick)} ${inviteUserPrefix}${email}`); output.log(`${chalk.cyan(chars.tick)} ${inviteUserPrefix}${email}`);
} }
} }
return 0;
} }

View File

@@ -1,22 +1,41 @@
import chars from '../../util/output/chars'; import chars from '../../util/output/chars';
import table from '../../util/output/table'; import table from '../../util/output/table';
import getUser from '../../util/get-user.ts'; import getUser from '../../util/get-user';
import getTeams from '../../util/teams/get-teams';
import getPrefixedFlags from '../../util/get-prefixed-flags'; import getPrefixedFlags from '../../util/get-prefixed-flags';
import { getPkgName } from '../../util/pkg-name.ts'; import { getPkgName } from '../../util/pkg-name';
import getCommandFlags from '../../util/get-command-flags'; import getCommandFlags from '../../util/get-command-flags';
import cmd from '../../util/output/cmd.ts'; import cmd from '../../util/output/cmd';
import Client from '../../util/client';
import getArgs from '../../util/get-args';
export default async function list(client, argv, teams) { export default async function list(client: Client): Promise<number> {
const { config, output } = client; const { config, output } = client;
const { next } = argv;
const argv = getArgs(client.argv.slice(2), {
'--since': String,
'--until': String,
'--count': Number,
'--next': Number,
'-C': '--count',
'-N': '--next',
});
const next = argv['--next'];
const count = argv['--count'];
if (typeof next !== 'undefined' && !Number.isInteger(next)) { if (typeof next !== 'undefined' && !Number.isInteger(next)) {
output.error('Please provide a number for flag --next'); output.error('Please provide a number for flag `--next`');
return 1;
}
if (typeof count !== 'undefined' && !Number.isInteger(next)) {
output.error('Please provide a number for flag `--count`');
return 1; return 1;
} }
output.spinner('Fetching teams'); output.spinner('Fetching teams');
const { teams: list, pagination } = await teams.ls({ const { teams, pagination } = await getTeams(client, {
next, next,
apiVersion: 2, apiVersion: 2,
}); });
@@ -37,40 +56,31 @@ export default async function list(client, argv, teams) {
} }
if (accountIsCurrent) { if (accountIsCurrent) {
currentTeam = { currentTeam = user.uid;
slug: user.username || user.email,
};
} }
const teamList = list.map(({ slug, name }) => ({ const teamList = teams.map(({ id, slug, name }) => ({
id,
name, name,
value: slug, value: slug,
current: slug === currentTeam.slug ? chars.tick : '', current: id === currentTeam ? chars.tick : '',
})); }));
teamList.unshift({ teamList.unshift({
id: user.uid,
name: user.email, name: user.email,
value: user.username || user.email, value: user.username || user.email,
current: (accountIsCurrent && chars.tick) || '', current: accountIsCurrent ? chars.tick : '',
}); });
// Let's bring the current team to the beginning of the list // Bring the current Team to the beginning of the list
if (!accountIsCurrent) { if (!accountIsCurrent) {
const index = teamList.findIndex( const index = teamList.findIndex(choice => choice.id === currentTeam);
choice => choice.value === currentTeam.slug
);
const choice = teamList.splice(index, 1)[0]; const choice = teamList.splice(index, 1)[0];
teamList.unshift(choice); teamList.unshift(choice);
} }
// Printing // Printing
const count = teamList.length;
if (!count) {
// Maybe should not happen
output.error(`No teams found`);
return 1;
}
output.stopSpinner(); output.stopSpinner();
console.log(); // empty line console.log(); // empty line
@@ -80,7 +90,7 @@ export default async function list(client, argv, teams) {
[1, 5] [1, 5]
); );
if (pagination && pagination.count === 20) { if (pagination?.count === 20) {
const prefixedArgs = getPrefixedFlags(argv); const prefixedArgs = getPrefixedFlags(argv);
const flags = getCommandFlags(prefixedArgs, ['_', '--next', '-N', '-d']); const flags = getCommandFlags(prefixedArgs, ['_', '--next', '-N', '-d']);
const nextCmd = `${getPkgName()} teams ls${flags} --next ${ const nextCmd = `${getPkgName()} teams ls${flags} --next ${
@@ -89,4 +99,6 @@ export default async function list(client, argv, teams) {
console.log(); // empty line console.log(); // empty line
output.log(`To display the next page run ${cmd(nextCmd)}`); output.log(`To display the next page run ${cmd(nextCmd)}`);
} }
return 0;
} }

View File

@@ -5,7 +5,7 @@ import chalk from 'chalk';
import Client from '../../util/client'; import Client from '../../util/client';
import { emoji } from '../../util/emoji'; import { emoji } from '../../util/emoji';
import getUser from '../../util/get-user'; import getUser from '../../util/get-user';
import getTeams from '../../util/get-teams'; import getTeams from '../../util/teams/get-teams';
import listInput from '../../util/input/list'; import listInput from '../../util/input/list';
import { Team, GlobalConfig } from '../../types'; import { Team, GlobalConfig } from '../../types';
import { writeToConfigFile } from '../../util/config/files'; import { writeToConfigFile } from '../../util/config/files';

View File

@@ -3,7 +3,6 @@ import logo from '../util/output/logo';
import getScope from '../util/get-scope'; import getScope from '../util/get-scope';
import { getPkgName } from '../util/pkg-name'; import { getPkgName } from '../util/pkg-name';
import getArgs from '../util/get-args'; import getArgs from '../util/get-args';
import handleError from '../util/handle-error';
import Client from '../util/client'; import Client from '../util/client';
const help = () => { const help = () => {
@@ -32,16 +31,9 @@ const help = () => {
`); `);
}; };
export default async (client: Client) => { export default async (client: Client): Promise<number> => {
const { output } = client; const { output } = client;
let argv; const argv = getArgs(client.argv.slice(2), {});
try {
argv = getArgs(client.argv.slice(2), {});
} catch (error) {
handleError(error);
return 1;
}
argv._ = argv._.slice(1); argv._ = argv._.slice(1);
if (argv['--help'] || argv._[0] === 'help') { if (argv['--help'] || argv._[0] === 'help') {
@@ -62,9 +54,13 @@ export default async (client: Client) => {
throw err; throw err;
} }
if (process.stdout.isTTY) { if (output.isTTY) {
process.stdout.write('> '); output.log(contextName);
} else {
// If stdout is not a TTY, then only print the username
// to support piping the output to another file / exe
output.print(`${contextName}\n`, { w: process.stdout });
} }
console.log(contextName); return 0;
}; };

View File

@@ -20,10 +20,9 @@ import epipebomb from 'epipebomb';
import updateNotifier from 'update-notifier'; import updateNotifier from 'update-notifier';
import { URL } from 'url'; import { URL } from 'url';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import { NowBuildError } from '@vercel/build-utils';
import hp from './util/humanize-path'; import hp from './util/humanize-path';
import commands from './commands/index.ts'; import commands from './commands';
import pkg from './util/pkg.ts'; import pkg from './util/pkg';
import createOutput from './util/output'; import createOutput from './util/output';
import cmd from './util/output/cmd'; import cmd from './util/output/cmd';
import info from './util/output/info'; import info from './util/output/info';
@@ -31,9 +30,9 @@ import error from './util/output/error';
import param from './util/output/param'; import param from './util/output/param';
import highlight from './util/output/highlight'; import highlight from './util/output/highlight';
import getArgs from './util/get-args'; import getArgs from './util/get-args';
import getUser from './util/get-user.ts'; import getUser from './util/get-user';
import Client from './util/client.ts'; import getTeams from './util/teams/get-teams';
import NowTeams from './util/teams'; import Client from './util/client';
import { handleError } from './util/error'; import { handleError } from './util/error';
import reportError from './util/report-error'; import reportError from './util/report-error';
import getConfig from './util/get-config'; import getConfig from './util/get-config';
@@ -44,13 +43,14 @@ import {
getDefaultAuthConfig, getDefaultAuthConfig,
} from './util/config/get-default'; } from './util/config/get-default';
import * as ERRORS from './util/errors-ts'; import * as ERRORS from './util/errors-ts';
import { NowError } from './util/now-error'; import { APIError } from './util/errors-ts';
import { APIError } from './util/errors-ts.ts'; import { SENTRY_DSN } from './util/constants';
import { SENTRY_DSN } from './util/constants.ts';
import getUpdateCommand from './util/get-update-command'; import getUpdateCommand from './util/get-update-command';
import { metrics, shouldCollectMetrics } from './util/metrics.ts'; import { metrics, shouldCollectMetrics } from './util/metrics';
import { getCommandName, getTitleName } from './util/pkg-name.ts'; import { getCommandName, getTitleName } from './util/pkg-name';
import doLoginPrompt from './util/login/prompt.ts'; import doLoginPrompt from './util/login/prompt';
import { GlobalConfig } from './types';
import { VercelConfig } from '@vercel/client';
const isCanary = pkg.version.includes('canary'); const isCanary = pkg.version.includes('canary');
@@ -77,8 +77,8 @@ Sentry.init({
environment: isCanary ? 'canary' : 'stable', environment: isCanary ? 'canary' : 'stable',
}); });
let client; let client: Client;
let debug = () => {}; let debug: (s: string) => void = () => {};
let apiUrl = 'https://api.vercel.com'; let apiUrl = 'https://api.vercel.com';
const main = async () => { const main = async () => {
@@ -108,26 +108,30 @@ const main = async () => {
debug = output.debug; debug = output.debug;
const localConfigPath = argv['--local-config']; const localConfigPath = argv['--local-config'];
const localConfig = await getConfig(output, localConfigPath); let localConfig: VercelConfig | Error | undefined = await getConfig(
output,
if (localConfigPath && localConfig instanceof ERRORS.CantFindConfig) { localConfigPath
output.error( );
`Couldn't find a project configuration file at \n ${localConfig.meta.paths.join(
' or\n '
)}`
);
return 1;
}
if (localConfig instanceof ERRORS.CantParseJSONFile) { if (localConfig instanceof ERRORS.CantParseJSONFile) {
output.error(`Couldn't parse JSON file ${localConfig.meta.file}.`); output.error(`Couldn't parse JSON file ${localConfig.meta.file}.`);
return 1; return 1;
} }
if ( if (localConfig instanceof ERRORS.CantFindConfig) {
(localConfig instanceof NowError || localConfig instanceof NowBuildError) && if (localConfigPath) {
!(localConfig instanceof ERRORS.CantFindConfig) output.error(
) { `Couldn't find a project configuration file at \n ${localConfig.meta.paths.join(
' or\n '
)}`
);
return 1;
} else {
localConfig = undefined;
}
}
if (localConfig instanceof Error) {
output.prettyError(localConfig); output.prettyError(localConfig);
return 1; return 1;
} }
@@ -207,7 +211,7 @@ const main = async () => {
return 0; return 0;
} }
let config; let config: GlobalConfig | null = null;
if (configExists) { if (configExists) {
try { try {
@@ -229,8 +233,11 @@ const main = async () => {
// multiple providers. In that case, we really // multiple providers. In that case, we really
// need to migrate. // need to migrate.
if ( if (
// @ts-ignore
config.sh || config.sh ||
// @ts-ignore
config.user || config.user ||
// @ts-ignore
typeof config.user === 'object' || typeof config.user === 'object' ||
typeof config.currentTeam === 'object' typeof config.currentTeam === 'object'
) { ) {
@@ -300,6 +307,7 @@ const main = async () => {
// This is from when Vercel CLI supported // This is from when Vercel CLI supported
// multiple providers. In that case, we really // multiple providers. In that case, we really
// need to migrate. // need to migrate.
// @ts-ignore
if (authConfig.credentials) { if (authConfig.credentials) {
authConfigExists = false; authConfigExists = false;
} }
@@ -346,6 +354,11 @@ const main = async () => {
return 1; return 1;
} }
if (!config) {
output.error(`Vercel global config was not loaded.`);
return 1;
}
// Shared API `Client` instance for all sub-commands to utilize // Shared API `Client` instance for all sub-commands to utilize
client = new Client({ client = new Client({
apiUrl, apiUrl,
@@ -397,7 +410,7 @@ const main = async () => {
} }
if (subcommandExists) { if (subcommandExists) {
debug('user supplied known subcommand', targetOrSubcommand); debug(`user supplied known subcommand: "${targetOrSubcommand}"`);
subcommand = targetOrSubcommand; subcommand = targetOrSubcommand;
} else { } else {
debug('user supplied a possible target for deployment'); debug('user supplied a possible target for deployment');
@@ -430,12 +443,16 @@ const main = async () => {
return result; return result;
} }
if (result.teamId) {
// SSO login, so set the current scope to the appropriate Team
client.config.currentTeam = result.teamId;
} else {
delete client.config.currentTeam;
}
// When `result` is a string it's the user's authentication token. // When `result` is a string it's the user's authentication token.
// It needs to be saved to the configuration file. // It needs to be saved to the configuration file.
client.authConfig.token = result; client.authConfig.token = result.token;
// New user, so we can't keep the team
delete client.config.currentTeam;
configFiles.writeToAuthConfigFile(client.authConfig); configFiles.writeToAuthConfigFile(client.authConfig);
configFiles.writeToConfigFile(client.config); configFiles.writeToConfigFile(client.config);
@@ -453,14 +470,12 @@ const main = async () => {
} }
if (typeof argv['--token'] === 'string' && subcommand === 'switch') { if (typeof argv['--token'] === 'string' && subcommand === 'switch') {
console.error( output.prettyError({
error({ message: `This command doesn't work with ${param(
message: `This command doesn't work with ${param( '--token'
'--token' )}. Please use ${param('--scope')}.`,
)}. Please use ${param('--scope')}.`, link: 'https://err.sh/vercel/no-token-allowed',
slug: 'no-token-allowed', });
})
);
return 1; return 1;
} }
@@ -469,12 +484,10 @@ const main = async () => {
const token = argv['--token']; const token = argv['--token'];
if (token.length === 0) { if (token.length === 0) {
console.error( output.prettyError({
error({ message: `You defined ${param('--token')}, but it's missing a value`,
message: `You defined ${param('--token')}, but it's missing a value`, link: 'https://err.sh/vercel/missing-token-value',
slug: 'missing-token-value', });
})
);
return 1; return 1;
} }
@@ -482,16 +495,14 @@ const main = async () => {
const invalid = token.match(/(\W)/g); const invalid = token.match(/(\W)/g);
if (invalid) { if (invalid) {
const notContain = Array.from(new Set(invalid)).sort(); const notContain = Array.from(new Set(invalid)).sort();
console.error( output.prettyError({
error({ message: `You defined ${param(
message: `You defined ${param( '--token'
'--token' )}, but its contents are invalid. Must not contain: ${notContain
)}, but its contents are invalid. Must not contain: ${notContain .map(c => JSON.stringify(c))
.map(c => JSON.stringify(c)) .join(', ')}`,
.join(', ')}`, link: 'https://err.sh/vercel/invalid-token-value',
slug: 'invalid-token-value', });
})
);
return 1; return 1;
} }
@@ -512,13 +523,8 @@ const main = async () => {
); );
} }
const {
authConfig: { token },
} = client;
let scope = argv['--scope'] || argv['--team'] || localConfig.scope;
const targetCommand = commands.get(subcommand); const targetCommand = commands.get(subcommand);
const scope = argv['--scope'] || argv['--team'] || localConfig?.scope;
if ( if (
typeof scope === 'string' && typeof scope === 'string' &&
@@ -532,12 +538,10 @@ const main = async () => {
user = await getUser(client); user = await getUser(client);
} catch (err) { } catch (err) {
if (err.code === 'NOT_AUTHORIZED') { if (err.code === 'NOT_AUTHORIZED') {
console.error( output.prettyError({
error({ message: `You do not have access to the specified account`,
message: `You do not have access to the specified account`, link: 'https://err.sh/vercel/scope-not-accessible',
slug: 'scope-not-accessible', });
})
);
return 1; return 1;
} }
@@ -549,19 +553,16 @@ const main = async () => {
if (user.uid === scope || user.email === scope || user.username === scope) { if (user.uid === scope || user.email === scope || user.username === scope) {
delete client.config.currentTeam; delete client.config.currentTeam;
} else { } else {
let list = []; let teams = [];
try { try {
const teams = new NowTeams({ apiUrl, token, debug: isDebugging }); teams = await getTeams(client);
list = (await teams.ls()).teams;
} catch (err) { } catch (err) {
if (err.code === 'not_authorized') { if (err.code === 'not_authorized') {
console.error( output.prettyError({
error({ message: `You do not have access to the specified team`,
message: `You do not have access to the specified team`, link: 'https://err.sh/vercel/scope-not-accessible',
slug: 'scope-not-accessible', });
})
);
return 1; return 1;
} }
@@ -571,15 +572,13 @@ const main = async () => {
} }
const related = const related =
list && list.find(item => item.id === scope || item.slug === scope); teams && teams.find(team => team.id === scope || team.slug === scope);
if (!related) { if (!related) {
console.error( output.prettyError({
error({ message: 'The specified scope does not exist',
message: 'The specified scope does not exist', link: 'https://err.sh/vercel/scope-not-existent',
slug: 'scope-not-existent', });
})
);
return 1; return 1;
} }
@@ -588,20 +587,93 @@ const main = async () => {
} }
} }
if (!targetCommand) {
const sub = param(subcommand);
console.error(error(`The ${sub} subcommand does not exist`));
return 1;
}
const metric = metrics(); const metric = metrics();
let exitCode; let exitCode;
const eventCategory = 'Exit Code'; const eventCategory = 'Exit Code';
try { try {
const start = Date.now(); const start = Date.now();
const full = require(`./commands/${targetCommand}`).default; let func: any;
exitCode = await full(client); switch (targetCommand) {
case 'alias':
func = await import('./commands/alias');
break;
case 'billing':
func = await import('./commands/billing');
break;
case 'certs':
func = await import('./commands/certs');
break;
case 'deploy':
func = await import('./commands/deploy');
break;
case 'dev':
func = await import('./commands/dev');
break;
case 'dns':
func = await import('./commands/dns');
break;
case 'domains':
func = await import('./commands/domains');
break;
case 'env':
func = await import('./commands/env');
break;
case 'init':
func = await import('./commands/init');
break;
case 'inspect':
func = await import('./commands/inspect');
break;
case 'link':
func = await import('./commands/link');
break;
case 'list':
func = await import('./commands/list');
break;
case 'logs':
func = await import('./commands/logs');
break;
case 'login':
func = await import('./commands/login');
break;
case 'logout':
func = await import('./commands/logout');
break;
case 'projects':
func = await import('./commands/projects');
break;
case 'remove':
func = await import('./commands/remove');
break;
case 'secrets':
func = await import('./commands/secrets');
break;
case 'teams':
func = await import('./commands/teams');
break;
case 'update':
func = await import('./commands/update');
break;
case 'whoami':
func = await import('./commands/whoami');
break;
default:
func = null;
break;
}
if (!func || !targetCommand) {
const sub = param(subcommand);
output.error(`The ${sub} subcommand does not exist`);
return 1;
}
if (func.default) {
func = func.default;
}
exitCode = await func(client);
const end = Date.now() - start; const end = Date.now() - start;
if (shouldCollectMetrics) { if (shouldCollectMetrics) {
@@ -674,7 +746,7 @@ const main = async () => {
return exitCode; return exitCode;
}; };
const handleRejection = async err => { const handleRejection = async (err: any) => {
debug('handling rejection'); debug('handling rejection');
if (err) { if (err) {
@@ -691,7 +763,7 @@ const handleRejection = async err => {
process.exit(1); process.exit(1);
}; };
const handleUnexpected = async err => { const handleUnexpected = async (err: Error) => {
const { message } = err; const { message } = err;
// We do not want to render errors about Sentry not being reachable // We do not want to render errors about Sentry not being reachable
@@ -700,9 +772,8 @@ const handleUnexpected = async err => {
return; return;
} }
await reportError(Sentry, client, err);
console.error(error(`An unexpected error occurred!\n${err.stack}`)); console.error(error(`An unexpected error occurred!\n${err.stack}`));
await reportError(Sentry, client, err);
process.exit(1); process.exit(1);
}; };
@@ -713,6 +784,7 @@ process.on('uncaughtException', handleUnexpected);
main() main()
.then(exitCode => { .then(exitCode => {
process.exitCode = exitCode; process.exitCode = exitCode;
// @ts-ignore - "nowExit" is a non-standard event name
process.emit('nowExit'); process.emit('nowExit');
}) })
.catch(handleUnexpected); .catch(handleUnexpected);

View File

@@ -16,11 +16,13 @@ export interface JSONObject {
} }
export interface AuthConfig { export interface AuthConfig {
_?: string;
token?: string; token?: string;
skipWrite?: boolean; skipWrite?: boolean;
} }
export interface GlobalConfig { export interface GlobalConfig {
_?: string;
currentTeam?: string; currentTeam?: string;
includeScheme?: string; includeScheme?: string;
collectMetrics?: boolean; collectMetrics?: boolean;
@@ -306,3 +308,141 @@ export interface Token {
createdAt: number; createdAt: number;
teamId?: string; teamId?: string;
} }
/**
* An object representing a Build on Vercel
*/
export interface Build {
/**
* The unique identifier of the Build
* @example "bld_q5fj68jh7eewfe8"
*/
id: string;
/**
* The unique identifier of the deployment
* @example "dpl_BRGyoU2Jzzwx7myBnqv3xjRDD2GnHTwUWyFybnrUvjDD"
*/
deploymentId: string;
/**
* The entrypoint of the deployment
* @example "api/index.js"
*/
entrypoint: string;
/**
* The state of the deployment depending on the process of deploying,
* or if it is ready or in an error state
* @example "READY"
*/
readyState:
| 'INITIALIZING'
| 'BUILDING'
| 'UPLOADING'
| 'DEPLOYING'
| 'READY'
| 'ARCHIVED'
| 'ERROR'
| 'QUEUED'
| 'CANCELED';
/**
* The time at which the Build state was last modified
* @example 1567024758130
*/
readyStateAt?: number;
/**
* The time at which the Build was scheduled to be built
* @example 1567024756543
*/
scheduledAt?: number | null;
/**
* The time at which the Build was created
* @example 1567071524208
*/
createdAt?: number;
/**
* The time at which the Build was deployed
* @example 1567071598563
*/
deployedAt?: number;
/**
* The region where the Build was first created
* @example "sfo1"
*/
createdIn?: string;
/**
* The Runtime the Build used to generate the output
* @example "@vercel/node"
*/
use?: string;
/**
* An object that contains the Build's configuration
* @example {"zeroConfig": true}
*/
config?: {
distDir?: string | undefined;
forceBuildIn?: string | undefined;
reuseWorkPathFrom?: string | undefined;
zeroConfig?: boolean | undefined;
};
/**
* A list of outputs for the Build that can be either Serverless Functions or static files
*/
output: BuildOutput[];
/**
* If the Build uses the `@vercel/static` Runtime, it contains a hashed string of all outputs
* @example null
*/
fingerprint?: string | null;
copiedFrom?: string;
}
export interface BuildOutput {
/**
* The type of the output
*/
type?: 'lambda' | 'file';
/**
* The absolute path of the file or Serverless Function
*/
path: string;
/**
* The SHA1 of the file
*/
digest: string;
/**
* The POSIX file permissions
*/
mode: number;
/**
* The size of the file in bytes
*/
size?: number;
/**
* If the output is a Serverless Function, an object
* containing the name, location and memory size of the function
*/
lambda?: {
functionName: string;
deployedTo: string[];
memorySize?: number;
timeout?: number;
layers?: string[];
} | null;
}

View File

@@ -35,7 +35,7 @@ export async function getDeploymentForAlias(
localConfigPath: string | undefined, localConfigPath: string | undefined,
user: User, user: User,
contextName: string, contextName: string,
localConfig: VercelConfig localConfig?: VercelConfig
) { ) {
output.spinner(`Fetching deployment to alias in ${chalk.bold(contextName)}`); output.spinner(`Fetching deployment to alias in ${chalk.bold(contextName)}`);
@@ -52,7 +52,7 @@ export async function getDeploymentForAlias(
} }
const appName = const appName =
(localConfig && localConfig.name) || localConfig?.name ||
path.basename(path.resolve(process.cwd(), localConfigPath || '')); path.basename(path.resolve(process.cwd(), localConfigPath || ''));
if (!appName) { if (!appName) {

View File

@@ -1,5 +0,0 @@
export const isReady = ({ readyState }) => readyState === 'READY';
export const isFailed = ({ readyState }) =>
readyState.endsWith('_ERROR') || readyState === 'ERROR';
export const isDone = ({ readyState }) =>
isReady({ readyState }) || isFailed({ readyState });

View File

@@ -0,0 +1,7 @@
import { Build } from '../types';
export const isReady = ({ readyState }: Pick<Build, 'readyState'>) =>
readyState === 'READY';
export const isFailed = ({ readyState }: Pick<Build, 'readyState'>) =>
readyState.endsWith('_ERROR') || readyState === 'ERROR';

View File

@@ -34,10 +34,10 @@ export interface ClientOptions {
authConfig: AuthConfig; authConfig: AuthConfig;
output: Output; output: Output;
config: GlobalConfig; config: GlobalConfig;
localConfig: VercelConfig; localConfig?: VercelConfig;
} }
const isJSONObject = (v: any): v is JSONObject => { export const isJSONObject = (v: any): v is JSONObject => {
return v && typeof v == 'object' && v.constructor === Object; return v && typeof v == 'object' && v.constructor === Object;
}; };
@@ -47,7 +47,7 @@ export default class Client extends EventEmitter {
authConfig: AuthConfig; authConfig: AuthConfig;
output: Output; output: Output;
config: GlobalConfig; config: GlobalConfig;
localConfig: VercelConfig; localConfig?: VercelConfig;
private requestIdCounter: number; private requestIdCounter: number;
constructor(opts: ClientOptions) { constructor(opts: ClientOptions) {
@@ -69,7 +69,7 @@ export default class Client extends EventEmitter {
}); });
} }
_fetch(_url: string, opts: FetchOptions = {}) { private _fetch(_url: string, opts: FetchOptions = {}) {
const parsedUrl = parseUrl(_url, true); const parsedUrl = parseUrl(_url, true);
const apiUrl = parsedUrl.host const apiUrl = parsedUrl.host
? `${parsedUrl.protocol}//${parsedUrl.host}` ? `${parsedUrl.protocol}//${parsedUrl.host}`

View File

@@ -100,8 +100,8 @@ export function getAuthConfigFilePath() {
export function readLocalConfig( export function readLocalConfig(
prefix: string = process.cwd() prefix: string = process.cwd()
): VercelConfig | null { ): VercelConfig | undefined {
let config: VercelConfig | null = null; let config: VercelConfig | undefined = undefined;
let target = ''; let target = '';
try { try {
@@ -116,7 +116,7 @@ export function readLocalConfig(
} }
if (!target) { if (!target) {
return null; return;
} }
try { try {
@@ -134,7 +134,7 @@ export function readLocalConfig(
} }
if (!config) { if (!config) {
return null; return;
} }
config[fileNameSymbol] = basename(target); config[fileNameSymbol] = basename(target);

View File

@@ -1,9 +1,10 @@
export const getDefaultConfig = async existingCopy => { import { AuthConfig, GlobalConfig } from '../../types';
export const getDefaultConfig = async (existingCopy?: GlobalConfig | null) => {
let migrated = false; let migrated = false;
const config = { const config: GlobalConfig = {
_: _: 'This is your Vercel config file. For more information see the global configuration documentation: https://vercel.com/docs/configuration#global',
'This is your Vercel config file. For more information see the global configuration documentation: https://vercel.com/docs/configuration#global',
collectMetrics: true, collectMetrics: true,
}; };
@@ -16,44 +17,33 @@ export const getDefaultConfig = async existingCopy => {
'collectMetrics', 'collectMetrics',
'api', 'api',
// This is deleted later in the code // This is deleted later in the code
'shownTips',
]; ];
try { try {
const existing = Object.assign({}, existingCopy); const existing = Object.assign({}, existingCopy);
// @ts-ignore
const sh = Object.assign({}, existing.sh || {}); const sh = Object.assign({}, existing.sh || {});
Object.assign(config, existing, sh); Object.assign(config, existing, sh);
for (const key of Object.keys(config)) { for (const key of Object.keys(config)) {
if (!keep.includes(key)) { if (!keep.includes(key)) {
// @ts-ignore
delete config[key]; delete config[key];
} }
} }
if (typeof config.currentTeam === 'object') { if (typeof config.currentTeam === 'object') {
// @ts-ignore
config.currentTeam = config.currentTeam.id; config.currentTeam = config.currentTeam.id;
} }
// @ts-ignore
if (typeof config.user === 'object') { if (typeof config.user === 'object') {
// @ts-ignore
config.user = config.user.uid || config.user.id; config.user = config.user.uid || config.user.id;
} }
// Make sure Now Desktop users don't see any tips
// again that they already dismissed
if (config.shownTips) {
if (config.desktop) {
config.desktop.shownTips = config.shownTips;
} else {
config.desktop = {
shownTips: config.shownTips,
};
}
// Clean up the old property
delete config.shownTips;
}
migrated = true; migrated = true;
} catch (err) {} } catch (err) {}
} }
@@ -61,16 +51,16 @@ export const getDefaultConfig = async existingCopy => {
return { config, migrated }; return { config, migrated };
}; };
export const getDefaultAuthConfig = async existing => { export const getDefaultAuthConfig = async (existing?: AuthConfig | null) => {
let migrated = false; let migrated = false;
const config = { const config: AuthConfig = {
_: _: 'This is your Vercel credentials file. DO NOT SHARE! More: https://vercel.com/docs/configuration#global',
'This is your Vercel credentials file. DO NOT SHARE! More: https://vercel.com/docs/configuration#global',
}; };
if (existing) { if (existing) {
try { try {
// @ts-ignore
const sh = existing.credentials.find(item => item.provider === 'sh'); const sh = existing.credentials.find(item => item.provider === 'sh');
if (sh) { if (sh) {

View File

@@ -4,7 +4,7 @@ import * as ERRORS from '../errors';
import { NowError } from '../now-error'; import { NowError } from '../now-error';
import mapCertError from '../certs/map-cert-error'; import mapCertError from '../certs/map-cert-error';
import { Org } from '../../types'; import { Org } from '../../types';
import Now from '..'; import Now, { CreateOptions } from '..';
import Client from '../client'; import Client from '../client';
import { DeploymentError } from '../../../../client/dist'; import { DeploymentError } from '../../../../client/dist';
@@ -13,8 +13,8 @@ export default async function createDeploy(
now: Now, now: Now,
contextName: string, contextName: string,
paths: string[], paths: string[],
createArgs: any, createArgs: CreateOptions,
org: Org | null, org: Org,
isSettingUpProject: boolean, isSettingUpProject: boolean,
cwd?: string cwd?: string
): Promise<any | DeploymentError> { ): Promise<any | DeploymentError> {

View File

@@ -69,10 +69,15 @@ export default async function processDeployment({
const { env = {} } = requestBody; const { env = {} } = requestBody;
const token = now._token;
if (!token) {
throw new Error('Missing authentication token');
}
const clientOptions: VercelClientOptions = { const clientOptions: VercelClientOptions = {
teamId: org.type === 'team' ? org.id : undefined, teamId: org.type === 'team' ? org.id : undefined,
apiUrl: now._apiUrl, apiUrl: now._apiUrl,
token: now._token, token,
debug: now._debug, debug: now._debug,
userAgent: ua, userAgent: ua,
path: paths[0], path: paths[0],
@@ -149,7 +154,6 @@ export default async function processDeployment({
org.slug org.slug
); );
// @ts-ignore
now.url = event.payload.url; now.url = event.payload.url;
output.stopSpinner(); output.stopSpinner();

View File

@@ -19,7 +19,7 @@ import { BuilderWithPackage } from './types';
type CliPackageJson = typeof cliPkg; type CliPackageJson = typeof cliPkg;
declare const __non_webpack_require__: typeof require; const require_: typeof require = eval('require');
const registryTypes = new Set(['version', 'tag', 'range']); const registryTypes = new Set(['version', 'tag', 'range']);
@@ -82,14 +82,14 @@ function getNpmVersion(use = ''): string {
return ''; return '';
} }
export function getBuildUtils(packages: string[], org: string): string { export function getBuildUtils(packages: string[]): string {
const version = packages const version = packages
.map(getNpmVersion) .map(getNpmVersion)
.some(ver => ver.includes('canary')) .some(ver => ver.includes('canary'))
? 'canary' ? 'canary'
: 'latest'; : 'latest';
return `@${org}/build-utils@${version}`; return `@vercel/build-utils@${version}`;
} }
function parseVersionSafe(rawSpec: string) { function parseVersionSafe(rawSpec: string) {
@@ -104,7 +104,7 @@ export function filterPackage(
builderSpec: string, builderSpec: string,
distTag: string, distTag: string,
buildersPkg: PackageJson, buildersPkg: PackageJson,
cliPkg: CliPackageJson cliPkg: Partial<CliPackageJson>
) { ) {
if (builderSpec in localBuilders) return false; if (builderSpec in localBuilders) return false;
const parsed = npa(builderSpec); const parsed = npa(builderSpec);
@@ -191,10 +191,7 @@ export async function installBuilders(
return; return;
} }
packagesToInstall.push( packagesToInstall.push(getBuildUtils(packages));
getBuildUtils(packages, 'vercel'),
getBuildUtils(packages, 'now')
);
await npmInstall(builderDir, output, packagesToInstall, false); await npmInstall(builderDir, output, packagesToInstall, false);
@@ -307,10 +304,7 @@ export async function updateBuilders(
}); });
if (packagesToUpdate.length > 0) { if (packagesToUpdate.length > 0) {
packagesToUpdate.push( packagesToUpdate.push(getBuildUtils(packages));
getBuildUtils(packages, 'vercel'),
getBuildUtils(packages, 'now')
);
await npmInstall(builderDir, output, packagesToUpdate, true); await npmInstall(builderDir, output, packagesToUpdate, true);
@@ -361,8 +355,8 @@ export async function getBuilder(
try { try {
output.debug(`Requiring runtime: "${requirePath}"`); output.debug(`Requiring runtime: "${requirePath}"`);
const mod = require(requirePath); const mod = require_(requirePath);
const pkg = require(join(requirePath, 'package.json')); const pkg = require_(join(requirePath, 'package.json'));
builderWithPkg = { builderWithPkg = {
requirePath, requirePath,
builder: Object.freeze(mod), builder: Object.freeze(mod),
@@ -438,18 +432,13 @@ function purgeRequireCache(
builderDir: string, builderDir: string,
output: Output output: Output
) { ) {
const _require =
typeof __non_webpack_require__ === 'function'
? __non_webpack_require__
: require;
// The `require()` cache for the builder's assets must be purged // The `require()` cache for the builder's assets must be purged
const packagesPaths = packages.map(b => join(builderDir, 'node_modules', b)); const packagesPaths = packages.map(b => join(builderDir, 'node_modules', b));
for (const id of Object.keys(_require.cache)) { for (const id of Object.keys(require_.cache)) {
for (const path of packagesPaths) { for (const path of packagesPaths) {
if (id.startsWith(path)) { if (id.startsWith(path)) {
output.debug(`Purging require cache for "${id}"`); output.debug(`Purging require cache for "${id}"`);
delete _require.cache[id]; delete require_.cache[id];
} }
} }
} }

View File

@@ -109,7 +109,8 @@ export async function executeBuild(
builderWithPkg: { runInProcess, requirePath, builder, package: pkg }, builderWithPkg: { runInProcess, requirePath, builder, package: pkg },
} = match; } = match;
const { entrypoint } = match; const { entrypoint } = match;
const { debug, envConfigs, cwd: workPath, devCacheDir } = devServer; const { envConfigs, cwd: workPath, devCacheDir } = devServer;
const debug = devServer.output.isDebugEnabled();
const startTime = Date.now(); const startTime = Date.now();
const showBuildTimestamp = const showBuildTimestamp =

View File

@@ -117,7 +117,6 @@ function sortBuilders(buildA: Builder, buildB: Builder) {
export default class DevServer { export default class DevServer {
public cwd: string; public cwd: string;
public debug: boolean;
public output: Output; public output: Output;
public proxy: httpProxy; public proxy: httpProxy;
public envConfigs: EnvConfigs; public envConfigs: EnvConfigs;
@@ -157,7 +156,6 @@ export default class DevServer {
constructor(cwd: string, options: DevServerOptions) { constructor(cwd: string, options: DevServerOptions) {
this.cwd = cwd; this.cwd = cwd;
this.debug = options.debug;
this.output = options.output; this.output = options.output;
this.envConfigs = { buildEnv: {}, runEnv: {}, allEnv: {} }; this.envConfigs = { buildEnv: {}, runEnv: {}, allEnv: {} };
this.systemEnvValues = options.systemEnvValues || []; this.systemEnvValues = options.systemEnvValues || [];

View File

@@ -23,7 +23,6 @@ export { VercelConfig };
export interface DevServerOptions { export interface DevServerOptions {
output: Output; output: Output;
debug: boolean;
devCommand?: string; devCommand?: string;
frameworkSlug?: string; frameworkSlug?: string;
projectSettings?: ProjectSettings; projectSettings?: ProjectSettings;

View File

@@ -1,10 +1,9 @@
//@ts-ignore Missing types for 'title'
import title from 'title'; import title from 'title';
import { ProjectEnvVariable } from '../../types'; import { ProjectEnvVariable } from '../../types';
export default function formatEnvTarget(env: ProjectEnvVariable): string { export default function formatEnvTarget(env: ProjectEnvVariable): string {
const target = (Array.isArray(env.target) ? env.target : [env.target || '']) const target = (Array.isArray(env.target) ? env.target : [env.target || ''])
.map(title) .map(t => title(t))
.join(', '); .join(', ');
return env.gitBranch ? `${target} (${env.gitBranch})` : target; return env.gitBranch ? `${target} (${env.gitBranch})` : target;

View File

@@ -1,14 +1,22 @@
import { Response } from 'node-fetch';
import errorOutput from './output/error'; import errorOutput from './output/error';
export { default as handleError } from './handle-error'; export { default as handleError } from './handle-error';
export const error = errorOutput; export const error = errorOutput;
export interface ResponseError extends Error {
status: number;
serverMessage: string;
retryAfter?: number;
[key: string]: any;
}
export async function responseError( export async function responseError(
res, res: Response,
fallbackMessage = null, fallbackMessage: string | null = null,
parsedBody = {} parsedBody = {}
) { ) {
let message; let message = '';
let bodyError; let bodyError;
if (res.status >= 400 && res.status < 500) { if (res.status >= 400 && res.status < 500) {
@@ -25,11 +33,11 @@ export async function responseError(
message = bodyError.message; message = bodyError.message;
} }
if (message == null) { if (!message) {
message = fallbackMessage === null ? 'Response Error' : fallbackMessage; message = fallbackMessage === null ? 'Response Error' : fallbackMessage;
} }
const err = new Error(`${message} (${res.status})`); const err = new Error(`${message} (${res.status})`) as ResponseError;
err.status = res.status; err.status = res.status;
err.serverMessage = message; err.serverMessage = message;
@@ -54,7 +62,10 @@ export async function responseError(
return err; return err;
} }
export async function responseErrorMessage(res, fallbackMessage = null) { export async function responseErrorMessage(
res: Response,
fallbackMessage: string | null = null
) {
let message; let message;
if (res.status >= 400 && res.status < 500) { if (res.status >= 400 && res.status < 500) {

View File

@@ -15,6 +15,7 @@ export class APIError extends Error {
status: number; status: number;
serverMessage: string; serverMessage: string;
link?: string; link?: string;
slug?: string;
action?: string; action?: string;
retryAfter: number | null | 'never'; retryAfter: number | null | 'never';
[key: string]: any; [key: string]: any;

View File

@@ -1,20 +0,0 @@
import { basename } from 'path';
export default function getProjectName({ argv, nowConfig, isFile, paths }) {
const nameCli = argv['--name'] || argv.name;
if (nameCli) {
return nameCli;
}
if (nowConfig.name) {
return nowConfig.name;
}
if (isFile || paths.length > 1) {
return 'files';
}
// Otherwise let's send the name of the directory
return basename(paths[0]);
}

View File

@@ -0,0 +1,33 @@
import { basename } from 'path';
import { VercelConfig } from '@vercel/client';
export interface GetProjectNameOptions {
argv: { '--name'?: string };
nowConfig?: VercelConfig;
isFile?: boolean;
paths?: string[];
}
export default function getProjectName({
argv,
nowConfig = {},
isFile = false,
paths = [],
}: GetProjectNameOptions) {
const nameCli = argv['--name'];
if (nameCli) {
return nameCli;
}
if (nowConfig.name) {
return nowConfig.name;
}
if (isFile || paths.length > 1) {
return 'files';
}
// Otherwise, use the name of the directory
return basename(paths[0] || '');
}

View File

@@ -1,6 +1,6 @@
import Client from './client'; import Client from './client';
import getUser from './get-user'; import getUser from './get-user';
import getTeamById from './get-team-by-id'; import getTeamById from './teams/get-team-by-id';
import { TeamDeleted } from './errors-ts'; import { TeamDeleted } from './errors-ts';
import { Team } from '../types'; import { Team } from '../types';

View File

@@ -1,22 +0,0 @@
import Client from './client';
import { Team } from '../types';
import { APIError, InvalidToken } from './errors-ts';
let teams: Team[] | undefined;
export default async function getTeams(client: Client): Promise<Team[]> {
if (teams) return teams;
try {
const body = await client.fetch<{ teams: Team[] }>('/v1/teams', {
useCurrentTeam: false,
});
teams = body.teams || [];
return teams;
} catch (error) {
if (error instanceof APIError && error.status === 403) {
throw new InvalidToken();
}
throw error;
}
}

View File

@@ -2,11 +2,7 @@ import Client from './client';
import { User } from '../types'; import { User } from '../types';
import { APIError, InvalidToken, MissingUser } from './errors-ts'; import { APIError, InvalidToken, MissingUser } from './errors-ts';
let user: User | undefined;
export default async function getUser(client: Client) { export default async function getUser(client: Client) {
if (user) return user;
try { try {
const res = await client.fetch<{ user: User }>('/www/user', { const res = await client.fetch<{ user: User }>('/www/user', {
useCurrentTeam: false, useCurrentTeam: false,
@@ -16,8 +12,7 @@ export default async function getUser(client: Client) {
throw new MissingUser(); throw new MissingUser();
} }
user = res.user; return res.user;
return user;
} catch (error) { } catch (error) {
if (error instanceof APIError && error.status === 403) { if (error instanceof APIError && error.status === 403) {
throw new InvalidToken(); throw new InvalidToken();

View File

@@ -1,8 +0,0 @@
function indent(text, n) {
return text
.split('\n')
.map(l => ' '.repeat(n) + l)
.join('\n');
}
export default indent;

View File

@@ -3,20 +3,79 @@ import qs from 'querystring';
import { parse as parseUrl } from 'url'; import { parse as parseUrl } from 'url';
import retry from 'async-retry'; import retry from 'async-retry';
import ms from 'ms'; import ms from 'ms';
import fetch from 'node-fetch'; import fetch, { Headers } from 'node-fetch';
import { URLSearchParams } from 'url'; import { URLSearchParams } from 'url';
import bytes from 'bytes'; import bytes from 'bytes';
import chalk from 'chalk'; import chalk from 'chalk';
import ua from './ua.ts'; import ua from './ua';
import processDeployment from './deploy/process-deployment.ts'; import processDeployment from './deploy/process-deployment';
import highlight from './output/highlight'; import highlight from './output/highlight';
import createOutput from './output'; import createOutput, { Output } from './output';
import { responseError } from './error'; import { responseError } from './error';
import stamp from './output/stamp'; import stamp from './output/stamp';
import { BuildError } from './errors-ts'; import { APIError, BuildError } from './errors-ts';
import printIndications from './print-indications.ts'; import printIndications from './print-indications';
import { Org } from '../types';
import { VercelConfig } from './dev/types';
import { FetchOptions, isJSONObject } from './client';
import { Dictionary } from '@vercel/client';
export interface NowOptions {
apiUrl: string;
token?: string;
url?: string | null;
currentTeam?: string | null;
output: Output;
forceNew?: boolean;
withCache?: boolean;
debug?: boolean;
}
export interface CreateOptions {
// Legacy
nowConfig?: VercelConfig;
isFile?: boolean;
// Latest
name: string;
project?: string;
wantsPublic: boolean;
meta: Dictionary<string>;
regions?: string[];
quiet?: boolean;
env: Dictionary<string>;
build: { env: Dictionary<string> };
forceNew?: boolean;
withCache?: boolean;
target?: string | null;
deployStamp: () => string;
projectSettings?: any;
skipAutoDetectionConfirmation?: boolean;
}
export interface RemoveOptions {
hard?: boolean;
}
export interface ListOptions {
version?: number;
meta?: Dictionary<string>;
nextTimestamp?: number;
}
export default class Now extends EventEmitter { export default class Now extends EventEmitter {
url: string | null;
currentTeam: string | null;
_apiUrl: string;
_token?: string;
_debug: boolean;
_forceNew: boolean;
_withCache: boolean;
_output: Output;
_syncAmount?: number;
_files?: any[];
_missing?: string[];
constructor({ constructor({
apiUrl, apiUrl,
token, token,
@@ -26,7 +85,7 @@ export default class Now extends EventEmitter {
withCache = false, withCache = false,
debug = false, debug = false,
output = createOutput({ debug }), output = createOutput({ debug }),
}) { }: NowOptions) {
super(); super();
this.url = url; this.url = url;
@@ -41,10 +100,10 @@ export default class Now extends EventEmitter {
} }
async create( async create(
paths, paths: string[],
{ {
// Legacy // Legacy
nowConfig = {}, nowConfig: nowConfig = {},
// Latest // Latest
name, name,
@@ -61,12 +120,12 @@ export default class Now extends EventEmitter {
deployStamp, deployStamp,
projectSettings, projectSettings,
skipAutoDetectionConfirmation, skipAutoDetectionConfirmation,
}, }: CreateOptions,
org, org: Org,
isSettingUpProject, isSettingUpProject: boolean,
cwd cwd?: string
) { ) {
let hashes = {}; let hashes: any = {};
const uploadStamp = stamp(); const uploadStamp = stamp();
let requestBody = { let requestBody = {
@@ -109,7 +168,7 @@ export default class Now extends EventEmitter {
let sizeExceeded = 0; let sizeExceeded = 0;
const { log, warn } = this._output; const { log, warn } = this._output;
deployment.warnings.forEach(warning => { deployment.warnings.forEach((warning: any) => {
if (warning.reason === 'size_limit_exceeded') { if (warning.reason === 'size_limit_exceeded') {
const { sha, limit } = warning; const { sha, limit } = warning;
const n = hashes[sha].names.pop(); const n = hashes[sha].names.pop();
@@ -135,14 +194,14 @@ export default class Now extends EventEmitter {
return deployment; return deployment;
} }
async handleDeploymentError(error, { env }) { async handleDeploymentError(error: any, { env }: any) {
if (error.status === 429) { if (error.status === 429) {
if (error.code === 'builds_rate_limited') { if (error.code === 'builds_rate_limited') {
const err = new Error(error.message); const err = Object.create(APIError.prototype);
err.message = error.message;
err.status = error.status; err.status = error.status;
err.retryAfter = 'never'; err.retryAfter = 'never';
err.code = error.code; err.code = error.code;
return err; return err;
} }
@@ -157,8 +216,8 @@ export default class Now extends EventEmitter {
msg += 'Please slow down.'; msg += 'Please slow down.';
} }
const err = new Error(msg); const err = Object.create(APIError.prototype);
err.message = msg;
err.status = error.status; err.status = error.status;
err.retryAfter = 'never'; err.retryAfter = 'never';
@@ -172,7 +231,6 @@ export default class Now extends EventEmitter {
if (error.status === 400 && error.code === 'missing_files') { if (error.status === 400 && error.code === 'missing_files') {
this._missing = error.missing || []; this._missing = error.missing || [];
return error; return error;
} }
@@ -199,7 +257,7 @@ export default class Now extends EventEmitter {
'.vercelignore' '.vercelignore'
)}):` + )}):` +
`\n- ${unreferencedBuildSpecs `\n- ${unreferencedBuildSpecs
.map(item => JSON.stringify(item)) .map((item: any) => JSON.stringify(item))
.join('\n- ')}`; .join('\n- ')}`;
} else { } else {
Object.assign(err, error); Object.assign(err, error);
@@ -211,13 +269,17 @@ export default class Now extends EventEmitter {
// Handle build errors // Handle build errors
if (error.id && error.id.startsWith('bld_')) { if (error.id && error.id.startsWith('bld_')) {
return new BuildError({ return new BuildError({
message: 'Build failed',
meta: { meta: {
entrypoint: error.entrypoint, entrypoint: error.entrypoint,
}, },
}); });
} }
if (error.errorCode && error.errorCode === 'BUILD_FAILED') { if (
error.errorCode === 'BUILD_FAILED' ||
error.errorCode === 'UNEXPECTED_ERROR'
) {
return new BuildError({ return new BuildError({
message: error.errorMessage, message: error.errorMessage,
meta: {}, meta: {},
@@ -227,7 +289,7 @@ export default class Now extends EventEmitter {
return new Error(error.message); return new Error(error.message);
} }
async listSecrets(next, testWarningFlag) { async listSecrets(next?: number, testWarningFlag?: boolean) {
const payload = await this.retry(async bail => { const payload = await this.retry(async bail => {
let secretsUrl = '/v3/now/secrets?limit=20'; let secretsUrl = '/v3/now/secrets?limit=20';
@@ -256,8 +318,11 @@ export default class Now extends EventEmitter {
return payload; return payload;
} }
async list(app, { version = 4, meta = {}, nextTimestamp } = {}) { async list(
const fetchRetry = async (url, options = {}) => { app?: string,
{ version = 4, meta = {}, nextTimestamp }: ListOptions = {}
) {
const fetchRetry = async (url: string, options: FetchOptions = {}) => {
return this.retry( return this.retry(
async bail => { async bail => {
const res = await this._fetch(url, options); const res = await this._fetch(url, options);
@@ -293,8 +358,8 @@ export default class Now extends EventEmitter {
); );
const deployments = await Promise.all( const deployments = await Promise.all(
projects.map(async ({ id: projectId }) => { projects.map(async ({ id: projectId }: any) => {
const query = new URLSearchParams({ limit: 1, projectId }); const query = new URLSearchParams({ limit: '1', projectId });
const { deployments } = await fetchRetry( const { deployments } = await fetchRetry(
`/v${version}/now/deployments?${query}` `/v${version}/now/deployments?${query}`
); );
@@ -323,7 +388,7 @@ export default class Now extends EventEmitter {
return response; return response;
} }
async findDeployment(hostOrId) { async findDeployment(hostOrId: string) {
const { debug } = this._output; const { debug } = this._output;
let id = hostOrId && !hostOrId.includes('.'); let id = hostOrId && !hostOrId.includes('.');
@@ -387,7 +452,7 @@ export default class Now extends EventEmitter {
); );
} }
async remove(deploymentId, { hard }) { async remove(deploymentId: string, { hard = false }: RemoveOptions) {
const url = `/now/deployments/${deploymentId}?hard=${hard ? 1 : 0}`; const url = `/now/deployments/${deploymentId}?hard=${hard ? 1 : 0}`;
await this.retry(async bail => { await this.retry(async bail => {
@@ -409,56 +474,51 @@ export default class Now extends EventEmitter {
return true; return true;
} }
retry(fn, { retries = 3, maxTimeout = Infinity } = {}) { retry<T>(
return retry(fn, { fn: retry.RetryFunction<T>,
{ retries = 3, maxTimeout = Infinity }: retry.Options = {}
) {
return retry<T>(fn, {
retries, retries,
maxTimeout, maxTimeout,
onRetry: this._onRetry, onRetry: this._onRetry,
}); });
} }
_onRetry(err) { _onRetry(err: Error) {
this._output.debug(`Retrying: ${err}\n${err.stack}`); this._output.debug(`Retrying: ${err}\n${err.stack}`);
} }
close() {} close() {}
get syncAmount() { async _fetch(_url: string, opts: FetchOptions = {}) {
if (!this._syncAmount) {
this._syncAmount = this._missing
.map(sha => this._files.get(sha).data.length)
.reduce((a, b) => a + b, 0);
}
return this._syncAmount;
}
async _fetch(_url, opts = {}) {
if (opts.useCurrentTeam !== false && this.currentTeam) { if (opts.useCurrentTeam !== false && this.currentTeam) {
const parsedUrl = parseUrl(_url, true); const parsedUrl = parseUrl(_url, true);
const query = parsedUrl.query; const query = parsedUrl.query;
query.teamId = this.currentTeam; query.teamId = this.currentTeam;
_url = `${parsedUrl.pathname}?${qs.encode(query)}`; _url = `${parsedUrl.pathname}?${qs.stringify(query)}`;
delete opts.useCurrentTeam; delete opts.useCurrentTeam;
} }
opts.headers = opts.headers || {}; opts.headers = new Headers(opts.headers);
opts.headers.accept = 'application/json'; opts.headers.set('accept', 'application/json');
opts.headers.authorization = `Bearer ${this._token}`; if (this._token) {
opts.headers['user-agent'] = ua; opts.headers.set('authorization', `Bearer ${this._token}`);
if (
opts.body &&
typeof opts.body === 'object' &&
opts.body.constructor === Object
) {
opts.body = JSON.stringify(opts.body);
opts.headers['content-type'] = 'application/json; charset=utf-8';
} }
opts.headers.set('user-agent', ua);
let body;
if (isJSONObject(opts.body)) {
body = JSON.stringify(opts.body);
opts.headers.set('content-type', 'application/json; charset=utf8');
} else {
body = opts.body;
}
const res = await this._output.time( const res = await this._output.time(
`${opts.method || 'GET'} ${this._apiUrl}${_url} ${opts.body || ''}`, `${opts.method || 'GET'} ${this._apiUrl}${_url} ${opts.body || ''}`,
fetch(`${this._apiUrl}${_url}`, opts) fetch(`${this._apiUrl}${_url}`, { ...opts, body })
); );
printIndications(res); printIndications(res);
return res; return res;
@@ -472,7 +532,7 @@ export default class Now extends EventEmitter {
// which automatically returns the json response body // which automatically returns the json response body
// if the response is ok and content-type json // if the response is ok and content-type json
// it does the same for JSON` body` in opts // it does the same for JSON` body` in opts
async fetch(url, opts = {}) { async fetch(url: string, opts: FetchOptions = {}) {
return this.retry(async bail => { return this.retry(async bail => {
if (opts.json !== false && opts.body && typeof opts.body === 'object') { if (opts.json !== false && opts.body && typeof opts.body === 'object') {
opts = Object.assign({}, opts, { opts = Object.assign({}, opts, {
@@ -492,7 +552,7 @@ export default class Now extends EventEmitter {
return null; return null;
} }
return res.headers.get('content-type').includes('application/json') return res.headers.get('content-type')?.includes('application/json')
? res.json() ? res.json()
: res; : res;
} }

View File

@@ -4,17 +4,16 @@ import confirm from './confirm';
import getProjectByIdOrName from '../projects/get-project-by-id-or-name'; import getProjectByIdOrName from '../projects/get-project-by-id-or-name';
import chalk from 'chalk'; import chalk from 'chalk';
import { ProjectNotFound } from '../../util/errors-ts'; import { ProjectNotFound } from '../../util/errors-ts';
import { Output } from '../output';
import { Project, Org } from '../../types'; import { Project, Org } from '../../types';
import slugify from '@sindresorhus/slugify'; import slugify from '@sindresorhus/slugify';
export default async function inputProject( export default async function inputProject(
output: Output,
client: Client, client: Client,
org: Org, org: Org,
detectedProjectName: string, detectedProjectName: string,
autoConfirm: boolean autoConfirm: boolean
): Promise<Project | string> { ): Promise<Project | string> {
const { output } = client;
const slugifiedName = slugify(detectedProjectName); const slugifiedName = slugify(detectedProjectName);
// attempt to auto-detect a project to link // attempt to auto-detect a project to link

View File

@@ -1,10 +1,11 @@
import inquirer from 'inquirer';
import chalk from 'chalk'; import chalk from 'chalk';
import inquirer from 'inquirer';
import Prompt from 'inquirer/lib/prompts/base';
// Here we patch inquirer to use a `>` instead of the ugly green `?` // Here we patch inquirer to use a `>` instead of the ugly green `?`
/* eslint-disable no-multiple-empty-lines, no-var, no-undef, no-eq-null, eqeqeq, semi */ /* eslint-disable no-multiple-empty-lines, no-var, no-undef, no-eq-null, eqeqeq, semi */
const getQuestion = function() { const getQuestion = function (this: Prompt) {
var message = `${chalk.bold(`> ${this.opt.message}`)} `; var message = `${chalk.bold(`> ${this.opt.message}`)} `;
// Append the default if available, and if question isn't answered // Append the default if available, and if question isn't answered

View File

@@ -1,5 +1,8 @@
import inquirer from 'inquirer';
import chalk from 'chalk'; import chalk from 'chalk';
import inquirer from 'inquirer';
import Prompt from 'inquirer/lib/prompts/base';
import Choice from 'inquirer/lib/objects/choice';
import Separator from 'inquirer/lib/objects/separator';
/** /**
* Here we patch inquirer with some tweaks: * Here we patch inquirer with some tweaks:
@@ -10,7 +13,7 @@ import chalk from 'chalk';
*/ */
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/base.js#L126 // adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/base.js#L126
const getQuestion = function () { const getQuestion = function (this: Prompt) {
let message = `${chalk.gray('?')} ${this.opt.message} `; let message = `${chalk.gray('?')} ${this.opt.message} `;
if (this.opt.type === 'confirm') { if (this.opt.type === 'confirm') {
@@ -57,7 +60,7 @@ inquirer.prompt.prompts.list.prototype.render = function () {
this.screen.render(message); this.screen.render(message);
}; };
function listRender(choices, pointer) { function listRender(choices: (Choice | Separator)[], pointer: number) {
let output = ''; let output = '';
let separatorOffset = 0; let separatorOffset = 0;
@@ -89,7 +92,7 @@ function listRender(choices, pointer) {
} }
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/checkbox.js#L84 // adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/checkbox.js#L84
inquirer.prompt.prompts.checkbox.prototype.render = function (error) { inquirer.prompt.prompts.checkbox.prototype.render = function (error?: string) {
// Render question // Render question
let message = this.getQuestion(); let message = this.getQuestion();
let bottomContent = ''; let bottomContent = '';
@@ -125,7 +128,7 @@ inquirer.prompt.prompts.checkbox.prototype.render = function (error) {
this.screen.render(message, bottomContent); this.screen.render(message, bottomContent);
}; };
function renderChoices(choices, pointer) { function renderChoices(choices: (Choice | Separator)[], pointer: number) {
let output = ''; let output = '';
let separatorOffset = 0; let separatorOffset = 0;
@@ -162,7 +165,7 @@ function renderChoices(choices, pointer) {
} }
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/input.js#L44 // adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/input.js#L44
inquirer.prompt.prompts.input.prototype.render = function (error) { inquirer.prompt.prompts.input.prototype.render = function (error?: string) {
let bottomContent = ''; let bottomContent = '';
let appendContent = ''; let appendContent = '';
let message = this.getQuestion(); let message = this.getQuestion();
@@ -189,7 +192,7 @@ inquirer.prompt.prompts.input.prototype.render = function (error) {
}; };
// adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/confirm.js#L64 // adjusted from https://github.com/SBoudrias/Inquirer.js/blob/942908f17319343d1acc7b876f990797c5695918/packages/inquirer/lib/prompts/confirm.js#L64
inquirer.prompt.prompts.confirm.prototype.render = function (answer) { inquirer.prompt.prompts.confirm.prototype.render = function (answer?: boolean) {
let message = this.getQuestion(); let message = this.getQuestion();
if (this.status === 'answered') { if (this.status === 'answered') {

View File

@@ -1,7 +1,7 @@
import Client from '../client';
import inquirer from 'inquirer'; import inquirer from 'inquirer';
import Client from '../client';
import getUser from '../get-user'; import getUser from '../get-user';
import getTeams from '../get-teams'; import getTeams from '../teams/get-teams';
import { User, Team, Org } from '../../types'; import { User, Team, Org } from '../../types';
type Choice = { name: string; value: Org }; type Choice = { name: string; value: Org };

View File

@@ -2,7 +2,6 @@ import { join, basename } from 'path';
import chalk from 'chalk'; import chalk from 'chalk';
import { remove } from 'fs-extra'; import { remove } from 'fs-extra';
import { ProjectLinkResult, ProjectSettings } from '../../types'; import { ProjectLinkResult, ProjectSettings } from '../../types';
import { VercelConfig } from '../dev/types';
import { import {
getLinkedProject, getLinkedProject,
linkFolderToProject, linkFolderToProject,
@@ -23,18 +22,30 @@ import editProjectSettings from '../input/edit-project-settings';
import stamp from '../output/stamp'; import stamp from '../output/stamp';
import { EmojiLabel } from '../emoji'; import { EmojiLabel } from '../emoji';
import createDeploy from '../deploy/create-deploy'; import createDeploy from '../deploy/create-deploy';
import Now from '../index'; import Now, { CreateOptions } from '../index';
export interface SetupAndLinkOptions {
forceDelete?: boolean;
autoConfirm?: boolean;
successEmoji: EmojiLabel;
setupMsg: string;
projectName?: string;
}
export default async function setupAndLink( export default async function setupAndLink(
client: Client, client: Client,
path: string, path: string,
forceDelete: boolean, {
autoConfirm: boolean, forceDelete = false,
successEmoji: EmojiLabel, autoConfirm = false,
setupMsg: string successEmoji,
setupMsg,
projectName,
}: SetupAndLinkOptions
): Promise<ProjectLinkResult> { ): Promise<ProjectLinkResult> {
const { const {
authConfig: { token }, authConfig: { token },
localConfig,
apiUrl, apiUrl,
output, output,
config, config,
@@ -90,10 +101,9 @@ export default async function setupAndLink(
throw err; throw err;
} }
const detectedProjectName = basename(path); const detectedProjectName = projectName || basename(path);
const projectOrNewProjectName = await inputProject( const projectOrNewProjectName = await inputProject(
output,
client, client,
org, org,
detectedProjectName, detectedProjectName,
@@ -134,13 +144,9 @@ export default async function setupAndLink(
return { status: 'error', exitCode: 1 }; return { status: 'error', exitCode: 1 };
} }
let localConfig: VercelConfig = {};
if (client.localConfig && !(client.localConfig instanceof Error)) {
localConfig = client.localConfig;
}
config.currentTeam = org.type === 'team' ? org.id : undefined; config.currentTeam = org.type === 'team' ? org.id : undefined;
const isZeroConfig = !localConfig.builds || localConfig.builds.length === 0; const isZeroConfig =
!localConfig || !localConfig.builds || localConfig.builds.length === 0;
try { try {
let settings: ProjectSettings = {}; let settings: ProjectSettings = {};
@@ -153,16 +159,15 @@ export default async function setupAndLink(
output, output,
currentTeam: config.currentTeam, currentTeam: config.currentTeam,
}); });
const createArgs: any = { const createArgs: CreateOptions = {
name: newProjectName, name: newProjectName,
env: {}, env: {},
build: { env: {} }, build: { env: {} },
forceNew: undefined, forceNew: undefined,
withCache: undefined, withCache: undefined,
quiet, quiet,
wantsPublic: localConfig.public, wantsPublic: localConfig?.public || false,
isFile, isFile,
type: null,
nowConfig: localConfig, nowConfig: localConfig,
regions: undefined, regions: undefined,
meta: {}, meta: {},
@@ -171,7 +176,7 @@ export default async function setupAndLink(
skipAutoDetectionConfirmation: false, skipAutoDetectionConfirmation: false,
}; };
if (!localConfig.builds || localConfig.builds.length === 0) { if (isZeroConfig) {
// Only add projectSettings for zero config deployments // Only add projectSettings for zero config deployments
createArgs.projectSettings = { sourceFilesOutsideRootDirectory }; createArgs.projectSettings = { sourceFilesOutsideRootDirectory };
} }

View File

@@ -2,6 +2,11 @@ import chalk from 'chalk';
import title from 'title'; import title from 'title';
import bytes from 'bytes'; import bytes from 'bytes';
import { isReady, isFailed } from '../build-state'; import { isReady, isFailed } from '../build-state';
import { Build, BuildOutput } from '../../types';
export interface Times {
[id: string]: string | null;
}
// That's how long the word "Initializing" is // That's how long the word "Initializing" is
const longestState = 12; const longestState = 12;
@@ -13,21 +18,24 @@ const padding = 8;
const MAX_BUILD_GROUPS = 5; const MAX_BUILD_GROUPS = 5;
const MAX_OUTPUTS_PER_GROUP = 5; const MAX_OUTPUTS_PER_GROUP = 5;
const prepareState = state => title(state.replace('_', ' ')); const prepareState = (state: string) => title(state.replace('_', ' '));
const hasOutput = (b: Build) => Array.isArray(b.output) && b.output.length > 0;
// Get the common path out of multiple builds // Get the common path out of multiple builds
const getCommonPath = (buildGroup) => { const getCommonPath = (buildGroup: Build[]) => {
const commonPath = []; const commonPath = [];
const splits = buildGroup.map((build) => getDirPath(build.entrypoint).split('/')); const splits = buildGroup.map(build =>
const shortest = splits.reduce((prevValue, currentValue) => getDirPath(build.entrypoint).split('/')
prevValue.length < currentValue.length );
? prevValue.length const shortest = splits.reduce(
: currentValue.length (prevValue, currentValue) => Math.min(prevValue, currentValue.length),
Infinity
); );
for (let i = 0; i <= shortest; i++) { for (let i = 0; i <= shortest; i++) {
const first = splits[0][i]; const first = splits[0][i];
if (splits.every((pathParts) => pathParts[i] === first)) { if (splits.every(pathParts => pathParts[i] === first)) {
commonPath.push(first); commonPath.push(first);
continue; continue;
} }
@@ -38,58 +46,63 @@ const getCommonPath = (buildGroup) => {
return commonPath.join('/') || '/'; return commonPath.join('/') || '/';
}; };
const styleBuild = (build, times, longestSource) => { const styleBuild = (build: Build, times: Times, longestSource: number) => {
const { entrypoint, readyState, id, hasOutput } = build; const { entrypoint, readyState, id } = build;
const state = prepareState(readyState).padEnd(longestState + padding); const state = prepareState(readyState).padEnd(longestState + padding);
const time = typeof times[id] === 'string' ? times[id] : ''; const time = typeof times[id] === 'string' ? times[id] : '';
let stateColor = chalk.grey; let stateColor = chalk.grey;
let pathColor = chalk.cyan; let pathColor = chalk.cyan;
if (isReady({ readyState })) { if (isReady(build)) {
stateColor = item => item; stateColor = chalk;
} else if (isFailed({ readyState })) { } else if (isFailed(build)) {
stateColor = chalk.red; stateColor = chalk.red;
pathColor = chalk.red; pathColor = chalk.red;
} }
const entry = entrypoint.padEnd(longestSource + padding); const entry = entrypoint.padEnd(longestSource + padding);
const prefix = hasOutput ? '┌' : '╶'; const prefix = hasOutput(build) ? '┌' : '╶';
return `${chalk.grey(prefix)} ${pathColor(entry)}${stateColor(state)}${time}`; return `${chalk.grey(prefix)} ${pathColor(entry)}${stateColor(state)}${time}`;
}; };
const styleHiddenBuilds = (commonPath, buildGroup, times, longestSource, isHidden = false) => { const styleHiddenBuilds = (
commonPath: string,
buildGroup: Build[],
times: Times,
longestSource: number,
isHidden = false
) => {
const { id } = buildGroup[0]; const { id } = buildGroup[0];
const entry = commonPath.padEnd(longestSource + padding); const entry = commonPath.padEnd(longestSource + padding);
const time = typeof times[id] === 'string' ? times[id] : ''; const time = typeof times[id] === 'string' ? times[id] : '';
const prefix = isHidden === false && buildGroup.some((build) => build.hasOutput) ? '┌' : '╶'; const prefix = isHidden === false && buildGroup.some(hasOutput) ? '┌' : '╶';
// Set the defaults so that they will be sorted // Set the defaults so that they will be sorted
const stateMap = { const stateMap: { [readyState: string]: number } = {
READY: 0, READY: 0,
ERROR: 0, ERROR: 0,
BUILDING: 0 BUILDING: 0,
}; };
buildGroup.map(({ readyState }) => { for (const { readyState } of buildGroup) {
stateMap[readyState] = stateMap[readyState] stateMap[readyState]++;
? stateMap[readyState] + 1 }
: 1;
return readyState; let state = Object.keys(stateMap)
}); .map(readyState => {
const counter = stateMap[readyState];
const name = prepareState(readyState);
let state = Object.keys(stateMap).map((readyState) => { if (!counter) {
const counter = stateMap[readyState]; return null;
const name = prepareState(readyState); }
if (!counter) { return `${counter > 9 ? '9+' : counter} ${name}`;
return null; })
} .filter(s => s)
.join(', ');
return `${counter > 9 ? '9+' : counter} ${name}`;
}).filter(s => s).join(', ')
// Since the longestState might still be shorter // Since the longestState might still be shorter
// than multiple states we still want to ensure // than multiple states we still want to ensure
@@ -100,7 +113,7 @@ const styleHiddenBuilds = (commonPath, buildGroup, times, longestSource, isHidd
let stateColor = chalk.grey; let stateColor = chalk.grey;
if (buildGroup.every(isReady)) { if (buildGroup.every(isReady)) {
stateColor = item => item; stateColor = chalk;
} else if (buildGroup.every(isFailed)) { } else if (buildGroup.every(isFailed)) {
stateColor = chalk.red; stateColor = chalk.red;
pathColor = chalk.red; pathColor = chalk.red;
@@ -113,8 +126,12 @@ const styleHiddenBuilds = (commonPath, buildGroup, times, longestSource, isHidd
return `${chalk.grey(prefix)} ${pathColor(entry)}${stateColor(state)}${time}`; return `${chalk.grey(prefix)} ${pathColor(entry)}${stateColor(state)}${time}`;
}; };
const styleOutput = (output) => { const styleOutput = (
const { type, path, readyState, size, isLast, lambda } = output; output: BuildOutput,
readyState: Build['readyState'],
isLast: boolean
) => {
const { type, path, size, lambda } = output;
const prefix = type === 'lambda' ? 'λ ' : ''; const prefix = type === 'lambda' ? 'λ ' : '';
const finalSize = size ? ` ${chalk.grey(`(${bytes(size)})`)}` : ''; const finalSize = size ? ` ${chalk.grey(`(${bytes(size)})`)}` : '';
@@ -122,7 +139,7 @@ const styleOutput = (output) => {
let finalRegion = ''; let finalRegion = '';
if (isReady({ readyState })) { if (isReady({ readyState })) {
color = item => item; color = chalk;
} else if (isFailed({ readyState })) { } else if (isFailed({ readyState })) {
color = chalk.red; color = chalk.red;
} }
@@ -141,7 +158,11 @@ const styleOutput = (output) => {
return `${chalk.grey(corner)} ${color(main)}`; return `${chalk.grey(corner)} ${color(main)}`;
}; };
const getDirPath = (path, level = 0, highestLevel = null) => { const getDirPath = (
path: string,
level = 0,
highestLevel: number | null = null
) => {
const parts = path.split('/').slice(0, -1); const parts = path.split('/').slice(0, -1);
if (highestLevel === null || level === 0) { if (highestLevel === null || level === 0) {
@@ -152,7 +173,7 @@ const getDirPath = (path, level = 0, highestLevel = null) => {
return parts.slice(0, reverseLevel).join('/'); return parts.slice(0, reverseLevel).join('/');
}; };
const sortByEntrypoint = (a, b) => { const sortByEntrypoint = (a: Build, b: Build) => {
const aPath = getDirPath(a.entrypoint); const aPath = getDirPath(a.entrypoint);
const bPath = getDirPath(b.entrypoint); const bPath = getDirPath(b.entrypoint);
@@ -175,22 +196,30 @@ const sortByEntrypoint = (a, b) => {
return 0; return 0;
}; };
const groupBuilds = (buildList, highestLevel, counter) => { const groupBuilds = (
const currentIndex = counter % (buildList.length); buildList: Build[][],
highestLevel: number,
counter: number
) => {
const currentIndex = counter % buildList.length;
const __level = Math.ceil(counter / buildList.length); const __level = Math.ceil(counter / buildList.length);
const _level = (__level === 0 ? 1 : __level) - 1; const _level = (__level === 0 ? 1 : __level) - 1;
const level = _level > highestLevel ? highestLevel : _level; const level = _level > highestLevel ? highestLevel : _level;
const currentPath = getDirPath(buildList[currentIndex][0].entrypoint, level, highestLevel); const currentPath = getDirPath(
buildList[currentIndex][0].entrypoint,
level,
highestLevel
);
const nextList = []; const nextList = [];
let currentGroup = []; const currentGroup = [];
for (let i = 0; i < buildList.length; i++) { for (let i = 0; i < buildList.length; i++) {
const group = buildList[i]; const group = buildList[i];
const path = getDirPath(group[0].entrypoint, level, highestLevel); const path = getDirPath(group[0].entrypoint, level, highestLevel);
if (path === currentPath) { if (path === currentPath) {
currentGroup = currentGroup.concat(group); currentGroup.push(...group);
} else { } else {
nextList.push(group); nextList.push(group);
} }
@@ -205,26 +234,10 @@ const groupBuilds = (buildList, highestLevel, counter) => {
return nextList; return nextList;
}; };
const prepareBuild = (build) => { export default (builds: Build[], times: Times) => {
build.hasOutput = Array.isArray(build.output) && build.output.length > 0;
if (build.hasOutput) {
build.output = build.output.map((item) => {
item.readyState = build.readyState;
return item;
});
}
return build;
};
export default (builds, times) => {
// Sort the builds by path // Sort the builds by path
// so that the grouping will be easier // so that the grouping will be easier
let path = builds let path = builds.sort(sortByEntrypoint).map(build => [build]);
.map(prepareBuild)
.sort(sortByEntrypoint)
.map(build => [build]);
const highestLevel = builds.reduce((prev, curr) => { const highestLevel = builds.reduce((prev, curr) => {
const partCounter = curr.entrypoint.split('/').length - 1; const partCounter = curr.entrypoint.split('/').length - 1;
@@ -251,28 +264,28 @@ export default (builds, times) => {
const final = []; const final = [];
let finalBuildsLength = path.length; let finalBuildsLength = path.length;
let lengthWithoutRootPaths = path.length; let lengthWithoutRootPaths = path.length;
let hiddenBuildGroup = []; let hiddenBuildGroup: Build[] = [];
// Ungroup the root files // Ungroup the root files
path = (() => { path = (() => {
const nextList = []; const nextList = [];
const rootList = []; const rootList: Build[][] = [];
for (const group of path) { for (const group of path) {
if (getCommonPath(group) === '/') { if (getCommonPath(group) === '/') {
group.map((item) => rootList.push([item])); group.map(item => rootList.push([item]));
} else { } else {
nextList.push(group); nextList.push(group);
} }
} }
lengthWithoutRootPaths = nextList.length; lengthWithoutRootPaths = nextList.length;
rootList.map((group) => nextList.push(group)); rootList.map(group => nextList.push(group));
return nextList; return nextList;
})(); })();
path.map((buildGroup) => { for (const buildGroup of path) {
const commonPath = getCommonPath(buildGroup); const commonPath = getCommonPath(buildGroup);
// All items with the common path / are a single group // All items with the common path / are a single group
@@ -286,51 +299,63 @@ export default (builds, times) => {
finalBuildsLength++; finalBuildsLength++;
} else { } else {
hiddenBuildGroup.push(buildGroup[0]); hiddenBuildGroup.push(buildGroup[0]);
return buildGroup; continue;
} }
} else if (buildGroup.length === 1) { } else if (buildGroup.length === 1) {
const item = buildGroup[0]; const item = buildGroup[0];
final.push(`${styleBuild(item, times, longestSource)}\n`); final.push(`${styleBuild(item, times, longestSource)}\n`);
finalBuildsLength++; finalBuildsLength++;
} else { } else {
final.push(`${styleHiddenBuilds(`${commonPath}/*`, buildGroup, times, longestSource)}\n`); final.push(
`${styleHiddenBuilds(
`${commonPath}/*`,
buildGroup,
times,
longestSource
)}\n`
);
finalBuildsLength++; finalBuildsLength++;
} }
// Get the first five outputs when the deployment is ready // Get the first five outputs when the deployment is ready
const outputs = buildGroup.reduce((prevValue, currentValue) => ( const outputs: BuildOutput[] = [];
prevValue.concat(Array.isArray(currentValue.output) for (const build of buildGroup) {
? currentValue.output if (Array.isArray(build.output)) {
: [] outputs.push(...build.output);
) }
), []);
outputs.slice(0, MAX_OUTPUTS_PER_GROUP).map((output, index) => (
final.push(`${styleOutput({
...output,
isLast: outputs.length === (index + 1)
})}\n`)
));
if (outputs.length > MAX_OUTPUTS_PER_GROUP) {
final.push(chalk.grey(`└── ${outputs.length - MAX_OUTPUTS_PER_GROUP} output items hidden\n`));
} }
return buildGroup; outputs
}); .slice(0, MAX_OUTPUTS_PER_GROUP)
.forEach((output, index) =>
final.push(
`${styleOutput(output, 'READY', outputs.length === index + 1)}\n`
)
);
if (outputs.length > MAX_OUTPUTS_PER_GROUP) {
final.push(
chalk.grey(
`└── ${outputs.length - MAX_OUTPUTS_PER_GROUP} output items hidden\n`
)
);
}
}
if (hiddenBuildGroup.length) { if (hiddenBuildGroup.length) {
final.push(`${styleHiddenBuilds( final.push(
`${hiddenBuildGroup.length} builds hidden`, `${styleHiddenBuilds(
hiddenBuildGroup, `${hiddenBuildGroup.length} builds hidden`,
times, hiddenBuildGroup,
longestSource, times,
true longestSource,
)}\n`); true
)}\n`
);
} }
return { return {
lines: final.length + 1, lines: final.length + 1,
toPrint: `${final.join('')}` toPrint: `${final.join('')}`,
}; };
}; };

View File

@@ -1,8 +1,6 @@
const chars = { const chars = {
// in some setups now.exe crashes if we use
// the normal tick unicode character :|
tick: process.platform === 'win32' ? '√' : '✔', tick: process.platform === 'win32' ? '√' : '✔',
cross: process.platform === 'win32' ? '☓' : '✘' cross: process.platform === 'win32' ? '☓' : '✘',
}; } as const;
export default chars; export default chars;

View File

@@ -2,45 +2,54 @@ import chalk from 'chalk';
import boxen from 'boxen'; import boxen from 'boxen';
import renderLink from './link'; import renderLink from './link';
import wait, { StopSpinner } from './wait'; import wait, { StopSpinner } from './wait';
import { Writable } from 'stream';
export type Output = ReturnType<typeof _createOutput>;
export interface OutputOptions { export interface OutputOptions {
debug?: boolean; debug?: boolean;
} }
// Singleton export interface PrintOptions {
let instance: Output | null = null; w?: Writable;
export default function createOutput(opts?: OutputOptions) {
if (!instance) {
instance = _createOutput(opts);
}
return instance;
} }
function _createOutput({ debug: debugEnabled = false }: OutputOptions = {}) { export interface LogOptions extends PrintOptions {
let spinnerMessage = ''; color?: typeof chalk;
let spinner: StopSpinner | null = null; }
function isDebugEnabled() { export class Output {
return debugEnabled; private debugEnabled: boolean;
private spinnerMessage: string;
private _spinner: StopSpinner | null;
constructor({ debug: debugEnabled = false }: OutputOptions = {}) {
this.debugEnabled = debugEnabled;
this.spinnerMessage = '';
this._spinner = null;
} }
function print(str: string) { get isTTY() {
stopSpinner(); return process.stdout.isTTY;
process.stderr.write(str);
} }
function log(str: string, color = chalk.grey) { isDebugEnabled = () => {
print(`${color('>')} ${str}\n`); return this.debugEnabled;
} };
function dim(str: string, color = chalk.grey) { print = (str: string, { w }: PrintOptions = { w: process.stderr }) => {
print(`${color(`> ${str}`)}\n`); this.stopSpinner();
} const stream: Writable = w || process.stderr;
stream.write(str);
};
function warn( log = (str: string, color = chalk.grey) => {
this.print(`${color('>')} ${str}\n`);
};
dim = (str: string, color = chalk.grey) => {
this.print(`${color(`> ${str}`)}\n`);
};
warn = (
str: string, str: string,
slug: string | null = null, slug: string | null = null,
link: string | null = null, link: string | null = null,
@@ -48,10 +57,10 @@ function _createOutput({ debug: debugEnabled = false }: OutputOptions = {}) {
options?: { options?: {
boxen?: boxen.Options; boxen?: boxen.Options;
} }
) { ) => {
const details = slug ? `https://err.sh/vercel/${slug}` : link; const details = slug ? `https://err.sh/vercel/${slug}` : link;
print( this.print(
boxen( boxen(
chalk.bold.yellow('WARN! ') + chalk.bold.yellow('WARN! ') +
str + str +
@@ -68,110 +77,99 @@ function _createOutput({ debug: debugEnabled = false }: OutputOptions = {}) {
} }
) )
); );
print('\n'); this.print('\n');
} };
function note(str: string) { note = (str: string) => {
log(chalk`{yellow.bold NOTE:} ${str}`); this.log(chalk`{yellow.bold NOTE:} ${str}`);
} };
function error( error = (
str: string, str: string,
slug?: string, slug?: string,
link?: string, link?: string,
action = 'Learn More' action = 'Learn More'
) { ) => {
print(`${chalk.red(`Error!`)} ${str}\n`); this.print(`${chalk.red(`Error!`)} ${str}\n`);
const details = slug ? `https://err.sh/vercel/${slug}` : link; const details = slug ? `https://err.sh/vercel/${slug}` : link;
if (details) { if (details) {
print(`${chalk.bold(action)}: ${renderLink(details)}\n`); this.print(`${chalk.bold(action)}: ${renderLink(details)}\n`);
} }
} };
function prettyError(err: Error & { link?: string; action?: string }) { prettyError = (
return error(err.message, undefined, err.link, err.action); err: Pick<Error, 'message'> & { link?: string; action?: string }
} ) => {
return this.error(err.message, undefined, err.link, err.action);
};
function ready(str: string) { ready = (str: string) => {
print(`${chalk.cyan('> Ready!')} ${str}\n`); this.print(`${chalk.cyan('> Ready!')} ${str}\n`);
} };
function success(str: string) { success = (str: string) => {
print(`${chalk.cyan('> Success!')} ${str}\n`); this.print(`${chalk.cyan('> Success!')} ${str}\n`);
} };
function debug(str: string) { debug = (str: string) => {
if (debugEnabled) { if (this.debugEnabled) {
log( this.log(
`${chalk.bold('[debug]')} ${chalk.gray( `${chalk.bold('[debug]')} ${chalk.gray(
`[${new Date().toISOString()}]` `[${new Date().toISOString()}]`
)} ${str}` )} ${str}`
); );
} }
} };
function setSpinner(message: string, delay: number = 300): void { spinner = (message: string, delay: number = 300): void => {
spinnerMessage = message; this.spinnerMessage = message;
if (debugEnabled) { if (this.debugEnabled) {
debug(`Spinner invoked (${message}) with a ${delay}ms delay`); this.debug(`Spinner invoked (${message}) with a ${delay}ms delay`);
return; return;
} }
if (spinner) { if (this._spinner) {
spinner.text = message; this._spinner.text = message;
} else { } else {
spinner = wait(message, delay); this._spinner = wait(message, delay);
} }
} };
function stopSpinner() { stopSpinner = () => {
if (debugEnabled && spinnerMessage) { if (this.debugEnabled && this.spinnerMessage) {
const msg = `Spinner stopped (${spinnerMessage})`; const msg = `Spinner stopped (${this.spinnerMessage})`;
spinnerMessage = ''; this.spinnerMessage = '';
debug(msg); this.debug(msg);
} }
if (spinner) { if (this._spinner) {
spinner(); this._spinner();
spinner = null; this._spinner = null;
spinnerMessage = ''; this.spinnerMessage = '';
} }
} };
async function time<T>( time = async <T>(
label: string | ((r?: T) => string), label: string | ((r?: T) => string),
fn: Promise<T> | (() => Promise<T>) fn: Promise<T> | (() => Promise<T>)
) { ) => {
const promise = typeof fn === 'function' ? fn() : fn; const promise = typeof fn === 'function' ? fn() : fn;
if (debugEnabled) { if (this.debugEnabled) {
const startLabel = typeof label === 'function' ? label() : label; const startLabel = typeof label === 'function' ? label() : label;
debug(startLabel); this.debug(startLabel);
const start = Date.now(); const start = Date.now();
const r = await promise; const r = await promise;
const endLabel = typeof label === 'function' ? label(r) : label; const endLabel = typeof label === 'function' ? label(r) : label;
const duration = Date.now() - start; const duration = Date.now() - start;
const durationPretty = const durationPretty =
duration < 1000 ? `${duration}ms` : `${(duration / 1000).toFixed(2)}s`; duration < 1000 ? `${duration}ms` : `${(duration / 1000).toFixed(2)}s`;
debug(`${endLabel} ${chalk.gray(`[${durationPretty}]`)}`); this.debug(`${endLabel} ${chalk.gray(`[${durationPretty}]`)}`);
return r; return r;
} }
return promise; return promise;
}
return {
isDebugEnabled,
print,
log,
warn,
error,
prettyError,
ready,
success,
debug,
dim,
time,
note,
spinner: setSpinner,
stopSpinner,
}; };
} }
export default function createOutput(opts?: OutputOptions) {
return new Output(opts);
}

View File

@@ -1,5 +0,0 @@
import { gray } from 'chalk';
const effect = msg => `${gray(`+ ${msg}`)}`;
export default effect;

View File

@@ -5,7 +5,9 @@ import renderLink from './link';
const metric = metrics(); const metric = metrics();
export default function error(...input: string[] | [APIError]) { export default function error(
...input: string[] | [Pick<APIError, 'slug' | 'message' | 'link' | 'action'>]
) {
let messages = input; let messages = input;
if (typeof input[0] === 'object') { if (typeof input[0] === 'object') {
const { slug, message, link, action = 'Learn More' } = input[0]; const { slug, message, link, action = 'Learn More' } = input[0];

View File

@@ -1,6 +0,0 @@
//
import formatLogText from './format-log-text';
export default function formatLogCmd(text) {
return `${formatLogText(text)}`;
}

View File

@@ -1,8 +0,0 @@
//
import formatLogText from './format-log-text';
export default function formatLogOutput(text, prefix = '') {
return formatLogText(text)
.split('\n')
.map(textItem => `${prefix}${textItem.replace(/^> /, '')}`);
}

View File

@@ -1,4 +0,0 @@
//
export default function formatLogText(text) {
return text.replace(/\n$/, '').replace(/^\n/, '');
}

View File

@@ -1,4 +1,4 @@
export default (input, level) => { export default (input: string, level: number) => {
const fill = ' '.repeat(level); const fill = ' '.repeat(level);
return `${fill}${input.replace(/\n/g, `\n${fill}`)}`; return `${fill}${input.replace(/\n/g, `\n${fill}`)}`;
}; };

View File

@@ -1,5 +0,0 @@
import { yellow } from 'chalk';
const note = msg => `${yellow('> NOTE:')} ${msg}`;
export default note;

View File

@@ -0,0 +1,3 @@
import { yellow } from 'chalk';
export default (msg: string) => `${yellow('> NOTE:')} ${msg}`;

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