Compare commits

...

62 Commits

Author SHA1 Message Date
Steven
b52d01f809 Publish Stable
- @vercel/build-utils@5.3.1
 - vercel@28.0.0
 - @vercel/client@12.2.0
 - @vercel/go@2.1.1
 - @vercel/hydrogen@0.0.14
 - @vercel/next@3.1.18
 - @vercel/node@2.5.8
 - @vercel/python@3.1.9
 - @vercel/redwood@1.0.18
 - @vercel/remix@1.0.19
 - @vercel/ruby@1.3.25
 - @vercel/static-build@1.0.18
2022-08-12 16:31:35 -04:00
Matthew Stanciu
ffefaf82a1 [cli] Fix vc env pull delta changes bug (#8382)
#8170 shipped with a small bug: double quotes aren't stripped when pulling existing environment variables, but double quotes are stripped when reading `.env`, so if an environment variable uploaded to Vercel contained double quotes, it would always show as "Changed". This PR fixes the bug.
2022-08-12 15:56:47 -04:00
Steven
6d8dbfc7d6 [ruby] Handle exit code 18 gracefully (#8388)
This reverts #8381 and instead supports the "2.7.x" syntax.

- Related to https://github.com/vercel/customer-issues/issues/684
2022-08-12 15:56:13 -04:00
Steven
551cd7f688 [ruby] Fix ruby version error message (#8381)
This error message was incorrectly telling user to use the `2.7.x` value even though it will fail `bundle install` with the following error:

```
Your Ruby patchlevel is 0, but your Gemfile specified -1
Command failed with exit code 18 (EXDEV)
```

The exact version, `2.7.0`, is necessary.
2022-08-12 11:31:36 -04:00
Chris Barber
2dfb6b45cd [cli] MAJOR: Remove update command (#8369)
The `vc update` command is essentially a noop command that doesn't actually update the CLI. This command is not listed in the `--help` output, nor the CLI documentation. It should be safe to remove in the next major release.
2022-08-12 11:24:31 -04:00
Steven
65ae2a289e Revert "[go] add lambda wrapper support to the go runtime" (#8385)
Revert "[go] add lambda wrapper support to the go runtime (#8350)"

This reverts commit 4c230c8436.
2022-08-12 10:51:43 -04:00
Damien Simonin Feugas
72ea3532b1 chore: does not add /index when already present (#8373) 2022-08-12 10:10:05 +02:00
Sean Massa
78fac00823 [cli] improve vc build --help language (#8380)
Tweak the `--yes` help language for the `build` command to be more consistent with other flag descriptions.

---

Related to: https://github.com/vercel/vercel/pull/8330
2022-08-12 02:16:07 +00:00
Nathan Rajlich
9e255afa37 [cli] Support more granular file upload progress bar (#8371)
### Before

Previously, the file upload progress bar would only get updated upon completion of uploading an individual file. This would cause the the progress bar to appear "chunky" when uploading large files. This problem is exasperated with #8356, since there is only a single (potentially large) file that gets uploaded, which would cause the progress bar to not render at all, making the deployment process seem "stuck".

https://user-images.githubusercontent.com/71256/184241009-ba6d1e63-a0fe-462f-804b-e58f56ec310c.mov

### After

Now, the progress bar gets updated incrementally as individual files get uploaded which makes the progress bar updates smoother and play nicely with large files. This is done by "chunking" the individual file into slices and creating a Readable stream from those chunks.

https://user-images.githubusercontent.com/71256/184241050-51832996-d42b-46fd-9a71-d8a1f87a026c.mov
2022-08-12 01:18:58 +00:00
Matthew Stanciu
e4be68270f [cli] MAJOR: vc ls visual changes (#8151)
- Username field does not show if all deployment usernames in the page match the context name
- The `age` field shows first in the table
- A new `duration` field shows the time it took to deploy
- A new `--prod` flag filters for production deployments
- Table headers are colored cyan

### Before

<img width="1000" alt="Screen Shot 2022-07-13 at 6 29 15 PM" src="https://user-images.githubusercontent.com/14811170/178867816-5d252c94-86c0-46f4-82bf-a33341686aa6.png">

<img width="1081" alt="Screen Shot 2022-07-13 at 6 31 15 PM" src="https://user-images.githubusercontent.com/14811170/178867934-4fa570a1-e2ba-4d1f-8a93-b88b095c1dca.png">

### After

<img width="1002" alt="Screen Shot 2022-07-26 at 2 23 27 PM" src="https://user-images.githubusercontent.com/14811170/181114658-b1cc0f8d-18a7-4996-8635-a7868af50a19.png">

<img width="1077" alt="Screen Shot 2022-07-26 at 2 23 21 PM" src="https://user-images.githubusercontent.com/14811170/181114646-f614a235-f962-468e-a8e0-ca65f1a94b69.png">

### 📋 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
2022-08-12 00:15:17 +00:00
Matthew Stanciu
9c636dc1ba [cli] MAJOR: Remove vc billing command (#8377) 2022-08-11 16:29:49 -07:00
Matthew Stanciu
c98c9996bf [cli] MAJOR: Connect a Git provider repository in vc link (#8290)
#8100 added a new `vc git` command, which allows users to connect a Git provider repository, enabling them to set up a full Git workflow for their Vercel projects without having to leave the CLI.

This PR takes this functionality a step further by including it as part of the `vc link` flow. This way, users can set up a Vercel project and add a Git provider repository all in one step.

This PR is blocked by a PR to `front` which adds an option to the Git Settings page for a Project to re-enable the prompt if the user opted out (in review).

### 📋 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
2022-08-11 22:49:36 +00:00
Matthew Stanciu
0fcf172a10 [cli] vc project ls visual updates (#8157)
Related to #8102 & #8151. This PR updates the look of `vc project ls`:

- Header names are cyan
- A new "Latest Production URL" section

### Before

<img width="583" alt="Screen Shot 2022-08-11 at 10 46 03 AM" src="https://user-images.githubusercontent.com/14811170/184199554-5b298f2c-7d62-4200-a580-ddb16765c20e.png">

### After

<img width="816" alt="Screen Shot 2022-08-11 at 10 45 54 AM" src="https://user-images.githubusercontent.com/14811170/184199502-34295b2f-be7f-4289-b426-372c58f37eb6.png">

### 📋 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
2022-08-11 22:08:27 +00:00
Matthew Stanciu
99e5c4a6db [cli] Accept URL argument in vc git connect (#8351)
* Add functional but bad code

* Add docs

* Re-add thing just in case

* Fix some things

* Add a few tests

* Add test for multiple

* Add another test

* Small type fix

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

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

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

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

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

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

* Update packages/cli/test/unit/commands/git.test.ts

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

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

Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>

* Fix typo

Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>

* Update packages/cli/src/commands/git/connect.ts

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

* Update packages/cli/src/commands/git/connect.ts

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

* Update packages/cli/src/commands/git/connect.ts

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

* Update packages/cli/src/commands/git/connect.ts

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

* Update packages/cli/src/commands/git/connect.ts

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

* return 0

* only return boolean in `promptConnectArg()`

* Remove err.meta check

* `parseRepoUrl()`: parse url without a scheme

* Remove todo

* Fix typo

* Small wording change

* Print each line instead of building buffer

* Make consistent quotes vs template literals

* Remove `multiple` variable

* Remove else

* Accept objects & rename some variables

* Move `connect-git-provider` to `git` folder

* Don't pass in `output`

* Fix small thing

* Add another test

Co-authored-by: Steven <steven@ceriously.com>
Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>
Co-authored-by: Chris Barber <chris.barber@vercel.com>
2022-08-11 14:28:58 -07:00
Nathan Rajlich
b8269b0111 [build-utils] Remove as cast in glob() (#8376)
This is just a small TypeScript cleanup to remove an `as Stats` cast for the "statsCache".

Also drops the `FsFiles` interface in favor of just using the built-in `Record` utility type.
2022-08-11 21:08:53 +00:00
Sean Massa
decac0fe3f Revert "[remix] add image optimization configuration" (#8375)
Reverts vercel/vercel#8217

The previous PR had unresolved comments, but the kodiak bot automerged it. Let's revert for now, then we can continue to iterate on code in a new PR.
2022-08-11 19:20:20 +00:00
Mosaad
591d1686d0 [remix] add image optimization configuration (#8217)
### Feature
Makes it possible to use [Vercel's image optimization](https://vercel.com/docs/build-output-api/v3#build-output-configuration/supported-properties/images).

### Demo
This example is built locally with this patch:
https://github.com/theMosaad/remix-vercel-image
2022-08-11 18:30:21 +00:00
Matthew Stanciu
5e1d5c921c [client] Fix archive type on VercelClientOptions (#8367)
Fix `archive` type on `VercelClientOptions`
2022-08-10 13:44:20 -07:00
Matthew Stanciu
603b1256c6 [client] Send single source.tgz archive when creating deployment (#8356)
Right now, deployments via Vercel CLI are created by uploading every source file individually. This PR creates a tarball of the user's source files and sends the packed tarball instead. This speeds up deployment times, especially for large projects, and also bypasses the 15,000 file upload limit.

Currently, it requires a `--archive=tgz` flag. Perhaps we can enable this by default for prebuilt, but that may be for a separate PR.

### 📋 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
2022-08-10 19:35:25 +00:00
Nathan Rajlich
6957c72828 [client] Remove extraneous file read during deployment upload (#8366)
The `file` object already contains the contents as a Buffer, so there's no need to read the files again.

Extracted from #8356.
2022-08-10 12:09:04 -07:00
Steven
9be3650cb7 [tests] Use yarn --frozen-lockfile (#8363)
This PR updates CI to use `yarn install --frozen-lockfile` and also updates some github actions to v3.
2022-08-10 15:55:38 +00:00
Steven
6e1ee7a7d6 [cli] Fix dev query string merging (#8183)
Since `vercel dev` performs query string merging with `vercel.json` routes and the requested url, there is sometimes a slight difference between the request that the browser sends and the request that the upstream dev server receives.

Most servers treat the query string key with "empty" value the same as undefined value. Meaning, these two URLs should be equivalent because each has empty values:

- `http://example.com/src/App.vue?vue&type=style&index=0&lang.css`
- `http://example.com/src/App.vue?vue=&type=style&index=0&lang.css=`

However, `vite dev` handles these two URLs differently because of [string comparisons](2289d04af5/packages/plugin-vue/src/handleHotUpdate.ts (L98-L99)) instead of URL parsing, which causes requests from `vercel dev` to fail. Thus, this PR changes from Node.js native URL parsing to a custom implementation in order to handle this corner case and preserve the difference between `?a=` and `?a`.

Ideally this would be [fixed in vite](https://github.com/vitejs/vite/pull/9589), however we might run into other dev servers that also fail in the same way in the future.

- Fixes https://github.com/vercel/vercel/issues/7283
- Closes https://github.com/vitejs/vite/pull/9589
- Related to https://github.com/nodejs/node/issues/9500
2022-08-10 11:34:29 -04:00
Damien Simonin Feugas
767ce2cff1 [node] adds /index to / middleware matchers (#8349) 2022-08-10 15:17:15 +02:00
Craig Andrews
bb1d0ce1b7 chore(docs) Add details of how to enable lambda runtime wrappers to custom runtimes (#8360)
### 📋 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
2022-08-09 21:54:25 +00:00
Sean Massa
31f79c7de1 Publish Stable
- vercel@27.4.0
 - @vercel/go@2.1.0
 - @vercel/next@3.1.17
2022-08-09 16:11:57 -05:00
Craig Andrews
4c230c8436 [go] add lambda wrapper support to the go runtime (#8350)
Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>
2022-08-09 16:08:20 -05:00
Lee Robinson
7941f5a104 [examples] Update Astro template for 1.0. (#8357)
To prepare for [Astro 1.0](https://github.com/withastro/astro/releases/tag/astro%401.0.0-rc.1), this PR updates the template (it has [been deployed](https://astro-template.vercel.app/)).
2022-08-09 18:09:38 +00:00
JJ Kasper
5b931afbf3 [next] Ensure $$ in ISR page is handled correctly (#8359)
* Ensure $$ in ISR page is handled correctly

* update test

* Remove duplicate tests

* add comment

* tweak
2022-08-09 11:13:15 -05:00
Chris Barber
15080364b8 [cli] Standardize on --yes instead of --confirm (#8330)
Allow for `--yes` flag consistently throughout CLI commands.

Also adds a deprecation warning for usage of `--confirm`,
since `--yes` is now preferred.
2022-08-08 18:05:22 -07:00
Steven
47e3381c6d Publish Stable
- @vercel/build-utils@5.3.0
 - vercel@27.3.8
 - @vercel/client@12.1.11
 - @vercel/go@2.0.16
 - @vercel/hydrogen@0.0.13
 - @vercel/next@3.1.16
 - @vercel/node@2.5.7
 - @vercel/python@3.1.8
 - @vercel/redwood@1.0.17
 - @vercel/remix@1.0.18
 - @vercel/ruby@1.3.24
 - @vercel/static-build@1.0.17
2022-08-08 19:11:12 -04:00
Sean Massa
33aefdc029 [go] fix function name conflict (#8299)
Co-authored-by: Steven <steven@ceriously.com>
2022-08-08 17:07:02 -05:00
Steven
30fe76a0cf [build-utils] Push back nodejs12.x sunset date (#8355)
This PR pushes back the `nodejs12.x` discontinue date to give more projects a chance to upgrade
2022-08-08 20:46:04 +00:00
Thomas Knickman
97ef88dc28 chore(docs): use relative links for README (#8354)
This allows README links to work locally (within your IDE) without kicking to a browser - and maintains the current functionality elsewhere.
2022-08-08 20:04:09 +00:00
Nathan Rajlich
f679098d7a [cli] Fix "retrieve" typo (#8352) 2022-08-08 19:22:00 +00:00
Steven
2b57e12ad3 [build-utils] Refactor framework prefixed env vars into shared function (#8166)
This consolidates the logic to get the framework-specific prefixed System Environment Variables into a single shared function so each builder can reuse the same function.

- Related to #7009 
- Related to #8306

In the future, this feature could be added to any other missing builders as well as `vc dev` but we'll save that for a new PR.
2022-08-05 00:51:52 +00:00
Nathan Rajlich
c4e94ad03f [cli] Always set BROWSER=none env var for frontend dev server in vc dev (#8326)
Previously `vc dev` would only set this env var for "create-react-app" Framework preset, but other frameworks do the "auto-open browser window" behavior as well, and respect this env var (Docusaurus, specifically).

So always set the `BROWSER=none` env var regardless of which framework preset is selected. Also simplifies the code since this was the only place `frameworkSlug` property was being used.
2022-08-04 21:49:14 +00:00
Steven
32afd67d29 Publish Stable
- @vercel/build-utils@5.2.0
 - vercel@27.3.7
 - @vercel/client@12.1.10
 - @vercel/edge@0.0.3
 - @vercel/frameworks@1.1.3
 - @vercel/fs-detectors@2.0.5
 - @vercel/go@2.0.15
 - @vercel/hydrogen@0.0.12
 - @vercel/next@3.1.15
 - @vercel/node@2.5.6
 - @vercel/python@3.1.7
 - @vercel/redwood@1.0.16
 - @vercel/remix@1.0.17
 - @vercel/routing-utils@2.0.2
 - @vercel/ruby@1.3.23
 - @vercel/static-build@1.0.16
 - @vercel/static-config@2.0.3
2022-08-04 15:11:10 -04:00
Nathan Rajlich
7523e39f18 [tests] Remove leftover debugging .pipe() call in vc build tests (#8317) 2022-08-04 17:51:48 +00:00
Craig Andrews
99f2f2f1ba [build-utils] Add flag to indicate that a custom runtime supports lambda wrappers (#8324)
### 📋 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
2022-08-04 17:23:55 +00:00
Steven
63830d38ce [cli] Fix vc secret rm (#8320)
Fixes a regression from the the refactor in https://github.com/vercel/vercel/pull/8039 that was causing the following error:

```
Error! client.prompt is not a function
```
2022-08-04 13:01:01 -04:00
JJ Kasper
f3428dd212 [next] Remove old middleware test (#8323)
Remove old middleware test
2022-08-04 11:57:43 -05:00
Steven
5eb8b16cbd Publish Stable
- @vercel/build-utils@5.1.1
 - vercel@27.3.6
 - @vercel/client@12.1.9
 - @vercel/edge@0.0.2
 - @vercel/frameworks@1.1.2
 - @vercel/fs-detectors@2.0.4
 - @vercel/go@2.0.14
 - @vercel/hydrogen@0.0.11
 - @vercel/next@3.1.14
 - @vercel/node@2.5.5
 - @vercel/python@3.1.6
 - @vercel/redwood@1.0.15
 - @vercel/remix@1.0.16
 - @vercel/routing-utils@2.0.1
 - @vercel/ruby@1.3.22
 - @vercel/static-build@1.0.15
 - @vercel/static-config@2.0.2
2022-08-04 11:39:58 -04:00
JJ Kasper
226bf02be2 [next] Remove middleware regexp modifying (#8321)
x-ref: [slack thread](https://vercel.slack.com/archives/C03SF65BYSG/p1659626639087909)
2022-08-04 11:39:21 -04:00
Steven
8505872f55 [tests] Update package.json scripts (#8318)
This PR consolidates all the `test` scripts to be the same and removes the `prepublishOnly` script since we always run `build` before publishing to npm.
2022-08-04 11:02:56 -04:00
Steven
7db6436797 Publish Stable
- @vercel/build-utils@5.1.0
 - vercel@27.3.5
 - @vercel/client@12.1.8
 - @vercel/go@2.0.13
 - @vercel/hydrogen@0.0.10
 - @vercel/next@3.1.13
 - @vercel/node@2.5.4
 - @vercel/python@3.1.5
 - @vercel/redwood@1.0.14
 - @vercel/remix@1.0.15
 - @vercel/ruby@1.3.21
 - @vercel/static-build@1.0.14
2022-08-04 08:37:04 -04:00
Chris Barber
e2d76e9c92 [cli] recreate symlinked files instead of copying (#8270)
Instead of copying symlinked files during a build, recreate the symlink.

### 📋 Checklist

#### 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
2022-08-04 04:07:33 +00:00
Nathan Rajlich
337cb21d67 [cli] Fix flaky error in builds.json tests (#8316)
Follow-up to #8305.

The `expect().toOutput()` call was frequently timing out before the
command writes the error to the terminal, causing the test to fail.
So wait for the command to return the exit code before running assertion
on the printed output.
2022-08-03 20:38:45 -07:00
JJ Kasper
6bfff3e9eb [next] Remove un-necessary duplicate i18n route (#8313)
* Remove un-necessary duplicate i18n route

* update
2022-08-03 21:02:19 -05:00
Sean Massa
ac5b259c11 [go] refactor away use of downloaded result (#8291)
Review Notes: Turn off diff whitespace.

### Refactor

This PR refactors away the use of the result of `download` for a couple of reasons:
- Keeping `files` and `downloadedFiles` in sync with the file system (like when we rename a file that starts with a bracket) is easy to forget to do, causing bugs
- Nate says that `files` is something we've wanted to move away from using anyway
- It simplifies the code in a few places
- It was getting in the way of other fixes that need to be made

We do still call `download`, but it should be a no-op most of the time.

As a consequence of these changes, this PR also addresses:
- the builder no longer leaves build artifacts around, in many cases
- the builder can compile files that start with brackets again; routes don't seem to allow this to file to respond to a dynamic segments yet, though

### Next Steps

Upcoming PRs will resolve builder issues:

- bracket endpoints responding to dynamic segments
- exported function name conflict handling
- compilation targets should only apply to the source code build, not the analyze go utility

### Operating In-place

We also now have a cleanup step that clears out created files, created directories, and undoes file renames. This fixes an issue where multiple builds on the same directory would fail. It also cleans the user's project code when they are using `vc build`.

Ideally, we'd probably copy all of the code to a separate location, then freely do filesystem operations there. It's not clear to me if this is preferred for large projects because it would have to happen once per endpoint: 100 Go files would cause 10,000 (100 * 100) file copies (or symlinks). 

It has to copy once per endpoint because we potentially need all of the files around in case any of them are imported. If we had nft-style tracing for Go, we could copy only what we needed.

This gets more complex in the next step where the exported function names will be renamed during compilation to fix the name conflict issue.
2022-08-04 01:11:35 +00:00
Steven
bfc553db11 [tests] Add support for probes.json (#8279)
Previously, our test fixtures used to use a probes prop in `vercel.json` that was removed right before it was deployed.

This PR allows a separate `probes.json` file with the same content to separate the test fixture input from the test probes.

This allows us to test real "zero config" deployments without a `vercel.json` file.
2022-08-03 20:15:50 -04:00
Nathan Rajlich
2b101d4692 [cli] Remove legacy config file migration logic (#8199)
Removes the legacy config file migration logic from back in the days when Zeit CLI supported multiple "providers". This was from a _very_ long time ago and we should expect that anyone who would have migrated at this point, has.
2022-08-03 23:48:17 +00:00
Matthew Stanciu
3316f38cb4 [cli] Strip scheme from vc inspect argument (#8307)
Right now, `vc inspect` fails to find a deployment if you include `http://` before it. But it works with no scheme and with `https://`.

Since it appears no scheme is what the API looks for anyway, and to avoid confusion, this PR strips any included scheme from the `deploymentIdOrHost` argument.

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

- [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
2022-08-03 22:48:44 +00:00
Nathan Rajlich
7837387127 [cli] Print error from Builder in vc build (#8305)
Ensures that errors that are serialized into `builds.json` are also printed to the terminal when running the `vc build` command.

Co-authored-by: Steven <steven@ceriously.com>
2022-08-03 17:32:50 -04:00
Thomas Knickman
f478200dd3 [static-build] set TURBO_CI_VENDOR_ENV_KEY environment variable (#8306)
Set `TURBO_CI_VENDOR_ENV_KEY` to support https://github.com/vercel/turborepo/pull/1622
2022-08-03 17:31:18 -04:00
Matthew Stanciu
c29de8206a [cli] Minor vc env pull diff formatting changes (#8303)
#8170 added a new message at the end of `vc env pull` which shows a delta of what was added, modified, and removed. Some people shared feedback that the yellow chalk color and `~` prefix to indicate modified variables was confusing. This PR instead keeps the prefix as `+` with a green color, but adds a `(Modified)` suffix at the end of every modified variable.

<img width="638" alt="Screen Shot 2022-08-03 at 10 18 28 AM" src="https://user-images.githubusercontent.com/14811170/182670327-5a3df6db-d84d-40a1-956b-9cf159501759.png">

### 📋 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
2022-08-03 20:04:03 +00:00
JJ Kasper
a2df3b5463 [next] Update data route handling for i18n and static routes (#8304)
* Ensure dynamic data route handles missing default locale path

* Ensure static data routes are still handled
2022-08-03 13:40:00 -05:00
Steven
73446e544a [python] Fix error message for discontinued Python 3.6 (#8300)
This fixes the error message when a discontinued version of python (for example, Python 3.6) is detected.

https://vercel.com/changelog/python-3-6-is-being-deprecated
2022-08-03 13:09:03 -04:00
JJ Kasper
21ff4a58c3 [next] Ensure we resolve _next/data dynamic routes correctly with i18n (#8297)
* Ensure we resolve _next/data dynamic routes correctly with i18n

* remove test version
2022-08-03 09:03:15 -05:00
JJ Kasper
2b9eb02b8c [next] Fix _next/data resolving priority for dynamic routes (#8278)
* Fix _next/data resolving priority for dynamic routes

* Apply suggestions from code review

* Ensure we match middleware for _next/data without header

* fix nested middleware case

* Update data routes generating

* Add version lock for non-nested middleware

* use path.posix
2022-08-02 17:41:06 -05:00
JJ Kasper
4ef4722460 [next] Fix priority for notFound preview routes (#7902)
Fix priority for notFound preview routes
2022-08-02 17:09:09 -05:00
Sean Massa
be5308b137 [dev] log middleware errors in vc dev (#8267)
Middleware server setup wasn't logging errors the same way that dev server setup was. This meant that middleware instantiation errors (like invalid config) would cause requests to 500, but no errors to be logged to the console.

This PR updates the invalid config error, makes sure errors in this area are logged out, and adds a test for this behavior.

**It may be appropriate to fail the deploy (and crash `vc dev`) in this case instead, though. What do others think?**

---

During `vc dev` with middleware that has an invalid `config.matcher` value...

Before: You see a 500 response in the browser and no output in the terminal.

After: You see a 500 response in the browser and this output in the terminal:

```
Error! Middleware's `config.matcher` values must start with "/". Received: not-a-valid-matcher
```

---

Related Issue: https://github.com/vercel/edge-functions/issues/220
2022-08-02 20:01:42 +00:00
Steven
08a83a94f8 [docs] Link to Build Output API docs (#8292)
* [docs] Link to Build Output API docs

Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>
2022-08-02 12:35:36 -04:00
407 changed files with 6302 additions and 7516 deletions

View File

@@ -26,12 +26,12 @@ jobs:
fi
- name: Setup Go
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: '1.13.15'
- name: Setup Node
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 14
- name: Install

View File

@@ -25,20 +25,20 @@ jobs:
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
- uses: actions/setup-go@v2
- uses: actions/setup-go@v3
with:
go-version: '1.13.15'
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 100
- run: git --version
- run: git fetch origin main --depth=100
- run: git fetch origin ${{ github.ref }} --depth=100
- run: git diff origin/main...HEAD --name-only
- run: yarn install --network-timeout 1000000
- run: yarn install --network-timeout 1000000 --frozen-lockfile
- run: yarn run build
- run: yarn test-integration-cli
env:

View File

@@ -25,20 +25,20 @@ jobs:
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
- uses: actions/setup-go@v2
- uses: actions/setup-go@v3
with:
go-version: '1.13.15'
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 100
- run: git --version
- run: git fetch origin main --depth=100
- run: git fetch origin ${{ github.ref }} --depth=100
- run: git diff origin/main...HEAD --name-only
- run: yarn install --network-timeout 1000000
- run: yarn install --network-timeout 1000000 --frozen-lockfile
- run: yarn run build
- run: yarn run lint
if: matrix.os == 'ubuntu-latest' && matrix.node == 14 # only run lint once

View File

@@ -19,14 +19,14 @@ jobs:
tests: ${{ steps['set-tests'].outputs['tests'] }}
dplUrl: ${{ steps.waitForTarball.outputs.url }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- run: git --version
- run: git fetch origin main
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- run: yarn install --network-timeout 1000000
- run: yarn install --network-timeout 1000000 --frozen-lockfile
- id: set-tests
run: |
TESTS_ARRAY=$(node utils/chunk-tests.js $SCRIPT_NAME)
@@ -58,13 +58,13 @@ jobs:
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 2
- uses: actions/setup-go@v2
- uses: actions/setup-go@v3
with:
go-version: '1.13.15'
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'

View File

@@ -1,7 +1,9 @@
# Runtime Developer Reference
The following page is a reference for how to create a Runtime by implementing
the Runtime API interface.
the Runtime API interface. It's a way to add support for a new programming language to Vercel.
> Note: If you're the author of a web framework, please use the [Build Output API](https://vercel.com/docs/build-output-api/v3) instead to make your framework compatible with Vercel.
A Runtime is an npm module that implements the following interface:
@@ -113,7 +115,8 @@ export async function shouldServe(options: ShouldServeOptions) {
}
```
If this function is not defined, Vercel CLI will use the [default implementation](https://github.com/vercel/vercel/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
If this function is not defined, Vercel CLI will use the [default
implementation](https://github.com/vercel/vercel/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
### `startDevServer()`
@@ -187,7 +190,8 @@ If you need to share state between those steps, use the filesystem.
### Directory and Cache Lifecycle
When a new build is created, we pre-populate the `workPath` supplied to `analyze` with the results of the `prepareCache` step of the previous build.
When a new build is created, we pre-populate the `workPath` supplied to `analyze` with the results of the `prepareCache` step of the
previous build.
The `analyze` step can modify that directory, and it will not be re-created when it's supplied to `build` and `prepareCache`.
@@ -195,6 +199,77 @@ The `analyze` step can modify that directory, and it will not be re-created when
The env and secrets specified by the user as `build.env` are passed to the Runtime process. This means you can access user env via `process.env` in Node.js.
### Supporting Large Environment
We provide the ability to support more than 4KB of environment (up to 64KB) by way of
a Lambda runtime wrapper that is added to every Lambda function we create. These are
supported by many of the existing Lambda runtimes, but custom runtimes may require
additional work.
The following Lambda runtime families have built-in support for the runtime wrapper:
- `nodejs`
- `python` (>= 3.8)
- `ruby`
- `java11`
- `java8.al2` (not `java8`)
- `dotnetcore`
If a custom runtime is based on one of these Lambda runtimes, large environment
support will be available without further configuration. Custom runtimes based on
other Lambda runtimes, including those that provide the runtime via `provided` and
`provided.al2`, must implement runtime wrapper support and indicate it via the
`supportsWrapper` flag when calling [`createLambda`](#createlambda()).
To add support for runtime wrappers to a custom runtime, first check the value of the
`AWS_LAMBDA_EXEC_WRAPPER` environment variable in the bootstrap script. Its value is
the path to the wrapper executable.
The wrapper must be passed the path to the runtime as well as any parameters that the
runtime requires. This is most easily done in a small `bootstrap` script.
In this simple `bash` example, the runtime is called directly if
`AWS_LAMBDA_EXEC_WRAPPER` has no value, otherwise the wrapper is called with the
runtime command as parameters.
```bash
#!/bin/bash
exec $AWS_LAMBDA_EXEC_WRAPPER path/to/runtime param1 param2
```
If the `bootstrap` file is not a launcher script, but the entrypoint of the runtime
itself, replace the bootstrap process with the wrapper. Pass the path and parameters
of the executing file, ensuring the `AWS_LAMBDA_EXEC_WRAPPER` environment variable is
set to blank.
This `bash` example uses `exec` to replace the running bootstrap process with the
wrapper, passing its own path and parameters.
```bash
#!/bin/bash
if [[ -n $AWS_LAMBDA_EXEC_WRAPPER ]]
__WRAPPER=$AWS_LAMBDA_EXEC_WRAPPER
AWS_LAMBDA_EXEC_WRAPPER=""
exec $__WRAPPER "$0" "${@}"
fi
# start the actual runtime functionality
```
Note that unsetting the variable may not have the desired effect due to the way
Lambda spawns runtime processes. It is better to explicitly set it to blank.
The best way to replace the existing bootstrap process is with the
[`execve`](https://www.man7.org/linux/man-pages/man2/execve.2.html) syscall.
This is achieved by using `exec` in `bash` to replace the running process with the wrapper,
maintaining the same PID and environment.
Once support for runtime wrappers is included, ensure `supportsWrapper` is set to
`true` in the call to [`createLambda`](#createlambda()). This will inform the build
process to enable large environment support for this runtime.
### Utilities as peerDependencies
When you publish your Runtime to npm, make sure to not specify `@vercel/build-utils` (as seen below in the API definitions) as a dependency, but rather as part of `peerDependencies`.
@@ -302,6 +377,7 @@ This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refere
- `handler: String` path to handler file and (optionally) a function name it exports
- `runtime: LambdaRuntime` the name of the lambda runtime
- `environment: Object` key-value map of handler-related (aside of those passed by user) environment variables
- `supportsWrapper: Boolean` set to true to indicate that Lambda runtime wrappers are supported by this runtime
### `LambdaRuntime`

View File

@@ -35,6 +35,6 @@ For details on how to use Vercel, check out our [documentation](https://vercel.c
## Contributing
- [Code of Conduct](https://github.com/vercel/vercel/blob/main/.github/CODE_OF_CONDUCT.md)
- [Contributing Guidelines](https://github.com/vercel/vercel/blob/main/.github/CONTRIBUTING.md)
- [MIT License](https://github.com/vercel/vercel/blob/main/LICENSE)
- [Code of Conduct](./.github/CODE_OF_CONDUCT.md)
- [Contributing Guidelines](./.github/CONTRIBUTING.md)
- [MIT License](./LICENSE)

View File

@@ -18,3 +18,4 @@ pnpm-debug.log*
# macOS-specific files
.DS_Store
.vercel

View File

@@ -1 +0,0 @@
README.md

View File

@@ -1,10 +1,16 @@
# Welcome to [Astro](https://astro.build)
# Astro
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/starter)
This directory is a brief example of an [Astro](https://astro.build/) site that can be deployed to Vercel with zero configuration.
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
## Deploy Your Own
## 🚀 Project Structure
Deploy your own Astro project with Vercel.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/vercel/tree/main/examples/astro&template=astro)
_Live Example: https://astro-template.vercel.app_
## Project Structure
Inside of your Astro project, you'll see the following folders and files:
@@ -26,17 +32,15 @@ There's nothing special about `src/components/`, but that's where we like to put
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 Commands
## Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :---------------- | :------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:3000` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
## 👀 Want to learn more?
Feel free to check [our documentation](https://github.com/withastro/astro) or jump into our [Discord server](https://astro.build/chat).
| Command | Action |
| :--------------------- | :------------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:3000` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` |
| `npm run astro --help` | Get help using the Astro CLI |

View File

@@ -1,14 +1,13 @@
{
"name": "@example/basics",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview"
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"astro": "^1.0.0-beta.20"
"astro": "^1.0.0-rc.8"
}
}

View File

@@ -0,0 +1,76 @@
---
export interface Props {
title: string;
body: string;
href: string;
}
const { href, title, body } = Astro.props as Props;
---
<li class="link-card">
<a href={href}>
<h2>
{title}
<span>&rarr;</span>
</h2>
<p>
{body}
</p>
</a>
</li>
<style>
:root {
--link-gradient: linear-gradient(45deg, #4f39fa, #da62c4 30%, var(--color-border) 60%);
}
.link-card {
list-style: none;
display: flex;
padding: 0.15rem;
background-image: var(--link-gradient);
background-size: 400%;
border-radius: 0.5rem;
background-position: 100%;
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.link-card > a {
width: 100%;
text-decoration: none;
line-height: 1.4;
padding: 1em 1.3em;
border-radius: 0.35rem;
color: var(--text-color);
background-color: white;
opacity: 0.8;
}
h2 {
margin: 0;
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
p {
margin-top: 0.75rem;
margin-bottom: 0;
}
h2 span {
display: inline-block;
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.link-card:is(:hover, :focus-within) {
background-position: 0;
}
.link-card:is(:hover, :focus-within) h2 {
color: #4f39fa;
}
.link-card:is(:hover, :focus-within) h2 span {
will-change: transform;
transform: translateX(2px);
}
</style>

View File

@@ -1,55 +0,0 @@
---
export interface Props {
title: string;
}
const { title } = Astro.props as Props;
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>
<style>
:root {
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
--font-size-lg: clamp(1.2rem, 0.7vw + 1.2rem, 1.5rem);
--font-size-xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
--color-text: hsl(12, 5%, 4%);
--color-bg: hsl(10, 21%, 95%);
}
html {
font-family: system-ui, sans-serif;
font-size: var(--font-size-base);
color: var(--color-text);
background-color: var(--color-bg);
}
body {
margin: 0;
}
:global(h1) {
font-size: var(--font-size-xl);
}
:global(h2) {
font-size: var(--font-size-lg);
}
:global(code) {
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}
</style>

1
examples/astro/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="astro/client" />

View File

@@ -0,0 +1,56 @@
---
export interface Props {
title: string;
}
const { title } = Astro.props as Props;
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>
<style>
:root {
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
--font-size-lg: clamp(1.2rem, 0.7vw + 1.2rem, 1.5rem);
--font-size-xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
--color-text: hsl(12, 5%, 4%);
--color-bg: hsl(10, 21%, 95%);
--color-border: hsl(17, 24%, 90%);
}
html {
font-family: system-ui, sans-serif;
font-size: var(--font-size-base);
color: var(--color-text);
background-color: var(--color-bg);
}
body {
margin: 0;
}
:global(h1) {
font-size: var(--font-size-xl);
}
:global(h2) {
font-size: var(--font-size-lg);
}
:global(code) {
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}
</style>

View File

@@ -1,81 +1,52 @@
---
import Layout from '../components/Layout.astro';
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
---
<Layout title="Welcome to Astro.">
<main>
<h1>Welcome to <span class="text-gradient">Astro</span></h1>
<p class="instructions"><strong>Your first mission:</strong> tweak this message to try our hot module reloading. Check the <code>src/pages</code> directory!</p>
<p class="instructions">
Check out the <code>src/pages</code> directory to get started.<br />
<strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above.
</p>
<ul role="list" class="link-card-grid">
<li class="link-card">
<a href="https://astro.build/integrations/">
<h2>Integrations <span>&rarr;</span></h2>
<p>Add component frameworks, Tailwind, Partytown, and more!</p>
</a>
</li>
<li class="link-card">
<a href="https://astro.build/themes/">
<h2>Themes <span>&rarr;</span></h2>
<p>Explore a galaxy of community-built starters.</p>
</a>
</li>
<li class="link-card">
<a href="https://docs.astro.build/">
<h2>Docs <span>&rarr;</span></h2>
<p>Learn our complete feature set and explore the API.</p>
</a>
</li>
<li class="link-card">
<a href="https://astro.build/chat/">
<h2>Chat <span>&rarr;</span></h2>
<p>
Ask, contribute, and have fun on our community Discord
<svg
class="heart"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
width="16"
height="16"
fill="currentColor"
>
<title>heart</title>
<path d="M256 448l-30.164-27.211C118.718 322.442 48 258.61 48 179.095 48 114.221 97.918 64 162.4 64c36.399 0 70.717 16.742 93.6 43.947C278.882 80.742 313.199 64 349.6 64 414.082 64 464 114.221 464 179.095c0 79.516-70.719 143.348-177.836 241.694L256 448z" />
</svg>
</p>
</a>
</li>
<Card
href="https://docs.astro.build/"
title="Documentation"
body="Learn how Astro works and explore the official API docs."
/>
<Card
href="https://astro.build/integrations/"
title="Integrations"
body="Supercharge your project with new frameworks and libraries."
/>
<Card
href="https://astro.build/themes/"
title="Themes"
body="Explore a galaxy of community-built starter themes."
/>
<Card
href="https://astro.build/chat/"
title="Chat"
body="Come say hi to our amazing Discord community. ❤️"
/>
</ul>
</main>
</Layout>
<style>
:root {
--color-border: hsl(17, 24%, 90%);
--astro-gradient: linear-gradient(0deg,#4F39FA, #DA62C4);
--link-gradient: linear-gradient(45deg, #4F39FA, #DA62C4 30%, var(--color-border) 60%);
--night-sky-gradient: linear-gradient(0deg, #392362 -33%, #431f69 10%, #30216b 50%, #1f1638 100%);
--astro-gradient: linear-gradient(0deg, #4f39fa, #da62c4);
}
h2 {
margin: 0;
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
h2 span {
display: inline-block;
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
code {
font-size: 0.875em;
border: 0.1em solid var(--color-border);
border-radius: 4px;
padding: 0.15em 0.25em;
h1 {
margin: 2rem 0;
}
main {
margin: auto;
padding: 1em;
padding: 1em;
max-width: 60ch;
}
@@ -83,7 +54,7 @@ import Layout from '../components/Layout.astro';
font-weight: 900;
background-image: var(--astro-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
-webkit-text-fill-color: transparent;
background-size: 100% 200%;
background-position-y: 100%;
border-radius: 0.4rem;
@@ -91,7 +62,8 @@ import Layout from '../components/Layout.astro';
}
@keyframes pulse {
0%, 100% {
0%,
100% {
background-position-y: 0%;
}
50% {
@@ -100,75 +72,25 @@ import Layout from '../components/Layout.astro';
}
.instructions {
line-height: 1.8;
margin-bottom: 2rem;
background-image: var(--night-sky-gradient);
padding: 1.5rem;
line-height: 1.6;
margin: 1rem 0;
background: #4f39fa;
padding: 1rem;
border-radius: 0.4rem;
color: var(--color-bg);
}
.instructions code {
font-size: 0.875em;
border: 0.1em solid var(--color-border);
border-radius: 4px;
padding: 0.15em 0.25em;
}
.link-card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
gap: 1rem;
padding: 0;
}
.link-card {
list-style: none;
display: flex;
padding: 0.15rem;
background-image: var(--link-gradient);
background-size: 400%;
border-radius: 0.5rem;
background-position: 100%;
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.link-card > a {
width: 100%;
text-decoration: none;
line-height: 1.4;
padding: 1em 1.3em;
border-radius: 0.35rem;
color: var(--text-color);
background-color: white;
opacity: 0.8;
}
.link-card:is(:hover, :focus-within) {
background-position: 0;
}
.link-card:is(:hover, :focus-within) h2 {
color: #4F39FA;
}
.link-card:is(:hover, :focus-within) h2 span {
transform: translateX(2px);
}
.heart {
display: inline-block;
color: #DA62C4;
animation: heartbeat 3s ease-in-out infinite;
}
@keyframes heartbeat {
0%,
50%,
100% {
transform: scale(1);
}
5% {
transform: scale(1.125);
}
10% {
transform: scale(1.05);
}
15% {
transform: scale(1.25);
}
}
</style>

View File

@@ -1,15 +0,0 @@
{
"compilerOptions": {
// Enable top-level await, and other modern ESM features.
"target": "ESNext",
"module": "ESNext",
// Enable node-style module resolution, for things like npm package imports.
"moduleResolution": "node",
// Enable JSON imports.
"resolveJsonModule": true,
// Enable stricter transpilation for better output.
"isolatedModules": true,
// Add type definitions for our Vite runtime.
"types": ["vite/client"]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "5.0.8",
"version": "5.3.1",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -14,8 +14,7 @@
"build": "node build",
"test": "jest --env node --verbose --runInBand --bail",
"test-unit": "yarn test test/unit.*test.*",
"test-integration-once": "yarn test test/integration.test.ts",
"prepublishOnly": "node build"
"test-integration-once": "yarn test test/integration.test.ts"
},
"devDependencies": {
"@iarna/toml": "2.2.3",

View File

@@ -27,9 +27,7 @@ async function prepareSymlinkTarget(
}
if (file.type === 'FileRef' || file.type === 'FileBlob') {
const targetPathBufferPromise = await streamToBuffer(
await file.toStreamAsync()
);
const targetPathBufferPromise = streamToBuffer(await file.toStreamAsync());
const [targetPathBuffer] = await Promise.all([
targetPathBufferPromise,
mkdirPromise,
@@ -42,9 +40,15 @@ async function prepareSymlinkTarget(
);
}
async function downloadFile(file: File, fsPath: string): Promise<FileFsRef> {
export async function downloadFile(
file: File,
fsPath: string
): Promise<FileFsRef> {
const { mode } = file;
// If the source is a symlink, try to create it instead of copying the file.
// Note: creating symlinks on Windows requires admin priviliges or symlinks
// enabled in the group policy. We may want to improve the error message.
if (isSymbolicLink(mode)) {
const target = await prepareSymlinkTarget(file, fsPath);

View File

@@ -8,17 +8,13 @@ import FileFsRef from '../file-fs-ref';
export type GlobOptions = vanillaGlob_.IOptions;
interface FsFiles {
[filePath: string]: FileFsRef;
}
const vanillaGlob = promisify(vanillaGlob_);
export default async function glob(
pattern: string,
opts: GlobOptions | string,
mountpoint?: string
): Promise<FsFiles> {
): Promise<Record<string, FileFsRef>> {
let options: GlobOptions;
if (typeof opts === 'string') {
options = { cwd: opts };
@@ -36,10 +32,11 @@ export default async function glob(
throw new Error(`basePath/cwd must be an absolute path (${options.cwd})`);
}
const results: FsFiles = {};
const results: Record<string, FileFsRef> = {};
const statCache: Record<string, Stats> = {};
options.symlinks = {};
options.statCache = {};
options.statCache = statCache;
options.stat = true;
options.dot = true;
@@ -47,7 +44,7 @@ export default async function glob(
for (const relativePath of files) {
const fsPath = normalizePath(path.join(options.cwd, relativePath));
let stat: Stats = options.statCache[fsPath] as Stats;
let stat = statCache[fsPath];
assert(
stat,
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`

View File

@@ -10,7 +10,7 @@ const allOptions = [
major: 12,
range: '12.x',
runtime: 'nodejs12.x',
discontinueDate: new Date('2022-08-09'),
discontinueDate: new Date('2022-10-01'),
},
{
major: 10,

View File

@@ -0,0 +1,32 @@
type Envs = { [key: string]: string | undefined };
/**
* Get the framework-specific prefixed System Environment Variables.
* See https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables
* @param envPrefix - Prefix, typically from `@vercel/frameworks`
* @param envs - Environment Variables, typically from `process.env`
*/
export function getPrefixedEnvVars({
envPrefix,
envs,
}: {
envPrefix: string | undefined;
envs: Envs;
}): Envs {
const vercelSystemEnvPrefix = 'VERCEL_';
const newEnvs: Envs = {};
if (envPrefix && envs.VERCEL_URL) {
Object.keys(envs)
.filter(key => key.startsWith(vercelSystemEnvPrefix))
.forEach(key => {
const newKey = `${envPrefix}${key}`;
if (!(newKey in envs)) {
newEnvs[newKey] = envs[key];
}
});
// Tell turbo to exclude all Vercel System Env Vars
// See https://github.com/vercel/turborepo/pull/1622
newEnvs.TURBO_CI_VENDOR_ENV_KEY = `${envPrefix}${vercelSystemEnvPrefix}`;
}
return newEnvs;
}

View File

@@ -4,7 +4,11 @@ import FileRef from './file-ref';
import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
import { NodejsLambda } from './nodejs-lambda';
import { Prerender } from './prerender';
import download, { DownloadedFiles, isSymbolicLink } from './fs/download';
import download, {
downloadFile,
DownloadedFiles,
isSymbolicLink,
} from './fs/download';
import getWriteableDirectory from './fs/get-writable-directory';
import glob, { GlobOptions } from './fs/glob';
import rename from './fs/rename';
@@ -36,6 +40,7 @@ import streamToBuffer from './fs/stream-to-buffer';
import debug from './debug';
import getIgnoreFilter from './get-ignore-filter';
import { getPlatformEnv } from './get-platform-env';
import { getPrefixedEnvVars } from './get-prefixed-env-vars';
export {
FileBlob,
@@ -46,6 +51,7 @@ export {
createLambda,
Prerender,
download,
downloadFile,
DownloadedFiles,
getWriteableDirectory,
glob,
@@ -71,6 +77,7 @@ export {
getDiscontinuedNodeVersions,
getSpawnOptions,
getPlatformEnv,
getPrefixedEnvVars,
streamToBuffer,
debug,
isSymbolicLink,

View File

@@ -22,6 +22,7 @@ export interface LambdaOptionsBase {
allowQuery?: string[];
regions?: string[];
supportsMultiPayloads?: boolean;
supportsWrapper?: boolean;
}
export interface LambdaOptionsWithFiles extends LambdaOptionsBase {
@@ -58,6 +59,7 @@ export class Lambda {
*/
zipBuffer?: Buffer;
supportsMultiPayloads?: boolean;
supportsWrapper?: boolean;
constructor(opts: LambdaOptions) {
const {
@@ -69,6 +71,7 @@ export class Lambda {
allowQuery,
regions,
supportsMultiPayloads,
supportsWrapper,
} = opts;
if ('files' in opts) {
assert(typeof opts.files === 'object', '"files" must be an object');
@@ -103,6 +106,13 @@ export class Lambda {
);
}
if (supportsWrapper !== undefined) {
assert(
typeof supportsWrapper === 'boolean',
'"supportsWrapper" is not a boolean'
);
}
if (regions !== undefined) {
assert(Array.isArray(regions), '"regions" is not an Array');
assert(
@@ -121,6 +131,7 @@ export class Lambda {
this.regions = regions;
this.zipBuffer = 'zipBuffer' in opts ? opts.zipBuffer : undefined;
this.supportsMultiPayloads = supportsMultiPayloads;
this.supportsWrapper = supportsWrapper;
}
async createZip(): Promise<Buffer> {

View File

@@ -335,6 +335,7 @@ export interface ProjectSettings {
directoryListing?: boolean;
gitForkProtection?: boolean;
commandForIgnoringBuildStep?: string | null;
skipGitConnectDuringLink?: boolean;
}
export interface BuilderV2 {

View File

@@ -0,0 +1,87 @@
import { getPrefixedEnvVars } from '../src';
describe('Test `getPrefixedEnvVars()`', () => {
const cases: Array<{
name: string;
args: Parameters<typeof getPrefixedEnvVars>[0];
want: ReturnType<typeof getPrefixedEnvVars>;
}> = [
{
name: 'should work with NEXT_PUBLIC_',
args: {
envPrefix: 'NEXT_PUBLIC_',
envs: {
VERCEL: '1',
VERCEL_URL: 'example.vercel.sh',
USER_ENV_VAR_NOT_VERCEL: 'example.com',
FOO: 'bar',
},
},
want: {
NEXT_PUBLIC_VERCEL_URL: 'example.vercel.sh',
TURBO_CI_VENDOR_ENV_KEY: 'NEXT_PUBLIC_VERCEL_',
},
},
{
name: 'should work with GATSBY_',
args: {
envPrefix: 'GATSBY_',
envs: {
USER_ENV_VAR_NOT_VERCEL: 'example.com',
FOO: 'bar',
VERCEL_URL: 'example.vercel.sh',
VERCEL_ENV: 'production',
VERCEL_REGION: 'iad1',
VERCEL_GIT_COMMIT_AUTHOR_LOGIN: 'rauchg',
},
},
want: {
GATSBY_VERCEL_URL: 'example.vercel.sh',
GATSBY_VERCEL_ENV: 'production',
GATSBY_VERCEL_REGION: 'iad1',
GATSBY_VERCEL_GIT_COMMIT_AUTHOR_LOGIN: 'rauchg',
TURBO_CI_VENDOR_ENV_KEY: 'GATSBY_VERCEL_',
},
},
{
name: 'should not return anything if no system env vars detected',
args: {
envPrefix: 'GATSBY_',
envs: {
USER_ENV_VAR_NOT_VERCEL: 'example.com',
FOO: 'bar',
BLARG_VERCEL_THING: 'fake',
},
},
want: {},
},
{
name: 'should not return anything if envPrefix is empty string',
args: {
envPrefix: '',
envs: {
VERCEL: '1',
VERCEL_URL: 'example.vercel.sh',
},
},
want: {},
},
{
name: 'should not return anything if envPrefix is undefined',
args: {
envPrefix: undefined,
envs: {
VERCEL: '1',
VERCEL_URL: 'example.vercel.sh',
},
},
want: {},
},
];
for (const { name, args, want } of cases) {
it(name, () => {
expect(getPrefixedEnvVars(args)).toEqual(want);
});
}
});

View File

@@ -394,7 +394,7 @@ it('should get latest node version', async () => {
it('should throw for discontinued versions', async () => {
// Mock a future date so that Node 8 and 10 become discontinued
const realDateNow = Date.now.bind(global.Date);
global.Date.now = () => new Date('2022-09-01').getTime();
global.Date.now = () => new Date('2022-10-15').getTime();
expect(getSupportedNodeVersion('8.10.x', false)).rejects.toThrow();
expect(getSupportedNodeVersion('8.10.x', true)).rejects.toThrow();
@@ -436,8 +436,8 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
expect(warningMessages).toStrictEqual([
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-01 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-01 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
]);
global.Date.now = realDateNow;

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "27.3.4",
"version": "28.0.0",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -16,7 +16,6 @@
"test-unit": "yarn test test/unit/",
"test-integration-cli": "rimraf test/fixtures/integration && ava test/integration.js --serial --fail-fast --verbose",
"test-integration-dev": "yarn test test/dev/",
"prepublishOnly": "yarn build",
"coverage": "codecov",
"build": "ts-node ./scripts/build.ts",
"dev": "ts-node ./src/index.ts"
@@ -42,16 +41,16 @@
"node": ">= 14"
},
"dependencies": {
"@vercel/build-utils": "5.0.8",
"@vercel/go": "2.0.12",
"@vercel/hydrogen": "0.0.9",
"@vercel/next": "3.1.12",
"@vercel/node": "2.5.3",
"@vercel/python": "3.1.4",
"@vercel/redwood": "1.0.13",
"@vercel/remix": "1.0.14",
"@vercel/ruby": "1.3.20",
"@vercel/static-build": "1.0.13",
"@vercel/build-utils": "5.3.1",
"@vercel/go": "2.1.1",
"@vercel/hydrogen": "0.0.14",
"@vercel/next": "3.1.18",
"@vercel/node": "2.5.8",
"@vercel/python": "3.1.9",
"@vercel/redwood": "1.0.18",
"@vercel/remix": "1.0.19",
"@vercel/ruby": "1.3.25",
"@vercel/static-build": "1.0.18",
"update-notifier": "5.1.0"
},
"devDependencies": {
@@ -97,9 +96,9 @@
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.1.7",
"@vercel/frameworks": "1.1.1",
"@vercel/fs-detectors": "2.0.3",
"@vercel/client": "12.2.0",
"@vercel/frameworks": "1.1.3",
"@vercel/fs-detectors": "2.0.5",
"@vercel/fun": "1.0.4",
"@vercel/ncc": "0.24.0",
"@zeit/source-map-support": "0.6.2",
@@ -119,7 +118,6 @@
"chokidar": "3.3.1",
"codecov": "3.8.2",
"cpy": "7.2.0",
"credit-card": "3.0.1",
"date-fns": "1.29.0",
"debug": "3.1.0",
"dot": "1.1.3",

View File

@@ -37,6 +37,7 @@ const help = () => {
)} Login token
-S, --scope Set a custom scope
-N, --next Show next page of results
-y, --yes Skip the confirmation prompt when removing an alias
${chalk.dim('Examples:')}

View File

@@ -1,170 +0,0 @@
import ansiEscapes from 'ansi-escapes';
import chalk from 'chalk';
import ccValidator from 'credit-card';
import textInput from '../../util/input/text';
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 error from '../../util/output/error';
const expDateMiddleware = data => data;
export default async function ({ creditCards, clear = false, contextName }) {
const state = {
error: undefined,
cardGroupLabel: `> ${chalk.bold(
`Enter your card details for ${chalk.bold(contextName)}`
)}`,
name: {
label: 'Full Name'.padEnd(12),
placeholder: 'John Appleseed',
validateValue: data => data.trim().length > 0,
},
cardNumber: {
label: 'Number'.padEnd(12),
mask: 'cc',
placeholder: '#### #### #### ####',
validateKeypress: (data, value) => /\d/.test(data) && value.length < 19,
validateValue: data => {
data = data.replace(/ /g, '');
const type = ccValidator.determineCardType(data);
if (!type) {
return false;
}
return ccValidator.isValidCardNumber(data, type);
},
},
ccv: {
label: 'CCV'.padEnd(12),
mask: 'ccv',
placeholder: '###',
validateValue: data => {
const brand = state.cardNumber.brand.toLowerCase();
return ccValidator.doesCvvMatchType(data, brand);
},
},
expDate: {
label: 'Exp. Date'.padEnd(12),
mask: 'expDate',
placeholder: 'mm / yyyy',
middleware: expDateMiddleware,
validateValue: data => !ccValidator.isExpired(...data.split(' / ')),
},
};
async function render() {
for (const key in state) {
if (!Object.hasOwnProperty.call(state, key)) {
continue;
}
const piece = state[key];
if (typeof piece === 'string') {
console.log(piece);
} else if (typeof piece === 'object') {
let result;
try {
/* eslint-disable no-await-in-loop */
result = await textInput({
label: `- ${piece.label}`,
initialValue: piece.initialValue || piece.value,
placeholder: piece.placeholder,
mask: piece.mask,
validateKeypress: piece.validateKeypress,
validateValue: piece.validateValue,
autoComplete: piece.autoComplete,
});
piece.value = result;
if (key === 'cardNumber') {
let brand = cardBrands[ccValidator.determineCardType(result)];
piece.brand = brand;
if (brand === 'American Express') {
state.ccv.placeholder = '#'.repeat(4);
} else {
state.ccv.placeholder = '#'.repeat(3);
}
brand = chalk.cyan(`[${brand}]`);
const masked = chalk.gray('#### '.repeat(3)) + result.split(' ')[3];
process.stdout.write(
`${chalk.cyan(chars.tick)} ${piece.label}${masked} ${brand}\n`
);
} else if (key === 'ccv') {
process.stdout.write(
`${chalk.cyan(chars.tick)} ${piece.label}${'*'.repeat(
result.length
)}\n`
);
} else if (key === 'expDate') {
let text = result.split(' / ');
text = text[0] + chalk.gray(' / ') + text[1];
process.stdout.write(
`${chalk.cyan(chars.tick)} ${piece.label}${text}\n`
);
} else {
process.stdout.write(
`${chalk.cyan(chars.tick)} ${piece.label}${result}\n`
);
}
} catch (err) {
if (err.message === 'USER_ABORT') {
process.exit(1);
} else {
console.error(error(err));
}
}
}
}
console.log(''); // New line
const stopSpinner = wait(process.stderr, 'Saving card');
try {
const res = await creditCards.add({
name: state.name.value,
cardNumber: state.cardNumber.value,
ccv: state.ccv.value,
expDate: state.expDate.value,
});
stopSpinner();
if (clear) {
const linesToClear = state.error ? 15 : 14;
process.stdout.write(ansiEscapes.eraseLines(linesToClear));
}
console.log(
success(
`${state.cardNumber.brand || state.cardNumber.card.brand} ending in ${
res.last4 || res.card.last4
} was added to ${chalk.bold(contextName)}`
)
);
} catch (err) {
stopSpinner();
const linesToClear = state.error ? 15 : 14;
process.stdout.write(ansiEscapes.eraseLines(linesToClear));
state.error = `${chalk.red('> Error!')} ${
err.message
} Please make sure the info is correct`;
await render();
}
}
try {
await render();
} catch (err) {
console.erorr(err);
}
}

View File

@@ -1,353 +0,0 @@
import chalk from 'chalk';
import ms from 'ms';
import plural from 'pluralize';
import { error } from '../../util/error';
import NowCreditCards from '../../util/credit-cards';
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';
import info from '../../util/output/info';
import logo from '../../util/output/logo';
import addBilling from './add';
import exit from '../../util/exit';
import getScope from '../../util/get-scope.ts';
import { getPkgName } from '../../util/pkg-name.ts';
import getArgs from '../../util/get-args.ts';
import handleError from '../../util/handle-error.ts';
const help = () => {
console.log(`
${chalk.bold(`${logo} ${getPkgName()} billing`)} [options] <command>
${chalk.dim('Options:')}
ls Show all of your credit cards
add Add a new credit card
rm [id] Remove a credit card
set-default [id] Make a credit card your default one
${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]
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN'
)} Login token
-S, --scope Set a custom scope
${chalk.dim('Examples:')}
${chalk.gray('')} Add a new credit card (interactively)
${chalk.cyan(`$ ${getPkgName()} billing add`)}
`);
};
let argv;
let subcommand;
export default async client => {
try {
argv = getArgs(client.argv.slice(2), {});
} catch (error) {
handleError(error);
return 1;
}
argv._ = argv._.slice(1);
subcommand = argv._[0];
if (argv['--help'] || !subcommand) {
help();
return 2;
}
const {
output,
config: { currentTeam },
} = client;
const start = new Date();
const creditCards = new NowCreditCards({
client,
currentTeam,
});
let contextName = null;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const args = argv._.slice(1);
switch (subcommand) {
case 'ls':
case 'list': {
let cards;
try {
cards = await creditCards.ls();
} catch (err) {
console.error(error(err.message));
return 1;
}
const text = cards.sources
.map(source => {
const _default =
source.id === cards.defaultSource
? ` ${chalk.bold('(default)')}`
: '';
const id = `${chalk.gray('-')} ${chalk.cyan(
`ID: ${source.id}`
)}${_default}`;
const number = `${chalk.gray('#### ').repeat(3)}${
source.last4 || source.card.last4
}`;
return [
id,
indent(source.name || source.owner.name, 2),
indent(`${source.brand || source.card.brand} ${number}`, 2),
].join('\n');
})
.join('\n\n');
const elapsed = ms(new Date() - start);
console.log(
`> ${plural(
'card',
cards.sources.length,
true
)} found under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`
);
if (text) {
console.log(`\n${text}\n`);
}
break;
}
case 'set-default': {
if (args.length > 1) {
console.error(error('Invalid number of arguments'));
return 1;
}
const start = new Date();
let cards;
try {
cards = await creditCards.ls();
} catch (err) {
console.error(error(err.message));
return 1;
}
if (cards.sources.length === 0) {
console.error(error('You have no credit cards to choose from'));
return 0;
}
let cardId = args[0];
if (cardId === undefined) {
const elapsed = ms(new Date() - start);
const message = `Selecting a new default payment card for ${chalk.bold(
contextName
)} ${chalk.gray(`[${elapsed}]`)}`;
const choices = buildInquirerChoices(cards);
cardId = await listInput(client, {
message,
choices,
separator: true,
abort: 'end',
});
}
// Check if the provided cardId (in case the user
// typed `vercel billing set-default <some-id>`) is valid
if (cardId) {
const label = `Are you sure that you to set this card as the default?`;
const confirmation = await promptBool(label, {
...client,
trailing: '\n',
});
if (!confirmation) {
console.log(info('Aborted'));
break;
}
const start = new Date();
await creditCards.setDefault(cardId);
const card = cards.sources.find(card => card.id === cardId);
const elapsed = ms(new Date() - start);
console.log(
success(
`${card.brand || card.card.brand} ending in ${
card.last4 || card.card.last4
} is now the default ${chalk.gray(`[${elapsed}]`)}`
)
);
} else {
console.log('No changes made');
}
break;
}
case 'rm':
case 'remove': {
if (args.length > 1) {
console.error(error('Invalid number of arguments'));
return 1;
}
const start = new Date();
let cards;
try {
cards = await creditCards.ls();
} catch (err) {
console.error(error(err.message));
return 1;
}
if (cards.sources.length === 0) {
console.error(
error(
`You have no credit cards to choose from to delete under ${chalk.bold(
contextName
)}`
)
);
return 0;
}
let cardId = args[0];
if (cardId === undefined) {
const elapsed = ms(new Date() - start);
const message = `Selecting a card to ${chalk.underline(
'remove'
)} under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`;
const choices = buildInquirerChoices(cards);
cardId = await listInput(client, {
message,
choices,
separator: true,
abort: 'start',
});
}
// Shoud check if the provided cardId (in case the user
// typed `vercel billing rm <some-id>`) is valid
if (cardId) {
const label = `Are you sure that you want to remove this card?`;
const confirmation = await promptBool(label, client);
if (!confirmation) {
console.log('Aborted');
break;
}
const start = new Date();
await creditCards.rm(cardId);
const deletedCard = cards.sources.find(card => card.id === cardId);
const remainingCards = cards.sources.filter(card => card.id !== cardId);
let text = `${deletedCard.brand || deletedCard.card.brand} ending in ${
deletedCard.last4 || deletedCard.card.last4
} was deleted`;
// ${chalk.gray(`[${elapsed}]`)}
if (cardId === cards.defaultSource) {
if (remainingCards.length === 0) {
// The user deleted the last card in their account
text += `\n${chalk.yellow('Warning!')} You have no default card`;
} else {
// We can't guess the current default card let's ask the API
const cards = await creditCards.ls();
const newDefaultCard = cards.sources.find(
card => card.id === cards.defaultCardId
);
text += `\n${
newDefaultCard.brand || newDefaultCard.card.brand
} ending in ${
newDefaultCard.last4 || newDefaultCard.card.last4
} in now default for ${chalk.bold(contextName)}`;
}
}
const elapsed = ms(new Date() - start);
text += ` ${chalk.gray(`[${elapsed}]`)}`;
console.log(success(text));
} else {
console.log('No changes made');
}
break;
}
case 'add': {
await addBilling({
creditCards,
contextName,
});
break;
}
default:
console.error(
error('Please specify a valid subcommand: ls | add | rm | set-default')
);
help();
return 1;
}
// This is required, otherwise we get those weird zlib errors
return exit(0);
};
// Builds a `choices` object that can be passesd to inquirer.prompt()
function buildInquirerChoices(cards) {
return cards.sources.map(source => {
const _default =
source.id === cards.defaultSource ? ` ${chalk.bold('(default)')}` : '';
const id = `${chalk.cyan(`ID: ${source.id}`)}${_default}`;
const number = `${chalk.gray('#### ').repeat(3)}${
source.last4 || source.card.last4
}`;
const str = [
id,
indent(source.name || source.owner.name, 2),
indent(`${source.brand || source.card.brand} ${number}`, 2),
].join('\n');
return {
name: str, // Will be displayed by Inquirer
value: source.id, // Will be used to identify the answer
short: source.id, // Will be displayed after the users answers
};
});
}

View File

@@ -91,7 +91,7 @@ const help = () => {
--output [path] Directory where built assets should be written to
--prod Build a production deployment
-d, --debug Debug mode [off]
-y, --yes Skip the confirmation prompt
-y, --yes Skip the confirmation prompt about pulling environment variables and project settings when not found locally
${chalk.dim('Examples:')}
@@ -157,7 +157,7 @@ export default async function main(client: Client): Promise<number> {
client.output.print(
`No Project Settings found locally. Run ${cli.getCommandName(
'pull --yes'
)} to retreive them.`
)} to retrieve them.`
);
return 1;
}
@@ -469,6 +469,8 @@ async function doBuild(
)
);
} catch (err: any) {
output.prettyError(err);
const writeConfigJsonPromise = fs.writeJSON(
join(outputDir, 'config.json'),
{ version: 3 },

View File

@@ -9,7 +9,6 @@ export const help = () => `
${chalk.dim('Basic')}
billing Manages the account payment methods
deploy [path] Performs a deployment ${chalk.bold(
'(default)'
)}
@@ -73,7 +72,7 @@ export const help = () => `
-S, --scope Set a custom scope
--regions Set default regions to enable the deployment on
--prod Create a production deployment
-c, --confirm Confirm default options and skip questions
-y, --yes Skip questions when setting up new project using default scope and settings
${chalk.dim('Examples:')}

View File

@@ -3,7 +3,11 @@ import fs from 'fs-extra';
import bytes from 'bytes';
import chalk from 'chalk';
import { join, resolve, basename } from 'path';
import { fileNameSymbol, VercelConfig } from '@vercel/client';
import {
fileNameSymbol,
VALID_ARCHIVE_FORMATS,
VercelConfig,
} from '@vercel/client';
import code from '../../util/output/code';
import highlight from '../../util/output/highlight';
import { readLocalConfig } from '../../util/config/files';
@@ -66,10 +70,11 @@ import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
import parseTarget from '../../util/deploy/parse-target';
import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
import { createGitMeta } from '../../util/create-git-meta';
import { isValidArchive } from '../../util/deploy/validate-archive-format';
import { parseEnv } from '../../util/parse-env';
import { errorToString, isErrnoException, isError } from '../../util/is-error';
export default async (client: Client) => {
export default async (client: Client): Promise<number> => {
const { output } = client;
let argv = null;
@@ -87,20 +92,28 @@ export default async (client: Client) => {
'--regions': String,
'--prebuilt': Boolean,
'--prod': Boolean,
'--confirm': Boolean,
'--archive': String,
'--yes': Boolean,
'-f': '--force',
'-p': '--public',
'-e': '--env',
'-b': '--build-env',
'-m': '--meta',
'-c': '--confirm',
'-y': '--yes',
// deprecated
'--name': String,
'-n': '--name',
'--no-clipboard': Boolean,
'--target': String,
'--confirm': Boolean,
'-c': '--confirm',
});
if ('--confirm' in argv) {
output.warn('`--confirm` is deprecated, please use `--yes` instead');
argv['--yes'] = argv['--confirm'];
}
} catch (error) {
handleError(error);
return 1;
@@ -173,7 +186,7 @@ export default async (client: Client) => {
}
const { path } = pathValidation;
const autoConfirm = argv['--confirm'];
const autoConfirm = argv['--yes'];
// deprecate --name
if (argv['--name']) {
@@ -254,6 +267,12 @@ export default async (client: Client) => {
}
}
const archive = argv['--archive'];
if (typeof archive === 'string' && !isValidArchive(archive)) {
output.error(`Format must be one of: ${VALID_ARCHIVE_FORMATS.join(', ')}`);
return 1;
}
// retrieve `project` and `org` from .vercel
const link = await getLinkedProject(client, path);
@@ -538,7 +557,8 @@ export default async (client: Client) => {
createArgs,
org,
!project,
path
path,
archive
);
if (deployment.code === 'missing_project_settings') {
@@ -911,4 +931,6 @@ const printDeploymentStatus = async (
) + newline;
output.print(message + link);
}
return 0;
};

View File

@@ -17,7 +17,7 @@ import { OUTPUT_DIR } from '../../util/build/write-build-result';
type Options = {
'--listen': string;
'--confirm': boolean;
'--yes': boolean;
};
export default async function dev(
@@ -38,7 +38,7 @@ export default async function dev(
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
link = await setupAndLink(client, cwd, {
autoConfirm: opts['--confirm'],
autoConfirm: opts['--yes'],
successEmoji: 'link',
setupMsg: 'Set up and develop',
});
@@ -54,14 +54,13 @@ export default async function dev(
client.output.error(
`Command ${getCommandName(
'dev'
)} requires confirmation. Use option ${param('--confirm')} to confirm.`
)} requires confirmation. Use option ${param('--yes')} to confirm.`
);
}
return link.exitCode;
}
let devCommand: string | undefined;
let frameworkSlug: string | undefined;
let projectSettings: ProjectSettings | undefined;
let projectEnvs: ProjectEnvVariable[] = [];
let systemEnvValues: string[] = [];
@@ -77,10 +76,6 @@ export default async function dev(
const framework = frameworks.find(f => f.slug === project.framework);
if (framework) {
if (framework.slug) {
frameworkSlug = framework.slug;
}
const defaults = framework.settings.devCommand.value;
if (defaults) {
devCommand = defaults;
@@ -120,7 +115,6 @@ export default async function dev(
const devServer = new DevServer(cwd, {
output,
devCommand,
frameworkSlug,
projectSettings,
projectEnvs,
systemEnvValues,

View File

@@ -33,7 +33,7 @@ const help = () => {
-d, --debug Debug mode [off]
-l, --listen [uri] Specify a URI endpoint on which to listen [0.0.0.0:3000]
-t, --token [token] Specify an Authorization Token
--confirm Skip questions and use defaults when setting up a new project
-y, --yes Skip questions when setting up new project using default scope and settings
${chalk.dim('Examples:')}
@@ -74,14 +74,22 @@ export default async function main(client: Client) {
argv = getArgs(client.argv.slice(2), {
'--listen': String,
'-l': '--listen',
'--confirm': Boolean,
'--yes': Boolean,
'-y': '--yes',
// Deprecated
'--port': Number,
'-p': '--port',
'--confirm': Boolean,
'-c': '--confirm',
});
args = getSubcommand(argv._.slice(1), COMMAND_CONFIG).args;
if ('--confirm' in argv) {
output.warn('`--confirm` is deprecated, please use `--yes` instead');
argv['--yes'] = argv['--confirm'];
}
if ('--port' in argv) {
output.warn('`--port` is deprecated, please use `--listen` instead');
argv['--listen'] = String(argv['--port']);

View File

@@ -116,9 +116,7 @@ export default async function buy(
if (buyResult instanceof ERRORS.SourceNotFound) {
output.error(
`Could not purchase domain. Please add a payment method using ${getCommandName(
`billing add`
)}.`
`Could not purchase domain. Please add a payment method using the dashboard.`
);
return 1;
}

View File

@@ -45,6 +45,7 @@ const help = () => {
)} Login token
-S, --scope Set a custom scope
-N, --next Show next page of results
-y, --yes Skip the confirmation prompt when removing a domain
${chalk.dim('Examples:')}
@@ -92,6 +93,7 @@ export default async function main(client: Client) {
'--force': Boolean,
'--next': Number,
'-N': '--next',
'-y': '--yes',
});
} catch (error) {
handleError(error);

View File

@@ -111,9 +111,7 @@ export default async function transferIn(
if (transferInResult instanceof ERRORS.SourceNotFound) {
output.error(
`Could not purchase domain. Please add a payment method using ${getCommandName(
`billing add`
)}.`
`Could not purchase domain. Please add a payment method using the dashboard.`
);
return 1;
}

View File

@@ -42,6 +42,7 @@ const help = () => {
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN'
)} Login token
-y, --yes Skip the confirmation prompt when overwriting env file on pull or removing an env variable
${chalk.dim('Examples:')}

View File

@@ -117,7 +117,12 @@ export default async function pull(
if (exists) {
oldEnv = await createEnvObject(fullPath, output);
if (oldEnv) {
deltaString = buildDeltaString(oldEnv, records);
// Removes any double quotes from `records`, if they exist
// We need this because double quotes are stripped from the local .env file,
// but `records` is already in the form of a JSON object that doesn't filter
// double quotes.
const newEnv = JSON.parse(JSON.stringify(records).replace(/\\"/g, ''));
deltaString = buildDeltaString(oldEnv, newEnv);
}
}
@@ -130,6 +135,12 @@ export default async function pull(
await outputFile(fullPath, contents, 'utf8');
if (deltaString) {
output.print('\n' + deltaString);
} else if (oldEnv && exists) {
output.log('No changes found.');
}
output.print(
`${prependEmoji(
`${exists ? 'Updated' : 'Created'} ${chalk.bold(
@@ -139,13 +150,6 @@ export default async function pull(
)}\n`
);
output.print('\n');
if (deltaString) {
output.print(deltaString);
} else if (oldEnv && exists) {
output.log('No changes found.');
}
return 0;
}

View File

@@ -1,5 +1,4 @@
import chalk from 'chalk';
import inquirer from 'inquirer';
import { Project } from '../../types';
import { Output } from '../../util/output';
import confirm from '../../util/input/confirm';
@@ -45,7 +44,7 @@ export default async function rm(
let [envName, envTarget, envGitBranch] = args;
while (!envName) {
const { inputName } = await inquirer.prompt({
const { inputName } = await client.prompt({
type: 'input',
name: 'inputName',
message: `Whats the name of the variable?`,
@@ -87,7 +86,7 @@ export default async function rm(
}
while (envs.length > 1) {
const { id } = await inquirer.prompt({
const { id } = await client.prompt({
name: 'id',
type: 'list',
message: `Remove ${envName} from which Environments?`,

View File

@@ -1,21 +1,54 @@
import { Dictionary } from '@vercel/client';
import chalk from 'chalk';
import { join } from 'path';
import { Org, Project } from '../../types';
import { Org, Project, ProjectLinkData } from '../../types';
import Client from '../../util/client';
import { parseGitConfig, pluckRemoteUrls } from '../../util/create-git-meta';
import confirm from '../../util/input/confirm';
import list, { ListChoice } from '../../util/input/list';
import { Output } from '../../util/output';
import link from '../../util/output/link';
import { getCommandName } from '../../util/pkg-name';
import {
connectGitProvider,
disconnectGitProvider,
formatProvider,
RepoInfo,
parseRepoUrl,
} from '../../util/projects/connect-git-provider';
printRemoteUrls,
} from '../../util/git/connect-git-provider';
import validatePaths from '../../util/validate-paths';
interface GitRepoCheckParams {
client: Client;
confirm: boolean;
gitProviderLink?: ProjectLinkData;
org: Org;
gitOrg: string;
project: Project;
provider: string;
repo: string;
repoPath: string;
}
interface ConnectArgParams {
client: Client;
org: Org;
project: Project;
confirm: boolean;
repoInfo: RepoInfo;
}
interface ConnectGitArgParams extends ConnectArgParams {
gitConfig: Dictionary<any>;
}
interface PromptConnectArgParams {
client: Client;
yes: boolean;
repoInfo: RepoInfo;
remoteUrls: Dictionary<string>;
}
export default async function connect(
client: Client,
argv: any,
@@ -24,9 +57,10 @@ export default async function connect(
org: Org | undefined
) {
const { output } = client;
const confirm = Boolean(argv['--confirm']);
const confirm = Boolean(argv['--yes']);
const repoArg = argv._[1];
if (args.length !== 0) {
if (args.length > 1) {
output.error(
`Invalid number of arguments. Usage: ${chalk.cyan(
`${getCommandName('project connect')}`
@@ -36,7 +70,7 @@ export default async function connect(
}
if (!project || !org) {
output.error(
`Can't find \`org\` or \`project\`. Make sure your current directory is linked to a Vercel projet by running ${getCommandName(
`Can't find \`org\` or \`project\`. Make sure your current directory is linked to a Vercel project by running ${getCommandName(
'link'
)}.`
);
@@ -57,9 +91,38 @@ export default async function connect(
// get project from .git
const gitConfigPath = join(path, '.git/config');
const gitConfig = await parseGitConfig(gitConfigPath, output);
if (repoArg) {
// parse repo arg
const parsedUrlArg = parseRepoUrl(repoArg);
if (!parsedUrlArg) {
output.error(
`Failed to parse URL "${repoArg}". Please ensure the URL is valid.`
);
return 1;
}
if (gitConfig) {
return await connectArgWithLocalGit({
client,
org,
project,
confirm,
gitConfig,
repoInfo: parsedUrlArg,
});
}
return await connectArg({
client,
confirm,
org,
project,
repoInfo: repoArg,
});
}
if (!gitConfig) {
output.error(
`No local git repo found. Run ${chalk.cyan(
`No local Git repository found. Run ${chalk.cyan(
'`git clone <url>`'
)} to clone a remote Git repository first.`
);
@@ -78,7 +141,7 @@ export default async function connect(
let remoteUrl: string;
if (Object.keys(remoteUrls).length > 1) {
output.log(`Found multiple remote URLs.`);
output.log('Found multiple remote URLs.');
remoteUrl = await selectRemoteUrl(client, remoteUrls);
} else {
// If only one is found, get it — usually "origin"
@@ -92,8 +155,8 @@ export default async function connect(
output.log(`Connecting Git remote: ${link(remoteUrl)}`);
const parsedUrl = parseRepoUrl(remoteUrl);
if (!parsedUrl) {
const repoInfo = parseRepoUrl(remoteUrl);
if (!repoInfo) {
output.error(
`Failed to parse Git repo data from the following remote URL: ${link(
remoteUrl
@@ -101,10 +164,176 @@ export default async function connect(
);
return 1;
}
const { provider, org: gitOrg, repo } = parsedUrl;
const { provider, org: gitOrg, repo } = repoInfo;
const repoPath = `${gitOrg}/${repo}`;
let connectedRepoPath;
const checkAndConnect = await checkExistsAndConnect({
client,
confirm,
org,
project,
gitProviderLink,
provider,
repoPath,
gitOrg,
repo,
});
if (typeof checkAndConnect === 'number') {
return checkAndConnect;
}
output.log(
`Connected ${formatProvider(provider)} repository ${chalk.cyan(repoPath)}!`
);
return 0;
}
async function connectArg({
client,
confirm,
org,
project,
repoInfo,
}: ConnectArgParams) {
const { url: repoUrl } = repoInfo;
client.output.log(`Connecting Git remote: ${link(repoUrl)}`);
const parsedRepoArg = parseRepoUrl(repoUrl);
if (!parsedRepoArg) {
client.output.error(
`Failed to parse URL "${repoUrl}". Please ensure the URL is valid.`
);
return 1;
}
const { provider, org: gitOrg, repo } = parsedRepoArg;
const repoPath = `${gitOrg}/${repo}`;
const connect = await checkExistsAndConnect({
client,
confirm,
org,
project,
gitProviderLink: project.link,
provider,
repoPath,
gitOrg,
repo,
});
if (typeof connect === 'number') {
return connect;
}
client.output.log(
`Connected ${formatProvider(provider)} repository ${chalk.cyan(repoPath)}!`
);
return 0;
}
async function connectArgWithLocalGit({
client,
org,
project,
confirm,
gitConfig,
repoInfo,
}: ConnectGitArgParams) {
const remoteUrls = pluckRemoteUrls(gitConfig);
if (remoteUrls) {
const shouldConnect = await promptConnectArg({
client,
yes: confirm,
repoInfo,
remoteUrls,
});
if (!shouldConnect) {
return 1;
}
if (shouldConnect) {
const { provider, org: gitOrg, repo, url: repoUrl } = repoInfo;
const repoPath = `${gitOrg}/${repo}`;
client.output.log(`Connecting Git remote: ${link(repoUrl)}`);
const connect = await checkExistsAndConnect({
client,
confirm,
org,
project,
gitProviderLink: project.link,
provider,
repoPath,
gitOrg,
repo,
});
if (typeof connect === 'number') {
return connect;
}
client.output.log(
`Connected ${formatProvider(provider)} repository ${chalk.cyan(
repoPath
)}!`
);
}
return 0;
}
return await connectArg({ client, confirm, org, project, repoInfo });
}
async function promptConnectArg({
client,
yes,
repoInfo: repoInfoFromArg,
remoteUrls,
}: PromptConnectArgParams) {
if (Object.keys(remoteUrls).length > 1) {
client.output.log(
'Found multiple Git repositories in your local Git config:'
);
printRemoteUrls(client.output, remoteUrls);
} else {
const url = Object.values(remoteUrls)[0];
const repoInfoFromGitConfig = parseRepoUrl(url);
if (!repoInfoFromGitConfig) {
client.output.error(
`Failed to parse URL "${url}". Please ensure the URL is valid.`
);
return false;
}
if (
JSON.stringify(repoInfoFromGitConfig) === JSON.stringify(repoInfoFromArg)
) {
return true;
}
client.output.log(
`Found a repository in your local Git Config: ${chalk.cyan(
Object.values(remoteUrls)[0]
)}`
);
}
let shouldConnect = yes;
if (!shouldConnect) {
const { url: repoUrlFromArg } = repoInfoFromArg;
shouldConnect = await confirm(
client,
`Do you still want to connect ${link(repoUrlFromArg)}?`,
false
);
if (!shouldConnect) {
client.output.log('Aborted. Repo not connected.');
}
}
return shouldConnect;
}
async function checkExistsAndConnect({
client,
confirm,
org,
project,
gitProviderLink,
provider,
repoPath,
gitOrg,
repo,
}: GitRepoCheckParams) {
if (!gitProviderLink) {
const connect = await connectGitProvider(
client,
@@ -120,14 +349,14 @@ export default async function connect(
const connectedProvider = gitProviderLink.type;
const connectedOrg = gitProviderLink.org;
const connectedRepo = gitProviderLink.repo;
connectedRepoPath = `${connectedOrg}/${connectedRepo}`;
const connectedRepoPath = `${connectedOrg}/${connectedRepo}`;
const isSameRepo =
connectedProvider === provider &&
connectedOrg === gitOrg &&
connectedRepo === repo;
if (isSameRepo) {
output.log(
client.output.log(
`${chalk.cyan(connectedRepoPath)} is already connected to your project.`
);
return 1;
@@ -135,8 +364,8 @@ export default async function connect(
const shouldReplaceRepo = await confirmRepoConnect(
client,
output,
confirm,
connectedProvider,
connectedRepoPath
);
if (!shouldReplaceRepo) {
@@ -155,31 +384,27 @@ export default async function connect(
return connect;
}
}
output.log(
`Connected ${formatProvider(provider)} repository ${chalk.cyan(repoPath)}!`
);
return 0;
}
async function confirmRepoConnect(
client: Client,
output: Output,
yes: boolean,
connectedProvider: string,
connectedRepoPath: string
) {
let shouldReplaceProject = yes;
if (!shouldReplaceProject) {
shouldReplaceProject = await confirm(
client,
`Looks like you already have a repository connected: ${chalk.cyan(
`Looks like you already have a ${formatProvider(
connectedProvider
)} repository connected: ${chalk.cyan(
connectedRepoPath
)}. Do you want to replace it?`,
true
);
if (!shouldReplaceProject) {
output.log(`Aborted. Repo not connected.`);
client.output.log('Aborted. Repo not connected.');
}
}
return shouldReplaceProject;
@@ -187,7 +412,7 @@ async function confirmRepoConnect(
async function selectRemoteUrl(
client: Client,
remoteUrls: { [key: string]: string }
remoteUrls: Dictionary<string>
): Promise<string> {
let choices: ListChoice[] = [];
for (const [urlKey, urlValue] of Object.entries(remoteUrls)) {

View File

@@ -3,7 +3,7 @@ import { Org, Project } from '../../types';
import Client from '../../util/client';
import confirm from '../../util/input/confirm';
import { getCommandName } from '../../util/pkg-name';
import { disconnectGitProvider } from '../../util/projects/connect-git-provider';
import { disconnectGitProvider } from '../../util/git/connect-git-provider';
export default async function disconnect(
client: Client,

View File

@@ -16,21 +16,30 @@ const help = () => {
${chalk.dim('Commands:')}
connect Connect your Git config "origin" remote as a Git provider to your project
disconnect Disconnect the Git provider repository from your project
connect [url] Connect your Vercel Project to your Git repository or provide the remote URL to your Git repository
disconnect Disconnect the Git provider repository from your project
${chalk.dim('Options:')}
-h, --help Output usage information
-h, --help Output usage information
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN'
)} Login token
)} Login token
-y, --yes Skip questions when setting up new project using default scope and settings
${chalk.dim('Examples:')}
${chalk.gray('')} Connect a Git provider repository
${chalk.gray(
''
)} Connect your Vercel Project to your Git repository defined in your local .git config
${chalk.cyan(`$ ${getPkgName()} git connect`)}
${chalk.gray('')} Connect your Vercel Project to a Git repository using the remote URL
${chalk.cyan(
`$ ${getPkgName()} git connect https://github.com/user/repo.git`
)}
${chalk.gray('')} Disconnect the Git provider repository
@@ -49,7 +58,12 @@ export default async function main(client: Client) {
try {
argv = getArgs(client.argv.slice(2), {
'--confirm': Boolean,
'--yes': Boolean,
'-y': '--yes',
// deprecated
'-c': '--yes',
'--confirm': '--yes',
});
} catch (error) {
handleError(error);
@@ -64,7 +78,7 @@ export default async function main(client: Client) {
argv._ = argv._.slice(1);
subcommand = argv._[0];
const args = argv._.slice(1);
const confirm = Boolean(argv['--confirm']);
const confirm = Boolean(argv['--yes']);
const { output } = client;
let paths = [process.cwd()];

View File

@@ -1,10 +1,8 @@
export default new Map([
['alias', 'alias'],
['aliases', 'alias'],
['billing', 'billing'],
['bisect', 'bisect'],
['build', 'build'],
['cc', 'billing'],
['cert', 'certs'],
['certs', 'certs'],
['deploy', 'deploy'],
@@ -36,6 +34,5 @@ export default new Map([
['switch', 'teams'],
['team', 'teams'],
['teams', 'teams'],
['update', 'update'],
['whoami', 'whoami'],
]);

View File

@@ -15,6 +15,7 @@ import { Build } from '../types';
import title from 'title';
import { isErrnoException } from '../util/is-error';
import { isAPIError } from '../util/errors-ts';
import { URL } from 'url';
const help = () => {
console.log(`
@@ -66,7 +67,7 @@ export default async function main(client: Client) {
const { print, log, error } = client.output;
// extract the first parameter
const [, deploymentIdOrHost] = argv._;
let [, deploymentIdOrHost] = argv._;
if (argv._.length !== 2) {
error(`${getCommandName('inspect <url>')} expects exactly one argument`);
@@ -90,12 +91,16 @@ export default async function main(client: Client) {
throw err;
}
// resolve the deployment, since we might have been given an alias
const depFetchStart = Date.now();
try {
deploymentIdOrHost = new URL(deploymentIdOrHost).hostname;
} catch {}
client.output.spinner(
`Fetching deployment "${deploymentIdOrHost}" in ${chalk.bold(contextName)}`
);
// resolve the deployment, since we might have been given an alias
try {
deployment = await getDeployment(client, deploymentIdOrHost);
} catch (err: unknown) {

View File

@@ -27,7 +27,7 @@ const help = () => {
-p ${chalk.bold.underline('NAME')}, --project=${chalk.bold.underline(
'NAME'
)} Project name
--confirm Confirm default options and skip questions
-y, --yes Skip questions when setting up new project using default scope and settings
${chalk.dim('Examples:')}
@@ -39,7 +39,7 @@ const help = () => {
''
)} Link current directory with default options and skip questions
${chalk.cyan(`$ ${getPkgName()} link --confirm`)}
${chalk.cyan(`$ ${getPkgName()} link --yes`)}
${chalk.gray('')} Link a specific directory to a Vercel Project
@@ -49,9 +49,14 @@ const help = () => {
export default async function main(client: Client) {
const argv = getArgs(client.argv.slice(2), {
'--confirm': Boolean,
'--yes': Boolean,
'-y': '--yes',
'--project': String,
'-p': '--project',
// deprecated
'--confirm': Boolean,
'-c': '--confirm',
});
if (argv['--help']) {
@@ -59,10 +64,15 @@ export default async function main(client: Client) {
return 2;
}
if ('--confirm' in argv) {
client.output.warn('`--confirm` is deprecated, please use `--yes` instead');
argv['--yes'] = argv['--confirm'];
}
const cwd = argv._[1] || process.cwd();
const link = await setupAndLink(client, cwd, {
forceDelete: true,
autoConfirm: argv['--confirm'],
autoConfirm: argv['--yes'],
projectName: argv['--project'],
successEmoji: 'success',
setupMsg: 'Set up',
@@ -73,7 +83,7 @@ export default async function main(client: Client) {
client.output.error(
`Command ${getCommandName(
'link'
)} requires confirmation. Use option ${param('--confirm')} to confirm.`
)} requires confirmation. Use option ${param('--yes')} to confirm.`
);
}
return link.exitCode;

View File

@@ -1,10 +1,10 @@
import chalk from 'chalk';
import ms from 'ms';
import table from 'text-table';
import title from 'title';
import Now from '../util';
import getArgs from '../util/get-args';
import { handleError } from '../util/error';
import cmd from '../util/output/cmd';
import logo from '../util/output/logo';
import elapsed from '../util/output/elapsed';
import strlen from '../util/strlen';
@@ -14,12 +14,13 @@ import { isValidName } from '../util/is-valid-name';
import getCommandFlags from '../util/get-command-flags';
import { getPkgName, getCommandName } from '../util/pkg-name';
import Client from '../util/client';
import { Deployment } from '../types';
import { Deployment } from '@vercel/client';
import validatePaths from '../util/validate-paths';
import { getLinkedProject } from '../util/projects/link';
import { ensureLink } from '../util/ensure-link';
import getScope from '../util/get-scope';
import { isAPIError } from '../util/errors-ts';
import { isErrnoException } from '../util/is-error';
const help = () => {
console.log(`
@@ -35,7 +36,7 @@ const help = () => {
'DIR'
)} Path to the global ${'`.vercel`'} directory
-d, --debug Debug mode [off]
--confirm Skip the confirmation prompt
-y, --yes Skip questions when setting up new project using default scope and settings
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN'
)} Login token
@@ -43,6 +44,7 @@ const help = () => {
-m, --meta Filter deployments by metadata (e.g.: ${chalk.dim(
'`-m KEY=value`'
)}). Can appear many times.
--prod Filter for production URLs
-N, --next Show next page of results
${chalk.dim('Examples:')}
@@ -78,7 +80,13 @@ export default async function main(client: Client) {
'-m': '--meta',
'--next': Number,
'-N': '--next',
'--prod': Boolean,
'--yes': Boolean,
'-y': '--yes',
// deprecated
'--confirm': Boolean,
'-c': '--confirm',
});
} catch (err) {
handleError(err);
@@ -87,6 +95,11 @@ export default async function main(client: Client) {
const { output, config } = client;
if ('--confirm' in argv) {
output.warn('`--confirm` is deprecated, please use `--yes` instead');
argv['--yes'] = argv['--confirm'];
}
const { print, log, error, note, debug, spinner } = output;
if (argv._.length > 2) {
@@ -99,10 +112,10 @@ export default async function main(client: Client) {
return 2;
}
const yes = argv['--confirm'] || false;
const yes = !!argv['--yes'];
const prod = argv['--prod'] || false;
const meta = parseMeta(argv['--meta']);
const { includeScheme } = config;
let paths = [process.cwd()];
const pathValidation = await validatePaths(client, paths);
@@ -136,11 +149,25 @@ export default async function main(client: Client) {
if (typeof linkedProject === 'number') {
return linkedProject;
}
link.org = linkedProject.org;
link.project = linkedProject.project;
org = linkedProject.org;
project = linkedProject.project;
app = project.name;
}
let { contextName, team } = await getScope(client);
let contextName;
let team;
try {
({ contextName, team } = await getScope(client));
} catch (err: unknown) {
if (
isErrnoException(err) &&
(err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED')
) {
error(err.message);
return 1;
}
}
// If user passed in a custom scope, update the current team & context name
if (argv['--scope']) {
@@ -198,11 +225,15 @@ export default async function main(client: Client) {
debug('Fetching deployments');
const response = await now.list(app, {
version: 6,
meta,
nextTimestamp,
});
const response = await now.list(
app,
{
version: 6,
meta,
nextTimestamp,
},
prod
);
let {
deployments,
@@ -212,6 +243,14 @@ export default async function main(client: Client) {
pagination: { count: number; next: number };
} = response;
let showUsername = false;
for (const deployment of deployments) {
const username = deployment.creator?.username;
if (username !== contextName) {
showUsername = true;
}
}
if (app && !deployments.length) {
debug(
'No deployments: attempting to find deployment that matches supplied app name'
@@ -247,37 +286,36 @@ export default async function main(client: Client) {
}
log(
`Deployments under ${chalk.bold(contextName)} ${elapsed(
Date.now() - start
)}`
`${prod ? `Production deployments` : `Deployments`} for ${chalk.bold(
app
)} under ${chalk.bold(contextName)} ${elapsed(Date.now() - start)}`
);
// information to help the user find other deployments or instances
if (app == null) {
log(
`To list more deployments for a project run ${cmd(
`${getCommandName('ls [project]')}`
)}`
);
}
log(
`To list more deployments for a project, run ${getCommandName(
'ls [project]'
)}.`
);
print('\n');
const headers = ['Age', 'Deployment', 'Status', 'Duration'];
if (showUsername) headers.push('Username');
client.output.print(
`${table(
[
['project', 'latest deployment', 'state', 'age', 'username'].map(
header => chalk.dim(header)
),
headers.map(header => chalk.bold(chalk.cyan(header))),
...deployments
.sort(sortRecent())
.map(dep => [
[
getProjectName(dep),
chalk.bold((includeScheme ? 'https://' : '') + dep.url),
stateString(dep.state),
chalk.gray(ms(Date.now() - dep.createdAt)),
dep.creator.username,
`https://${dep.url}`,
stateString(dep.state || ''),
chalk.gray(getDeploymentDuration(dep)),
showUsername ? chalk.gray(dep.creator?.username) : '',
],
])
// flatten since the previous step returns a nested
@@ -290,8 +328,8 @@ export default async function main(client: Client) {
),
],
{
align: ['l', 'l', 'r', 'l', 'l'],
hsep: ' '.repeat(4),
align: ['l', 'l', 'l', 'l', 'l'],
hsep: ' '.repeat(5),
stringLength: strlen,
}
).replace(/^/gm, ' ')}\n\n`
@@ -307,27 +345,36 @@ export default async function main(client: Client) {
}
}
function getProjectName(d: Deployment) {
// We group both file and files into a single project
if (d.name === 'file') {
return 'files';
export function getDeploymentDuration(dep: Deployment): string {
if (!dep || !dep.ready || !dep.buildingAt) {
return '?';
}
return d.name;
const duration = ms(dep.ready - dep.buildingAt);
if (duration === '0ms') {
return '--';
}
return duration;
}
// renders the state string
export function stateString(s: string) {
const CIRCLE = '● ';
// make `s` title case
const sTitle = title(s);
switch (s) {
case 'INITIALIZING':
return chalk.yellow(s);
case 'BUILDING':
case 'DEPLOYING':
case 'ANALYZING':
return chalk.yellow(CIRCLE) + sTitle;
case 'ERROR':
return chalk.red(s);
return chalk.red(CIRCLE) + sTitle;
case 'READY':
return s;
return chalk.green(CIRCLE) + sTitle;
case 'QUEUED':
return chalk.white(CIRCLE) + sTitle;
case 'CANCELED':
return chalk.gray(sTitle);
default:
return chalk.gray('UNKNOWN');
}

View File

@@ -1,6 +1,7 @@
import chalk from 'chalk';
import ms from 'ms';
import table from 'text-table';
import { Project } from '../../types';
import Client from '../../util/client';
import getCommandFlags from '../../util/get-command-flags';
import { getCommandName } from '../../util/pkg-name';
@@ -34,10 +35,10 @@ export default async function list(
}
const {
projects: list,
projects: projectList,
pagination,
}: {
projects: [{ name: string; updatedAt: number }];
projects: Project[];
pagination: { count: number; next: number };
} = await client.fetch(projectsUrl, {
method: 'GET',
@@ -48,39 +49,48 @@ export default async function list(
const elapsed = ms(Date.now() - start);
output.log(
`${list.length > 0 ? 'Projects' : 'No projects'} found under ${chalk.bold(
contextName
)} ${chalk.gray(`[${elapsed}]`)}`
`${
projectList.length > 0 ? 'Projects' : 'No projects'
} found under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`
);
if (list.length > 0) {
const cur = Date.now();
const header = [['', 'name', 'updated'].map(title => chalk.dim(title))];
const out = table(
header.concat(
list.map(secret => [
'',
chalk.bold(secret.name),
chalk.gray(`${ms(cur - secret.updatedAt)} ago`),
])
),
if (projectList.length > 0) {
const tablePrint = table(
[
['Project Name', 'Latest Production URL', 'Updated'].map(header =>
chalk.bold(chalk.cyan(header))
),
...projectList
.map(project => [
[
chalk.bold(project.name),
getLatestProdUrl(project),
chalk.gray(ms(Date.now() - project.updatedAt)),
],
])
.flat(),
],
{
align: ['l', 'l', 'l'],
hsep: ' '.repeat(2),
hsep: ' '.repeat(3),
stringLength: strlen,
}
);
if (out) {
output.print(`\n${out}\n\n`);
}
).replace(/^/gm, ' ');
output.print(`\n${tablePrint}\n\n`);
if (pagination && pagination.count === 20) {
const flags = getCommandFlags(argv, ['_', '--next', '-N', '-d', '-y']);
const nextCmd = `project ls${flags} --next ${pagination.next}`;
output.log(`To display the next page run ${getCommandName(nextCmd)}`);
output.log(`To display the next page, run ${getCommandName(nextCmd)}`);
}
}
return 0;
}
function getLatestProdUrl(project: Project): string {
const alias =
project.alias?.filter(al => al.deployment)?.[0]?.domain ||
project.alias?.[0]?.domain;
if (alias) return 'https://' + alias;
return '--';
}

View File

@@ -38,7 +38,7 @@ const help = () => {
)} Path to the global ${'`.vercel`'} directory
-d, --debug Debug mode [off]
--environment [environment] Deployment environment [development]
-y, --yes Skip the confirmation prompt
-y, --yes Skip questions when setting up new project using default scope and settings
${chalk.dim('Examples:')}

View File

@@ -226,7 +226,8 @@ async function run({ output, contextName, currentTeam, client }) {
if (theSecret) {
const yes =
argv.yes || (await readConfirmation(output, theSecret, contextName));
argv.yes ||
(await readConfirmation(client, output, theSecret, contextName));
if (!yes) {
output.print(`Aborted. Secret not deleted.\n`);
return 0;
@@ -353,7 +354,7 @@ async function run({ output, contextName, currentTeam, client }) {
return 2;
}
async function readConfirmation(output, secret, contextName) {
async function readConfirmation(client, output, secret, contextName) {
const time = chalk.gray(`${ms(new Date() - new Date(secret.created))} ago`);
const tbl = table([[chalk.bold(secret.name), time]], {
align: ['r', 'l'],
@@ -367,5 +368,5 @@ async function readConfirmation(output, secret, contextName) {
);
output.print(` ${tbl}\n`);
return confirm(`${chalk.bold.red('Are you sure?')}`, false);
return confirm(client, `${chalk.bold.red('Are you sure?')}`, false);
}

View File

@@ -1,64 +0,0 @@
import chalk from 'chalk';
import cmd from '../util/output/cmd';
import logo from '../util/output/logo';
import handleError from '../util/handle-error';
import getArgs from '../util/get-args';
import Client from '../util/client';
import getUpdateCommand from '../util/get-update-command';
import { getPkgName, getTitleName } from '../util/pkg-name';
const help = () => {
console.log(`
${chalk.bold(`${logo} ${getPkgName()} update`)} [options]
${chalk.dim('Options:')}
-h, --help Output usage information
-d, --debug Debug mode [off]
-c ${chalk.bold.underline('NAME')}, --channel=${chalk.bold.underline(
'NAME'
)} Specify which release channel to install [stable]
-r ${chalk.bold.underline('VERSION')}, --release=${chalk.bold.underline(
'VERSION'
)} Specfic version to install (overrides \`--channel\`)
-y, --yes Skip the confirmation prompt
${chalk.dim('Examples:')}
${chalk.gray('')} Update ${getTitleName()} CLI to the latest "canary" version
${chalk.cyan(`$ ${getPkgName()} update --channel=canary`)}
`);
};
export default async function main(client: Client): Promise<number> {
let argv;
const { output } = client;
try {
argv = getArgs(client.argv.slice(2), {
'--channel': String,
'-c': '--channel',
'--release': String,
'-V': '--release',
'--yes': Boolean,
'-y': '--yes',
});
} catch (err) {
handleError(err);
return 1;
}
if (argv['--help']) {
help();
return 2;
}
output.log(
`Please run ${cmd(
await getUpdateCommand()
)} to update ${getTitleName()} CLI`
);
return 0;
}

View File

@@ -5,7 +5,7 @@ try {
// Test to see if cwd has been deleted before
// importing 3rd party packages that might need cwd.
process.cwd();
} catch (err) {
} catch (err: unknown) {
if (isError(err) && err.message.includes('uv_cwd')) {
console.error('Error! The current working directory does not exist.');
process.exit(1);
@@ -40,8 +40,8 @@ import getConfig from './util/get-config';
import * as configFiles from './util/config/files';
import getGlobalPathConfig from './util/config/global-path';
import {
getDefaultConfig,
getDefaultAuthConfig,
defaultAuthConfig,
defaultGlobalConfig,
} from './util/config/get-default';
import * as ERRORS from './util/errors-ts';
import { APIError } from './util/errors-ts';
@@ -50,7 +50,7 @@ import getUpdateCommand from './util/get-update-command';
import { metrics, shouldCollectMetrics } from './util/metrics';
import { getCommandName, getTitleName } from './util/pkg-name';
import doLoginPrompt from './util/login/prompt';
import { GlobalConfig } from './types';
import { AuthConfig, GlobalConfig } from './types';
import { VercelConfig } from '@vercel/client';
const isCanary = pkg.version.includes('canary');
@@ -208,160 +208,59 @@ const main = async () => {
VERCEL_DIR
)}" ${errorToString(err)}`
);
}
let migrated = false;
let configExists;
try {
configExists = existsSync(VERCEL_CONFIG_PATH);
} catch (err: unknown) {
console.error(
error(
`${
'An unexpected error occurred while trying to find the ' +
`config file "${hp(VERCEL_CONFIG_PATH)}" `
}${errorToString(err)}`
)
);
return 0;
}
let config: GlobalConfig | null = null;
if (configExists) {
try {
config = configFiles.readConfigFile();
} catch (err) {
console.error(
error(
`${
'An unexpected error occurred while trying to read the ' +
`config in "${hp(VERCEL_CONFIG_PATH)}" `
}${errorToString(err)}`
)
);
return 1;
}
// This is from when Vercel CLI supported
// 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'
) {
configExists = false;
}
}
if (!configExists) {
const results = await getDefaultConfig(config);
config = results.config;
migrated = results.migrated;
try {
configFiles.writeToConfigFile(config);
} catch (err: unknown) {
console.error(
error(
`${
'An unexpected error occurred while trying to write the ' +
`default config to "${hp(VERCEL_CONFIG_PATH)}" `
}${errorToString(err)}`
)
);
return 1;
}
}
let authConfigExists;
try {
authConfigExists = existsSync(VERCEL_AUTH_CONFIG_PATH);
} catch (err: unknown) {
console.error(
error(
`${
'An unexpected error occurred while trying to find the ' +
`auth file "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${errorToString(err)}`
)
);
return 1;
}
let authConfig = null;
const subcommandsWithoutToken = [
'login',
'logout',
'help',
'init',
'update',
'build',
];
if (authConfigExists) {
try {
authConfig = configFiles.readAuthConfigFile();
} catch (err: unknown) {
console.error(
error(
`${
'An unexpected error occurred while trying to read the ' +
`auth config in "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${errorToString(err)}`
)
);
return 1;
}
// This is from when Vercel CLI supported
// multiple providers. In that case, we really
// need to migrate.
// @ts-ignore
if (authConfig.credentials) {
authConfigExists = false;
}
} else {
const results = await getDefaultAuthConfig(authConfig);
authConfig = results.config;
migrated = results.migrated;
try {
configFiles.writeToAuthConfigFile(authConfig);
} catch (err: unknown) {
console.error(
error(
`${
'An unexpected error occurred while trying to write the ' +
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${errorToString(err)}`
)
let config: GlobalConfig;
try {
config = configFiles.readConfigFile();
} catch (err: unknown) {
if (isErrnoException(err) && err.code === 'ENOENT') {
config = defaultGlobalConfig;
try {
configFiles.writeToConfigFile(config);
} catch (err: unknown) {
output.error(
`An unexpected error occurred while trying to save the config to "${hp(
VERCEL_CONFIG_PATH
)}" ${errorToString(err)}`
);
return 1;
}
} else {
output.error(
`An unexpected error occurred while trying to read the config in "${hp(
VERCEL_CONFIG_PATH
)}" ${errorToString(err)}`
);
return 1;
}
}
// Let the user know we migrated the config
if (migrated) {
const directory = param(hp(VERCEL_DIR));
debug(
`The credentials and configuration within the ${directory} directory were upgraded`
);
let authConfig: AuthConfig;
try {
authConfig = configFiles.readAuthConfigFile();
} catch (err: unknown) {
if (isErrnoException(err) && err.code === 'ENOENT') {
authConfig = defaultAuthConfig;
try {
configFiles.writeToAuthConfigFile(authConfig);
} catch (err: unknown) {
output.error(
`An unexpected error occurred while trying to write the auth config to "${hp(
VERCEL_AUTH_CONFIG_PATH
)}" ${errorToString(err)}`
);
return 1;
}
} else {
output.error(
`An unexpected error occurred while trying to read the auth config in "${hp(
VERCEL_AUTH_CONFIG_PATH
)}" ${errorToString(err)}`
);
return 1;
}
}
if (typeof argv['--api'] === 'string') {
@@ -371,18 +270,12 @@ const main = async () => {
}
try {
// eslint-disable-next-line no-new
new URL(apiUrl);
} catch (err) {
} catch (err: unknown) {
output.error(`Please provide a valid URL instead of ${highlight(apiUrl)}.`);
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,
@@ -430,6 +323,8 @@ const main = async () => {
client.argv.push('-h');
}
const subcommandsWithoutToken = ['login', 'logout', 'help', 'init', 'build'];
// Prompt for login if there is no current token
if (
(!authConfig || !authConfig.token) &&
@@ -603,9 +498,6 @@ const main = async () => {
case 'alias':
func = require('./commands/alias').default;
break;
case 'billing':
func = require('./commands/billing').default;
break;
case 'bisect':
func = require('./commands/bisect').default;
break;
@@ -669,9 +561,6 @@ const main = async () => {
case 'teams':
func = require('./commands/teams').default;
break;
case 'update':
func = require('./commands/update').default;
break;
case 'whoami':
func = require('./commands/whoami').default;
break;

View File

@@ -20,15 +20,16 @@ export interface JSONObject {
}
export interface AuthConfig {
_?: string;
'// Note'?: string;
'// Docs'?: string;
token?: string;
skipWrite?: boolean;
}
export interface GlobalConfig {
_?: string;
'// Note'?: string;
'// Docs'?: string;
currentTeam?: string;
includeScheme?: string;
collectMetrics?: boolean;
api?: string;
@@ -140,6 +141,7 @@ export type Deployment = {
meta: {
[key: string]: any;
};
alias?: string[];
};
export type Alias = {
@@ -209,6 +211,7 @@ export interface ProjectAliasTarget {
configuredBy?: null | 'CNAME' | 'A';
configuredChangedAt?: null | number;
configuredChangeAttempts?: [number, number];
deployment?: Deployment | undefined;
}
export interface Secret {

View File

@@ -1,8 +0,0 @@
{
"VISA": "Visa",
"MASTERCARD": "MasterCard",
"AMERICANEXPRESS": "American Express",
"DINERSCLUB": "Diners Club",
"DISCOVER": "Discover",
"JCB": "JCB"
}

View File

@@ -21,6 +21,7 @@ import {
PackageJson,
Prerender,
download,
downloadFile,
EdgeFunction,
BuildResultBuildOutput,
getLambdaOptionsFromFunction,
@@ -266,9 +267,7 @@ async function writeStaticFile(
const dest = join(outputDir, 'static', fsPath);
await fs.mkdirp(dirname(dest));
// TODO: handle (or skip) symlinks?
const stream = file.toStream();
await pipe(stream, fs.createWriteStream(dest, { mode: file.mode }));
await downloadFile(file, dest);
}
/**

View File

@@ -1,75 +1,15 @@
import { AuthConfig, GlobalConfig } from '../../types';
export const getDefaultConfig = async (existingCopy?: GlobalConfig | null) => {
let migrated = false;
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,
};
if (existingCopy) {
const keep = [
'_',
'currentTeam',
'desktop',
'updateChannel',
'collectMetrics',
'api',
// This is deleted later in the code
];
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;
}
migrated = true;
} catch (err) {}
}
return { config, migrated };
export const defaultGlobalConfig: GlobalConfig = {
'// Note':
'This is your Vercel config file. For more information see the global configuration documentation.',
'// Docs':
'https://vercel.com/docs/project-configuration#global-configuration/config-json',
collectMetrics: true,
};
export const getDefaultAuthConfig = async (existing?: AuthConfig | null) => {
let migrated = false;
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) {
config.token = sh.token;
}
migrated = true;
} catch (err) {}
}
return { config, migrated };
export const defaultAuthConfig: AuthConfig = {
'// Note': 'This is your Vercel credentials file. DO NOT SHARE!',
'// Docs':
'https://vercel.com/docs/project-configuration#global-configuration/auth-json',
};

View File

@@ -6,7 +6,7 @@ import mapCertError from '../certs/map-cert-error';
import { Org } from '../../types';
import Now, { CreateOptions } from '..';
import Client from '../client';
import { DeploymentError } from '../../../../client/dist';
import { ArchiveFormat, DeploymentError } from '@vercel/client';
export default async function createDeploy(
client: Client,
@@ -16,10 +16,18 @@ export default async function createDeploy(
createArgs: CreateOptions,
org: Org,
isSettingUpProject: boolean,
cwd?: string
cwd?: string,
archive?: ArchiveFormat
): Promise<any | DeploymentError> {
try {
return await now.create(paths, createArgs, org, isSettingUpProject, cwd);
return await now.create(
paths,
createArgs,
org,
isSettingUpProject,
archive,
cwd
);
} catch (err: unknown) {
if (ERRORS_TS.isAPIError(err)) {
if (err.code === 'rate_limited') {

View File

@@ -2,12 +2,12 @@ import bytes from 'bytes';
import Progress from 'progress';
import chalk from 'chalk';
import {
ArchiveFormat,
createDeployment,
DeploymentOptions,
VercelClientOptions,
} from '@vercel/client';
import { Output } from '../output';
// @ts-ignore
import Now from '../../util';
import { Org } from '../../types';
import ua from '../ua';
@@ -32,6 +32,7 @@ export default async function processDeployment({
cwd,
projectName,
isSettingUpProject,
archive,
skipAutoDetectionConfirmation,
...args
}: {
@@ -48,6 +49,7 @@ export default async function processDeployment({
prebuilt: boolean;
projectName: string;
isSettingUpProject: boolean;
archive?: ArchiveFormat;
skipAutoDetectionConfirmation?: boolean;
cwd?: string;
rootDirectory?: string;
@@ -87,14 +89,13 @@ export default async function processDeployment({
prebuilt,
rootDirectory,
skipAutoDetectionConfirmation,
archive,
};
output.spinner(
isSettingUpProject
? 'Setting up project'
: `Deploying ${chalk.bold(`${org.slug}/${projectName}`)}`,
0
);
const deployingSpinnerVal = isSettingUpProject
? 'Setting up project'
: `Deploying ${chalk.bold(`${org.slug}/${projectName}`)}`;
output.spinner(deployingSpinnerVal, 0);
// collect indications to show the user once
// the deployment is done
@@ -107,12 +108,11 @@ export default async function processDeployment({
}
if (event.type === 'file-count') {
debug(
`Total files ${event.payload.total.size}, ${event.payload.missing.length} changed`
);
const { total, missing, uploads } = event.payload;
debug(`Total files ${total.size}, ${missing.length} changed`);
const missingSize = event.payload.missing
.map((sha: string) => event.payload.total.get(sha).data.length)
const missingSize = missing
.map((sha: string) => total.get(sha).data.length)
.reduce((a: number, b: number) => a + b, 0);
output.stopSpinner();
@@ -123,6 +123,26 @@ export default async function processDeployment({
total: missingSize,
clear: true,
});
bar.tick(0);
uploads.forEach((e: any) =>
e.on('progress', () => {
if (!bar) return;
const totalBytesUploaded = uploads.reduce((acc: number, e: any) => {
return acc + e.bytesUploaded;
}, 0);
// set the current progress bar value
bar.curr = totalBytesUploaded;
// trigger rendering
bar.tick(0);
if (bar.complete) {
output.spinner(deployingSpinnerVal, 0);
}
})
);
}
if (event.type === 'file-uploaded') {
@@ -131,10 +151,6 @@ export default async function processDeployment({
event.payload.file.data.length
)})`
);
if (bar) {
bar.tick(event.payload.file.data.length);
}
}
if (event.type === 'created') {

View File

@@ -0,0 +1,7 @@
import { ArchiveFormat, VALID_ARCHIVE_FORMATS } from '@vercel/client';
const validArchiveFormats = new Set<string>(VALID_ARCHIVE_FORMATS);
export function isValidArchive(archive: string): archive is ArchiveFormat {
return validArchiveFormats.has(archive);
}

View File

@@ -0,0 +1,59 @@
/**
* This function is necessary to account for the difference between
* `?a=` and `?a` because native `url.parse(str, true)` can't tell.
* @param querystring - The querystring to parse, also known as the "search" string.
*/
export function parseQueryString(
querystring?: string
): Record<string, string[]> {
const query: Record<string, string[]> = Object.create(null);
if (!querystring || !querystring.startsWith('?') || querystring === '?') {
return query;
}
const params = querystring.slice(1).split('&');
for (let param of params) {
let [key, value] = param.split('=');
if (key !== undefined) {
key = decodeURIComponent(key);
}
if (value !== undefined) {
value = decodeURIComponent(value);
}
let existing = query[key];
if (!existing) {
existing = [];
query[key] = existing;
}
existing.push(value);
}
return query;
}
/**
* This function is necessary to account for the difference between
* `?a=` and `?a` because native `url.format({ query })` can't tell.
* @param query - The query object to stringify.
*/
export function formatQueryString(
query: Record<string, string[]> | undefined
): string | undefined {
if (!query) {
return undefined;
}
let s = '';
let prefix = '?';
for (let [key, values] of Object.entries(query)) {
for (let value of values) {
s += prefix;
s += encodeURIComponent(key);
if (value !== undefined) {
s += '=';
s += encodeURIComponent(value);
}
prefix = '&';
}
}
return s || undefined;
}

View File

@@ -6,6 +6,7 @@ import DevServer from './server';
import { VercelConfig, HttpHeadersConfig, RouteResult } from './types';
import { isHandler, Route, HandleValue } from '@vercel/routing-utils';
import { parseQueryString } from './parse-query-string';
export function resolveRouteParameters(
str: string,
@@ -56,7 +57,8 @@ export async function devRouter(
phase?: HandleValue | null
): Promise<RouteResult> {
let result: RouteResult | undefined;
let { query, pathname: reqPathname = '/' } = url.parse(reqUrl, true);
let { pathname: reqPathname = '/', search: reqSearch } = url.parse(reqUrl);
const reqQuery = parseQueryString(reqSearch);
const combinedHeaders: HttpHeadersConfig = { ...previousHeaders };
let status: number | undefined;
let isContinue = false;
@@ -174,7 +176,7 @@ export async function devRouter(
isDestUrl,
status: routeConfig.status || status,
headers: combinedHeaders,
uri_args: query,
query: reqQuery,
matched_route: routeConfig,
matched_route_idx: idx,
phase,
@@ -184,17 +186,19 @@ export async function devRouter(
if (!destPath.startsWith('/')) {
destPath = `/${destPath}`;
}
const destParsed = url.parse(destPath, true);
Object.assign(destParsed.query, query);
const { pathname: destPathname = '/', search: destSearch } =
url.parse(destPath);
const destQuery = parseQueryString(destSearch);
Object.assign(destQuery, reqQuery);
result = {
found: true,
dest: destParsed.pathname || '/',
dest: destPathname,
continue: isContinue,
userDest: Boolean(routeConfig.dest),
isDestUrl,
status: routeConfig.status || status,
headers: combinedHeaders,
uri_args: destParsed.query,
query: destQuery,
matched_route: routeConfig,
matched_route_idx: idx,
phase,
@@ -212,7 +216,7 @@ export async function devRouter(
continue: isContinue,
status,
isDestUrl: false,
uri_args: query,
query: reqQuery,
headers: combinedHeaders,
phase,
};

View File

@@ -94,6 +94,7 @@ import { ProjectEnvVariable, ProjectSettings } from '../../types';
import exposeSystemEnvs from './expose-system-envs';
import { treeKill } from '../tree-kill';
import { nodeHeadersToFetchHeaders } from './headers';
import { formatQueryString, parseQueryString } from './parse-query-string';
import {
errorToString,
isErrnoException,
@@ -131,7 +132,6 @@ export default class DevServer {
public output: Output;
public proxy: httpProxy;
public envConfigs: EnvConfigs;
public frameworkSlug?: string;
public files: BuilderInputs;
public address: string;
public devCacheDir: string;
@@ -175,7 +175,6 @@ export default class DevServer {
this.address = '';
this.devCommand = options.devCommand;
this.projectSettings = options.projectSettings;
this.frameworkSlug = options.frameworkSlug;
this.caseSensitive = false;
this.apiDir = null;
this.apiExtensions = new Set();
@@ -1398,9 +1397,11 @@ export default class DevServer {
const getReqUrl = (rr: RouteResult): string | undefined => {
if (rr.dest) {
if (rr.uri_args) {
const destParsed = url.parse(rr.dest, true);
Object.assign(destParsed.query, rr.uri_args);
if (rr.query) {
const destParsed = url.parse(rr.dest);
const destQuery = parseQueryString(destParsed.search);
Object.assign(destQuery, rr.query);
destParsed.search = formatQueryString(destQuery);
return url.format(destParsed);
}
return rr.dest;
@@ -1535,9 +1536,8 @@ export default class DevServer {
// Retain orginal pathname, but override query parameters from the rewrite
const beforeRewriteUrl = req.url || '/';
const rewriteUrlParsed = url.parse(beforeRewriteUrl, true);
delete rewriteUrlParsed.search;
rewriteUrlParsed.query = url.parse(rewritePath, true).query;
const rewriteUrlParsed = url.parse(beforeRewriteUrl);
rewriteUrlParsed.search = url.parse(rewritePath).search;
req.url = url.format(rewriteUrlParsed);
debug(
`Rewrote incoming HTTP URL from "${beforeRewriteUrl}" to "${req.url}"`
@@ -1556,6 +1556,8 @@ export default class DevServer {
(err as any).link = 'https://vercel.link/command-not-found';
}
this.output.prettyError(err);
await this.sendError(
req,
res,
@@ -1594,9 +1596,10 @@ export default class DevServer {
if (routeResult.isDestUrl) {
// Mix the `routes` result dest query params into the req path
const destParsed = url.parse(routeResult.dest, true);
delete destParsed.search;
Object.assign(destParsed.query, routeResult.uri_args);
const destParsed = url.parse(routeResult.dest);
const destQuery = parseQueryString(destParsed.search);
Object.assign(destQuery, routeResult.query);
destParsed.search = formatQueryString(destQuery);
const destUrl = url.format(destParsed);
debug(`ProxyPass: ${destUrl}`);
@@ -1737,7 +1740,7 @@ export default class DevServer {
throw new Error('Expected Route Result but none was found.');
}
const { dest, headers, uri_args } = routeResult;
const { dest, query, headers } = routeResult;
// Set any headers defined in the matched `route` config
for (const [name, value] of Object.entries(headers)) {
@@ -1773,10 +1776,11 @@ export default class DevServer {
}
this.setResponseHeaders(res, requestId);
const origUrl = url.parse(req.url || '/', true);
delete origUrl.search;
const origUrl = url.parse(req.url || '/');
const origQuery = parseQueryString(origUrl.search);
origUrl.pathname = dest;
Object.assign(origUrl.query, uri_args);
Object.assign(origQuery, query);
origUrl.search = formatQueryString(origQuery);
req.url = url.format(origUrl);
return proxyPass(req, res, upstream, this, requestId, false);
}
@@ -1798,10 +1802,11 @@ export default class DevServer {
Array.isArray(buildResult.routes) &&
buildResult.routes.length > 0
) {
const origUrl = url.parse(req.url || '/', true);
delete origUrl.search;
const origUrl = url.parse(req.url || '/');
const origQuery = parseQueryString(origUrl.search);
origUrl.pathname = dest;
Object.assign(origUrl.query, uri_args);
Object.assign(origQuery, query);
origUrl.search = formatQueryString(origQuery);
const newUrl = url.format(origUrl);
debug(
`Checking build result's ${buildResult.routes.length} \`routes\` to match ${newUrl}`
@@ -1897,11 +1902,13 @@ export default class DevServer {
);
// Mix in the routing based query parameters
const parsed = url.parse(req.url || '/', true);
Object.assign(parsed.query, uri_args);
const origUrl = url.parse(req.url || '/');
const origQuery = parseQueryString(origUrl.search);
Object.assign(origQuery, query);
origUrl.search = formatQueryString(origQuery);
req.url = url.format({
pathname: parsed.pathname,
query: parsed.query,
pathname: origUrl.pathname,
search: origUrl.search,
});
// Add the Vercel platform proxy request headers
@@ -2017,11 +2024,13 @@ export default class DevServer {
requestId = generateRequestId(this.podId, true);
// Mix the `routes` result dest query params into the req path
const parsed = url.parse(req.url || '/', true);
Object.assign(parsed.query, uri_args);
const origUrl = url.parse(req.url || '/');
const origQuery = parseQueryString(origUrl.search);
Object.assign(origQuery, query);
origUrl.search = formatQueryString(origQuery);
const path = url.format({
pathname: parsed.pathname,
query: parsed.query,
pathname: origUrl.pathname,
search: origUrl.search,
});
const body = await rawBody(req);
@@ -2208,7 +2217,10 @@ export default class DevServer {
// Because of child process 'pipe' below, isTTY will be false.
// Most frameworks use `chalk`/`supports-color` so we enable it anyway.
FORCE_COLOR: process.stdout.isTTY ? '1' : '0',
...(this.frameworkSlug === 'create-react-app' ? { BROWSER: 'none' } : {}),
// Prevent framework dev servers from automatically opening a web
// browser window, since it will not be the port that `vc dev`
// is listening on and thus will be missing Vercel features.
BROWSER: 'none',
...process.env,
...this.envConfigs.allEnv,
PORT: `${port}`,

View File

@@ -24,7 +24,6 @@ export { VercelConfig };
export interface DevServerOptions {
output: Output;
devCommand?: string;
frameworkSlug?: string;
projectSettings?: ProjectSettings;
systemEnvValues?: string[];
projectEnvs?: ProjectEnvVariable[];
@@ -146,8 +145,8 @@ export interface RouteResult {
status?: number;
// "headers": <object of the added response header values>
headers: HttpHeadersConfig;
// "uri_args": <object (key=value) list of new uri args to be passed along to dest >
uri_args?: { [key: string]: any };
// "query": <object (key=values) of new uri args to be passed along to dest>
query?: Record<string, string[]>;
// "matched_route": <object of the route spec that matched>
matched_route?: Route;
// "matched_route_idx": <integer of the index of the route matched>

View File

@@ -9,6 +9,7 @@ type LinkResult = {
org: Org;
project: Project;
};
export async function ensureLink(
commandName: string,
client: Client,

View File

@@ -63,19 +63,25 @@ export function buildDeltaString(
const { added, changed, removed } = findChanges(oldEnv, newEnv);
let deltaString = '';
deltaString += chalk.green(addDeltaSection('+', changed, true));
deltaString += chalk.green(addDeltaSection('+', added));
deltaString += chalk.yellow(addDeltaSection('~', changed));
deltaString += chalk.red(addDeltaSection('-', removed));
return deltaString ? chalk.gray('Changes:\n') + deltaString : deltaString;
return deltaString
? chalk.gray('Changes:\n') + deltaString + '\n'
: deltaString;
}
function addDeltaSection(prefix: string, arr: string[]): string {
function addDeltaSection(
prefix: string,
arr: string[],
changed: boolean = false
): string {
if (arr.length === 0) return '';
return (
arr
.sort()
.map(item => `${prefix} ${item}`)
.map(item => `${prefix} ${item}${changed ? ' (Updated)' : ''}`)
.join('\n') + '\n'
);
}

View File

@@ -152,9 +152,7 @@ export class SourceNotFound extends NowError<'SOURCE_NOT_FOUND', {}> {
super({
code: 'SOURCE_NOT_FOUND',
meta: {},
message: `Not able to purchase. Please add a payment method using ${getCommandName(
`billing add`
)}.`,
message: `Not able to purchase. Please add a payment method using the dashboard.`,
});
}
}

View File

@@ -4,13 +4,22 @@ import { Org } from '../../types';
import chalk from 'chalk';
import link from '../output/link';
import { isAPIError } from '../errors-ts';
import { Output } from '../output';
import { Dictionary } from '@vercel/client';
export interface RepoInfo {
url: string;
provider: string;
org: string;
repo: string;
}
export async function disconnectGitProvider(
client: Client,
org: Org,
projectId: string
) {
const fetchUrl = `/v4/projects/${projectId}/link?${stringify({
const fetchUrl = `/v9/projects/${projectId}/link?${stringify({
teamId: org.type === 'team' ? org.id : undefined,
})}`;
return client.fetch(fetchUrl, {
@@ -28,7 +37,7 @@ export async function connectGitProvider(
type: string,
repo: string
) {
const fetchUrl = `/v4/projects/${projectId}/link?${stringify({
const fetchUrl = `/v9/projects/${projectId}/link?${stringify({
teamId: org.type === 'team' ? org.id : undefined,
})}`;
try {
@@ -43,22 +52,21 @@ export async function connectGitProvider(
}),
});
} catch (err: unknown) {
if (isAPIError(err)) {
if (
err.meta?.action === 'Install GitHub App' ||
err.code === 'repo_not_found'
) {
client.output.error(
`Failed to link ${chalk.cyan(
repo
)}. Make sure there aren't any typos and that you have access to the repository if it's private.`
);
} else if (err.action === 'Add a Login Connection') {
client.output.error(
err.message.replace(repo, chalk.cyan(repo)) +
`\nVisit ${link(err.link)} for more information.`
);
}
const apiError = isAPIError(err);
if (
apiError &&
(err.action === 'Install GitHub App' || err.code === 'repo_not_found')
) {
client.output.error(
`Failed to link ${chalk.cyan(
repo
)}. Make sure there aren't any typos and that you have access to the repository if it's private.`
);
} else if (apiError && err.action === 'Add a Login Connection') {
client.output.error(
err.message.replace(repo, chalk.cyan(repo)) +
`\nVisit ${link(err.link)} for more information.`
);
} else {
client.output.error(
`Failed to connect the ${formatProvider(
@@ -83,15 +91,13 @@ export function formatProvider(type: string): string {
}
}
export function parseRepoUrl(originUrl: string): {
provider: string;
org: string;
repo: string;
} | null {
export function parseRepoUrl(originUrl: string): RepoInfo | null {
const isSSH = originUrl.startsWith('git@');
// Matches all characters between (// or @) and (.com or .org)
// eslint-disable-next-line prefer-named-capture-group
const provider = /(?<=(\/\/|@)).*(?=(\.com|\.org))/.exec(originUrl);
const provider =
/(?<=(\/\/|@)).*(?=(\.com|\.org))/.exec(originUrl)?.[0] ||
originUrl.replace('www.', '').split('.')[0];
if (!provider) {
return null;
}
@@ -104,8 +110,8 @@ export function parseRepoUrl(originUrl: string): {
repo = originUrl.split('/')[1]?.replace('.git', '');
} else {
// Assume https:// or git://
org = originUrl.split('/')[3];
repo = originUrl.split('/')[4]?.replace('.git', '');
org = originUrl.replace('//', '').split('/')[1];
repo = originUrl.replace('//', '').split('/')[2]?.replace('.git', '');
}
if (!org || !repo) {
@@ -113,8 +119,17 @@ export function parseRepoUrl(originUrl: string): {
}
return {
provider: provider[0],
url: originUrl,
provider,
org,
repo,
};
}
export function printRemoteUrls(
output: Output,
remoteUrls: Dictionary<string>
) {
for (const [name, url] of Object.entries(remoteUrls)) {
output.print(`${name}: ${chalk.cyan(url)}\n`);
}
}

View File

@@ -17,7 +17,7 @@ import printIndications from './print-indications';
import { GitMetadata, Org } from '../types';
import { VercelConfig } from './dev/types';
import Client, { FetchOptions, isJSONObject } from './client';
import { Dictionary } from '@vercel/client';
import { ArchiveFormat, Dictionary } from '@vercel/client';
export interface NowOptions {
client: Client;
@@ -131,6 +131,7 @@ export default class Now extends EventEmitter {
}: CreateOptions,
org: Org,
isSettingUpProject: boolean,
archive?: ArchiveFormat,
cwd?: string
) {
let hashes: any = {};
@@ -168,6 +169,7 @@ export default class Now extends EventEmitter {
org,
projectName: name,
isSettingUpProject,
archive,
skipAutoDetectionConfirmation,
cwd,
prebuilt,
@@ -330,7 +332,8 @@ export default class Now extends EventEmitter {
async list(
app?: string,
{ version = 4, meta = {}, nextTimestamp }: ListOptions = {}
{ version = 4, meta = {}, nextTimestamp }: ListOptions = {},
prod?: boolean
) {
const fetchRetry = async (url: string, options: FetchOptions = {}) => {
return this.retry(
@@ -393,6 +396,9 @@ export default class Now extends EventEmitter {
if (nextTimestamp) {
query.set('until', String(nextTimestamp));
}
if (prod) {
query.set('target', 'production');
}
const response = await fetchRetry(`/v${version}/now/deployments?${query}`);
return response;

View File

@@ -0,0 +1,107 @@
import { Dictionary } from '@vercel/client';
import { parseRepoUrl } from '../git/connect-git-provider';
import Client from '../client';
import { Org, Project, ProjectSettings } from '../../types';
import { handleOptions } from './handle-options';
import {
promptGitConnectMultipleUrls,
promptGitConnectSingleUrl,
} from './git-connect-prompts';
function getProjectSettings(project: Project): ProjectSettings {
return {
createdAt: project.createdAt,
framework: project.framework,
devCommand: project.devCommand,
installCommand: project.installCommand,
buildCommand: project.buildCommand,
outputDirectory: project.outputDirectory,
rootDirectory: project.rootDirectory,
directoryListing: project.directoryListing,
nodeVersion: project.nodeVersion,
skipGitConnectDuringLink: project.skipGitConnectDuringLink,
};
}
export async function addGitConnection(
client: Client,
org: Org,
project: Project,
remoteUrls: Dictionary<string>,
settings?: ProjectSettings
): Promise<number | void> {
if (!settings) {
settings = getProjectSettings(project);
}
if (Object.keys(remoteUrls).length === 1) {
return addSingleGitRemote(
client,
org,
project,
remoteUrls,
settings || project
);
} else if (Object.keys(remoteUrls).length > 1 && !project.link) {
return addMultipleGitRemotes(
client,
org,
project,
remoteUrls,
settings || project
);
}
}
async function addSingleGitRemote(
client: Client,
org: Org,
project: Project,
remoteUrls: Dictionary<string>,
settings: ProjectSettings
) {
const [remoteName, remoteUrl] = Object.entries(remoteUrls)[0];
const repoInfo = parseRepoUrl(remoteUrl);
if (!repoInfo) {
client.output.debug(`Could not parse repo url ${repoInfo}.`);
return 1;
}
const { org: parsedOrg, repo, provider } = repoInfo;
const alreadyLinked =
project.link &&
project.link.org === parsedOrg &&
project.link.repo === repo &&
project.link.type === provider;
if (alreadyLinked) {
client.output.debug('Project already linked. Skipping...');
return;
}
const replace =
project.link &&
(project.link.org !== parsedOrg ||
project.link.repo !== repo ||
project.link.type !== provider);
const shouldConnect = await promptGitConnectSingleUrl(
client,
project,
remoteName,
remoteUrl,
replace
);
return handleOptions(shouldConnect, client, org, project, settings, repoInfo);
}
async function addMultipleGitRemotes(
client: Client,
org: Org,
project: Project,
remoteUrls: Dictionary<string>,
settings: ProjectSettings
) {
client.output.log('Found multiple Git remote URLs in Git config.');
const remoteUrlOrOptions = await promptGitConnectMultipleUrls(
client,
remoteUrls
);
return handleOptions(remoteUrlOrOptions, client, org, project, settings);
}

View File

@@ -0,0 +1,86 @@
import { Dictionary } from '@vercel/client';
import chalk from 'chalk';
import { Project } from '../../types';
import Client from '../client';
import { formatProvider } from '../git/connect-git-provider';
import list from '../input/list';
export async function promptGitConnectSingleUrl(
client: Client,
project: Project,
remoteName: string,
remoteUrl: string,
hasDiffConnectedProvider = false
) {
const { output } = client;
if (hasDiffConnectedProvider) {
const currentRepoPath = `${project.link!.org}/${project.link!.repo}`;
const currentProvider = project.link!.type;
output.print('\n');
output.log(
`Found Git remote URL ${chalk.cyan(
remoteUrl
)}, which is different from the connected ${formatProvider(
currentProvider
)} repository ${chalk.cyan(currentRepoPath)}.`
);
} else {
output.print('\n');
output.log(
`Found local Git remote "${remoteName}": ${chalk.cyan(remoteUrl)}`
);
}
return await list(client, {
message: hasDiffConnectedProvider
? 'Do you want to replace it?'
: `Do you want to connect "${remoteName}" to your Vercel project?`,
choices: [
{
name: 'Yes',
value: 'yes',
short: 'yes',
},
{
name: 'No',
value: 'no',
short: 'no',
},
{
name: 'Do not ask again for this project',
value: 'opt-out',
short: 'no (opt out)',
},
],
});
}
export async function promptGitConnectMultipleUrls(
client: Client,
remoteUrls: Dictionary<string>
) {
const staticOptions = [
{
name: 'No',
value: 'no',
short: 'no',
},
{
name: 'Do not ask again for this project',
value: 'opt-out',
short: 'no (opt out)',
},
];
let choices = [];
for (const url of Object.values(remoteUrls)) {
choices.push({
name: url,
value: url,
short: url,
});
}
choices = choices.concat(staticOptions);
return await list(client, {
message: 'Do you want to connect a Git repository to your Vercel project?',
choices,
});
}

View File

@@ -0,0 +1,98 @@
import chalk from 'chalk';
import { Org, Project, ProjectSettings } from '../../types';
import Client from '../client';
import {
connectGitProvider,
disconnectGitProvider,
formatProvider,
RepoInfo,
parseRepoUrl,
} from '../git/connect-git-provider';
import { Output } from '../output';
import { getCommandName } from '../pkg-name';
import updateProject from '../projects/update-project';
export async function handleOptions(
option: string,
client: Client,
org: Org,
project: Project,
settings: ProjectSettings,
repoInfo?: RepoInfo
) {
const { output } = client;
if (option === 'no') {
skip(output);
return;
} else if (option === 'opt-out') {
optOut(client, project, settings);
return;
} else if (option !== '') {
// Option is "yes" or a URL
// Ensure parsed url exists
if (!repoInfo) {
const _repoInfo = parseRepoUrl(option);
if (!_repoInfo) {
output.debug(`Could not parse repo url ${option}.`);
return 1;
}
repoInfo = _repoInfo;
}
return connect(client, org, project, repoInfo);
}
}
async function optOut(
client: Client,
project: Project,
settings: ProjectSettings
) {
settings.skipGitConnectDuringLink = true;
await updateProject(client, project.name, settings);
client.output
.log(`Opted out. You can re-enable this prompt by visiting the Settings > Git page on the
dashboard for this Project.`);
}
function skip(output: Output) {
output.log('Skipping...');
output.log(
`You can connect a Git repository in the future by running ${getCommandName(
'git connect'
)}.`
);
}
async function connect(
client: Client,
org: Org,
project: Project,
repoInfo: RepoInfo
): Promise<number | void> {
const { output } = client;
const { provider, org: parsedOrg, repo } = repoInfo;
const repoPath = `${parsedOrg}/${repo}`;
output.log('Connecting...');
if (project.link) {
await disconnectGitProvider(client, org, project.id);
}
const connect = await connectGitProvider(
client,
org,
project.id,
provider,
repoPath
);
if (connect !== 1) {
output.log(
`Connected ${formatProvider(provider)} repository ${chalk.cyan(
repoPath
)}!`
);
} else {
return connect;
}
}

View File

@@ -28,6 +28,8 @@ import { EmojiLabel } from '../emoji';
import createDeploy from '../deploy/create-deploy';
import Now, { CreateOptions } from '../index';
import { isAPIError } from '../errors-ts';
import { getRemoteUrls } from '../create-git-meta';
import { addGitConnection } from './add-git-connection';
export interface SetupAndLinkOptions {
forceDelete?: boolean;
@@ -128,6 +130,19 @@ export default async function setupAndLink(
} else {
const project = projectOrNewProjectName;
const remoteUrls = await getRemoteUrls(join(path, '.git/config'), output);
if (remoteUrls && !project.skipGitConnectDuringLink) {
const connectGit = await addGitConnection(
client,
org,
project,
remoteUrls
);
if (typeof connectGit === 'number') {
return { status: 'error', exitCode: connectGit };
}
}
await linkFolderToProject(
output,
path,
@@ -241,6 +256,21 @@ export default async function setupAndLink(
}
const project = await createProject(client, newProjectName);
const remoteUrls = await getRemoteUrls(join(path, '.git/config'), output);
if (remoteUrls) {
const connectGit = await addGitConnection(
client,
org,
project,
remoteUrls,
settings
);
if (typeof connectGit === 'number') {
return { status: 'error', exitCode: connectGit };
}
}
await updateProject(client, project.id, settings);
Object.assign(project, settings);

View File

@@ -7,7 +7,7 @@ export default async function createProject(
) {
const project = await client.fetch<Project>('/v1/projects', {
method: 'POST',
body: JSON.stringify({ name: projectName }),
body: { name: projectName },
});
return project;
}

View File

@@ -1,5 +1,5 @@
import Client from '../client';
import { ProjectSettings } from '../../types';
import type { JSONObject, ProjectSettings } from '../../types';
interface ProjectSettingsResponse extends ProjectSettings {
id: string;
@@ -13,11 +13,14 @@ export default async function updateProject(
prjNameOrId: string,
settings: ProjectSettings
) {
// `ProjectSettings` is technically compatible with JSONObject
const body = settings as JSONObject;
const res = await client.fetch<ProjectSettingsResponse>(
`/v2/projects/${encodeURIComponent(prjNameOrId)}`,
{
method: 'PATCH',
body: JSON.stringify(settings),
body,
}
);
return res;

View File

@@ -1,3 +1,5 @@
import stripAnsi from 'strip-ansi';
export default function strlen(str: string) {
return str.replace(/\u001b[^m]*m/g, '').length;
return stripAnsi(str).length;
}

View File

@@ -0,0 +1,10 @@
package handler
import (
"fmt"
"net/http"
)
func Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Req Path: %s", r.URL.Path)
}

View File

@@ -1,10 +1,10 @@
package handler
package another
import (
"fmt"
"net/http"
)
func Another(w http.ResponseWriter, r *http.Request) {
func HandlerAnother(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This is another page")
}

View File

@@ -0,0 +1,7 @@
export const config = {
matcher: 'not-a-valid-matcher',
};
export default function middleware(request, _event) {
return new Response(null);
}

View File

@@ -0,0 +1,7 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
.vercel
!public

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,16 @@
{
"private": true,
"scripts": {
"dev": "vite --port $PORT",
"build": "vite build",
"serve": "vite preview"
},
"dependencies": {
"vue": "3.2.37"
},
"devDependencies": {
"@vitejs/plugin-vue": "1.10.2",
"@vue/compiler-sfc": "3.2.37",
"vite": "2.9.14"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,22 @@
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Hello Vue 3 + Vite" />
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
// This starter template is using Vue 3 experimental <script setup> SFCs
// Check out https://github.com/vuejs/rfcs/blob/script-setup-2/active-rfcs/0000-script-setup.md
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,33 @@
<template>
<h1>{{ msg }}</h1>
<p>
<a href="https://vitejs.dev/guide/features.html" target="_blank">
Vite Documentation
</a>
|
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Documentation</a>
</p>
<button type="button" @click="state.count++">count is: {{ state.count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test hot module replacement.
</p>
</template>
<script setup>
import { defineProps, reactive } from 'vue'
defineProps({
msg: String
})
const state = reactive({ count: 0 })
</script>
<style scoped>
a {
color: #42b983;
}
</style>

View File

@@ -0,0 +1,4 @@
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

View File

@@ -0,0 +1,7 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
});

View File

@@ -0,0 +1,359 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@babel/parser@^7.16.4":
version "7.18.11"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9"
integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==
"@esbuild/linux-loong64@0.14.54":
version "0.14.54"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028"
integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==
"@vitejs/plugin-vue@1.10.2":
version "1.10.2"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-1.10.2.tgz#d718479e2789d8a94b63e00f23f1898ba239253a"
integrity sha512-/QJ0Z9qfhAFtKRY+r57ziY4BSbGUTGsPRMpB/Ron3QPwBZM4OZAZHdTa4a8PafCwU5DTatXG8TMDoP8z+oDqJw==
"@vue/compiler-core@3.2.37":
version "3.2.37"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.37.tgz#b3c42e04c0e0f2c496ff1784e543fbefe91e215a"
integrity sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/shared" "3.2.37"
estree-walker "^2.0.2"
source-map "^0.6.1"
"@vue/compiler-dom@3.2.37":
version "3.2.37"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz#10d2427a789e7c707c872da9d678c82a0c6582b5"
integrity sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==
dependencies:
"@vue/compiler-core" "3.2.37"
"@vue/shared" "3.2.37"
"@vue/compiler-sfc@3.2.37":
version "3.2.37"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz#3103af3da2f40286edcd85ea495dcb35bc7f5ff4"
integrity sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.37"
"@vue/compiler-dom" "3.2.37"
"@vue/compiler-ssr" "3.2.37"
"@vue/reactivity-transform" "3.2.37"
"@vue/shared" "3.2.37"
estree-walker "^2.0.2"
magic-string "^0.25.7"
postcss "^8.1.10"
source-map "^0.6.1"
"@vue/compiler-ssr@3.2.37":
version "3.2.37"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz#4899d19f3a5fafd61524a9d1aee8eb0505313cff"
integrity sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==
dependencies:
"@vue/compiler-dom" "3.2.37"
"@vue/shared" "3.2.37"
"@vue/reactivity-transform@3.2.37":
version "3.2.37"
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz#0caa47c4344df4ae59f5a05dde2a8758829f8eca"
integrity sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.37"
"@vue/shared" "3.2.37"
estree-walker "^2.0.2"
magic-string "^0.25.7"
"@vue/reactivity@3.2.37":
version "3.2.37"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.37.tgz#5bc3847ac58828e2b78526e08219e0a1089f8848"
integrity sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==
dependencies:
"@vue/shared" "3.2.37"
"@vue/runtime-core@3.2.37":
version "3.2.37"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.37.tgz#7ba7c54bb56e5d70edfc2f05766e1ca8519966e3"
integrity sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==
dependencies:
"@vue/reactivity" "3.2.37"
"@vue/shared" "3.2.37"
"@vue/runtime-dom@3.2.37":
version "3.2.37"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz#002bdc8228fa63949317756fb1e92cdd3f9f4bbd"
integrity sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==
dependencies:
"@vue/runtime-core" "3.2.37"
"@vue/shared" "3.2.37"
csstype "^2.6.8"
"@vue/server-renderer@3.2.37":
version "3.2.37"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.37.tgz#840a29c8dcc29bddd9b5f5ffa22b95c0e72afdfc"
integrity sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==
dependencies:
"@vue/compiler-ssr" "3.2.37"
"@vue/shared" "3.2.37"
"@vue/shared@3.2.37":
version "3.2.37"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.37.tgz#8e6adc3f2759af52f0e85863dfb0b711ecc5c702"
integrity sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==
csstype@^2.6.8:
version "2.6.17"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.17.tgz#4cf30eb87e1d1a005d8b6510f95292413f6a1c0e"
integrity sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==
esbuild-android-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be"
integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==
esbuild-android-arm64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771"
integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==
esbuild-darwin-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25"
integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==
esbuild-darwin-arm64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73"
integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==
esbuild-freebsd-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d"
integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==
esbuild-freebsd-arm64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48"
integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==
esbuild-linux-32@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5"
integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==
esbuild-linux-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652"
integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==
esbuild-linux-arm64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b"
integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==
esbuild-linux-arm@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59"
integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==
esbuild-linux-mips64le@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34"
integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==
esbuild-linux-ppc64le@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e"
integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==
esbuild-linux-riscv64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8"
integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==
esbuild-linux-s390x@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6"
integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==
esbuild-netbsd-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81"
integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==
esbuild-openbsd-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b"
integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==
esbuild-sunos-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da"
integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==
esbuild-windows-32@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31"
integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==
esbuild-windows-64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4"
integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==
esbuild-windows-arm64@0.14.54:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982"
integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==
esbuild@^0.14.27:
version "0.14.54"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2"
integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==
optionalDependencies:
"@esbuild/linux-loong64" "0.14.54"
esbuild-android-64 "0.14.54"
esbuild-android-arm64 "0.14.54"
esbuild-darwin-64 "0.14.54"
esbuild-darwin-arm64 "0.14.54"
esbuild-freebsd-64 "0.14.54"
esbuild-freebsd-arm64 "0.14.54"
esbuild-linux-32 "0.14.54"
esbuild-linux-64 "0.14.54"
esbuild-linux-arm "0.14.54"
esbuild-linux-arm64 "0.14.54"
esbuild-linux-mips64le "0.14.54"
esbuild-linux-ppc64le "0.14.54"
esbuild-linux-riscv64 "0.14.54"
esbuild-linux-s390x "0.14.54"
esbuild-netbsd-64 "0.14.54"
esbuild-openbsd-64 "0.14.54"
esbuild-sunos-64 "0.14.54"
esbuild-windows-32 "0.14.54"
esbuild-windows-64 "0.14.54"
esbuild-windows-arm64 "0.14.54"
estree-walker@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
is-core-module@^2.9.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed"
integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==
dependencies:
has "^1.0.3"
magic-string@^0.25.7:
version "0.25.7"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
dependencies:
sourcemap-codec "^1.4.4"
nanoid@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
postcss@^8.1.10, postcss@^8.4.13:
version "8.4.16"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c"
integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==
dependencies:
nanoid "^3.3.4"
picocolors "^1.0.0"
source-map-js "^1.0.2"
resolve@^1.22.0:
version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
dependencies:
is-core-module "^2.9.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
rollup@^2.59.0:
version "2.77.2"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.2.tgz#6b6075c55f9cc2040a5912e6e062151e42e2c4e3"
integrity sha512-m/4YzYgLcpMQbxX3NmAqDvwLATZzxt8bIegO78FZLl+lAgKJBd1DRAOeEiZcKOIOPjxE6ewHWHNgGEalFXuz1g==
optionalDependencies:
fsevents "~2.3.2"
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
sourcemap-codec@^1.4.4:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
vite@2.9.14:
version "2.9.14"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.14.tgz#c438324c6594afd1050df3777da981dee988bb1b"
integrity sha512-P/UCjSpSMcE54r4mPak55hWAZPlyfS369svib/gpmz8/01L822lMPOJ/RYW6tLCe1RPvMvOsJ17erf55bKp4Hw==
dependencies:
esbuild "^0.14.27"
postcss "^8.4.13"
resolve "^1.22.0"
rollup "^2.59.0"
optionalDependencies:
fsevents "~2.3.2"
vue@3.2.37:
version "3.2.37"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.37.tgz#da220ccb618d78579d25b06c7c21498ca4e5452e"
integrity sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==
dependencies:
"@vue/compiler-dom" "3.2.37"
"@vue/compiler-sfc" "3.2.37"
"@vue/runtime-dom" "3.2.37"
"@vue/server-renderer" "3.2.37"
"@vue/shared" "3.2.37"

View File

@@ -227,7 +227,7 @@ test('[vercel dev] should handle syntax errors thrown in edge functions', async
expect(await res.text()).toMatch(
/<strong>500<\/strong>: INTERNAL_SERVER_ERROR/g
);
expect(stderr).toMatch(/Failed to instantiate edge runtime./g);
expect(stderr).toMatch(/Failed to compile user code for edge runtime./g);
expect(stderr).toMatch(/Unexpected end of file/g);
expect(stderr).toMatch(
/Failed to complete request to \/api\/edge-error-syntax: Error: socket hang up/g
@@ -307,6 +307,35 @@ test('[vercel dev] should handle missing handler errors thrown in edge functions
}
});
test('[vercel dev] should handle invalid middleware config', async () => {
const dir = fixture('middleware-matchers-invalid');
const { dev, port, readyResolver } = await testFixture(dir);
try {
await readyResolver;
let res = await fetch(`http://localhost:${port}/api/whatever`, {
method: 'GET',
headers: {
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
},
});
validateResponseHeaders(res);
const { stderr } = await dev.kill('SIGTERM');
expect(await res.text()).toMatch(
/<strong>500<\/strong>: INTERNAL_SERVER_ERROR/g
);
expect(stderr).toMatch(
/Middleware's `config.matcher` .+ Received: not-a-valid-matcher/g
);
} finally {
await dev.kill('SIGTERM');
}
});
test('[vercel dev] should support request body', async () => {
const dir = fixture('node-request-body');
const { dev, port, readyResolver } = await testFixture(dir);
@@ -1016,3 +1045,20 @@ test('[vercel dev] validate rewrites', async () => {
/Invalid vercel\.json - `rewrites\[0\].destination` should be string/m
);
});
test(
'[vercel dev] should correctly proxy to vite dev',
testFixtureStdio(
'vite-dev',
async (testPath: any) => {
const url = '/src/App.vue?vue&type=style&index=0&lang.css';
// The first request should return the HTML template
await testPath(200, url, /<template>/gm);
// The second request should return the HMR JS
await testPath(200, url, /__vite__createHotContext/gm);
// Home page should always return HTML
await testPath(200, '/', /<title>Vite App<\/title>/gm);
},
{ skipDeploy: true }
)
);

View File

@@ -388,9 +388,8 @@ test(
await testPath(200, `/api/index.go`, 'This is the index page');
await testPath(200, `/api/another`, 'This is another page');
await testPath(200, '/api/another.go', 'This is another page');
// DISABLED: These assertions rely on different bracket names working.
// await testPath(200, `/api/foo`, 'Req Path: /api/foo');
// await testPath(200, `/api/bar`, 'Req Path: /api/bar');
await testPath(200, `/api/foo`, 'Req Path: /api/foo');
await testPath(200, `/api/bar`, 'Req Path: /api/bar');
})
);

View File

@@ -305,7 +305,7 @@ function testFixtureStdio(
? ['--scope', process.env.VERCEL_TEAM_ID]
: []),
'link',
'--confirm',
'--yes',
],
{ cwd, stdio: 'pipe', reject: false }
);
@@ -348,7 +348,10 @@ function testFixtureStdio(
: []),
'deploy',
...(process.env.VERCEL_CLI_VERSION
? ['--build-env', `VERCEL_CLI_VERSION=${process.env.VERCEL_CLI_VERSION}`]
? [
'--build-env',
`VERCEL_CLI_VERSION=${process.env.VERCEL_CLI_VERSION}`,
]
: []),
'--public',
'--debug',
@@ -430,7 +433,7 @@ function testFixtureStdio(
);
}
if (stderr.includes('Command failed') || stderr.includes('Error!')) {
if (stderr.includes('Command failed')) {
dev.kill('SIGTERM');
throw new Error(`Failed for "${directory}" with stderr "${stderr}".`);
}

View File

@@ -0,0 +1,7 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"framework": null
}
}

View File

@@ -0,0 +1 @@
<h1>Vercel</h1>

View File

@@ -0,0 +1 @@
!.vercel

View File

@@ -0,0 +1,4 @@
{
"orgId": "team_dummy",
"projectId": "archive"
}

View File

@@ -0,0 +1 @@
<h1>hi</h1>

View File

@@ -0,0 +1,3 @@
body {
background-color: red;
}

View File

@@ -0,0 +1,2 @@
!.vercel
.vercel

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