Compare commits

...

90 Commits

Author SHA1 Message Date
Sean Massa
8d2c0fec89 Publish Stable
- vercel@28.5.0
 - @vercel/edge@0.1.2
 - @vercel/fs-detectors@3.5.0
 - @vercel/python@3.1.25
2022-11-14 12:53:42 -06:00
Ethan Arrowood
0663524cd7 [cli][fs-detectors] Improve vc build monorepo support (#8742)
### Related Issues

Improves how `vc build` handles monorepos. In short, this introduces
monorepo manager detection logic and then some helpful defaults so users
don't have to manually specify a `buildCommand` or `installCommand` when
linking a project within a monorepo.

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

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2022-11-14 10:23:33 -07:00
Chris Barber
f283b3b106 [cli] Add node_modules/.bin to PATH instead of running npx/yarn run (#8890)
`runDevCommand()` assumes the dev command is an npm package and thus uses `npx` or `yarn run` to execute it. In the case of a Hugo-based app, there is no npm package, so we want spawn to find Hugo in the `PATH`. Then for Node-based apps, instead of `npx`, spawn should find the command since `node_modules/.bin` has been added to the `PATH`.

### Related Issues

> https://github.com/vercel/customer-issues/issues/871

Note: This PR is a recreation of https://github.com/vercel/vercel/pull/8864 because prettier changed a bunch of Hugo files, which was bloating the original PR.

### 📋 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-11-14 15:26:27 +00:00
github-actions[bot]
b572ef5d71 [examples] Upgrade Next.js to version 13.0.3 (#8891) 2022-11-11 14:38:57 -08:00
Chris Barber
71e233ce7f [python] Explicitly bind HTTP server to localhost (#8880)
On Windows, Python 3 is unable to listen on `0.0.0.0` and errors with:

```
[WinError 10049] The requested address is not valid in its context
```

The only solution I found is to bind to `127.0.0.1` or `localhost`. I've run into issues before with using `localhost`, so I chose to go with `127.0.0.1` and Windows (and macOS) seems to be happy.

### 📋 Checklist

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

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-11-11 02:33:31 +00:00
Nathan Rajlich
5669fad6bc [cli] Remove --runInBand from Jest (#8888) 2022-11-11 02:25:38 +00:00
Dominik Ferber
c294409f5b [edge] fix broken link on npmjs.org (#8848)
Before this change clicking on "follow the documentation" from
https://www.npmjs.com/package/@vercel/edge would lead to a 404 as the
docs link is relative.
2022-11-10 13:30:27 -08:00
Andy McKay
686f78a86e [cli] Add in a message for deployment-error message (#8865)
We have a link to an error page here:
https://github.com/vercel/vercel/blob/main/packages/cli/src/commands/deploy/index.ts#L873,
however that page is currently a 404.

This is an attempt to explain the error a bit more. Because there are
many possible causes of a deployment error, this mostly focuses on
explaining how to get to the logs.

### Related Issues

Fixes https://github.com/vercel/vercel/issues/1732. 

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

Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Chris Barber <chris.barber@vercel.com>
2022-11-10 11:40:57 -06:00
Josh Thomas
c6ed021d2e [examples] Add Vercel Analytics to Astro template (#8472)
Co-authored-by: Lee Robinson <lrobinson2011@gmail.com>
Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>
2022-11-10 09:24:20 -06:00
Ryan Carniato
f8cdc943ca [examples] update SolidStart example (#8707)
Updates the SolidStart example to the latest in anticipation of the
upcoming beta release.
2022-11-09 23:27:24 -08:00
Nathan Rajlich
d8d30b59ec [examples] Remove ENABLE_VC_BUILD env var from Solidstart template (#8875)
Env var is no longer required.
2022-11-09 00:43:42 -08:00
Nathan Rajlich
a93383cf2e Remove ENABLE_VC_BUILD from Vercel deployment (#8876)
This env var is no longer necessary.
2022-11-09 07:29:05 +00:00
Lee Robinson
49dac4c229 [examples] Update Remix template for latest. (#8857)
From `create-remix`
2022-11-08 22:22:58 -06:00
JJ Kasper
1b211f28df Publish Stable
- vercel@28.4.17
 - @vercel/client@12.2.17
 - @vercel/frameworks@1.1.12
 - @vercel/fs-detectors@3.4.9
 - @vercel/next@3.2.11
 - @vercel/redwood@1.0.35
 - @vercel/routing-utils@2.1.3
 - @vercel/static-build@1.0.36
2022-11-08 16:21:25 -08:00
JJ Kasper
c2d0887b94 [next][routing-utils] Add missing matcher support (#8874)
### Related Issues

This adds handling to ensure we pass through the `missing` route field
correctly for custom routes and middleware matchers. Tests are also
added in the complimentary Next.js PR for this, example deployment can
be seen here
https://vtest314-e2e-tests-jj4-vtest314-next-e2e-tests.vercel.app/

x-ref: [slack
thread](https://vercel.slack.com/archives/C03S8ED1DKM/p1667935428788529?thread_ts=1667850697.542269&cid=C03S8ED1DKM)
x-ref: https://github.com/vercel/next.js/pull/42660

### 📋 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-11-08 16:17:34 -08:00
JJ Kasper
fbb8bba4cf Publish Stable
- vercel@28.4.16
 - @vercel/next@3.2.10
2022-11-08 11:03:01 -08:00
JJ Kasper
56cc87fe9d [next] Handle skip normalize flag for middleware (#8873)
### Related Issues

x-ref: [slack
thread](https://vercel.slack.com/archives/C01224Q5M99/p1667927545637489?thread_ts=1664536480.045539&cid=C01224Q5M99)

### 📋 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-11-08 10:31:02 -08:00
JJ Kasper
0027ffa65b [next] Ensure rsc dynamic routes are correct (#8866)
### Related Issues

This ensures we properly route dynamic routes with segments in the
middle of the path for rsc paths and also ensures we normalize the
header values from the routes-manifest correctly.

x-ref: https://github.com/vercel/next.js/issues/42364
x-ref:
https://github.com/vercel/next.js/actions/runs/3415662540/jobs/5685787894

### 📋 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-11-08 07:24:32 -08:00
JJ Kasper
0964be1710 Publish Stable
- vercel@28.4.15
 - @vercel/client@12.2.16
 - @vercel/error-utils@1.0.3
 - @vercel/frameworks@1.1.11
 - @vercel/fs-detectors@3.4.8
 - @vercel/hydrogen@0.0.29
 - @vercel/next@3.2.9
 - @vercel/node-bridge@3.1.2
 - @vercel/node@2.6.2
 - @vercel/redwood@1.0.34
 - @vercel/remix@1.0.35
 - @vercel/routing-utils@2.1.2
 - @vercel/static-build@1.0.35
 - @vercel/static-config@2.0.6
2022-11-07 12:46:03 -08:00
JJ Kasper
9618ffe05f [next] Update rsc header handling (#8862)
### Related Issues

Required for https://github.com/vercel/next.js/pull/42482

### 📋 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-11-07 12:42:26 -08:00
github-actions[bot]
832ba3fe23 [examples] Upgrade Next.js to version 13.0.2 (#8847)
This auto-generated PR updates Next.js to version 13.0.2
2022-11-04 16:18:30 -07:00
Ethan Arrowood
253b4fd1d2 [cli][client][error-utils][frameworks][fs-detectors][hydrogen][next][node-bridge][node][redwood][remix][routing-utils][static-config] update @types/node to v14 across repo (#8842)
### Related Issues

Updates @types/node to the latest version within the v14 major (based on `npm view @types/node`)

```
❯ npm view @types/node@'>=14.0.0 <15.0.0' version | tail -1
@types/node@14.18.33 '14.18.33'
```

This PR also fixes the various necessary type changes

### 📋 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-11-04 20:21:13 +00:00
Nathan Rajlich
9e5f17b3c6 [cli] Allow vc link to overwrite existing link (#8845)
There was a regression in #8670 which caused `vc link` to be a no-op
when there was already a link to a Vercel project in the `.vercel`
directory.
2022-11-04 12:12:37 -07:00
Ethan Arrowood
39d0f8dbfc [cli] fix broken integration test (#8844)
### Related Issues

https://github.com/vercel/api/pull/15009 broke one of our integration tests. This PR fixes it.

### 📋 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-11-04 00:12:56 +00:00
Andy McKay
a567d047d1 Publish Stable
- @vercel/build-utils@5.5.7
 - vercel@28.4.14
 - @vercel/client@12.2.15
 - @vercel/edge@0.1.1
 - @vercel/error-utils@1.0.2
 - @vercel/frameworks@1.1.10
 - @vercel/fs-detectors@3.4.7
 - @vercel/go@2.2.15
 - @vercel/hydrogen@0.0.28
 - @vercel/next@3.2.8
 - @vercel/node-bridge@3.1.1
 - @vercel/node@2.6.1
 - @vercel/python@3.1.24
 - @vercel/redwood@1.0.33
 - @vercel/remix@1.0.34
 - @vercel/routing-utils@2.1.1
 - @vercel/ruby@1.3.41
 - @vercel/static-build@1.0.34
 - @vercel/static-config@2.0.5
2022-11-02 13:14:37 -07:00
Steven
fdfb3a385e [error-utils] Allow publishing to npm (#8839)
> If you set "private": true in your package.json, then npm will refuse
to publish it.


https://docs.npmjs.com/cli/v8/configuring-npm/package-json#private
2022-11-02 16:12:09 -04:00
Andy McKay
a630e19896 Publish Stable
- @vercel/build-utils@5.5.6
 - vercel@28.4.13
 - @vercel/client@12.2.14
 - @vercel/edge@0.1.0
 - @vercel/error-utils@1.0.1
 - @vercel/frameworks@1.1.9
 - @vercel/fs-detectors@3.4.6
 - @vercel/go@2.2.14
 - @vercel/hydrogen@0.0.27
 - @vercel/next@3.2.7
 - @vercel/node-bridge@3.1.0
 - @vercel/node@2.6.0
 - @vercel/python@3.1.23
 - @vercel/redwood@1.0.32
 - @vercel/remix@1.0.33
 - @vercel/routing-utils@2.1.0
 - @vercel/ruby@1.3.40
 - @vercel/static-build@1.0.33
 - @vercel/static-config@2.0.4
2022-11-02 12:59:38 -07:00
Steven
00430eeabf [tests] Update turbo cache key (#8830)
This ensures we don't share cache results between node major versions
like 14.x vs 16.x (as well as OS and Arch)
2022-11-02 15:43:27 -04:00
Ethan Arrowood
49f453742b [error-utils] Create @vercel/error-utils package (#8828)
While working on #8742 i found some useful error checking code buried in
the CLI. This PR adds a new private package `@vercel/errors` that moves
those helpful utilities into its own package so it can be used
throughout the monorepo.
2022-11-02 15:18:00 -04:00
Chris Barber
bcb8d4f812 [next] Prepend basePath to edge function destination (#8835)
For Next.js apps, a custom `basePath` in the `next.config.js` file was being prepended to all paths except edge functions of which this PR resolves.

### Related Issues

> Fixes [#853](https://github.com/vercel/customer-issues/issues/853)

### 📋 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-11-02 15:32:00 +00:00
Steven
d42a8a6588 [build-utils] Experimental flag to allow nodejs18.x (#8836)
Add a new experimental flag to allow `nodejs18.x`
2022-11-02 10:56:14 -04:00
Nathan Rajlich
301bcf58fb [node-bridge] Support streaming response for Serverless Function (#8795)
Adds streaming response support for React Server Components with Next 13.
2022-11-02 02:16:37 +00:00
Steven
11d0091393 [cli] Fix middleware 500 in vc dev (#8833)
The status code no longer prints the generic error page.
2022-11-01 18:43:18 -07:00
Steven
6405fb51a1 [tests] Fix next & react version for dev test (#8832)
https://nextjs.org/docs/upgrading#upgrading-from-12-to-13
2022-11-01 18:42:35 -04:00
github-actions[bot]
edd477e602 [examples] Upgrade Next.js to version 13.0.1 (#8820)
This auto-generated PR updates Next.js to version 13.0.1

Co-authored-by: Vercel Team Bot <team@zeit.co>
2022-11-01 11:20:16 -04:00
Tobias Lins
4f8f8a5b98 [routing-utils] Allow to appendRoutes add null phase (#8814)
For Analytics V2 we need to append routes to the `null` phase instead of
`filesystem`.
2022-11-01 11:17:50 -04:00
chloetedder
a8e66eef41 Remove output directory placeholder (#8817)
### Related Issues

Related - https://github.com/vercel/api/pull/15027
Removing setting the output directory placeholder - it is not extremely reliable https://vercel.slack.com/archives/C02HEJASXGD/p1667234137439189?thread_ts=1667232443.320769&cid=C02HEJASXGD

### 📋 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-11-01 03:23:38 +00:00
Kiko Beats
23dd29e269 [node] upgrade edge-runtime to v2.0.0 (#8811)
Drop node12 support
2022-10-31 10:52:40 -04:00
Seiya Nuta
4eb4d2b355 [dev] Support request headers override in middleware (#8752)
Implements request headers override in middlewares.

#### New middleware headers

- `x-middleware-override-headers`: A comma separated list of *all* request header names. Headers not listed will be deleted.
- `x-middleware-request-<name>`: A new value for the header `<name>`.

### Related Issues

- #8724: Add helper functions for non-Next.js middlewares
- https://github.com/vercel/next.js/pull/41380: Next.js' implementation

### 📋 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-10-27 06:51:27 +00:00
Tim Paine
3590ea06a4 [docs][typo] Update license link in readme (#8779)
Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>
2022-10-26 10:00:31 -05:00
github-actions[bot]
314a97b318 [examples] Upgrade Next.js to version 13.0.0 (#8778)
This auto-generated PR updates Next.js to version 13.0.0
2022-10-26 01:42:21 -04:00
JJ Kasper
d41d9e7374 [next] Ensure static app paths have vary correctly (#8771)
### Related Issues

x-ref: https://github.com/vercel/vercel/pull/8763
x-ref: [slack
thread](https://vercel.slack.com/archives/C035J346QQL/p1666645450798089)

### 📋 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-10-24 14:49:41 -07:00
Kiko Beats
80b211fb4a [node] upgrade edge-runtime to v1.1.0 (#8765) 2022-10-24 22:12:31 +02:00
JJ Kasper
ffaf5c9143 [next] Add leveraging initialHeaders/status for prerender (#8763)
### Related Issues

x-ref: https://github.com/vercel/vercel/pull/8757
Fixes: [slack thread](https://vercel.slack.com/archives/C035J346QQL/p1666278547705429)

### 📋 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-10-23 16:12:03 +00:00
Andy McKay
ba1c2a7e54 Cope with socket hang up (#8760)
### Related Issues

Copes with `socket hang up` errors.

I tested with https://github.com/Shopify/toxiproxy and set up a proxy that generated a socket hangup error. With a test URL etc it looks like this:

<img width="935" alt="Screen Shot 2022-10-21 at 2 13 48 PM" src="https://user-images.githubusercontent.com/74699/197289788-9467ebef-d4dd-4fae-bf41-f635b7857d03.png">

### 📋 Checklist

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

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [x] Issue from task tracker has a link to this PR
2022-10-21 22:26:41 +00:00
Baruch-Adi Hen
30a9183836 [ENV] Use new env/pull endpoint for vc env pull, vc pull (#8751)
### Related Issues

We introduced a new endpoint: `/env/pull/:projectId/:target?/:gitBranch?` which will return a complete list of key/value pairs (environment variables) for the proper target environment, using the same logic as we use for deployments. 

This means that we don't need to fetch env variables from multiple sources (system, project, shared) and stitch them on the client anymore. 

- removes unused logic for fetching & merging environment variables on the CLI client
- offloads env variables generation to the API
  - new: shared env variables are now supported in vc env pull! 


### 📋 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-10-21 20:18:15 +00:00
Seiya Nuta
df9accfd6c [@vercel/edge] Support overriding request headers (#8724)
<!--
Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change that you're making:
-->

This PR adds a feature in middleware to add, modify, or delete request
headers. This feature is quite useful to pass data from middleware to
Serverless/Edge API routes.

### New APIs

Adds a new option `request.headers` to the `MiddlewareResponseInit`
parameter in `NextResponse.next()` and `NextResponse.rewrite()`. It's a
[`Header`](https://developer.mozilla.org/en-US/docs/Web/API/Headers)
object holding *all* request headers. Specifically:

```ts
interface ExtraResponseInit extends ResponseInit {
  request?: {
    headers?: Headers
  }
}
```

### Example

```ts
// api/hello.ts
export default (req, res) => {
  const valueFromMiddleware = req.headers['x-hello-from-middleware']
  return res.send(valueFromMiddleware)
}

// middleware.ts
import { next } from '@vercel/edge'

export default function middleware(request: NextRequest) {
  // Clone request headers
  const headers = new Headers(request.headers);
  // Add a new request header
  headers.set('x-hello-from-middleware', 'foo');
  // Delete a request header from the client
  headers.delete('x-from-client');

  return next({
    request: {
      headers
    }
  });
}
```

### New middleware headers

- `x-middleware-override-headers`: A comma separated list of *all*
request header names. Headers not listed will be deleted.
- `x-middleware-request-<name>`: A new value for the header `<name>`.

### Related Issues

- Next.js' implementation: https://github.com/vercel/next.js/pull/41380

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

Co-authored-by: Gal Schlezinger <gal@spitfire.co.il>
2022-10-21 11:27:37 +09:00
JJ Kasper
b388357c0b [build-utils] Fix check for initialHeaders on prerender (#8757)
### Related Issues

x-ref: https://github.com/vercel/vercel/pull/8743

### 📋 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-10-20 15:48:01 -07:00
Seiya Nuta
90291525c2 [edge] Fix the region field of geolocation (#8517) 2022-10-20 17:59:20 +09:00
Gal Schlezinger
812dd43b6a [build-utils] allow EdgeFunction constructor to receive any string as a region (#8747)
### Related Issues

This will allow a shorthand form of `export const config = { regions:
'iad1' }`

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

Co-authored-by: Damien Simonin Feugas <damien@vercel.com>
2022-10-19 14:27:30 -05:00
Gal Schlezinger
9e97e0fd58 [node] allow to extract Edge Function regions from source code (#8733)
Co-authored-by: Steven <steven@ceriously.com>
2022-10-19 18:38:07 +03:00
Chris Barber
74528c2160 [cli] Use ensureLink() in link and pull commands (#8670)
The `link` and `pull` commands have essentially duplicate code from `ensureLink()`. This

Card: https://linear.app/vercel/issue/VCCLI-224/adopt-ensurelink-across-commands

The `dev` command should also be updated to use `ensureLink()`, however that may interfere with this in progress PR: https://github.com/vercel/vercel/pull/8666.

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

- [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-10-18 23:32:58 +00:00
JJ Kasper
82fd2b8068 [build-utils] Allow initialHeaders and initialStatus for Prerender (#8743)
### Related Issues

This allows `initialHeaders` and `initialStatus` for Prender as has been discussed quite a bit so that the proper headers can be applied when serving the fallback.

x-ref: [slack thread](https://vercel.slack.com/archives/C035J346QQL/p1666130102396799?thread_ts=1666122861.189349&cid=C035J346QQL)
x-ref: follow-up to https://github.com/vercel/vercel/pull/8737
x-ref: dd94dcab32/packages/next/src/server-build.ts (L970)

### 📋 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-10-18 22:50:25 +00:00
JJ Kasper
dd94dcab32 Publish Stable
- vercel@28.4.12
 - @vercel/edge@0.0.5
 - @vercel/next@3.2.6
 - @vercel/node@2.5.26
2022-10-18 09:50:29 -07:00
JJ Kasper
300e6c6ebb [next] Ensure vary header is set for static app paths (#8737)
### Related Issues

x-ref: [slack thread](https://vercel.slack.com/archives/C035J346QQL/p1666025811694049?thread_ts=1666008214.416389&cid=C035J346QQL)
x-ref: https://github.com/vercel/next.js/pull/41479

### 📋 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-10-18 14:47:56 +00:00
Kiko Beats
cfe6550ac8 [node] upgrade edge-runtime to v1.1.0-beta.40 (#8740) 2022-10-18 12:28:14 +00:00
Andy McKay
dfe009ffe2 [tests] Update workflow to examine both labels and use annotations (#8736)
### Related Issues

I couldn't find an issue related to this, but just noticed it when I
submitted a request. I got a build failing because I failed to add in an
`area` label, so added that label and it failed again this time on
`semver`. This change makes it so the workflow looks at both labels,
rather than just one and exiting early. This might save some people
doing 2 builds, like I did 😄

Also changed it to use Actions annotations, which does colourization for
you. Docs on that are here:
https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-debug-message

But the output is now something like this ([test
repo](https://github.com/andymckay/test-config)). Missing both labels:

<img width="724" alt="Screen Shot 2022-10-17 at 4 01 35 PM"
src="https://user-images.githubusercontent.com/74699/196299860-9f8ec5e7-fe40-4fe4-8e36-486bf00b2d13.png">

And in the log:

<img width="805" alt="Screen Shot 2022-10-17 at 4 01 41 PM"
src="https://user-images.githubusercontent.com/74699/196299903-149a56e0-2538-419d-8e9f-8e459c7fb42a.png">

And if you've got the labels:

<img width="576" alt="Screen Shot 2022-10-17 at 4 02 59 PM"
src="https://user-images.githubusercontent.com/74699/196299931-88f7f623-f08f-4189-83ec-cd75c466e6a5.png">
2022-10-17 19:16:53 -04:00
Steven
40f38948a0 Publish Stable
- @vercel/build-utils@5.5.5
 - vercel@28.4.11
 - @vercel/client@12.2.13
 - @vercel/frameworks@1.1.8
 - @vercel/fs-detectors@3.4.5
 - @vercel/go@2.2.13
 - @vercel/hydrogen@0.0.26
 - @vercel/next@3.2.5
 - @vercel/node@2.5.25
 - @vercel/python@3.1.22
 - @vercel/redwood@1.0.31
 - @vercel/remix@1.0.32
 - @vercel/ruby@1.3.39
 - @vercel/static-build@1.0.32
2022-10-16 14:11:02 -04:00
Sean Massa
87eba56063 [node] update typescript detection message (#8727)
When deploying a project that uses typescript, but typescript is not a dependency, the default typescript is used. The message that's logged when this happens says:

> Using TypeScript 4.3.4 (no local tsconfig.json)

which is not necessarily true. You can have a `tsconfig.json` file with no typescript dependency.

This message leads to bad debugging paths.

This PR updates the message to be more specific. Now the message will say:

> Using TypeScript 4.3.4 (no local "typescript" package detected)
2022-10-14 21:31:13 +00:00
JJ Kasper
d0a5676c26 [next] Fix index rsc route handling (#8729)
### Related Issues

This ensures we probably route the `/` rsc route properly and adds a regression test along with enabling some now patched tests that were skipped. 

Fixes: [slack thread](https://vercel.slack.com/archives/C043ANYDB24/p1665746921485109)

### 📋 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-10-14 21:18:32 +00:00
Siarhei
da9fa997ed [tests] Update the checkout action to V3 in publish.yml (#8721) 2022-10-11 19:57:55 -04:00
Peter van der Zee
3d79a9d4d4 [build-utils] Cleanup getNodeVersion (#8720)
- `meta.isDev` is unconditionally read after the first check (even though it could technically still be `null`, ignoring typescript)
- `meta.isDev` is explicitly asserted to be falsy, since the first `if` will return early if it's truthy, so the later checks are all redundant
- added `?.` for the `packageJson?.engines?.node` read

The rest is auto formatting (and if my local eslint warnings are correct then I'm just gonna close this PR...)
2022-10-11 23:47:52 +00:00
Sean Massa
ae13c5ee92 Publish Stable
- vercel@28.4.10
 - @vercel/frameworks@1.1.7
 - @vercel/fs-detectors@3.4.4
 - @vercel/node@2.5.24
 - @vercel/static-build@1.0.31
2022-10-11 12:43:42 -05:00
Jacob Ford
ad654139df [frameworks] Detect Hydrogen TypeScript config (#8711)
Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>
2022-10-11 12:04:53 -05:00
Jacob Ford
74f8414e12 [frameworks] Detect additional valid Sanity v3 configs (#8710)
Co-authored-by: Steven <steven@ceriously.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Sean Massa <EndangeredMassa@gmail.com>
2022-10-11 11:02:27 -05:00
Kiko Beats
65c2860e14 [node] upgrade edge-runtime to v1.1.0-beta.38 (#8715)
It fixes an implementation detail issue users are facing when you try to redirect
2022-10-11 13:02:34 +00:00
JJ Kasper
3b5b397b35 Publish Stable
- vercel@28.4.9
 - @vercel/fs-detectors@3.4.3
 - @vercel/next@3.2.4
 - @vercel/node@2.5.23
2022-10-10 17:52:11 -07:00
JJ Kasper
051e061176 [next] Fix optional catch-all data route with middleware (#8713)
### Related Issues

This ensures we properly normalize the optional catch-all data route correctly with middleware as currently the route is expecting a repeated slash in the regex `(?:/(?` which it should just be `(?:(?`. This also adds a regression test for this specific case. 

Fixes: [slack thread](https://vercel.slack.com/archives/C045FKE5P51/p1665153317613089)

### 📋 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-10-10 23:50:20 +00:00
chloetedder
30d46321cc [fs-detectors] Add dark/light turborepo logo (#8694)
### 📋 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-10-07 12:22:41 -05:00
Christian Rackerseder
c2563535ea [node] Allow VercelApiHandler to also return a Promise (#8690)
### Related Issues

Otherwise you are not able to write a serverless function that returns a
Promise when using the ESLint rule
[@typescript-eslint/no-misused-promises](https://typescript-eslint.io/rules/no-misused-promises/)

Following function

```typescript
const handler: VercelApiHandler = async () => {};
```

will report a `Promise-returning function provided to variable where a
void return was expected`

### 📋 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
- [ ] Issue from task tracker has a link to this PR
2022-10-07 12:59:12 -04:00
Steven
5f2bed4f24 [tests] Enable corepack for monorepo (#8691)
This will ensure yarn 1 is used by contributors
2022-10-06 18:07:22 +00:00
chloetedder
cfb7946f4b Publish Stable
- vercel@28.4.8
 - @vercel/fs-detectors@3.4.2
2022-10-06 09:51:35 -05:00
chloetedder
05c5b3a80d [fs-detectors] Add setting placeholders for monorepos (#8688)
### Related Issues

Add in placeholder settings for monorepos

### 📋 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-10-05 21:34:00 +00:00
Nathan Rajlich
45bd855250 Publish Stable
- @vercel/build-utils@5.5.4
 - vercel@28.4.7
 - @vercel/client@12.2.12
 - @vercel/go@2.2.12
 - @vercel/hydrogen@0.0.25
 - @vercel/next@3.2.3
 - @vercel/node@2.5.22
 - @vercel/python@3.1.21
 - @vercel/redwood@1.0.30
 - @vercel/remix@1.0.31
 - @vercel/ruby@1.3.38
 - @vercel/static-build@1.0.30
2022-10-05 12:41:42 -07:00
Nathan Rajlich
49de8ad9a0 [node] Update edge-runtime to v1.1.0-beta.37 (#8687)
Fixes error:

```
ENOENT: no such file or directory, open 'querystring'
```

Unfortunately this issue would only manifest when installed externally. I.e. our tests didn't catch this since the `querystring` module is presumably present in the monorepo.
2022-10-05 19:33:53 +00:00
JJ Kasper
a1ea56fd67 [next] Update max route src check for generated route (#8689)
### Related Issues

This reduces the max length we check for when generating routes to ensure we stay under the 4096 limit after normalizing. 

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

### 📋 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-10-05 19:18:41 +00:00
JJ Kasper
e88addc9ed [next] Fix legacy pages/404 case (#8682)
### Related Issues

This ensures we handle the case were a lambda isn't present for `pages/404.js` with `getStaticProps` which can occur in older Next.js versions e.g. `v9.5.5`. This also adds a regression test for this specific version to ensure it is working as expected. 

x-ref: https://github.com/vercel/vercel/pull/8663
Fixes: [slack thread](https://vercel.slack.com/archives/C03DQ3QFV7C/p1664945825621409)

### 📋 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-10-05 14:21:10 +00:00
Peter van der Zee
5d50013f93 [build-utils] Allow file-ref sema to be controlled through env flag (#8681)
My IDE tells me `process` is unknown but mentions something about package.json so that may just be a superficial issue. I guess CI/CD will tell me soon enough.

This adds an env flag to override the file ref sema's so we can experiment with setting a higher sema.

One potential problem I'm seeing is that this is a generic sema for all the things that use this class. Not sure if that's going to work out as intended but in that case we'll have to find a different way :)
2022-10-05 13:21:27 +00:00
Lee Robinson
44e1eb3983 Update CLI README (#8675)
:another-one:
2022-10-04 04:48:10 +00:00
Lee Robinson
f8af013349 Update README (#8674)
Small changes 😄
2022-10-04 04:13:41 +00:00
Steven
972cc495ec [tests] Replace cancel-workflow-action with native cancel-in-progress (#8671)
This removes the `styfle/cancel-workflow-action` in favor of native GitHub Actions `cancel-in-progress`.

The cancel key is workflow+branch but we don't want to cancel on the `main` branch.

https://docs.github.com/en/actions/using-jobs/using-concurrency
2022-10-03 23:49:28 +00:00
Steven
1c580da3d8 [cli] Fix vc build to error early when runtime is discontinued (#8669)
This moves an existing error from the build container to `vercel build`.

Its rare, but [Vercel Runtimes](https://vercel.com/docs/runtimes) might target a discontinued [AWS Lambda Runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html) so we should fail fast when we know this has happened in `vercel build`.

A test has been added to demonstrate the failure using an old PHP version.
2022-10-03 22:07:02 +00:00
Steven
244554ab1b [tests] Remove nodejs12.x tests (#8667)
Now that `nodejs12.x` has passed the sunset date, new deployments will fail so we need to update a few tests.

https://vercel.com/changelog/node-js-12-is-being-deprecated
2022-10-03 20:59:05 +00:00
Steven
053c185481 Publish Stable
- vercel@28.4.6
 - @vercel/client@12.2.11
 - @vercel/next@3.2.2
2022-10-03 10:07:07 -04:00
JJ Kasper
8805b586ea [next] Allow revalidating ISR 404 path itself (#8663)
### Related Issues

Fixes: https://vercel.slack.com/archives/C03S8ED1DKM/p1664521958768189

### 📋 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-10-02 13:09:24 +00:00
Chris Barber
681070ffa0 [tests] Adding test for next builder OS path separator for serverless file refs (#8661)
Here's the test for https://github.com/vercel/vercel/pull/8657.

#### 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-09-30 18:29:39 +00:00
Chris Barber
362b17d60a [next] Use OS path separator to match serverless file references (#8657)
When running `vc build` for a Next.js app, the Next builder will execute the server build which performs several steps. One of the steps is to trace each serverless function for any referenced files, then the raw list of files is scrubbed and filtered. The filtering uses OS specific file path comparisons to see if a file is of interest. Since it's comparing OS specific paths, we need to use OS specific path separators.

During testing on Windows, the traced serverless functions file list was always empty.

### 📋 Checklist

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

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-09-30 15:25:33 +00:00
JJ Kasper
c7c9b1a791 [next] Update RSC header in has routes (#8651)
### Related Issues

x-ref: https://github.com/vercel/next.js/pull/40979

### 📋 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-09-28 13:24:01 -07:00
Nathan Rajlich
c42f309463 [cli] Print upload progress in increments of 25% when non-TTY (#8650)
When running `vc deploy` in a non-TTY context (i.e. CI), limit the number of progress updates to 25% increments (for a total of 5).

```
Uploading [--------------------] (0.0B/71.9MB)
Uploading [=====---------------] (18.0MB/71.9MB)
Uploading [==========----------] (36.0MB/71.9MB)
Uploading [===============-----] (54.0MB/71.9MB)
Uploading [====================] (71.9MB/71.9MB)
```

This avoids spamming the user logs with many progress updates.
2022-09-28 19:33:33 +00:00
Sean Massa
a0ead28369 [tests] replace spinner messages with normal output during tests (#8634)
Convert spinner output to simple prints during test runs. This makes it easier to write tests against the output of commands.
2022-09-28 17:52:40 +00:00
436 changed files with 37708 additions and 4558 deletions

View File

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

View File

@@ -1,17 +0,0 @@
name: Cancel
on:
push:
branches:
- '**'
- '!main'
jobs:
cancel:
name: 'Cancel Previous Runs'
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: styfle/cancel-workflow-action@0.9.1
with:
workflow_id: test.yml, test-integration-cli.yml, test-unit.yml
access_token: ${{ github.token }}

View File

@@ -18,7 +18,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v1 uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Check Release - name: Check Release
id: check-release id: check-release
run: | run: |

View File

@@ -10,13 +10,17 @@ jobs:
uses: actions/github-script@v6 uses: actions/github-script@v6
with: with:
script: | script: |
let missing = false;
const labels = context.payload.pull_request.labels.map(l => l.name); const labels = context.payload.pull_request.labels.map(l => l.name);
if (labels.filter(l => l.startsWith('area:')).length === 0) { if (labels.filter(l => l.startsWith('area:')).length === 0) {
console.error('\u001b[31mMissing label: Please add at least one "area" label.'); console.error('::error::Missing label: Please add at least one "area" label.');
process.exit(1); missing = true;
} }
if (labels.filter(l => l.startsWith('semver:')).length !== 1) { if (labels.filter(l => l.startsWith('semver:')).length !== 1) {
console.error('\u001b[31mMissing label: Please add exactly one "semver" label.'); console.error('::error::Missing label: Please add exactly one "semver" label.');
missing = true;
}
if (missing) {
process.exit(1); process.exit(1);
} }
console.log('\u001b[32mSuccess: This pull request has correct labels, thanks!'); console.log('::notice::Success: This pull request has correct labels, thanks!');

View File

@@ -13,6 +13,10 @@ env:
TURBO_TEAM: 'vercel' TURBO_TEAM: 'vercel'
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs: jobs:
test: test:
name: CLI name: CLI

View File

@@ -13,6 +13,10 @@ env:
TURBO_TEAM: 'vercel' TURBO_TEAM: 'vercel'
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs: jobs:
test: test:
name: Unit name: Unit

View File

@@ -14,6 +14,10 @@ env:
TURBO_TEAM: 'vercel' TURBO_TEAM: 'vercel'
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs: jobs:
setup: setup:
name: Find Changes name: Find Changes
@@ -78,11 +82,11 @@ jobs:
- run: yarn install --network-timeout 1000000 - run: yarn install --network-timeout 1000000
- name: Build ${{matrix.packageName}} and all its dependencies - name: Build ${{matrix.packageName}} and all its dependencies
run: node_modules/.bin/turbo run build --cache-dir=".turbo" --scope=${{matrix.packageName}} --include-dependencies --no-deps run: node utils/gen.js && node_modules/.bin/turbo run build --cache-dir=".turbo" --scope=${{matrix.packageName}} --include-dependencies --no-deps
env: env:
FORCE_COLOR: '1' FORCE_COLOR: '1'
- name: Test ${{matrix.packageName}} - name: Test ${{matrix.packageName}}
run: node_modules/.bin/turbo run test --cache-dir=".turbo" --scope=${{matrix.packageName}} --no-deps -- ${{ join(matrix.testPaths, ' ') }} run: node utils/gen.js && node_modules/.bin/turbo run test --cache-dir=".turbo" --scope=${{matrix.packageName}} --no-deps -- ${{ join(matrix.testPaths, ' ') }}
shell: bash shell: bash
env: env:
VERCEL_CLI_VERSION: ${{ needs.setup.outputs.dplUrl }}/tarballs/vercel.tgz VERCEL_CLI_VERSION: ${{ needs.setup.outputs.dplUrl }}/tarballs/vercel.tgz

1
.gitignore vendored
View File

@@ -28,3 +28,4 @@ test/lib/deployment/failed-page.txt
__pycache__ __pycache__
.vercel .vercel
.turbo .turbo
turbo-cache-key.json

View File

@@ -380,8 +380,8 @@ This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refere
This is an abstract enumeration type that is implemented by one of the following possible `String` values: This is an abstract enumeration type that is implemented by one of the following possible `String` values:
- `nodejs16.x`
- `nodejs14.x` - `nodejs14.x`
- `nodejs12.x`
- `go1.x` - `go1.x`
- `java11` - `java11`
- `python3.9` - `python3.9`

View File

@@ -19,11 +19,9 @@
## Vercel ## Vercel
Vercel is a platform for **static sites and frontend frameworks**, built to integrate with your headless content, commerce, or database. Vercel is the platform for frontend developers, providing the speed and reliability innovators need to create at the moment of inspiration.
We provide a **frictionless developer experience** to take care of the hard things: deploy instantly, scale automatically, and serve personalized content around the globe. We enable teams to iterate quickly and develop, preview, and ship delightful user experiences. Vercel has zero-configuration support for 35+ frontend frameworks and integrates with your headless content, commerce, or database of choice.
We make it easy for frontend teams to **develop, preview, and ship** delightful user experiences, where performance is the default.
## Deploy ## Deploy
@@ -37,4 +35,4 @@ For details on how to use Vercel, check out our [documentation](https://vercel.c
- [Code of Conduct](./.github/CODE_OF_CONDUCT.md) - [Code of Conduct](./.github/CODE_OF_CONDUCT.md)
- [Contributing Guidelines](./.github/CONTRIBUTING.md) - [Contributing Guidelines](./.github/CONTRIBUTING.md)
- [MIT License](./LICENSE) - [Apache 2.0 License](./LICENSE)

View File

@@ -18,6 +18,12 @@ async function main() {
{ recursive: true, force: true } { recursive: true, force: true }
); );
await fs.cp(
join(repoRoot, 'packages', 'fs-detectors', 'logos'),
join(pubDir, 'monorepo-logos'),
{ recursive: true, force: true }
);
const examples = await getExampleList(); const examples = await getExampleList();
const pathListAll = join(pubDir, 'list-all.json'); const pathListAll = join(pubDir, 'list-all.json');
await fs.writeFile(pathListAll, JSON.stringify(examples)); await fs.writeFile(pathListAll, JSON.stringify(examples));

View File

@@ -16,7 +16,7 @@
"unzip-stream": "0.3.0" "unzip-stream": "0.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "13.1.4", "@types/node": "14.18.33",
"@types/node-fetch": "2.5.4", "@types/node-fetch": "2.5.4",
"@vercel/node": "1.9.0", "@vercel/node": "1.9.0",
"typescript": "3.9.6" "typescript": "3.9.6"

View File

@@ -0,0 +1,13 @@
# Deployment failed
## Why This Error Occurred
When deploying this project, there was not a successful deployment into the `READY` state.
## How to Fix It
This is a generic error to catch problems in the deployment. The error is likely to vary depending on the deployment and the conditions at the time.
Try looking in the logs for information about the deployment and the failure, this could be done at (vercel.com)[https://vercel.com]. You can also use the `vc logs` command to display the build logs for the deployment.
This might not be a permanent error and retrying the deployment might also resolve it.

View File

@@ -8,6 +8,7 @@
"astro": "astro" "astro": "astro"
}, },
"devDependencies": { "devDependencies": {
"astro": "^1.0.0-rc.8" "astro": "^1.0.0-rc.8",
"web-vitals": "^3.0.0"
} }
} }

View File

@@ -1 +1,9 @@
/// <reference types="astro/client" /> /// <reference types="astro/client" />
interface ImportMetaEnv {
readonly PUBLIC_VERCEL_ANALYTICS_ID: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@@ -14,6 +14,19 @@ const { title } = Astro.props as Props;
<link rel="icon" type="image/x-icon" href="/favicon.ico" /> <link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<title>{title}</title> <title>{title}</title>
<script>
import { webVitals } from "../lib/vitals";
let analyticsId = import.meta.env.PUBLIC_VERCEL_ANALYTICS_ID;
if (analyticsId) {
webVitals({
path: location.pathname,
params: location.search,
analyticsId,
});
}
</script>
</head> </head>
<body> <body>
<slot /> <slot />

View File

@@ -0,0 +1,66 @@
import { onCLS, onFCP, onFID, onLCP, onTTFB } from "web-vitals";
const vitalsUrl = "https://vitals.vercel-analytics.com/v1/vitals";
function getConnectionSpeed() {
return "connection" in navigator &&
navigator["connection"] &&
"effectiveType" in navigator["connection"]
? // @ts-ignore
navigator["connection"]["effectiveType"]
: "";
}
/**
* @param {import("web-vitals").Metric} metric
* @param {{ params: { [s: string]: any; } | ArrayLike<any>; path: string; analyticsId: string; debug: boolean; }} options
*/
export function sendToAnalytics(metric, options) {
const page = Object.entries(options.params).reduce(
(acc, [key, value]) => acc.replace(value, `[${key}]`),
options.path
);
const body = {
dsn: options.analyticsId,
id: metric.id,
page,
href: location.href,
event_name: metric.name,
value: metric.value.toString(),
speed: getConnectionSpeed(),
};
if (options.debug) {
console.log("[Analytics]", metric.name, JSON.stringify(body, null, 2));
}
const blob = new Blob([new URLSearchParams(body).toString()], {
// This content type is necessary for `sendBeacon`
type: "application/x-www-form-urlencoded",
});
if (navigator.sendBeacon) {
navigator.sendBeacon(vitalsUrl, blob);
} else
fetch(vitalsUrl, {
body: blob,
method: "POST",
credentials: "omit",
keepalive: true,
});
}
/**
* @param {any} options
*/
export function webVitals(options) {
try {
onFID((metric) => sendToAnalytics(metric, options));
onTTFB((metric) => sendToAnalytics(metric, options));
onLCP((metric) => sendToAnalytics(metric, options));
onCLS((metric) => sendToAnalytics(metric, options));
onFCP((metric) => sendToAnalytics(metric, options));
} catch (err) {
console.error("[Analytics]", err);
}
}

View File

@@ -1,7 +1,6 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
swcMinify: true,
} }
module.exports = nextConfig module.exports = nextConfig

4745
examples/nextjs/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -9,12 +9,10 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"next": "12.3.1", "eslint": "8.27.0",
"eslint-config-next": "13.0.3",
"next": "13.0.3",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0" "react-dom": "18.2.0"
},
"devDependencies": {
"eslint": "8.23.1",
"eslint-config-next": "12.3.1"
} }
} }

View File

@@ -42,6 +42,8 @@ export default function Home() {
<a <a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app" href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
className={styles.card} className={styles.card}
> >
<h2>Deploy &rarr;</h2> <h2>Deploy &rarr;</h2>

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,13 @@ This directory is a brief example of a [Remix](https://remix.run/docs) site that
_Live Example: https://remix-run-template.vercel.app_ _Live Example: https://remix-run-template.vercel.app_
You can also deploy using the [Vercel CLI](https://vercel.com/cli):
```sh
npm i -g vercel
vercel
```
## Development ## Development
To run your Remix app locally, make sure your project's local dependencies are installed: To run your Remix app locally, make sure your project's local dependencies are installed:
@@ -23,5 +30,3 @@ npm run dev
``` ```
Open up [http://localhost:3000](http://localhost:3000) and you should be ready to go! Open up [http://localhost:3000](http://localhost:3000) and you should be ready to go!
If you're used to using the `vercel dev` command provided by [Vercel CLI](https://vercel.com/cli) instead, you can also use that, but it's not needed.

View File

@@ -1,4 +1,22 @@
import { RemixBrowser } from "@remix-run/react"; import { RemixBrowser } from "@remix-run/react";
import { hydrate } from "react-dom"; import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
hydrate(<RemixBrowser />, document); function hydrate() {
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
</StrictMode>
);
});
}
if (window.requestIdleCallback) {
window.requestIdleCallback(hydrate);
} else {
// Safari doesn't support requestIdleCallback
// https://caniuse.com/requestidlecallback
window.setTimeout(hydrate, 1);
}

View File

@@ -8,14 +8,14 @@ export default function handleRequest(
responseHeaders: Headers, responseHeaders: Headers,
remixContext: EntryContext remixContext: EntryContext
) { ) {
let markup = renderToString( const markup = renderToString(
<RemixServer context={remixContext} url={request.url} /> <RemixServer context={remixContext} url={request.url} />
); );
responseHeaders.set("Content-Type", "text/html"); responseHeaders.set("Content-Type", "text/html");
return new Response("<!DOCTYPE html>" + markup, { return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders, headers: responseHeaders,
status: responseStatusCode,
}); });
} }

View File

@@ -1,182 +1,34 @@
import type { LinksFunction, MetaFunction } from "@remix-run/node"; import type { MetaFunction } from "@remix-run/node";
import { import {
Link,
Links, Links,
LiveReload, LiveReload,
Meta, Meta,
Outlet, Outlet,
Scripts, Scripts,
ScrollRestoration, ScrollRestoration,
useCatch,
} from "@remix-run/react"; } from "@remix-run/react";
import { Analytics } from '@vercel/analytics/react';
import darkStylesUrl from "~/styles/dark.css"; export const meta: MetaFunction = () => ({
import globalStylesUrl from "~/styles/global.css";
// https://remix.run/api/conventions#links
export let links: LinksFunction = () => {
return [
{ rel: "stylesheet", href: globalStylesUrl },
{
rel: "stylesheet",
href: darkStylesUrl,
media: "(prefers-color-scheme: dark)"
}
];
};
// https://remix.run/api/conventions#meta
export let meta: MetaFunction = () => ({
charset: "utf-8", charset: "utf-8",
title: "New Remix App",
viewport: "width=device-width,initial-scale=1", viewport: "width=device-width,initial-scale=1",
}); });
// https://remix.run/api/conventions#default-export
// https://remix.run/api/conventions#route-filenames
export default function App() { export default function App() {
return (
<Document>
<Layout>
<Outlet />
</Layout>
</Document>
);
}
// https://remix.run/api/conventions#errorboundary
export function ErrorBoundary({ error }: { error: Error }) {
console.error(error);
return (
<Document title="Error!">
<Layout>
<div>
<h1>There was an error</h1>
<p>{error.message}</p>
<hr />
<p>
Hey, developer, you should replace this with what you want your
users to see.
</p>
</div>
</Layout>
</Document>
);
}
// https://remix.run/api/conventions#catchboundary
export function CatchBoundary() {
let caught = useCatch();
let message;
switch (caught.status) {
case 401:
message = (
<p>
Oops! Looks like you tried to visit a page that you do not have access
to.
</p>
);
break;
case 404:
message = (
<p>Oops! Looks like you tried to visit a page that does not exist.</p>
);
break;
default:
throw new Error(caught.data || caught.statusText);
}
return (
<Document title={`${caught.status} ${caught.statusText}`}>
<Layout>
<h1>
{caught.status}: {caught.statusText}
</h1>
{message}
</Layout>
</Document>
);
}
function Document({
children,
title
}: {
children: React.ReactNode;
title?: string;
}) {
return ( return (
<html lang="en"> <html lang="en">
<head> <head>
{title ? <title>{title}</title> : null}
<Meta /> <Meta />
<Links /> <Links />
</head> </head>
<body> <body>
{children} <Outlet />
<ScrollRestoration /> <ScrollRestoration />
<Scripts /> <Scripts />
<LiveReload /> <LiveReload />
<Analytics />
</body> </body>
</html> </html>
); );
} }
function Layout({ children }: { children: React.ReactNode }) {
return (
<div className="remix-app">
<header className="remix-app__header">
<div className="container remix-app__header-content">
<Link to="/" title="Remix" className="remix-app__header-home-link">
<RemixLogo />
</Link>
<nav aria-label="Main navigation" className="remix-app__header-nav">
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<a href="https://remix.run/docs">Remix Docs</a>
</li>
<li>
<a href="https://github.com/remix-run/remix">GitHub</a>
</li>
</ul>
</nav>
</div>
</header>
<div className="remix-app__main">
<div className="container remix-app__main-content">{children}</div>
</div>
<footer className="remix-app__footer">
<div className="container remix-app__footer-content">
<p>&copy; You!</p>
</div>
</footer>
</div>
);
}
function RemixLogo() {
return (
<svg
viewBox="0 0 659 165"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
aria-labelledby="remix-run-logo-title"
role="img"
width="106"
height="30"
fill="currentColor"
>
<title id="remix-run-logo-title">Remix Logo</title>
<path d="M0 161V136H45.5416C53.1486 136 54.8003 141.638 54.8003 145V161H0Z M133.85 124.16C135.3 142.762 135.3 151.482 135.3 161H92.2283C92.2283 158.927 92.2653 157.03 92.3028 155.107C92.4195 149.128 92.5411 142.894 91.5717 130.304C90.2905 111.872 82.3473 107.776 67.7419 107.776H54.8021H0V74.24H69.7918C88.2407 74.24 97.4651 68.632 97.4651 53.784C97.4651 40.728 88.2407 32.816 69.7918 32.816H0V0H77.4788C119.245 0 140 19.712 140 51.2C140 74.752 125.395 90.112 105.665 92.672C122.32 96 132.057 105.472 133.85 124.16Z" />
<path d="M229.43 120.576C225.59 129.536 218.422 133.376 207.158 133.376C194.614 133.376 184.374 126.72 183.35 112.64H263.478V101.12C263.478 70.1437 243.254 44.0317 205.11 44.0317C169.526 44.0317 142.902 69.8877 142.902 105.984C142.902 142.336 169.014 164.352 205.622 164.352C235.83 164.352 256.822 149.76 262.71 123.648L229.43 120.576ZM183.862 92.6717C185.398 81.9197 191.286 73.7277 204.598 73.7277C216.886 73.7277 223.542 82.4317 224.054 92.6717H183.862Z" />
<path d="M385.256 66.5597C380.392 53.2477 369.896 44.0317 349.672 44.0317C332.52 44.0317 320.232 51.7117 314.088 64.2557V47.1037H272.616V161.28H314.088V105.216C314.088 88.0638 318.952 76.7997 332.52 76.7997C345.064 76.7997 348.136 84.9917 348.136 100.608V161.28H389.608V105.216C389.608 88.0638 394.216 76.7997 408.04 76.7997C420.584 76.7997 423.4 84.9917 423.4 100.608V161.28H464.872V89.5997C464.872 65.7917 455.656 44.0317 424.168 44.0317C404.968 44.0317 391.4 53.7597 385.256 66.5597Z" />
<path d="M478.436 47.104V161.28H519.908V47.104H478.436ZM478.18 36.352H520.164V0H478.18V36.352Z" />
<path d="M654.54 47.1035H611.788L592.332 74.2395L573.388 47.1035H527.564L568.78 103.168L523.98 161.28H566.732L589.516 130.304L612.3 161.28H658.124L613.068 101.376L654.54 47.1035Z" />
</svg>
);
}

View File

@@ -1,44 +0,0 @@
import type { MetaFunction, LinksFunction } from "@remix-run/node";
import { Outlet } from "@remix-run/react";
import stylesUrl from "~/styles/demos/about.css";
export let meta: MetaFunction = () => {
return {
title: "About Remix"
};
};
export let links: LinksFunction = () => {
return [{ rel: "stylesheet", href: stylesUrl }];
};
export default function Index() {
return (
<div className="about">
<div className="about__intro">
<h2>About Us</h2>
<p>
Ok, so this page isn't really <em>about us</em>, but we did want to
show you a few more things Remix can do.
</p>
<p>
Did you notice that things look a little different on this page? The
CSS that we import in the route file and include in its{" "}
<code>links</code> export is only included on this route and its
children.
</p>
<p>
Wait a sec...<em>its children</em>? To understand what we mean by
this,{" "}
<a href="https://remix.run/docs/en/v1/guides/routing">
read all about nested routes in the docs
</a>
.
</p>
<hr />
<Outlet />
</div>
</div>
);
}

View File

@@ -1,17 +0,0 @@
import { Link } from "@remix-run/react";
export default function AboutIndex() {
return (
<div>
<p>
You are looking at the index route for the <code>/about</code> URL
segment, but there are nested routes as well!
</p>
<p>
<strong>
<Link to="whoa">Check out one of them here.</Link>
</strong>
</p>
</div>
);
}

View File

@@ -1,20 +0,0 @@
import { Link } from "@remix-run/react";
export default function AboutIndex() {
return (
<div>
<p>
Whoa, this is a nested route! We render the <code>/about</code> layout
route component, and its <code>Outlet</code> renders our route
component. 🤯
</p>
<p>
<strong>
<Link to="..">
Go back to the <code>/about</code> index.
</Link>
</strong>
</p>
</div>
);
}

View File

@@ -1,102 +0,0 @@
import type { ActionFunction } from "@remix-run/node";
import { json, redirect } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
import { useEffect, useRef } from "react";
export function meta() {
return { title: "Actions Demo" };
}
// When your form sends a POST, the action is called on the server.
// - https://remix.run/api/conventions#action
// - https://remix.run/guides/data-updates
export let action: ActionFunction = async ({ request }) => {
let formData = await request.formData();
let answer = formData.get("answer");
// Typical action workflows start with validating the form data that just came
// over the network. Clientside validation is fine, but you definitely need it
// server side. If there's a problem, return the the data and the component
// can render it.
if (typeof answer !== "string") {
return json("Come on, at least try!", { status: 400 });
}
if (answer !== "egg") {
return json(`Sorry, ${answer} is not right.`, { status: 400 });
}
// Finally, if the data is valid, you'll typically write to a database or send or
// email or log the user in, etc. It's recommended to redirect after a
// successful action, even if it's to the same place so that non-JavaScript workflows
// from the browser doesn't repost the data if the user clicks back.
return redirect("/demos/correct");
};
export default function ActionsDemo() {
// https://remix.run/api/remix#useactiondata
let actionMessage = useActionData<string>();
let answerRef = useRef<HTMLInputElement>(null);
// This form works without JavaScript, but when we have JavaScript we can make
// the experience better by selecting the input on wrong answers! Go ahead, disable
// JavaScript in your browser and see what happens.
useEffect(() => {
if (actionMessage && answerRef.current) {
answerRef.current.select();
}
}, [actionMessage]);
return (
<div className="remix__page">
<main>
<h2>Actions!</h2>
<p>
This form submission will send a post request that we handle in our
`action` export. Any route can export an action to handle data
mutations.
</p>
<Form method="post" className="remix__form">
<h3>Post an Action</h3>
<p>
<i>What is more useful when it is broken?</i>
</p>
<label>
<div>Answer:</div>
<input ref={answerRef} name="answer" type="text" />
</label>
<div>
<button>Answer!</button>
</div>
{actionMessage ? (
<p>
<b>{actionMessage}</b>
</p>
) : null}
</Form>
</main>
<aside>
<h3>Additional Resources</h3>
<ul>
<li>
Guide:{" "}
<a href="https://remix.run/guides/data-writes">Data Writes</a>
</li>
<li>
API:{" "}
<a href="https://remix.run/api/conventions#action">
Route Action Export
</a>
</li>
<li>
API:{" "}
<a href="https://remix.run/api/remix#useactiondata">
<code>useActionData</code>
</a>
</li>
</ul>
</aside>
</div>
);
}

View File

@@ -1,3 +0,0 @@
export default function NiceWork() {
return <h1>You got it right!</h1>;
}

View File

@@ -1,43 +0,0 @@
import type { MetaFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Link, Outlet, useCatch, useLoaderData } from "@remix-run/react";
export let meta: MetaFunction = () => ({ title: "Boundaries Demo" });
export default function Boundaries() {
return (
<div className="remix__page">
<main>
<Outlet />
</main>
<aside>
<h2>Click these Links</h2>
<ul>
<li>
<Link to=".">Start over</Link>
</li>
<li>
<Link to="one">
Param: <i>one</i>
</Link>
</li>
<li>
<Link to="two">
Param: <i>two</i>
</Link>
</li>
<li>
<Link to="this-record-does-not-exist">This will be a 404</Link>
</li>
<li>
<Link to="shh-its-a-secret">And this will be 401 Unauthorized</Link>
</li>
<li>
<Link to="kaboom">This one will throw an error</Link>
</li>
</ul>
</aside>
</div>
);
}

View File

@@ -1,111 +0,0 @@
import type { LoaderFunction, MetaFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Link, useCatch, useLoaderData } from "@remix-run/react";
// The `$` in route filenames becomes a pattern that's parsed from the URL and
// passed to your loaders so you can look up data.
// - https://remix.run/api/conventions#loader-params
export let loader: LoaderFunction = async ({ params }) => {
// pretend like we're using params.id to look something up in the db
if (params.id === "this-record-does-not-exist") {
// If the record doesn't exist we can't render the route normally, so
// instead we throw a 404 reponse to stop running code here and show the
// user the catch boundary.
throw new Response("Not Found", { status: 404 });
}
// now pretend like the record exists but the user just isn't authorized to
// see it.
if (params.id === "shh-its-a-secret") {
// Again, we can't render the component if the user isn't authorized. You
// can even put data in the response that might help the user rectify the
// issue! Like emailing the webmaster for access to the page. (Oh, right,
// `json` is just a Response helper that makes it easier to send JSON
// responses).
throw json({ webmasterEmail: "hello@remix.run" }, { status: 401 });
}
// Sometimes your code just blows up and you never anticipated it. Remix will
// automatically catch it and send the UI to the error boundary.
if (params.id === "kaboom") {
lol();
}
// but otherwise the record was found, user has access, so we can do whatever
// else we needed to in the loader and return the data. (This is boring, we're
// just gonna return the params.id).
return { param: params.id };
};
export default function ParamDemo() {
let data = useLoaderData();
return (
<h1>
The param is <i style={{ color: "red" }}>{data.param}</i>
</h1>
);
}
// https://remix.run/api/conventions#catchboundary
// https://remix.run/api/remix#usecatch
// https://remix.run/api/guides/not-found
export function CatchBoundary() {
let caught = useCatch();
let message: React.ReactNode;
switch (caught.status) {
case 401:
message = (
<p>
Looks like you tried to visit a page that you do not have access to.
Maybe ask the webmaster ({caught.data.webmasterEmail}) for access.
</p>
);
case 404:
message = (
<p>Looks like you tried to visit a page that does not exist.</p>
);
default:
message = (
<p>
There was a problem with your request!
<br />
{caught.status} {caught.statusText}
</p>
);
}
return (
<>
<h2>Oops!</h2>
<p>{message}</p>
<p>
(Isn't it cool that the user gets to stay in context and try a different
link in the parts of the UI that didn't blow up?)
</p>
</>
);
}
// https://remix.run/api/conventions#errorboundary
// https://remix.run/api/guides/not-found
export function ErrorBoundary({ error }: { error: Error }) {
console.error(error);
return (
<>
<h2>Error!</h2>
<p>{error.message}</p>
<p>
(Isn't it cool that the user gets to stay in context and try a different
link in the parts of the UI that didn't blow up?)
</p>
</>
);
}
export let meta: MetaFunction = ({ data }) => {
return {
title: data ? `Param: ${data.param}` : "Oops...",
};
};

View File

@@ -1,41 +0,0 @@
import type { LoaderFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Link, Outlet, useCatch, useLoaderData } from "@remix-run/react";
export default function Boundaries() {
return (
<>
<h2>Params</h2>
<p>
When you name a route segment with $ like{" "}
<code>routes/users/$userId.js</code>, the $ segment will be parsed from
the URL and sent to your loaders and actions by the same name.
</p>
<h2>Errors</h2>
<p>
When a route throws and error in it's action, loader, or component,
Remix automatically catches it, won't even try to render the component,
but it will render the route's ErrorBoundary instead. If the route
doesn't have one, it will bubble up to the routes above it until it hits
the root.
</p>
<p>So be as granular as you want with your error handling.</p>
<h2>Not Found</h2>
<p>
(and other{" "}
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#client_error_responses">
client errors
</a>
)
</p>
<p>
Loaders and Actions can throw a <code>Response</code> instead of an
error and Remix will render the CatchBoundary instead of the component.
This is great when loading data from a database isn't found. As soon as
you know you can't render the component normally, throw a 404 response
and send your app into the catch boundary. Just like error boundaries,
catch boundaries bubble, too.
</p>
</>
);
}

View File

@@ -1,101 +1,32 @@
import type { MetaFunction, LoaderFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
import { Link, useLoaderData } from "@remix-run/react";
type IndexData = {
resources: Array<{ name: string; url: string }>;
demos: Array<{ name: string; to: string }>;
};
// Loaders provide data to components and are only ever called on the server, so
// you can connect to a database or run any server side code you want right next
// to the component that renders it.
// https://remix.run/api/conventions#loader
export let loader: LoaderFunction = () => {
let data: IndexData = {
resources: [
{
name: "Remix Docs",
url: "https://remix.run/docs"
},
{
name: "React Router Docs",
url: "https://reactrouter.com/docs"
},
{
name: "Remix Discord",
url: "https://discord.gg/VBePs6d"
}
],
demos: [
{
to: "demos/actions",
name: "Actions"
},
{
to: "demos/about",
name: "Nested Routes, CSS loading/unloading"
},
{
to: "demos/params",
name: "URL Params and Error Boundaries"
}
]
};
// https://remix.run/api/remix#json
return json(data);
};
// https://remix.run/api/conventions#meta
export let meta: MetaFunction = () => {
return {
title: "Remix Starter",
description: "Welcome to remix!"
};
};
// https://remix.run/guides/routing#index-routes
export default function Index() { export default function Index() {
let data = useLoaderData<IndexData>();
return ( return (
<div className="remix__page"> <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
<main> <h1>Welcome to Remix</h1>
<h2>Welcome to Remix!</h2> <ul>
<p>We're stoked that you're here. 🥳</p> <li>
<p> <a
Feel free to take a look around the code to see how Remix does things, target="_blank"
it might be a bit different than what youre used to. When you're href="https://remix.run/tutorials/blog"
ready to dive deeper, we've got plenty of resources to get you rel="noreferrer"
up-and-running quickly. >
</p> 15m Quickstart Blog Tutorial
<p> </a>
Check out all the demos in this starter, and then just delete the{" "} </li>
<code>app/routes/demos</code> and <code>app/styles/demos</code>{" "} <li>
folders when you're ready to turn this into your next project. <a
</p> target="_blank"
</main> href="https://remix.run/tutorials/jokes"
<aside> rel="noreferrer"
<h2>Demos In This App</h2> >
<ul> Deep Dive Jokes App Tutorial
{data.demos.map(demo => ( </a>
<li key={demo.to} className="remix__page__resource"> </li>
<Link to={demo.to} prefetch="intent"> <li>
{demo.name} <a target="_blank" href="https://remix.run/docs" rel="noreferrer">
</Link> Remix Docs
</li> </a>
))} </li>
</ul> </ul>
<h2>Resources</h2>
<ul>
{data.resources.map(resource => (
<li key={resource.url} className="remix__page__resource">
<a href={resource.url}>{resource.name}</a>
</li>
))}
</ul>
</aside>
</div> </div>
); );
} }

View File

@@ -1,7 +0,0 @@
:root {
--color-foreground: hsl(0, 0%, 100%);
--color-background: hsl(0, 0%, 7%);
--color-links: hsl(213, 100%, 73%);
--color-links-hover: hsl(213, 100%, 80%);
--color-border: hsl(0, 0%, 25%);
}

View File

@@ -1,26 +0,0 @@
/*
* Whoa whoa whoa, wait a sec...why are we overriding global CSS selectors?
* Isn't that kind of scary? How do we know this won't have side effects?
*
* In Remix, CSS that is included in a route file will *only* show up on that
* route (and for nested routes, its children). When the user navigates away
* from that route the CSS files linked from those routes will be automatically
* unloaded, making your styles much easier to predict and control.
*
* Read more about styling routes in the docs:
* https://remix.run/guides/styling
*/
:root {
--color-foreground: hsl(0, 0%, 7%);
--color-background: hsl(56, 100%, 50%);
--color-links: hsl(345, 56%, 39%);
--color-links-hover: hsl(345, 51%, 49%);
--color-border: rgb(184, 173, 20);
--font-body: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
Liberation Mono, Courier New, monospace;
}
.about__intro {
max-width: 500px;
}

View File

@@ -1,216 +0,0 @@
/*
* You can just delete everything here or keep whatever you like, it's just a
* quick baseline!
*/
:root {
--color-foreground: hsl(0, 0%, 7%);
--color-background: hsl(0, 0%, 100%);
--color-links: hsl(213, 100%, 52%);
--color-links-hover: hsl(213, 100%, 43%);
--color-border: hsl(0, 0%, 82%);
--font-body: -apple-system, "Segoe UI", Helvetica Neue, Helvetica, Roboto,
Arial, sans-serif, system-ui, "Apple Color Emoji", "Segoe UI Emoji";
}
html {
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
:-moz-focusring {
outline: auto;
}
:focus {
outline: var(--color-links) solid 2px;
outline-offset: 2px;
}
html,
body {
padding: 0;
margin: 0;
background-color: var(--color-background);
color: var(--color-foreground);
}
body {
font-family: var(--font-body);
line-height: 1.5;
}
a {
color: var(--color-links);
text-decoration: none;
}
a:hover {
color: var(--color-links-hover);
text-decoration: underline;
}
hr {
display: block;
height: 1px;
border: 0;
background-color: var(--color-border);
margin-top: 2rem;
margin-bottom: 2rem;
}
input:where([type="text"]),
input:where([type="search"]) {
display: block;
border: 1px solid var(--color-border);
width: 100%;
font: inherit;
line-height: 1;
height: calc(1ch + 1.5em);
padding-right: 0.5em;
padding-left: 0.5em;
background-color: hsl(0 0% 100% / 20%);
color: var(--color-foreground);
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.container {
--gutter: 16px;
width: 1024px;
max-width: calc(100% - var(--gutter) * 2);
margin-right: auto;
margin-left: auto;
}
.remix-app {
display: flex;
flex-direction: column;
min-height: 100vh;
min-height: calc(100vh - env(safe-area-inset-bottom));
}
.remix-app > * {
width: 100%;
}
.remix-app__header {
padding-top: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--color-border);
}
.remix-app__header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.remix-app__header-home-link {
width: 106px;
height: 30px;
color: var(--color-foreground);
}
.remix-app__header-nav ul {
list-style: none;
margin: 0;
display: flex;
align-items: center;
gap: 1.5em;
}
.remix-app__header-nav li {
font-weight: bold;
}
.remix-app__main {
flex: 1 1 100%;
}
.remix-app__footer {
padding-top: 1rem;
padding-bottom: 1rem;
border-top: 1px solid var(--color-border);
}
.remix-app__footer-content {
display: flex;
justify-content: center;
align-items: center;
}
.remix__page {
--gap: 1rem;
--space: 2rem;
display: grid;
grid-auto-rows: min-content;
gap: var(--gap);
padding-top: var(--space);
padding-bottom: var(--space);
}
@media print, screen and (min-width: 640px) {
.remix__page {
--gap: 2rem;
grid-auto-rows: unset;
grid-template-columns: repeat(2, 1fr);
}
}
@media screen and (min-width: 1024px) {
.remix__page {
--gap: 4rem;
}
}
.remix__page > main > :first-child {
margin-top: 0;
}
.remix__page > main > :last-child {
margin-bottom: 0;
}
.remix__page > aside {
margin: 0;
padding: 1.5ch 2ch;
border: solid 1px var(--color-border);
border-radius: 0.5rem;
}
.remix__page > aside > :first-child {
margin-top: 0;
}
.remix__page > aside > :last-child {
margin-bottom: 0;
}
.remix__form {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
border: 1px solid var(--color-border);
border-radius: 0.5rem;
}
.remix__form > * {
margin-top: 0;
margin-bottom: 0;
}

23926
examples/remix/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,21 +6,22 @@
"dev": "remix dev" "dev": "remix dev"
}, },
"dependencies": { "dependencies": {
"@remix-run/node": "^1.5.1", "@remix-run/node": "^1.7.5",
"@remix-run/react": "^1.5.1", "@remix-run/react": "^1.7.5",
"@remix-run/vercel": "^1.5.1", "@remix-run/vercel": "^1.7.5",
"@vercel/node": "^2.0.0", "@vercel/analytics": "^0.1.3",
"react": "^17.0.2", "@vercel/node": "^2.4.4",
"react-dom": "^17.0.2" "react": "^18.2.0",
"react-dom": "^18.2.0"
}, },
"devDependencies": { "devDependencies": {
"@remix-run/dev": "^1.5.1", "@remix-run/dev": "^1.7.5",
"@remix-run/eslint-config": "^1.5.1", "@remix-run/eslint-config": "^1.7.5",
"@remix-run/serve": "^1.5.1", "@remix-run/serve": "^1.7.5",
"@types/react": "^17.0.45", "@types/react": "^18.0.15",
"@types/react-dom": "^17.0.17", "@types/react-dom": "^18.0.6",
"eslint": "^8.15.0", "eslint": "^8.23.1",
"typescript": "^4.6.4" "typescript": "^4.7.4"
}, },
"engines": { "engines": {
"node": ">=14" "node": ">=14"

View File

@@ -1,6 +1,4 @@
/** /** @type {import('@remix-run/dev').AppConfig} */
* @type {import('@remix-run/dev').AppConfig}
*/
module.exports = { module.exports = {
serverBuildTarget: "vercel", serverBuildTarget: "vercel",
// When running locally in development mode, we use the built in remix // When running locally in development mode, we use the built in remix

View File

@@ -1,2 +1,2 @@
/// <reference types="@remix-run/dev" /> /// <reference types="@remix-run/dev" />
/// <reference types="@remix-run/node/globals" /> /// <reference types="@remix-run/node" />

View File

@@ -1,4 +1,4 @@
import * as build from "@remix-run/dev/server-build";
import { createRequestHandler } from "@remix-run/vercel"; import { createRequestHandler } from "@remix-run/vercel";
import * as build from "@remix-run/dev/server-build";
export default createRequestHandler({ build, mode: process.env.NODE_ENV }); export default createRequestHandler({ build, mode: process.env.NODE_ENV });

View File

@@ -2,12 +2,16 @@
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
"compilerOptions": { "compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2019"], "lib": ["DOM", "DOM.Iterable", "ES2019"],
"isolatedModules": true,
"esModuleInterop": true, "esModuleInterop": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"target": "ES2019", "target": "ES2019",
"strict": true, "strict": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": { "paths": {
"~/*": ["./app/*"] "~/*": ["./app/*"]
}, },

View File

@@ -7,12 +7,16 @@
"type": "module", "type": "module",
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"solid-app-router": "^0.3.2", "solid-start-vercel": "^0.2.0",
"solid-js": "^1.3.15", "typescript": "^4.8.3",
"solid-meta": "^0.27.3", "vite": "^3.1.0"
"solid-start": "next", },
"solid-start-vercel": "next", "dependencies": {
"vite": "^2.9.9" "@solidjs/meta": "^0.28.2",
"@solidjs/router": "^0.5.0",
"solid-js": "^1.6.0",
"solid-start": "^0.2.0",
"undici": "^5.11.0"
}, },
"engines": { "engines": {
"node": "16.x" "node": "16.x"

View File

@@ -1,4 +1,3 @@
import { hydrate } from "solid-js/web"; import { mount, StartClient } from 'solid-start/entry-client';
import { StartClient } from "solid-start/entry-client";
hydrate(() => <StartClient />, document); mount(() => <StartClient />, document);

View File

@@ -1,7 +1,9 @@
import { StartServer, createHandler, renderAsync } from "solid-start/entry-server"; import {
import { inlineServerModules } from "solid-start/server"; StartServer,
createHandler,
renderAsync,
} from 'solid-start/entry-server';
export default createHandler( export default createHandler(
inlineServerModules, renderAsync(event => <StartServer event={event} />)
renderAsync((context) => <StartServer context={context} />)
); );

View File

@@ -1,25 +1,33 @@
// @refresh reload // @refresh reload
import { Links, Meta, Routes, Scripts } from "solid-start/root"; import {
import { ErrorBoundary } from "solid-start/error-boundary"; Html,
import { Suspense } from "solid-js"; Head,
Body,
Meta,
Routes,
FileRoutes,
Scripts,
ErrorBoundary,
} from 'solid-start';
import { Suspense } from 'solid-js';
export default function Root() { export default function Root() {
return ( return (
<html lang="en"> <Html lang="en">
<head> <Head>
<meta charset="utf-8" /> <Meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <Meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta /> </Head>
<Links /> <Body>
</head>
<body>
<ErrorBoundary> <ErrorBoundary>
<Suspense> <Suspense>
<Routes /> <Routes>
<FileRoutes />
</Routes>
</Suspense> </Suspense>
</ErrorBoundary> </ErrorBoundary>
<Scripts /> <Scripts />
</body> </Body>
</html> </Html>
); );
} }

View File

@@ -8,8 +8,8 @@ export default function Home() {
<Counter /> <Counter />
<p> <p>
Visit{" "} Visit{" "}
<a href="https://solidjs.com" target="_blank"> <a href="https://start.solidjs.com" target="_blank">
solidjs.com start.solidjs.com
</a>{" "} </a>{" "}
to learn how to build Solid apps. to learn how to build Solid apps.
</p> </p>

View File

@@ -1,7 +0,0 @@
{
"build": {
"env": {
"ENABLE_VC_BUILD": "1"
}
}
}

View File

@@ -1,5 +1,5 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import solid from "solid-start"; import solid from "solid-start/vite";
import vercel from "solid-start-vercel"; import vercel from "solid-start-vercel";
export default defineConfig({ export default defineConfig({

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"packageManager": "yarn@1.22.19",
"workspaces": { "workspaces": {
"packages": [ "packages": [
"packages/*" "packages/*"
@@ -43,14 +44,14 @@
"publish-canary": "git checkout main && git pull && lerna version prerelease --preid canary --message \"Publish Canary\" --exact", "publish-canary": "git checkout main && git pull && lerna version prerelease --preid canary --message \"Publish Canary\" --exact",
"publish-from-github": "./utils/publish.sh", "publish-from-github": "./utils/publish.sh",
"changelog": "node utils/changelog.js", "changelog": "node utils/changelog.js",
"build": "turbo run build", "build": "node utils/gen.js && turbo run build",
"vercel-build": "yarn build && yarn run pack && cd api && node -r ts-eager/register ./_lib/script/build.ts", "vercel-build": "yarn build && yarn run pack && cd api && node -r ts-eager/register ./_lib/script/build.ts",
"pre-commit": "lint-staged", "pre-commit": "lint-staged",
"test": "jest --rootDir=\"test\" --testPathPattern=\"\\.test.js\"", "test": "jest --rootDir=\"test\" --testPathPattern=\"\\.test.js\"",
"test-unit": "yarn test && turbo run test-unit", "test-unit": "yarn test && node utils/gen.js && turbo run test-unit",
"test-integration-cli": "turbo run test-integration-cli", "test-integration-cli": "node utils/gen.js && turbo run test-integration-cli",
"test-integration-once": "turbo run test-integration-once", "test-integration-once": "node utils/gen.js && turbo run test-integration-once",
"test-integration-dev": "turbo run test-integration-dev", "test-integration-dev": "node utils/gen.js && turbo run test-integration-dev",
"lint": "eslint . --ext .ts,.js", "lint": "eslint . --ext .ts,.js",
"prepare": "husky install", "prepare": "husky install",
"pack": "cd utils && node -r ts-eager/register ./pack.ts" "pack": "cd utils && node -r ts-eager/register ./pack.ts"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/build-utils", "name": "@vercel/build-utils",
"version": "5.5.3", "version": "5.5.7",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.js", "types": "./dist/index.d.js",

View File

@@ -39,7 +39,7 @@ export class EdgeFunction {
assets?: { name: string; path: string }[]; assets?: { name: string; path: string }[];
/** The regions where the edge function will be executed on */ /** The regions where the edge function will be executed on */
regions?: 'auto' | string[] | 'all' | 'default'; regions?: string | string[];
constructor(params: Omit<EdgeFunction, 'type'>) { constructor(params: Omit<EdgeFunction, 'type'>) {
this.type = 'EdgeFunction'; this.type = 'EdgeFunction';

View File

@@ -5,7 +5,13 @@ import path from 'path';
import Sema from 'async-sema'; import Sema from 'async-sema';
import { FileBase } from './types'; import { FileBase } from './types';
const semaToPreventEMFILE = new Sema(20); const DEFAULT_SEMA = 20;
const semaToPreventEMFILE = new Sema(
parseInt(
process.env.VERCEL_INTERNAL_FILE_FS_REF_SEMA || String(DEFAULT_SEMA),
10
) || DEFAULT_SEMA
);
interface FileFsRefOptions { interface FileFsRefOptions {
mode?: number; mode?: number;

View File

@@ -12,7 +12,13 @@ interface FileRefOptions {
mutable?: boolean; mutable?: boolean;
} }
const semaToDownloadFromS3 = new Sema(5); const DEFAULT_SEMA = 5;
const semaToDownloadFromS3 = new Sema(
parseInt(
process.env.VERCEL_INTERNAL_FILE_REF_SEMA || String(DEFAULT_SEMA),
10
) || DEFAULT_SEMA
);
class BailableError extends Error { class BailableError extends Error {
public bail: boolean; public bail: boolean;

View File

@@ -3,28 +3,37 @@ import { NodeVersion } from '../types';
import { NowBuildError } from '../errors'; import { NowBuildError } from '../errors';
import debug from '../debug'; import debug from '../debug';
const allOptions = [ function getOptions() {
{ major: 16, range: '16.x', runtime: 'nodejs16.x' }, const options = [
{ major: 14, range: '14.x', runtime: 'nodejs14.x' }, { major: 16, range: '16.x', runtime: 'nodejs16.x' },
{ { major: 14, range: '14.x', runtime: 'nodejs14.x' },
major: 12, {
range: '12.x', major: 12,
runtime: 'nodejs12.x', range: '12.x',
discontinueDate: new Date('2022-10-03'), runtime: 'nodejs12.x',
}, discontinueDate: new Date('2022-10-03'),
{ },
major: 10, {
range: '10.x', major: 10,
runtime: 'nodejs10.x', range: '10.x',
discontinueDate: new Date('2021-04-20'), runtime: 'nodejs10.x',
}, discontinueDate: new Date('2021-04-20'),
{ },
major: 8, {
range: '8.10.x', major: 8,
runtime: 'nodejs8.10', range: '8.10.x',
discontinueDate: new Date('2020-01-06'), runtime: 'nodejs8.10',
}, discontinueDate: new Date('2020-01-06'),
] as const; },
] as const;
if (process.env.VERCEL_ALLOW_NODEJS18 === '1') {
return [
{ major: 18, range: '18.x', runtime: 'nodejs18.x' },
...options,
] as const;
}
return options;
}
function getHint(isAuto = false) { function getHint(isAuto = false) {
const { major, range } = getLatestNodeVersion(); const { major, range } = getLatestNodeVersion();
@@ -34,11 +43,11 @@ function getHint(isAuto = false) {
} }
export function getLatestNodeVersion() { export function getLatestNodeVersion() {
return allOptions[0]; return getOptions()[0];
} }
export function getDiscontinuedNodeVersions(): NodeVersion[] { export function getDiscontinuedNodeVersions(): NodeVersion[] {
return allOptions.filter(isDiscontinued); return getOptions().filter(isDiscontinued);
} }
export async function getSupportedNodeVersion( export async function getSupportedNodeVersion(
@@ -50,7 +59,7 @@ export async function getSupportedNodeVersion(
if (engineRange) { if (engineRange) {
const found = const found =
validRange(engineRange) && validRange(engineRange) &&
allOptions.some(o => { getOptions().some(o => {
// the array is already in order so return the first // the array is already in order so return the first
// match which will be the newest version of node // match which will be the newest version of node
selection = o; selection = o;

View File

@@ -251,33 +251,24 @@ export async function getNodeVersion(
meta: Meta = {} meta: Meta = {}
): Promise<NodeVersion> { ): Promise<NodeVersion> {
const latest = getLatestNodeVersion(); const latest = getLatestNodeVersion();
if (meta && meta.isDev) { if (meta.isDev) {
// Use the system-installed version of `node` in PATH for `vercel dev` // Use the system-installed version of `node` in PATH for `vercel dev`
return { ...latest, runtime: 'nodejs' }; return { ...latest, runtime: 'nodejs' };
} }
const { packageJson } = await scanParentDirs(destPath, true); const { packageJson } = await scanParentDirs(destPath, true);
let { nodeVersion } = config; let { nodeVersion } = config;
let isAuto = true; let isAuto = true;
if (packageJson && packageJson.engines && packageJson.engines.node) { if (packageJson?.engines?.node) {
const { node } = packageJson.engines; const { node } = packageJson.engines;
if ( if (nodeVersion && validRange(node) && !intersects(nodeVersion, node)) {
nodeVersion &&
validRange(node) &&
!intersects(nodeVersion, node) &&
!meta.isDev
) {
console.warn( console.warn(
`Warning: Due to "engines": { "node": "${node}" } in your \`package.json\` file, the Node.js Version defined in your Project Settings ("${nodeVersion}") will not apply. Learn More: http://vercel.link/node-version` `Warning: Due to "engines": { "node": "${node}" } in your \`package.json\` file, the Node.js Version defined in your Project Settings ("${nodeVersion}") will not apply. Learn More: http://vercel.link/node-version`
); );
} else if (coerce(node)?.raw === node && !meta.isDev) { } else if (coerce(node)?.raw === node) {
console.warn( console.warn(
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` with major.minor.patch, but only major Node.js Version can be selected. Learn More: http://vercel.link/node-version` `Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` with major.minor.patch, but only major Node.js Version can be selected. Learn More: http://vercel.link/node-version`
); );
} else if ( } else if (validRange(node) && intersects(`${latest.major + 1}.x`, node)) {
validRange(node) &&
intersects(`${latest.major + 1}.x`, node) &&
!meta.isDev
) {
console.warn( console.warn(
`Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version` `Warning: Detected "engines": { "node": "${node}" } in your \`package.json\` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version`
); );

View File

@@ -23,6 +23,7 @@ export interface LambdaOptionsBase {
regions?: string[]; regions?: string[];
supportsMultiPayloads?: boolean; supportsMultiPayloads?: boolean;
supportsWrapper?: boolean; supportsWrapper?: boolean;
experimentalResponseStreaming?: boolean;
} }
export interface LambdaOptionsWithFiles extends LambdaOptionsBase { export interface LambdaOptionsWithFiles extends LambdaOptionsBase {
@@ -60,6 +61,7 @@ export class Lambda {
zipBuffer?: Buffer; zipBuffer?: Buffer;
supportsMultiPayloads?: boolean; supportsMultiPayloads?: boolean;
supportsWrapper?: boolean; supportsWrapper?: boolean;
experimentalResponseStreaming?: boolean;
constructor(opts: LambdaOptions) { constructor(opts: LambdaOptions) {
const { const {
@@ -72,6 +74,7 @@ export class Lambda {
regions, regions,
supportsMultiPayloads, supportsMultiPayloads,
supportsWrapper, supportsWrapper,
experimentalResponseStreaming,
} = opts; } = opts;
if ('files' in opts) { if ('files' in opts) {
assert(typeof opts.files === 'object', '"files" must be an object'); assert(typeof opts.files === 'object', '"files" must be an object');
@@ -132,6 +135,7 @@ export class Lambda {
this.zipBuffer = 'zipBuffer' in opts ? opts.zipBuffer : undefined; this.zipBuffer = 'zipBuffer' in opts ? opts.zipBuffer : undefined;
this.supportsMultiPayloads = supportsMultiPayloads; this.supportsMultiPayloads = supportsMultiPayloads;
this.supportsWrapper = supportsWrapper; this.supportsWrapper = supportsWrapper;
this.experimentalResponseStreaming = experimentalResponseStreaming;
} }
async createZip(): Promise<Buffer> { async createZip(): Promise<Buffer> {

View File

@@ -8,6 +8,8 @@ interface PrerenderOptions {
group?: number; group?: number;
bypassToken?: string | null /* optional to be non-breaking change */; bypassToken?: string | null /* optional to be non-breaking change */;
allowQuery?: string[]; allowQuery?: string[];
initialHeaders?: Record<string, string>;
initialStatus?: number;
} }
export class Prerender { export class Prerender {
@@ -18,6 +20,8 @@ export class Prerender {
public group?: number; public group?: number;
public bypassToken: string | null; public bypassToken: string | null;
public allowQuery?: string[]; public allowQuery?: string[];
public initialHeaders?: Record<string, string>;
public initialStatus?: number;
constructor({ constructor({
expiration, expiration,
@@ -26,6 +30,8 @@ export class Prerender {
group, group,
bypassToken, bypassToken,
allowQuery, allowQuery,
initialHeaders,
initialStatus,
}: PrerenderOptions) { }: PrerenderOptions) {
this.type = 'Prerender'; this.type = 'Prerender';
this.expiration = expiration; this.expiration = expiration;
@@ -64,6 +70,30 @@ export class Prerender {
} }
this.fallback = fallback; this.fallback = fallback;
if (initialHeaders !== undefined) {
if (
!initialHeaders ||
typeof initialHeaders !== 'object' ||
Object.entries(initialHeaders).some(
([key, value]) => typeof key !== 'string' || typeof value !== 'string'
)
) {
throw new Error(
`The \`initialHeaders\` argument for \`Prerender\` must be an object with string key/values`
);
}
this.initialHeaders = initialHeaders;
}
if (initialStatus !== undefined) {
if (initialStatus <= 0 || !Number.isInteger(initialStatus)) {
throw new Error(
`The \`initialStatus\` argument for \`Prerender\` must be a natural number.`
);
}
this.initialStatus = initialStatus;
}
if (allowQuery !== undefined) { if (allowQuery !== undefined) {
if (!Array.isArray(allowQuery)) { if (!Array.isArray(allowQuery)) {
throw new Error( throw new Error(

View File

@@ -216,10 +216,6 @@ it('should download symlinks even with incorrect file', async () => {
}); });
it('should only match supported node versions, otherwise throw an error', async () => { it('should only match supported node versions, otherwise throw an error', async () => {
expect(await getSupportedNodeVersion('12.x', false)).toHaveProperty(
'major',
12
);
expect(await getSupportedNodeVersion('14.x', false)).toHaveProperty( expect(await getSupportedNodeVersion('14.x', false)).toHaveProperty(
'major', 'major',
14 14
@@ -240,10 +236,6 @@ it('should only match supported node versions, otherwise throw an error', async
await expectBuilderError(getSupportedNodeVersion('foo', true), autoMessage); await expectBuilderError(getSupportedNodeVersion('foo', true), autoMessage);
await expectBuilderError(getSupportedNodeVersion('=> 10', true), autoMessage); await expectBuilderError(getSupportedNodeVersion('=> 10', true), autoMessage);
expect(await getSupportedNodeVersion('12.x', true)).toHaveProperty(
'major',
12
);
expect(await getSupportedNodeVersion('14.x', true)).toHaveProperty( expect(await getSupportedNodeVersion('14.x', true)).toHaveProperty(
'major', 'major',
14 14
@@ -273,24 +265,41 @@ it('should only match supported node versions, otherwise throw an error', async
it('should match all semver ranges', async () => { it('should match all semver ranges', async () => {
// See https://docs.npmjs.com/files/package.json#engines // See https://docs.npmjs.com/files/package.json#engines
expect(await getSupportedNodeVersion('12.0.0')).toHaveProperty('major', 12); expect(await getSupportedNodeVersion('14.0.0')).toHaveProperty('major', 14);
expect(await getSupportedNodeVersion('12.x')).toHaveProperty('major', 12); expect(await getSupportedNodeVersion('14.x')).toHaveProperty('major', 14);
expect(await getSupportedNodeVersion('>=10')).toHaveProperty('major', 16); expect(await getSupportedNodeVersion('>=10')).toHaveProperty('major', 16);
expect(await getSupportedNodeVersion('>=10.3.0')).toHaveProperty('major', 16); expect(await getSupportedNodeVersion('>=10.3.0')).toHaveProperty('major', 16);
expect(await getSupportedNodeVersion('11.5.0 - 12.5.0')).toHaveProperty( expect(await getSupportedNodeVersion('16.5.0 - 16.9.0')).toHaveProperty(
'major', 'major',
12 16
); );
expect(await getSupportedNodeVersion('>=9.5.0 <=12.5.0')).toHaveProperty( expect(await getSupportedNodeVersion('>=9.5.0 <=14.5.0')).toHaveProperty(
'major',
12
);
expect(await getSupportedNodeVersion('~12.5.0')).toHaveProperty('major', 12);
expect(await getSupportedNodeVersion('^12.5.0')).toHaveProperty('major', 12);
expect(await getSupportedNodeVersion('12.5.0 - 14.5.0')).toHaveProperty(
'major', 'major',
14 14
); );
expect(await getSupportedNodeVersion('~14.5.0')).toHaveProperty('major', 14);
expect(await getSupportedNodeVersion('^14.5.0')).toHaveProperty('major', 14);
expect(await getSupportedNodeVersion('14.5.0 - 14.20.0')).toHaveProperty(
'major',
14
);
});
it('should only allow nodejs18.x when env var is set', async () => {
try {
expect(getLatestNodeVersion()).toHaveProperty('major', 16);
expect(getSupportedNodeVersion('18.x')).rejects.toThrow();
process.env.VERCEL_ALLOW_NODEJS18 = '1';
expect(getLatestNodeVersion()).toHaveProperty('major', 18);
expect(await getSupportedNodeVersion('18.x')).toHaveProperty('major', 18);
expect(await getSupportedNodeVersion('18')).toHaveProperty('major', 18);
expect(await getSupportedNodeVersion('18.1.0')).toHaveProperty('major', 18);
expect(await getSupportedNodeVersion('>=16')).toHaveProperty('major', 18);
} finally {
delete process.env.VERCEL_ALLOW_NODEJS18;
}
}); });
it('should ignore node version in vercel dev getNodeVersion()', async () => { it('should ignore node version in vercel dev getNodeVersion()', async () => {
@@ -441,6 +450,38 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
global.Date.now = realDateNow; global.Date.now = realDateNow;
}); });
it('should support initialHeaders and initialStatus correctly', async () => {
const { Prerender } = require('@vercel/build-utils/dist/prerender.js');
new Prerender({
expiration: 1,
fallback: null,
group: 1,
bypassToken: 'some-long-bypass-token-to-make-it-work',
initialHeaders: {
'content-type': 'application/json',
'x-initial': 'true',
},
initialStatus: 308,
});
new Prerender({
expiration: 1,
fallback: null,
group: 1,
bypassToken: 'some-long-bypass-token-to-make-it-work',
initialStatus: 308,
});
new Prerender({
expiration: 1,
fallback: null,
group: 1,
bypassToken: 'some-long-bypass-token-to-make-it-work',
initialHeaders: {
'content-type': 'application/json',
'x-initial': 'true',
},
});
});
it('should support require by path for legacy builders', () => { it('should support require by path for legacy builders', () => {
const index = require('@vercel/build-utils'); const index = require('@vercel/build-utils');

View File

@@ -10,11 +10,9 @@
## Usage ## Usage
Vercel is a platform for **static sites and frontend frameworks**, built to integrate with your headless content, commerce, or database. Vercel is the platform for frontend developers, providing the speed and reliability innovators need to create at the moment of inspiration.
We provide a **frictionless developer experience** to take care of the hard things: deploy instantly, scale automatically, and serve personalized content around the globe. We enable teams to iterate quickly and develop, preview, and ship delightful user experiences. Vercel has zero-configuration support for 35+ frontend frameworks and integrates with your headless content, commerce, or database of choice.
We make it easy for frontend teams to **develop, preview, and ship** delightful user experiences, where performance is the default.
To install the latest version of Vercel CLI, run this command: To install the latest version of Vercel CLI, run this command:

View File

@@ -1,6 +1,6 @@
{ {
"name": "vercel", "name": "vercel",
"version": "28.4.5", "version": "28.5.0",
"preferGlobal": true, "preferGlobal": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "The command-line interface for Vercel", "description": "The command-line interface for Vercel",
@@ -12,7 +12,7 @@
}, },
"scripts": { "scripts": {
"preinstall": "node ./scripts/preinstall.js", "preinstall": "node ./scripts/preinstall.js",
"test": "jest --env node --verbose --runInBand --bail --forceExit", "test": "jest --env node --verbose --bail --forceExit",
"test-unit": "yarn test test/unit/", "test-unit": "yarn test test/unit/",
"test-integration-cli": "rimraf test/fixtures/integration && ava test/integration.js --serial --fail-fast --verbose", "test-integration-cli": "rimraf test/fixtures/integration && ava test/integration.js --serial --fail-fast --verbose",
"test-integration-dev": "yarn test test/dev/", "test-integration-dev": "yarn test test/dev/",
@@ -41,16 +41,16 @@
"node": ">= 14" "node": ">= 14"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "5.5.3", "@vercel/build-utils": "5.5.7",
"@vercel/go": "2.2.11", "@vercel/go": "2.2.15",
"@vercel/hydrogen": "0.0.24", "@vercel/hydrogen": "0.0.29",
"@vercel/next": "3.2.1", "@vercel/next": "3.2.11",
"@vercel/node": "2.5.21", "@vercel/node": "2.6.2",
"@vercel/python": "3.1.20", "@vercel/python": "3.1.25",
"@vercel/redwood": "1.0.29", "@vercel/redwood": "1.0.35",
"@vercel/remix": "1.0.30", "@vercel/remix": "1.0.35",
"@vercel/ruby": "1.3.37", "@vercel/ruby": "1.3.41",
"@vercel/static-build": "1.0.29", "@vercel/static-build": "1.0.36",
"update-notifier": "5.1.0" "update-notifier": "5.1.0"
}, },
"devDependencies": { "devDependencies": {
@@ -81,7 +81,7 @@
"@types/minimatch": "3.0.3", "@types/minimatch": "3.0.3",
"@types/mri": "1.1.0", "@types/mri": "1.1.0",
"@types/ms": "0.7.30", "@types/ms": "0.7.30",
"@types/node": "11.11.0", "@types/node": "14.18.33",
"@types/node-fetch": "2.5.10", "@types/node-fetch": "2.5.10",
"@types/npm-package-arg": "6.1.0", "@types/npm-package-arg": "6.1.0",
"@types/pluralize": "0.0.29", "@types/pluralize": "0.0.29",
@@ -95,9 +95,10 @@
"@types/which": "1.3.2", "@types/which": "1.3.2",
"@types/write-json-file": "2.2.1", "@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0", "@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.2.10", "@vercel/client": "12.2.17",
"@vercel/frameworks": "1.1.6", "@vercel/error-utils": "1.0.3",
"@vercel/fs-detectors": "3.4.1", "@vercel/frameworks": "1.1.12",
"@vercel/fs-detectors": "3.5.0",
"@vercel/fun": "1.0.4", "@vercel/fun": "1.0.4",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@zeit/source-map-support": "0.6.2", "@zeit/source-map-support": "0.6.2",
@@ -171,7 +172,6 @@
"typescript": "4.7.4", "typescript": "4.7.4",
"universal-analytics": "0.4.20", "universal-analytics": "0.4.20",
"utility-types": "2.1.0", "utility-types": "2.1.0",
"which": "2.0.2",
"write-json-file": "2.2.0", "write-json-file": "2.2.0",
"xdg-app-paths": "5.1.0", "xdg-app-paths": "5.1.0",
"yauzl-promise": "2.1.3" "yauzl-promise": "2.1.3"

View File

@@ -3,6 +3,7 @@ import chalk from 'chalk';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import { join, normalize, relative, resolve } from 'path'; import { join, normalize, relative, resolve } from 'path';
import { import {
getDiscontinuedNodeVersions,
normalizePath, normalizePath,
Files, Files,
FileFsRef, FileFsRef,
@@ -57,6 +58,8 @@ import { sortBuilders } from '../util/build/sort-builders';
import { toEnumerableError } from '../util/error'; import { toEnumerableError } from '../util/error';
import { validateConfig } from '../util/validate-config'; import { validateConfig } from '../util/validate-config';
import { setMonorepoDefaultSettings } from '../util/build/monorepo';
type BuildResult = BuildResultV2 | BuildResultV3; type BuildResult = BuildResultV2 | BuildResultV3;
interface SerializedBuilder extends Builder { interface SerializedBuilder extends Builder {
@@ -98,7 +101,7 @@ const help = () => {
${chalk.dim('Examples:')} ${chalk.dim('Examples:')}
${chalk.gray('')} Build the project ${chalk.gray('-')} Build the project
${chalk.cyan(`$ ${cli.name} build`)} ${chalk.cyan(`$ ${cli.name} build`)}
${chalk.cyan(`$ ${cli.name} build --cwd ./path-to-project`)} ${chalk.cyan(`$ ${cli.name} build --cwd ./path-to-project`)}
@@ -270,6 +273,7 @@ async function doBuild(
outputDir: string outputDir: string
): Promise<void> { ): Promise<void> {
const { output } = client; const { output } = client;
const workPath = join(cwd, project.settings.rootDirectory || '.'); const workPath = join(cwd, project.settings.rootDirectory || '.');
const [pkg, vercelConfig, nowConfig] = await Promise.all([ const [pkg, vercelConfig, nowConfig] = await Promise.all([
@@ -300,6 +304,8 @@ async function doBuild(
...pickOverrides(localConfig), ...pickOverrides(localConfig),
}; };
await setMonorepoDefaultSettings(cwd, workPath, projectSettings, output);
// Get a list of source files // Get a list of source files
const files = (await getFiles(workPath, client)).map(f => const files = (await getFiles(workPath, client)).map(f =>
normalizePath(relative(workPath, f)) normalizePath(relative(workPath, f))
@@ -467,6 +473,25 @@ async function doBuild(
); );
const buildResult = await builder.build(buildOptions); const buildResult = await builder.build(buildOptions);
if (
buildResult &&
'output' in buildResult &&
'runtime' in buildResult.output &&
'type' in buildResult.output &&
buildResult.output.type === 'Lambda'
) {
const lambdaRuntime = buildResult.output.runtime;
if (
getDiscontinuedNodeVersions().some(o => o.runtime === lambdaRuntime)
) {
throw new NowBuildError({
code: 'NODEJS_DISCONTINUED_VERSION',
message: `The Runtime "${build.use}" is using "${lambdaRuntime}", which is discontinued. Please upgrade your Runtime to a more recent version or consult the author for more details.`,
link: 'https://github.com/vercel/vercel/blob/main/DEVELOPING_A_RUNTIME.md#lambdaruntime',
});
}
}
// Store the build result to generate the final `config.json` after // Store the build result to generate the final `config.json` after
// all builds have completed // all builds have completed
buildResults.set(build, buildResult); buildResults.set(build, buildResult);

View File

@@ -70,7 +70,7 @@ import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
import { createGitMeta } from '../../util/create-git-meta'; import { createGitMeta } from '../../util/create-git-meta';
import { isValidArchive } from '../../util/deploy/validate-archive-format'; import { isValidArchive } from '../../util/deploy/validate-archive-format';
import { parseEnv } from '../../util/parse-env'; import { parseEnv } from '../../util/parse-env';
import { errorToString, isErrnoException, isError } from '../../util/is-error'; import { errorToString, isErrnoException, isError } from '@vercel/error-utils';
import { pickOverrides } from '../../util/projects/project-settings'; import { pickOverrides } from '../../util/projects/project-settings';
export default async (client: Client): Promise<number> => { export default async (client: Client): Promise<number> => {

View File

@@ -3,16 +3,14 @@ import fs from 'fs-extra';
import DevServer from '../../util/dev/server'; import DevServer from '../../util/dev/server';
import { parseListen } from '../../util/dev/parse-listen'; import { parseListen } from '../../util/dev/parse-listen';
import { ProjectEnvVariable } from '../../types';
import Client from '../../util/client'; import Client from '../../util/client';
import { getLinkedProject } from '../../util/projects/link'; import { getLinkedProject } from '../../util/projects/link';
import { ProjectSettings } from '../../types'; import { ProjectSettings } from '../../types';
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
import setupAndLink from '../../util/link/setup-and-link'; import setupAndLink from '../../util/link/setup-and-link';
import getSystemEnvValues from '../../util/env/get-system-env-values';
import { getCommandName } from '../../util/pkg-name'; import { getCommandName } from '../../util/pkg-name';
import param from '../../util/output/param'; import param from '../../util/output/param';
import { OUTPUT_DIR } from '../../util/build/write-build-result'; import { OUTPUT_DIR } from '../../util/build/write-build-result';
import { pullEnvRecords } from '../../util/env/get-env-records';
type Options = { type Options = {
'--listen': string; '--listen': string;
@@ -57,8 +55,7 @@ export default async function dev(
} }
let projectSettings: ProjectSettings | undefined; let projectSettings: ProjectSettings | undefined;
let projectEnvs: ProjectEnvVariable[] = []; let envValues: Record<string, string> = {};
let systemEnvValues: string[] = [];
if (link.status === 'linked') { if (link.status === 'linked') {
const { project, org } = link; const { project, org } = link;
client.config.currentTeam = org.type === 'team' ? org.id : undefined; client.config.currentTeam = org.type === 'team' ? org.id : undefined;
@@ -69,19 +66,15 @@ export default async function dev(
cwd = join(cwd, project.rootDirectory); cwd = join(cwd, project.rootDirectory);
} }
[{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([ envValues = (
getDecryptedEnvRecords(output, client, project.id, 'vercel-cli:dev'), await pullEnvRecords(output, client, project.id, 'vercel-cli:dev')
project.autoExposeSystemEnvs ).env;
? getSystemEnvValues(output, client, project.id)
: { systemEnvValues: [] },
]);
} }
const devServer = new DevServer(cwd, { const devServer = new DevServer(cwd, {
output, output,
projectSettings, projectSettings,
projectEnvs, envValues,
systemEnvValues,
}); });
// If there is no Development Command, we must delete the // If there is no Development Command, we must delete the

View File

@@ -15,7 +15,7 @@ import readConfig from '../../util/config/read-config';
import readJSONFile from '../../util/read-json-file'; import readJSONFile from '../../util/read-json-file';
import { getPkgName, getCommandName } from '../../util/pkg-name'; import { getPkgName, getCommandName } from '../../util/pkg-name';
import { CantParseJSONFile } from '../../util/errors-ts'; import { CantParseJSONFile } from '../../util/errors-ts';
import { isErrnoException } from '../../util/is-error'; import { isErrnoException } from '@vercel/error-utils';
const COMMAND_CONFIG = { const COMMAND_CONFIG = {
dev: ['dev'], dev: ['dev'],

View File

@@ -11,7 +11,7 @@ import promptBool from '../../util/input/prompt-bool';
import purchaseDomain from '../../util/domains/purchase-domain'; import purchaseDomain from '../../util/domains/purchase-domain';
import stamp from '../../util/output/stamp'; import stamp from '../../util/output/stamp';
import { getCommandName } from '../../util/pkg-name'; import { getCommandName } from '../../util/pkg-name';
import { errorToString } from '../../util/is-error'; import { errorToString } from '@vercel/error-utils';
type Options = {}; type Options = {};

View File

@@ -4,21 +4,21 @@ import { closeSync, openSync, readSync } from 'fs';
import { resolve } from 'path'; import { resolve } from 'path';
import { Project, ProjectEnvTarget } from '../../types'; import { Project, ProjectEnvTarget } from '../../types';
import Client from '../../util/client'; import Client from '../../util/client';
import exposeSystemEnvs from '../../util/dev/expose-system-envs';
import { emoji, prependEmoji } from '../../util/emoji'; import { emoji, prependEmoji } from '../../util/emoji';
import getSystemEnvValues from '../../util/env/get-system-env-values';
import getDecryptedEnvRecords from '../../util/get-decrypted-env-records';
import confirm from '../../util/input/confirm'; import confirm from '../../util/input/confirm';
import { Output } from '../../util/output'; import { Output } from '../../util/output';
import param from '../../util/output/param'; import param from '../../util/output/param';
import stamp from '../../util/output/stamp'; import stamp from '../../util/output/stamp';
import { getCommandName } from '../../util/pkg-name'; import { getCommandName } from '../../util/pkg-name';
import { EnvRecordsSource } from '../../util/env/get-env-records'; import {
EnvRecordsSource,
pullEnvRecords,
} from '../../util/env/get-env-records';
import { import {
buildDeltaString, buildDeltaString,
createEnvObject, createEnvObject,
} from '../../util/env/diff-env-files'; } from '../../util/env/diff-env-files';
import { isErrnoException } from '../../util/is-error'; import { isErrnoException } from '@vercel/error-utils';
const CONTENTS_PREFIX = '# Created by Vercel CLI\n'; const CONTENTS_PREFIX = '# Created by Vercel CLI\n';
@@ -97,20 +97,11 @@ export default async function pull(
const pullStamp = stamp(); const pullStamp = stamp();
output.spinner('Downloading'); output.spinner('Downloading');
const [{ envs: projectEnvs }, { systemEnvValues }] = await Promise.all([ const records = (
getDecryptedEnvRecords(output, client, project.id, source, environment), await pullEnvRecords(output, client, project.id, source, {
project.autoExposeSystemEnvs target: environment || ProjectEnvTarget.Development,
? getSystemEnvValues(output, client, project.id) })
: { systemEnvValues: [] }, ).env;
]);
const records = exposeSystemEnvs(
projectEnvs,
systemEnvValues,
project.autoExposeSystemEnvs,
undefined,
environment
);
let deltaString = ''; let deltaString = '';
let oldEnv; let oldEnv;

View File

@@ -1,6 +1,6 @@
import chalk from 'chalk'; import chalk from 'chalk';
import Client from '../../util/client'; import Client from '../../util/client';
import { ensureLink } from '../../util/ensure-link'; import { ensureLink } from '../../util/link/ensure-link';
import getArgs from '../../util/get-args'; import getArgs from '../../util/get-args';
import getInvalidSubcommand from '../../util/get-invalid-subcommand'; import getInvalidSubcommand from '../../util/get-invalid-subcommand';
import handleError from '../../util/handle-error'; import handleError from '../../util/handle-error';
@@ -80,7 +80,7 @@ export default async function main(client: Client) {
argv._ = argv._.slice(1); argv._ = argv._.slice(1);
subcommand = argv._[0]; subcommand = argv._[0];
const args = argv._.slice(1); const args = argv._.slice(1);
const confirm = Boolean(argv['--yes']); const autoConfirm = Boolean(argv['--yes']);
const { output } = client; const { output } = client;
let paths = [process.cwd()]; let paths = [process.cwd()];
@@ -90,7 +90,7 @@ export default async function main(client: Client) {
} }
const { path } = pathValidation; const { path } = pathValidation;
const linkedProject = await ensureLink('git', client, path, confirm); const linkedProject = await ensureLink('git', client, path, { autoConfirm });
if (typeof linkedProject === 'number') { if (typeof linkedProject === 'number') {
return linkedProject; return linkedProject;
} }

View File

@@ -7,7 +7,7 @@ import handleError from '../../util/handle-error';
import logo from '../../util/output/logo'; import logo from '../../util/output/logo';
import init from './init'; import init from './init';
import { getPkgName } from '../../util/pkg-name'; import { getPkgName } from '../../util/pkg-name';
import { isError } from '../../util/is-error'; import { isError } from '@vercel/error-utils';
const COMMAND_CONFIG = { const COMMAND_CONFIG = {
init: ['init'], init: ['init'],

View File

@@ -13,7 +13,7 @@ import { getDeployment } from '../util/get-deployment';
import { Deployment } from '@vercel/client'; import { Deployment } from '@vercel/client';
import { Build } from '../types'; import { Build } from '../types';
import title from 'title'; import title from 'title';
import { isErrnoException } from '../util/is-error'; import { isErrnoException } from '@vercel/error-utils';
import { isAPIError } from '../util/errors-ts'; import { isAPIError } from '../util/errors-ts';
import { URL } from 'url'; import { URL } from 'url';

View File

@@ -3,9 +3,7 @@ import Client from '../../util/client';
import getArgs from '../../util/get-args'; import getArgs from '../../util/get-args';
import logo from '../../util/output/logo'; import logo from '../../util/output/logo';
import { getPkgName } from '../../util/pkg-name'; import { getPkgName } from '../../util/pkg-name';
import setupAndLink from '../../util/link/setup-and-link'; import { ensureLink } from '../../util/link/ensure-link';
import { getCommandName } from '../../util/pkg-name';
import param from '../../util/output/param';
const help = () => { const help = () => {
console.log(` console.log(`
@@ -70,31 +68,16 @@ export default async function main(client: Client) {
} }
const cwd = argv._[1] || process.cwd(); const cwd = argv._[1] || process.cwd();
const link = await setupAndLink(client, cwd, {
const link = await ensureLink('link', client, cwd, {
autoConfirm: !!argv['--yes'],
forceDelete: true, forceDelete: true,
autoConfirm: argv['--yes'],
projectName: argv['--project'], projectName: argv['--project'],
successEmoji: 'success', successEmoji: 'success',
setupMsg: 'Set up',
}); });
if (link.status === 'error') { if (typeof link === 'number') {
if (link.reason === 'HEADLESS') { return link;
client.output.error(
`Command ${getCommandName(
'link'
)} requires confirmation. Use option ${param('--yes')} to confirm.`
);
}
return link.exitCode;
} else if (link.status === 'not_linked') {
// User aborted project linking questions
return 0;
} else if (link.status === 'linked') {
// Successfully linked
return 0;
} else {
const err: never = link;
throw new Error('Unknown link status: ' + err);
} }
return 0;
} }

View File

@@ -17,10 +17,10 @@ import Client from '../util/client';
import { Deployment } from '@vercel/client'; import { Deployment } from '@vercel/client';
import validatePaths from '../util/validate-paths'; import validatePaths from '../util/validate-paths';
import { getLinkedProject } from '../util/projects/link'; import { getLinkedProject } from '../util/projects/link';
import { ensureLink } from '../util/ensure-link'; import { ensureLink } from '../util/link/ensure-link';
import getScope from '../util/get-scope'; import getScope from '../util/get-scope';
import { isAPIError } from '../util/errors-ts'; import { isAPIError } from '../util/errors-ts';
import { isErrnoException } from '../util/is-error'; import { isErrnoException } from '@vercel/error-utils';
const help = () => { const help = () => {
console.log(` console.log(`
@@ -56,7 +56,7 @@ const help = () => {
${chalk.gray('')} List all deployments for the project ${chalk.dim( ${chalk.gray('')} List all deployments for the project ${chalk.dim(
'`my-app`' '`my-app`'
)} in the team of the currently linked project )} in the team of the currently linked project
${chalk.cyan(`$ ${getPkgName()} ls my-app`)} ${chalk.cyan(`$ ${getPkgName()} ls my-app`)}
${chalk.gray('')} Filter deployments by metadata ${chalk.gray('')} Filter deployments by metadata
@@ -112,7 +112,7 @@ export default async function main(client: Client) {
return 2; return 2;
} }
const yes = !!argv['--yes']; const autoConfirm = !!argv['--yes'];
const prod = argv['--prod'] || false; const prod = argv['--prod'] || false;
const meta = parseMeta(argv['--meta']); const meta = parseMeta(argv['--meta']);
@@ -145,7 +145,9 @@ export default async function main(client: Client) {
// If there's no linked project and user doesn't pass `app` arg, // If there's no linked project and user doesn't pass `app` arg,
// prompt to link their current directory. // prompt to link their current directory.
if (status === 'not_linked' && !app) { if (status === 'not_linked' && !app) {
const linkedProject = await ensureLink('list', client, path, yes); const linkedProject = await ensureLink('list', client, path, {
autoConfirm,
});
if (typeof linkedProject === 'number') { if (typeof linkedProject === 'number') {
return linkedProject; return linkedProject;
} }

View File

@@ -6,7 +6,7 @@ import getArgs from '../util/get-args';
import Client from '../util/client'; import Client from '../util/client';
import { getCommandName, getPkgName } from '../util/pkg-name'; import { getCommandName, getPkgName } from '../util/pkg-name';
import { isAPIError } from '../util/errors-ts'; import { isAPIError } from '../util/errors-ts';
import { errorToString } from '../util/is-error'; import { errorToString } from '@vercel/error-utils';
const help = () => { const help = () => {
console.log(` console.log(`

View File

@@ -4,24 +4,18 @@ import Client from '../util/client';
import { ProjectEnvTarget } from '../types'; import { ProjectEnvTarget } from '../types';
import { emoji, prependEmoji } from '../util/emoji'; import { emoji, prependEmoji } from '../util/emoji';
import getArgs from '../util/get-args'; import getArgs from '../util/get-args';
import setupAndLink from '../util/link/setup-and-link';
import logo from '../util/output/logo'; import logo from '../util/output/logo';
import stamp from '../util/output/stamp'; import stamp from '../util/output/stamp';
import { getPkgName } from '../util/pkg-name'; import { getPkgName } from '../util/pkg-name';
import { import { VERCEL_DIR, VERCEL_DIR_PROJECT } from '../util/projects/link';
getLinkedProject,
VERCEL_DIR,
VERCEL_DIR_PROJECT,
} from '../util/projects/link';
import { writeProjectSettings } from '../util/projects/project-settings'; import { writeProjectSettings } from '../util/projects/project-settings';
import envPull from './env/pull'; import envPull from './env/pull';
import { getCommandName } from '../util/pkg-name'; import type { Project } from '../types';
import param from '../util/output/param';
import type { Project, Org } from '../types';
import { import {
isValidEnvTarget, isValidEnvTarget,
getEnvTargetPlaceholder, getEnvTargetPlaceholder,
} from '../util/env/env-target'; } from '../util/env/env-target';
import { ensureLink } from '../util/link/ensure-link';
const help = () => { const help = () => {
return console.log(` return console.log(`
@@ -83,43 +77,6 @@ function parseArgs(client: Client) {
return argv; return argv;
} }
type LinkResult = {
org: Org;
project: Project;
};
async function ensureLink(
client: Client,
cwd: string,
yes: boolean
): Promise<LinkResult | number> {
let link = await getLinkedProject(client, cwd);
if (link.status === 'not_linked') {
link = await setupAndLink(client, cwd, {
autoConfirm: yes,
successEmoji: 'link',
setupMsg: 'Set up',
});
if (link.status === 'not_linked') {
// User aborted project linking questions
return 0;
}
}
if (link.status === 'error') {
if (link.reason === 'HEADLESS') {
client.output.error(
`Command ${getCommandName(
'pull'
)} requires confirmation. Use option ${param('--yes')} to confirm.`
);
}
return link.exitCode;
}
return { org: link.org, project: link.project };
}
async function pullAllEnvFiles( async function pullAllEnvFiles(
environment: ProjectEnvTarget, environment: ProjectEnvTarget,
client: Client, client: Client,
@@ -140,7 +97,9 @@ async function pullAllEnvFiles(
); );
} }
function parseEnvironment(environment = 'development'): ProjectEnvTarget { export function parseEnvironment(
environment = 'development'
): ProjectEnvTarget {
if (!isValidEnvTarget(environment)) { if (!isValidEnvTarget(environment)) {
throw new Error( throw new Error(
`environment "${environment}" not supported; must be one of ${getEnvTargetPlaceholder()}` `environment "${environment}" not supported; must be one of ${getEnvTargetPlaceholder()}`
@@ -156,10 +115,10 @@ export default async function main(client: Client) {
} }
const cwd = argv._[1] || process.cwd(); const cwd = argv._[1] || process.cwd();
const yes = Boolean(argv['--yes']); const autoConfirm = Boolean(argv['--yes']);
const environment = parseEnvironment(argv['--environment'] || undefined); const environment = parseEnvironment(argv['--environment'] || undefined);
const link = await ensureLink(client, cwd, yes); const link = await ensureLink('pull', client, cwd, { autoConfirm });
if (typeof link === 'number') { if (typeof link === 'number') {
return link; return link;
} }

View File

@@ -11,7 +11,7 @@ import { getPkgName, getCommandName } from '../../util/pkg-name';
import Client from '../../util/client'; import Client from '../../util/client';
import createTeam from '../../util/teams/create-team'; import createTeam from '../../util/teams/create-team';
import patchTeam from '../../util/teams/patch-team'; import patchTeam from '../../util/teams/patch-team';
import { errorToString, isError } from '../../util/is-error'; import { errorToString, isError } from '@vercel/error-utils';
const validateSlugKeypress = (data: string, value: string) => const validateSlugKeypress = (data: string, value: string) =>
// TODO: the `value` here should contain the current value + the keypress // TODO: the `value` here should contain the current value + the keypress

View File

@@ -12,7 +12,7 @@ import { email as regexEmail } from '../../util/input/regexes';
import getTeams from '../../util/teams/get-teams'; import getTeams from '../../util/teams/get-teams';
import inviteUserToTeam from '../../util/teams/invite-user-to-team'; import inviteUserToTeam from '../../util/teams/invite-user-to-team';
import { isAPIError } from '../../util/errors-ts'; import { isAPIError } from '../../util/errors-ts';
import { errorToString, isError } from '../../util/is-error'; import { errorToString, isError } from '@vercel/error-utils';
const validateEmail = (data: string) => const validateEmail = (data: string) =>
regexEmail.test(data.trim()) || data.length === 0; regexEmail.test(data.trim()) || data.length === 0;

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
import { isErrnoException, isError, errorToString } from './util/is-error'; import { isErrnoException, isError, errorToString } from '@vercel/error-utils';
try { try {
// Test to see if cwd has been deleted before // Test to see if cwd has been deleted before
@@ -610,6 +610,21 @@ const main = async () => {
return 1; return 1;
} }
if (isErrnoException(err) && err.code === 'ECONNRESET') {
// Error message will look like the following:
// request to https://api.vercel.com/v2/user failed, reason: socket hang up
const matches = /request to https:\/\/(.*?)\//.exec(err.message || '');
const hostname = matches?.[1];
if (hostname) {
output.error(
`Connection to ${highlight(
hostname
)} interrupted. Please verify your internet connectivity and DNS configuration.`
);
}
return 1;
}
if ( if (
isErrnoException(err) && isErrnoException(err) &&
(err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED')

View File

@@ -16,7 +16,7 @@ import { VERCEL_DIR } from '../projects/link';
import { Output } from '../output'; import { Output } from '../output';
import readJSONFile from '../read-json-file'; import readJSONFile from '../read-json-file';
import { CantParseJSONFile } from '../errors-ts'; import { CantParseJSONFile } from '../errors-ts';
import { errorToString, isErrnoException, isError } from '../is-error'; import { errorToString, isErrnoException, isError } from '@vercel/error-utils';
import cmd from '../output/cmd'; import cmd from '../output/cmd';
import code from '../output/code'; import code from '../output/code';

View File

@@ -0,0 +1,139 @@
import fs from 'fs-extra';
import { join, relative, basename } from 'path';
import {
detectFramework,
monorepoManagers,
LocalFileSystemDetector,
packageManagers,
} from '@vercel/fs-detectors';
import { ProjectLinkAndSettings } from '../projects/project-settings';
import { Output } from '../output';
import title from 'title';
export async function setMonorepoDefaultSettings(
cwd: string,
workPath: string,
projectSettings: ProjectLinkAndSettings['settings'],
output: Output
) {
const localFileSystem = new LocalFileSystemDetector(cwd);
const [monorepoManager, packageManager] = await Promise.all([
detectFramework({
fs: localFileSystem,
frameworkList: monorepoManagers,
}),
detectFramework({
fs: localFileSystem,
frameworkList: packageManagers,
}),
]);
const projectName = basename(workPath);
const relativeToRoot = relative(workPath, cwd);
const setCommand = (
command: 'buildCommand' | 'installCommand',
value: string
) => {
if (projectSettings[command]) {
output.warn(
`Cannot automatically assign ${command} as it is already set via project settings or configuarion overrides.`
);
} else {
projectSettings[command] = value;
}
};
if (monorepoManager) {
output.log(
`Automatically detected ${title(
monorepoManager
)} monorepo manager. Attempting to assign default \`buildCommand\` and \`installCommand\` settings.`
);
}
if (monorepoManager === 'turbo') {
// No ENOENT handling required here since conditional wouldn't be `true` unless `turbo.json` was found.
const turboJSON = JSON.parse(
fs.readFileSync(join(cwd, 'turbo.json'), 'utf-8')
);
if (!turboJSON?.pipeline?.build) {
output.warn(
'Missing required `build` pipeline in turbo.json. Skipping automatic setting assignment.'
);
return;
}
setCommand(
'buildCommand',
`cd ${relativeToRoot} && npx turbo run build --filter=${projectName}...`
);
setCommand(
'installCommand',
`cd ${relativeToRoot} && ${packageManager} install`
);
} else if (monorepoManager === 'nx') {
// No ENOENT handling required here since conditional wouldn't be `true` unless `nx.json` was found.
const nxJSON = JSON.parse(fs.readFileSync(join(cwd, 'nx.json'), 'utf-8'));
if (!nxJSON?.targetDefaults?.build) {
output.log(
'Missing default `build` target in nx.json. Checking for project level Nx configuration...'
);
const [projectJSONBuf, packageJsonBuf] = await Promise.all([
fs.readFile(join(workPath, 'project.json')).catch(() => null),
fs.readFile(join(workPath, 'package.json')).catch(() => null),
]);
let hasBuildTarget = false;
if (projectJSONBuf) {
output.log('Found project.json Nx configuration.');
const projectJSON = JSON.parse(projectJSONBuf.toString('utf-8'));
if (projectJSON?.targets?.build) {
hasBuildTarget = true;
}
}
if (packageJsonBuf) {
const packageJSON = JSON.parse(packageJsonBuf.toString('utf-8'));
if (packageJSON?.nx) {
output.log('Found package.json Nx configuration.');
if (packageJSON.nx.targets?.build) {
hasBuildTarget = true;
}
}
}
if (!hasBuildTarget) {
output.warn(
'Missing required `build` target in either project.json or package.json Nx configuration. Skipping automatic setting assignment.'
);
return;
}
}
setCommand(
'buildCommand',
`cd ${relativeToRoot} && npx nx build ${projectName}`
);
setCommand(
'installCommand',
`cd ${relativeToRoot} && ${packageManager} install`
);
}
// TODO (@Ethan-Arrowood) - Revisit rush support when we can test it better
/* else if (monorepoManager === 'rush') {
setCommand(
'buildCommand',
`node ${relativeToRoot}/common/scripts/install-run-rush.js build --to ${projectName}`
);
setCommand(
'installCommand',
`node ${relativeToRoot}/common/scripts/install-run-rush.js install`
);
} */
}

View File

@@ -2,7 +2,7 @@ import { readFileSync } from 'fs';
import { resolve } from 'path'; import { resolve } from 'path';
import Client from '../client'; import Client from '../client';
import { Cert } from '../../types'; import { Cert } from '../../types';
import { isErrnoException } from '../is-error'; import { isErrnoException } from '@vercel/error-utils';
import { isAPIError } from '../errors-ts'; import { isAPIError } from '../errors-ts';
export default async function createCertFromFile( export default async function createCertFromFile(

View File

@@ -2,7 +2,7 @@ import retry from 'async-retry';
import { Cert } from '../../types'; import { Cert } from '../../types';
import Client from '../client'; import Client from '../client';
import { isAPIError } from '../errors-ts'; import { isAPIError } from '../errors-ts';
import { isError } from '../is-error'; import { isError } from '@vercel/error-utils';
// When it's a configuration error we should retry because of the DNS propagation // When it's a configuration error we should retry because of the DNS propagation
// otherwise we bail to handle the error in the upper level // otherwise we bail to handle the error in the upper level

View File

@@ -1,8 +1,7 @@
import { bold } from 'chalk'; import { bold } from 'chalk';
import inquirer from 'inquirer'; import inquirer from 'inquirer';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { URLSearchParams } from 'url'; import { URL } from 'url';
import { parse as parseUrl } from 'url';
import { VercelConfig } from '@vercel/client'; import { VercelConfig } from '@vercel/client';
import retry, { RetryFunction, Options as RetryOptions } from 'async-retry'; import retry, { RetryFunction, Options as RetryOptions } from 'async-retry';
import fetch, { BodyInit, Headers, RequestInit, Response } from 'node-fetch'; import fetch, { BodyInit, Headers, RequestInit, Response } from 'node-fetch';
@@ -23,7 +22,7 @@ import type {
} from '../types'; } from '../types';
import { sharedPromise } from './promise'; import { sharedPromise } from './promise';
import { APIError } from './errors-ts'; import { APIError } from './errors-ts';
import { normalizeError } from './is-error'; import { normalizeError } from '@vercel/error-utils';
const isSAMLError = (v: any): v is SAMLError => { const isSAMLError = (v: any): v is SAMLError => {
return v && v.saml; return v && v.saml;
@@ -87,25 +86,18 @@ export default class Client extends EventEmitter implements Stdio {
} }
private _fetch(_url: string, opts: FetchOptions = {}) { private _fetch(_url: string, opts: FetchOptions = {}) {
const parsedUrl = parseUrl(_url, true); const url = new URL(_url, this.apiUrl);
const apiUrl = parsedUrl.host
? `${parsedUrl.protocol}//${parsedUrl.host}`
: '';
if (opts.accountId || opts.useCurrentTeam !== false) { if (opts.accountId || opts.useCurrentTeam !== false) {
const query = new URLSearchParams(parsedUrl.query);
if (opts.accountId) { if (opts.accountId) {
if (opts.accountId.startsWith('team_')) { if (opts.accountId.startsWith('team_')) {
query.set('teamId', opts.accountId); url.searchParams.set('teamId', opts.accountId);
} else { } else {
query.delete('teamId'); url.searchParams.delete('teamId');
} }
} else if (opts.useCurrentTeam !== false && this.config.currentTeam) { } else if (opts.useCurrentTeam !== false && this.config.currentTeam) {
query.set('teamId', this.config.currentTeam); url.searchParams.set('teamId', this.config.currentTeam);
} }
_url = `${apiUrl}${parsedUrl.pathname}?${query}`;
} }
const headers = new Headers(opts.headers); const headers = new Headers(opts.headers);
@@ -122,7 +114,6 @@ export default class Client extends EventEmitter implements Stdio {
body = opts.body; body = opts.body;
} }
const url = `${apiUrl ? '' : this.apiUrl}${_url}`;
const requestId = this.requestIdCounter++; const requestId = this.requestIdCounter++;
return this.output.time(res => { return this.output.time(res => {
if (res) { if (res) {
@@ -130,7 +121,7 @@ export default class Client extends EventEmitter implements Stdio {
res.statusText res.statusText
}: ${res.headers.get('x-vercel-id')}`; }: ${res.headers.get('x-vercel-id')}`;
} else { } else {
return `#${requestId}${opts.method || 'GET'} ${url}`; return `#${requestId}${opts.method || 'GET'} ${url.href}`;
} }
}, fetch(url, { ...opts, headers, body })); }, fetch(url, { ...opts, headers, body }));
} }

View File

@@ -10,7 +10,7 @@ import error from '../output/error';
import highlight from '../output/highlight'; import highlight from '../output/highlight';
import { VercelConfig } from '../dev/types'; import { VercelConfig } from '../dev/types';
import { AuthConfig, GlobalConfig } from '../../types'; import { AuthConfig, GlobalConfig } from '../../types';
import { isErrnoException, isError } from '../is-error'; import { isErrnoException, isError } from '@vercel/error-utils';
const VERCEL_DIR = getGlobalPathConfig(); const VERCEL_DIR = getGlobalPathConfig();
const CONFIG_FILE_PATH = join(VERCEL_DIR, 'config.json'); const CONFIG_FILE_PATH = join(VERCEL_DIR, 'config.json');

View File

@@ -5,7 +5,7 @@ import git from 'git-last-commit';
import { exec } from 'child_process'; import { exec } from 'child_process';
import { GitMetadata, Project } from '../types'; import { GitMetadata, Project } from '../types';
import { Output } from './output'; import { Output } from './output';
import { errorToString } from './is-error'; import { errorToString } from '@vercel/error-utils';
export async function createGitMeta( export async function createGitMeta(
directory: string, directory: string,

View File

@@ -115,29 +115,39 @@ export default async function processDeployment({
.reduce((a: number, b: number) => a + b, 0); .reduce((a: number, b: number) => a + b, 0);
const totalSizeHuman = bytes.format(missingSize, { decimalPlaces: 1 }); const totalSizeHuman = bytes.format(missingSize, { decimalPlaces: 1 });
uploads.forEach((e: any) => // When stderr is not a TTY then we only want to
e.on('progress', () => { // print upload progress in 25% increments
const uploadedBytes = uploads.reduce((acc: number, e: any) => { let nextStep = 0;
return acc + e.bytesUploaded; const stepSize = now._client.stderr.isTTY ? 0 : 0.25;
}, 0);
const bar = progress(uploadedBytes, missingSize); const updateProgress = () => {
if (!bar || uploadedBytes === missingSize) { const uploadedBytes = uploads.reduce((acc: number, e: any) => {
output.spinner(deployingSpinnerVal, 0); return acc + e.bytesUploaded;
} else { }, 0);
const uploadedHuman = bytes.format(uploadedBytes, {
decimalPlaces: 1, const bar = progress(uploadedBytes, missingSize);
fixedDecimals: true, if (!bar) {
}); output.spinner(deployingSpinnerVal, 0);
} else {
const uploadedHuman = bytes.format(uploadedBytes, {
decimalPlaces: 1,
fixedDecimals: true,
});
const percent = uploadedBytes / missingSize;
if (percent >= nextStep) {
output.spinner( output.spinner(
`Uploading ${chalk.reset( `Uploading ${chalk.reset(
`[${bar}] (${uploadedHuman}/${totalSizeHuman})` `[${bar}] (${uploadedHuman}/${totalSizeHuman})`
)}`, )}`,
0 0
); );
nextStep += stepSize;
} }
}) }
); };
uploads.forEach((e: any) => e.on('progress', updateProgress));
updateProgress();
} }
if (event.type === 'file-uploaded') { if (event.type === 'file-uploaded') {

View File

@@ -87,8 +87,12 @@ async function createBuildProcess(
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// The first message that the builder process sends is the `ready` event // The first message that the builder process sends is the `ready` event
buildProcess.once('message', ({ type }) => { buildProcess.once('message', data => {
if (type !== 'ready') { if (
data !== null &&
typeof data === 'object' &&
(data as { type: string }).type !== 'ready'
) {
reject(new Error('Did not get "ready" event from builder')); reject(new Error('Did not get "ready" event from builder'));
} else { } else {
resolve(buildProcess); resolve(buildProcess);

View File

@@ -1,46 +0,0 @@
import {
ProjectEnvType,
ProjectEnvVariable,
ProjectEnvTarget,
} from '../../types';
import { Env } from '@vercel/build-utils';
function getSystemEnvValue(
systemEnvRef: string,
{ vercelUrl }: { vercelUrl?: string }
) {
if (systemEnvRef === 'VERCEL_URL') {
return vercelUrl || '';
}
return '';
}
export default function exposeSystemEnvs(
projectEnvs: ProjectEnvVariable[],
systemEnvValues: string[],
autoExposeSystemEnvs: boolean | undefined,
vercelUrl?: string,
target?: ProjectEnvTarget
) {
const envs: Env = {};
if (autoExposeSystemEnvs) {
envs['VERCEL'] = '1';
envs['VERCEL_ENV'] = target || 'development';
for (const key of systemEnvValues) {
envs[key] = getSystemEnvValue(key, { vercelUrl });
}
}
for (let env of projectEnvs) {
if (env.type === ProjectEnvType.System) {
envs[env.key] = getSystemEnvValue(env.value, { vercelUrl });
} else {
envs[env.key] = env.value;
}
}
return envs;
}

View File

@@ -16,3 +16,75 @@ export function nodeHeadersToFetchHeaders(
} }
return headers; return headers;
} }
/**
* Request headers that are not allowed to be overridden by a middleware.
*/
const NONOVERRIDABLE_HEADERS: Set<string> = new Set([
'host',
'connection',
'content-length',
'transfer-encoding',
'keep-alive',
'transfer-encoding',
'te',
'upgrade',
'trailer',
]);
/**
* Adds/Updates/Deletes headers in `reqHeaders` based on the response headers
* from a middleware (`respHeaders`).
*
* `x-middleware-override-headers` is a comma-separated list of *all* header
* names that should appear in new request headers. Names not in this list
* will be deleted.
*
* `x-middleware-request-*` is the new value for each header. This can't be
* omitted, even if the header is not being modified.
*
*/
export function applyOverriddenHeaders(
reqHeaders: { [k: string]: string | string[] | undefined },
respHeaders: Headers
) {
const overriddenHeaders = respHeaders.get('x-middleware-override-headers');
if (!overriddenHeaders) {
return;
}
const overriddenKeys: Set<string> = new Set();
for (const key of overriddenHeaders.split(',')) {
overriddenKeys.add(key.trim());
}
respHeaders.delete('x-middleware-override-headers');
// Delete headers.
for (const key of Object.keys(reqHeaders)) {
if (!NONOVERRIDABLE_HEADERS.has(key) && !overriddenKeys.has(key)) {
delete reqHeaders[key];
}
}
// Update or add headers.
for (const key of overriddenKeys.keys()) {
if (NONOVERRIDABLE_HEADERS.has(key)) {
continue;
}
const valueKey = 'x-middleware-request-' + key;
const newValue = respHeaders.get(valueKey);
const oldValue = reqHeaders[key];
if (oldValue !== newValue) {
if (newValue) {
reqHeaders[key] = newValue;
} else {
delete reqHeaders[key];
}
}
respHeaders.delete(valueKey);
}
}

View File

@@ -31,11 +31,11 @@ export function parseListen(str: string, defaultPort = 3000): ListenSpec {
return [url.pathname]; return [url.pathname];
case 'tcp:': case 'tcp:':
url.port = url.port || String(defaultPort); url.port = url.port || String(defaultPort);
return [parseInt(url.port, 10), url.hostname]; return [parseInt(url.port, 10), url.hostname ?? undefined];
default: default:
if (!url.slashes) { if (!url.slashes) {
if (url.protocol === null) { if (url.protocol === null) {
return [defaultPort, url.pathname]; return [defaultPort, url.pathname ?? undefined];
} }
port = Number(url.hostname); port = Number(url.hostname);
if (url.protocol && !isNaN(port)) { if (url.protocol && !isNaN(port)) {

View File

@@ -4,7 +4,7 @@
* @param querystring - The querystring to parse, also known as the "search" string. * @param querystring - The querystring to parse, also known as the "search" string.
*/ */
export function parseQueryString( export function parseQueryString(
querystring?: string querystring?: string | null
): Record<string, string[]> { ): Record<string, string[]> {
const query: Record<string, string[]> = Object.create(null); const query: Record<string, string[]> = Object.create(null);
if (!querystring || !querystring.startsWith('?') || querystring === '?') { if (!querystring || !querystring.startsWith('?') || querystring === '?') {
@@ -38,9 +38,9 @@ export function parseQueryString(
*/ */
export function formatQueryString( export function formatQueryString(
query: Record<string, string[]> | undefined query: Record<string, string[]> | undefined
): string | undefined { ): string | null {
if (!query) { if (!query) {
return undefined; return null;
} }
let s = ''; let s = '';
let prefix = '?'; let prefix = '?';
@@ -55,5 +55,5 @@ export function formatQueryString(
prefix = '&'; prefix = '&';
} }
} }
return s || undefined; return s || null;
} }

View File

@@ -57,7 +57,8 @@ export async function devRouter(
phase?: HandleValue | null phase?: HandleValue | null
): Promise<RouteResult> { ): Promise<RouteResult> {
let result: RouteResult | undefined; let result: RouteResult | undefined;
let { pathname: reqPathname = '/', search: reqSearch } = url.parse(reqUrl); let { pathname: reqPathname, search: reqSearch } = url.parse(reqUrl);
reqPathname ??= '/';
const reqQuery = parseQueryString(reqSearch); const reqQuery = parseQueryString(reqSearch);
const combinedHeaders: HttpHeadersConfig = { ...previousHeaders }; const combinedHeaders: HttpHeadersConfig = { ...previousHeaders };
let status: number | undefined; let status: number | undefined;
@@ -130,7 +131,8 @@ export async function devRouter(
phase !== 'hit' && phase !== 'hit' &&
!isDestUrl !isDestUrl
) { ) {
const { pathname = '/' } = url.parse(destPath); let { pathname } = url.parse(destPath);
pathname ??= '/';
const hasDestFile = await devServer.hasFilesystem( const hasDestFile = await devServer.hasFilesystem(
pathname, pathname,
vercelConfig vercelConfig
@@ -186,8 +188,9 @@ export async function devRouter(
if (!destPath.startsWith('/')) { if (!destPath.startsWith('/')) {
destPath = `/${destPath}`; destPath = `/${destPath}`;
} }
const { pathname: destPathname = '/', search: destSearch } = let { pathname: destPathname, search: destSearch } =
url.parse(destPath); url.parse(destPath);
destPathname ??= '/';
const destQuery = parseQueryString(destSearch); const destQuery = parseQueryString(destSearch);
Object.assign(destQuery, reqQuery); Object.assign(destQuery, reqQuery);
result = { result = {

View File

@@ -18,7 +18,6 @@ import directoryTemplate from 'serve-handler/src/directory';
import getPort from 'get-port'; import getPort from 'get-port';
import isPortReachable from 'is-port-reachable'; import isPortReachable from 'is-port-reachable';
import deepEqual from 'fast-deep-equal'; import deepEqual from 'fast-deep-equal';
import which from 'which';
import npa from 'npm-package-arg'; import npa from 'npm-package-arg';
import type { ChildProcess } from 'child_process'; import type { ChildProcess } from 'child_process';
@@ -33,6 +32,7 @@ import {
Builder, Builder,
cloneEnv, cloneEnv,
Env, Env,
getNodeBinPath,
StartDevServerResult, StartDevServerResult,
FileFsRef, FileFsRef,
PackageJson, PackageJson,
@@ -85,17 +85,16 @@ import {
HttpHeadersConfig, HttpHeadersConfig,
EnvConfigs, EnvConfigs,
} from './types'; } from './types';
import { ProjectEnvVariable, ProjectSettings } from '../../types'; import { ProjectSettings } from '../../types';
import exposeSystemEnvs from './expose-system-envs';
import { treeKill } from '../tree-kill'; import { treeKill } from '../tree-kill';
import { nodeHeadersToFetchHeaders } from './headers'; import { applyOverriddenHeaders, nodeHeadersToFetchHeaders } from './headers';
import { formatQueryString, parseQueryString } from './parse-query-string'; import { formatQueryString, parseQueryString } from './parse-query-string';
import { import {
errorToString, errorToString,
isErrnoException, isErrnoException,
isError, isError,
isSpawnError, isSpawnError,
} from '../is-error'; } from '@vercel/error-utils';
import isURL from './is-url'; import isURL from './is-url';
import { pickOverrides } from '../projects/project-settings'; import { pickOverrides } from '../projects/project-settings';
import { replaceLocalhost } from './parse-listen'; import { replaceLocalhost } from './parse-listen';
@@ -168,15 +167,13 @@ export default class DevServer {
private blockingBuildsPromise: Promise<void> | null; private blockingBuildsPromise: Promise<void> | null;
private startPromise: Promise<void> | null; private startPromise: Promise<void> | null;
private systemEnvValues: string[]; private envValues: Record<string, string>;
private projectEnvs: ProjectEnvVariable[];
constructor(cwd: string, options: DevServerOptions) { constructor(cwd: string, options: DevServerOptions) {
this.cwd = cwd; this.cwd = cwd;
this.output = options.output; this.output = options.output;
this.envConfigs = { buildEnv: {}, runEnv: {}, allEnv: {} }; this.envConfigs = { buildEnv: {}, runEnv: {}, allEnv: {} };
this.systemEnvValues = options.systemEnvValues || []; this.envValues = options.envValues || {};
this.projectEnvs = options.projectEnvs || [];
this.files = {}; this.files = {};
this.originalProjectSettings = options.projectSettings; this.originalProjectSettings = options.projectSettings;
this.projectSettings = options.projectSettings; this.projectSettings = options.projectSettings;
@@ -684,16 +681,13 @@ export default class DevServer {
// If no .env/.build.env is present, use cloud environment variables // If no .env/.build.env is present, use cloud environment variables
if (Object.keys(allEnv).length === 0) { if (Object.keys(allEnv).length === 0) {
const cloudEnv = exposeSystemEnvs( const envValues = { ...this.envValues };
this.projectEnvs || [], if (this.address.host) {
this.systemEnvValues || [], envValues['VERCEL_URL'] = this.address.host;
this.projectSettings?.autoExposeSystemEnvs, }
this.address.host allEnv = { ...envValues };
); runEnv = { ...envValues };
buildEnv = { ...envValues };
allEnv = { ...cloudEnv };
runEnv = { ...cloudEnv };
buildEnv = { ...cloudEnv };
} }
// legacy NOW_REGION env variable // legacy NOW_REGION env variable
@@ -1454,7 +1448,9 @@ export default class DevServer {
} }
); );
if (middlewareRes.status === 500) { const middlewareBody = await middlewareRes.buffer();
if (middlewareRes.status === 500 && middlewareBody.byteLength === 0) {
await this.sendError( await this.sendError(
req, req,
res, res,
@@ -1478,6 +1474,9 @@ export default class DevServer {
'content-length', 'content-length',
'transfer-encoding', 'transfer-encoding',
]); ]);
applyOverriddenHeaders(req.headers, middlewareRes.headers);
for (const [name, value] of middlewareRes.headers) { for (const [name, value] of middlewareRes.headers) {
if (name === 'x-middleware-next') { if (name === 'x-middleware-next') {
shouldContinue = value === '1'; shouldContinue = value === '1';
@@ -1496,7 +1495,6 @@ export default class DevServer {
} }
if (!shouldContinue) { if (!shouldContinue) {
const middlewareBody = await middlewareRes.buffer();
this.setResponseHeaders(res, requestId); this.setResponseHeaders(res, requestId);
if (middlewareBody.length > 0) { if (middlewareBody.length > 0) {
res.setHeader('content-length', middlewareBody.length); res.setHeader('content-length', middlewareBody.length);
@@ -2240,6 +2238,10 @@ export default class DevServer {
} }
); );
// add the node_modules/.bin directory to the PATH
const nodeBinPath = await getNodeBinPath({ cwd });
env.PATH = `${nodeBinPath}${path.delimiter}${env.PATH}`;
// This is necesary so that the dev command in the Project // This is necesary so that the dev command in the Project
// will work cross-platform (especially Windows). // will work cross-platform (especially Windows).
let command = devCommand let command = devCommand
@@ -2254,22 +2256,6 @@ export default class DevServer {
})}` })}`
); );
const isNpxAvailable = await which('npx')
.then(() => true)
.catch(() => false);
if (isNpxAvailable) {
command = `npx --no-install ${command}`;
} else {
const isYarnAvailable = await which('yarn')
.then(() => true)
.catch(() => false);
if (isYarnAvailable) {
command = `yarn run --silent ${command}`;
}
}
this.output.debug(`Spawning dev command: ${command}`); this.output.debug(`Spawning dev command: ${command}`);
const proxyPort = new RegExp(port.toString(), 'g'); const proxyPort = new RegExp(port.toString(), 'g');

View File

@@ -16,7 +16,7 @@ import {
import { VercelConfig } from '@vercel/client'; import { VercelConfig } from '@vercel/client';
import { HandleValue, Route } from '@vercel/routing-utils'; import { HandleValue, Route } from '@vercel/routing-utils';
import { Output } from '../output'; import { Output } from '../output';
import { ProjectEnvVariable, ProjectSettings } from '../../types'; import { ProjectSettings } from '../../types';
import { BuilderWithPkg } from '../build/import-builders'; import { BuilderWithPkg } from '../build/import-builders';
export { VercelConfig }; export { VercelConfig };
@@ -24,8 +24,7 @@ export { VercelConfig };
export interface DevServerOptions { export interface DevServerOptions {
output: Output; output: Output;
projectSettings?: ProjectSettings; projectSettings?: ProjectSettings;
systemEnvValues?: string[]; envValues?: Record<string, string>;
projectEnvs?: ProjectEnvVariable[];
} }
export interface EnvConfigs { export interface EnvConfigs {

View File

@@ -1,45 +0,0 @@
import { Org, Project } from '../types';
import Client from './client';
import setupAndLink from './link/setup-and-link';
import param from './output/param';
import { getCommandName } from './pkg-name';
import { getLinkedProject } from './projects/link';
type LinkResult = {
org: Org;
project: Project;
};
export async function ensureLink(
commandName: string,
client: Client,
cwd: string,
yes: boolean
): Promise<LinkResult | number> {
let link = await getLinkedProject(client, cwd);
if (link.status === 'not_linked') {
link = await setupAndLink(client, cwd, {
autoConfirm: yes,
successEmoji: 'link',
setupMsg: 'Set up',
});
if (link.status === 'not_linked') {
// User aborted project linking questions
return 0;
}
}
if (link.status === 'error') {
if (link.reason === 'HEADLESS') {
client.output.error(
`Command ${getCommandName(
commandName
)} requires confirmation. Use option ${param('--yes')} to confirm.`
);
}
return link.exitCode;
}
return { org: link.org, project: link.project };
}

View File

@@ -2,6 +2,7 @@ import { Output } from '../output';
import Client from '../client'; import Client from '../client';
import { ProjectEnvVariable, ProjectEnvTarget } from '../../types'; import { ProjectEnvVariable, ProjectEnvTarget } from '../../types';
import { URLSearchParams } from 'url'; import { URLSearchParams } from 'url';
import * as path from 'path';
/** The CLI command that was used that needs the environment variables. */ /** The CLI command that was used that needs the environment variables. */
export type EnvRecordsSource = export type EnvRecordsSource =
@@ -49,3 +50,40 @@ export default async function getEnvRecords(
return client.fetch<{ envs: ProjectEnvVariable[] }>(url); return client.fetch<{ envs: ProjectEnvVariable[] }>(url);
} }
interface PullEnvOptions {
target?: ProjectEnvTarget | string;
gitBranch?: string;
}
export async function pullEnvRecords(
output: Output,
client: Client,
projectId: string,
source: EnvRecordsSource,
{ target, gitBranch }: PullEnvOptions = {}
) {
output.debug(
`Fetching Environment Variables of project ${projectId} and target ${target}`
);
const query = new URLSearchParams();
let url = `/v1/env/pull/${projectId}`;
if (target) {
url = path.join(url, target, gitBranch ?? '');
}
if (source) {
query.set('source', source);
}
if (Array.from(query).length > 0) {
url += `?${query}`;
}
return client.fetch<{
env: Record<string, string>;
buildEnv: Record<string, string>;
}>(url);
}

View File

@@ -1,12 +0,0 @@
import { Output } from '../output';
import Client from '../client';
export default async function getSystemEnvValues(
output: Output,
client: Client,
projectId: string
) {
output.debug(`Fetching System Environment Values of project ${projectId}`);
const url = `/v6/projects/${projectId}/system-env-values`;
return client.fetch<{ systemEnvValues: string[] }>(url);
}

View File

@@ -1,4 +1,4 @@
import { isErrnoException } from '../is-error'; import { isErrnoException } from '@vercel/error-utils';
const knownErrorsCodes = new Set([ const knownErrorsCodes = new Set([
'PAYMENT_REQUIRED', 'PAYMENT_REQUIRED',

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