Compare commits

...

68 Commits

Author SHA1 Message Date
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
Steven
543ffdfe5c Publish Stable
- @vercel/build-utils@5.0.8
 - vercel@27.3.4
 - @vercel/client@12.1.7
 - @vercel/fs-detectors@2.0.3
 - @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
2022-08-02 09:29:15 -04:00
Steven
c11527e904 [build-utils] Fix symlink on download (#8288)
Some builders, such as `@vercel/next`, return both the symlinked directory and the resolved file.

When `vc build` iterates over the files to recreate them in `.vercel/output`, it fails with `EEXIST: file already exists` when creating the symlink because it first creates the file `node_modules/<symlink>/package.json` and then attempts to create the symlink `node_modules/<symlink>`.

This happened to work before `vc build` because yazl would accept the symlinked directory instead of the package.json file, so this PR is created to match that behavior.
2022-08-01 23:35:24 +00:00
Sean Massa
d296064386 [go] remove meta.isDev references and restore partial tests (#8287)
Remove `meta.isDev` checks inside the `build` function because it's never set there. Instead, `startDevServer` would be used.

Also restored the Go tests in a partial form. Will fix the Go builder issues and make sure those features are tested completely in follow-up PRs.
2022-08-01 19:52:34 +00:00
Lee Robinson
400a6c42bd [examples] Update Docusaurus 2 template for stable (#8286) 2022-08-01 18:48:24 +00:00
Seiya Nuta
71b3ded398 [fs-detectors] Exclude the middleware builder if it's a Next.js app (#8239) 2022-08-01 08:18:29 -04:00
Sean Massa
fc3fa61b59 update edge-runtime to allow instanceof to work with primitives (#8242)
* update edge-runtime to allow `instanceof` to work with primitives

* finishin upgrading edge-runtime

* update to latest

* fix merge

* remove dev only from test
2022-07-29 16:11:37 -05:00
Sean Massa
1f98c4fee7 Publish Stable
- vercel@27.3.3
2022-07-29 14:20:13 -05:00
Matthew Stanciu
1cb5a91727 [cli] Fix env delta message (#8271)
Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-07-29 14:18:55 -05:00
Sean Massa
e8c7db59cf Publish Stable
- @vercel/build-utils@5.0.7
 - vercel@27.3.2
 - @vercel/client@12.1.6
 - @vercel/go@2.0.11
 - @vercel/hydrogen@0.0.8
 - @vercel/next@3.1.11
 - @vercel/node@2.5.2
 - @vercel/python@3.1.3
 - @vercel/redwood@1.0.12
 - @vercel/remix@1.0.13
 - @vercel/ruby@1.3.19
 - @vercel/static-build@1.0.12
2022-07-29 13:45:48 -05:00
Steven
57b230e25f [all] Revert ncc back to 0.24.0 (#8276) 2022-07-29 13:44:09 -05:00
Steven
ab3fb25790 [tests] Run dev e2e against same cli version (#8274)
The dev integration tests compare `vc dev` with a real deployment to make sure the results are the same. This PR ensures the deployment uses the same version of Vercel CLI as the local `vc dev` instance.

Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>
2022-07-29 13:19:36 -04:00
Steven
88d98f7497 [cli] Fix vc build with legacy @now/static (#8273)
There was a special case for `@vercel/static` but vc build was missing a special case for the legacy `@now/static`, which should work the same way.
2022-07-29 11:40:09 -04:00
Sean Massa
90c1895949 Publish Stable
- @vercel/build-utils@5.0.6
 - vercel@27.3.1
 - @vercel/client@12.1.5
 - @vercel/fs-detectors@2.0.2
 - @vercel/go@2.0.10
 - @vercel/hydrogen@0.0.7
 - @vercel/next@3.1.10
 - @vercel/node@2.5.1
 - @vercel/python@3.1.2
 - @vercel/redwood@1.0.11
 - @vercel/remix@1.0.12
 - @vercel/ruby@1.3.18
 - @vercel/static-build@1.0.11
2022-07-28 16:01:36 -05:00
Steven
46a1f3670b [tests] Remove turbo's dependsOn to speed up tests (#8264)
Since CI is already setup to run Build and then Test, we don't need to add it as an explicit turbo dependency so this PR removes `dependsOn: ['build']` from each test task.

In fact, the `test-unit` step doesn't need `build` in most cases now that jest is configured to run the TS sources directly. 

This PR will also improve the time it takes to run the "Find Changes" job since it can ask jest for relevant files that changed without waiting for a build. This drops the time down from 2 minutes to 8 seconds.
2022-07-28 18:59:50 +00:00
Nathan Rajlich
4b025fee92 [node] Consider "browser" and "module" fields in package.json when tracing Edge Functions (#8262)
For Edge Functions, we are now considering the `"browser"` and `"module"` fields of packages to find the proper entrypoint. This PR updates `@vercel/node` to consider those same fields when using the node-file-trace tracing, so that the proper files get included in the output Edge Function.
2022-07-28 17:37:35 +00:00
Chris Barber
dc8293dc13 [cli] Add 'alias' command to help screen and alphabetize commands (#8256)
The `alias` command has been added to the `vercel --help` screen.

### Related Issues

> https://linear.app/vercel/issue/VCCLI-221/alias-command-missing-on-vc-h-help-screen

### 📋 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-07-28 01:07:14 +00:00
Nathan Rajlich
78883dea23 [node] Remove .toString() in nft readFile() hook (#8263)
The nft `readFile()` hook accepts `Buffer` type so there's no reason to `.toString()` here.
2022-07-27 22:26:40 +00:00
Steven
b5b792e42f [tests] Update tests to run with vercel cli tarball (#8257)
This PR update the tests suite to wait for Vercel CLI tarball and then use that tarball to run E2E tests.

This is valuable because it will package all the packages in this monorepo to make the tests follow more closely what will happen in production once merged.

Since the current "Find Changes" step takes about 2 minutes, we run that first (it happens concurrently with the tarball preparation). Then once we complete "Find Changes" we wait for the tarball but it will likely be ready by that point since it also takes about 2 minutes. After both of those steps, the E2E tests continue as usual but with the `VERCEL_CLI_VERSION` set to the tarball.

- Related to #7967 
- Closes #8245 
- Closes #8227
2022-07-27 17:56:03 -04:00
Sean Massa
8993a3c4af [node] trim down values to only what is required for esbuild wrapping logic (#8260)
The edge runtime context we were providing was necessary to  have `ncc`-bundled code run, but after switching to `esbuild`, we didn't update this list.

This PR trims the values down to only what's necessary for `esbuild`-bundled code to run. It also adds comments about why these values are necessary.
2022-07-27 20:15:00 +00:00
Steven
57241aad81 [tests] Speed up vc tarball (#8261)
This updates the API that generates tarballs so that it doesn't need to generate tarballs for every Serverless Function and instead generates it one time. This cuts the time in half.
2022-07-27 19:41:48 +00:00
Sean Massa
4773ff5efd Publish Stable
- @vercel/build-utils@5.0.5
 - vercel@27.3.0
 - @vercel/client@12.1.4
 - @vercel/go@2.0.9
 - @vercel/hydrogen@0.0.6
 - @vercel/next@3.1.9
 - @vercel/node@2.5.0
 - @vercel/python@3.1.1
 - @vercel/redwood@1.0.10
 - @vercel/remix@1.0.11
 - @vercel/ruby@1.3.17
 - @vercel/static-build@1.0.10
2022-07-27 13:33:34 -05:00
Gal Schlezinger
d8c7308eb6 [node] Add WebAssembly importing support for the @vercel/node builder (#8204)
* [node] Add WebAssembly importing support for the @vercel/node builder

* add comment about the original source

* trim down values to only what is required for esbuild wrapping logic

* Revert "trim down values to only what is required for esbuild wrapping logic"

This reverts commit c04dacad99f25156938dccdf2f29aac6e8282564.

Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>
2022-07-27 13:17:32 -05:00
Steven
5df1c89138 [tests] Fix next runtime test (#8258)
This test fails with `vc build`
2022-07-27 02:51:43 +00:00
Ethan Arrowood
f5d879143c [static-build] Add .vercel to static-build ignore list regardless of config (#8255)
### Related Issues

Adds `.vercel` path to the ignore list regardless if `zeroConfig` is enabled. This fixes a bug where the `.vercel` folder was being copied into the resulting `.vercel/output/static` directory after running `vc build` with `distDir: "."` configured for static-build.

### 📋 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-07-26 22:04:25 +00:00
Matthew Stanciu
9a55809515 [cli] Minor vc inspect visual updates (#8160)
Featuring:

- Aliases are now shown
- `readyState` –> `status` in "General" section
- `createdAt` –> `created` in "General" section
- Update `stateString` to support more states & be more visually appealing

### Before

<img width="754" alt="Screen Shot 2022-07-14 at 3 10 40 PM" src="https://user-images.githubusercontent.com/14811170/179097164-df7acd8a-d677-4e75-b7d5-b8ec6cf9bb12.png">

### After

<img width="748" alt="Screen Shot 2022-07-14 at 3 10 29 PM" src="https://user-images.githubusercontent.com/14811170/179097170-137d8977-60d6-402d-825e-8b0fb3025969.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-07-26 18:15:38 +00:00
Nathan Rajlich
56adf15823 [all] Update @vercel/ncc to v0.34.0 (#8248)
The version of `@vercel/ncc` that was being used in this repo is rather old. Let's update to the latest version. Specifically, this is a baby step towards allowing CLI to become ESM format.
2022-07-26 08:24:01 +00:00
Steven
1acab3d06c [tests] Update node tests to run concurrently (#8247)
This applies the same chunking algorithm from `@vercel/static-build` tests to `@vercel/node` tests that we can run up to 5 integration tests concurrently.
2022-07-26 00:06:59 +00:00
Nathan Rajlich
081b38466b [cli] Update to TypeScript v4.7.4 (#8232) 2022-07-25 16:08:14 -07:00
Nathan Rajlich
c397fd1856 [cli] Apply functions configuration to Serverless Functions in "vc build" (#8243) 2022-07-25 23:04:49 +00:00
Steven
afd303b94a [tests] Fix turbo cache for test changes (#8246)
When we make changes in `test/lib/**`, it should run all the E2E tests again for every package.

https://turborepo.org/docs/reference/configuration
2022-07-25 17:14:33 -04:00
Ethan Arrowood
b12387034a [cli] fix zero config resolution in vc build (#8244)
### Related Issues

Adds a `isZeroConfig` check to static-build so that when `"zeroConfig": true` is enabled the correct settings are resolved.

### 📋 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-07-25 19:27:12 +00:00
Steven
5af65d5a24 Revert "[python] support Sanic >=21 and python >= 3.10" (#8241)
- Reverts vercel/vercel#8045
- Fixes vercel/vercel#8231
2022-07-25 17:40:21 +00:00
Steven
1ee9a96a62 [build-utils] Fix package.json and lockfile detection (#8230)
This PR fixes a couple issues where `vercel build` was not correctly detecting the package.json files

```
Error: @vercel/node:test: ERROR: frontend/index.ts(1,12): error TS2580: Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.
```

It also fixes an issue where all deployments were incorrectly detecting the lock file because the lock file doesn't always live in the same directory as the package.json file. So we need to do 2 passes: one to find the nearest package.json and one to find the nearest lock file.
2022-07-25 16:29:24 +00:00
Nathan Rajlich
76130faf26 [cli] Do not load .env in "vercel build" command (#8228)
Only consider the `.vercel/.env.${target}.local` file when loading env vars at the beginning of `vercel build`.

If a project-level `.env` (or `.env.production` or whatever) file needs to be loaded then it is the responsibility of the frontend framework's build command to consider those files (i.e. CRA build, `next build`, etc.)
2022-07-24 05:08:13 +00:00
Nathan Rajlich
fb3601d178 [cli] Add "dev" script, remove "build-dev" script (#8222)
I don't think anyone is using this command anymore in the world of M1.

Anyways, we should be using `ts-node` to execute the CLI from source code instead of skipping steps at build-time to iterate quickly.
2022-07-22 22:39:10 +00:00
Steven
aebfb6812d [cli][build-utils] Fix dependencies for boxen (#8210)
The `boxen` package was incorrectly listed as a dependency of build-utils so I moved it to cli, which is only used by the [`vercel bisect`](3d3774ee7e/packages/cli/src/commands/bisect/index.ts (L2)) command.
2022-07-22 22:08:51 +00:00
Matthew Stanciu
73999e7253 [cli] Log delta in vc env pull (#8170)
This PR adds a new message to the end of `vc env pull` which shows delta of what was added, changed, and removed.

Example: you have a local `.env` file in a directory linked to a Vercel project. You change `TEST_1`, remove `TEST_2` via `vc env rm TEST_2`, and add `TEST_3` via `vc env add`. Then you run `vc env pull`. Here's what you will see:

```
> Updating existing .env file
> Downloading `development` Environment Variables for Project name
  Updated .env file

+ TEST_3
~ TEST_1
- TEST_2
```

<img width="562" alt="Screen Shot 2022-07-20 at 11 09 56 AM" src="https://user-images.githubusercontent.com/14811170/180064534-2ff5facb-95cb-4712-aaad-cbb47490cebe.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-07-22 19:16:56 +00:00
Nathan Rajlich
989dad5570 [cli] Use ts-node with SWC instead of ts-eager (#8224)
`ts-eager` isn't really maintained anymore and doesn't support ESM packages.

`ts-node` with SWC is more or less just as fast, so let's use that instead since it's well maintained any we can dogfood SWC more.
2022-07-22 17:53:15 +00:00
Nathan Rajlich
68c2dea601 [cli] Add support for Vercel Analytics in "vercel build" command (#8220)
Pull the Vercel Analytics ID in the `vercel pull` command, so that `vercel build` can set the `VERCEL_ANALYTICS_ID` environment variable at build-time for the frontend framework to utilize.
2022-07-22 17:23:29 +00:00
JJ Kasper
63f2da2f68 [next] Update size limit test (#8226)
This test is failing due to a decrease in the server layer so it is no longer over the limit as expected so this adds some more data to the specific route to ensure it does hit the limit. 

Fixes: https://github.com/vercel/vercel/runs/7469021922?check_suite_focus=true#step:9:6800
2022-07-22 11:05:53 -04:00
57e5f81361 [docs] Fix CONTRIBUTING file local development guideline, change dir after git clone (#8225)
Update CONTRIBUTING.md
2022-07-22 09:07:29 -04:00
Mosaad
fd5e440533 [next] Fix error message typo (#8218)
User the correct indefinite articles.

Co-authored-by: Steven <steven@ceriously.com>
2022-07-22 08:44:48 -04:00
Sean Massa
2a45805b26 [tests] increase timeout for test that's failing on windows (#8221)
This test sometimes times out on Windows. When it succeeds, it takes ~6 seconds. When it fails, we don't know how long it would have taken, but the current timeout is 7 seconds. Let's try 10 seconds and see if that's better.
2022-07-22 08:52:28 +00:00
Nathan Rajlich
5523383e50 [cli] Prevent vc deploy --prebuilt when vc build failed (#8194)
If `vc build` failed then don't allow a deployment to be created with `vc deploy --prebuilt`.
2022-07-22 07:31:52 +00:00
Sean Massa
0ecbb24cab [utils] allow a github PAT token to get past rate limit (#8209)
If you run `yarn changelog` when your IP is already rate limited by github, you'll get an error. This allows you to set a [Github PAT](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) to get past the generic rate limit.

Given a PAT with value `MY_PAT_TOKEN`, you can run:

```
$ GITHUB_TOKEN=MY_PAT_TOKEN yarn changelog
```

If you do get an error, it now actually shows up in the output:

```
$  yarn changelog
yarn run v1.22.18
$ node utils/changelog.js
Error: Failed to fetch releases from github: API rate limit exceeded for 98.139.180.149. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)
```
2022-07-22 06:14:59 +00:00
Nathan Rajlich
922223bd19 [cli] Lazy-load "metrics" object to fix jest reporting open handle (#7866)
Jest was saying that there's an open handle in the unit tests due to the `crypto.pbkdf2Sync()` call in this metrics file, so lazy-load that object in the case when metrics should not be collected.
2022-07-22 04:02:26 +00:00
JJ Kasper
0ad7fd34f4 [next][node][redwood][remix] Update @vercel/nft to 0.21.0 (#8208)
### Related Issues

Updates to the latest version of `@vercel/nft` which adds fs concurrency limits to help alleviate memory usage. 

x-ref: https://github.com/vercel/nft/pull/301

### 📋 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-07-22 00:45:53 +00:00
536 changed files with 12808 additions and 14415 deletions

View File

@@ -12,6 +12,7 @@ To get started, execute the following:
```
git clone https://github.com/vercel/vercel
cd vercel
yarn install
yarn bootstrap
yarn build

View File

@@ -17,6 +17,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
tests: ${{ steps['set-tests'].outputs['tests'] }}
dplUrl: ${{ steps.waitForTarball.outputs.url }}
steps:
- uses: actions/checkout@v2
- run: git --version
@@ -32,6 +33,12 @@ jobs:
echo "Files to test:"
echo "$TESTS_ARRAY"
echo "::set-output name=tests::$TESTS_ARRAY"
- uses: patrickedqvist/wait-for-vercel-preview@ae34b392ef30297f2b672f9afb3c329bde9bd487
id: waitForTarball
with:
token: ${{ secrets.GITHUB_TOKEN }}
max_timeout: 360
check_interval: 5
test:
timeout-minutes: 120
@@ -69,13 +76,14 @@ jobs:
- run: yarn install --network-timeout 1000000
- name: Build ${{matrix.packageName}} and all its dependencies
run: yarn turbo run build --cache-dir=".turbo" --scope=${{matrix.packageName}} --include-dependencies --no-deps
run: node_modules/.bin/turbo run build --cache-dir=".turbo" --scope=${{matrix.packageName}} --include-dependencies --no-deps
env:
FORCE_COLOR: '1'
- name: Test ${{matrix.packageName}}
run: node_modules/.bin/turbo run test --cache-dir=".turbo" --scope=${{matrix.packageName}} --no-deps -- ${{ join(matrix.testPaths, ' ') }}
shell: bash
env:
VERCEL_CLI_VERSION: ${{ needs.setup.outputs.dplUrl }}/tarballs/vercel.tgz
VERCEL_TEAM_TOKEN: ${{ secrets.VERCEL_TEAM_TOKEN }}
VERCEL_REGISTRATION_URL: ${{ secrets.VERCEL_REGISTRATION_URL }}
FORCE_COLOR: '1'

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:

View File

@@ -5,8 +5,7 @@
"description": "API for the vercel/vercel repo",
"main": "index.js",
"scripts": {
"//TODO": "We should add this pkg to yarn workspaces",
"vercel-build": "cd .. && yarn install && yarn vercel-build"
"//TODO": "We should add this pkg to yarn workspaces"
},
"dependencies": {
"@sentry/node": "5.11.1",

View File

@@ -15,5 +15,5 @@ _Live Example: https://docusaurus-2-template.vercel.app_
To get started with Docusaurus on Vercel, you can use the [Docusaurus CLI](https://v2.docusaurus.io/docs/installation#scaffold-project-website) to initialize the project:
```shell
$ npx @docusaurus/init@next init my-website classic
npx create-docusaurus@latest my-website classic
```

View File

@@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};

View File

@@ -1,10 +1,11 @@
---
id: hola
title: Hola
author: Gao Wei
author_title: Docusaurus Core Team
author_url: https://github.com/wgao19
author_image_url: https://avatars1.githubusercontent.com/u/2055384?v=4
slug: first-blog-post
title: First Blog Post
authors:
name: Gao Wei
title: Docusaurus Core Team
url: https://github.com/wgao19
image_url: https://github.com/wgao19.png
tags: [hola, docusaurus]
---

View File

@@ -1,17 +0,0 @@
---
id: hello-world
title: Hello
author: Endilie Yacop Sucipto
author_title: Maintainer of Docusaurus
author_url: https://github.com/endiliey
author_image_url: https://avatars1.githubusercontent.com/u/17883920?s=460&v=4
tags: [hello, docusaurus]
---
Welcome to this blog. This blog is created with [**Docusaurus 2 alpha**](https://v2.docusaurus.io/).
<!--truncate-->
This is a test post.
A whole bunch of other information.

View File

@@ -0,0 +1,44 @@
---
slug: long-blog-post
title: Long Blog Post
authors: endi
tags: [hello, docusaurus]
---
This is the summary of a very long blog post,
Use a `<!--` `truncate` `-->` comment to limit blog post size in the list view.
<!--truncate-->
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

View File

@@ -1,13 +0,0 @@
---
id: welcome
title: Welcome
author: Yangshun Tay
author_title: Front End Engineer @ Facebook
author_url: https://github.com/yangshun
author_image_url: https://avatars0.githubusercontent.com/u/1315101?s=400&v=4
tags: [facebook, hello, docusaurus]
---
Blog features are powered by the blog plugin. Simply add files to the `blog` directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!

View File

@@ -0,0 +1,20 @@
---
slug: mdx-blog-post
title: MDX Blog Post
authors: [slorber]
tags: [docusaurus]
---
Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/).
:::tip
Use the power of React to create interactive blog posts.
```js
<button onClick={() => alert('button clicked!')}>Click me!</button>
```
<button onClick={() => alert('button clicked!')}>Click me!</button>
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@@ -0,0 +1,25 @@
---
slug: welcome
title: Welcome
authors: [slorber, yangshun]
tags: [facebook, hello, docusaurus]
---
[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog).
Simply add Markdown files (or folders) to the `blog` directory.
Regular blog authors can be added to `authors.yml`.
The blog post date can be extracted from filenames, such as:
- `2019-05-30-welcome.md`
- `2019-05-30-welcome/index.md`
A blog post folder can be convenient to co-locate blog post images:
![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg)
The blog supports tags as well!
**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config.

View File

@@ -0,0 +1,17 @@
endi:
name: Endilie Yacop Sucipto
title: Maintainer of Docusaurus
url: https://github.com/endiliey
image_url: https://github.com/endiliey.png
yangshun:
name: Yangshun Tay
title: Front End Engineer @ Facebook
url: https://github.com/yangshun
image_url: https://github.com/yangshun.png
slorber:
name: Sébastien Lorber
title: Docusaurus maintainer
url: https://sebastienlorber.com
image_url: https://github.com/slorber.png

View File

@@ -1,202 +0,0 @@
---
id: doc1
title: Style Guide
sidebar_label: Style Guide
---
You can write content using [GitHub-flavored Markdown syntax](https://github.github.com/gfm/).
## Markdown Syntax
To serve as an example page when styling markdown based Docusaurus sites.
## Headers
# H1 - Create the best documentation
## H2 - Create the best documentation
### H3 - Create the best documentation
#### H4 - Create the best documentation
##### H5 - Create the best documentation
###### H6 - Create the best documentation
---
## Emphasis
Emphasis, aka italics, with _asterisks_ or _underscores_.
Strong emphasis, aka bold, with **asterisks** or **underscores**.
Combined emphasis with **asterisks and _underscores_**.
Strikethrough uses two tildes. ~~Scratch this.~~
---
## Lists
1. First ordered list item
1. Another item ⋅⋅\* Unordered sub-list.
1. Actual numbers don't matter, just that it's a number ⋅⋅1. Ordered sub-list
1. And another item.
⋅⋅⋅You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown).
⋅⋅⋅To have a line break without a paragraph, you will need to use two trailing spaces.⋅⋅ ⋅⋅⋅Note that this line is separate, but within the same paragraph.⋅⋅ ⋅⋅⋅(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.)
- Unordered list can use asterisks
* Or minuses
- Or pluses
---
## Links
[I'm an inline-style link](https://www.google.com)
[I'm an inline-style link with title](https://www.google.com "Google's Homepage")
[I'm a reference-style link][arbitrary case-insensitive reference text]
[I'm a relative reference to a repository file](../blob/master/LICENSE)
[You can use numbers for reference-style link definitions][1]
Or leave it empty and use the [link text itself].
URLs and URLs in angle brackets will automatically get turned into links. http://www.example.com or <http://www.example.com> and sometimes example.com (but not on Github, for example).
Some text to show that the reference links can follow later.
[arbitrary case-insensitive reference text]: https://www.mozilla.org
[1]: http://slashdot.org
[link text itself]: http://www.reddit.com
---
## Images
Here's our logo (hover to see the title text):
Inline-style: ![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Logo Title Text 1')
Reference-style: ![alt text][logo]
[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Logo Title Text 2'
---
## Code
```javascript
var s = 'JavaScript syntax highlighting';
alert(s);
```
```python
s = "Python syntax highlighting"
print(s)
```
```
No language indicated, so no syntax highlighting.
But let's throw in a <b>tag</b>.
```
```js {2}
function highlightMe() {
console.log('This line can be highlighted!');
}
```
---
## Tables
Colons can be used to align columns.
| Tables | Are | Cool |
| ------------- | :-----------: | -----: |
| col 3 is | right-aligned | \$1600 |
| col 2 is | centered | \$12 |
| zebra stripes | are neat | \$1 |
There must be at least 3 dashes separating each header cell. The outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown.
| Markdown | Less | Pretty |
| -------- | --------- | ---------- |
| _Still_ | `renders` | **nicely** |
| 1 | 2 | 3 |
---
## Blockquotes
> Blockquotes are very handy in email to emulate reply text. This line is part of the same quote.
Quote break.
> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can _put_ **Markdown** into a blockquote.
---
## Inline HTML
<dl>
<dt>Definition list</dt>
<dd>Is something people use sometimes.</dd>
<dt>Markdown in HTML</dt>
<dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
</dl>
---
## Line Breaks
Here's a line for us to start with.
This line is separated from the one above by two newlines, so it will be a _separate paragraph_.
This line is also a separate paragraph, but... This line is only separated by a single newline, so it's a separate line in the _same paragraph_.
---
## Admonitions
:::note
This is a note
:::
:::tip
This is a tip
:::
:::important
This is important
:::
:::caution
This is a caution
:::
:::warning
This is a warning
:::

View File

@@ -1,6 +0,0 @@
---
id: doc2
title: Document Number 2
---
This is a link to [another document.](doc3.md) This is a link to an [external page.](http://www.example.com)

View File

@@ -1,14 +0,0 @@
---
id: doc3
title: This is Document Number 3
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ac euismod odio, eu consequat dui. Nullam molestie consectetur risus id imperdiet. Proin sodales ornare turpis, non mollis massa ultricies id. Nam at nibh scelerisque, feugiat ante non, dapibus tortor. Vivamus volutpat diam quis tellus elementum bibendum. Praesent semper gravida velit quis aliquam. Etiam in cursus neque. Nam lectus ligula, malesuada et mauris a, bibendum faucibus mi. Phasellus ut interdum felis. Phasellus in odio pulvinar, porttitor urna eget, fringilla lectus. Aliquam sollicitudin est eros. Mauris consectetur quam vitae mauris interdum hendrerit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Duis et egestas libero, imperdiet faucibus ipsum. Sed posuere eget urna vel feugiat. Vivamus a arcu sagittis, fermentum urna dapibus, congue lectus. Fusce vulputate porttitor nisl, ac cursus elit volutpat vitae. Nullam vitae ipsum egestas, convallis quam non, porta nibh. Morbi gravida erat nec neque bibendum, eu pellentesque velit posuere. Fusce aliquam erat eu massa eleifend tristique.
Sed consequat sollicitudin ipsum eget tempus. Integer a aliquet velit. In justo nibh, pellentesque non suscipit eget, gravida vel lacus. Donec odio ante, malesuada in massa quis, pharetra tristique ligula. Donec eros est, tristique eget finibus quis, semper non nisl. Vivamus et elit nec enim ornare placerat. Sed posuere odio a elit cursus sagittis.
Phasellus feugiat purus eu tortor ultrices finibus. Ut libero nibh, lobortis et libero nec, dapibus posuere eros. Sed sagittis euismod justo at consectetur. Nulla finibus libero placerat, cursus sapien at, eleifend ligula. Vivamus elit nisl, hendrerit ac nibh eu, ultrices tempus dui. Nam tellus neque, commodo non rhoncus eu, gravida in risus. Nullam id iaculis tortor.
Nullam at odio in sem varius tempor sit amet vel lorem. Etiam eu hendrerit nisl. Fusce nibh mauris, vulputate sit amet ex vitae, congue rhoncus nisl. Sed eget tellus purus. Nullam tempus commodo erat ut tristique. Cras accumsan massa sit amet justo consequat eleifend. Integer scelerisque vitae tellus id consectetur.

View File

@@ -0,0 +1,47 @@
---
sidebar_position: 1
---
# Tutorial Intro
Let's discover **Docusaurus in less than 5 minutes**.
## Getting Started
Get started by **creating a new site**.
Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**.
### What you'll need
- [Node.js](https://nodejs.org/en/download/) version 16.14 or above:
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
## Generate a new site
Generate a new Docusaurus site using the **classic template**.
The classic template will automatically be added to your project after you run the command:
```bash
npm init docusaurus@latest my-website classic
```
You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor.
The command also installs all necessary dependencies you need to run Docusaurus.
## Start your site
Run the development server:
```bash
cd my-website
npm run start
```
The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there.
The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes.

View File

@@ -1,17 +0,0 @@
---
id: mdx
title: Powered by MDX
---
You can write JSX and use React components within your Markdown thanks to [MDX](https://mdxjs.com/).
export const Highlight = ({children, color}) => ( <span style={{
backgroundColor: color,
borderRadius: '2px',
color: '#fff',
padding: '0.2rem',
}}> {children} </span> );
<Highlight color="#25c2a0">Docusaurus green</Highlight> and <Highlight color="#1877F2">Facebook blue</Highlight> are my favorite colors.
I can write **Markdown** alongside my _JSX_!

View File

@@ -0,0 +1,8 @@
{
"label": "Tutorial - Basics",
"position": 2,
"link": {
"type": "generated-index",
"description": "5 minutes to learn the most important Docusaurus concepts."
}
}

View File

@@ -0,0 +1,21 @@
---
sidebar_position: 6
---
# Congratulations!
You have just learned the **basics of Docusaurus** and made some changes to the **initial template**.
Docusaurus has **much more to offer**!
Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**.
Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610)
## What's next?
- Read the [official documentation](https://docusaurus.io/).
- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout)
- Add a [search bar](https://docusaurus.io/docs/search)
- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase)
- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support)

View File

@@ -0,0 +1,34 @@
---
sidebar_position: 3
---
# Create a Blog Post
Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed...
## Create your first Post
Create a file at `blog/2021-02-28-greetings.md`:
```md title="blog/2021-02-28-greetings.md"
---
slug: greetings
title: Greetings!
authors:
- name: Joel Marcey
title: Co-creator of Docusaurus 1
url: https://github.com/JoelMarcey
image_url: https://github.com/JoelMarcey.png
- name: Sébastien Lorber
title: Docusaurus maintainer
url: https://sebastienlorber.com
image_url: https://github.com/slorber.png
tags: [greetings]
---
Congratulations, you have made your first post!
Feel free to play around and edit this post as much you like.
```
A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings).

View File

@@ -0,0 +1,55 @@
---
sidebar_position: 2
---
# Create a Document
Documents are **groups of pages** connected through:
- a **sidebar**
- **previous/next navigation**
- **versioning**
## Create your first Doc
Create a Markdown file at `docs/hello.md`:
```md title="docs/hello.md"
# Hello
This is my **first Docusaurus document**!
```
A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello).
## Configure the Sidebar
Docusaurus automatically **creates a sidebar** from the `docs` folder.
Add metadata to customize the sidebar label and position:
```md title="docs/hello.md" {1-4}
---
sidebar_label: 'Hi!'
sidebar_position: 3
---
# Hello
This is my **first Docusaurus document**!
```
It is also possible to create your sidebar explicitly in `sidebars.js`:
```js title="sidebars.js"
module.exports = {
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
// highlight-next-line
items: ['hello'],
},
],
};
```

View File

@@ -0,0 +1,43 @@
---
sidebar_position: 1
---
# Create a Page
Add **Markdown or React** files to `src/pages` to create a **standalone page**:
- `src/pages/index.js``localhost:3000/`
- `src/pages/foo.md``localhost:3000/foo`
- `src/pages/foo/bar.js``localhost:3000/foo/bar`
## Create your first React Page
Create a file at `src/pages/my-react-page.js`:
```jsx title="src/pages/my-react-page.js"
import React from 'react';
import Layout from '@theme/Layout';
export default function MyReactPage() {
return (
<Layout>
<h1>My React page</h1>
<p>This is a React page</p>
</Layout>
);
}
```
A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page).
## Create your first Markdown Page
Create a file at `src/pages/my-markdown-page.md`:
```mdx title="src/pages/my-markdown-page.md"
# My Markdown page
This is a Markdown page
```
A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page).

View File

@@ -0,0 +1,31 @@
---
sidebar_position: 5
---
# Deploy your site
Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**).
It builds your site as simple **static HTML, JavaScript and CSS files**.
## Build your site
Build your site **for production**:
```bash
npm run build
```
The static files are generated in the `build` folder.
## Deploy your site
Test your production build locally:
```bash
npm run serve
```
The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/).
You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**).

View File

@@ -0,0 +1,146 @@
---
sidebar_position: 4
---
# Markdown Features
Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**.
## Front Matter
Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/):
```text title="my-doc.md"
// highlight-start
---
id: my-doc-id
title: My document title
description: My document description
slug: /my-custom-url
---
// highlight-end
## Markdown heading
Markdown text with [links](./hello.md)
```
## Links
Regular Markdown links are supported, using url paths or relative file paths.
```md
Let's see how to [Create a page](/create-a-page).
```
```md
Let's see how to [Create a page](./create-a-page.md).
```
**Result:** Let's see how to [Create a page](./create-a-page.md).
## Images
Regular Markdown images are supported.
You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`):
```md
![Docusaurus logo](/img/docusaurus.png)
```
![Docusaurus logo](/img/docusaurus.png)
You can reference images relative to the current file as well, as shown in [the extra guides](../tutorial-extras/manage-docs-versions.md).
## Code Blocks
Markdown code blocks are supported with Syntax highlighting.
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return (
<h1>Hello, Docusaurus!</h1>
)
}
```
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return <h1>Hello, Docusaurus!</h1>;
}
```
## Admonitions
Docusaurus has a special syntax to create admonitions and callouts:
:::tip My tip
Use this awesome feature option
:::
:::danger Take care
This action is dangerous
:::
:::tip My tip
Use this awesome feature option
:::
:::danger Take care
This action is dangerous
:::
## MDX and React Components
[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**:
```jsx
export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '20px',
color: '#fff',
padding: '10px',
cursor: 'pointer',
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`)
}}>
{children}
</span>
);
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
```
export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '20px',
color: '#fff',
padding: '10px',
cursor: 'pointer',
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`);
}}>
{children}
</span>
);
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
This is <Highlight color="#1877F2">Facebook blue</Highlight> !

View File

@@ -0,0 +1,7 @@
{
"label": "Tutorial - Extras",
"position": 3,
"link": {
"type": "generated-index"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,55 @@
---
sidebar_position: 1
---
# Manage Docs Versions
Docusaurus can manage multiple versions of your docs.
## Create a docs version
Release a version 1.0 of your project:
```bash
npm run docusaurus docs:version 1.0
```
The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created.
Your docs now have 2 versions:
- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs
- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs**
## Add a Version Dropdown
To navigate seamlessly across versions, add a version dropdown.
Modify the `docusaurus.config.js` file:
```js title="docusaurus.config.js"
module.exports = {
themeConfig: {
navbar: {
items: [
// highlight-start
{
type: 'docsVersionDropdown',
},
// highlight-end
],
},
},
};
```
The docs version dropdown appears in your navbar:
![Docs Version Dropdown](./img/docsVersionDropdown.png)
## Update an existing version
It is possible to edit versioned docs in their respective folder:
- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello`
- `docs/hello.md` updates `http://localhost:3000/docs/next/hello`

View File

@@ -0,0 +1,88 @@
---
sidebar_position: 2
---
# Translate your site
Let's translate `docs/intro.md` to French.
## Configure i18n
Modify `docusaurus.config.js` to add support for the `fr` locale:
```js title="docusaurus.config.js"
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
};
```
## Translate a doc
Copy the `docs/intro.md` file to the `i18n/fr` folder:
```bash
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md
```
Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French.
## Start your localized site
Start your site on the French locale:
```bash
npm run start -- --locale fr
```
Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated.
:::caution
In development, you can only use one locale at a same time.
:::
## Add a Locale Dropdown
To navigate seamlessly across languages, add a locale dropdown.
Modify the `docusaurus.config.js` file:
```js title="docusaurus.config.js"
module.exports = {
themeConfig: {
navbar: {
items: [
// highlight-start
{
type: 'localeDropdown',
},
// highlight-end
],
},
},
};
```
The locale dropdown now appears in your navbar:
![Locale Dropdown](./img/localeDropdown.png)
## Build your localized site
Build your site for a specific locale:
```bash
npm run build -- --locale fr
```
Or build your site to include all the locales at once:
```bash
npm run build
```

View File

@@ -1,103 +1,132 @@
module.exports = {
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const lightCodeTheme = require('prism-react-renderer/themes/github');
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'My Site',
tagline: 'The tagline of my site',
tagline: 'Dinosaurs are cool',
url: 'https://your-docusaurus-test-site.com',
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'facebook', // Usually your GitHub org/user name.
projectName: 'docusaurus', // Usually your repo name.
themeConfig: {
navbar: {
title: 'My Site',
logo: {
alt: 'My Site Logo',
src: 'img/logo.svg',
},
links: [
{
to: 'docs/doc1',
activeBasePath: 'docs',
label: 'Docs',
position: 'left',
},
{to: 'blog', label: 'Blog', position: 'left'},
{
href: 'https://github.com/facebook/docusaurus',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Docs',
items: [
{
label: 'Style Guide',
to: 'docs/doc1',
},
{
label: 'Second Doc',
to: 'docs/doc2',
},
],
},
{
title: 'Community',
items: [
{
label: 'Stack Overflow',
href: 'https://stackoverflow.com/questions/tagged/docusaurus',
},
{
label: 'Discord',
href: 'https://discordapp.com/invite/docusaurus',
},
{
label: 'Twitter',
href: 'https://twitter.com/docusaurus',
},
],
},
{
title: 'More',
items: [
{
label: 'Blog',
to: 'blog',
},
{
label: 'GitHub',
href: 'https://github.com/facebook/docusaurus',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`,
},
// Even if you don't use internalization, you can use this field to set useful
// metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
'@docusaurus/preset-classic',
{
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
sidebarPath: require.resolve('./sidebars.js'),
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/facebook/docusaurus/edit/master/website/',
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
},
blog: {
showReadingTime: true,
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/facebook/docusaurus/edit/master/website/blog/',
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
},
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
},
}),
],
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
navbar: {
title: 'My Site',
logo: {
alt: 'My Site Logo',
src: 'img/logo.svg',
},
items: [
{
type: 'doc',
docId: 'intro',
position: 'left',
label: 'Tutorial',
},
{to: '/blog', label: 'Blog', position: 'left'},
{
href: 'https://github.com/facebook/docusaurus',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Docs',
items: [
{
label: 'Tutorial',
to: '/docs/intro',
},
],
},
{
title: 'Community',
items: [
{
label: 'Stack Overflow',
href: 'https://stackoverflow.com/questions/tagged/docusaurus',
},
{
label: 'Discord',
href: 'https://discordapp.com/invite/docusaurus',
},
{
label: 'Twitter',
href: 'https://twitter.com/docusaurus',
},
],
},
{
title: 'More',
items: [
{
label: 'Blog',
to: '/blog',
},
{
label: 'GitHub',
href: 'https://github.com/facebook/docusaurus',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
}),
};
module.exports = config;

View File

@@ -1,23 +1,31 @@
{
"name": "docusaurus-2",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy"
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "^2.0.0-alpha.54",
"@docusaurus/preset-classic": "^2.0.0-alpha.54",
"classnames": "^2.2.6",
"react": "^16.8.4",
"react-dom": "^16.8.4"
"@docusaurus/core": "2.0.1",
"@docusaurus/preset-classic": "2.0.1",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.1"
},
"browserslist": {
"production": [
">0.2%",
">0.5%",
"not dead",
"not op_mini all"
],
@@ -26,5 +34,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"engines": {
"node": ">=16.14"
}
}

View File

@@ -1,6 +1,31 @@
module.exports = {
someSidebar: {
Docusaurus: ['doc1', 'doc2', 'doc3'],
Features: ['mdx'],
},
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
// But you can create a sidebar manually
/*
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
},
],
*/
};
module.exports = sidebars;

View File

@@ -0,0 +1,64 @@
import React from 'react';
import clsx from 'clsx';
import styles from './styles.module.css';
const FeatureList = [
{
title: 'Easy to Use',
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
description: (
<>
Docusaurus was designed from the ground up to be easily installed and
used to get your website up and running quickly.
</>
),
},
{
title: 'Focus on What Matters',
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
description: (
<>
Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go
ahead and move your docs into the <code>docs</code> directory.
</>
),
},
{
title: 'Powered by React',
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
description: (
<>
Extend or customize your website layout by reusing React. Docusaurus can
be extended while reusing the same header and footer.
</>
),
},
];
function Feature({Svg, title, description}) {
return (
<div className={clsx('col col--4')}>
<div className="text--center">
<Svg className={styles.featureSvg} role="img" />
</div>
<div className="text--center padding-horiz--md">
<h3>{title}</h3>
<p>{description}</p>
</div>
</div>
);
}
export default function HomepageFeatures() {
return (
<section className={styles.features}>
<div className="container">
<div className="row">
{FeatureList.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,11 @@
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}

View File

@@ -1,4 +1,3 @@
/* stylelint-disable docusaurus/copyright-header */
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
@@ -7,19 +6,25 @@
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: rgb(33, 175, 144);
--ifm-color-primary-darker: rgb(31, 165, 136);
--ifm-color-primary-darkest: rgb(26, 136, 112);
--ifm-color-primary-light: rgb(70, 203, 174);
--ifm-color-primary-lighter: rgb(102, 212, 189);
--ifm-color-primary-lightest: rgb(146, 224, 208);
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
.docusaurus-highlight-code-line {
background-color: rgb(72, 77, 91);
display: block;
margin: 0 calc(-1 * var(--ifm-pre-padding));
padding: 0 var(--ifm-pre-padding);
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}

View File

@@ -1,97 +1,41 @@
import React from 'react';
import classnames from 'classnames';
import Layout from '@theme/Layout';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import useBaseUrl from '@docusaurus/useBaseUrl';
import styles from './styles.module.css';
import Layout from '@theme/Layout';
import HomepageFeatures from '@site/src/components/HomepageFeatures';
const features = [
{
title: <>Easy to Use</>,
imageUrl: 'img/undraw_docusaurus_mountain.svg',
description: (
<>
Docusaurus was designed from the ground up to be easily installed and
used to get your website up and running quickly.
</>
),
},
{
title: <>Focus on What Matters</>,
imageUrl: 'img/undraw_docusaurus_tree.svg',
description: (
<>
Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go
ahead and move your docs into the <code>docs</code> directory.
</>
),
},
{
title: <>Powered by React</>,
imageUrl: 'img/undraw_docusaurus_react.svg',
description: (
<>
Extend or customize your website layout by reusing React. Docusaurus can
be extended while reusing the same header and footer.
</>
),
},
];
import styles from './index.module.css';
function Feature({imageUrl, title, description}) {
const imgUrl = useBaseUrl(imageUrl);
function HomepageHeader() {
const {siteConfig} = useDocusaurusContext();
return (
<div className={classnames('col col--4', styles.feature)}>
{imgUrl && (
<div className="text--center">
<img className={styles.featureImage} src={imgUrl} alt={title} />
<header className={clsx('hero hero--primary', styles.heroBanner)}>
<div className="container">
<h1 className="hero__title">{siteConfig.title}</h1>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<div className={styles.buttons}>
<Link
className="button button--secondary button--lg"
to="/docs/intro">
Docusaurus Tutorial - 5min
</Link>
</div>
)}
<h3>{title}</h3>
<p>{description}</p>
</div>
</div>
</header>
);
}
function Home() {
const context = useDocusaurusContext();
const {siteConfig = {}} = context;
export default function Home() {
const {siteConfig} = useDocusaurusContext();
return (
<Layout
title={`Hello from ${siteConfig.title}`}
description="Description will go into a meta tag in <head />">
<header className={classnames('hero hero--primary', styles.heroBanner)}>
<div className="container">
<h1 className="hero__title">{siteConfig.title}</h1>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<div className={styles.buttons}>
<Link
className={classnames(
'button button--outline button--secondary button--lg',
styles.getStarted,
)}
to={useBaseUrl('docs/doc1')}>
Get Started
</Link>
</div>
</div>
</header>
<HomepageHeader />
<main>
{features && features.length && (
<section className={styles.features}>
<div className="container">
<div className="row">
{features.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
)}
<HomepageFeatures />
</main>
</Layout>
);
}
export default Home;

View File

@@ -1,4 +1,3 @@
/* stylelint-disable docusaurus/copyright-header */
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
@@ -11,7 +10,7 @@
overflow: hidden;
}
@media screen and (max-width: 966px) {
@media screen and (max-width: 996px) {
.heroBanner {
padding: 2rem;
}
@@ -22,15 +21,3 @@
align-items: center;
justify-content: center;
}
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureImage {
height: 200px;
width: 200px;
}

View File

@@ -0,0 +1,7 @@
---
title: Markdown page example
---
# Markdown page example
You don't need React to write simple standalone pages.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 B

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1,4 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1088" height="687.962" viewBox="0 0 1088 687.962">
<title>Easy to Use</title>
<g id="Group_12" data-name="Group 12" transform="translate(-57 -56)">
<g id="Group_11" data-name="Group 11" transform="translate(57 56)">
<path id="Path_83" data-name="Path 83" d="M1017.81,560.461c-5.27,45.15-16.22,81.4-31.25,110.31-20,38.52-54.21,54.04-84.77,70.28a193.275,193.275,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.282,657.282,0,0,0-104.09-13.16q-14.97-.675-29.97-.67c-15.42.02-293.07,5.29-360.67-131.57-16.69-33.76-28.13-75-32.24-125.27-11.63-142.12,52.29-235.46,134.74-296.47,155.97-115.41,369.76-110.57,523.43,7.88C941.15,276.621,1036.99,396.031,1017.81,560.461Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -1,4 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1041.277" height="554.141" viewBox="0 0 1041.277 554.141">
<title>Powered by React</title>
<g id="Group_24" data-name="Group 24" transform="translate(-440 -263)">
<g id="Group_23" data-name="Group 23" transform="translate(439.989 262.965)">
<path id="Path_299" data-name="Path 299" d="M1040.82,611.12q-1.74,3.75-3.47,7.4-2.7,5.67-5.33,11.12c-.78,1.61-1.56,3.19-2.32,4.77-8.6,17.57-16.63,33.11-23.45,45.89A73.21,73.21,0,0,1,942.44,719l-151.65,1.65h-1.6l-13,.14-11.12.12-34.1.37h-1.38l-17.36.19h-.53l-107,1.16-95.51,1-11.11.12-69,.75H429l-44.75.48h-.48l-141.5,1.53-42.33.46a87.991,87.991,0,0,1-10.79-.54h0c-1.22-.14-2.44-.3-3.65-.49a87.38,87.38,0,0,1-51.29-27.54C116,678.37,102.75,655,93.85,629.64q-1.93-5.49-3.6-11.12C59.44,514.37,97,380,164.6,290.08q4.25-5.64,8.64-11l.07-.08c20.79-25.52,44.1-46.84,68.93-62,44-26.91,92.75-34.49,140.7-11.9,40.57,19.12,78.45,28.11,115.17,30.55,3.71.24,7.42.42,11.11.53,84.23,2.65,163.17-27.7,255.87-47.29,3.69-.78,7.39-1.55,11.12-2.28,66.13-13.16,139.49-20.1,226.73-5.51a189.089,189.089,0,0,1,26.76,6.4q5.77,1.86,11.12,4c41.64,16.94,64.35,48.24,74,87.46q1.37,5.46,2.37,11.11C1134.3,384.41,1084.19,518.23,1040.82,611.12Z" transform="translate(-79.34 -172.91)" fill="#f2f2f2"/>

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "5.0.4",
"version": "5.1.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",
@@ -35,7 +34,6 @@
"aggregate-error": "3.0.1",
"async-retry": "1.2.3",
"async-sema": "2.1.4",
"boxen": "4.2.0",
"cross-spawn": "6.0.5",
"end-of-stream": "1.4.1",
"fs-extra": "10.0.0",

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);
@@ -92,12 +96,28 @@ export default async function download(
await removeFile(basePath, name);
return;
}
// If a file didn't change, do not re-download it.
if (Array.isArray(filesChanged) && !filesChanged.includes(name)) {
return;
}
// Some builders resolve symlinks and return both
// a file, node_modules/<symlink>/package.json, and
// node_modules/<symlink>, a symlink.
// Removing the file matches how the yazl lambda zip
// behaves so we can use download() with `vercel build`.
const parts = name.split('/');
for (let i = 1; i < parts.length; i++) {
const dir = parts.slice(0, i).join('/');
const parent = files[dir];
if (parent && isSymbolicLink(parent.mode)) {
console.warn(
`Warning: file "${name}" is within a symlinked directory "${dir}" and will be ignored`
);
return;
}
}
const file = files[name];
const fsPath = path.join(basePath, name);

View File

@@ -305,67 +305,46 @@ export async function scanParentDirs(
): Promise<ScanParentDirsResult> {
assert(path.isAbsolute(destPath));
let cliType: CliType = 'yarn';
let packageJson: PackageJson | undefined;
let packageJsonPath: string | undefined;
let currentDestPath = destPath;
const pkgJsonPath = await walkParentDirs({
base: '/',
start: destPath,
filename: 'package.json',
});
const packageJson: PackageJson | undefined =
readPackageJson && pkgJsonPath
? JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'))
: undefined;
const [yarnLockPath, npmLockPath, pnpmLockPath] = await walkParentDirsMulti({
base: '/',
start: destPath,
filenames: ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml'],
});
let lockfileVersion: number | undefined;
let cliType: CliType = 'yarn';
// eslint-disable-next-line no-constant-condition
while (true) {
packageJsonPath = path.join(currentDestPath, 'package.json');
// eslint-disable-next-line no-await-in-loop
if (await fs.pathExists(packageJsonPath)) {
// Only read the contents of the *first* `package.json` file found,
// since that's the one related to this installation.
if (readPackageJson && !packageJson) {
// eslint-disable-next-line no-await-in-loop
packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
}
const [hasYarnLock, packageLockJson, pnpmLockYaml] = await Promise.all([
Boolean(yarnLockPath),
npmLockPath
? readConfigFile<{ lockfileVersion: number }>(npmLockPath)
: null,
pnpmLockPath
? readConfigFile<{ lockfileVersion: number }>(pnpmLockPath)
: null,
]);
// eslint-disable-next-line no-await-in-loop
const [packageLockJson, hasYarnLock, pnpmLockYaml] = await Promise.all([
fs
.readJson(path.join(currentDestPath, 'package-lock.json'))
.catch(error => {
// If the file doesn't exist, fail gracefully otherwise error
if (error.code === 'ENOENT') {
return null;
}
throw error;
}),
fs.pathExists(path.join(currentDestPath, 'yarn.lock')),
readConfigFile<{ lockfileVersion: number }>(
path.join(currentDestPath, 'pnpm-lock.yaml')
),
]);
// Priority order is Yarn > pnpm > npm
// - find highest priority lock file and use that
if (hasYarnLock) {
cliType = 'yarn';
} else if (pnpmLockYaml) {
cliType = 'pnpm';
// just ensure that it is read as a number and not a string
lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
} else if (packageLockJson) {
cliType = 'npm';
lockfileVersion = packageLockJson.lockfileVersion;
}
// Only stop iterating if a lockfile was found, because it's possible
// that the lockfile is in a higher path than where the `package.json`
// file was found.
if (packageLockJson || hasYarnLock || pnpmLockYaml) {
break;
}
}
const newDestPath = path.dirname(currentDestPath);
if (currentDestPath === newDestPath) break;
currentDestPath = newDestPath;
// Priority order is Yarn > pnpm > npm
if (hasYarnLock) {
cliType = 'yarn';
} else if (pnpmLockYaml) {
cliType = 'pnpm';
// just ensure that it is read as a number and not a string
lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
} else if (packageLockJson) {
cliType = 'npm';
lockfileVersion = packageLockJson.lockfileVersion;
}
const packageJsonPath = pkgJsonPath || undefined;
return { cliType, packageJson, lockfileVersion, packageJsonPath };
}
@@ -387,11 +366,48 @@ export async function walkParentDirs({
}
parent = path.dirname(current);
if (parent === current) {
// Reached root directory of the filesystem
break;
}
}
return null;
}
async function walkParentDirsMulti({
base,
start,
filenames,
}: {
base: string;
start: string;
filenames: string[];
}): Promise<(string | undefined)[]> {
let parent = '';
for (let current = start; base.length <= current.length; current = parent) {
const fullPaths = filenames.map(f => path.join(current, f));
const existResults = await Promise.all(
fullPaths.map(f => fs.pathExists(f))
);
const foundOneOrMore = existResults.some(b => b);
if (foundOneOrMore) {
return fullPaths.map((f, i) => (existResults[i] ? f : undefined));
}
parent = path.dirname(current);
if (parent === current) {
// Reached root directory of the filesystem
break;
}
}
return [];
}
function isSet<T>(v: any): v is Set<T> {
return v?.constructor?.name === 'Set';
}

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';
@@ -46,6 +50,7 @@ export {
createLambda,
Prerender,
download,
downloadFile,
DownloadedFiles,
getWriteableDirectory,
glob,

View File

@@ -1,22 +1,12 @@
import path from 'path';
import fs from 'fs-extra';
import {
packAndDeploy,
testDeployment,
// @ts-ignore
} from '../../../test/lib/deployment/test-deployment';
jest.setTimeout(4 * 60 * 1000);
const builderUrl = '@canary';
let buildUtilsUrl: string;
beforeAll(async () => {
const buildUtilsPath = path.resolve(__dirname, '..');
buildUtilsUrl = await packAndDeploy(buildUtilsPath);
console.log('buildUtilsUrl', buildUtilsUrl);
});
const fixturesPath = path.resolve(__dirname, 'fixtures');
// Fixtures that have separate tests and should be skipped in the loop
@@ -42,10 +32,7 @@ for (const fixture of fs.readdirSync(fixturesPath)) {
// eslint-disable-next-line no-loop-func
it(`Should build "${fixture}"`, async () => {
await expect(
testDeployment(
{ builderUrl, buildUtilsUrl },
path.join(fixturesPath, fixture)
)
testDeployment(path.join(fixturesPath, fixture))
).resolves.toBeDefined();
});
}
@@ -68,10 +55,7 @@ for (const builder of buildersToTestWith) {
// eslint-disable-next-line no-loop-func
it(`Should build "${builder}/${fixture}"`, async () => {
await expect(
testDeployment(
{ builderUrl, buildUtilsUrl },
path.join(fixturesPath2, fixture)
)
testDeployment(path.join(fixturesPath2, fixture))
).resolves.toBeDefined();
});
}

View File

@@ -19,7 +19,7 @@ import {
Meta,
} from '../src';
jest.setTimeout(7 * 1000);
jest.setTimeout(10 * 1000);
async function expectBuilderError(promise: Promise<any>, pattern: string) {
let result;
@@ -170,6 +170,53 @@ it('should create zip files with symlinks properly', async () => {
assert(aStat.isFile());
});
it('should download symlinks even with incorrect file', async () => {
if (process.platform === 'win32') {
console.log('Skipping test on windows');
return;
}
const files = {
'dir/file.txt': new FileBlob({
mode: 33188,
contentType: undefined,
data: 'file text',
}),
linkdir: new FileBlob({
mode: 41453,
contentType: undefined,
data: 'dir',
}),
'linkdir/file.txt': new FileBlob({
mode: 33188,
contentType: undefined,
data: 'this file should be discarded',
}),
};
const outDir = path.join(__dirname, 'symlinks-out');
await fs.remove(outDir);
await fs.mkdirp(outDir);
await download(files, outDir);
const [dir, file, linkdir] = await Promise.all([
fs.lstat(path.join(outDir, 'dir')),
fs.lstat(path.join(outDir, 'dir/file.txt')),
fs.lstat(path.join(outDir, 'linkdir')),
]);
expect(dir.isFile()).toBe(false);
expect(dir.isSymbolicLink()).toBe(false);
expect(file.isFile()).toBe(true);
expect(file.isSymbolicLink()).toBe(false);
expect(linkdir.isSymbolicLink()).toBe(true);
expect(warningMessages).toEqual([
'Warning: file "linkdir/file.txt" is within a symlinked directory "linkdir" and will be ignored',
]);
});
it('should only match supported node versions, otherwise throw an error', async () => {
expect(await getSupportedNodeVersion('12.x', false)).toHaveProperty(
'major',
@@ -454,6 +501,7 @@ it('should return lockfileVersion 2 with npm7', async () => {
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('npm');
expect(result.lockfileVersion).toEqual(2);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should not return lockfileVersion with yarn', async () => {
@@ -461,6 +509,7 @@ it('should not return lockfileVersion with yarn', async () => {
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('yarn');
expect(result.lockfileVersion).toEqual(undefined);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should return lockfileVersion 1 with older versions of npm', async () => {
@@ -468,6 +517,7 @@ it('should return lockfileVersion 1 with older versions of npm', async () => {
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('npm');
expect(result.lockfileVersion).toEqual(1);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should detect npm Workspaces', async () => {
@@ -475,20 +525,45 @@ it('should detect npm Workspaces', async () => {
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('npm');
expect(result.lockfileVersion).toEqual(2);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should detect pnpm', async () => {
it('should detect pnpm without workspace', async () => {
const fixture = path.join(__dirname, 'fixtures', '22-pnpm');
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('pnpm');
expect(result.lockfileVersion).toEqual(5.3);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should detect pnpm Workspaces', async () => {
const fixture = path.join(__dirname, 'fixtures', '23-pnpm-workspaces/a');
it('should detect pnpm with workspaces', async () => {
const fixture = path.join(__dirname, 'fixtures', '23-pnpm-workspaces/c');
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('pnpm');
expect(result.lockfileVersion).toEqual(5.3);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should detect package.json in nested backend', async () => {
const fixture = path.join(
__dirname,
'../../node/test/fixtures/18.1-nested-packagejson/backend'
);
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('yarn');
expect(result.lockfileVersion).toEqual(undefined);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should detect package.json in nested frontend', async () => {
const fixture = path.join(
__dirname,
'../../node/test/fixtures/18.1-nested-packagejson/frontend'
);
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('yarn');
expect(result.lockfileVersion).toEqual(undefined);
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
});
it('should only invoke `runNpmInstall()` once per `package.json` file (serial)', async () => {

View File

@@ -53,13 +53,13 @@ At this point you can make modifications to the CLI source code and test them ou
cd packages/cli
```
From within the `packages/cli` directory, you can use the `ts-eager` command line tool to quickly excute Vercel CLI from its TypeScript source code directly (without having to manually compile first). For example:
From within the `packages/cli` directory, you can use the "dev" script to quickly execute Vercel CLI from its TypeScript source code directly (without having to manually compile first). For example:
```bash
npx ts-eager src
npx ts-eager src login
npx ts-eager src switch --debug
npx ts-eager src dev
yarn dev deploy
yarn dev whoami
yarn dev login
yarn dev switch --debug
```
When you are satisfied with your changes, make a commit and create a pull request!

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "27.2.0",
"version": "27.3.6",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -16,10 +16,9 @@
"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": "node -r ts-eager/register ./scripts/build.ts",
"build-dev": "node -r ts-eager/register ./scripts/build.ts --dev"
"build": "ts-node ./scripts/build.ts",
"dev": "ts-node ./src/index.ts"
},
"bin": {
"vc": "./dist/index.js",
@@ -42,16 +41,16 @@
"node": ">= 14"
},
"dependencies": {
"@vercel/build-utils": "5.0.4",
"@vercel/go": "2.0.8",
"@vercel/hydrogen": "0.0.5",
"@vercel/next": "3.1.8",
"@vercel/node": "2.4.5",
"@vercel/python": "3.1.0",
"@vercel/redwood": "1.0.9",
"@vercel/remix": "1.0.10",
"@vercel/ruby": "1.3.16",
"@vercel/static-build": "1.0.9",
"@vercel/build-utils": "5.1.1",
"@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/ruby": "1.3.22",
"@vercel/static-build": "1.0.15",
"update-notifier": "5.1.0"
},
"devDependencies": {
@@ -59,6 +58,7 @@
"@next/env": "11.1.2",
"@sentry/node": "5.5.0",
"@sindresorhus/slugify": "0.11.0",
"@swc/core": "1.2.218",
"@tootallnate/once": "1.1.2",
"@types/ansi-escapes": "3.0.0",
"@types/ansi-regex": "4.0.0",
@@ -96,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.3",
"@vercel/frameworks": "1.1.1",
"@vercel/fs-detectors": "2.0.1",
"@vercel/client": "12.1.9",
"@vercel/frameworks": "1.1.2",
"@vercel/fs-detectors": "2.0.4",
"@vercel/fun": "1.0.4",
"@vercel/ncc": "0.24.0",
"@zeit/source-map-support": "0.6.2",
@@ -111,6 +111,7 @@
"async-retry": "1.1.3",
"async-sema": "2.1.4",
"ava": "2.2.0",
"boxen": "4.2.0",
"bytes": "3.0.0",
"chalk": "4.1.0",
"chance": "1.1.7",
@@ -169,8 +170,8 @@
"title": "3.4.1",
"tmp-promise": "1.0.3",
"tree-kill": "1.2.2",
"ts-node": "8.3.0",
"typescript": "4.3.4",
"ts-node": "10.9.1",
"typescript": "4.7.4",
"universal-analytics": "0.4.20",
"utility-types": "2.1.0",
"which": "2.0.2",

View File

@@ -27,33 +27,31 @@ function envToString(key: string) {
}
async function main() {
const isDev = process.argv[2] === '--dev';
// Read the secrets from GitHub Actions and generate a file.
// During local development, these secrets will be empty.
await createConstants();
if (!isDev) {
// Read the secrets from GitHub Actions and generate a file.
// During local development, these secrets will be empty.
await createConstants();
// `vercel dev` uses chokidar to watch the filesystem, but opts-out of the
// `fsevents` feature using `useFsEvents: false`, so delete the module here so
// that it is not compiled by ncc, which makes the npm package size larger
// than necessary.
await remove(join(dirRoot, '../../node_modules/fsevents'));
// `vercel dev` uses chokidar to watch the filesystem, but opts-out of the
// `fsevents` feature using `useFsEvents: false`, so delete the module here so
// that it is not compiled by ncc, which makes the npm package size larger
// than necessary.
await remove(join(dirRoot, '../../node_modules/fsevents'));
// Compile the `doT.js` template files for `vercel dev`
console.log();
await execa(process.execPath, [join(__dirname, 'compile-templates.js')], {
stdio: 'inherit',
});
}
// Compile the `doT.js` template files for `vercel dev`
console.log();
await execa(process.execPath, [join(__dirname, 'compile-templates.js')], {
stdio: 'inherit',
});
// Do the initial `ncc` build
console.log();
const args = ['ncc', 'build', '--external', 'update-notifier'];
if (isDev) {
args.push('--source-map');
}
args.push('src/index.ts');
const args = [
'ncc',
'build',
'--external',
'update-notifier',
'src/index.ts',
];
await execa('yarn', args, { stdio: 'inherit', cwd: dirRoot });
// `ncc` has some issues with `@vercel/fun`'s runtime files:
@@ -79,6 +77,7 @@ async function main() {
// Band-aid to bundle stuff that `ncc` neglects to bundle
await cpy(join(dirRoot, 'src/util/projects/VERCEL_DIR_README.txt'), distRoot);
await cpy(join(dirRoot, 'src/util/dev/builder-worker.js'), distRoot);
console.log('Finished building Vercel CLI');
}

View File

@@ -22,19 +22,7 @@ export default async function ls(
) {
const { output } = client;
const { '--next': nextTimestamp } = opts;
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 { contextName } = await getScope(client);
if (typeof nextTimestamp !== undefined && Number.isNaN(nextTimestamp)) {
output.error('Please provide a number for flag --next');

View File

@@ -23,19 +23,7 @@ export default async function rm(
args: string[]
) {
const { output } = client;
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 { contextName } = await getScope(client);
const [aliasOrId] = args;

View File

@@ -15,10 +15,9 @@ import { isValidName } from '../../util/is-valid-name';
import handleCertError from '../../util/certs/handle-cert-error';
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
import link from '../../util/output/link';
import { User } from '../../types';
import { getCommandName } from '../../util/pkg-name';
import toHost from '../../util/to-host';
import { VercelConfig } from '../../util/dev/types';
import type { VercelConfig } from '@vercel/client';
type Options = {
'--debug': boolean;
@@ -30,23 +29,9 @@ export default async function set(
opts: Partial<Options>,
args: string[]
) {
const { output, localConfig } = client;
const setStamp = stamp();
let user: User;
let contextName: string | null = null;
try {
({ contextName, user } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const { output, localConfig } = client;
const { contextName, user } = await getScope(client);
// If there are more than two args we have to error
if (args.length > 2) {

View File

@@ -66,7 +66,7 @@ interface SerializedBuilder extends Builder {
/**
* Contents of the `builds.json` file.
*/
interface BuildsManifest {
export interface BuildsManifest {
'//': string;
target: string;
argv: string[];
@@ -189,20 +189,6 @@ export default async function main(client: Client): Promise<number> {
project = await readProjectSettings(join(cwd, VERCEL_DIR));
}
// TODO: load env vars from the API, fall back to local files if that fails
const envPath = await checkExists([
join(cwd, VERCEL_DIR, `.env.${target}.local`),
join(cwd, `.env`),
]);
if (envPath) {
dotenv.config({ path: envPath, debug: client.output.isDebugEnabled() });
output.log(`Loaded env from "${relative(cwd, envPath)}"`);
}
// Some build processes use these env vars to platform detect Vercel
process.env.VERCEL = '1';
process.env.NOW_BUILDER = '1';
// Delete output directory from potential previous build
const outputDir = argv['--output']
? resolve(argv['--output'])
@@ -215,7 +201,36 @@ export default async function main(client: Client): Promise<number> {
argv: process.argv,
};
const envToUnset = new Set<string>(['VERCEL', 'NOW_BUILDER']);
try {
const envPath = join(cwd, VERCEL_DIR, `.env.${target}.local`);
// TODO (maybe?): load env vars from the API, fall back to the local file if that fails
const dotenvResult = dotenv.config({
path: envPath,
debug: client.output.isDebugEnabled(),
});
if (dotenvResult.error) {
output.debug(
`Failed loading environment variables: ${dotenvResult.error}`
);
} else if (dotenvResult.parsed) {
for (const key of Object.keys(dotenvResult.parsed)) {
envToUnset.add(key);
}
output.debug(`Loaded environment variables from "${envPath}"`);
}
// For Vercel Analytics support
if (project.settings.analyticsId) {
envToUnset.add('VERCEL_ANALYTICS_ID');
process.env.VERCEL_ANALYTICS_ID = project.settings.analyticsId;
}
// Some build processes use these env vars to platform detect Vercel
process.env.VERCEL = '1';
process.env.NOW_BUILDER = '1';
return await doBuild(client, project, buildsJson, cwd, outputDir);
} catch (err: any) {
output.prettyError(err);
@@ -230,6 +245,12 @@ export default async function main(client: Client): Promise<number> {
await fs.writeJSON(configJsonPath, { version: 3 }, { spaces: 2 });
return 1;
} finally {
// Unset environment variables that were added by dotenv
// (this is mostly for the unit tests)
for (const key of envToUnset) {
delete process.env[key];
}
}
}
@@ -278,6 +299,7 @@ async function doBuild(
let builds = vercelConfig?.builds || [];
let zeroConfigRoutes: Route[] = [];
let isZeroConfig = false;
if (builds.length > 0) {
output.warn(
@@ -286,6 +308,7 @@ async function doBuild(
builds = builds.map(b => expandBuild(files, b)).flat();
} else {
// Zero config
isZeroConfig = true;
// Detect the Vercel Builders that will need to be invoked
const detectedBuilders = await detectBuilders(files, pkg, {
@@ -400,16 +423,18 @@ async function doBuild(
try {
const { builder, pkg: builderPkg } = builderWithPkg;
const buildConfig: Config = {
outputDirectory: project.settings.outputDirectory ?? undefined,
...build.config,
projectSettings: project.settings,
installCommand: project.settings.installCommand ?? undefined,
devCommand: project.settings.devCommand ?? undefined,
buildCommand: project.settings.buildCommand ?? undefined,
framework: project.settings.framework,
nodeVersion: project.settings.nodeVersion,
};
const buildConfig: Config = isZeroConfig
? {
outputDirectory: project.settings.outputDirectory ?? undefined,
...build.config,
projectSettings: project.settings,
installCommand: project.settings.installCommand ?? undefined,
devCommand: project.settings.devCommand ?? undefined,
buildCommand: project.settings.buildCommand ?? undefined,
framework: project.settings.framework,
nodeVersion: project.settings.nodeVersion,
}
: build.config || {};
const buildOptions: BuildOptions = {
files: filesMap,
entrypoint: build.src,
@@ -435,7 +460,7 @@ async function doBuild(
build,
builder,
builderPkg,
vercelConfig?.cleanUrls
vercelConfig
).then(
override => {
if (override) overrides.push(override);
@@ -444,6 +469,8 @@ async function doBuild(
)
);
} catch (err: any) {
output.prettyError(err);
const writeConfigJsonPromise = fs.writeJSON(
join(outputDir, 'config.json'),
{ version: 3 },
@@ -596,7 +623,7 @@ function mergeImages(
let images: BuildResultV2Typical['images'] = undefined;
for (const result of buildResults) {
if ('images' in result && result.images) {
images = Object.assign({} || images, result.images);
images = Object.assign({}, images, result.images);
}
}
return images;
@@ -614,14 +641,3 @@ function mergeWildcard(
}
return wildcard;
}
async function checkExists(paths: Iterable<string>) {
for (const path of paths) {
try {
await fs.stat(path);
return path;
} catch (err: any) {
if (err.code !== 'ENOENT') throw err;
}
}
}

View File

@@ -5,6 +5,7 @@ import stamp from '../../util/output/stamp';
import createCertFromFile from '../../util/certs/create-cert-from-file';
import createCertForCns from '../../util/certs/create-cert-for-cns';
import { getCommandName } from '../../util/pkg-name';
import { Cert } from '../../types';
interface Options {
'--overwrite'?: boolean;
@@ -21,7 +22,7 @@ async function add(
const { output } = client;
const addStamp = stamp();
let cert;
let cert: Cert | Error;
const {
'--overwrite': overwite,
@@ -30,18 +31,7 @@ async function add(
'--ca': caPath,
} = opts;
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 { contextName } = await getScope(client);
if (overwite) {
output.error('Overwrite option is deprecated');

View File

@@ -39,18 +39,7 @@ export default async function issue(
'--ca': caPath,
} = opts;
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 { contextName } = await getScope(client);
if (overwite) {
output.error('Overwrite option is deprecated');

View File

@@ -21,18 +21,8 @@ async function ls(
): Promise<number> {
const { output } = client;
const { '--next': nextTimestamp } = opts;
let contextName = null;
const { contextName } = await getScope(client);
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
if (typeof nextTimestamp !== 'undefined' && Number.isNaN(nextTimestamp)) {
output.error('Please provide a number for flag --next');
return 1;

View File

@@ -17,21 +17,9 @@ import { getCommandName } from '../../util/pkg-name';
type Options = {};
async function rm(client: Client, opts: Options, args: string[]) {
const { output } = client;
const rmStamp = stamp();
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 { output } = client;
const { contextName } = await getScope(client);
if (args.length !== 1) {
output.error(

View File

@@ -16,26 +16,27 @@ export const help = () => `
dev Start a local development server
env Manages the Environment Variables for your current Project
git Manage Git provider repository for your current Project
help [cmd] Displays complete help for [cmd]
init [example] Initialize an example project
ls | list [app] Lists deployments
inspect [id] Displays information related to a deployment
link [path] Link local directory to a Vercel Project
ls | list [app] Lists deployments
login [email] Logs into your account or creates a new one
logout Logs out of your account
pull [path] Pull your Project Settings from the cloud
switch [scope] Switches between teams and your personal account
help [cmd] Displays complete help for [cmd]
${chalk.dim('Advanced')}
rm | remove [id] Removes a deployment
alias [cmd] Manages your domain aliases
bisect Use binary search to find the deployment that introduced a bug
domains [name] Manages your domain names
projects Manages your Projects
dns [name] Manages your DNS records
certs [cmd] Manages your SSL certificates
secrets [name] Manages your global Secrets, for use in Environment Variables
dns [name] Manages your DNS records
domains [name] Manages your domain names
logs [url] Displays the logs for a deployment
projects Manages your Projects
rm | remove [id] Removes a deployment
secrets [name] Manages your global Secrets, for use in Environment Variables
teams Manages your teams
whoami Shows the username of the currently logged in user

View File

@@ -3,7 +3,7 @@ import fs from 'fs-extra';
import bytes from 'bytes';
import chalk from 'chalk';
import { join, resolve, basename } from 'path';
import { Dictionary, fileNameSymbol, VercelConfig } from '@vercel/client';
import { fileNameSymbol, VercelConfig } from '@vercel/client';
import code from '../../util/output/code';
import highlight from '../../util/output/highlight';
import { readLocalConfig } from '../../util/config/files';
@@ -38,6 +38,7 @@ import {
ConflictingPathSegment,
BuildError,
NotDomainOwner,
isAPIError,
} from '../../util/errors-ts';
import { SchemaValidationFailed } from '../../util/errors';
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
@@ -65,6 +66,8 @@ 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 { parseEnv } from '../../util/parse-env';
import { errorToString, isErrnoException, isError } from '../../util/is-error';
export default async (client: Client) => {
const { output } = client;
@@ -216,6 +219,22 @@ export default async (client: Client) => {
}
const prebuiltBuild = await getPrebuiltJson(path);
// Ensure that there was not a build error
const prebuiltError =
prebuiltBuild?.error ||
prebuiltBuild?.builds?.find(build => 'error' in build)?.error;
if (prebuiltError) {
output.log(
`Prebuilt deployment cannot be created because ${getCommandName(
'build'
)} failed with error:\n`
);
prettyError(prebuiltError);
return 1;
}
// Ensure that the deploy target matches the build target
const assumedTarget = target || 'preview';
if (prebuiltBuild?.target && prebuiltBuild.target !== assumedTarget) {
let specifyTarget = '';
@@ -268,8 +287,11 @@ export default async (client: Client) => {
'Which scope do you want to deploy to?',
autoConfirm
);
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
} catch (err: unknown) {
if (
isErrnoException(err) &&
(err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED')
) {
output.error(err.message);
return 1;
}
@@ -448,8 +470,8 @@ export default async (client: Client) => {
try {
await addProcessEnv(log, deploymentEnv);
await addProcessEnv(log, deploymentBuildEnv);
} catch (err) {
error(err.message);
} catch (err: unknown) {
error(errorToString(err));
return 1;
}
@@ -610,8 +632,10 @@ export default async (client: Client) => {
error('Uploading failed. Please try again.');
return 1;
}
} catch (err) {
debug(`Error: ${err}\n${err.stack}`);
} catch (err: unknown) {
if (isError(err)) {
debug(`Error: ${err}\n${err.stack}`);
}
if (err instanceof NotDomainOwner) {
output.error(err.message);
@@ -678,13 +702,7 @@ export default async (client: Client) => {
return 1;
}
if (err.keyword === 'additionalProperties' && err.dataPath === '.scale') {
const { additionalProperty = '' } = err.params || {};
const message = `Invalid DC name for the scale option: ${additionalProperty}`;
error(message);
}
if (err.code === 'size_limit_exceeded') {
if (isAPIError(err) && err.code === 'size_limit_exceeded') {
const { sizeLimit = 0 } = err;
const message = `File size limit exceeded (${bytes(sizeLimit)})`;
error(message);
@@ -894,36 +912,3 @@ const printDeploymentStatus = async (
output.print(message + link);
}
};
// Converts `env` Arrays, Strings and Objects into env Objects.
const parseEnv = (env?: string[] | Dictionary<string>) => {
if (!env) {
return {};
}
if (typeof env === 'string') {
// a single `--env` arg comes in as a String
env = [env];
}
if (Array.isArray(env)) {
return env.reduce((o, e) => {
let key;
let value;
const equalsSign = e.indexOf('=');
if (equalsSign === -1) {
key = e;
} else {
key = e.slice(0, equalsSign);
value = e.slice(equalsSign + 1);
}
o[key] = value;
return o;
}, {} as Dictionary<string | undefined>);
}
// assume it's already an Object
return env;
};

View File

@@ -15,6 +15,7 @@ import readConfig from '../../util/config/read-config';
import readJSONFile from '../../util/read-json-file';
import { getPkgName, getCommandName } from '../../util/pkg-name';
import { CantParseJSONFile } from '../../util/errors-ts';
import { isErrnoException } from '../../util/is-error';
const COMMAND_CONFIG = {
dev: ['dev'],
@@ -136,7 +137,7 @@ export default async function main(client: Client) {
try {
return await dev(client, argv, args);
} catch (err) {
if (err.code === 'ENOTFOUND') {
if (isErrnoException(err) && err.code === 'ENOTFOUND') {
// Error message will look like the following:
// "request to https://api.vercel.com/v2/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
@@ -148,7 +149,9 @@ export default async function main(client: Client) {
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
);
}
output.debug(err.stack);
if (typeof err.stack === 'string') {
output.debug(err.stack);
}
return 1;
}
output.prettyError(err);

View File

@@ -21,18 +21,7 @@ export default async function add(
args: string[]
) {
const { output } = client;
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 { contextName } = await getScope(client);
const parsedParams = parseAddDNSRecordArgs(args);
if (!parsedParams) {

View File

@@ -14,18 +14,7 @@ export default async function add(
args: string[]
) {
const { output } = client;
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 { contextName } = await getScope(client);
if (args.length !== 2) {
output.error(

View File

@@ -24,18 +24,7 @@ export default async function ls(
) {
const { output } = client;
const { '--next': nextTimestamp } = opts;
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 { contextName } = await getScope(client);
const [domainName] = args;
const lsStamp = stamp();

View File

@@ -14,21 +14,11 @@ type Options = {};
export default async function rm(
client: Client,
opts: Options,
_opts: Options,
args: string[]
) {
const { output } = client;
try {
await getScope(client);
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
await getScope(client);
const [recordId] = args;
if (args.length !== 1) {

View File

@@ -26,18 +26,7 @@ export default async function add(
) {
const { output } = client;
const force = opts['--force'];
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 { contextName } = await getScope(client);
const project = await getLinkedProject(client).then(result => {
if (result.status === 'linked') {

View File

@@ -11,6 +11,7 @@ import promptBool from '../../util/input/prompt-bool';
import purchaseDomain from '../../util/domains/purchase-domain';
import stamp from '../../util/output/stamp';
import { getCommandName } from '../../util/pkg-name';
import { errorToString } from '../../util/is-error';
type Options = {};
@@ -20,18 +21,7 @@ export default async function buy(
args: string[]
) {
const { output } = client;
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 { contextName } = await getScope(client);
const [domainName] = args;
if (!domainName) {
@@ -68,6 +58,11 @@ export default async function buy(
return 1;
}
if (renewalPrice instanceof Error) {
output.prettyError(renewalPrice);
return 1;
}
if (!(await getDomainStatus(client, domainName)).available) {
output.error(
`The domain ${param(domainName)} is ${chalk.underline(
@@ -109,11 +104,11 @@ export default async function buy(
try {
buyResult = await purchaseDomain(client, domainName, price, autoRenew);
} catch (err) {
} catch (err: unknown) {
output.error(
'An unexpected error occurred while purchasing your domain. Please try again later.'
);
output.debug(`Server response: ${err.message}`);
output.debug(`Server response: ${errorToString(err)}`);
return 1;
}

View File

@@ -23,19 +23,7 @@ export default async function inspect(
args: string[]
) {
const { output } = client;
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 { contextName } = await getScope(client);
const [domainName] = args;
const inspectStamp = stamp();

View File

@@ -25,23 +25,13 @@ export default async function ls(
) {
const { output } = client;
const { '--next': nextTimestamp } = opts;
let contextName = null;
if (typeof nextTimestamp !== undefined && Number.isNaN(nextTimestamp)) {
output.error('Please provide a number for flag --next');
return 1;
}
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 { contextName } = await getScope(client);
const lsStamp = stamp();

View File

@@ -25,20 +25,7 @@ export default async function move(
args: string[]
) {
const { output } = client;
let contextName = null;
let user = null;
try {
({ contextName, user } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const { contextName, user } = await getScope(client);
const { domainName, destination } = await getArgs(args);
if (!isRootDomain(domainName)) {
output.error(

View File

@@ -29,18 +29,7 @@ export default async function rm(
) {
const { output } = client;
const [domainName] = args;
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 { contextName } = await getScope(client);
if (!domainName) {
output.error(
@@ -122,10 +111,10 @@ async function removeDomain(
output.debug(`Removing alias ${id}`);
try {
await removeAliasById(client, id);
} catch (error) {
} catch (err: unknown) {
// Ignore if the alias does not exist anymore
if (error.status !== 404) {
throw error;
if (!ERRORS.isAPIError(err) || err.status !== 404) {
throw err;
}
}
}
@@ -134,10 +123,10 @@ async function removeDomain(
output.debug(`Removing cert ${id}`);
try {
await deleteCertById(output, client, id);
} catch (error) {
} catch (err: unknown) {
// Ignore if the cert does not exist anymore
if (error.status !== 404) {
throw error;
if (!ERRORS.isAPIError(err) || err.status !== 404) {
throw err;
}
}
}

View File

@@ -23,18 +23,7 @@ export default async function transferIn(
args: string[]
) {
const { output } = client;
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 { contextName } = await getScope(client);
const [domainName] = args;
if (!domainName) {

View File

@@ -1,5 +1,4 @@
import chalk from 'chalk';
import inquirer from 'inquirer';
import { ProjectEnvTarget, Project, ProjectEnvType } from '../../types';
import { Output } from '../../util/output';
import Client from '../../util/client';
@@ -16,6 +15,7 @@ import param from '../../util/output/param';
import { emoji, prependEmoji } from '../../util/emoji';
import { isKnownError } from '../../util/env/known-error';
import { getCommandName } from '../../util/pkg-name';
import { isAPIError } from '../../util/errors-ts';
type Options = {
'--debug': boolean;
@@ -66,7 +66,7 @@ export default async function add(
}
while (!envName) {
const { inputName } = await inquirer.prompt({
const { inputName } = await client.prompt({
type: 'input',
name: 'inputName',
message: `Whats the name of the variable?`,
@@ -106,7 +106,7 @@ export default async function add(
if (stdInput) {
envValue = stdInput;
} else {
const { inputValue } = await inquirer.prompt({
const { inputValue } = await client.prompt({
type: 'input',
name: 'inputValue',
message: `Whats the value of ${envName}?`,
@@ -116,7 +116,7 @@ export default async function add(
}
while (envTargets.length === 0) {
const { inputTargets } = await inquirer.prompt({
const { inputTargets } = await client.prompt({
name: 'inputTargets',
type: 'checkbox',
message: `Add ${envName} to which Environments (select multiple)?`,
@@ -136,7 +136,7 @@ export default async function add(
envTargets.length === 1 &&
envTargets[0] === ProjectEnvTarget.Preview
) {
const { inputValue } = await inquirer.prompt({
const { inputValue } = await client.prompt({
type: 'input',
name: 'inputValue',
message: `Add ${envName} to which Git branch? (leave empty for all Preview branches)?`,
@@ -157,12 +157,12 @@ export default async function add(
envTargets,
envGitBranch
);
} catch (error) {
if (isKnownError(error) && error.serverMessage) {
output.error(error.serverMessage);
} catch (err: unknown) {
if (isAPIError(err) && isKnownError(err)) {
output.error(err.serverMessage);
return 1;
}
throw error;
throw err;
}
output.print(

View File

@@ -14,6 +14,11 @@ import param from '../../util/output/param';
import stamp from '../../util/output/stamp';
import { getCommandName } from '../../util/pkg-name';
import { EnvRecordsSource } from '../../util/env/get-env-records';
import {
buildDeltaString,
createEnvObject,
} from '../../util/env/diff-env-files';
import { isErrnoException } from '../../util/is-error';
const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
@@ -36,8 +41,8 @@ function readHeadSync(path: string, length: number) {
function tryReadHeadSync(path: string, length: number) {
try {
return readHeadSync(path, length);
} catch (err) {
if (err.code !== 'ENOENT') {
} catch (err: unknown) {
if (!isErrnoException(err) || err.code !== 'ENOENT') {
throw err;
}
}
@@ -69,7 +74,7 @@ export default async function pull(
const exists = typeof head !== 'undefined';
if (head === CONTENTS_PREFIX) {
output.print(`Overwriting existing ${chalk.bold(filename)} file\n`);
output.log(`Overwriting existing ${chalk.bold(filename)} file`);
} else if (
exists &&
!skipConfirmation &&
@@ -83,10 +88,10 @@ export default async function pull(
return 0;
}
output.print(
`Downloading "${environment}" Environment Variables for Project ${chalk.bold(
project.name
)}\n`
output.log(
`Downloading \`${chalk.cyan(
environment
)}\` Environment Variables for Project ${chalk.bold(project.name)}`
);
const pullStamp = stamp();
@@ -107,6 +112,15 @@ export default async function pull(
environment
);
let deltaString = '';
let oldEnv;
if (exists) {
oldEnv = await createEnvObject(fullPath, output);
if (oldEnv) {
deltaString = buildDeltaString(oldEnv, records);
}
}
const contents =
CONTENTS_PREFIX +
Object.entries(records)
@@ -116,6 +130,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(

View File

@@ -16,6 +16,7 @@ import param from '../../util/output/param';
import { emoji, prependEmoji } from '../../util/emoji';
import { isKnownError } from '../../util/env/known-error';
import { getCommandName } from '../../util/pkg-name';
import { isAPIError } from '../../util/errors-ts';
type Options = {
'--debug': boolean;
@@ -120,12 +121,12 @@ export default async function rm(
try {
output.spinner('Removing');
await removeEnvRecord(output, client, project.id, env);
} catch (error) {
if (isKnownError(error) && error.serverMessage) {
output.error(error.serverMessage);
} catch (err: unknown) {
if (isAPIError(err) && isKnownError(err)) {
output.error(err.serverMessage);
return 1;
}
throw error;
throw err;
}
output.print(

View File

@@ -7,6 +7,7 @@ import handleError from '../../util/handle-error';
import logo from '../../util/output/logo';
import init from './init';
import { getPkgName } from '../../util/pkg-name';
import { isError } from '../../util/is-error';
const COMMAND_CONFIG = {
init: ['init'],
@@ -70,9 +71,11 @@ export default async function main(client: Client) {
try {
return await init(client, argv, args);
} catch (err) {
} catch (err: unknown) {
output.prettyError(err);
output.debug(err.stack);
if (isError(err) && typeof err.stack === 'string') {
output.debug(err.stack);
}
return 1;
}
}

View File

@@ -12,6 +12,10 @@ import Client from '../util/client';
import { getDeployment } from '../util/get-deployment';
import { Deployment } from '@vercel/client';
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(`
@@ -63,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`);
@@ -75,8 +79,11 @@ export default async function main(client: Client) {
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
} catch (err: unknown) {
if (
isErrnoException(err) &&
(err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED')
) {
error(err.message);
return 1;
}
@@ -84,36 +91,50 @@ 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) {
if (err.status === 404) {
error(
`Failed to find deployment "${deploymentIdOrHost}" in ${chalk.bold(
contextName
)}`
);
return 1;
}
if (err.status === 403) {
error(
`No permission to access deployment "${deploymentIdOrHost}" in ${chalk.bold(
contextName
)}`
);
return 1;
} catch (err: unknown) {
if (isAPIError(err)) {
if (err.status === 404) {
error(
`Failed to find deployment "${deploymentIdOrHost}" in ${chalk.bold(
contextName
)}`
);
return 1;
}
if (err.status === 403) {
error(
`No permission to access deployment "${deploymentIdOrHost}" in ${chalk.bold(
contextName
)}`
);
return 1;
}
}
// unexpected
throw err;
}
const { id, name, url, createdAt, routes, readyState } = deployment;
const {
id,
name,
url,
createdAt,
routes,
readyState,
alias: aliases,
} = deployment;
const { builds } =
deployment.version === 2
@@ -121,20 +142,20 @@ export default async function main(client: Client) {
: { builds: [] };
log(
`Fetched deployment "${url}" in ${chalk.bold(contextName)} ${elapsed(
Date.now() - depFetchStart
)}`
`Fetched deployment ${chalk.bold(url)} in ${chalk.bold(
contextName
)} ${elapsed(Date.now() - depFetchStart)}`
);
print('\n');
print(chalk.bold(' General\n\n'));
print(` ${chalk.cyan('id')}\t\t${id}\n`);
print(` ${chalk.cyan('name')}\t${name}\n`);
print(` ${chalk.cyan('readyState')}\t${stateString(readyState)}\n`);
print(` ${chalk.cyan('url')}\t\t${url}\n`);
print(` ${chalk.cyan('status')}\t${stateString(readyState)}\n`);
print(` ${chalk.cyan('url')}\t\thttps://${url}\n`);
if (createdAt) {
print(
` ${chalk.cyan('createdAt')}\t${new Date(createdAt)} ${elapsed(
` ${chalk.cyan('created')}\t${new Date(createdAt)} ${elapsed(
Date.now() - createdAt,
true
)}\n`
@@ -142,6 +163,16 @@ export default async function main(client: Client) {
}
print('\n\n');
if (aliases.length > 0) {
print(chalk.bold(' Aliases\n\n'));
let aliasList = '';
for (const alias of aliases) {
aliasList += `${chalk.gray('╶')} https://${alias}\n`;
}
print(indent(aliasList, 4));
print('\n\n');
}
if (builds.length > 0) {
const times: { [id: string]: string | null } = {};
@@ -165,19 +196,24 @@ export default async function main(client: Client) {
return 0;
}
// renders the state string
function stateString(s: Deployment['readyState']) {
const CIRCLE = '● ';
const sTitle = s && 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.gray(CIRCLE) + sTitle;
case 'CANCELED':
return chalk.gray(CIRCLE) + sTitle;
default:
return chalk.gray(s || 'UNKNOWN');
return chalk.gray('UNKNOWN');
}
}

View File

@@ -19,6 +19,7 @@ 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';
const help = () => {
console.log(`
@@ -152,16 +153,7 @@ export default async function main(client: Client) {
const { currentTeam } = config;
try {
({ contextName } = await getScope(client));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
error(err.message);
return 1;
}
throw err;
}
({ contextName } = await getScope(client));
const nextTimestamp = argv['--next'];
@@ -228,8 +220,8 @@ export default async function main(client: Client) {
try {
await now.findDeployment(app);
} catch (err) {
if (err.status === 404) {
} catch (err: unknown) {
if (isAPIError(err) && err.status === 404) {
debug('Ignore findDeployment 404');
} else {
throw err;

View File

@@ -5,6 +5,8 @@ import { writeToConfigFile, writeToAuthConfigFile } from '../util/config/files';
import getArgs from '../util/get-args';
import Client from '../util/client';
import { getCommandName, getPkgName } from '../util/pkg-name';
import { isAPIError } from '../util/errors-ts';
import { errorToString } from '../util/is-error';
const help = () => {
console.log(`
@@ -63,12 +65,14 @@ export default async function main(client: Client): Promise<number> {
method: 'DELETE',
useCurrentTeam: false,
});
} catch (err) {
if (err.status === 403) {
output.debug('Token is invalid so it cannot be revoked');
} else if (err.status !== 200) {
output.debug(err?.message ?? '');
exitCode = 1;
} catch (err: unknown) {
if (isAPIError(err)) {
if (err.status === 403) {
output.debug('Token is invalid so it cannot be revoked');
} else if (err.status !== 200) {
output.debug(err?.message ?? '');
exitCode = 1;
}
}
}
@@ -86,8 +90,8 @@ export default async function main(client: Client): Promise<number> {
writeToConfigFile(config);
writeToAuthConfigFile(authConfig);
output.debug('Configuration has been deleted');
} catch (err) {
output.debug(err?.message ?? '');
} catch (err: unknown) {
output.debug(errorToString(err));
exitCode = 1;
}

View File

@@ -8,6 +8,7 @@ import { getPkgName } from '../util/pkg-name';
import getArgs from '../util/get-args';
import Client from '../util/client';
import { getDeployment } from '../util/get-deployment';
import { isAPIError } from '../util/errors-ts';
const help = () => {
console.log(`
@@ -125,22 +126,24 @@ export default async function main(client: Client) {
let deployment;
try {
deployment = await getDeployment(client, id);
} catch (err) {
} catch (err: unknown) {
output.stopSpinner();
if (err.status === 404) {
output.error(
`Failed to find deployment "${id}" in ${chalk.bold(contextName)}`
);
return 1;
}
if (err.status === 403) {
output.error(
`No permission to access deployment "${id}" in ${chalk.bold(
contextName
)}`
);
return 1;
if (isAPIError(err)) {
if (err.status === 404) {
output.error(
`Failed to find deployment "${id}" in ${chalk.bold(contextName)}`
);
return 1;
}
if (err.status === 403) {
output.error(
`No permission to access deployment "${id}" in ${chalk.bold(
contextName
)}`
);
return 1;
}
}
// unexpected
throw err;

View File

@@ -1,6 +1,7 @@
import chalk from 'chalk';
import ms from 'ms';
import Client from '../../util/client';
import { isAPIError } from '../../util/errors-ts';
import { getCommandName } from '../../util/pkg-name';
export default async function add(
@@ -36,12 +37,12 @@ export default async function add(
method: 'POST',
body: { name },
});
} catch (error) {
if (error.status === 409) {
} catch (err: unknown) {
if (isAPIError(err) && err.status === 409) {
// project already exists, so we can
// show a success message
} else {
throw error;
throw err;
}
}
const elapsed = ms(Date.now() - start);

View File

@@ -72,18 +72,7 @@ export default async function main(client: Client) {
subcommand = argv._[0] || 'list';
const args = argv._.slice(1);
const { output } = client;
let contextName = '';
try {
({ contextName } = await getScope(client));
} catch (error) {
if (error.code === 'NOT_AUTHORIZED' || error.code === 'TEAM_DELETED') {
output.error(error.message);
return 1;
}
throw error;
}
const { contextName } = await getScope(client);
switch (subcommand) {
case 'ls':

View File

@@ -2,6 +2,7 @@ import chalk from 'chalk';
import ms from 'ms';
import Client from '../../util/client';
import { emoji, prependEmoji } from '../../util/emoji';
import { isAPIError } from '../../util/errors-ts';
import confirm from '../../util/input/confirm';
import { getCommandName } from '../../util/pkg-name';
@@ -32,8 +33,8 @@ export default async function rm(client: Client, args: string[]) {
await client.fetch(`/v2/projects/${e(name)}`, {
method: 'DELETE',
});
} catch (err) {
if (err.status === 404) {
} catch (err: unknown) {
if (isAPIError(err) && err.status === 404) {
client.output.error('No such project exists');
return 1;
}

View File

@@ -179,6 +179,8 @@ export default async function main(client: Client) {
return pullResultCode;
}
client.output.print('\n');
client.output.log('Downloading project settings');
await writeProjectSettings(cwd, project, org);
const settingsStamp = stamp();

View File

@@ -114,18 +114,7 @@ export default async function main(client: Client) {
return 1;
}
let contextName: string | null = 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 { contextName } = await getScope(client);
output.spinner(
`Fetching deployment(s) ${ids

View File

@@ -11,6 +11,7 @@ import { getPkgName, getCommandName } from '../../util/pkg-name';
import Client from '../../util/client';
import createTeam from '../../util/teams/create-team';
import patchTeam from '../../util/teams/patch-team';
import { errorToString, isError } from '../../util/is-error';
const validateSlugKeypress = (data: string, value: string) =>
// TODO: the `value` here should contain the current value + the keypress
@@ -56,8 +57,8 @@ export default async function add(client: Client): Promise<number> {
valid: team,
forceLowerCase: true,
});
} catch (err) {
if (err.message === 'USER_ABORT') {
} catch (err: unknown) {
if (isError(err) && err.message === 'USER_ABORT') {
output.log('Aborted');
return 0;
}
@@ -71,10 +72,10 @@ export default async function add(client: Client): Promise<number> {
try {
// eslint-disable-next-line no-await-in-loop
team = await createTeam(client, { slug });
} catch (err) {
} catch (err: unknown) {
output.stopSpinner();
output.print(eraseLines(2));
output.error(err.message);
output.error(errorToString(err));
}
} while (!team);
@@ -92,8 +93,8 @@ export default async function add(client: Client): Promise<number> {
label: `- ${teamNamePrefix}`,
validateKeypress: validateNameKeypress,
});
} catch (err) {
if (err.message === 'USER_ABORT') {
} catch (err: unknown) {
if (isError(err) && err.message === 'USER_ABORT') {
console.log(info('No name specified'));
return gracefulExit();
}

View File

@@ -11,6 +11,8 @@ import { getCommandName } from '../../util/pkg-name';
import { email as regexEmail } from '../../util/input/regexes';
import getTeams from '../../util/teams/get-teams';
import inviteUserToTeam from '../../util/teams/invite-user-to-team';
import { isAPIError } from '../../util/errors-ts';
import { errorToString, isError } from '../../util/is-error';
const validateEmail = (data: string) =>
regexEmail.test(data.trim()) || data.length === 0;
@@ -67,17 +69,7 @@ export default async function invite(
const currentTeam = teams.find(team => team.id === currentTeamId);
output.spinner('Fetching user information');
let user;
try {
user = await getUser(client);
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const user = await getUser(client);
domains.push(user.email.split('@')[1]);
@@ -107,8 +99,8 @@ export default async function invite(
// eslint-disable-next-line no-await-in-loop
const res = await inviteUserToTeam(client, currentTeam.id, email);
userInfo = res.username;
} catch (err) {
if (err.code === 'user_not_found') {
} catch (err: unknown) {
if (isAPIError(err) && err.code === 'user_not_found') {
output.error(`No user exists with the email address "${email}".`);
return 1;
}
@@ -141,8 +133,8 @@ export default async function invite(
validateValue: validateEmail,
autoComplete: value => emailAutoComplete(value, currentTeam.slug),
});
} catch (err) {
if (err.message !== 'USER_ABORT') {
} catch (err: unknown) {
if (!isError(err) || err.message !== 'USER_ABORT') {
throw err;
}
}
@@ -174,7 +166,7 @@ export default async function invite(
} catch (err) {
output.stopSpinner();
process.stderr.write(eraseLines(emails.length + 2));
output.error(err.message);
output.error(errorToString(err));
hasError = true;
for (const email of emails) {
output.log(`${chalk.cyan(chars.tick)} ${sentEmailPrefix}${email}`);

View File

@@ -43,17 +43,7 @@ export default async function list(client: Client): Promise<number> {
const accountIsCurrent = !currentTeam;
output.spinner('Fetching user information');
let user;
try {
user = await getUser(client);
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const user = await getUser(client);
if (accountIsCurrent) {
currentTeam = user.id;

View File

@@ -41,18 +41,7 @@ export default async (client: Client): Promise<number> => {
return 2;
}
let contextName = null;
try {
({ contextName } = await getScope(client, { getTeam: false }));
} catch (err) {
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.error(err.message);
return 1;
}
throw err;
}
const { contextName } = await getScope(client, { getTeam: false });
if (client.stdout.isTTY) {
output.log(contextName);

View File

@@ -1,11 +1,12 @@
#!/usr/bin/env node
import { isErrnoException, isError, errorToString } from './util/is-error';
try {
// Test to see if cwd has been deleted before
// importing 3rd party packages that might need cwd.
process.cwd();
} catch (e) {
if (e && e.message && e.message.includes('uv_cwd')) {
} catch (err: unknown) {
if (isError(err) && err.message.includes('uv_cwd')) {
console.error('Error! The current working directory does not exist.');
process.exit(1);
}
@@ -39,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';
@@ -49,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');
@@ -103,7 +104,7 @@ const main = async () => {
},
{ permissive: true }
);
} catch (err) {
} catch (err: unknown) {
handleError(err);
return 1;
}
@@ -201,169 +202,65 @@ const main = async () => {
// Ensure that the Vercel global configuration directory exists
try {
await mkdirp(VERCEL_DIR);
} catch (err) {
console.error(
error(
`${
'An unexpected error occurred while trying to create the ' +
`global directory "${hp(VERCEL_DIR)}" `
}${err.message}`
)
} catch (err: unknown) {
output.error(
`An unexpected error occurred while trying to create the global directory "${hp(
VERCEL_DIR
)}" ${errorToString(err)}`
);
}
let migrated = false;
let configExists;
try {
configExists = existsSync(VERCEL_CONFIG_PATH);
} catch (err) {
console.error(
error(
`${
'An unexpected error occurred while trying to find the ' +
`config file "${hp(VERCEL_CONFIG_PATH)}" `
}${err.message}`
)
);
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)}" `
}${err.message}`
)
);
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) {
console.error(
error(
`${
'An unexpected error occurred while trying to write the ' +
`default config to "${hp(VERCEL_CONFIG_PATH)}" `
}${err.message}`
)
);
return 1;
}
}
let authConfigExists;
try {
authConfigExists = existsSync(VERCEL_AUTH_CONFIG_PATH);
} catch (err) {
console.error(
error(
`${
'An unexpected error occurred while trying to find the ' +
`auth file "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${err.message}`
)
);
return 1;
}
let authConfig = null;
const subcommandsWithoutToken = [
'login',
'logout',
'help',
'init',
'update',
'build',
];
if (authConfigExists) {
try {
authConfig = configFiles.readAuthConfigFile();
} catch (err) {
console.error(
error(
`${
'An unexpected error occurred while trying to read the ' +
`auth config in "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${err.message}`
)
);
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) {
console.error(
error(
`${
'An unexpected error occurred while trying to write the ' +
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${err.message}`
)
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') {
@@ -373,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,
@@ -432,6 +323,15 @@ const main = async () => {
client.argv.push('-h');
}
const subcommandsWithoutToken = [
'login',
'logout',
'help',
'init',
'update',
'build',
];
// Prompt for login if there is no current token
if (
(!authConfig || !authConfig.token) &&
@@ -543,8 +443,8 @@ const main = async () => {
try {
user = await getUser(client);
} catch (err) {
if (err.code === 'NOT_AUTHORIZED') {
} catch (err: unknown) {
if (isErrnoException(err) && err.code === 'NOT_AUTHORIZED') {
output.prettyError({
message: `You do not have access to the specified account`,
link: 'https://err.sh/vercel/scope-not-accessible',
@@ -564,8 +464,8 @@ const main = async () => {
try {
teams = await getTeams(client);
} catch (err) {
if (err.code === 'not_authorized') {
} catch (err: unknown) {
if (isErrnoException(err) && err.code === 'not_authorized') {
output.prettyError({
message: `You do not have access to the specified team`,
link: 'https://err.sh/vercel/scope-not-accessible',
@@ -594,8 +494,8 @@ const main = async () => {
}
}
const metric = metrics();
let exitCode;
let metric: ReturnType<typeof metrics> | undefined;
const eventCategory = 'Exit Code';
try {
@@ -698,13 +598,14 @@ const main = async () => {
if (shouldCollectMetrics) {
const category = 'Command Invocation';
if (!metric) metric = metrics();
metric
.timing(category, targetCommand, end, pkg.version)
.event(category, targetCommand, pkg.version)
.send();
}
} catch (err) {
if (err.code === 'ENOTFOUND') {
} catch (err: unknown) {
if (isErrnoException(err) && err.code === 'ENOTFOUND') {
// Error message will look like the following:
// "request to https://api.vercel.com/v2/user failed, reason: getaddrinfo ENOTFOUND api.vercel.com"
const matches = /getaddrinfo ENOTFOUND (.*)$/.exec(err.message || '');
@@ -716,11 +617,16 @@ const main = async () => {
)} could not be resolved. Please verify your internet connectivity and DNS configuration.`
);
}
output.debug(err.stack);
if (typeof err.stack === 'string') {
output.debug(err.stack);
}
return 1;
}
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
if (
isErrnoException(err) &&
(err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED')
) {
output.prettyError(err);
return 1;
}
@@ -732,9 +638,10 @@ const main = async () => {
}
if (shouldCollectMetrics) {
if (!metric) metric = metrics();
metric
.event(eventCategory, '1', pkg.version)
.exception(err.message)
.exception(errorToString(err))
.send();
}
@@ -742,8 +649,10 @@ const main = async () => {
// but instead show the message. Any error that is handled by this should
// actually be handled in the sub command instead. Please make sure
// that happens for anything that lands here. It should NOT bubble up to here.
if (err.code) {
output.debug(err.stack);
if (isErrnoException(err)) {
if (typeof err.stack === 'string') {
output.debug(err.stack);
}
output.prettyError(err);
} else {
await reportError(Sentry, client, err);
@@ -757,6 +666,7 @@ const main = async () => {
}
if (shouldCollectMetrics) {
if (!metric) metric = metrics();
metric.event(eventCategory, `${exitCode}`, pkg.version).send();
}

View File

@@ -20,13 +20,15 @@ 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;
@@ -271,6 +273,12 @@ export interface ProjectLinkData {
export interface Project extends ProjectSettings {
id: string;
analytics?: {
id: string;
enabledAt?: number;
disabledAt?: number;
canceledAt?: number | null;
};
name: string;
accountId: string;
updatedAt: number;

View File

@@ -68,37 +68,39 @@ async function performCreateAlias(
body: { alias },
}
);
} catch (error) {
if (error.code === 'cert_missing' || error.code === 'cert_expired') {
return new ERRORS.CertMissing(alias);
}
if (error.status === 409) {
return { uid: error.uid, alias: error.alias } as AliasRecord;
}
if (error.code === 'deployment_not_found') {
return new ERRORS.DeploymentNotFound({
context: contextName,
id: deployment.uid,
});
}
if (error.code === 'gone') {
return new ERRORS.DeploymentFailedAliasImpossible();
}
if (error.code === 'invalid_alias') {
return new ERRORS.InvalidAlias(alias);
}
if (error.status === 403) {
if (error.code === 'alias_in_use') {
return new ERRORS.AliasInUse(alias);
} catch (err: unknown) {
if (ERRORS.isAPIError(err)) {
if (err.code === 'cert_missing' || err.code === 'cert_expired') {
return new ERRORS.CertMissing(alias);
}
if (error.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(alias, contextName);
if (err.status === 409) {
return { uid: err.uid, alias: err.alias } as AliasRecord;
}
if (err.code === 'deployment_not_found') {
return new ERRORS.DeploymentNotFound({
context: contextName,
id: deployment.uid,
});
}
if (err.code === 'gone') {
return new ERRORS.DeploymentFailedAliasImpossible();
}
if (err.code === 'invalid_alias') {
return new ERRORS.InvalidAlias(alias);
}
if (err.status === 403) {
if (err.code === 'alias_in_use') {
return new ERRORS.AliasInUse(alias);
}
if (err.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(alias, contextName);
}
}
if (err.status === 400) {
return new ERRORS.DeploymentNotReady({ url: deployment.url });
}
}
if (error.status === 400) {
return new ERRORS.DeploymentNotReady({ url: deployment.url });
}
throw error;
throw err;
}
}

View File

@@ -81,7 +81,7 @@ export async function resolveBuilders(
continue;
}
if (name === '@vercel/static') {
if (name === '@vercel/static' || name === '@now/static') {
// `@vercel/static` is a special-case built-in builder
builders.set(name, {
builder: staticBuilder,

View File

@@ -21,33 +21,50 @@ import {
PackageJson,
Prerender,
download,
downloadFile,
EdgeFunction,
BuildResultBuildOutput,
getLambdaOptionsFromFunction,
normalizePath,
} from '@vercel/build-utils';
import pipe from 'promisepipe';
import { unzip } from './unzip';
import { VERCEL_DIR } from '../projects/link';
import { VercelConfig } from '@vercel/client';
const { normalize } = posix;
export const OUTPUT_DIR = join(VERCEL_DIR, 'output');
/**
* An entry in the "functions" object in `vercel.json`.
*/
interface FunctionConfiguration {
memory?: number;
maxDuration?: number;
}
export async function writeBuildResult(
outputDir: string,
buildResult: BuildResultV2 | BuildResultV3,
build: Builder,
builder: BuilderV2 | BuilderV3,
builderPkg: PackageJson,
cleanUrls?: boolean
vercelConfig: VercelConfig | null
) {
const { version } = builder;
if (typeof version !== 'number' || version === 2) {
return writeBuildResultV2(
outputDir,
buildResult as BuildResultV2,
cleanUrls
vercelConfig
);
} else if (version === 3) {
return writeBuildResultV3(outputDir, buildResult as BuildResultV3, build);
return writeBuildResultV3(
outputDir,
buildResult as BuildResultV3,
build,
vercelConfig
);
}
throw new Error(
`Unsupported Builder version \`${version}\` from "${builderPkg.name}"`
@@ -90,7 +107,7 @@ function stripDuplicateSlashes(path: string): string {
async function writeBuildResultV2(
outputDir: string,
buildResult: BuildResultV2,
cleanUrls?: boolean
vercelConfig: VercelConfig | null
) {
if ('buildOutputPath' in buildResult) {
await mergeBuilderOutput(outputDir, buildResult);
@@ -102,9 +119,15 @@ async function writeBuildResultV2(
for (const [path, output] of Object.entries(buildResult.output)) {
const normalizedPath = stripDuplicateSlashes(path);
if (isLambda(output)) {
await writeLambda(outputDir, output, normalizedPath, lambdas);
await writeLambda(outputDir, output, normalizedPath, undefined, lambdas);
} else if (isPrerender(output)) {
await writeLambda(outputDir, output.lambda, normalizedPath, lambdas);
await writeLambda(
outputDir,
output.lambda,
normalizedPath,
undefined,
lambdas
);
// Write the fallback file alongside the Lambda directory
let fallback = output.fallback;
@@ -140,7 +163,7 @@ async function writeBuildResultV2(
output,
normalizedPath,
overrides,
cleanUrls
vercelConfig?.cleanUrls
);
} else if (isEdgeFunction(output)) {
await writeEdgeFunction(outputDir, output, normalizedPath);
@@ -162,19 +185,28 @@ async function writeBuildResultV2(
async function writeBuildResultV3(
outputDir: string,
buildResult: BuildResultV3,
build: Builder
build: Builder,
vercelConfig: VercelConfig | null
) {
const { output } = buildResult;
const src = build.src;
if (typeof src !== 'string') {
throw new Error(`Expected "build.src" to be a string`);
}
const functionConfiguration = vercelConfig
? await getLambdaOptionsFromFunction({
sourceFile: src,
config: vercelConfig,
})
: {};
const ext = extname(src);
const path = stripDuplicateSlashes(
build.config?.zeroConfig ? src.substring(0, src.length - ext.length) : src
);
if (isLambda(output)) {
await writeLambda(outputDir, output, path);
await writeLambda(outputDir, output, path, functionConfiguration);
} else if (isEdgeFunction(output)) {
await writeEdgeFunction(outputDir, output, path);
} else {
@@ -235,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);
}
/**
@@ -260,6 +290,7 @@ async function writeEdgeFunction(
const config = {
runtime: 'edge',
...edgeFunction,
entrypoint: normalizePath(edgeFunction.entrypoint),
files: undefined,
type: undefined,
};
@@ -283,6 +314,7 @@ async function writeLambda(
outputDir: string,
lambda: Lambda,
path: string,
functionConfiguration?: FunctionConfiguration,
lambdas?: Map<Lambda, string>
) {
const dest = join(outputDir, 'functions', `${path}.func`);
@@ -317,8 +349,14 @@ async function writeLambda(
throw new Error('Malformed `Lambda` - no "files" present');
}
const memory = functionConfiguration?.memory ?? lambda.memory;
const maxDuration = functionConfiguration?.maxDuration ?? lambda.maxDuration;
const config = {
...lambda,
handler: normalizePath(lambda.handler),
memory,
maxDuration,
type: undefined,
files: undefined,
zipBuffer: undefined,

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