Compare commits

...

125 Commits

Author SHA1 Message Date
Steven
ed1dacd276 Publish Canary
- @vercel/build-utils@2.12.3-canary.17
 - vercel@23.1.3-canary.28
 - @vercel/client@10.2.3-canary.18
 - vercel-plugin-go@1.0.0-canary.1
 - vercel-plugin-python@1.0.0-canary.1
 - vercel-plugin-ruby@1.0.0-canary.1
 - @vercel/python@2.0.6-canary.5
 - @vercel/ruby@1.2.8-canary.4
2021-11-09 12:50:34 -05:00
Steven
144e890bfa Add plugin packages for go/python/ruby (#6961)
* Add plugin packages

* Fix usage

* Fix build

* Fix workspace linking to build-utils
2021-11-09 12:48:40 -05:00
Andy Bitz
af097c2c06 Publish Canary
- vercel@23.1.3-canary.27
2021-11-09 16:44:14 +01:00
Andy
873a582986 [cli] Ignore .next/cache in vc build (#6968)
* [cli] Ignore .next/cache in `vc build`

* Handle middleware-manifest.json

* Update manifest update
2021-11-09 16:43:26 +01:00
Andy Bitz
986b4c0b1a Publish Canary
- vercel@23.1.3-canary.26
2021-11-09 01:31:30 +01:00
Andy
14071819ac [cli] Fix NFT output path for monorepos (#6965)
* [cli] Fix `output` path for `.output` for `node_modules`

* Use baseDir instead of cwd

* Update comment

* Update output for requiredServerFiles
2021-11-09 01:30:49 +01:00
jj@jjsweb.site
2a8588a0c5 Publish Canary
- @vercel/build-utils@2.12.3-canary.16
 - vercel@23.1.3-canary.25
 - @vercel/client@10.2.3-canary.17
 - @vercel/frameworks@0.5.1-canary.11
 - @vercel/routing-utils@1.11.4-canary.6
2021-11-08 14:42:18 -06:00
JJ Kasper
0f7e89f76c [routing-utils] Add caseSensitive field to routes schema (#6952)
### Related Issues

x-ref: https://github.com/vercel/customer-issues/issues/34

### 📋 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-11-08 20:29:31 +00:00
Andy Bitz
e68ed33a88 Publish Canary
- vercel@23.1.3-canary.24
 - vercel-plugin-middleware@0.0.0-canary.6
2021-11-08 19:38:58 +01:00
Gary Borton
d3e98cdb73 [cli] Run middleware as a plugin instead of running directly (#6941)
* Move loadPlugins to a utils file to be shared w/ dev server.

* Update loadCliPlugins to also return startDevServer and runDevMiddleware based plugins.

* Move plugins back to dependencies.

These can't be bundled as it interferes with plugin resolution.

* Hook up middleware plugins to dev server.

* Pass output object to loadCliPlugins instead of a logging function.

* Allow more than one runDevMiddleware defining plugins.

* Bundle esbuild w/ middleware plugin.

* Keep esbuild as an external

* Update middleware's esbuild.

* set old space size

* Revert "set old space size"

This reverts commit b579194a862949a11769e9087f01c31f2e1f3b60.

* Use --max-old-space-size for CLI unit tests

* Increase memory

* Use `run.js` to set the memory

* Make NODE_OPTIONS optional

Co-authored-by: Leo Lamprecht <leo@vercel.com>
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>
Co-authored-by: Andy Bitz <artzbitz@gmail.com>
2021-11-08 19:38:13 +01:00
Andy Bitz
bf4e77110f Publish Canary
- vercel@23.1.3-canary.23
 - @vercel/node@1.12.2-canary.6
 - vercel-plugin-node@1.12.2-canary.7
2021-11-08 15:00:01 +01:00
Nathan Rajlich
5b5197d2c5 [node] Create vercel-plugin-node (#6874)
* [node] Refactor to Vercel CLI Plugin

* Enforce "index" suffix on output Serverless Functions

So that nesting works properly

* Some cleanup

* Add version

* Use `@vercel/static-config`

* .

* Add support for wildcard routes

* Don't compile dotfiles, underscore prefixed files, files within `node_modules`, nor TypeScript definition files

Matches the logic from `maybeGetBuilder()` in `@vercel/build-utils`.

* Bump version

* Introduce testing framework

* Debug

* Add test without any deps

* Longer timeout to install Node.js for vercel/fun

* Install deps

* Add legacy Node.js server interface test

* More tests

* Test "assets" fixture

* Test "helpers" fixture

* fix

* Support AWS native API

* Remove debugging `console.log()` calls

* Use plugin-node for new plugin instead

* Revert "Use plugin-node for new plugin instead"

This reverts commit f317b8c6ecdc67a74d5f2b12a2e7567a27d4b6b8.

* Move to `plugin-node` directory

* Update plugin-node version in package.json

* Checkout node from main

* Add yarn.lock files for tests

* Update node-bridge

Co-authored-by: Andy <AndyBitz@users.noreply.github.com>
Co-authored-by: Andy Bitz <artzbitz@gmail.com>
2021-11-08 14:59:01 +01:00
Steven
a6ccf6c180 Publish Canary
- @vercel/build-utils@2.12.3-canary.15
 - vercel@23.1.3-canary.22
 - @vercel/client@10.2.3-canary.16
 - @vercel/node-bridge@2.1.1-canary.2
 - @vercel/node@1.12.2-canary.5
2021-11-05 18:40:08 -04:00
Steven
8d848ebe8b [node] Fix launcher ESM on Windows dev (#6953) 2021-11-05 18:38:55 -04:00
Steven
6ef2c16d63 [build-utils] Add convertRuntimeToPlugin() (#6942) 2021-11-05 16:12:21 -04:00
Andy Bitz
6c71ceaaeb Publish Canary
- vercel@23.1.3-canary.21
2021-11-05 10:26:15 +01:00
Andy
1dcb6dfc6f [cli] Rename certain outputs in vercel build (#6948) 2021-11-05 10:24:49 +01:00
Steven
4fd24575e5 Publish Canary
- vercel@23.1.3-canary.20
 - vercel-plugin-middleware@0.0.0-canary.5
2021-11-03 09:06:27 -04:00
Gary Borton
8714f1905e [cli] Render an error page when middleware explodes. (#6934)
### Related Issues

This adds error handling for middleware in `vc dev`

Local looks a little different than production:
Local
![image](https://user-images.githubusercontent.com/4172067/139951848-efa08a88-aeee-446f-8319-35c8bb0b1d75.png)

Production
![image](https://user-images.githubusercontent.com/4172067/139951580-33a4ad0f-18a5-4aa1-bfb0-5e1da646a4b8.png)


### 📋 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
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2021-11-02 23:57:46 +00:00
Jared Palmer
2e69f2513d [cli] Make sure vc pull properly sets currentTeam (#6936)
### Related Issues

> Fixes https://github.com/vercel/runtimes/issues/219

### 📋 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-11-02 23:07:28 +00:00
Gary Borton
979e4b674a [cli] Bundle middleware deps (#6932)
* Use ncc to bundle middleware plugin, move dependencies to devDependencies, remove querystring

* Switch from globby (deprecated dependencies) to glob in middleware plugin.

* Bundle the node-plugin and middleware-plugin into cli.
2021-11-02 17:08:02 -04:00
Nathan Rajlich
07fa47bcfb Publish Canary
- vercel@23.1.3-canary.19
 - vercel-plugin-middleware@0.0.0-canary.4
2021-10-29 18:17:08 -07:00
Nathan Rajlich
307c4fc377 [cli] Bump vercel-plugin-node to latest 2021-10-29 18:16:16 -07:00
Nathan Rajlich
44868d79b6 Add initial vercel-plugin-middleware (#6892)
* Add initial `vercel-plugin-middleware`

* Ignore `entries.js` from ESLint

* Add `runDevMiddleware()` stub

* Add test

* Add support for "_middleware.{js,ts}" to `vercel dev` (#6880)

* Add websandbox from next.js codebase.

* Use node-fetch instead of next's polyfilled fetch.

* Handle middleware rewrites.

* Add response, headers, and request to websandbox context.

* Move websandbox dependency to middleware plugin.

* Add integration tests, update websandbox to support ts files and json imports.

* commit yarn.lock changes after rebasing

* Clean up left over console.logs, fix some tsc issues, and rebase issue.

* Fix failing test and eslint.

* Fix middleware test on windows.

* [examples] Update Vercel Next.js example template to 12.0.1 (#6905)

* Mark the Plugins as external to CLI's ncc build

* [cli] Improve tracing in vc build (#6898)

* [cli] Fix tracing for `vc build`

* Ignore object when there are no changes

* Make Next < 12 work with FS API w/ nft

* Update packages/cli/src/commands/build.ts

Co-authored-by: Nathan Rajlich <n@n8.io>

* Document how Next.js processing works in build

* [cli] Fix static assets (#6906)

* Make sure output path is .next

* Fix up require-server-files for processing

* Fix typo

* Move static

* Update static rename

Co-authored-by: Andy Bitz <artzbitz@gmail.com>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>

* Publish Canary

 - vercel@23.1.3-canary.17
 - @vercel/client@10.2.3-canary.15
 - @vercel/static-config@0.0.1-canary.0

* [cli] Ignore `.env` and `.gitignore` in "vc build" (#6910)

* Publish Canary

 - vercel@23.1.3-canary.18

* Pass workPath to plugins.

The new plugin architecture doesn't pass a full BuildOptions object, previous
to this commit it wasn't passing any options at all. I've added workingPath to
support running dev/build from directories other than the project root.

* Remove error state when package.json exists, but no build script

This allows vercel build to continue working for projects that are not using
frameworks, but use package.json to manage dependencies.

* Fix types, pull in middleware header fix from next.js

Next js PR w/ the header fix:
https://github.com/vercel/next.js/pull/30560

* Fix missing entries file for vc build.

* Update call signature of middleware when using vc build.

Co-authored-by: Drew Bredvick <dbredvick@gmail.com>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Jared Palmer <jared@jaredpalmer.com>
Co-authored-by: Andy Bitz <artzbitz@gmail.com>
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>

Co-authored-by: Gary Borton <gdborton@gmail.com>
Co-authored-by: Drew Bredvick <dbredvick@gmail.com>
Co-authored-by: Jared Palmer <jared@jaredpalmer.com>
Co-authored-by: Andy Bitz <artzbitz@gmail.com>
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>
2021-10-29 17:15:20 -07:00
Nathan Rajlich
df9a4afa5c Publish Canary
- vercel@23.1.3-canary.18
2021-10-29 12:22:35 -07:00
Nathan Rajlich
8a6869bae2 [cli] Ignore .env and .gitignore in "vc build" (#6910) 2021-10-29 12:21:46 -07:00
Andy Bitz
a3fc3c1ca7 Publish Canary
- vercel@23.1.3-canary.17
 - @vercel/client@10.2.3-canary.15
 - @vercel/static-config@0.0.1-canary.0
2021-10-29 20:49:54 +02:00
Jared Palmer
44037c58be [cli] Improve tracing in vc build (#6898)
* [cli] Fix tracing for `vc build`

* Ignore object when there are no changes

* Make Next < 12 work with FS API w/ nft

* Update packages/cli/src/commands/build.ts

Co-authored-by: Nathan Rajlich <n@n8.io>

* Document how Next.js processing works in build

* [cli] Fix static assets (#6906)

* Make sure output path is .next

* Fix up require-server-files for processing

* Fix typo

* Move static

* Update static rename

Co-authored-by: Andy Bitz <artzbitz@gmail.com>
Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Andy <AndyBitz@users.noreply.github.com>
2021-10-29 20:49:21 +02:00
Drew Bredvick
1a9419b690 [examples] Update Vercel Next.js example template to 12.0.1 (#6905) 2021-10-29 13:03:31 -04:00
Nathan Rajlich
93d0e5966c [cli] Fix integration tests that check .gitignore (#6904) 2021-10-28 12:37:39 -07:00
Nathan Rajlich
306f3a1312 Add to "static-config" to run.js script 2021-10-27 17:36:32 -07:00
Nathan Rajlich
9c67e8115e Add @vercel/static-config (#6897) 2021-10-27 17:03:12 -07:00
Andy
b890ac1e44 [cli] Fix tracing for vc build (#6896)
* [cli] Fix tracing for `vc build`

* Ignore object when there are no changes
2021-10-28 00:20:44 +02:00
Andy
28e71ff109 [cli][client] Add step for checks (#6889)
* [cli][client] Add step for checks

* Update output

* Remove skipped message

* Correctly use `finishedEvents`

* Update packages/cli/src/commands/deploy/index.ts

Co-authored-by: Chris <7249920+chriswdmr@users.noreply.github.com>

* Add back deleted files

Co-authored-by: Chris <7249920+chriswdmr@users.noreply.github.com>
2021-10-27 20:22:35 +02:00
Andy Bitz
2bf060c708 Publish Canary
- @vercel/build-utils@2.12.3-canary.14
 - vercel@23.1.3-canary.16
 - @vercel/client@10.2.3-canary.14
 - @vercel/frameworks@0.5.1-canary.10
2021-10-26 14:10:32 +02:00
Jared Palmer
c8ef1d71d1 [cli] Add vercel build (#6861)
* Add PoC pull command

* Improve pull's linking logic

* Remove `__VERCEL_SKIP_PULL_CMD` env var and unneeded type narrowing

* Improve pull's link logic

* Fix team mock

* Add smoke test for vc pull

* Add setupFixture helper

* Remove debug duplicate

* Use a fixture in pull test

* Put back debug

* Fix unit test

* Fix test on windows

* Remove mock-stdin

* Pass cwd to link and pull

* Add help command for `vc pull`

* Organize imports

* Update packages/cli/src/commands/deploy/args.ts

Co-authored-by: Steven <steven@ceriously.com>

* Use tmp-promise instead of tmp

* Update packages/cli/src/commands/pull.ts

Co-authored-by: Steven <steven@ceriously.com>

* Update packages/cli/src/commands/pull.ts

Co-authored-by: Steven <steven@ceriously.com>

* Clean up pull comments and variable declarations

* Remove graceful tmp directory cleanup

* Put back graceful cleanup

* Print output for debugging

* Moar debug

* Set 10s timeout for `vc pull` test

* Change fixture to now-pull-next

* Fix pull test name

* Switch project mock to use encrypted env

* Add --env flag

* Update fixtures

* Remove now- from everywhere

* Remove instrumentation

* Rename setupFixture to setup-fixture

* Add deprecation warning to `vc env pull`

* Update packages/cli/src/commands/deploy/args.ts

Co-authored-by: Nathan Rajlich <nathan@tootallnate.net>

* Update packages/cli/src/commands/env/pull.ts

Co-authored-by: Nathan Rajlich <nathan@tootallnate.net>

* Fix pull and update imports

* Revamped build command

* printConfig -> renderConfig

* Remove frameworks

* Add build state unit test for gatsby

* Update packages/cli/src/commands/pull.ts

Co-authored-by: Steven <steven@ceriously.com>

* Update packages/cli/src/commands/env/index.ts

Co-authored-by: Steven <steven@ceriously.com>

* Update packages/cli/src/util/projects/write-project-settings.ts

Co-authored-by: Nathan Rajlich <nathan@tootallnate.net>

* Update packages/cli/src/commands/pull.ts

Co-authored-by: Nathan Rajlich <nathan@tootallnate.net>

* Don't writeProjectSettings in env pull

* Cleanup fixtures once afterAll

* Add help text for --env and -y

* Add output for pull

* Write project settings output after env output

* Pushing for Nate

* Add todo about handling Other's build output directory

* Add support for plugins

* Improve prompt logic loop

* Pluralize plugin output

* Better plugin failure handling

* Refactor away from BuildState class

* Remove unused file

* Correct framework detection logic, smart copy w/hardlinks

* Generate build manifest

* Generate routes-manifest.json

* Remove ambiguity when calling vc build

* Make sure manifests have all keys

* Ensure that .output is added to .gitignore in vc link

* Remove directoryListing from project settings

* Add support for default cli plugins

* Remove directory listing

* Install vercel-plugin-node

* Patch console.log and console.error for plugins

* Improve build output

* Remove unused unit tests

* Allow build without token

* Add vercel plugin middleware

* Bump vercel-plugin-middleware

* Bump middleware plugin

* Tweak require.resolve

* Tweak require.resolve to __dirname

* Update `vercel-plugin-node`

* Fix prefixed logs when using formatters i.e. `%j`

* Pretty print the manifest files

* Get unique CLI deps

For the case where the local project has one of the CLI default plugins
specified.

* Update packages/cli/package.json

Co-authored-by: Nathan Rajlich <nathan@tootallnate.net>

* Add help output

* Remove p-limit properly

* Confirm before pull in build

* Bump `vercel-plugin-node`

* [cli] Make Next.js move files into .output (#6885)

* Use OUTPUT_DIR

* Return properly if no package.json exists and no build command

* Invert logic for runPackageJsonScript

* Bump `vercel-plugin-middleware`

* Fix defaultHeaders in frameworks

* Fix build when nft files is object

* Update `yarn.lock`

* 2nd attempt at --prebuilt (#6888)

Co-authored-by: Andy Bitz <artzbitz@gmail.com>

* Add beta and feedback to vc build

Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Steven <steven@ceriously.com>
Co-authored-by: Nathan Rajlich <nathan@tootallnate.net>
Co-authored-by: Andy Bitz <artzbitz@gmail.com>
2021-10-26 14:04:52 +02:00
Nathan Rajlich
3d2efc7dcd [frameworks] Fix small typo in "Other" preset description (#6882) 2021-10-23 14:56:22 +00:00
jj@jjsweb.site
cd7185a872 Publish Canary
- @vercel/build-utils@2.12.3-canary.13
 - vercel@23.1.3-canary.15
 - @vercel/client@10.2.3-canary.13
 - @vercel/frameworks@0.5.1-canary.9
 - @vercel/routing-utils@1.11.4-canary.5
2021-10-21 17:58:50 -05:00
Kiko Beats
981a76fbe2 [routing-utils] Add missing fields (#6872)
* chore: add missing fields

* Update types.ts

* Update types.ts

* chore add missing as part of the schema

* test: add missing case
2021-10-21 17:57:48 -05:00
Nathan Rajlich
c35c05446b [cli] Add CLI login unit test (#6866)
This has just been sitting in my working tree for a while, might as well push it up.
2021-10-19 20:38:29 +00:00
Jared Palmer
b88e65c4ad [cli] Add vercel pull command (#6822)
Add `vercel pull` command. Closes https://github.com/vercel/runtimes/issues/142. 

![CleanShot 2021-10-19 at 08 50 07](https://user-images.githubusercontent.com/4060187/137946853-860204ba-afaa-42da-be8a-d35bec97c808.png)

It does the following:
- Pull the `.env` file (it calls `pull` internally)
- Adds the following to `.vercel/project.json` (which will then be used by `vercel build`):
   ```tsx
   interface ProjectLink {
        projectId: string
        orgId: string
        settings: {
          buildCommand: string | null,
          devCommand:  string | null,
          directoryListing:  string | null,
          outputDirectory:  string | null,
          rootDirectory:  string | null,
          framework: string | null,
        }
    }
   ```
This PR also deprecates `vc env pull` with a warning:

![CleanShot 2021-10-19 at 08 50 21](https://user-images.githubusercontent.com/4060187/137946847-e102bd99-943a-4f07-b457-fe886007c044.png)

### 📋 Checklist

- [x] Help output
- [x] Flags (need to combine flags from `env pull`)

#### 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

- [x] This PR has a concise title and thorough description useful to a reviewer
- [x] Issue from task tracker has a link to this PR
2021-10-19 19:10:31 +00:00
Javi Velasco
9e16ce750b Publish Canary
- @vercel/build-utils@2.12.3-canary.12
 - vercel@23.1.3-canary.14
 - @vercel/client@10.2.3-canary.12
 - @vercel/frameworks@0.5.1-canary.8
 - @vercel/routing-utils@1.11.4-canary.4
2021-10-19 14:52:11 +02:00
Javi Velasco
2105d31730 Update middleware route (#6863) 2021-10-19 14:50:41 +02:00
jj@jjsweb.site
3d7e01ebf0 Publish Canary
- @vercel/build-utils@2.12.3-canary.11
 - vercel@23.1.3-canary.13
 - @vercel/client@10.2.3-canary.11
 - @vercel/frameworks@0.5.1-canary.7
 - @vercel/routing-utils@1.11.4-canary.3
2021-10-18 16:50:01 -05:00
JJ Kasper
dccacc4ca0 [routing-utils] Update routes schema for new property (#6860)
Adds an additional property for the `routes` schema. 

### Related Issues

N/A

### 📋 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-10-18 21:45:42 +00:00
Devon Govett
be5c0da521 [example][frameworks] Add support for Parcel framework (#6836) 2021-10-14 14:53:57 -07:00
Katie Byers
f985d953ed [cli] Fix swapped error messages in executeBuild() (#6724) 2021-10-14 14:31:02 -07:00
Nathan Rajlich
b8f8289afe [cli] Use getters for Now props that mirror Client (#6838)
This will prevent any issues where the `Now` instance is out-of-sync with the `Client` instance, for example, during a re-auth where a new auth token is issued. Also reduces a bit of code which is nice.
2021-10-13 22:40:06 +00:00
Nathan Rajlich
e01a1cebfd [cli] Don't pass teamId query param to /verify token endpoint (#6841)
Fixes an issue where the token would not be properly upgraded while
a Team scope was currently active, causing a new auth token to be issued
and lose the previous access scope(s).
2021-10-13 14:39:24 -07:00
Matt Netkow
2515d522a3 [frameworks] Make "vercel dev" work with Ionic Angular projects (#6658) 2021-10-13 11:32:26 -07:00
Nathan Rajlich
32e935d632 [cli] Use GET /v2/user endpoint (#6828)
The `/www/user` endpoint is deprecated.
2021-10-13 17:20:54 +00:00
Miroslav Simulcik
e6818dd3f9 Publish Canary
- @vercel/build-utils@2.12.3-canary.10
 - vercel@23.1.3-canary.12
 - @vercel/client@10.2.3-canary.10
2021-10-13 16:52:12 +02:00
Miroslav Simulcik
e510415a66 Add regions to lambda options (#6829) 2021-10-13 16:47:18 +02:00
Nathan Rajlich
1be75712e0 [cli] Add outputBuffer getter and mutable isTTY for tests (#6827)
These changes originally from #6652, but pulled out to be merged
separately.

`outputBuffer` is a simpler way of asserting tests against the CLI
output instead of working directly withe Jest mock function.

`output.isTTY` is also now mutable, so that we can write tests for both
cases when the output is different based on TTY-ness (for example,
see the updated `vc whoami` tests in this PR).
2021-10-12 16:16:55 -07:00
Andy Bitz
f682aefc9d Publish Canary
- @vercel/build-utils@2.12.3-canary.9
 - vercel@23.1.3-canary.11
 - @vercel/client@10.2.3-canary.9
 - @vercel/frameworks@0.5.1-canary.6
2021-10-12 12:56:49 +02:00
Andy
cd485c1866 [frameworks] Add Nuxt 3 to framework detection (#6823) 2021-10-12 12:43:40 +02:00
Steven
f7de7227b4 Publish Canary
- @vercel/build-utils@2.12.3-canary.8
 - vercel@23.1.3-canary.10
 - @vercel/client@10.2.3-canary.8
2021-09-23 14:28:52 -04:00
Steven
abea217177 [build-utils] Add allowQuery to Prerender (#6766) 2021-09-23 14:25:44 -04:00
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
532 changed files with 41893 additions and 14293 deletions

View File

@@ -1,11 +1,10 @@
node_modules
dist
examples
packages/build-utils/test/fixtures
packages/*/test/fixtures
packages/cli/@types
packages/cli/download
packages/cli/dist
packages/cli/test/fixtures
packages/cli/test/dev/fixtures
packages/cli/bin
packages/cli/link
@@ -13,6 +12,6 @@ packages/cli/src/util/dev/templates/*.ts
packages/client/tests/fixtures
packages/client/lib
packages/node/src/bridge.ts
packages/node/test/fixtures
packages/node-bridge/bridge.js
packages/node-bridge/launcher.js
packages/middleware/src/entries.js

View File

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

View File

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

View File

@@ -11,7 +11,11 @@
## 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).

View File

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

View File

@@ -6,3 +6,5 @@ coverage:
project: 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,
"dependencies": {
"@angular/animations": "~8.1.0",
"@angular/common": "~8.1.0",
"@angular/compiler": "~8.1.0",
"@angular/core": "~8.1.0",
"@angular/forms": "~8.1.0",
"@angular/platform-browser": "~8.1.0",
"@angular/platform-browser-dynamic": "~8.1.0",
"@angular/router": "~8.1.0",
"@angular/animations": "^8.1.0",
"@angular/common": "^8.1.0",
"@angular/core": "^8.1.0",
"@angular/forms": "^8.1.0",
"@angular/platform-browser": "^8.1.0",
"@angular/platform-browser-dynamic": "^8.1.0",
"@angular/router": "^8.1.0",
"rxjs": "~6.4.0",
"tslib": "^1.9.0",
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.801.0",
"@angular/cli": "~8.1.0",
"@angular/compiler-cli": "~8.1.0",
"@angular-devkit/build-angular": "^12.2.2",
"@angular/cli": "^12.2.2",
"@angular/compiler": "^12.2.2",
"@angular/compiler-cli": "^12.2.2",
"@angular/language-service": "~8.1.0",
"@types/node": "~8.9.4",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4",
"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",
"karma": "~4.1.0",
"karma": "^6.3.4",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.0",
"protractor": "^7.0.0",
"ts-node": "~7.0.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": {
"react": "^16.6.1",
"react-dom": "^16.6.1",
"react-scripts": "2.1.1"
"react-scripts": "^4.0.3"
},
"scripts": {
"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
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.

View File

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

View File

@@ -1,15 +1,20 @@
{
"name": "nextjs",
"version": "0.1.0",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "10.x",
"react": "17.x",
"react-dom": "17.x"
"next": "12.0.1",
"react": "17.0.2",
"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
export default (req, res) => {
res.statusCode = 200
res.json({ name: 'John Doe' })
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}

View File

@@ -1,4 +1,5 @@
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
export default function Home() {
@@ -6,6 +7,7 @@ export default function Home() {
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
@@ -21,12 +23,12 @@ export default function Home() {
<div className={styles.grid}>
<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>
</a>
<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>
</a>
@@ -34,15 +36,15 @@ export default function Home() {
href="https://github.com/vercel/next.js/tree/master/examples"
className={styles.card}
>
<h3>Examples &rarr;</h3>
<h2>Examples &rarr;</h2>
<p>Discover and deploy boilerplate example Next.js projects.</p>
</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}
>
<h3>Deploy &rarr;</h3>
<h2>Deploy &rarr;</h2>
<p>
Instantly deploy your Next.js site to a public URL with Vercel.
</p>
@@ -57,7 +59,9 @@ export default function Home() {
rel="noopener noreferrer"
>
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>
</footer>
</div>

View File

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

File diff suppressed because it is too large Load Diff

7
examples/parcel/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
node_modules
.parcel-cache
dist
/.pnp
.pnp.js
.DS_Store
.vercel

34
examples/parcel/README.md Normal file
View File

@@ -0,0 +1,34 @@
This is a vanilla web app built with [Parcel](https://parceljs.org).
## Getting Started
Run the dev server:
```bash
yarn start
# or
npm start
```
And open [localhost:1234](http://localhost:1234) in your browser!
As you make changes, you should see your app automatically update in the browser without even refreshing the page!
## Learn More
- [Getting started guide](https://parceljs.org/getting-started/webapp/)
- [Documentation](https://parceljs.org/docs/)
## Deploy Your Own
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/vercel/tree/main/examples/parcel&template=parcel)
_Live Example: https://parcel.examples.vercel.com_
### Deploying From Your Terminal
You can deploy your new Parcel project with a single command from your terminal using [Vercel CLI](https://vercel.com/download):
```shell
$ vercel
```

View File

@@ -0,0 +1,12 @@
{
"name": "parcel-app",
"private": true,
"source": "src/index.html",
"scripts": {
"start": "parcel",
"build": "parcel build"
},
"devDependencies": {
"parcel": "^2.0.0"
}
}

View File

@@ -0,0 +1 @@
console.log('Hello world!');

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>My First Parcel App</title>
<link rel="stylesheet" href="styles.css" />
<script type="module" src="app.js"></script>
</head>
<body>
<h1>Hello, Parcel!</h1>
<p>This is a vanilla web app built with Parcel 2!<br>Check out the <a href="https://parceljs.org/getting-started/webapp/" target="_blank">getting started guide</a> and the <a href="https://parceljs.org/docs/" target="_blank">documentation</a> to learn more!</p>
</body>
</html>

View File

@@ -0,0 +1,18 @@
body {
text-align: center;
}
h1 {
color: hotpink;
font-family: cursive;
font-size: 55px;
}
p {
font-family: Helvetica, Arial, sans-serif;
line-height: 1.5em;
}
a {
color: dodgerblue;
}

4541
examples/parcel/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,6 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "4.28.0",
"@typescript-eslint/parser": "4.28.0",
"@vercel/ncc": "0.24.0",
"async-retry": "1.2.3",
"buffer-replace": "1.0.0",
"cheerio": "1.0.0-rc.3",
@@ -25,17 +24,19 @@
"eslint-config-prettier": "8.3.0",
"eslint-plugin-jest": "24.3.6",
"husky": "6.0.0",
"jest": "27.3.1",
"json5": "2.1.1",
"lint-staged": "9.2.5",
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"prettier": "2.3.1"
"prettier": "2.3.1",
"ts-jest": "27.0.4"
},
"scripts": {
"lerna": "lerna",
"bootstrap": "lerna bootstrap",
"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",
"changelog": "node utils/changelog.js",
"build": "node utils/run.js build all",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "2.12.0",
"version": "2.12.3-canary.17",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -21,16 +21,16 @@
"@types/async-retry": "^1.2.1",
"@types/cross-spawn": "6.0.0",
"@types/end-of-stream": "^1.4.0",
"@types/fs-extra": "^5.0.5",
"@types/fs-extra": "9.0.13",
"@types/glob": "^7.1.1",
"@types/jest": "26.0.24",
"@types/jest": "27.0.1",
"@types/js-yaml": "3.12.1",
"@types/ms": "0.7.31",
"@types/multistream": "2.1.1",
"@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0",
"@types/yazl": "^2.4.1",
"@vercel/frameworks": "0.5.0",
"@vercel/frameworks": "0.5.1-canary.11",
"@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1",
"async-retry": "1.2.3",
@@ -38,16 +38,14 @@
"boxen": "4.2.0",
"cross-spawn": "6.0.5",
"end-of-stream": "1.4.1",
"fs-extra": "7.0.0",
"fs-extra": "10.0.0",
"glob": "7.1.3",
"into-stream": "5.0.0",
"jest": "27.0.6",
"js-yaml": "3.13.1",
"minimatch": "3.0.4",
"multistream": "2.1.1",
"node-fetch": "2.6.1",
"semver": "6.1.1",
"ts-jest": "27.0.4",
"typescript": "4.3.4",
"yazl": "2.4.3"
}

View File

@@ -0,0 +1,132 @@
import fs from 'fs-extra';
import { join, dirname, relative } from 'path';
import glob from './fs/glob';
import { normalizePath } from './fs/normalize-path';
import { FILES_SYMBOL, Lambda } from './lambda';
import type FileBlob from './file-blob';
import type { BuilderFunctions, BuildOptions, Files } from './types';
import minimatch from 'minimatch';
/**
* Convert legacy Runtime to a Plugin.
* @param buildRuntime - a legacy build() function from a Runtime
* @param ext - the file extension, for example `.py`
*/
export function convertRuntimeToPlugin(
buildRuntime: (options: BuildOptions) => Promise<{ output: Lambda }>,
ext: string
) {
return async function build({ workPath }: { workPath: string }) {
const opts = { cwd: workPath };
const files = await glob('**', opts);
delete files['vercel.json']; // Builders/Runtimes didn't have vercel.json
const entrypoints = await glob(`api/**/*${ext}`, opts);
const functionsManifest: { [key: string]: any } = {};
const functions = await readVercelConfigFunctions(workPath);
const traceDir = join(workPath, '.output', 'runtime-traced-files');
await fs.ensureDir(traceDir);
for (const entrypoint of Object.keys(entrypoints)) {
const key =
Object.keys(functions).find(
src => src === entrypoint || minimatch(entrypoint, src)
) || '';
const config = functions[key] || {};
const { output } = await buildRuntime({
files,
entrypoint,
workPath,
config: {
zeroConfig: true,
includeFiles: config.includeFiles,
excludeFiles: config.excludeFiles,
},
});
functionsManifest[entrypoint] = {
handler: output.handler,
runtime: output.runtime,
memory: config.memory || output.memory,
maxDuration: config.maxDuration || output.maxDuration,
environment: output.environment,
allowQuery: output.allowQuery,
regions: output.regions,
};
// @ts-ignore This symbol is a private API
const lambdaFiles: Files = output[FILES_SYMBOL];
const entry = join(workPath, '.output', 'server', 'pages', entrypoint);
await fs.ensureDir(dirname(entry));
await linkOrCopy(files[entrypoint].fsPath, entry);
const tracedFiles: {
absolutePath: string;
relativePath: string;
}[] = [];
Object.entries(lambdaFiles).forEach(async ([relPath, file]) => {
const newPath = join(traceDir, relPath);
tracedFiles.push({ absolutePath: newPath, relativePath: relPath });
if (file.fsPath) {
await linkOrCopy(file.fsPath, newPath);
} else if (file.type === 'FileBlob') {
const { data, mode } = file as FileBlob;
await fs.writeFile(newPath, data, { mode });
} else {
throw new Error(`Unknown file type: ${file.type}`);
}
});
const nft = join(
workPath,
'.output',
'server',
'pages',
`${entrypoint}.nft.json`
);
const json = JSON.stringify({
version: 1,
files: tracedFiles.map(f => ({
input: normalizePath(relative(nft, f.absolutePath)),
output: normalizePath(f.relativePath),
})),
});
await fs.ensureDir(dirname(nft));
await fs.writeFile(nft, json);
}
await fs.writeFile(
join(workPath, '.output', 'functions-manifest.json'),
JSON.stringify(functionsManifest)
);
};
}
async function linkOrCopy(existingPath: string, newPath: string) {
try {
await fs.createLink(existingPath, newPath);
} catch (err: any) {
if (err.code !== 'EEXIST') {
await fs.copyFile(existingPath, newPath);
}
}
}
async function readVercelConfigFunctions(
workPath: string
): Promise<BuilderFunctions> {
const vercelJsonPath = join(workPath, 'vercel.json');
try {
const str = await fs.readFile(vercelJsonPath, 'utf8');
const obj = JSON.parse(str);
return obj.functions || {};
} catch (err) {
if (err.code === 'ENOENT') {
return {};
}
throw err;
}
}

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ import assert from 'assert';
import vanillaGlob_ from 'glob';
import { promisify } from 'util';
import { lstat, Stats } from 'fs-extra';
import { normalizePath } from './normalize-path';
import FileFsRef from '../file-fs-ref';
export type GlobOptions = vanillaGlob_.IOptions;
@@ -45,7 +46,7 @@ export default async function glob(
const files = await vanillaGlob(pattern, options);
for (const relativePath of files) {
const fsPath = path.join(options.cwd!, relativePath).replace(/\\/g, '/');
const fsPath = normalizePath(path.join(options.cwd!, relativePath));
let stat: Stats = options.statCache![fsPath] as Stats;
assert(
stat,

View File

@@ -0,0 +1,8 @@
const isWin = process.platform === 'win32';
/**
* Convert Windows separators to Unix separators.
*/
export function normalizePath(p: string): string {
return isWin ? p.replace(/\\/g, '/') : p;
}

View File

@@ -244,10 +244,13 @@ export async function scanParentDirs(
const packageJsonPath = path.join(currentDestPath, 'package.json');
// eslint-disable-next-line no-await-in-loop
if (await fs.pathExists(packageJsonPath)) {
// eslint-disable-next-line no-await-in-loop
if (readPackageJson) {
// Only read the contents of the *first* `package.json` file found,
// 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'));
}
// eslint-disable-next-line no-await-in-loop
const [packageLockJson, hasYarnLock] = await Promise.all([
fs
@@ -256,9 +259,8 @@ export async function scanParentDirs(
// If the file doesn't exist, fail gracefully otherwise error
if (error.code === 'ENOENT') {
return null;
} else {
throw error;
}
throw error;
}),
fs.pathExists(path.join(currentDestPath, 'yarn.lock')),
]);
@@ -267,7 +269,13 @@ export async function scanParentDirs(
cliType = 'npm';
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);
@@ -305,7 +313,8 @@ export async function runNpmInstall(
destPath: string,
args: string[] = [],
spawnOpts?: SpawnOptions,
meta?: Meta
meta?: Meta,
nodeVersion?: NodeVersion
) {
if (meta?.isDev) {
debug('Skipping dependency installation because dev mode is enabled');
@@ -329,7 +338,12 @@ export async function runNpmInstall(
.filter(a => a !== '--prefer-offline')
.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`
env.PATH = `/node16/bin-npm7:${env.PATH}`;
console.log('Detected `package-lock.json` generated by npm 7...');

View File

@@ -81,6 +81,8 @@ export {
export { detectFramework } from './detect-framework';
export { DetectorFilesystem } from './detectors/filesystem';
export { readConfigFile } from './fs/read-config-file';
export { normalizePath } from './fs/normalize-path';
export { convertRuntimeToPlugin } from './convert-runtime-to-plugin';
export * from './schemas';
export * from './types';

View File

@@ -19,6 +19,8 @@ interface LambdaOptions {
memory?: number;
maxDuration?: number;
environment: Environment;
allowQuery?: string[];
regions?: string[];
}
interface CreateLambdaOptions {
@@ -28,6 +30,8 @@ interface CreateLambdaOptions {
memory?: number;
maxDuration?: number;
environment?: Environment;
allowQuery?: string[];
regions?: string[];
}
interface GetLambdaOptionsFromFunctionOptions {
@@ -35,6 +39,8 @@ interface GetLambdaOptionsFromFunctionOptions {
config?: Config;
}
export const FILES_SYMBOL = Symbol('files');
export class Lambda {
public type: 'Lambda';
public zipBuffer: Buffer;
@@ -43,6 +49,8 @@ export class Lambda {
public memory?: number;
public maxDuration?: number;
public environment: Environment;
public allowQuery?: string[];
public regions?: string[];
constructor({
zipBuffer,
@@ -51,6 +59,8 @@ export class Lambda {
maxDuration,
memory,
environment,
allowQuery,
regions,
}: LambdaOptions) {
this.type = 'Lambda';
this.zipBuffer = zipBuffer;
@@ -59,6 +69,8 @@ export class Lambda {
this.memory = memory;
this.maxDuration = maxDuration;
this.environment = environment;
this.allowQuery = allowQuery;
this.regions = regions;
}
}
@@ -72,6 +84,8 @@ export async function createLambda({
memory,
maxDuration,
environment = {},
allowQuery,
regions,
}: CreateLambdaOptions): Promise<Lambda> {
assert(typeof files === 'object', '"files" must be an object');
assert(typeof handler === 'string', '"handler" is not a string');
@@ -86,18 +100,38 @@ export async function createLambda({
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'
);
}
if (regions !== undefined) {
assert(Array.isArray(regions), '"regions" is not an Array');
assert(
regions.every(r => typeof r === 'string'),
'"regions" is not a string Array'
);
}
await sema.acquire();
try {
const zipBuffer = await createZip(files);
return new Lambda({
const lambda = new Lambda({
zipBuffer,
handler,
runtime,
memory,
maxDuration,
environment,
regions,
});
// @ts-ignore This symbol is a private API
lambda[FILES_SYMBOL] = files;
return lambda;
} finally {
sema.release();
}
@@ -131,9 +165,7 @@ export async function createZip(files: Files): Promise<Buffer> {
}
zipFile.end();
streamToBuffer(zipFile.outputStream)
.then(resolve)
.catch(reject);
streamToBuffer(zipFile.outputStream).then(resolve).catch(reject);
});
return zipBuffer;

View File

@@ -9,6 +9,7 @@ interface PrerenderOptions {
fallback: FileBlob | FileFsRef | FileRef | null;
group?: number;
bypassToken?: string | null /* optional to be non-breaking change */;
allowQuery?: string[];
}
export class Prerender {
@@ -18,6 +19,7 @@ export class Prerender {
public fallback: FileBlob | FileFsRef | FileRef | null;
public group?: number;
public bypassToken: string | null;
public allowQuery?: string[];
constructor({
expiration,
@@ -25,6 +27,7 @@ export class Prerender {
fallback,
group,
bypassToken,
allowQuery,
}: PrerenderOptions) {
this.type = 'Prerender';
this.expiration = expiration;
@@ -62,5 +65,19 @@ export class Prerender {
);
}
this.fallback = fallback;
if (allowQuery !== undefined) {
if (!Array.isArray(allowQuery)) {
throw new Error(
'The `allowQuery` argument for `Prerender` must be Array.'
);
}
if (!allowQuery.every(q => typeof q === 'string')) {
throw new Error(
'The `allowQuery` argument for `Prerender` must be Array of strings.'
);
}
this.allowQuery = allowQuery;
}
}
}

View File

@@ -10,6 +10,7 @@ export interface File {
mode: number;
contentType?: string;
toStream: () => NodeJS.ReadableStream;
toStreamAsync?: () => Promise<NodeJS.ReadableStream>;
/**
* The absolute path to the file in the filesystem
*/

View File

@@ -1,5 +1,5 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@vercel/static-build" }],
"probes": [{ "path": "/", "mustContain": "npm version: 7" }]
"probes": [{ "path": "/", "mustContain": "npm version: 8" }]
}

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',
'07-zero-config-jekyll',
'08-zero-config-middleman',
'21-npm-workspaces',
];
// eslint-disable-next-line no-restricted-syntax

View File

@@ -2013,15 +2013,11 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
framework: 'redwoodjs',
};
const {
builders,
defaultRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
});
const { builders, defaultRoutes, rewriteRoutes, errorRoutes } =
await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
});
expect(builders).toStrictEqual([
{
@@ -2038,7 +2034,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(errorRoutes).toStrictEqual([
{
status: 404,
src: '^/(?!.*api).*$',
src: '^(?!/api).*$',
dest: '/404.html',
},
]);
@@ -2050,15 +2046,11 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
framework: 'redwoodjs',
};
const {
builders,
defaultRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
});
const { builders, defaultRoutes, rewriteRoutes, errorRoutes } =
await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
});
expect(builders).toStrictEqual([
{
@@ -2096,7 +2088,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(errorRoutes).toStrictEqual([
{
status: 404,
src: '^/(?!.*api).*$',
src: '^(?!/api).*$',
dest: '/404.html',
},
]);
@@ -2417,7 +2409,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
expect(errorRoutes).toStrictEqual([
{
status: 404,
src: '^/(?!.*api).*$',
src: '^(?!/api).*$',
dest: '/404.html',
},
]);
@@ -2435,6 +2427,11 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
'/another/sub/index.html',
'/another/sub/page.html',
'/another/sub/page',
'/another/api',
'/another/api/page.html',
'/rapid',
'/rapid/page.html',
'/health-api.html',
].forEach(file => {
expect(file).toMatch(pattern);
});
@@ -2443,12 +2440,12 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
'/api',
'/api/',
'/api/index.html',
'/api/page.html',
'/api/page',
'/api/users.js',
'/api/users',
'/api/sub',
'/api/sub/index.html',
'/api/sub/page.html',
'/api/sub/page',
'/api/sub/users.js',
'/api/sub/users',
].forEach(file => {
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 {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, null, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes, errorRoutes } =
await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([
@@ -2836,7 +2829,7 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
expect(errorRoutes).toStrictEqual([
{
status: 404,
src: '^/(?!.*api).*$',
src: '^(?!/api).*$',
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 {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([
@@ -2936,11 +2926,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
'api/[endpoint]/[id].js',
];
const {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).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 {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, pkg, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, pkg, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).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 {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).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 {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([
@@ -3051,11 +3029,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
'api/food.ts',
'api/ts/gold.ts',
];
const {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).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 files = ['api/user.php'];
const {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, { functions, ...options });
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, null, { functions, ...options });
testHeaders(redirectRoutes);
expect(defaultRoutes).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 {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).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 {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([
@@ -3184,11 +3150,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
'api/[endpoint]/[id].js',
];
const {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).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 {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, pkg, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, pkg, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).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 {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).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 {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([
@@ -3292,11 +3246,8 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
'api/food.ts',
'api/ts/gold.ts',
];
const {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, options);
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, null, options);
testHeaders(redirectRoutes);
expect(defaultRoutes).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 files = ['api/user.php'];
const {
defaultRoutes,
redirectRoutes,
rewriteRoutes,
} = await detectBuilders(files, null, { functions, ...options });
const { defaultRoutes, redirectRoutes, rewriteRoutes } =
await detectBuilders(files, null, { functions, ...options });
testHeaders(redirectRoutes);
expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([

View File

@@ -0,0 +1,179 @@
import { join } from 'path';
import fs from 'fs-extra';
import { BuildOptions, createLambda } from '../src';
import { convertRuntimeToPlugin } from '../src/convert-runtime-to-plugin';
async function fsToJson(dir: string, output: Record<string, any> = {}) {
const files = await fs.readdir(dir);
for (const file of files) {
const fsPath = join(dir, file);
const stat = await fs.stat(fsPath);
if (stat.isDirectory()) {
output[file] = {};
await fsToJson(fsPath, output[file]);
} else {
output[file] = await fs.readFile(fsPath, 'utf8');
}
}
return output;
}
const workPath = join(__dirname, 'walk', 'python-api');
describe('convert-runtime-to-plugin', () => {
afterEach(async () => {
await fs.remove(join(workPath, '.output'));
});
it('should create correct fileystem for python', async () => {
const lambdaOptions = {
handler: 'index.handler',
runtime: 'python3.9',
memory: 512,
maxDuration: 5,
environment: {},
regions: ['sfo1'],
};
const buildRuntime = async (opts: BuildOptions) => {
const lambda = await createLambda({
files: opts.files,
...lambdaOptions,
});
return { output: lambda };
};
const lambdaFiles = await fsToJson(workPath);
delete lambdaFiles['vercel.json'];
const build = await convertRuntimeToPlugin(buildRuntime, '.py');
await build({ workPath });
const output = await fsToJson(join(workPath, '.output'));
expect(output).toMatchObject({
'functions-manifest.json': expect.stringContaining('{'),
'runtime-traced-files': lambdaFiles,
server: {
pages: {
api: {
'index.py': expect.stringContaining('index'),
'index.py.nft.json': expect.stringContaining('{'),
users: {
'get.py': expect.stringContaining('get'),
'get.py.nft.json': expect.stringContaining('{'),
'post.py': expect.stringContaining('post'),
'post.py.nft.json': expect.stringContaining('{'),
},
},
},
},
});
const funcManifest = JSON.parse(output['functions-manifest.json']);
expect(funcManifest).toMatchObject({
'api/index.py': lambdaOptions,
'api/users/get.py': lambdaOptions,
'api/users/post.py': { ...lambdaOptions, memory: 3008 },
});
const indexJson = JSON.parse(output.server.pages.api['index.py.nft.json']);
expect(indexJson).toMatchObject({
version: 1,
files: [
{
input: '../../../../runtime-traced-files/api/index.py',
output: 'api/index.py',
},
{
input: '../../../../runtime-traced-files/api/users/get.py',
output: 'api/users/get.py',
},
{
input: '../../../../runtime-traced-files/api/users/post.py',
output: 'api/users/post.py',
},
{
input: '../../../../runtime-traced-files/file.txt',
output: 'file.txt',
},
{
input: '../../../../runtime-traced-files/util/date.py',
output: 'util/date.py',
},
{
input: '../../../../runtime-traced-files/util/math.py',
output: 'util/math.py',
},
],
});
const getJson = JSON.parse(
output.server.pages.api.users['get.py.nft.json']
);
expect(getJson).toMatchObject({
version: 1,
files: [
{
input: '../../../../../runtime-traced-files/api/index.py',
output: 'api/index.py',
},
{
input: '../../../../../runtime-traced-files/api/users/get.py',
output: 'api/users/get.py',
},
{
input: '../../../../../runtime-traced-files/api/users/post.py',
output: 'api/users/post.py',
},
{
input: '../../../../../runtime-traced-files/file.txt',
output: 'file.txt',
},
{
input: '../../../../../runtime-traced-files/util/date.py',
output: 'util/date.py',
},
{
input: '../../../../../runtime-traced-files/util/math.py',
output: 'util/math.py',
},
],
});
const postJson = JSON.parse(
output.server.pages.api.users['post.py.nft.json']
);
expect(postJson).toMatchObject({
version: 1,
files: [
{
input: '../../../../../runtime-traced-files/api/index.py',
output: 'api/index.py',
},
{
input: '../../../../../runtime-traced-files/api/users/get.py',
output: 'api/users/get.py',
},
{
input: '../../../../../runtime-traced-files/api/users/post.py',
output: 'api/users/post.py',
},
{
input: '../../../../../runtime-traced-files/file.txt',
output: 'file.txt',
},
{
input: '../../../../../runtime-traced-files/util/date.py',
output: 'util/date.py',
},
{
input: '../../../../../runtime-traced-files/util/math.py',
output: 'util/math.py',
},
],
});
expect(output.server.pages['file.txt']).toBeUndefined();
expect(output.server.pages.api['file.txt']).toBeUndefined();
});
});

View File

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

@@ -0,0 +1 @@
# index

View File

@@ -0,0 +1 @@
# get

View File

@@ -0,0 +1 @@
# post

View File

@@ -0,0 +1 @@
This file should also be included

View File

@@ -0,0 +1 @@
# date

View File

@@ -0,0 +1 @@
# math

View File

@@ -0,0 +1,10 @@
{
"functions": {
"api/users/post.py": {
"memory": 3008
},
"api/not-matching-anything.py": {
"memory": 768
}
}
}

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
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
npm i -g vercel
@@ -26,6 +30,8 @@ cd <PROJECT> # Change directory to the new project
vercel # Deploy to the cloud
```
Finally, [connect your Git repository to Vercel](https://vercel.com/docs/git) and deploy with `git push`.
## Documentation
For details on how to use Vercel CLI, check out our [documentation](https://vercel.com/docs).

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "23.1.0",
"version": "23.1.3-canary.28",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -12,33 +12,15 @@
},
"scripts": {
"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-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
"prepublishOnly": "yarn build",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"coverage": "codecov",
"build": "node -r ts-eager/register ./scripts/build.ts",
"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": {
"vc": "./dist/index.js",
"vercel": "./dist/index.js"
@@ -61,14 +43,17 @@
"node": ">= 12"
},
"dependencies": {
"@vercel/build-utils": "2.12.0",
"@vercel/go": "1.2.3",
"@vercel/node": "1.12.0",
"@vercel/python": "2.0.5",
"@vercel/ruby": "1.2.7",
"update-notifier": "4.1.0"
"@vercel/build-utils": "2.12.3-canary.17",
"@vercel/go": "1.2.4-canary.3",
"@vercel/node": "1.12.2-canary.6",
"@vercel/python": "2.0.6-canary.5",
"@vercel/ruby": "1.2.8-canary.4",
"update-notifier": "4.1.0",
"vercel-plugin-middleware": "0.0.0-canary.6",
"vercel-plugin-node": "1.12.2-canary.7"
},
"devDependencies": {
"@next/env": "11.1.2",
"@sentry/node": "5.5.0",
"@sindresorhus/slugify": "0.11.0",
"@tootallnate/once": "1.1.2",
@@ -76,13 +61,16 @@
"@types/ansi-regex": "4.0.0",
"@types/async-retry": "1.2.1",
"@types/bytes": "3.0.0",
"@types/chance": "1.1.3",
"@types/debug": "0.0.31",
"@types/dotenv": "6.1.1",
"@types/escape-html": "0.0.20",
"@types/fs-extra": "5.0.5",
"@types/express": "4.17.13",
"@types/fs-extra": "9.0.13",
"@types/glob": "7.1.1",
"@types/http-proxy": "1.16.2",
"@types/inquirer": "7.3.1",
"@types/jest": "27.0.1",
"@types/load-json-file": "2.0.7",
"@types/mime-types": "2.1.0",
"@types/minimatch": "3.0.3",
@@ -97,11 +85,14 @@
"@types/semver": "6.0.1",
"@types/tar-fs": "1.16.1",
"@types/text-table": "0.2.0",
"@types/title": "3.4.1",
"@types/universal-analytics": "0.4.2",
"@types/update-notifier": "5.1.0",
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@vercel/frameworks": "0.5.0",
"@vercel/frameworks": "0.5.1-canary.11",
"@vercel/ncc": "0.24.0",
"@vercel/nft": "0.17.0",
"@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2",
"ajv": "6.12.2",
@@ -115,6 +106,7 @@
"ava": "2.2.0",
"bytes": "3.0.0",
"chalk": "4.1.0",
"chance": "1.1.7",
"chokidar": "3.3.1",
"clipboardy": "2.1.0",
"codecov": "3.8.2",
@@ -130,8 +122,9 @@
"escape-html": "1.0.3",
"esm": "3.1.4",
"execa": "3.2.0",
"express": "4.17.1",
"fast-deep-equal": "3.1.3",
"fs-extra": "7.0.1",
"fs-extra": "10.0.0",
"get-port": "5.1.1",
"glob": "7.1.2",
"http-proxy": "1.18.1",
@@ -148,7 +141,6 @@
"ms": "2.1.2",
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"nyc": "13.2.0",
"open": "8.2.0",
"ora": "3.4.0",
"pcre-to-regexp": "1.0.0",
@@ -161,7 +153,6 @@
"rimraf": "3.0.2",
"semver": "5.5.0",
"serve-handler": "6.1.1",
"sinon": "4.4.2",
"strip-ansi": "5.2.0",
"stripe": "5.1.0",
"tar-fs": "1.16.3",
@@ -178,5 +169,19 @@
"which": "2.0.2",
"write-json-file": "2.2.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';
const dirRoot = join(__dirname, '..');
const distRoot = join(dirRoot, 'dist');
async function createConstants() {
console.log('Creating constants.ts');
@@ -48,13 +49,12 @@ async function main() {
// Do the initial `ncc` build
console.log();
const src = join(dirRoot, 'src');
const args = ['ncc', 'build', '--external', 'update-notifier'];
if (isDev) {
args.push('--source-map');
}
args.push(src);
await execa('yarn', args, { stdio: 'inherit' });
args.push('src/index.ts');
await execa('yarn', args, { stdio: 'inherit', cwd: dirRoot });
// `ncc` has some issues with `@zeit/fun`'s runtime files:
// - Executable bits on the `bootstrap` files appear to be lost:
@@ -72,19 +72,13 @@ async function main() {
dirRoot,
'../../node_modules/@zeit/fun/dist/src/runtimes'
);
const dest = join(dirRoot, 'dist/runtimes');
await cpy('**/*', dest, { parents: true, cwd: runtimes });
await cpy('**/*', join(distRoot, 'runtimes'), {
parents: true,
cwd: runtimes,
});
// Band-aid to delete stuff that `ncc` bundles, but it shouldn't:
// 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'));
// Band-aid to bundle stuff that `ncc` neglects to bundle
await cpy(join(dirRoot, 'src/util/projects/VERCEL_DIR_README.txt'), distRoot);
console.log('Finished building Vercel CLI');
}

View File

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

View File

@@ -3,7 +3,7 @@ import ms from 'ms';
import plural from 'pluralize';
import { error } from '../../util/error';
import NowCreditCards from '../../util/credit-cards';
import indent from '../../util/indent';
import indent from '../../util/output/indent';
import listInput from '../../util/input/list';
import success from '../../util/output/success';
import promptBool from '../../util/input/prompt-bool';
@@ -51,8 +51,6 @@ const help = () => {
};
let argv;
let debug;
let apiUrl;
let subcommand;
export default async client => {
@@ -65,8 +63,6 @@ export default async client => {
argv._ = argv._.slice(1);
debug = argv['--debug'];
apiUrl = client.apiUrl;
subcommand = argv._[0];
if (argv['--help'] || !subcommand) {
@@ -76,17 +72,13 @@ export default async client => {
const {
output,
authConfig: { token },
config: { currentTeam },
} = client;
const start = new Date();
const creditCards = new NowCreditCards({
apiUrl,
token,
debug,
client,
currentTeam,
output,
});
let contextName = null;

View File

@@ -0,0 +1,754 @@
import { loadEnvConfig, processEnv } from '@next/env';
import {
execCommand,
getScriptName,
GlobOptions,
scanParentDirs,
spawnAsync,
} from '@vercel/build-utils';
import { nodeFileTrace } from '@vercel/nft';
import Sema from 'async-sema';
import chalk from 'chalk';
import { SpawnOptions } from 'child_process';
import { assert } from 'console';
import { createHash } from 'crypto';
import fs from 'fs-extra';
import ogGlob from 'glob';
import { isAbsolute, join, parse, relative, resolve } from 'path';
import pluralize from 'pluralize';
import Client from '../util/client';
import { emoji, prependEmoji } from '../util/emoji';
import getArgs from '../util/get-args';
import handleError from '../util/handle-error';
import confirm from '../util/input/confirm';
import { isSettingValue } from '../util/is-setting-value';
import cmd from '../util/output/cmd';
import logo from '../util/output/logo';
import param from '../util/output/param';
import stamp from '../util/output/stamp';
import { getCommandName, getPkgName } from '../util/pkg-name';
import { loadCliPlugins } from '../util/plugins';
import { findFramework } from '../util/projects/find-framework';
import { VERCEL_DIR } from '../util/projects/link';
import {
ProjectLinkAndSettings,
readProjectSettings,
} from '../util/projects/project-settings';
import pull from './pull';
const sema = new Sema(16, {
capacity: 100,
});
const help = () => {
return console.log(`
${chalk.bold(`${logo} ${getPkgName()} build`)}
${chalk.dim('Options:')}
-h, --help Output usage information
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'
)} Path to the local ${'`vercel.json`'} file
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
'DIR'
)} Path to the global ${'`.vercel`'} directory
--cwd [path] The current working directory
-d, --debug Debug mode [off]
-y, --yes Skip the confirmation prompt
${chalk.dim('Examples:')}
${chalk.gray('')} Build the project
${chalk.cyan(`$ ${getPkgName()} build`)}
${chalk.cyan(`$ ${getPkgName()} build --cwd ./path-to-project`)}
`);
};
const OUTPUT_DIR = '.output';
const fields: {
name: string;
value: keyof ProjectLinkAndSettings['settings'];
}[] = [
{ name: 'Build Command', value: 'buildCommand' },
{ name: 'Output Directory', value: 'outputDirectory' },
{ name: 'Root Directory', value: 'rootDirectory' },
];
export default async function main(client: Client) {
let argv;
const buildStamp = stamp();
try {
argv = getArgs(client.argv.slice(2), {
'--debug': Boolean,
'--cwd': String,
});
} catch (err) {
handleError(err);
return 1;
}
if (argv['--help']) {
help();
return 2;
}
let cwd = argv['--cwd'] || process.cwd();
let project = await readProjectSettings(join(cwd, VERCEL_DIR));
// If there are no project settings, only then do we pull them down
while (!project?.settings) {
const confirmed = await confirm(
`No Project Settings found locally. Run ${getCommandName(
'pull'
)} for retrieving them?`,
true
);
if (!confirmed) {
client.output.print(`Aborted. No Project Settings retrieved.\n`);
return 0;
}
const result = await pull(client);
if (result !== 0) {
return result;
}
project = await readProjectSettings(join(cwd, VERCEL_DIR));
}
// If `rootDirectory` exists, then `baseDir` will be the repo's root directory.
const baseDir = cwd;
cwd = project.settings.rootDirectory
? join(cwd, project.settings.rootDirectory)
: cwd;
// Load the environment
const { combinedEnv, loadedEnvFiles } = loadEnvConfig(cwd, false, {
info: () => ({}), // we don't want to log this yet.
error: (...args: any[]) => client.output.error(args.join(' ')),
});
// Set process.env with loaded environment variables
await processEnv(loadedEnvFiles);
const spawnOpts = {
env: { ...combinedEnv, VERCEL: '1' },
};
process.chdir(cwd);
const framework = findFramework(project.settings.framework);
// If this is undefined, we bail. If it is null, then findFramework should return "Other",
// so this should really never happen, but just in case....
if (framework === undefined) {
client.output.error(
`Framework detection failed or is malformed. Please run ${getCommandName(
'pull'
)} again.`
);
return 1;
}
const buildState = { ...project.settings };
client.output.log(`Retrieved Project Settings:`);
client.output.print(
chalk.dim(` - ${chalk.bold(`Framework Preset:`)} ${framework.name}\n`)
);
for (let field of fields) {
const defaults = (framework.settings as any)[field.value];
if (defaults) {
client.output.print(
chalk.dim(
` - ${chalk.bold(`${field.name}:`)} ${`${
project.settings[field.value]
? project.settings[field.value] + ` (override)`
: isSettingValue(defaults)
? defaults.value
: chalk.italic(`${defaults.placeholder}`)
}`}\n`
)
);
}
if (field.value != 'buildCommand') {
(buildState as any)[field.value] = project.settings[field.value]
? project.settings[field.value]
: defaults
? isSettingValue(defaults)
? defaults.value
: null
: null;
}
}
if (loadedEnvFiles.length > 0) {
client.output.log(
`Loaded Environment Variables from ${loadedEnvFiles.length} ${pluralize(
'file',
loadedEnvFiles.length
)}:`
);
for (let envFile of loadedEnvFiles) {
client.output.print(chalk.dim(` - ${envFile.path}\n`));
}
}
// Load plugins
const debug = argv['--debug'];
let plugins;
try {
plugins = await loadCliPlugins(cwd, client.output);
} catch (error) {
client.output.error('Failed to load CLI Plugins');
handleError(error, { debug });
return 1;
}
const origLog = console.log;
const origErr = console.error;
const prefixedLog = (
prefix: string,
args: any[],
logger: (...args: any[]) => void
) => {
if (typeof args[0] === 'string') {
args[0] = `${prefix} ${args[0]}`;
} else {
args.unshift(prefix);
}
return logger(...args);
};
if (plugins?.pluginCount && plugins?.pluginCount > 0) {
client.output.log(
`Loaded ${plugins.pluginCount} CLI ${pluralize(
'Plugin',
plugins.pluginCount
)}`
);
// preBuild Plugins
if (plugins.preBuildPlugins.length > 0) {
client.output.log(
`Running ${plugins.pluginCount} CLI ${pluralize(
'Plugin',
plugins.pluginCount
)} before Build Command:`
);
for (let item of plugins.preBuildPlugins) {
const { name, plugin, color } = item;
if (typeof plugin.preBuild === 'function') {
const pluginStamp = stamp();
const fullName = name + '.preBuild';
const prefix = chalk.gray(' > ') + color(fullName + ':');
client.output.debug(`Running ${fullName}:`);
try {
console.log = (...args: any[]) =>
prefixedLog(prefix, args, origLog);
console.error = (...args: any[]) =>
prefixedLog(prefix, args, origErr);
await plugin.preBuild();
client.output.debug(
`Completed ${fullName} ${chalk.dim(`${pluginStamp()}`)}`
);
} catch (error) {
client.output.error(`${prefix} failed`);
handleError(error, { debug });
return 1;
} finally {
console.log = origLog;
console.error = origErr;
}
}
}
}
}
// Clean the output directory
fs.removeSync(join(cwd, OUTPUT_DIR));
if (typeof buildState.buildCommand === 'string') {
client.output.log(`Running Build Command: ${cmd(buildState.buildCommand)}`);
await execCommand(buildState.buildCommand, {
...spawnOpts,
// Yarn v2 PnP mode may be activated, so force
// "node-modules" linker style
env: {
YARN_NODE_LINKER: 'node-modules',
...spawnOpts.env,
},
cwd: cwd,
});
} else if (fs.existsSync(join(cwd, 'package.json'))) {
await runPackageJsonScript(
client,
cwd,
['vercel-build', 'now-build', 'build'],
spawnOpts
);
}
if (!fs.existsSync(join(cwd, OUTPUT_DIR))) {
let outputDir = join(OUTPUT_DIR, 'static');
let distDir = await framework.getFsOutputDir(cwd);
if (framework.slug === 'nextjs') {
outputDir = OUTPUT_DIR;
}
const copyStamp = stamp();
await fs.ensureDir(join(cwd, outputDir));
const relativeDistDir = relative(cwd, distDir);
client.output.spinner(
`Copying files from ${param(distDir)} to ${param(outputDir)}`
);
const files = await glob(join(relativeDistDir, '**'), {
ignore: [
'node_modules/**',
'.vercel/**',
'.env',
'.env.*',
'.*ignore',
'_middleware.ts',
'_middleware.mts',
'_middleware.cts',
'_middleware.mjs',
'_middleware.cjs',
'_middleware.js',
'api/**',
'.git/**',
'.next/cache/**',
],
nodir: true,
dot: true,
cwd,
absolute: true,
});
await Promise.all(
files.map(f =>
smartCopy(
client,
f,
distDir === '.'
? join(cwd, outputDir, relative(cwd, f))
: f.replace(distDir, outputDir)
)
)
);
client.output.stopSpinner();
client.output.log(
`Copied ${files.length.toLocaleString()} files from ${param(
distDir
)} to ${param(outputDir)} ${copyStamp()}`
);
const buildManifestPath = join(cwd, OUTPUT_DIR, 'build-manifest.json');
const routesManifestPath = join(cwd, OUTPUT_DIR, 'routes-manifest.json');
if (!fs.existsSync(buildManifestPath)) {
client.output.debug(
`Generating build manifest: ${param(buildManifestPath)}`
);
const buildManifest = {
cache: framework.cachePattern ? [framework.cachePattern] : [],
};
await fs.writeJSON(buildManifestPath, buildManifest, { spaces: 2 });
}
if (!fs.existsSync(routesManifestPath)) {
client.output.debug(
`Generating routes manifest: ${param(routesManifestPath)}`
);
const routesManifest = {
version: 3,
pages404: true,
basePath: '',
redirects: framework.defaultRedirects ?? [],
headers: framework.defaultHeaders ?? [],
dynamicRoutes: [],
dataRoutes: [],
rewrites: framework.defaultRewrites ?? [],
};
await fs.writeJSON(
join(cwd, OUTPUT_DIR, 'routes-manifest.json'),
routesManifest,
{ spaces: 2 }
);
}
// Special Next.js processing.
if (framework.slug === 'nextjs') {
// The contents of `.output/static` should be placed inside of `.output/static/_next/static`
const tempStatic = '___static';
await fs.rename(
join(cwd, OUTPUT_DIR, 'static'),
join(cwd, OUTPUT_DIR, tempStatic)
);
await fs.mkdirp(join(cwd, OUTPUT_DIR, 'static', '_next', 'static'));
await fs.rename(
join(cwd, OUTPUT_DIR, tempStatic),
join(cwd, OUTPUT_DIR, 'static', '_next', 'static')
);
// Next.js might reference files from the `static` directory in `middleware-manifest.json`.
// Since we move all files from `static` to `static/_next/static`, we'll need to change
// those references as well and update the manifest file.
const middlewareManifest = join(
cwd,
OUTPUT_DIR,
'server',
'middleware-manifest.json'
);
if (fs.existsSync(middlewareManifest)) {
const manifest = await fs.readJSON(middlewareManifest);
Object.keys(manifest.middleware).forEach(key => {
const files = manifest.middleware[key].files.map((f: string) => {
if (f.startsWith('static/')) {
const next = f.replace(/^static\//gm, 'static/_next/static/');
client.output.debug(
`Replacing file in \`middleware-manifest.json\`: ${f} => ${next}`
);
return next;
}
return f;
});
manifest.middleware[key].files = files;
});
await fs.writeJSON(middlewareManifest, manifest);
}
// We want to pick up directories for user-provided static files into `.`output/static`.
// More specifically, the static directory contents would then be mounted to `output/static/static`,
// and the public directory contents would be mounted to `output/static`. Old Next.js versions
// allow `static`, and newer ones allow both, but since there's nobody that actually uses both,
// we can check for the existence of both and pick the first match that we find (first
// `public`, then`static`). We can't read both at the same time because that would mean we'd
// read public for old Next.js versions that don't support it, which might be breaking (and
// we don't want to make vercel build specific framework versions).
const publicFiles = await glob('public/**', {
nodir: true,
dot: true,
cwd,
absolute: true,
});
if (publicFiles.length > 0) {
await Promise.all(
publicFiles.map(f =>
smartCopy(
client,
f,
f.replace('public', join(OUTPUT_DIR, 'static'))
)
)
);
} else {
const staticFiles = await glob('static/**', {
nodir: true,
dot: true,
cwd,
absolute: true,
});
await Promise.all(
staticFiles.map(f =>
smartCopy(
client,
f,
f.replace('static', join(OUTPUT_DIR, 'static', 'static'))
)
)
);
}
// Regardless of the Next.js version, we make sure that it is compatible with
// the Filesystem API. We get there by moving all the files needed
// into the outputs directory `inputs` folder. Next.js is > 12, we can
// read the .nft.json files directly. If there aren't .nft.json files
// we trace and create them. We then resolve the files in each nft file list
// and move them into the "inputs" directory. We rename them with hashes to
// prevent collisions and then update the related .nft files accordingly
// to point to the newly named input files. Again, all of this is so that Next.js
// works with the Filesystem API (and so .output contains all inputs
// needed to run Next.js) and `vc --prebuilt`.
const nftFiles = await glob(join(OUTPUT_DIR, '**', '*.nft.json'), {
nodir: true,
dot: true,
cwd,
absolute: true,
});
// If there are no .nft.json files, we know that Next.js < 12. We then
// execute the tracing on our own.
if (nftFiles.length === 0) {
const serverFiles = await glob(
join(OUTPUT_DIR, 'server', 'pages', '**', '*.js'),
{
nodir: true,
dot: true,
cwd,
ignore: ['webpack-runtime.js'],
absolute: true,
}
);
for (let f of serverFiles) {
const { ext, dir } = parse(f);
const { fileList } = await nodeFileTrace([f], {
ignore: [
relative(cwd, f),
'node_modules/next/dist/pages/**/*',
'node_modules/next/dist/compiled/webpack/(bundle4|bundle5).js',
'node_modules/react/**/*.development.js',
'node_modules/react-dom/**/*.development.js',
'node_modules/use-subscription/**/*.development.js',
'node_modules/sharp/**/*',
],
});
fileList.delete(relative(cwd, f));
await resolveNftToOutput({
client,
baseDir,
outputDir: OUTPUT_DIR,
nftFileName: f.replace(ext, '.js.nft.json'),
nft: {
version: 1,
files: Array.from(fileList).map(fileListEntry =>
relative(dir, fileListEntry)
),
},
});
}
} else {
for (let f of nftFiles) {
const json = await fs.readJson(f);
await resolveNftToOutput({
client,
baseDir,
outputDir: OUTPUT_DIR,
nftFileName: f,
nft: json,
});
}
}
const requiredServerFilesPath = join(
OUTPUT_DIR,
'required-server-files.json'
);
const requiredServerFilesJson = await fs.readJSON(
requiredServerFilesPath
);
await fs.writeJSON(requiredServerFilesPath, {
...requiredServerFilesJson,
appDir: '.',
files: requiredServerFilesJson.files.map((i: string) => {
const absolutePath = join(cwd, i.replace('.next', '.output'));
const output = relative(baseDir, absolutePath);
return {
input: i.replace('.next', '.output'),
output,
};
}),
});
}
}
// Build Plugins
if (plugins?.buildPlugins && plugins.buildPlugins.length > 0) {
client.output.log(
`Running ${plugins.pluginCount} CLI ${pluralize(
'Plugin',
plugins.pluginCount
)} after Build Command:`
);
for (let item of plugins.buildPlugins) {
const { name, plugin, color } = item;
if (typeof plugin.build === 'function') {
const pluginStamp = stamp();
const fullName = name + '.build';
const prefix = chalk.gray(' > ') + color(fullName + ':');
client.output.debug(`Running ${fullName}:`);
try {
console.log = (...args: any[]) => prefixedLog(prefix, args, origLog);
console.error = (...args: any[]) =>
prefixedLog(prefix, args, origErr);
await plugin.build({
workPath: cwd,
});
client.output.debug(
`Completed ${fullName} ${chalk.dim(`${pluginStamp()}`)}`
);
} catch (error) {
client.output.error(`${prefix} failed`);
handleError(error, { debug });
return 1;
} finally {
console.log = origLog;
console.error = origLog;
}
}
}
}
client.output.print(
`${prependEmoji(
`Build Completed in ${chalk.bold(OUTPUT_DIR)} ${chalk.gray(
buildStamp()
)}`,
emoji('success')
)}\n`
);
return 0;
}
export async function runPackageJsonScript(
client: Client,
destPath: string,
scriptNames: string | Iterable<string>,
spawnOpts?: SpawnOptions
) {
assert(isAbsolute(destPath));
const { packageJson, cliType, lockfileVersion } = await scanParentDirs(
destPath,
true
);
const scriptName = getScriptName(
packageJson,
typeof scriptNames === 'string' ? [scriptNames] : scriptNames
);
if (!scriptName) return false;
client.output.debug('Running user script...');
const runScriptTime = Date.now();
const opts: any = { cwd: destPath, ...spawnOpts };
const env = (opts.env = { ...process.env, ...opts.env });
if (cliType === 'npm') {
opts.prettyCommand = `npm run ${scriptName}`;
if (typeof lockfileVersion === 'number' && lockfileVersion >= 2) {
// Ensure that npm 7 is at the beginning of the `$PATH`
env.PATH = `/node16/bin-npm7:${env.PATH}`;
}
} else {
opts.prettyCommand = `yarn run ${scriptName}`;
// Yarn v2 PnP mode may be activated, so force "node-modules" linker style
if (!env.YARN_NODE_LINKER) {
env.YARN_NODE_LINKER = 'node-modules';
}
}
client.output.log(`Running Build Command: ${cmd(opts.prettyCommand)}\n`);
await spawnAsync(cliType, ['run', scriptName], opts);
client.output.print('\n'); // give it some room
client.output.debug(`Script complete [${Date.now() - runScriptTime}ms]`);
return true;
}
async function linkOrCopy(existingPath: string, newPath: string) {
try {
await fs.createLink(existingPath, newPath);
} catch (err: any) {
// eslint-disable-line
// If a hard link to the same file already exists
// then trying to copy it will make an empty file from it.
if (err['code'] === 'EEXIST') return;
// In some VERY rare cases (1 in a thousand), hard-link creation fails on Windows.
// In that case, we just fall back to copying.
// This issue is reproducible with "pnpm add @material-ui/icons@4.9.1"
await fs.copyFile(existingPath, newPath);
}
}
async function smartCopy(client: Client, from: string, to: string) {
sema.acquire();
try {
client.output.debug(`Copying from ${from} to ${to}`);
await linkOrCopy(from, to);
} finally {
sema.release();
}
}
async function glob(pattern: string, options: GlobOptions): Promise<string[]> {
return new Promise((resolve, reject) => {
ogGlob(pattern, options, (err, files) => {
err ? reject(err) : resolve(files);
});
});
}
/**
* Computes a hash for the given buf.
*
* @param {Buffer} file data
* @return {String} hex digest
*/
function hash(buf: Buffer): string {
return createHash('sha1').update(buf).digest('hex');
}
interface NftFile {
version: number;
files: (string | { input: string; output: string })[];
}
// resolveNftToOutput takes nft file and moves all of its trace files
// into the specified directory + `inputs`, (renaming them to their hash + ext) and
// subsequently updating the original nft file accordingly. This is done
// to make the `.output` directory be self-contained, so that it works
// properly with `vc --prebuilt`.
async function resolveNftToOutput({
client,
baseDir,
outputDir,
nftFileName,
nft,
}: {
client: Client;
baseDir: string;
outputDir: string;
nftFileName: string;
nft: NftFile;
}) {
client.output.debug(`Processing and resolving ${nftFileName}`);
await fs.ensureDir(join(outputDir, 'inputs'));
const newFilesList: NftFile['files'] = [];
for (let fileEntity of nft.files) {
const relativeInput: string =
typeof fileEntity === 'string' ? fileEntity : fileEntity.input;
const fullInput = resolve(join(parse(nftFileName).dir, relativeInput));
// if the resolved path is NOT in the .output directory we move in it there
if (!fullInput.includes(outputDir)) {
const { ext } = parse(fullInput);
const raw = await fs.readFile(fullInput);
const newFilePath = join(outputDir, 'inputs', hash(raw) + ext);
smartCopy(client, fullInput, newFilePath);
// We have to use `baseDir` instead of `cwd`, because we want to
// mount everything from there (especially `node_modules`).
// This is important for NPM Workspaces where `node_modules` is not
// in the directory of the workspace.
const output = relative(baseDir, fullInput).replace('.output', '.next');
newFilesList.push({
input: relative(parse(nftFileName).dir, newFilePath),
output,
});
} else {
newFilesList.push(relativeInput);
}
}
// Update the .nft.json with new input and output mapping
await fs.writeJSON(nftFileName, {
...nft,
files: newFilesList,
});
}

View File

@@ -18,9 +18,10 @@ export const help = () => `
init [example] Initialize an example project
ls | list [app] Lists deployments
inspect [id] Displays information related to a deployment
link Link local directory to a Vercel Project
link [path] Link local directory to a Vercel Project
login [email] Logs into your account or creates a new one
logout Logs out of your account
pull [path] Pull your Project Settings from the cloud
switch [scope] Switches between teams and your personal account
help [cmd] Displays complete help for [cmd]

View File

@@ -1,14 +1,66 @@
import ms from 'ms';
import fs from 'fs-extra';
import { resolve, basename } from 'path';
import { VercelConfig, fileNameSymbol } from '@vercel/client';
import bytes from 'bytes';
import chalk from 'chalk';
import { join, resolve, basename } from 'path';
import { Dictionary, fileNameSymbol, VercelConfig } from '@vercel/client';
import code from '../../util/output/code';
import highlight from '../../util/output/highlight';
import { readLocalConfig } from '../../util/config/files';
import getArgs from '../../util/get-args';
import { handleError } from '../../util/error';
import { help } from './args';
import deploy from './latest';
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';
import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
export default async (client: Client) => {
const { output } = client;
@@ -27,6 +79,7 @@ export default async (client: Client) => {
// This is not an array in favor of matching
// the config property name.
'--regions': String,
'--prebuilt': Boolean,
'--prod': Boolean,
'--confirm': Boolean,
'-f': '--force',
@@ -65,10 +118,7 @@ export default async (client: Client) => {
paths = [process.cwd()];
}
let localConfig: VercelConfig | null = client.localConfig;
if (!localConfig || localConfig instanceof Error) {
localConfig = readLocalConfig(paths[0]);
}
let localConfig = client.localConfig || readLocalConfig(paths[0]);
for (const path of paths) {
try {
@@ -105,5 +155,755 @@ export default async (client: Client) => {
}
}
return deploy(client, paths, localConfig, argv);
const { log, debug, error, warn, isTTY } = output;
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({
client,
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'],
prebuilt: argv['--prebuilt'],
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;
}
if (deployment.checksConclusion === 'failed') {
const { checks } = await getDeploymentChecks(client, deployment.id);
const counters = new Map<string, number>();
checks.forEach(c => {
counters.set(c.conclusion, (counters.get(c.conclusion) ?? 0) + 1);
});
const counterList = Array.from(counters)
.map(([name, no]) => `${no} ${name}`)
.join(', ');
output.error(`Running Checks: ${counterList}`);
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';
type Options = {
'--debug'?: boolean;
'--listen'?: string;
'--listen': string;
'--confirm': boolean;
};
@@ -27,7 +26,6 @@ export default async function dev(
const [dir = '.'] = args;
let cwd = resolve(dir);
const listen = parseListen(opts['--listen'] || '3000');
const debug = opts['--debug'] || false;
// retrieve dev command
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) {
const autoConfirm = opts['--confirm'] || false;
const forceDelete = false;
link = await setupAndLink(
client,
cwd,
forceDelete,
autoConfirm,
'link',
'Set up and develop'
);
link = await setupAndLink(client, cwd, {
autoConfirm: opts['--confirm'],
successEmoji: 'link',
setupMsg: 'Set up and develop',
});
if (link.status === 'not_linked') {
// User aborted project linking questions
@@ -100,7 +92,6 @@ export default async function dev(
const devServer = new DevServer(cwd, {
output,
debug,
devCommand,
frameworkSlug,
projectSettings,

View File

@@ -121,7 +121,7 @@ export default async function main(client: Client) {
} catch (err) {
if (err.code === 'ENOTFOUND') {
// Error message will look like the following:
// "request to https://api.vercel.com/www/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
// "request to https://api.vercel.com/v2/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
if (matches && matches[1]) {
const hostname = matches[1];

View File

@@ -5,7 +5,6 @@ import { User, Team } from '../../types';
import * as ERRORS from '../../util/errors-ts';
import Client from '../../util/client';
import getScope from '../../util/get-scope';
import withSpinner from '../../util/with-spinner';
import moveOutDomain from '../../util/domains/move-out-domain';
import isRootDomain from '../../util/is-root-domain';
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 getDomainByName from '../../util/domains/get-domain-by-name';
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';
type Options = {
@@ -106,9 +105,14 @@ export default async function move(
}
const context = contextName;
const moveTokenResult = await withSpinner('Moving', () => {
return moveOutDomain(client, context, domainName, matchId || destination);
});
output.spinner('Moving');
const moveTokenResult = await moveOutDomain(
client,
context,
domainName,
matchId || destination
);
if (moveTokenResult instanceof ERRORS.DomainMoveConflict) {
const { suffix, pendingAsyncPurchase } = moveTokenResult.meta;
if (suffix) {
@@ -190,8 +194,8 @@ async function findDestinationMatch(
user: User,
teams: Team[]
) {
if (user.uid === destination || user.username === destination) {
return user.uid;
if (user.id === destination || user.username === destination) {
return user.id;
}
for (const team of teams) {

View File

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

View File

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

View File

@@ -1,18 +1,16 @@
import chalk from 'chalk';
import getArgs from '../../util/get-args';
import getSubcommand from '../../util/get-subcommand';
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
import { getEnvTargetPlaceholder } from '../../util/env/env-target';
import { getLinkedProject } from '../../util/projects/link';
import Client from '../../util/client';
import { getEnvTargetPlaceholder } from '../../util/env/env-target';
import getArgs from '../../util/get-args';
import getInvalidSubcommand from '../../util/get-invalid-subcommand';
import getSubcommand from '../../util/get-subcommand';
import handleError from '../../util/handle-error';
import logo from '../../util/output/logo';
import { getCommandName, getPkgName } from '../../util/pkg-name';
import { getLinkedProject } from '../../util/projects/link';
import add from './add';
import pull from './pull';
import ls from './ls';
import pull from './pull';
import rm from './rm';
const help = () => {
@@ -139,6 +137,13 @@ export default async function main(client: Client) {
case 'rm':
return rm(client, project, argv, args, output);
case 'pull':
output.warn(
`${getCommandName(
'env pull'
)} is deprecated and will be removed in future releases. Run ${getCommandName(
'pull'
)} instead.`
);
return pull(client, project, argv, args, output);
default:
output.error(getInvalidSubcommand(COMMAND_CONFIG));

View File

@@ -1,19 +1,18 @@
import chalk from 'chalk';
import { closeSync, openSync, promises, readSync } from 'fs';
import { resolve } from 'path';
import { Project } from '../../types';
import { Output } from '../../util/output';
import confirm from '../../util/input/confirm';
import Client from '../../util/client';
import stamp from '../../util/output/stamp';
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
import param from '../../util/output/param';
import withSpinner from '../../util/with-spinner';
import { join } from 'path';
import { promises, openSync, closeSync, readSync } from 'fs';
import exposeSystemEnvs from '../../util/dev/expose-system-envs';
import { emoji, prependEmoji } from '../../util/emoji';
import getSystemEnvValues from '../../util/env/get-system-env-values';
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
import confirm from '../../util/input/confirm';
import { Output } from '../../util/output';
import param from '../../util/output/param';
import stamp from '../../util/output/stamp';
import { getCommandName } from '../../util/pkg-name';
const { writeFile } = promises;
import exposeSystemEnvs from '../../util/dev/expose-system-envs';
import getSystemEnvValues from '../../util/env/get-system-env-values';
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
@@ -57,8 +56,9 @@ export default async function pull(
return 1;
}
// handle relative or absolute filename
const [filename = '.env'] = args;
const fullPath = join(process.cwd(), filename);
const fullPath = resolve(filename);
const skipConfirmation = opts['--yes'];
const head = tryReadHeadSync(fullPath, Buffer.byteLength(CONTENTS_PREFIX));
@@ -83,19 +83,16 @@ export default async function pull(
project.name
)}\n`
);
const pullStamp = stamp();
const [
{ envs: projectEnvs },
{ systemEnvValues },
] = await withSpinner('Downloading', () =>
Promise.all([
getDecryptedEnvRecords(output, client, project.id),
project.autoExposeSystemEnvs
? getSystemEnvValues(output, client, project.id)
: { systemEnvValues: [] },
])
);
const pullStamp = stamp();
output.spinner('Downloading');
const [{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([
getDecryptedEnvRecords(output, client, project.id),
project.autoExposeSystemEnvs
? getSystemEnvValues(output, client, project.id)
: { systemEnvValues: [] },
]);
const records = exposeSystemEnvs(
projectEnvs,
@@ -120,6 +117,7 @@ export default async function pull(
emoji('success')
)}\n`
);
return 0;
}

View File

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

View File

@@ -2,6 +2,7 @@ export default new Map([
['alias', 'alias'],
['aliases', 'alias'],
['billing', 'billing'],
['build', 'build'],
['cc', 'billing'],
['cert', 'certs'],
['certs', 'certs'],
@@ -10,7 +11,6 @@ export default new Map([
['dns', 'dns'],
['domain', 'domains'],
['domains', 'domains'],
['downgrade', 'upgrade'],
['env', 'env'],
['help', 'help'],
['init', 'init'],
@@ -25,6 +25,7 @@ export default new Map([
['ls', 'list'],
['project', 'projects'],
['projects', 'projects'],
['pull', 'pull'],
['remove', 'remove'],
['rm', 'remove'],
['secret', 'secrets'],

View File

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

View File

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

View File

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

View File

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

View File

@@ -77,14 +77,8 @@ export default async function main(client: Client) {
return 1;
}
const {
authConfig: { token },
output,
apiUrl,
config,
} = client;
const { output, config } = client;
const debugEnabled = argv['--debug'];
const { print, log, error, note, debug, spinner } = output;
if (argv._.length > 2) {
@@ -92,8 +86,8 @@ export default async function main(client: Client) {
return 1;
}
let app: string | null = argv._[1];
let host: string | null = null;
let app: string | undefined = argv._[1];
let host: string | undefined = undefined;
if (argv['--help']) {
help();
@@ -126,10 +120,7 @@ export default async function main(client: Client) {
spinner(`Fetching deployments in ${chalk.bold(contextName)}`);
const now = new Now({
apiUrl,
token,
debug: debugEnabled,
output,
client,
currentTeam,
});
const start = Date.now();
@@ -156,7 +147,7 @@ export default async function main(client: Client) {
return 1;
}
app = null;
app = undefined;
host = asHost;
}

View File

@@ -2,7 +2,6 @@ import { validate as validateEmail } from 'email-validator';
import chalk from 'chalk';
import hp from '../util/humanize-path';
import getArgs from '../util/get-args';
import handleError from '../util/handle-error';
import logo from '../util/output/logo';
import prompt from '../util/login/prompt';
import doSamlLogin from '../util/login/saml';
@@ -52,20 +51,14 @@ const help = () => {
};
export default async function login(client: Client): Promise<number> {
let argv;
const { output } = client;
try {
argv = getArgs(client.argv.slice(2), {
'--oob': Boolean,
'--github': Boolean,
'--gitlab': Boolean,
'--bitbucket': Boolean,
});
} catch (err) {
handleError(err);
return 1;
}
const argv = getArgs(client.argv.slice(2), {
'--oob': Boolean,
'--github': Boolean,
'--gitlab': Boolean,
'--bitbucket': Boolean,
});
if (argv['--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.
// It needs to be saved to the configuration file.
// Save the user's authentication token to the configuration file.
client.authConfig.token = result.token;
writeToAuthConfigFile(client.authConfig);
@@ -124,9 +116,9 @@ export default async function login(client: Client): Promise<number> {
output.debug(`Saved credentials in "${hp(getGlobalPathConfig())}"`);
console.log(
output.print(
`${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(

View File

@@ -0,0 +1,116 @@
import chalk from 'chalk';
import { join } from 'path';
import Client from '../util/client';
import { emoji, prependEmoji } from '../util/emoji';
import getArgs from '../util/get-args';
import handleError from '../util/handle-error';
import setupAndLink from '../util/link/setup-and-link';
import logo from '../util/output/logo';
import stamp from '../util/output/stamp';
import { getPkgName } from '../util/pkg-name';
import {
getLinkedProject,
VERCEL_DIR,
VERCEL_DIR_PROJECT,
} from '../util/projects/link';
import { writeProjectSettings } from '../util/projects/project-settings';
import pull from './env/pull';
const help = () => {
return console.log(`
${chalk.bold(`${logo} ${getPkgName()} pull`)} [path]
${chalk.dim('Options:')}
-h, --help Output usage information
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'
)} Path to the local ${'`vercel.json`'} file
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
'DIR'
)} Path to the global ${'`.vercel`'} directory
-d, --debug Debug mode [off]
--env [filename] The file to write Development Environment Variables to [.env]
-y, --yes Skip the confirmation prompt
${chalk.dim('Examples:')}
${chalk.gray('')} Pull the latest Project Settings from the cloud
${chalk.cyan(`$ ${getPkgName()} pull`)}
${chalk.cyan(`$ ${getPkgName()} pull ./path-to-project`)}
${chalk.cyan(`$ ${getPkgName()} pull --env .env.local`)}
${chalk.cyan(`$ ${getPkgName()} pull ./path-to-project --env .env.local`)}
`);
};
export default async function main(client: Client) {
let argv;
try {
argv = getArgs(client.argv.slice(2), {
'--yes': Boolean,
'--env': String,
'--debug': Boolean,
'-d': '--debug',
'-y': '--yes',
});
} catch (err) {
handleError(err);
return 1;
}
if (argv['--help']) {
help();
return 2;
}
const cwd = argv._[1] || process.cwd();
const yes = argv['--yes'];
const env = argv['--env'] ?? '.env';
const settingsStamp = stamp();
let link = await getLinkedProject(client, cwd);
if (link.status === 'not_linked') {
link = await setupAndLink(client, cwd, {
autoConfirm: yes,
successEmoji: 'link',
setupMsg: 'Set up',
});
if (link.status === 'not_linked') {
// User aborted project linking questions
return 0;
}
}
if (link.status === 'error') {
return link.exitCode;
}
const { project, org } = link;
client.config.currentTeam = org.type === 'team' ? org.id : undefined;
const result = await pull(
client,
project,
argv,
[join(cwd, env)],
client.output
);
if (result !== 0) {
// an error happened
return result;
}
await writeProjectSettings(cwd, project, org);
client.output.print(
`${prependEmoji(
`Downloaded project settings to ${chalk.bold(
join(VERCEL_DIR, VERCEL_DIR_PROJECT)
)} ${chalk.gray(settingsStamp())}`,
emoji('success')
)}\n`
);
return 0;
}

View File

@@ -85,8 +85,6 @@ export default async function main(client: Client) {
argv._ = argv._.slice(1);
const {
apiUrl,
authConfig: { token },
output,
config: { currentTeam },
} = client;
@@ -245,11 +243,8 @@ export default async function main(client: Client) {
}
const now = new Now({
apiUrl,
token,
debug: argv['--debug'],
client,
currentTeam,
output,
});
const start = Date.now();

View File

@@ -72,8 +72,6 @@ const help = () => {
// Options
let argv;
let debug;
let apiUrl;
let subcommand;
let nextTimestamp;
@@ -90,8 +88,6 @@ const main = async client => {
argv._ = argv._.slice(1);
debug = argv.debug;
apiUrl = client.apiUrl;
subcommand = argv._[0];
nextTimestamp = argv.next;
@@ -101,7 +97,6 @@ const main = async client => {
}
const {
authConfig: { token },
output,
config: { currentTeam },
} = client;
@@ -118,7 +113,7 @@ const main = async client => {
throw err;
}
return run({ output, token, contextName, currentTeam, client });
return run({ output, contextName, currentTeam, client });
};
export default async client => {
@@ -130,8 +125,8 @@ export default async client => {
}
};
async function run({ output, token, contextName, currentTeam, client }) {
const secrets = new NowSecrets({ apiUrl, token, debug, currentTeam, output });
async function run({ output, contextName, currentTeam, client }) {
const secrets = new NowSecrets({ client, currentTeam });
const args = argv._.slice(1);
const start = Date.now();
const { 'test-warning': testWarningFlag } = argv;

View File

@@ -1,21 +1,23 @@
import chalk from 'chalk';
import stamp from '../../util/output/stamp.ts';
import stamp from '../../util/output/stamp';
import info from '../../util/output/info';
import rightPad from '../../util/output/right-pad';
import eraseLines from '../../util/output/erase-lines';
import chars from '../../util/output/chars';
import note from '../../util/output/note';
import textInput from '../../util/input/text';
import invite from './invite';
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
// should be fixed on utils/input/text.js
/^[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
// should be fixed on utils/input/text.js
/^[ a-zA-Z0-9_-]+$/.test(value + data);
@@ -30,17 +32,17 @@ const gracefulExit = () => {
return 0;
};
const teamUrlPrefix = rightPad('Team URL', 14) + chalk.gray('vercel.com/');
const teamNamePrefix = rightPad('Team Name', 14);
const teamUrlPrefix = 'Team URL'.padEnd(14) + chalk.gray('vercel.com/');
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 team;
let elapsed;
const { output } = client;
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`'
)})`
);
@@ -66,14 +68,12 @@ export default async function add(client, teams) {
elapsed = stamp();
output.spinner(teamUrlPrefix + slug);
let res;
try {
// eslint-disable-next-line no-await-in-loop
res = await teams.create({ slug });
team = res;
team = await createTeam(client, { slug });
} catch (err) {
output.stopSpinner();
process.stdout.write(eraseLines(2));
output.print(eraseLines(2));
output.error(err.message);
}
} while (!team);
@@ -104,11 +104,12 @@ export default async function add(client, teams) {
elapsed = stamp();
output.spinner(teamNamePrefix + name);
const res = await teams.edit({ id: team.id, name });
const res = await patchTeam(client, team.id, { name });
output.stopSpinner();
process.stdout.write(eraseLines(2));
/*
if (res.error) {
output.error(res.error.message);
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
// there's a scenario where that would be wanted
}
*/
team = Object.assign(team, res);
output.success(`Team name saved ${elapsed()}`);
output.log(`${chalk.cyan(`${chars.tick} `) + teamNamePrefix + team.name}\n`);
output.spinner('Saving');
// Update config file
const configCopy = Object.assign({}, client.config);
if (configCopy.sh) {
configCopy.sh.currentTeam = team;
} else {
configCopy.currentTeam = team.id;
}
writeToConfigFile(configCopy);
output.spinner('Saving');
client.config.currentTeam = team.id;
writeToConfigFile(client.config);
output.stopSpinner();
await invite(client, { _: [] }, teams, {
await invite(client, [], {
introMsg: 'Invite your teammates! When done, press enter on an empty field',
noopMsg: `You can invite teammates later by running ${getCommandName(
`teams invite`
)}`,
});
gracefulExit();
return gracefulExit();
}

View File

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

View File

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

View File

@@ -1,22 +1,41 @@
import chars from '../../util/output/chars';
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 { getPkgName } from '../../util/pkg-name.ts';
import { getPkgName } from '../../util/pkg-name';
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 { 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)) {
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;
}
output.spinner('Fetching teams');
const { teams: list, pagination } = await teams.ls({
const { teams, pagination } = await getTeams(client, {
next,
apiVersion: 2,
});
@@ -37,40 +56,31 @@ export default async function list(client, argv, teams) {
}
if (accountIsCurrent) {
currentTeam = {
slug: user.username || user.email,
};
currentTeam = user.id;
}
const teamList = list.map(({ slug, name }) => ({
const teamList = teams.map(({ id, slug, name }) => ({
id,
name,
value: slug,
current: slug === currentTeam.slug ? chars.tick : '',
current: id === currentTeam ? chars.tick : '',
}));
teamList.unshift({
id: user.id,
name: 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) {
const index = teamList.findIndex(
choice => choice.value === currentTeam.slug
);
const index = teamList.findIndex(choice => choice.id === currentTeam);
const choice = teamList.splice(index, 1)[0];
teamList.unshift(choice);
}
// Printing
const count = teamList.length;
if (!count) {
// Maybe should not happen
output.error(`No teams found`);
return 1;
}
output.stopSpinner();
console.log(); // empty line
@@ -80,7 +90,7 @@ export default async function list(client, argv, teams) {
[1, 5]
);
if (pagination && pagination.count === 20) {
if (pagination?.count === 20) {
const prefixedArgs = getPrefixedFlags(argv);
const flags = getCommandFlags(prefixedArgs, ['_', '--next', '-N', '-d']);
const nextCmd = `${getPkgName()} teams ls${flags} --next ${
@@ -89,4 +99,6 @@ export default async function list(client, argv, teams) {
console.log(); // empty line
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 { emoji } from '../../util/emoji';
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 { Team, GlobalConfig } from '../../types';
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 { getPkgName } from '../util/pkg-name';
import getArgs from '../util/get-args';
import handleError from '../util/handle-error';
import Client from '../util/client';
const help = () => {
@@ -32,16 +31,9 @@ const help = () => {
`);
};
export default async (client: Client) => {
export default async (client: Client): Promise<number> => {
const { output } = client;
let argv;
try {
argv = getArgs(client.argv.slice(2), {});
} catch (error) {
handleError(error);
return 1;
}
const argv = getArgs(client.argv.slice(2), {});
argv._ = argv._.slice(1);
if (argv['--help'] || argv._[0] === 'help') {
@@ -62,9 +54,13 @@ export default async (client: Client) => {
throw err;
}
if (process.stdout.isTTY) {
process.stdout.write('> ');
if (output.isTTY) {
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 { URL } from 'url';
import * as Sentry from '@sentry/node';
import { NowBuildError } from '@vercel/build-utils';
import hp from './util/humanize-path';
import commands from './commands/index.ts';
import pkg from './util/pkg.ts';
import commands from './commands';
import pkg from './util/pkg';
import createOutput from './util/output';
import cmd from './util/output/cmd';
import info from './util/output/info';
@@ -31,9 +30,9 @@ import error from './util/output/error';
import param from './util/output/param';
import highlight from './util/output/highlight';
import getArgs from './util/get-args';
import getUser from './util/get-user.ts';
import Client from './util/client.ts';
import NowTeams from './util/teams';
import getUser from './util/get-user';
import getTeams from './util/teams/get-teams';
import Client from './util/client';
import { handleError } from './util/error';
import reportError from './util/report-error';
import getConfig from './util/get-config';
@@ -44,13 +43,14 @@ import {
getDefaultAuthConfig,
} from './util/config/get-default';
import * as ERRORS from './util/errors-ts';
import { NowError } from './util/now-error';
import { APIError } from './util/errors-ts.ts';
import { SENTRY_DSN } from './util/constants.ts';
import { APIError } from './util/errors-ts';
import { SENTRY_DSN } from './util/constants';
import getUpdateCommand from './util/get-update-command';
import { metrics, shouldCollectMetrics } from './util/metrics.ts';
import { getCommandName, getTitleName } from './util/pkg-name.ts';
import doLoginPrompt from './util/login/prompt.ts';
import { metrics, shouldCollectMetrics } from './util/metrics';
import { getCommandName, getTitleName } from './util/pkg-name';
import doLoginPrompt from './util/login/prompt';
import { GlobalConfig } from './types';
import { VercelConfig } from '@vercel/client';
const isCanary = pkg.version.includes('canary');
@@ -77,8 +77,8 @@ Sentry.init({
environment: isCanary ? 'canary' : 'stable',
});
let client;
let debug = () => {};
let client: Client;
let debug: (s: string) => void = () => {};
let apiUrl = 'https://api.vercel.com';
const main = async () => {
@@ -108,26 +108,30 @@ const main = async () => {
debug = output.debug;
const localConfigPath = argv['--local-config'];
const localConfig = await getConfig(output, localConfigPath);
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;
}
let localConfig: VercelConfig | Error | undefined = await getConfig(
output,
localConfigPath
);
if (localConfig instanceof ERRORS.CantParseJSONFile) {
output.error(`Couldn't parse JSON file ${localConfig.meta.file}.`);
return 1;
}
if (
(localConfig instanceof NowError || localConfig instanceof NowBuildError) &&
!(localConfig instanceof ERRORS.CantFindConfig)
) {
if (localConfig instanceof ERRORS.CantFindConfig) {
if (localConfigPath) {
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);
return 1;
}
@@ -160,9 +164,15 @@ const main = async () => {
output.print(
`${chalk.grey(
`${getTitleName()} CLI ${pkg.version}${
targetOrSubcommand === 'dev' ? ' dev (beta)' : ''
targetOrSubcommand === 'dev'
? ' dev (beta)'
: targetOrSubcommand === 'build'
? ' build (beta)'
: ''
}${
isCanary || targetOrSubcommand === 'dev'
isCanary ||
targetOrSubcommand === 'dev' ||
targetOrSubcommand === 'build'
? ' — https://vercel.com/feedback'
: ''
}`
@@ -207,7 +217,7 @@ const main = async () => {
return 0;
}
let config;
let config: GlobalConfig | null = null;
if (configExists) {
try {
@@ -229,8 +239,11 @@ const main = async () => {
// multiple providers. In that case, we really
// need to migrate.
if (
// @ts-ignore
config.sh ||
// @ts-ignore
config.user ||
// @ts-ignore
typeof config.user === 'object' ||
typeof config.currentTeam === 'object'
) {
@@ -279,7 +292,14 @@ const main = async () => {
let authConfig = null;
const subcommandsWithoutToken = ['login', 'logout', 'help', 'init', 'update'];
const subcommandsWithoutToken = [
'login',
'logout',
'help',
'init',
'update',
'build',
];
if (authConfigExists) {
try {
@@ -300,6 +320,7 @@ const main = async () => {
// This is from when Vercel CLI supported
// multiple providers. In that case, we really
// need to migrate.
// @ts-ignore
if (authConfig.credentials) {
authConfigExists = false;
}
@@ -346,6 +367,11 @@ const main = async () => {
return 1;
}
if (!config) {
output.error(`Vercel global config was not loaded.`);
return 1;
}
// Shared API `Client` instance for all sub-commands to utilize
client = new Client({
apiUrl,
@@ -380,24 +406,37 @@ const main = async () => {
} else if (commands.has(singular)) {
alternative = singular;
}
console.error(
error(
`The supplied argument ${param(targetOrSubcommand)} is ambiguous.` +
`\nIf you wish to deploy the ${fileType} ${param(
targetOrSubcommand
)}, first run "cd ${targetOrSubcommand}". ` +
if (targetOrSubcommand === 'build') {
output.note(
`If you wish to deploy the ${fileType} ${param(
targetOrSubcommand
)}, run ${getCommandName('deploy build')}.` +
(alternative
? `\nIf you wish to use the subcommand ${param(
targetOrSubcommand
)}, use ${param(alternative)} instead.`
: '')
)
);
return 1;
);
} else {
console.error(
error(
`The supplied argument ${param(targetOrSubcommand)} is ambiguous.` +
`\nIf you wish to deploy the ${fileType} ${param(
targetOrSubcommand
)}, first run "cd ${targetOrSubcommand}". ` +
(alternative
? `\nIf you wish to use the subcommand ${param(
targetOrSubcommand
)}, use ${param(alternative)} instead.`
: '')
)
);
return 1;
}
}
if (subcommandExists) {
debug('user supplied known subcommand', targetOrSubcommand);
debug(`user supplied known subcommand: "${targetOrSubcommand}"`);
subcommand = targetOrSubcommand;
} else {
debug('user supplied a possible target for deployment');
@@ -430,12 +469,16 @@ const main = async () => {
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.
// It needs to be saved to the configuration file.
client.authConfig.token = result;
// New user, so we can't keep the team
delete client.config.currentTeam;
client.authConfig.token = result.token;
configFiles.writeToAuthConfigFile(client.authConfig);
configFiles.writeToConfigFile(client.config);
@@ -453,14 +496,12 @@ const main = async () => {
}
if (typeof argv['--token'] === 'string' && subcommand === 'switch') {
console.error(
error({
message: `This command doesn't work with ${param(
'--token'
)}. Please use ${param('--scope')}.`,
slug: 'no-token-allowed',
})
);
output.prettyError({
message: `This command doesn't work with ${param(
'--token'
)}. Please use ${param('--scope')}.`,
link: 'https://err.sh/vercel/no-token-allowed',
});
return 1;
}
@@ -469,12 +510,10 @@ const main = async () => {
const token = argv['--token'];
if (token.length === 0) {
console.error(
error({
message: `You defined ${param('--token')}, but it's missing a value`,
slug: 'missing-token-value',
})
);
output.prettyError({
message: `You defined ${param('--token')}, but it's missing a value`,
link: 'https://err.sh/vercel/missing-token-value',
});
return 1;
}
@@ -482,16 +521,14 @@ const main = async () => {
const invalid = token.match(/(\W)/g);
if (invalid) {
const notContain = Array.from(new Set(invalid)).sort();
console.error(
error({
message: `You defined ${param(
'--token'
)}, but its contents are invalid. Must not contain: ${notContain
.map(c => JSON.stringify(c))
.join(', ')}`,
slug: 'invalid-token-value',
})
);
output.prettyError({
message: `You defined ${param(
'--token'
)}, but its contents are invalid. Must not contain: ${notContain
.map(c => JSON.stringify(c))
.join(', ')}`,
link: 'https://err.sh/vercel/invalid-token-value',
});
return 1;
}
@@ -512,13 +549,8 @@ const main = async () => {
);
}
const {
authConfig: { token },
} = client;
let scope = argv['--scope'] || argv['--team'] || localConfig.scope;
const targetCommand = commands.get(subcommand);
const scope = argv['--scope'] || argv['--team'] || localConfig?.scope;
if (
typeof scope === 'string' &&
@@ -532,12 +564,10 @@ const main = async () => {
user = await getUser(client);
} catch (err) {
if (err.code === 'NOT_AUTHORIZED') {
console.error(
error({
message: `You do not have access to the specified account`,
slug: 'scope-not-accessible',
})
);
output.prettyError({
message: `You do not have access to the specified account`,
link: 'https://err.sh/vercel/scope-not-accessible',
});
return 1;
}
@@ -546,22 +576,19 @@ const main = async () => {
return 1;
}
if (user.uid === scope || user.email === scope || user.username === scope) {
if (user.id === scope || user.email === scope || user.username === scope) {
delete client.config.currentTeam;
} else {
let list = [];
let teams = [];
try {
const teams = new NowTeams({ apiUrl, token, debug: isDebugging });
list = (await teams.ls()).teams;
teams = await getTeams(client);
} catch (err) {
if (err.code === 'not_authorized') {
console.error(
error({
message: `You do not have access to the specified team`,
slug: 'scope-not-accessible',
})
);
output.prettyError({
message: `You do not have access to the specified team`,
link: 'https://err.sh/vercel/scope-not-accessible',
});
return 1;
}
@@ -571,15 +598,13 @@ const main = async () => {
}
const related =
list && list.find(item => item.id === scope || item.slug === scope);
teams && teams.find(team => team.id === scope || team.slug === scope);
if (!related) {
console.error(
error({
message: 'The specified scope does not exist',
slug: 'scope-not-existent',
})
);
output.prettyError({
message: 'The specified scope does not exist',
link: 'https://err.sh/vercel/scope-not-existent',
});
return 1;
}
@@ -588,20 +613,99 @@ const main = async () => {
}
}
if (!targetCommand) {
const sub = param(subcommand);
console.error(error(`The ${sub} subcommand does not exist`));
return 1;
}
const metric = metrics();
let exitCode;
const eventCategory = 'Exit Code';
try {
const start = Date.now();
const full = require(`./commands/${targetCommand}`).default;
exitCode = await full(client);
let func: any;
switch (targetCommand) {
case 'alias':
func = await import('./commands/alias');
break;
case 'billing':
func = await import('./commands/billing');
break;
case 'build':
func = await import('./commands/build');
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 'pull':
func = await import('./commands/pull');
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;
if (shouldCollectMetrics) {
@@ -615,7 +719,7 @@ const main = async () => {
} catch (err) {
if (err.code === 'ENOTFOUND') {
// Error message will look like the following:
// "request to https://api.vercel.com/www/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
// "request to https://api.vercel.com/v2/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
if (matches && matches[1]) {
const hostname = matches[1];
@@ -674,7 +778,7 @@ const main = async () => {
return exitCode;
};
const handleRejection = async err => {
const handleRejection = async (err: any) => {
debug('handling rejection');
if (err) {
@@ -691,7 +795,7 @@ const handleRejection = async err => {
process.exit(1);
};
const handleUnexpected = async err => {
const handleUnexpected = async (err: Error) => {
const { message } = err;
// We do not want to render errors about Sentry not being reachable
@@ -700,9 +804,8 @@ const handleUnexpected = async err => {
return;
}
await reportError(Sentry, client, err);
console.error(error(`An unexpected error occurred!\n${err.stack}`));
await reportError(Sentry, client, err);
process.exit(1);
};
@@ -713,6 +816,7 @@ process.on('uncaughtException', handleUnexpected);
main()
.then(exitCode => {
process.exitCode = exitCode;
// @ts-ignore - "nowExit" is a non-standard event name
process.emit('nowExit');
})
.catch(handleUnexpected);

View File

@@ -16,11 +16,13 @@ export interface JSONObject {
}
export interface AuthConfig {
_?: string;
token?: string;
skipWrite?: boolean;
}
export interface GlobalConfig {
_?: string;
currentTeam?: string;
includeScheme?: string;
collectMetrics?: boolean;
@@ -43,25 +45,12 @@ type Billing = {
};
export type User = {
uid: string;
id: string;
avatar: string;
bio?: string;
date: number;
createdAt: number;
email: string;
username: string;
website?: string;
billingChecked: boolean;
billing: Billing;
github?: {
email: string;
installation: {
id: string;
login: string;
loginType: string;
};
login: string;
updatedAt: number;
};
name?: string;
limited?: boolean;
};
@@ -306,3 +295,141 @@ export interface Token {
createdAt: number;
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

@@ -18,7 +18,7 @@ async function getAppLastDeployment(
const deployments = await getDeploymentsByAppName(client, appName);
const deploymentItem = deployments
.sort((a, b) => b.created - a.created)
.filter(dep => dep.state === 'READY' && dep.creator.uid === user.uid)[0];
.filter(dep => dep.state === 'READY' && dep.creator.uid === user.id)[0];
// Try to fetch deployment details
if (deploymentItem) {
@@ -35,7 +35,7 @@ export async function getDeploymentForAlias(
localConfigPath: string | undefined,
user: User,
contextName: string,
localConfig: VercelConfig
localConfig?: VercelConfig
) {
output.spinner(`Fetching deployment to alias in ${chalk.bold(contextName)}`);
@@ -52,7 +52,7 @@ export async function getDeploymentForAlias(
}
const appName =
(localConfig && localConfig.name) ||
localConfig?.name ||
path.basename(path.resolve(process.cwd(), localConfigPath || ''));
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;
output: Output;
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;
};
@@ -47,7 +47,7 @@ export default class Client extends EventEmitter {
authConfig: AuthConfig;
output: Output;
config: GlobalConfig;
localConfig: VercelConfig;
localConfig?: VercelConfig;
private requestIdCounter: number;
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 apiUrl = parsedUrl.host
? `${parsedUrl.protocol}//${parsedUrl.host}`
@@ -100,7 +100,7 @@ export default class Client extends EventEmitter {
let body;
if (isJSONObject(opts.body)) {
body = JSON.stringify(opts.body);
headers.set('content-type', 'application/json; charset=utf8');
headers.set('content-type', 'application/json; charset=utf-8');
} else {
body = opts.body;
}

View File

@@ -100,8 +100,8 @@ export function getAuthConfigFilePath() {
export function readLocalConfig(
prefix: string = process.cwd()
): VercelConfig | null {
let config: VercelConfig | null = null;
): VercelConfig | undefined {
let config: VercelConfig | undefined = undefined;
let target = '';
try {
@@ -116,7 +116,7 @@ export function readLocalConfig(
}
if (!target) {
return null;
return;
}
try {
@@ -134,7 +134,7 @@ export function readLocalConfig(
}
if (!config) {
return null;
return;
}
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;
const config = {
_:
'This is your Vercel config file. For more information see the global configuration documentation: https://vercel.com/docs/configuration#global',
const config: GlobalConfig = {
_: 'This is your Vercel config file. For more information see the global configuration documentation: https://vercel.com/docs/configuration#global',
collectMetrics: true,
};
@@ -16,44 +17,33 @@ export const getDefaultConfig = async existingCopy => {
'collectMetrics',
'api',
// This is deleted later in the code
'shownTips',
];
try {
const existing = Object.assign({}, existingCopy);
// @ts-ignore
const sh = Object.assign({}, existing.sh || {});
Object.assign(config, existing, sh);
for (const key of Object.keys(config)) {
if (!keep.includes(key)) {
// @ts-ignore
delete config[key];
}
}
if (typeof config.currentTeam === 'object') {
// @ts-ignore
config.currentTeam = config.currentTeam.id;
}
// @ts-ignore
if (typeof config.user === 'object') {
// @ts-ignore
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;
} catch (err) {}
}
@@ -61,16 +51,16 @@ export const getDefaultConfig = async existingCopy => {
return { config, migrated };
};
export const getDefaultAuthConfig = async existing => {
export const getDefaultAuthConfig = async (existing?: AuthConfig | null) => {
let migrated = false;
const config = {
_:
'This is your Vercel credentials file. DO NOT SHARE! More: https://vercel.com/docs/configuration#global',
const config: AuthConfig = {
_: 'This is your Vercel credentials file. DO NOT SHARE! More: https://vercel.com/docs/configuration#global',
};
if (existing) {
try {
// @ts-ignore
const sh = existing.credentials.find(item => item.provider === 'sh');
if (sh) {

View File

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

View File

@@ -0,0 +1,37 @@
import Client from '../client';
type CheckStatus = 'registered' | 'running' | 'completed';
type CheckConclusion =
| 'canceled'
| 'failed'
| 'neutral'
| 'succeeded'
| 'skipped'
| 'stale';
export interface DeploymentCheck {
id: string;
status: CheckStatus;
conclusion: CheckConclusion;
name: string;
startedAt: number;
completedAt: number;
createdAt: number;
updatedAt: number;
integrationId: string;
rerequestable: boolean;
}
export interface DeploymentChecksResponse {
checks: DeploymentCheck[];
}
export async function getDeploymentChecks(
client: Client,
deploymentId: string
) {
const checksResponse = await client.fetch<DeploymentChecksResponse>(
`/v1/deployments/${encodeURIComponent(deploymentId)}/checks`
);
return checksResponse;
}

View File

@@ -47,6 +47,7 @@ export default async function processDeployment({
force?: boolean;
withCache?: boolean;
org: Org;
prebuilt: boolean;
projectName: string;
isSettingUpProject: boolean;
skipAutoDetectionConfirmation?: boolean;
@@ -62,6 +63,7 @@ export default async function processDeployment({
withCache,
nowConfig,
quiet,
prebuilt,
} = args;
const { debug } = output;
@@ -69,15 +71,21 @@ export default async function processDeployment({
const { env = {} } = requestBody;
const token = now._token;
if (!token) {
throw new Error('Missing authentication token');
}
const clientOptions: VercelClientOptions = {
teamId: org.type === 'team' ? org.id : undefined,
apiUrl: now._apiUrl,
token: now._token,
token,
debug: now._debug,
userAgent: ua,
path: paths[0],
force,
withCache,
prebuilt,
skipAutoDetectionConfirmation,
};
@@ -149,7 +157,6 @@ export default async function processDeployment({
org.slug
);
// @ts-ignore
now.url = event.payload.url;
output.stopSpinner();
@@ -175,10 +182,26 @@ export default async function processDeployment({
return event.payload;
}
if (event.type === 'ready') {
// If `checksState` is present, we can only continue to "Completing" if the checks finished,
// otherwise we might show "Completing" before "Running Checks".
if (
event.type === 'ready' &&
(event.payload.checksState
? event.payload.checksState === 'completed'
: true)
) {
output.spinner('Completing', 0);
}
if (event.type === 'checks-running') {
output.spinner('Running Checks', 0);
}
if (event.type === 'checks-conclusion-failed') {
output.stopSpinner();
return event.payload;
}
// Handle error events
if (event.type === 'error') {
output.stopSpinner();

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