Compare commits

..

38 Commits

Author SHA1 Message Date
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
chloetedder
8814fc1515 Publish Stable
- @vercel/build-utils@5.5.3
 - vercel@28.4.5
 - @vercel/client@12.2.10
 - @vercel/fs-detectors@3.4.1
 - @vercel/go@2.2.11
 - @vercel/hydrogen@0.0.24
 - @vercel/next@3.2.1
 - @vercel/node@2.5.21
 - @vercel/python@3.1.20
 - @vercel/redwood@1.0.29
 - @vercel/remix@1.0.30
 - @vercel/ruby@1.3.37
 - @vercel/static-build@1.0.29
2022-09-28 10:14:27 -05:00
chloetedder
0d044b4eac [fs-detectors] Use json5 parser for Rush to have valid json parsing (#8645)
### Related Issues

Parse `rush.json` files with `json5` because it is very common for these
to have comments in them

[Template for people to clone for
Rush](https://rushjs.io/pages/configs/rush_json/) which has comments in
it as a default which most people will clone
 
Docs of Rush showing to not use `JSON.parse`

https://rushjs.io/pages/help/faq/#why-do-rushs-json-config-files-contain--comments-that-github-shows-in-red

Added in tests with block comments and single line comments

### 📋 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: Sean Massa <EndangeredMassa@gmail.com>
2022-09-28 06:38:25 -05:00
Steven
f6bd1aa8c0 [tests] Remove console.log() from test (#8647)
Remove `console.log()` from test
2022-09-27 23:38:49 +00:00
Steven
8cd84ec066 Publish Stable
- @vercel/build-utils@5.5.2
 - vercel@28.4.4
 - @vercel/client@12.2.9
 - @vercel/go@2.2.10
 - @vercel/hydrogen@0.0.23
 - @vercel/next@3.2.0
 - @vercel/node@2.5.20
 - @vercel/python@3.1.19
 - @vercel/redwood@1.0.28
 - @vercel/remix@1.0.29
 - @vercel/ruby@1.3.36
 - @vercel/static-build@1.0.28
2022-09-27 16:35:00 -04:00
Steven
a8df231e4c [build-utils] Fix npm version detection for --legacy-peer-deps (#8646)
There was a case where the npm version wasn't decided base on Node.js version but instead based on the lockfile.
This PR fixes the case when a newer npm version is detected base on the lockfile.

- Follow up to #8598
- Follow up to #8550
2022-09-27 16:33:24 -04:00
Gal Schlezinger
f674842bed [next] read regions from middleware-manifest output (#8629)
### Related Issues

- Needs https://github.com/vercel/next.js/pull/40881 for this to be
useful
- Resolves EC-238

### 📋 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: Steven <steven@ceriously.com>
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-27 10:46:02 -07:00
Steven
bf5cfa9a41 Publish Stable
- @vercel/build-utils@5.5.1
 - vercel@28.4.3
 - @vercel/client@12.2.8
 - @vercel/go@2.2.9
 - @vercel/hydrogen@0.0.22
 - @vercel/next@3.1.30
 - @vercel/node@2.5.19
 - @vercel/python@3.1.18
 - @vercel/redwood@1.0.27
 - @vercel/remix@1.0.28
 - @vercel/ruby@1.3.35
 - @vercel/static-build@1.0.27
2022-09-27 09:39:04 -04:00
JJ Kasper
12121b7a71 [next] Fix build time 404 route with trailingSlash: true (#8627)
This ensures we properly handle trailing slashes in the `notFound: true` build time routes. 

Fixes: [slack thread](https://vercel.slack.com/archives/C03S8ED1DKM/p1663691719703509)
2022-09-27 09:21:49 -04:00
Steven
baa56aed2c [tests] Fix timeout for actions/setup-node (#8639)
Try fixing the timeout again. For example: https://github.com/vercel/vercel/actions/runs/3130757219/jobs/5081381465

- Follow up to #8613 
- Related to https://github.com/actions/cache/issues/810 
- Related to https://github.com/Azure/azure-sdk-for-js/issues/22321
2022-09-26 18:33:43 -04:00
Steven
6f767367e4 [build-utils] Adjust nodejs12.x discontinueDate to Monday (#8638)
The previous date was on a Saturday so lets move it to the following Monday to ensure support tickets are quickly answered.
2022-09-26 22:22:32 +00:00
Sean Massa
0e4124f94c [cli] fix vc bisect off by one (#8399)
Co-authored-by: Nathan Rajlich <n@n8.io>
2022-09-26 14:47:15 -05:00
Sean Massa
30503d0a3f [go] remove unused, breaking watchlist from Go builder (#8633) 2022-09-26 14:31:36 -05:00
Steven
6c9164f67d [cli] Refactor doBuild to return void (#8626)
This PR refactor `doBuild()` to return `void`.

This will prevent accidental bugs like #8623 where an exit code number was returned instead of throwing on error.
2022-09-24 01:25:59 +00:00
Steven
906b7a8f2c [cli] Fix invalid vercel.json config error serialization (#8623)
Follow up to #8622 since we should be throwing errors so the error is correctly serialized to `builds.json`.
2022-09-23 22:08:53 +00:00
Steven
43499b13d8 [cli] Add vercel.json validation to vercel build (#8622)
We were doing this validation in `vercel dev` but not `vercel build`.

This PR adds `vercel.json` validation to `vercel build` too.

Note I am calling this a patch because invalid `vercel.json` was already failing when passed to the API so this allows a nice error message earlier in the process.
2022-09-23 20:33:28 +00:00
Sean Massa
7d6e56670f [cli][dev] Add strict mode to vc dev edge function handlers (#8616)
Add strict mode to `vc dev` edge function handlers. This is behind a flag in production, but that flag has been at 100% for a while. So, it seems safe to include it here unconditionally.

Also remove legal comments.

These changes bring `vc dev` edge function support closer to production.
2022-09-23 14:23:03 +00:00
Sean Massa
dba337f148 [cli][dev] extract edge/serverless handler logic into separate files (#8615) 2022-09-22 16:34:32 -05:00
Steven
a825bc9540 Publish Stable
- @vercel/build-utils@5.5.0
 - vercel@28.4.2
 - @vercel/client@12.2.7
 - @vercel/go@2.2.8
 - @vercel/hydrogen@0.0.21
 - @vercel/next@3.1.29
 - @vercel/node@2.5.18
 - @vercel/python@3.1.17
 - @vercel/redwood@1.0.26
 - @vercel/remix@1.0.27
 - @vercel/ruby@1.3.34
 - @vercel/static-build@1.0.26
2022-09-22 16:50:27 -04:00
JJ Kasper
f5486a8297 [next] Update to route app rsc paths correctly (#8611)
### Related Issues

x-ref: [slack thread](https://vercel.slack.com/archives/C035J346QQL/p1663799417151099)

Failing probes are unrelated, more info can be seen in [this thread](https://vercel.slack.com/archives/C035J346QQL/p1663820032810519?thread_ts=1663775935.504379&cid=C035J346QQL)
2022-09-22 20:44:40 +00:00
Mark Glagola
225e0a4de3 [tests] Use new VERCEL_TEST_TOKEN for tests (#8614)
Use `VERCEL_TEST_TOKEN` for tests
2022-09-22 16:07:43 -04:00
Steven
0ecfdc1325 [tests] Add timeout for broken actions/setup-node@v3 (#8613)
This PR will make sure that CI fails fast if there is a network issue when restoring the cache.

This has been a known issue for 3 months and no resolution:

- https://github.com/actions/cache/issues/810
2022-09-22 14:42:48 +00:00
Gal Schlezinger
628409d233 [build-utils] support providing execution regions in Edge Functions (#8612) 2022-09-22 14:38:33 +03:00
Steven
51d968314f Publish Stable
- vercel@28.4.1
 - @vercel/fs-detectors@3.4.0
 - @vercel/next@3.1.28
 - @vercel/node@2.5.17
2022-09-21 18:47:45 -04:00
Chris Barber
b4e2cbc6e5 [tests] Test route src and dest for normalized path (#8609)
Adding assertions for normalized routes.
2022-09-21 18:44:52 -04:00
Jiachi Liu
7323beea8e [next] fix app dir SSG deployment paths (#8608)
### Related Issues

Seeing this while deploying appDir to production. The collecting path is
not correct for generated dist from appDir
```
Collected static files (public/, static/, .next/static): 18.834ms
Error: ENOENT: no such file or directory, open '/vercel/path0/.next/server/pages/route-groups/checkout.html'
Error: An unexpected error occurred!
Error: ENOENT: no such file or directory, open '/vercel/path0/.next/server/pages/route-groups/checkout.html'
Error: Command "vercel build" exited with 1
```

Fix the deployment of SSG, the output html/json paths should be prefixed
with `/app`

Adding a TODO now for how to determine the app path route, leveraging
`.rsc` suffix in data route. Better to use app paths manifest to
refactor it later.

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

Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-21 15:40:38 -07:00
Chris Barber
e4bb311144 [cli] Disable git status background index refresh/optimization (#8569)
The `create-git-meta` file contains a `isDirty()` function which calls `git status` and subsequently causes one of the test fixture's git index to be updated. Turns out that `git status` will update/optimize the index in the background when invoked: https://git-scm.com/docs/git-status#_background_refresh.

By disabling this using the `--no-optional-locks` flag, `isDirty()` will no longer update the index when checking the status.

From my testing, the `--no-optional-locks` flag must be set after `git` and before `status`.

This PR is likely a better alternative to https://github.com/vercel/vercel/pull/8568 as this PR eliminates the problem all together.

#### 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-21 21:47:10 +00:00
Andrew Gadzik
288546659d [fs-detectors] ensure processing of potentialFiles for readdir cache hits (#8604)
This is to fix the issue with processing  new entries of `potentialFiles` when there was a `readdir` cache hit 

### Related Issues

Related to https://linear.app/vercel/issue/HIT-57/monorepo-detection-api-prevent-rate-limits

### 📋 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-21 20:59:21 +00:00
Sean Massa
822224e212 [cli][dev] Use browser platform when bundling edge functions for vc dev (#8605) 2022-09-21 15:50:22 -05:00
Nathan Rajlich
6b23950b65 [cli] Don't set analyticsId when Vercel Analytics are disabled in "vc pull" (#8607)
The `disabledAt` and `enabledAt` properties need to be consulted in order to determine whether or not the Vercel Analytics feature has been enabled.

Related to https://github.com/vercel/vercel/discussions/8575.
2022-09-21 19:54:34 +00:00
Chris Barber
1558f21e49 [tests] Added lock file for normalize paths test fixture (#8600)
Added a lock file to the normalize paths text fixture to speed up install dependencies.

#### 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-21 19:31:27 +00:00
chloetedder
a6aee8b904 [fs-detectors] Rush json workspace (#8603)
### Related Issues

Add in getting the package paths for Rush workspaces.
First get the contents of the `rush.json` file. If the `projects`
property is an array, map through that array and return the
`projectFolder` as the package path.


### 📋 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-21 13:00:21 -05:00
105 changed files with 1799 additions and 795 deletions

View File

@@ -37,6 +37,7 @@ jobs:
- name: Setup Node
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
uses: actions/setup-node@v3
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
with:
node-version: 14
cache: 'yarn'

View File

@@ -31,6 +31,7 @@ jobs:
with:
go-version: '1.13.15'
- uses: actions/setup-node@v3
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
with:
node-version: ${{ matrix.node }}
cache: 'yarn'
@@ -38,5 +39,5 @@ jobs:
- run: yarn run build
- run: yarn test-integration-cli
env:
VERCEL_TEAM_TOKEN: ${{ secrets.VERCEL_TEAM_TOKEN }}
VERCEL_REGISTRATION_URL: ${{ secrets.VERCEL_REGISTRATION_URL }}
VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }}
VERCEL_TEST_REGISTRATION_URL: ${{ secrets.VERCEL_TEST_REGISTRATION_URL }}

View File

@@ -31,6 +31,7 @@ jobs:
with:
fetch-depth: 2
- uses: actions/setup-node@v3
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
with:
node-version: ${{ matrix.node }}
cache: 'yarn'

View File

@@ -29,6 +29,7 @@ jobs:
with:
go-version: '1.13.15'
- uses: actions/setup-node@v3
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
@@ -65,6 +66,7 @@ jobs:
with:
go-version: '1.13.15'
- uses: actions/setup-node@v3
timeout-minutes: 5 # See https://github.com/actions/cache/issues/810
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
@@ -84,8 +86,8 @@ jobs:
shell: bash
env:
VERCEL_CLI_VERSION: ${{ needs.setup.outputs.dplUrl }}/tarballs/vercel.tgz
VERCEL_TEAM_TOKEN: ${{ secrets.VERCEL_TEAM_TOKEN }}
VERCEL_REGISTRATION_URL: ${{ secrets.VERCEL_REGISTRATION_URL }}
VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }}
VERCEL_TEST_REGISTRATION_URL: ${{ secrets.VERCEL_TEST_REGISTRATION_URL }}
FORCE_COLOR: '1'
conclusion:

View File

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

View File

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

View File

@@ -61,14 +61,14 @@ export function getPrettyError(obj: {
}
return new NowBuildError({
code: 'DEV_VALIDATE_CONFIG',
code: 'INVALID_VERCEL_CONFIG',
message: message,
link: prop ? `${docsUrl}#project/${prop.toLowerCase()}` : docsUrl,
action: 'View Documentation',
});
} catch (e) {
return new NowBuildError({
code: 'DEV_VALIDATE_CONFIG',
code: 'INVALID_VERCEL_CONFIG',
message: `Failed to validate configuration.`,
link: docsUrl,
action: 'View Documentation',

View File

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

View File

@@ -461,7 +461,8 @@ export async function runNpmInstall(
let commandArgs: string[];
const isPotentiallyBrokenNpm =
cliType === 'npm' &&
nodeVersion?.major === 16 &&
(nodeVersion?.major === 16 ||
opts.env.PATH?.includes('/node16/bin-npm7')) &&
!args.includes('--legacy-peer-deps') &&
spawnOpts?.env?.ENABLE_EXPERIMENTAL_COREPACK !== '1';

View File

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

View File

@@ -0,0 +1,64 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
},
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"react": {
"version": "16.8.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.8.0.tgz",
"integrity": "sha512-g+nikW2D48kqgWSPwNo0NH9tIGG3DsQFlrtrQ1kj6W77z5ahyIHG0w8kPpz4Sdj6gyLnz0lEd/xsjOoGge2MYQ==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.13.0"
}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"scheduler": {
"version": "0.13.6",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
"integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"swr": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/swr/-/swr-1.3.0.tgz",
"integrity": "sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw=="
}
}
}

View File

@@ -0,0 +1,11 @@
{
"private": true,
"scripts": {
"build": "mkdir -p public && echo 'legacy peer deps' > public/index.txt"
},
"packageManager": "npm@6.14.17",
"dependencies": {
"swr": "1.3.0",
"react": "16.8.0"
}
}

View File

@@ -0,0 +1,3 @@
{
"probes": [{ "path": "/", "mustContain": "legacy peer deps" }]
}

View File

@@ -34,7 +34,7 @@ it('should not include peer dependencies when missing VERCEL_NPM_LEGACY_PEER_DEP
const fixture = path.join(__dirname, 'fixtures', '20-npm-7');
const meta: Meta = {};
const spawnOpts = getTestSpawnOpts({});
const nodeVersion = { major: 16 } as any;
const nodeVersion = getNodeVersion(16);
await runNpmInstall(fixture, [], spawnOpts, meta, nodeVersion);
expect(spawnMock.mock.calls.length).toBe(1);
const args = spawnMock.mock.calls[0];
@@ -71,10 +71,35 @@ it('should include peer dependencies when VERCEL_NPM_LEGACY_PEER_DEPS=1 on node1
});
});
it('should not include peer dependencies when VERCEL_NPM_LEGACY_PEER_DEPS=1 on node14', async () => {
it('should include peer dependencies when VERCEL_NPM_LEGACY_PEER_DEPS=1 on node14 and npm7+', async () => {
const fixture = path.join(__dirname, 'fixtures', '20-npm-7');
const meta: Meta = {};
const spawnOpts = getTestSpawnOpts({ VERCEL_NPM_LEGACY_PEER_DEPS: '1' });
const nodeVersion = getNodeVersion(14);
await runNpmInstall(fixture, [], spawnOpts, meta, nodeVersion);
expect(spawnMock.mock.calls.length).toBe(1);
const args = spawnMock.mock.calls[0];
expect(args[0]).toEqual('npm');
expect(args[1]).toEqual([
'install',
'--no-audit',
'--unsafe-perm',
'--legacy-peer-deps',
]);
expect(args[2]).toEqual({
cwd: fixture,
prettyCommand: 'npm install',
stdio: 'inherit',
env: expect.any(Object),
});
});
it('should not include peer dependencies when VERCEL_NPM_LEGACY_PEER_DEPS=1 on node14 and npm6', async () => {
const fixture = path.join(__dirname, 'fixtures', '14-npm-6-legacy-peer-deps');
const meta: Meta = {};
const spawnOpts = getTestSpawnOpts({ VERCEL_NPM_LEGACY_PEER_DEPS: '1' });
const nodeVersion = getNodeVersion(14);
await runNpmInstall(fixture, [], spawnOpts, meta, nodeVersion);
expect(spawnMock.mock.calls.length).toBe(1);

View File

@@ -434,8 +434,8 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
expect(warningMessages).toStrictEqual([
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-01 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-01 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-03 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-03 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
]);
global.Date.now = realDateNow;

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "28.4.0",
"version": "28.4.6",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -41,16 +41,16 @@
"node": ">= 14"
},
"dependencies": {
"@vercel/build-utils": "5.4.4",
"@vercel/go": "2.2.7",
"@vercel/hydrogen": "0.0.20",
"@vercel/next": "3.1.27",
"@vercel/node": "2.5.16",
"@vercel/python": "3.1.16",
"@vercel/redwood": "1.0.25",
"@vercel/remix": "1.0.26",
"@vercel/ruby": "1.3.33",
"@vercel/static-build": "1.0.25",
"@vercel/build-utils": "5.5.3",
"@vercel/go": "2.2.11",
"@vercel/hydrogen": "0.0.24",
"@vercel/next": "3.2.2",
"@vercel/node": "2.5.21",
"@vercel/python": "3.1.20",
"@vercel/redwood": "1.0.29",
"@vercel/remix": "1.0.30",
"@vercel/ruby": "1.3.37",
"@vercel/static-build": "1.0.29",
"update-notifier": "5.1.0"
},
"devDependencies": {
@@ -95,9 +95,9 @@
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.2.6",
"@vercel/client": "12.2.11",
"@vercel/frameworks": "1.1.6",
"@vercel/fs-detectors": "3.3.0",
"@vercel/fs-detectors": "3.4.1",
"@vercel/fun": "1.0.4",
"@vercel/ncc": "0.24.0",
"@zeit/source-map-support": "0.6.2",

View File

@@ -2,7 +2,6 @@ import open from 'open';
import boxen from 'boxen';
import execa from 'execa';
import plural from 'pluralize';
import inquirer from 'inquirer';
import { resolve } from 'path';
import chalk, { Chalk } from 'chalk';
import { URLSearchParams, parse } from 'url';
@@ -150,7 +149,9 @@ export default async function main(client: Client): Promise<number> {
if (badDeployment) {
if (badDeployment instanceof Error) {
badDeployment.message += ` "${bad}"`;
badDeployment.message += ` when requesting bad deployment "${normalizeURL(
bad
)}"`;
output.prettyError(badDeployment);
return 1;
}
@@ -165,7 +166,9 @@ export default async function main(client: Client): Promise<number> {
if (goodDeployment) {
if (goodDeployment instanceof Error) {
goodDeployment.message += ` "${good}"`;
goodDeployment.message += ` when requesting good deployment "${normalizeURL(
good
)}"`;
output.prettyError(goodDeployment);
return 1;
}
@@ -226,7 +229,8 @@ export default async function main(client: Client): Promise<number> {
// If we have the "good" deployment in this chunk, then we're done
for (let i = 0; i < newDeployments.length; i++) {
if (newDeployments[i].url === good) {
newDeployments = newDeployments.slice(0, i + 1);
// grab all deployments up until the good one
newDeployments = newDeployments.slice(0, i);
next = undefined;
break;
}
@@ -316,7 +320,7 @@ export default async function main(client: Client): Promise<number> {
if (openEnabled) {
await open(testUrl);
}
const answer = await inquirer.prompt({
const answer = await client.prompt({
type: 'expand',
name: 'action',
message: 'Select an action:',

View File

@@ -25,6 +25,7 @@ import {
MergeRoutesProps,
Route,
} from '@vercel/routing-utils';
import { fileNameSymbol } from '@vercel/client';
import type { VercelConfig } from '@vercel/client';
import pull from './pull';
@@ -54,6 +55,7 @@ import { importBuilders } from '../util/build/import-builders';
import { initCorepack, cleanupCorepack } from '../util/build/corepack';
import { sortBuilders } from '../util/build/sort-builders';
import { toEnumerableError } from '../util/error';
import { validateConfig } from '../util/validate-config';
type BuildResult = BuildResultV2 | BuildResultV3;
@@ -232,7 +234,8 @@ export default async function main(client: Client): Promise<number> {
process.env.VERCEL = '1';
process.env.NOW_BUILDER = '1';
return await doBuild(client, project, buildsJson, cwd, outputDir);
await doBuild(client, project, buildsJson, cwd, outputDir);
return 0;
} catch (err: any) {
output.prettyError(err);
@@ -265,23 +268,36 @@ async function doBuild(
buildsJson: BuildsManifest,
cwd: string,
outputDir: string
): Promise<number> {
): Promise<void> {
const { output } = client;
const workPath = join(cwd, project.settings.rootDirectory || '.');
// Load `package.json` and `vercel.json` files
const [pkg, vercelConfig] = await Promise.all([
const [pkg, vercelConfig, nowConfig] = await Promise.all([
readJSONFile<PackageJson>(join(workPath, 'package.json')),
readJSONFile<VercelConfig>(join(workPath, 'vercel.json')).then(
config => config || readJSONFile<VercelConfig>(join(workPath, 'now.json'))
),
readJSONFile<VercelConfig>(join(workPath, 'vercel.json')),
readJSONFile<VercelConfig>(join(workPath, 'now.json')),
]);
if (pkg instanceof CantParseJSONFile) throw pkg;
if (vercelConfig instanceof CantParseJSONFile) throw vercelConfig;
if (nowConfig instanceof CantParseJSONFile) throw nowConfig;
if (vercelConfig) {
vercelConfig[fileNameSymbol] = 'vercel.json';
} else if (nowConfig) {
nowConfig[fileNameSymbol] = 'now.json';
}
const localConfig = vercelConfig || nowConfig || {};
const validateError = validateConfig(localConfig);
if (validateError) {
throw validateError;
}
const projectSettings = {
...project.settings,
...pickOverrides(vercelConfig || {}),
...pickOverrides(localConfig),
};
// Get a list of source files
@@ -289,12 +305,12 @@ async function doBuild(
normalizePath(relative(workPath, f))
);
const routesResult = getTransformedRoutes(vercelConfig || {});
const routesResult = getTransformedRoutes(localConfig);
if (routesResult.error) {
throw routesResult.error;
}
if (vercelConfig?.builds && vercelConfig.functions) {
if (localConfig.builds && localConfig.functions) {
throw new NowBuildError({
code: 'bad_request',
message:
@@ -303,7 +319,7 @@ async function doBuild(
});
}
let builds = vercelConfig?.builds || [];
let builds = localConfig.builds || [];
let zeroConfigRoutes: Route[] = [];
let isZeroConfig = false;
@@ -318,7 +334,7 @@ async function doBuild(
// Detect the Vercel Builders that will need to be invoked
const detectedBuilders = await detectBuilders(files, pkg, {
...vercelConfig,
...localConfig,
projectSettings,
ignoreBuildScript: true,
featHandleMiss: true,
@@ -395,13 +411,10 @@ async function doBuild(
})
);
buildsJson.builds = Array.from(buildsJsonBuilds.values());
const buildsJsonPath = join(outputDir, 'builds.json');
const writeBuildsJsonPromise = fs.writeJSON(buildsJsonPath, buildsJson, {
await fs.writeJSON(join(outputDir, 'builds.json'), buildsJson, {
spaces: 2,
});
ops.push(writeBuildsJsonPromise);
// The `meta` config property is re-used for each Builder
// invocation so that Builders can share state between
// subsequent entrypoint builds.
@@ -466,7 +479,7 @@ async function doBuild(
build,
builder,
builderPkg,
vercelConfig
localConfig
).then(
override => {
if (override) overrides.push(override);
@@ -475,26 +488,11 @@ async function doBuild(
)
);
} catch (err: any) {
output.prettyError(err);
const writeConfigJsonPromise = fs.writeJSON(
join(outputDir, 'config.json'),
{ version: 3 },
{ spaces: 2 }
);
await Promise.all([writeBuildsJsonPromise, writeConfigJsonPromise]);
const buildJsonBuild = buildsJsonBuilds.get(build);
if (buildJsonBuild) {
buildJsonBuild.error = toEnumerableError(err);
await fs.writeJSON(buildsJsonPath, buildsJson, {
spaces: 2,
});
}
return 1;
throw err;
}
}
@@ -555,150 +553,7 @@ async function doBuild(
builds: builderRoutes,
});
const images = vercelConfig?.images
if (images) {
if (typeof images !== 'object') {
throw new Error(
`vercel.json "images" should be an object received ${typeof images}.`
);
}
if (!Array.isArray(images.domains)) {
throw new Error(
`vercel.json "images.domains" should be an Array received ${typeof images.domains}.`
);
}
if (images.domains.length > 50) {
throw new Error(
`vercel.json "images.domains" exceeds length of 50 received length (${images.domains.length}).`
);
}
const invalidImageDomains = images.domains.filter(
(d: unknown) => typeof d !== 'string'
);
if (invalidImageDomains.length > 0) {
throw new Error(
`vercel.json "images.domains" should be an Array of strings received invalid values (${invalidImageDomains.join(
', '
)}).`
);
}
if (images.remotePatterns) {
if (!Array.isArray(images.remotePatterns)) {
throw new Error(
`vercel.json "images.remotePatterns" should be an Array received ${typeof images.remotePatterns}.`
);
}
if (images.remotePatterns.length > 50) {
throw new Error(
`vercel.json "images.remotePatterns" exceeds length of 50, received length (${images.remotePatterns.length}).`
);
}
const validProps = new Set(['protocol', 'hostname', 'pathname', 'port']);
const requiredProps = ['hostname'];
const invalidPatterns = images.remotePatterns.filter(
(d: unknown) =>
!d ||
typeof d !== 'object' ||
Object.entries(d).some(
([k, v]) => !validProps.has(k) || typeof v !== 'string'
) ||
requiredProps.some(k => !(k in d))
);
if (invalidPatterns.length > 0) {
throw new Error(
`vercel.json "images.remotePatterns" received invalid values:\n${invalidPatterns
.map(item => JSON.stringify(item))
.join(
'\n'
)}\n\nremotePatterns value must follow format { protocol: 'https', hostname: 'example.com', port: '', pathname: '/imgs/**' }.`
);
}
}
if (!Array.isArray(images.sizes)) {
throw new Error(
`vercel.json "images.sizes" should be an Array received ${typeof images.sizes}.`
);
}
if (images.sizes.length < 1 || images.sizes.length > 50) {
throw new Error(
`vercel.json "images.sizes" should be an Array of length between 1 to 50 received length (${images.sizes.length}).`
);
}
const invalidImageSizes = images.sizes.filter((d: unknown) => {
return typeof d !== 'number' || d < 1 || d > 10000;
});
if (invalidImageSizes.length > 0) {
throw new Error(
`vercel.json "images.sizes" should be an Array of numbers that are between 1 and 10000, received invalid values (${invalidImageSizes.join(
', '
)}).`
);
}
if (images.minimumCacheTTL) {
if (
!Number.isInteger(images.minimumCacheTTL) ||
images.minimumCacheTTL < 0
) {
throw new Error(
`vercel.json "images.minimumCacheTTL" should be an integer 0 or more received (${images.minimumCacheTTL}).`
);
}
}
if (images.formats) {
if (!Array.isArray(images.formats)) {
throw new Error(
`vercel.json "images.formats" should be an Array received ${typeof images.formats}.`
);
}
if (images.formats.length < 1 || images.formats.length > 2) {
throw new Error(
`vercel.json "images.formats" must be length 1 or 2, received length (${images.formats.length}).`
);
}
const invalid = images.formats.filter(f => {
return f !== 'image/avif' && f !== 'image/webp';
});
if (invalid.length > 0) {
throw new Error(
`vercel.json "images.formats" should be an Array of mime type strings, received invalid values (${invalid.join(
', '
)}).`
);
}
}
if (
typeof images.dangerouslyAllowSVG !== 'undefined' &&
typeof images.dangerouslyAllowSVG !== 'boolean'
) {
throw new Error(
`vercel.json "images.dangerouslyAllowSVG" should be a boolean received (${images.dangerouslyAllowSVG}).`
);
}
if (
typeof images.contentSecurityPolicy !== 'undefined' &&
typeof images.contentSecurityPolicy !== 'string'
) {
throw new Error(
`vercel.json "images.contentSecurityPolicy" should be a string received ${images.contentSecurityPolicy}`
);
}
}
const mergedImages = mergeImages(images, buildResults.values());
const mergedImages = mergeImages(localConfig.images, buildResults.values());
const mergedWildcard = mergeWildcard(buildResults.values());
const mergedOverrides: Record<string, PathOverride> =
overrides.length > 0 ? Object.assign({}, ...overrides) : undefined;
@@ -724,8 +579,6 @@ async function doBuild(
emoji('success')
)}\n`
);
return 0;
}
function expandBuild(files: string[], build: Builder): Builder[] {

View File

@@ -23,6 +23,7 @@ import type {
} from '../types';
import { sharedPromise } from './promise';
import { APIError } from './errors-ts';
import { normalizeError } from './is-error';
const isSAMLError = (v: any): v is SAMLError => {
return v && v.saml;
@@ -146,10 +147,15 @@ export default class Client extends EventEmitter implements Stdio {
const error = await responseError(res);
if (isSAMLError(error)) {
// A SAML error means the token is expired, or is not
// designated for the requested team, so the user needs
// to re-authenticate
await this.reauthenticate(error);
try {
// A SAML error means the token is expired, or is not
// designated for the requested team, so the user needs
// to re-authenticate
await this.reauthenticate(error);
} catch (reauthError) {
// there's no sense in retrying
return bail(normalizeError(reauthError));
}
} else if (res.status >= 400 && res.status < 500) {
// Any other 4xx should bail without retrying
return bail(error);
@@ -186,7 +192,7 @@ export default class Client extends EventEmitter implements Stdio {
`Failed to re-authenticate for ${bold(error.scope)} scope`
);
}
process.exit(1);
throw error;
}
this.authConfig.token = result.token;

View File

@@ -78,16 +78,23 @@ function getLastCommit(directory: string): Promise<git.Commit> {
export function isDirty(directory: string, output: Output): Promise<boolean> {
return new Promise(resolve => {
exec('git status -s', { cwd: directory }, function (err, stdout, stderr) {
let debugMessage = `Failed to determine if Git repo has been modified:`;
if (err || stderr) {
if (err) debugMessage += `\n${err}`;
if (stderr) debugMessage += `\n${stderr.trim()}`;
output.debug(debugMessage);
return resolve(false);
// note: we specify the `--no-optional-locks` git flag so that `git status`
// does not perform any "optional" operations such as optimizing the index
// in the background: https://git-scm.com/docs/git-status#_background_refresh
exec(
'git --no-optional-locks status -s',
{ cwd: directory },
function (err, stdout, stderr) {
let debugMessage = `Failed to determine if Git repo has been modified:`;
if (err || stderr) {
if (err) debugMessage += `\n${err}`;
if (stderr) debugMessage += `\n${stderr.trim()}`;
output.debug(debugMessage);
return resolve(false);
}
resolve(stdout.trim().length > 0);
}
resolve(stdout.trim().length > 0);
});
);
});
}

View File

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

View File

@@ -57,7 +57,7 @@ import { MissingDotenvVarsError } from '../errors-ts';
import cliPkg from '../pkg';
import { getVercelDirectory } from '../projects/link';
import { staticFiles as getFiles } from '../get-files';
import { validateConfig } from './validate';
import { validateConfig } from '../validate-config';
import { devRouter, getRoutesTypes } from './router';
import getMimeType from './mime-type';
import { executeBuild, getBuildMatches, shutdownBuilder } from './builder';

View File

@@ -4,6 +4,8 @@ import wait, { StopSpinner } from './wait';
import type { WritableTTY } from '../../types';
import { errorToString } from '../is-error';
const IS_TEST = process.env.NODE_ENV === 'test';
export interface OutputOptions {
debug?: boolean;
}
@@ -108,12 +110,15 @@ export class Output {
};
spinner = (message: string, delay: number = 300): void => {
this.spinnerMessage = message;
if (this.debugEnabled) {
this.debug(`Spinner invoked (${message}) with a ${delay}ms delay`);
return;
}
if (this.stream.isTTY) {
if (IS_TEST || !this.stream.isTTY) {
this.print(`${message}\n`);
} else {
this.spinnerMessage = message;
if (this._spinner) {
this._spinner.text = message;
} else {
@@ -125,8 +130,6 @@ export class Output {
delay
);
}
} else {
this.print(`${message}\n`);
}
};

View File

@@ -28,6 +28,16 @@ export async function writeProjectSettings(
project: Project,
org: Org
) {
let analyticsId: string | undefined;
if (
project.analytics?.id &&
(!project.analytics.disabledAt ||
(project.analytics.enabledAt &&
project.analytics.enabledAt > project.analytics.disabledAt))
) {
analyticsId = project.analytics.id;
}
const projectLinkAndSettings: ProjectLinkAndSettings = {
projectId: project.id,
orgId: org.id,
@@ -41,7 +51,7 @@ export async function writeProjectSettings(
rootDirectory: project.rootDirectory,
directoryListing: project.directoryListing,
nodeVersion: project.nodeVersion,
analyticsId: project.analytics?.id,
analyticsId,
},
};
const path = join(cwd, VERCEL_DIR, VERCEL_DIR_PROJECT);

View File

@@ -7,7 +7,7 @@ import {
rewritesSchema,
trailingSlashSchema,
} from '@vercel/routing-utils';
import { VercelConfig } from './types';
import { VercelConfig } from './dev/types';
import {
functionsSchema,
buildsSchema,
@@ -16,6 +16,83 @@ import {
} from '@vercel/build-utils';
import { fileNameSymbol } from '@vercel/client';
const imagesSchema = {
type: 'object',
additionalProperties: false,
required: ['sizes'],
properties: {
contentSecurityPolicy: {
type: 'string',
minLength: 1,
maxLength: 256,
},
dangerouslyAllowSVG: {
type: 'boolean',
},
domains: {
type: 'array',
minItems: 0,
maxItems: 50,
items: {
type: 'string',
minLength: 1,
maxLength: 256,
},
},
formats: {
type: 'array',
minItems: 1,
maxItems: 4,
items: {
enum: ['image/avif', 'image/webp', 'image/jpeg', 'image/png'],
},
},
minimumCacheTTL: {
type: 'integer',
minimum: 1,
maximum: 315360000,
},
remotePatterns: {
type: 'array',
minItems: 0,
maxItems: 50,
items: {
type: 'object',
additionalProperties: false,
required: ['hostname'],
properties: {
protocol: {
enum: ['http', 'https'],
},
hostname: {
type: 'string',
minLength: 1,
maxLength: 256,
},
port: {
type: 'string',
minLength: 1,
maxLength: 5,
},
pathname: {
type: 'string',
minLength: 1,
maxLength: 256,
},
},
},
},
sizes: {
type: 'array',
minItems: 1,
maxItems: 50,
items: {
type: 'number',
},
},
},
};
const vercelConfigSchema = {
type: 'object',
// These are not all possibilities because `vc dev`
@@ -30,6 +107,7 @@ const vercelConfigSchema = {
rewrites: rewritesSchema,
trailingSlash: trailingSlashSchema,
functions: functionsSchema,
images: imagesSchema,
},
};

View File

@@ -0,0 +1,13 @@
import generateUUID from '../vendor/generate-uuid';
export const config = {
runtime: 'experimental-edge',
};
export default async function edge(request, event) {
return new Response(
JSON.stringify({
randomString: generateUUID(),
})
);
}

View File

@@ -0,0 +1,6 @@
// This uses the `crypto` globaly,
// which will work fine in a browser or Edge Runtime environment
export default function generateUUID(message) {
return crypto.randomUUID();
}

View File

@@ -0,0 +1,8 @@
import crypto from 'crypto';
// this uses a core Node library,
// which will fail to execute in a browser or Edge Runtime context
export default function say(message) {
return crypto.generateUUID();
}

View File

@@ -0,0 +1,6 @@
{
"name": "generate-uuid",
"private": true,
"main": "index-node.js",
"browser": "index-browser.js"
}

View File

@@ -86,6 +86,7 @@ test(
testFixtureStdio('edge-function', async (testPath: any) => {
await testPath(500, '/api/edge-500-response');
await testPath(200, '/api/edge-success');
await testPath(200, '/api/edge-import-browser');
})
);

View File

@@ -3,6 +3,6 @@
"sizes": [256, 384, 600, 1000],
"domains": [],
"minimumCacheTTL": 60,
"formats": ["image/webp", "image/avif"]
"formats": ["image/avif", "image/webp"]
}
}

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
{
"rewrites": [
{
"source": "/one",
"destination": "/api/one"
},
{
"source": "/two",
"destination": "/api/two"
},
{
"src": "/three",
"dest": "/api/three"
}
]
}

View File

@@ -3,20 +3,26 @@ import chance from 'chance';
import { Deployment } from '@vercel/client';
import { client } from './client';
import { Build, User } from '../../src/types';
import type { Request, Response } from 'express';
let deployments = new Map<string, Deployment>();
let deploymentBuilds = new Map<Deployment, Build[]>();
let alreadySetupDeplomentEndpoints = false;
type State = Deployment['readyState'];
export function useDeployment({
creator,
state = 'READY',
createdAt,
}: {
creator: Pick<User, 'id' | 'email' | 'name' | 'username'>;
state?: State;
createdAt?: number;
}) {
const createdAt = Date.now();
setupDeploymentEndpoints();
createdAt = createdAt || Date.now();
const url = new URL(chance().url());
const name = chance().name();
const id = `dpl_${chance().guid()}`;
@@ -99,6 +105,15 @@ export function useDeploymentMissingProjectSettings() {
beforeEach(() => {
deployments = new Map();
deploymentBuilds = new Map();
alreadySetupDeplomentEndpoints = false;
});
function setupDeploymentEndpoints() {
if (alreadySetupDeplomentEndpoints) {
return;
}
alreadySetupDeplomentEndpoints = true;
client.scenario.get('/:version/deployments/:id', (req, res) => {
const { id } = req.params;
@@ -136,8 +151,21 @@ beforeEach(() => {
res.json({ builds });
});
client.scenario.get('/:version/now/deployments', (req, res) => {
const deploymentsList = Array.from(deployments.values());
res.json({ deployments: deploymentsList });
});
});
function handleGetDeployments(req: Request, res: Response) {
const currentDeployments = Array.from(deployments.values()).sort(
(a: Deployment, b: Deployment) => {
// sort in reverse chronological order
return b.createdAt - a.createdAt;
}
);
res.json({
pagination: {
count: currentDeployments.length,
},
deployments: currentDeployments,
});
}
client.scenario.get('/:version/now/deployments', handleGetDeployments);
client.scenario.get('/:version/deployments', handleGetDeployments);
}

View File

@@ -0,0 +1,55 @@
import { client } from '../../mocks/client';
import { useUser } from '../../mocks/user';
import bisect from '../../../src/commands/bisect';
import { useDeployment } from '../../mocks/deployment';
describe('bisect', () => {
it('should find the bad deployment', async () => {
const user = useUser();
const now = Date.now();
const deployment1 = useDeployment({ creator: user, createdAt: now });
const deployment2 = useDeployment({
creator: user,
createdAt: now + 10000,
});
const deployment3 = useDeployment({
creator: user,
createdAt: now + 20000,
});
// also create an extra deployment before the known good deployment
// to make sure the bisect pool doesn't include it
useDeployment({
creator: user,
createdAt: now - 30000,
});
const bisectPromise = bisect(client);
await expect(client.stderr).toOutput('Specify a URL where the bug occurs:');
client.stdin.write(`https://${deployment3.url}\n`);
await expect(client.stderr).toOutput(
'Specify a URL where the bug does not occur:'
);
client.stdin.write(`https://${deployment1.url}\n`);
await expect(client.stderr).toOutput(
'Specify the URL subpath where the bug occurs:'
);
client.stdin.write('/docs\n');
await expect(client.stderr).toOutput('Bisecting');
await expect(client.stderr).toOutput(
`Deployment URL: https://${deployment2.url}`
);
client.stdin.write('b\n');
await expect(client.stderr).toOutput(
`The first bad deployment is: https://${deployment2.url}`
);
await expect(bisectPromise).resolves.toEqual(0);
});
});

View File

@@ -750,11 +750,22 @@ describe('build', () => {
const errorBuilds = builds.builds.filter((b: any) => 'error' in b);
expect(errorBuilds).toHaveLength(1);
expect(errorBuilds[0].error.name).toEqual('Error');
expect(errorBuilds[0].error.message).toMatch(`TS1005`);
expect(errorBuilds[0].error.message).toMatch(`',' expected.`);
expect(errorBuilds[0].error.hideStackTrace).toEqual(true);
expect(errorBuilds[0].error.code).toEqual('NODE_TYPESCRIPT_ERROR');
expect(errorBuilds[0].error).toEqual({
name: 'Error',
message: expect.stringContaining('TS1005'),
stack: expect.stringContaining('api/typescript.ts'),
hideStackTrace: true,
code: 'NODE_TYPESCRIPT_ERROR',
});
// top level "error" also contains the same error
expect(builds.error).toEqual({
name: 'Error',
message: expect.stringContaining('TS1005'),
stack: expect.stringContaining('api/typescript.ts'),
hideStackTrace: true,
code: 'NODE_TYPESCRIPT_ERROR',
});
// `config.json` contains `version`
const configJson = await fs.readJSON(join(output, 'config.json'));
@@ -920,7 +931,7 @@ describe('build', () => {
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
it('should apply "images" configuration from `vercel.json`', async () => {
const cwd = fixture('images');
const output = join(cwd, '.vercel/output');
@@ -936,7 +947,7 @@ describe('build', () => {
sizes: [256, 384, 600, 1000],
domains: [],
minimumCacheTTL: 60,
formats: ['image/webp', 'image/avif'],
formats: ['image/avif', 'image/webp'],
},
});
} finally {
@@ -945,6 +956,38 @@ describe('build', () => {
}
});
it('should fail with invalid "rewrites" configuration from `vercel.json`', async () => {
const cwd = fixture('invalid-rewrites');
const output = join(cwd, '.vercel/output');
try {
process.chdir(cwd);
const exitCode = await build(client);
expect(exitCode).toEqual(1);
await expect(client.stderr).toOutput(
'Error: Invalid vercel.json - `rewrites[2]` should NOT have additional property `src`. Did you mean `source`?' +
'\n' +
'View Documentation: https://vercel.com/docs/configuration#project/rewrites'
);
const builds = await fs.readJSON(join(output, 'builds.json'));
expect(builds.builds).toBeUndefined();
expect(builds.error).toEqual({
name: 'Error',
message:
'Invalid vercel.json - `rewrites[2]` should NOT have additional property `src`. Did you mean `source`?',
stack: expect.stringContaining('at validateConfig'),
hideStackTrace: true,
code: 'INVALID_VERCEL_CONFIG',
link: 'https://vercel.com/docs/configuration#project/rewrites',
action: 'View Documentation',
});
const configJson = await fs.readJSON(join(output, 'config.json'));
expect(configJson.version).toBe(3);
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
describe('should find packages with different main/module/browser keys', function () {
let output: string;

View File

@@ -1,4 +1,7 @@
import bytes from 'bytes';
import fs from 'fs-extra';
import { join } from 'path';
import { randomBytes } from 'crypto';
import { fileNameSymbol } from '@vercel/client';
import { client } from '../../mocks/client';
import deploy from '../../../src/commands/deploy';
@@ -199,4 +202,119 @@ describe('deploy', () => {
process.chdir(originalCwd);
}
});
it('should upload missing files', async () => {
const cwd = setupFixture('commands/deploy/archive');
const originalCwd = process.cwd();
// Add random 1mb file
await fs.writeFile(join(cwd, 'data'), randomBytes(bytes('1mb')));
try {
process.chdir(cwd);
const user = useUser();
useTeams('team_dummy');
useProject({
...defaultProject,
name: 'archive',
id: 'archive',
});
let body: any;
let fileUploaded = false;
client.scenario.post(`/v13/deployments`, (req, res) => {
if (fileUploaded) {
body = req.body;
res.json({
creator: {
uid: user.id,
username: user.username,
},
id: 'dpl_archive_test',
});
} else {
const sha = req.body.files[0].sha;
res.status(400).json({
error: {
code: 'missing_files',
message: 'Missing files',
missing: [sha],
},
});
}
});
client.scenario.post('/v2/files', (req, res) => {
// Wait for file to be finished uploading
req.on('data', () => {
// Noop
});
req.on('end', () => {
fileUploaded = true;
res.end();
});
});
client.scenario.get(`/v13/deployments/dpl_archive_test`, (req, res) => {
res.json({
creator: {
uid: user.id,
username: user.username,
},
id: 'dpl_archive_test',
readyState: 'READY',
aliasAssigned: true,
alias: [],
});
});
client.scenario.get(
`/v10/now/deployments/dpl_archive_test`,
(req, res) => {
res.json({
creator: {
uid: user.id,
username: user.username,
},
id: 'dpl_archive_test',
readyState: 'READY',
aliasAssigned: true,
alias: [],
});
}
);
// When stderr is not a TTY we expect 5 progress lines to be printed
client.stderr.isTTY = false;
client.setArgv('deploy', '--archive=tgz');
const uploadingLines: string[] = [];
client.stderr.on('data', data => {
if (data.startsWith('Uploading [')) {
uploadingLines.push(data);
}
});
client.stderr.resume();
const exitCode = await deploy(client);
expect(exitCode).toEqual(0);
expect(body?.files?.length).toEqual(1);
expect(body?.files?.[0].file).toEqual('.vercel/source.tgz');
expect(uploadingLines.length).toEqual(5);
expect(
uploadingLines[0].startsWith('Uploading [--------------------]')
).toEqual(true);
expect(
uploadingLines[1].startsWith('Uploading [=====---------------]')
).toEqual(true);
expect(
uploadingLines[2].startsWith('Uploading [==========----------]')
).toEqual(true);
expect(
uploadingLines[3].startsWith('Uploading [===============-----]')
).toEqual(true);
expect(
uploadingLines[4].startsWith('Uploading [====================]')
).toEqual(true);
} finally {
process.chdir(originalCwd);
}
});
});

View File

@@ -38,11 +38,11 @@ describe('list', () => {
await list(client);
const output = await readOutputStream(client, 4);
const output = await readOutputStream(client, 6);
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[0]);
const header: string[] = parseSpacedTableRow(output.split('\n')[3]);
const data: string[] = parseSpacedTableRow(output.split('\n')[4]);
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[2]);
const header: string[] = parseSpacedTableRow(output.split('\n')[5]);
const data: string[] = parseSpacedTableRow(output.split('\n')[6]);
data.shift();
expect(org).toEqual(team[0].slug);
@@ -81,11 +81,11 @@ describe('list', () => {
client.setArgv('-S', user.username);
await list(client);
const output = await readOutputStream(client, 4);
const output = await readOutputStream(client, 6);
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[0]);
const header: string[] = parseSpacedTableRow(output.split('\n')[3]);
const data: string[] = parseSpacedTableRow(output.split('\n')[4]);
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[2]);
const header: string[] = parseSpacedTableRow(output.split('\n')[5]);
const data: string[] = parseSpacedTableRow(output.split('\n')[6]);
data.shift();
expect(org).toEqual(user.username);
@@ -116,11 +116,11 @@ describe('list', () => {
client.setArgv(deployment.name);
await list(client);
const output = await readOutputStream(client, 4);
const output = await readOutputStream(client, 6);
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[0]);
const header: string[] = parseSpacedTableRow(output.split('\n')[3]);
const data: string[] = parseSpacedTableRow(output.split('\n')[4]);
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[2]);
const header: string[] = parseSpacedTableRow(output.split('\n')[5]);
const data: string[] = parseSpacedTableRow(output.split('\n')[6]);
data.shift();
expect(org).toEqual(teamSlug || team[0].slug);

View File

@@ -22,10 +22,10 @@ describe('project', () => {
client.setArgv('project', 'ls');
await projects(client);
const output = await readOutputStream(client, 2);
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[0]);
const header: string[] = parseSpacedTableRow(output.split('\n')[2]);
const data: string[] = parseSpacedTableRow(output.split('\n')[3]);
const output = await readOutputStream(client, 3);
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[1]);
const header: string[] = parseSpacedTableRow(output.split('\n')[3]);
const data: string[] = parseSpacedTableRow(output.split('\n')[4]);
data.pop();
expect(org).toEqual(user.username);
@@ -47,10 +47,10 @@ describe('project', () => {
client.setArgv('project', 'ls');
await projects(client);
const output = await readOutputStream(client, 2);
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[0]);
const header: string[] = parseSpacedTableRow(output.split('\n')[2]);
const data: string[] = parseSpacedTableRow(output.split('\n')[3]);
const output = await readOutputStream(client, 3);
const { org } = pluckIdentifiersFromDeploymentList(output.split('\n')[1]);
const header: string[] = parseSpacedTableRow(output.split('\n')[3]);
const data: string[] = parseSpacedTableRow(output.split('\n')[4]);
data.pop();
expect(org).toEqual(user.username);

View File

@@ -1,4 +1,4 @@
import { validateConfig } from '../../../../src/util/dev/validate';
import { validateConfig } from '../../../../src/util/validate-config';
describe('validateConfig', () => {
it('should not error with empty config', async () => {

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "12.2.6",
"version": "12.2.11",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -43,7 +43,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "5.4.4",
"@vercel/build-utils": "5.5.3",
"@vercel/routing-utils": "2.0.2",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",

View File

@@ -1,4 +1,5 @@
import { Agent } from 'https';
import http from 'http';
import https from 'https';
import { Readable } from 'stream';
import { EventEmitter } from 'events';
import retry from 'async-retry';
@@ -78,7 +79,9 @@ export async function* upload(
debug('Building an upload list...');
const semaphore = new Sema(50, { capacity: 50 });
const agent = new Agent({ keepAlive: true });
const agent = apiUrl?.startsWith('https://')
? new https.Agent({ keepAlive: true })
: new http.Agent({ keepAlive: true });
shas.forEach((sha, index) => {
const uploadProgress = uploads[index];

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/fs-detectors",
"version": "3.3.0",
"version": "3.4.1",
"description": "Vercel filesystem detectors",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -23,6 +23,7 @@
"@vercel/routing-utils": "2.0.2",
"glob": "8.0.3",
"js-yaml": "4.1.0",
"json5": "2.2.1",
"minimatch": "3.0.4",
"semver": "6.1.1"
},

View File

@@ -89,33 +89,34 @@ export abstract class DetectorFilesystem {
if (!p) {
p = this._readdir(dirPath);
this.readdirCache.set(dirPath, p);
}
const directoryContent = await p;
const directoryFiles = new Set<string>();
const directoryContent = await p;
const directoryFiles = new Set<string>();
for (const file of directoryContent) {
if (file.type === 'file') {
// we know this file exists, mark it as so on the filesystem
this.fileCache.set(file.path, Promise.resolve(true));
this.pathCache.set(file.path, Promise.resolve(true));
directoryFiles.add(file.name);
}
}
if (options?.potentialFiles) {
// calculate the set of paths that truly do not exist
const filesThatDoNotExist = options.potentialFiles.filter(
path => !directoryFiles.has(path)
);
for (const filePath of filesThatDoNotExist) {
const fullFilePath =
dirPath === '/' ? filePath : posixPath.join(dirPath, filePath);
// we know this file does not exist, mark it as so on the filesystem
this.fileCache.set(fullFilePath, Promise.resolve(false));
this.pathCache.set(fullFilePath, Promise.resolve(false));
}
for (const file of directoryContent) {
if (file.type === 'file') {
// we know this file exists, mark it as so on the filesystem
this.fileCache.set(file.path, Promise.resolve(true));
this.pathCache.set(file.path, Promise.resolve(true));
directoryFiles.add(file.name);
}
}
if (options?.potentialFiles) {
// calculate the set of paths that truly do not exist
const filesThatDoNotExist = options.potentialFiles.filter(
path => !directoryFiles.has(path)
);
for (const filePath of filesThatDoNotExist) {
const fullFilePath =
dirPath === '/' ? filePath : posixPath.join(dirPath, filePath);
// we know this file does not exist, mark it as so on the filesystem
this.fileCache.set(fullFilePath, Promise.resolve(false));
this.pathCache.set(fullFilePath, Promise.resolve(false));
}
}
return p;
};

View File

@@ -1,6 +1,7 @@
import _path from 'path';
import yaml from 'js-yaml';
import glob from 'glob';
import json5 from 'json5';
import { DetectorFilesystem } from '../detectors/filesystem';
import { Workspace } from './get-workspaces';
import { getGlobFs } from './get-glob-fs';
@@ -36,6 +37,9 @@ export async function getWorkspacePackagePaths({
case 'nx':
results = await getNxWorkspacePackagePaths({ fs: workspaceFs });
break;
case 'rush':
results = await getRushWorkspacePackagePaths({ fs: workspaceFs });
break;
default:
throw new Error(`Unknown workspace implementation: ${type}`);
}
@@ -58,6 +62,14 @@ type PnpmWorkspaces = {
packages?: string[];
};
type RushWorkspaces = {
projects: [
{
projectFolder: string;
}
];
};
const isWin = process.platform === 'win32';
const normalizePath = (p: string) => (isWin ? p.replace(/\\/g, '/') : p);
@@ -127,3 +139,23 @@ async function getPnpmWorkspacePackagePaths({
return getPackagePaths(packages, fs);
}
async function getRushWorkspacePackagePaths({
fs,
}: GetPackagePathOptions): Promise<string[]> {
const rushWorkspaceAsBuffer = await fs.readFile('rush.json');
const { projects = [] } = json5.parse(
rushWorkspaceAsBuffer.toString()
) as RushWorkspaces;
if (Array.isArray(projects)) {
const packages = projects
.filter(proj => proj.projectFolder)
.map(project => project.projectFolder);
return getPackagePaths(packages, fs);
} else {
return [];
}
}

View File

@@ -12,7 +12,7 @@ export interface GetWorkspaceOptions {
cwd?: string;
}
export type WorkspaceType = 'yarn' | 'pnpm' | 'npm' | 'nx';
export type WorkspaceType = 'yarn' | 'pnpm' | 'npm' | 'nx' | 'rush';
export type Workspace = {
type: WorkspaceType;

View File

@@ -71,6 +71,17 @@ export const workspaceManagers: Array<
],
},
},
{
name: 'rush',
slug: 'rush',
detectors: {
every: [
{
path: 'rush.json',
},
],
},
},
{
name: 'default',
slug: 'yarn',

View File

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

View File

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

View File

@@ -2,7 +2,6 @@
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json",
"rushVersion": "5.76.1",
"pnpmVersion": "6.7.1",
"pnpmOptions": {
@@ -22,6 +21,16 @@
"postRushBuild": []
},
// comment
"variants": [],
"projects": []
"projects": [
{
"packageName": "my-app",
"projectFolder": "apps/my-app"
},
{
"packageName": "my-second-app",
"projectFolder": "apps/my-second-app"
}
]
}

View File

@@ -1,5 +1,5 @@
{
"name": "b",
"name": "app-one",
"version": "1.0.0",
"description": "",
"main": "index.js",

View File

@@ -1,5 +1,5 @@
{
"name": "b",
"name": "app-two",
"version": "1.0.0",
"description": "",
"main": "index.js",

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json",
"rushVersion": "5.76.1",
"pnpmVersion": "6.7.1",
"pnpmOptions": {
"useWorkspaces": true
},
"nodeSupportedVersionRange": ">=12.13.0 <13.0.0 || >=14.15.0 <15.0.0 || >=16.13.0 <17.0.0",
"gitPolicy": {},
"repository": {},
"eventHooks": {
"preRushInstall": [],
"postRushInstall": [],
"preRushBuild": [],
"postRushBuild": []
},
"variants": [],
"projects": []
}

View File

@@ -1,5 +1,5 @@
{
"name": "b",
"name": "app-one",
"version": "1.0.0",
"description": "",
"main": "index.js",

View File

@@ -1,5 +1,5 @@
{
"name": "b",
"name": "app-two",
"version": "1.0.0",
"description": "",
"main": "index.js",

View File

@@ -0,0 +1,29 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json",
"rushVersion": "5.76.1",
"pnpmVersion": "6.7.1",
"pnpmOptions": {
"useWorkspaces": true
},
"nodeSupportedVersionRange": ">=12.13.0 <13.0.0 || >=14.15.0 <15.0.0 || >=16.13.0 <17.0.0",
"gitPolicy": {},
/*
this is a comment
*/
"repository": {},
"eventHooks": {
"preRushInstall": [],
"postRushInstall": [],
"preRushBuild": [],
"postRushBuild": []
},
"variants": [],
"projects": "projects"
}

View File

@@ -1,5 +1,5 @@
{
"name": "b",
"name": "app-one",
"version": "1.0.0",
"description": "",
"main": "index.js",

View File

@@ -1,5 +1,5 @@
{
"name": "b",
"name": "app-two",
"version": "1.0.0",
"description": "",
"main": "index.js",

View File

@@ -1,5 +1,5 @@
{
"name": "b",
"name": "app-one",
"version": "1.0.0",
"description": "",
"main": "index.js",

View File

@@ -1,5 +1,5 @@
{
"name": "b",
"name": "app-two",
"version": "1.0.0",
"description": "",
"main": "index.js",

View File

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

View File

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

View File

@@ -0,0 +1,35 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json",
"rushVersion": "5.76.1",
"pnpmVersion": "6.7.1",
"pnpmOptions": {
"useWorkspaces": true
},
"nodeSupportedVersionRange": ">=12.13.0 <13.0.0 || >=14.15.0 <15.0.0 || >=16.13.0 <17.0.0",
"gitPolicy": {},
"repository": {},
"eventHooks": {
"preRushInstall": [],
"postRushInstall": [],
"preRushBuild": [],
"postRushBuild": []
},
"variants": [],
"projects": [
{
"packageName": "my-app"
},
{
"packageName": "my-second-app",
"projectFolder": "apps/my-second-app"
}
]
}

View File

@@ -172,6 +172,20 @@ describe('DetectorFilesystem', () => {
hasPathSpy.mock.calls.length = 0;
expect(await fs.hasPath('packages/app1/package.json')).toBe(true);
expect(hasPathSpy).not.toHaveBeenCalled();
expect(
await fs.readdir('packages/app1', { potentialFiles: ['vercel.json'] })
).toEqual([
{
name: 'package.json',
path: 'packages/app1/package.json',
type: 'file',
},
]);
hasPathSpy.mock.calls.length = 0;
expect(await fs.hasPath('packages/app1/vercel.json')).toBe(false);
expect(hasPathSpy).not.toHaveBeenCalled();
});
it('should be able to write files', async () => {

View File

@@ -18,7 +18,11 @@ describe.each<[string, string[]]>([
['42-npm-workspace-with-nx', ['/apps/app-one', '/apps/app-two']],
['43-nx-json-misshaped', []],
['44-nx-json-string', []],
])('`getWorkspacesPackagePaths()`', (fixturePath, packagePaths) => {
['40-rush-monorepo', ['/apps/my-app', '/apps/my-second-app']],
['41-rush-monorepo-empty', []],
['42-rush-json-invalid', []],
['45-rush-no-project-folder', ['/apps/my-second-app']],
])('`getWorkspacePackagePaths()`', (fixturePath, packagePaths) => {
const testName =
packagePaths.length > 0
? `should detect ${packagePaths.join()} package${

View File

@@ -46,7 +46,6 @@ interface Analyzed {
found?: boolean;
packageName: string;
functionName: string;
watch: string[];
}
interface PortInfo {
@@ -498,18 +497,8 @@ export async function build({
environment: {},
});
const watch = parsedAnalyzed.watch;
let watchSub: string[] = [];
// if `entrypoint` located in subdirectory
// we will need to concat it with return watch array
if (entrypointArr.length > 1) {
entrypointArr.pop();
watchSub = parsedAnalyzed.watch.map(file => join(...entrypointArr, file));
}
return {
output: lambda,
watch: watch.concat(watchSub),
};
} catch (error) {
debug('Go Builder Error: ' + error);

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/go",
"version": "2.2.7",
"version": "2.2.11",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
@@ -35,7 +35,7 @@
"@types/jest": "28.1.6",
"@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0",
"@vercel/build-utils": "5.4.4",
"@vercel/build-utils": "5.5.3",
"@vercel/ncc": "0.24.0",
"async-retry": "1.3.1",
"execa": "^1.0.0",

View File

@@ -0,0 +1,10 @@
package handler
import (
"fmt"
"net/http"
)
func Handler2(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "from one.go")
}

View File

@@ -0,0 +1,10 @@
package handler
import (
"fmt"
"net/http"
)
func Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "from two.go")
}

View File

@@ -0,0 +1,3 @@
module handler
go 1.16

View File

@@ -0,0 +1,12 @@
{
"probes": [
{
"path": "/api/one",
"mustContain": "from one.go"
},
{
"path": "/api/two",
"mustContain": "from two.go"
}
]
}

View File

@@ -2,7 +2,6 @@ package main
import (
"encoding/json"
"flag"
"fmt"
"go/ast"
"go/parser"
@@ -104,84 +103,6 @@ func main() {
if err != nil {
log.Fatal(err)
}
se := string(rf)
var files []string
var relatedFiles []string
// Add entrypoint to watchlist
relFileName, err := filepath.Rel(filepath.Dir(fileName), fileName)
if err != nil {
log.Fatal(err)
}
relatedFiles = append(relatedFiles, relFileName)
// looking for all go files that have export func
// using in entrypoint
err = filepath.Walk(filepath.Dir(fileName), visit(&files))
if err != nil {
log.Fatal(err)
}
// looking related packages
var modPath string
flag.StringVar(&modPath, "modpath", "", "module path")
flag.Parse()
if len(modPath) > 1 {
err = filepath.Walk(modPath, visit(&files))
if err != nil {
log.Fatal(err)
}
}
for _, file := range files {
absFileName, _ := filepath.Abs(fileName)
absFile, _ := filepath.Abs(file)
// if it isn't entrypoint
if absFileName != absFile {
// find all export structs and functions
pf := parse(file)
var exportedDecl []string
ast.Inspect(pf, func(n ast.Node) bool {
switch t := n.(type) {
case *ast.FuncDecl:
if t.Name.IsExported() {
exportedDecl = append(exportedDecl, t.Name.Name)
}
// find variable declarations
case *ast.TypeSpec:
// which are public
if t.Name.IsExported() {
switch t.Type.(type) {
// and are interfaces
case *ast.StructType:
exportedDecl = append(exportedDecl, t.Name.Name)
}
}
}
return true
})
for _, ed := range exportedDecl {
if strings.Contains(se, ed) {
// find relative path of related file
var basePath string
if modPath == "" {
basePath = filepath.Dir(fileName)
} else {
basePath = modPath
}
rel, err := filepath.Rel(basePath, file)
if err != nil {
log.Fatal(err)
}
relatedFiles = append(relatedFiles, rel)
}
}
}
}
parsed := parse(fileName)
offset := parsed.Pos()
@@ -207,7 +128,6 @@ func main() {
analyzed := analyze{
PackageName: parsed.Name.Name,
FuncName: fn.Name.Name,
Watch: unique(relatedFiles),
}
analyzedJSON, _ := json.Marshal(analyzed)
fmt.Print(string(analyzedJSON))
@@ -229,7 +149,6 @@ func main() {
analyzed := analyze{
PackageName: parsed.Name.Name,
FuncName: fn.Name.Name,
Watch: unique(relatedFiles),
}
analyzedJSON, _ := json.Marshal(analyzed)
fmt.Print(string(analyzedJSON))

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/hydrogen",
"version": "0.0.20",
"version": "0.0.24",
"license": "MIT",
"main": "./dist/index.js",
"homepage": "https://vercel.com/docs",
@@ -21,7 +21,7 @@
"devDependencies": {
"@types/jest": "27.5.1",
"@types/node": "*",
"@vercel/build-utils": "5.4.4",
"@vercel/build-utils": "5.5.3",
"typescript": "4.6.4"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "3.1.27",
"version": "3.2.2",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -44,7 +44,7 @@
"@types/semver": "6.0.0",
"@types/text-table": "0.2.1",
"@types/webpack-sources": "3.2.0",
"@vercel/build-utils": "5.4.4",
"@vercel/build-utils": "5.5.3",
"@vercel/nft": "0.22.1",
"@vercel/routing-utils": "2.0.2",
"async-sema": "3.0.1",

View File

@@ -1088,10 +1088,15 @@ export const build: BuildV2 = async ({
'pages'
);
let appDir: string | null = null;
const appPathRoutesManifest = await readJSON(
path.join(entryPath, outputDirectory, 'app-path-routes-manifest.json')
).catch(() => null);
if (appPathRoutesManifest) {
appDir = path.join(pagesDir, '../app');
}
const { pages, appPaths: lambdaAppPaths } = await getServerlessPages({
pagesDir,
entryPath,
@@ -2032,9 +2037,10 @@ export const build: BuildV2 = async ({
console.timeEnd(allLambdasLabel);
}
const prerenderRoute = onPrerenderRoute({
appDir,
pagesDir,
hasPages404,
static404Page,
pagesDir,
pageLambdaMap,
lambdas,
isServerMode,
@@ -2042,6 +2048,7 @@ export const build: BuildV2 = async ({
entryDirectory,
routesManifest,
prerenderManifest,
appPathRoutesManifest,
isSharedLambdas,
canUsePreviewMode,
});

View File

@@ -633,7 +633,7 @@ export async function serverBuild({
const curPagesDir = isAppPath && appDir ? appDir : pagesDir;
const pageDir = path.dirname(path.join(curPagesDir, originalPagePath));
const normalizedBaseDir = `${baseDir}${
baseDir.endsWith('/') ? '' : '/'
baseDir.endsWith(path.sep) ? '' : path.sep
}`;
files.forEach((file: string) => {
const absolutePath = path.join(pageDir, file);
@@ -881,6 +881,7 @@ export async function serverBuild({
}
const prerenderRoute = onPrerenderRoute({
appDir,
pagesDir,
pageLambdaMap: {},
lambdas,
@@ -888,6 +889,7 @@ export async function serverBuild({
entryDirectory,
routesManifest,
prerenderManifest,
appPathRoutesManifest,
isServerMode: true,
isSharedLambdas: false,
canUsePreviewMode,
@@ -1005,7 +1007,7 @@ export async function serverBuild({
currentRouteSrc.length - 1
)}${
currentRouteSrc[currentRouteSrc.length - 2] === '(' ? '' : '|'
}${route})`;
}${route}/?)`;
if (isLastRoute) {
pushRoute(currentRouteSrc);
@@ -1129,6 +1131,23 @@ export async function serverBuild({
});
}
if (appPathRoutesManifest) {
// create .rsc variant for app lambdas and edge functions
// to match prerenders so we can route the same when the
// __rsc__ header is present
const edgeFunctions = middleware.edgeFunctions;
for (let route of Object.values(appPathRoutesManifest)) {
route = path.posix.join('./', route === '/' ? '/index' : route);
if (lambdas[route]) {
lambdas[`${route}.rsc`] = lambdas[route];
} else if (edgeFunctions[route]) {
edgeFunctions[`${route}.rsc`] = edgeFunctions[route];
}
}
}
return {
wildcard: wildcardConfig,
images:
@@ -1324,6 +1343,12 @@ export async function serverBuild({
.join('|')})?[/]?404/?`,
status: 404,
continue: true,
missing: [
{
type: 'header',
key: 'x-prerender-revalidate',
},
],
},
]
: [
@@ -1331,6 +1356,12 @@ export async function serverBuild({
src: path.posix.join('/', entryDirectory, '404/?'),
status: 404,
continue: true,
missing: [
{
type: 'header',
key: 'x-prerender-revalidate',
},
],
},
]),
@@ -1367,6 +1398,22 @@ export async function serverBuild({
// to prevent a local/deploy mismatch
...(!isCorrectMiddlewareOrder ? middleware.staticRoutes : []),
...(appDir
? [
{
src: `^${path.posix.join('/', entryDirectory, '/(.*)$')}`,
has: [
{
type: 'header',
key: '__rsc__',
},
],
dest: path.posix.join('/', entryDirectory, '/$1.rsc'),
check: true,
},
]
: []),
// Next.js page lambdas, `static/` folder, reserved assets, and `public/`
// folder
{ handle: 'filesystem' },

View File

@@ -1660,10 +1660,12 @@ export const onPrerenderRouteInitial = (
};
type OnPrerenderRouteArgs = {
appDir: string | null;
pagesDir: string;
static404Page?: string;
hasPages404: boolean;
entryDirectory: string;
appPathRoutesManifest?: Record<string, string>;
prerenderManifest: NextPrerenderedRoutes;
isSharedLambdas: boolean;
isServerMode: boolean;
@@ -1694,8 +1696,8 @@ export const onPrerenderRoute =
}
) => {
const {
appDir,
pagesDir,
hasPages404,
static404Page,
entryDirectory,
prerenderManifest,
@@ -1760,44 +1762,6 @@ export const onPrerenderRoute =
const isNotFound = prerenderManifest.notFoundRoutes.includes(routeKey);
const htmlFsRef =
isBlocking || (isNotFound && !static404Page)
? // Blocking pages do not have an HTML fallback
null
: new FileFsRef({
fsPath: path.join(
pagesDir,
isFallback
? // Fallback pages have a special file.
addLocaleOrDefault(
prerenderManifest.fallbackRoutes[routeKey].fallback,
routesManifest,
locale
)
: // Otherwise, the route itself should exist as a static HTML
// file.
`${
isOmitted || isNotFound
? addLocaleOrDefault('/404', routesManifest, locale)
: routeFileNoExt
}.html`
),
});
const jsonFsRef =
// JSON data does not exist for fallback or blocking pages
isFallback || isBlocking || (isNotFound && !static404Page)
? null
: new FileFsRef({
fsPath: path.join(
pagesDir,
`${
isOmitted || isNotFound
? addLocaleOrDefault('/404.html', routesManifest, locale)
: routeFileNoExt + '.json'
}`
),
});
let initialRevalidate: false | number;
let srcRoute: string | null;
let dataRoute: string;
@@ -1826,6 +1790,52 @@ export const onPrerenderRoute =
({ initialRevalidate, srcRoute, dataRoute } = pr);
}
let isAppPathRoute = false;
// TODO: leverage manifest to determine app paths more accurately
if (appDir && srcRoute && dataRoute.endsWith('.rsc')) {
isAppPathRoute = true;
}
const htmlFsRef =
isBlocking || (isNotFound && !static404Page)
? // Blocking pages do not have an HTML fallback
null
: new FileFsRef({
fsPath: path.join(
isAppPathRoute && appDir ? appDir : pagesDir,
isFallback
? // Fallback pages have a special file.
addLocaleOrDefault(
prerenderManifest.fallbackRoutes[routeKey].fallback,
routesManifest,
locale
)
: // Otherwise, the route itself should exist as a static HTML
// file.
`${
isOmitted || isNotFound
? addLocaleOrDefault('/404', routesManifest, locale)
: routeFileNoExt
}.html`
),
});
const jsonFsRef =
// JSON data does not exist for fallback or blocking pages
isFallback || isBlocking || (isNotFound && !static404Page)
? null
: new FileFsRef({
fsPath: path.join(
isAppPathRoute && appDir ? appDir : pagesDir,
`${
isOmitted || isNotFound
? addLocaleOrDefault('/404.html', routesManifest, locale)
: isAppPathRoute
? dataRoute
: routeFileNoExt + '.json'
}`
),
});
const outputPathPage = normalizeIndexOutput(
path.posix.join(entryDirectory, routeFileNoExt),
isServerMode
@@ -1885,11 +1895,9 @@ export const onPrerenderRoute =
});
}
// If revalidate isn't enabled we force the /404 route to be static
// to match next start behavior otherwise getStaticProps would be
// recalled for each 404 URL path since Prerender is cached based
// on the URL path
if (!canUsePreviewMode || (hasPages404 && routeKey === '/404')) {
// if preview mode/On-Demand ISR can't be leveraged
// we can output pure static outputs instead of prerenders
if (!canUsePreviewMode) {
htmlFsRef.contentType = htmlContentType;
prerenders[outputPathPage] = htmlFsRef;
prerenders[outputPathData] = jsonFsRef;
@@ -2191,6 +2199,7 @@ interface BaseEdgeFunctionInfo {
page: string;
wasm?: { filePath: string; name: string }[];
assets?: { filePath: string; name: string }[];
regions?: 'auto' | string[] | 'all' | 'default';
}
interface EdgeFunctionInfoV1 extends BaseEdgeFunctionInfo {
@@ -2218,7 +2227,11 @@ export async function getMiddlewareBundle({
prerenderBypassToken: string;
routesManifest: RoutesManifest;
isCorrectMiddlewareOrder: boolean;
}) {
}): Promise<{
staticRoutes: Route[];
dynamicRouteMap: Map<string, RouteWithSrc>;
edgeFunctions: Record<string, EdgeFunction>;
}> {
const middlewareManifest = await getMiddlewareManifest(
entryPath,
outputDirectory
@@ -2326,6 +2339,7 @@ export async function getMiddlewareBundle({
...wasmFiles,
...assetFiles,
},
regions: edgeFunction.regions,
entrypoint: 'index.js',
envVarsInUse: edgeFunction.env,
assets: (edgeFunction.assets ?? []).map(({ name }) => {
@@ -2409,7 +2423,6 @@ export async function getMiddlewareBundle({
}
}
}
return source;
}

View File

@@ -8,7 +8,3 @@ export default function Root({ children }) {
</html>
);
}
export const config = {
revalidate: 0,
};

View File

@@ -11,16 +11,27 @@
"status": 200,
"mustContain": "hello from app/dashboard"
},
{
"path": "/dashboard",
"status": 200,
"headers": {
"__rsc__": "1"
},
"mustContain": "M1:{",
"mustNotContain": "<html"
},
{
"path": "/dashboard/another",
"status": 200,
"mustContain": "hello from newroot/dashboard/another"
},
{
"path": "/dashboard/deployments/123",
"status": 200,
"mustContain": "hello from app/dashboard/deployments/[id]. ID is: <!-- -->123"
},
// TODO: uncomment after this is fixed upstream
// x-ref: https://vercel.slack.com/archives/C035J346QQL/p1663820032810519?thread_ts=1663775935.504379&cid=C035J346QQL
// {
// "path": "/dashboard/deployments/123",
// "status": 200,
// "mustContain": "hello from app/dashboard/deployments/[id]. ID is: <!-- -->123"
// },
{
"path": "/",
"status": 200,
@@ -31,11 +42,12 @@
"status": 200,
"mustContain": "hello from pages/blog/[slug]"
},
{
"path": "/dynamic/category-1/id-1",
"status": 200,
"mustContain": "{&quot;category&quot;:&quot;category-1&quot;,&quot;id&quot;:&quot;id-1&quot;}"
},
// TODO: uncomment after this is fixed upstream
// {
// "path": "/dynamic/category-1/id-1",
// "status": 200,
// "mustContain": "{&quot;category&quot;:&quot;category-1&quot;,&quot;id&quot;:&quot;id-1&quot;}"
// },
{
"path": "/dashboard/changelog",
"status": 200,

View File

@@ -1,7 +1,7 @@
/* eslint-env jest */
const path = require('path');
const cheerio = require('cheerio');
const { deployAndTest, check, waitFor } = require('../../utils');
const { deployAndTest, check } = require('../../utils');
const fetch = require('../../../../../test/lib/deployment/fetch-retry');
async function checkForChange(url, initialValue, getNewValue) {
@@ -141,4 +141,30 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
expect(preRevalidateRandom).toBeDefined();
expect(preRevalidateRandomData).toBeDefined();
});
it('should revalidate 404 page itself correctly', async () => {
const initial404 = await fetch(`${ctx.deploymentUrl}/404`);
const initial404Html = await initial404.text();
const initial404Props = JSON.parse(
cheerio.load(initial404Html)('#props').text()
);
expect(initial404.status).toBe(404);
expect(initial404Props.is404).toBe(true);
const revalidateRes = await fetch(
`${ctx.deploymentUrl}/api/revalidate?urlPath=/404`
);
expect(revalidateRes.status).toBe(200);
expect(await revalidateRes.json()).toEqual({ revalidated: true });
await check(async () => {
const res = await fetch(`${ctx.deploymentUrl}/404`);
const resHtml = await res.text();
const resProps = JSON.parse(cheerio.load(resHtml)('#props').text());
expect(res.status).toBe(404);
expect(resProps.is404).toBe(true);
expect(resProps.time).not.toEqual(initial404Props.time);
return 'success';
}, 'success');
});
});

View File

@@ -1,3 +1,18 @@
export default function Page() {
return <p>custom 404</p>;
export default function Page(props) {
return (
<>
<p>custom 404</p>
<p id="props">{JSON.stringify(props)}</p>
</>
);
}
export function getStaticProps() {
console.log('pages/404 getStaticProps');
return {
props: {
is404: true,
time: Date.now(),
},
};
}

View File

@@ -0,0 +1,21 @@
export default function Page(props) {
return (
<>
<p>/ssg/[slug]</p>
<p>{JSON.stringify(props)}</p>
</>
);
}
export function getStaticProps() {
return {
notFound: true,
};
}
export function getStaticPaths() {
return {
paths: ['/ssg/first', '/ssg/second'],
fallback: 'blocking',
};
}

View File

@@ -1,7 +1,20 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@vercel/next" }],
"probes": [
{
"path": "/ssg/first/",
"status": 404,
"mustContain": "This page could not be found"
},
{
"path": "/ssg/second/",
"status": 404,
"mustContain": "This page could not be found"
},
{
"path": "/ssg/third/",
"status": 404,
"mustContain": "This page could not be found"
},
{ "path": "/foo/", "status": 200, "mustContain": "foo page" },
{
"fetchOptions": { "redirect": "manual" },

View File

@@ -24,14 +24,26 @@ it('should build with app-dir correctly', async () => {
expect(buildResult.output['dashboard/another']).toBeDefined();
expect(buildResult.output['dashboard/changelog']).toBeDefined();
expect(buildResult.output['dashboard/deployments/[id]']).toBeDefined();
// prefixed static generation output with `/app` under dist server files
expect(buildResult.output['dashboard'].type).toBe('FileFsRef');
expect(buildResult.output['dashboard'].fsPath).toMatch(
/server\/app\/dashboard\.html$/
);
expect(buildResult.output['dashboard.rsc'].type).toBe('FileFsRef');
expect(buildResult.output['dashboard.rsc'].fsPath).toMatch(
/server\/app\/dashboard\.rsc$/
);
});
it('should build with app-dir in edg runtime correctly', async () => {
// TODO: re-enable after edge build failure is fixed in Next.js
// Disabled Oct, 1st 2022
// eslint-disable-next-line jest/no-disabled-tests
it.skip('should build with app-dir in edge runtime correctly', async () => {
const { buildResult } = await runBuildLambda(
path.join(__dirname, '../fixtures/00-app-dir-edge')
);
console.log('buildResult', buildResult);
const edgeFunctions = new Set();
for (const key of Object.keys(buildResult.output)) {
@@ -306,9 +318,9 @@ it('Should build the gip-gsp-404 example', async () => {
expect(routes[handleErrorIdx + 1].dest).toBe('/404');
expect(routes[handleErrorIdx + 1].headers).toBe(undefined);
expect(output['404']).toBeDefined();
expect(output['404'].type).toBe('FileFsRef');
expect(output['404'].type).toBe('Prerender');
expect(output['_next/data/testing-build-id/404.json']).toBeDefined();
expect(output['_next/data/testing-build-id/404.json'].type).toBe('FileFsRef');
expect(output['_next/data/testing-build-id/404.json'].type).toBe('Prerender');
const filePaths = Object.keys(output);
const serverlessError = filePaths.some(filePath => filePath.match(/_error/));
const hasUnderScoreAppStaticFile = filePaths.some(filePath =>

View File

@@ -0,0 +1,3 @@
{
"foo": "bar"
}

View File

@@ -1,19 +1,18 @@
const fs = require('fs-extra');
const ms = require('ms');
const path = require('path');
const { build } = require('../../../../src');
const { build } = require('../../../../dist');
const { FileFsRef } = require('@vercel/build-utils');
jest.setTimeout(ms('6m'));
describe(`${__dirname.split(path.sep).pop()}`, () => {
afterEach(() => fs.remove(path.join(__dirname, 'yarn.lock')));
it('should normalize routes in build results output', async () => {
const files = [
'index.test.js',
'next.config.js',
'package.json',
'data/strings.json',
'pages/foo/bar/index.js',
'pages/foo/index.js',
'pages/index.js',
@@ -24,7 +23,7 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
return filesMap;
}, {});
const { output } = await build({
const { output, routes } = await build({
config: {},
entrypoint: 'package.json',
files,
@@ -40,9 +39,26 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
for (const page of pages) {
expect(output).toHaveProperty(page);
expect(path.resolve(output[page].fsPath)).toEqual(
path.join(pagesDir, `${page}.html`)
);
if (page === 'index') {
const { files, type } = output[page];
expect(type).toEqual('Lambda');
expect(files).toHaveProperty([path.join('data', 'strings.json')]);
} else {
expect(path.resolve(output[page].fsPath)).toEqual(
path.join(pagesDir, `${page}.html`)
);
}
}
for (const route of routes) {
if (typeof route.src === 'string') {
// src must start with a forward slash (or caret if a regex)
expect(route.src).toMatch(/^[/^]/);
}
if (typeof route.dest === 'string') {
// dest can be `/400` or `/.*` or `$0`, but not start with \
expect(route.dest).not.toMatch(/^\\/);
}
}
});
});

View File

@@ -1,7 +1,18 @@
export default function Page() {
import fs from 'fs';
import path from 'path';
export default function Page({ foo }) {
return (
<>
<p>hello from pages</p>
<p>hello from pages {foo}</p>
</>
);
}
export async function getServerSideProps() {
const dataFile = path.join(process.cwd(), 'data', 'strings.json');
const strings = JSON.parse(fs.readFileSync(dataFile));
return {
props: strings,
};
}

View File

@@ -0,0 +1,184 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@next/env@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/env/-/env-12.3.1.tgz#18266bd92de3b4aa4037b1927aa59e6f11879260"
integrity sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==
"@next/swc-android-arm-eabi@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.1.tgz#b15ce8ad376102a3b8c0f3c017dde050a22bb1a3"
integrity sha512-i+BvKA8tB//srVPPQxIQN5lvfROcfv4OB23/L1nXznP+N/TyKL8lql3l7oo2LNhnH66zWhfoemg3Q4VJZSruzQ==
"@next/swc-android-arm64@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.3.1.tgz#85d205f568a790a137cb3c3f720d961a2436ac9c"
integrity sha512-CmgU2ZNyBP0rkugOOqLnjl3+eRpXBzB/I2sjwcGZ7/Z6RcUJXK5Evz+N0ucOxqE4cZ3gkTeXtSzRrMK2mGYV8Q==
"@next/swc-darwin-arm64@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.1.tgz#b105457d6760a7916b27e46c97cb1a40547114ae"
integrity sha512-hT/EBGNcu0ITiuWDYU9ur57Oa4LybD5DOQp4f22T6zLfpoBMfBibPtR8XktXmOyFHrL/6FC2p9ojdLZhWhvBHg==
"@next/swc-darwin-x64@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.1.tgz#6947b39082271378896b095b6696a7791c6e32b1"
integrity sha512-9S6EVueCVCyGf2vuiLiGEHZCJcPAxglyckTZcEwLdJwozLqN0gtS0Eq0bQlGS3dH49Py/rQYpZ3KVWZ9BUf/WA==
"@next/swc-freebsd-x64@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.1.tgz#2b6c36a4d84aae8b0ea0e0da9bafc696ae27085a"
integrity sha512-qcuUQkaBZWqzM0F1N4AkAh88lLzzpfE6ImOcI1P6YeyJSsBmpBIV8o70zV+Wxpc26yV9vpzb+e5gCyxNjKJg5Q==
"@next/swc-linux-arm-gnueabihf@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.1.tgz#6e421c44285cfedac1f4631d5de330dd60b86298"
integrity sha512-diL9MSYrEI5nY2wc/h/DBewEDUzr/DqBjIgHJ3RUNtETAOB3spMNHvJk2XKUDjnQuluLmFMloet9tpEqU2TT9w==
"@next/swc-linux-arm64-gnu@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.1.tgz#8863f08a81f422f910af126159d2cbb9552ef717"
integrity sha512-o/xB2nztoaC7jnXU3Q36vGgOolJpsGG8ETNjxM1VAPxRwM7FyGCPHOMk1XavG88QZSQf+1r+POBW0tLxQOJ9DQ==
"@next/swc-linux-arm64-musl@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.1.tgz#0038f07cf0b259d70ae0c80890d826dfc775d9f3"
integrity sha512-2WEasRxJzgAmP43glFNhADpe8zB7kJofhEAVNbDJZANp+H4+wq+/cW1CdDi8DqjkShPEA6/ejJw+xnEyDID2jg==
"@next/swc-linux-x64-gnu@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.1.tgz#c66468f5e8181ffb096c537f0dbfb589baa6a9c1"
integrity sha512-JWEaMyvNrXuM3dyy9Pp5cFPuSSvG82+yABqsWugjWlvfmnlnx9HOQZY23bFq3cNghy5V/t0iPb6cffzRWylgsA==
"@next/swc-linux-x64-musl@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.1.tgz#c6269f3e96ac0395bc722ad97ce410ea5101d305"
integrity sha512-xoEWQQ71waWc4BZcOjmatuvPUXKTv6MbIFzpm4LFeCHsg2iwai0ILmNXf81rJR+L1Wb9ifEke2sQpZSPNz1Iyg==
"@next/swc-win32-arm64-msvc@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.1.tgz#83c639ee969cee36ce247c3abd1d9df97b5ecade"
integrity sha512-hswVFYQYIeGHE2JYaBVtvqmBQ1CppplQbZJS/JgrVI3x2CurNhEkmds/yqvDONfwfbttTtH4+q9Dzf/WVl3Opw==
"@next/swc-win32-ia32-msvc@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.1.tgz#52995748b92aa8ad053440301bc2c0d9fbcf27c2"
integrity sha512-Kny5JBehkTbKPmqulr5i+iKntO5YMP+bVM8Hf8UAmjSMVo3wehyLVc9IZkNmcbxi+vwETnQvJaT5ynYBkJ9dWA==
"@next/swc-win32-x64-msvc@12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.1.tgz#27d71a95247a9eaee03d47adee7e3bd594514136"
integrity sha512-W1ijvzzg+kPEX6LAc+50EYYSEo0FVu7dmTE+t+DM4iOLqgGHoW9uYSz9wCVdkXOEEMP9xhXfGpcSxsfDucyPkA==
"@swc/helpers@0.4.11":
version "0.4.11"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.11.tgz#db23a376761b3d31c26502122f349a21b592c8de"
integrity sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==
dependencies:
tslib "^2.4.0"
caniuse-lite@^1.0.30001406:
version "1.0.30001409"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001409.tgz#6135da9dcab34cd9761d9cdb12a68e6740c5e96e"
integrity sha512-V0mnJ5dwarmhYv8/MzhJ//aW68UpvnQBXv8lJ2QUsvn2pHcmAuNtu8hQEDz37XnA1iE+lRR9CIfGWWpgJ5QedQ==
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
nanoid@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
next@latest:
version "12.3.1"
resolved "https://registry.yarnpkg.com/next/-/next-12.3.1.tgz#127b825ad2207faf869b33393ec8c75fe61e50f1"
integrity sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==
dependencies:
"@next/env" "12.3.1"
"@swc/helpers" "0.4.11"
caniuse-lite "^1.0.30001406"
postcss "8.4.14"
styled-jsx "5.0.7"
use-sync-external-store "1.2.0"
optionalDependencies:
"@next/swc-android-arm-eabi" "12.3.1"
"@next/swc-android-arm64" "12.3.1"
"@next/swc-darwin-arm64" "12.3.1"
"@next/swc-darwin-x64" "12.3.1"
"@next/swc-freebsd-x64" "12.3.1"
"@next/swc-linux-arm-gnueabihf" "12.3.1"
"@next/swc-linux-arm64-gnu" "12.3.1"
"@next/swc-linux-arm64-musl" "12.3.1"
"@next/swc-linux-x64-gnu" "12.3.1"
"@next/swc-linux-x64-musl" "12.3.1"
"@next/swc-win32-arm64-msvc" "12.3.1"
"@next/swc-win32-ia32-msvc" "12.3.1"
"@next/swc-win32-x64-msvc" "12.3.1"
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
postcss@8.4.14:
version "8.4.14"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf"
integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
dependencies:
nanoid "^3.3.4"
picocolors "^1.0.0"
source-map-js "^1.0.2"
react-dom@latest:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.23.0"
react@latest:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
dependencies:
loose-envify "^1.1.0"
scheduler@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
dependencies:
loose-envify "^1.1.0"
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
styled-jsx@5.0.7:
version "5.0.7"
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.7.tgz#be44afc53771b983769ac654d355ca8d019dff48"
integrity sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==
tslib@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
use-sync-external-store@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==

View File

@@ -3,6 +3,13 @@ const fs = require('fs-extra');
const execa = require('execa');
const { join } = require('path');
async function copyToDist(sourcePath, outDir) {
return fs.copyFile(
join(__dirname, sourcePath),
join(outDir, 'edge-functions/edge-handler-template.js')
);
}
async function main() {
const srcDir = join(__dirname, 'src');
const outDir = join(__dirname, 'dist');
@@ -50,6 +57,8 @@ async function main() {
join(outDir, 'index.d.ts'),
join(__dirname, 'test/fixtures/15-helpers/ts/types.d.ts')
);
await copyToDist('src/edge-functions/edge-handler-template.js', outDir);
}
main().catch(err => {

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "2.5.16",
"version": "2.5.21",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -31,7 +31,7 @@
"dependencies": {
"@edge-runtime/vm": "1.1.0-beta.32",
"@types/node": "*",
"@vercel/build-utils": "5.4.4",
"@vercel/build-utils": "5.5.3",
"@vercel/node-bridge": "3.0.0",
"@vercel/static-config": "2.0.3",
"edge-runtime": "1.1.0-beta.32",

View File

@@ -70,30 +70,13 @@ if (!process.env.VERCEL_DEV_IS_ESM) {
}
import { createServer, Server, IncomingMessage, ServerResponse } from 'http';
import { Readable } from 'stream';
import type { Bridge } from '@vercel/node-bridge/bridge';
import { getVercelLauncher } from '@vercel/node-bridge/launcher.js';
import { VercelProxyResponse } from '@vercel/node-bridge/types';
import { Config, streamToBuffer, debug } from '@vercel/build-utils';
import exitHook from 'exit-hook';
import { EdgeRuntime, runServer } from 'edge-runtime';
import type { EdgeContext } from '@edge-runtime/vm';
import { Config } from '@vercel/build-utils';
import { getConfig } from '@vercel/static-config';
import { Project } from 'ts-morph';
import esbuild from 'esbuild';
import fetch from 'node-fetch';
import { createEdgeWasmPlugin, WasmAssets } from './edge-wasm-plugin';
function logError(error: Error) {
console.error(error.message);
if (error.stack) {
// only show the stack trace if debug is enabled
// because it points to internals, not user code
const errorPrefixLength = 'Error: '.length;
const errorMessageLength = errorPrefixLength + error.message.length;
debug(error.stack.substring(errorMessageLength + 1));
}
}
import { logError } from './utils';
import { createEdgeEventHandler } from './edge-functions/edge-handler';
import { createServerlessEventHandler } from './serverless-functions/serverless-handler';
function listen(server: Server, port: number, host: string): Promise<void> {
return new Promise(resolve => {
@@ -103,241 +86,6 @@ function listen(server: Server, port: number, host: string): Promise<void> {
});
}
async function createServerlessEventHandler(
entrypoint: string,
options: { shouldAddHelpers: boolean }
): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>> {
const launcher = getVercelLauncher({
entrypointPath: entrypoint,
helpersPath: './helpers.js',
shouldAddHelpers: options.shouldAddHelpers,
useRequire,
// not used
bridgePath: '',
sourcemapSupportPath: '',
});
const bridge: Bridge = launcher();
return async function (request: IncomingMessage) {
const body = await rawBody(request);
const event = {
Action: 'Invoke',
body: JSON.stringify({
method: request.method,
path: request.url,
headers: request.headers,
encoding: 'base64',
body: body.toString('base64'),
}),
};
return bridge.launcher(event, {
callbackWaitsForEmptyEventLoop: false,
});
};
}
async function serializeRequest(message: IncomingMessage) {
const bodyBuffer = await streamToBuffer(message);
const body = bodyBuffer.toString('base64');
return JSON.stringify({
url: message.url,
method: message.method,
headers: message.headers,
body,
});
}
async function compileUserCode(
entrypointPath: string,
entrypointLabel: string,
isMiddleware: boolean
): Promise<undefined | { userCode: string; wasmAssets: WasmAssets }> {
const { wasmAssets, plugin: edgeWasmPlugin } = createEdgeWasmPlugin();
try {
const result = await esbuild.build({
platform: 'node',
target: 'node14',
sourcemap: 'inline',
bundle: true,
plugins: [edgeWasmPlugin],
entryPoints: [entrypointPath],
write: false, // operate in memory
format: 'cjs',
});
const compiledFile = result.outputFiles?.[0];
if (!compiledFile) {
throw new Error(
`Compilation of ${entrypointLabel} produced no output files.`
);
}
const userCode = `
${compiledFile.text};
const isMiddleware = ${isMiddleware};
addEventListener('fetch', async (event) => {
try {
let serializedRequest = await event.request.text();
let requestDetails = JSON.parse(serializedRequest);
let body;
if (requestDetails.method !== 'GET' && requestDetails.method !== 'HEAD') {
body = Uint8Array.from(atob(requestDetails.body), c => c.charCodeAt(0));
}
let requestUrl = requestDetails.headers['x-forwarded-proto'] + '://' + requestDetails.headers['x-forwarded-host'] + requestDetails.url;
let request = new Request(requestUrl, {
headers: requestDetails.headers,
method: requestDetails.method,
body: body
});
event.request = request;
let edgeHandler = module.exports.default;
if (!edgeHandler) {
throw new Error('No default export was found. Add a default export to handle requests. Learn more: https://vercel.link/creating-edge-middleware');
}
let response = await edgeHandler(event.request, event);
if (!response) {
if (isMiddleware) {
// allow empty responses to pass through
response = new Response(null, {
headers: {
'x-middleware-next': '1',
},
});
} else {
throw new Error('Edge Function "${entrypointLabel}" did not return a response.');
}
}
return event.respondWith(response);
} catch (error) {
// we can't easily show a meaningful stack trace
// so, stick to just the error message for now
const msg = error.cause
? (error.message + ': ' + (error.cause.message || error.cause))
: error.message;
event.respondWith(new Response(msg, {
status: 500,
headers: {
'x-vercel-failed': 'edge-wrapper'
}
}));
}
})`;
return { userCode, wasmAssets };
} catch (error) {
// We can't easily show a meaningful stack trace from ncc -> edge-runtime.
// So, stick with just the message for now.
console.error(`Failed to compile user code for edge runtime.`);
logError(error);
return undefined;
}
}
async function createEdgeRuntime(params?: {
userCode: string;
wasmAssets: WasmAssets;
}) {
try {
if (!params) {
return undefined;
}
const wasmBindings = await params.wasmAssets.getContext();
const edgeRuntime = new EdgeRuntime({
initialCode: params.userCode,
extend: (context: EdgeContext) => {
Object.assign(context, {
// This is required for esbuild wrapping logic to resolve
module: {},
// This is required for environment variable access.
// In production, env var access is provided by static analysis
// so that only the used values are available.
process: {
env: process.env,
},
// These are the global bindings for WebAssembly module
...wasmBindings,
});
return context;
},
});
const server = await runServer({ runtime: edgeRuntime });
exitHook(server.close);
return server;
} catch (error) {
// We can't easily show a meaningful stack trace from ncc -> edge-runtime.
// So, stick with just the message for now.
console.error('Failed to instantiate edge runtime.');
logError(error);
return undefined;
}
}
async function createEdgeEventHandler(
entrypointPath: string,
entrypointLabel: string,
isMiddleware: boolean
): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>> {
const userCode = await compileUserCode(
entrypointPath,
entrypointLabel,
isMiddleware
);
const server = await createEdgeRuntime(userCode);
return async function (request: IncomingMessage) {
if (!server) {
// this error state is already logged, but we have to wait until here to exit the process
// this matches the serverless function bridge launcher's behavior when
// an error is thrown in the function
process.exit(1);
}
const response = await fetch(server.url, {
redirect: 'manual',
method: 'post',
body: await serializeRequest(request),
});
const body = await response.text();
const isUserError =
response.headers.get('x-vercel-failed') === 'edge-wrapper';
if (isUserError && response.status >= 500) {
// this error was "unhandled" from the user code's perspective
console.log(`Unhandled rejection: ${body}`);
// this matches the serverless function bridge launcher's behavior when
// an error is thrown in the function
process.exit(1);
}
return {
statusCode: response.status,
headers: response.headers.raw(),
body,
encoding: 'utf8',
};
};
}
const validRuntimes = ['experimental-edge'];
function parseRuntime(
entrypoint: string,
@@ -376,7 +124,10 @@ async function createEventHandler(
);
}
return createServerlessEventHandler(entrypointPath, options);
return createServerlessEventHandler(entrypointPath, {
shouldAddHelpers: options.shouldAddHelpers,
useRequire,
});
}
let handleEvent: (request: IncomingMessage) => Promise<VercelProxyResponse>;
@@ -413,21 +164,6 @@ async function main() {
}
}
export function rawBody(readable: Readable): Promise<Buffer> {
return new Promise((resolve, reject) => {
let bytes = 0;
const chunks: Buffer[] = [];
readable.on('error', reject);
readable.on('data', chunk => {
chunks.push(chunk);
bytes += chunk.length;
});
readable.on('end', () => {
resolve(Buffer.concat(chunks, bytes));
});
});
}
export async function onDevRequest(
req: IncomingMessage,
res: ServerResponse

View File

@@ -0,0 +1,73 @@
// provided by the edge runtime:
/* global addEventListener Request Response atob */
// provided by our edge handler logic:
/* global IS_MIDDLEWARE ENTRYPOINT_LABEL */
function buildUrl(requestDetails) {
let proto = requestDetails.headers['x-forwarded-proto'];
let host = requestDetails.headers['x-forwarded-host'];
let path = requestDetails.url;
return `${proto}://${host}${path}`;
}
addEventListener('fetch', async event => {
try {
let serializedRequest = await event.request.text();
let requestDetails = JSON.parse(serializedRequest);
let body;
if (requestDetails.method !== 'GET' && requestDetails.method !== 'HEAD') {
body = Uint8Array.from(atob(requestDetails.body), c => c.charCodeAt(0));
}
let request = new Request(buildUrl(requestDetails), {
headers: requestDetails.headers,
method: requestDetails.method,
body: body,
});
event.request = request;
let edgeHandler = module.exports.default;
if (!edgeHandler) {
throw new Error(
'No default export was found. Add a default export to handle requests. Learn more: https://vercel.link/creating-edge-middleware'
);
}
let response = await edgeHandler(event.request, event);
if (!response) {
if (IS_MIDDLEWARE) {
// allow empty responses to pass through
response = new Response(null, {
headers: {
'x-middleware-next': '1',
},
});
} else {
throw new Error(
`Edge Function "${ENTRYPOINT_LABEL}" did not return a response.`
);
}
}
return event.respondWith(response);
} catch (error) {
// we can't easily show a meaningful stack trace
// so, stick to just the error message for now
const msg = error.cause
? error.message + ': ' + (error.cause.message || error.cause)
: error.message;
event.respondWith(
new Response(msg, {
status: 500,
headers: {
'x-vercel-failed': 'edge-wrapper',
},
})
);
}
});

View File

@@ -0,0 +1,182 @@
import { IncomingMessage } from 'http';
import { VercelProxyResponse } from '@vercel/node-bridge/types';
import { streamToBuffer } from '@vercel/build-utils';
import exitHook from 'exit-hook';
import { EdgeRuntime, runServer } from 'edge-runtime';
import type { EdgeContext } from '@edge-runtime/vm';
import esbuild from 'esbuild';
import fetch from 'node-fetch';
import { createEdgeWasmPlugin, WasmAssets } from './edge-wasm-plugin';
import { logError } from '../utils';
import { readFileSync } from 'fs';
const NODE_VERSION_MAJOR = process.version.match(/^v(\d+)\.\d+/)?.[1];
const NODE_VERSION_IDENTIFIER = `node${NODE_VERSION_MAJOR}`;
if (!NODE_VERSION_MAJOR) {
throw new Error(
`Unable to determine current node version: process.version=${process.version}`
);
}
const edgeHandlerTemplate = readFileSync(
`${__dirname}/edge-handler-template.js`
);
async function serializeRequest(message: IncomingMessage) {
const bodyBuffer = await streamToBuffer(message);
const body = bodyBuffer.toString('base64');
return JSON.stringify({
url: message.url,
method: message.method,
headers: message.headers,
body,
});
}
async function compileUserCode(
entrypointPath: string,
entrypointLabel: string,
isMiddleware: boolean
): Promise<undefined | { userCode: string; wasmAssets: WasmAssets }> {
const { wasmAssets, plugin: edgeWasmPlugin } = createEdgeWasmPlugin();
try {
const result = await esbuild.build({
// bundling behavior: use globals (like "browser") instead
// of "require" statements for core libraries (like "node")
platform: 'browser',
// target syntax: only use syntax available on the current
// version of node
target: NODE_VERSION_IDENTIFIER,
sourcemap: 'inline',
legalComments: 'none',
bundle: true,
plugins: [edgeWasmPlugin],
entryPoints: [entrypointPath],
write: false, // operate in memory
format: 'cjs',
});
const compiledFile = result.outputFiles?.[0];
if (!compiledFile) {
throw new Error(
`Compilation of ${entrypointLabel} produced no output files.`
);
}
const userCode = `
// strict mode
"use strict";var regeneratorRuntime;
// user code
${compiledFile.text};
// request metadata
const IS_MIDDLEWARE = ${isMiddleware};
const ENTRYPOINT_LABEL = '${entrypointLabel}';
// edge handler
${edgeHandlerTemplate}
`;
return { userCode, wasmAssets };
} catch (error) {
// We can't easily show a meaningful stack trace from ncc -> edge-runtime.
// So, stick with just the message for now.
console.error(`Failed to compile user code for edge runtime.`);
logError(error);
return undefined;
}
}
async function createEdgeRuntime(params?: {
userCode: string;
wasmAssets: WasmAssets;
}) {
try {
if (!params) {
return undefined;
}
const wasmBindings = await params.wasmAssets.getContext();
const edgeRuntime = new EdgeRuntime({
initialCode: params.userCode,
extend: (context: EdgeContext) => {
Object.assign(context, {
// This is required for esbuild wrapping logic to resolve
module: {},
// This is required for environment variable access.
// In production, env var access is provided by static analysis
// so that only the used values are available.
process: {
env: process.env,
},
// These are the global bindings for WebAssembly module
...wasmBindings,
});
return context;
},
});
const server = await runServer({ runtime: edgeRuntime });
exitHook(server.close);
return server;
} catch (error) {
// We can't easily show a meaningful stack trace from ncc -> edge-runtime.
// So, stick with just the message for now.
console.error('Failed to instantiate edge runtime.');
logError(error);
return undefined;
}
}
export async function createEdgeEventHandler(
entrypointPath: string,
entrypointLabel: string,
isMiddleware: boolean
): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>> {
const userCode = await compileUserCode(
entrypointPath,
entrypointLabel,
isMiddleware
);
const server = await createEdgeRuntime(userCode);
return async function (request: IncomingMessage) {
if (!server) {
// this error state is already logged, but we have to wait until here to exit the process
// this matches the serverless function bridge launcher's behavior when
// an error is thrown in the function
process.exit(1);
}
const response = await fetch(server.url, {
redirect: 'manual',
method: 'post',
body: await serializeRequest(request),
});
const body = await response.text();
const isUserError =
response.headers.get('x-vercel-failed') === 'edge-wrapper';
if (isUserError && response.status >= 500) {
// this error was "unhandled" from the user code's perspective
console.log(`Unhandled rejection: ${body}`);
// this matches the serverless function bridge launcher's behavior when
// an error is thrown in the function
process.exit(1);
}
return {
statusCode: response.status,
headers: response.headers.raw(),
body,
encoding: 'utf8',
};
};
}

View File

@@ -0,0 +1,58 @@
import { IncomingMessage } from 'http';
import { Readable } from 'stream';
import type { Bridge } from '@vercel/node-bridge/bridge';
import { getVercelLauncher } from '@vercel/node-bridge/launcher.js';
import { VercelProxyResponse } from '@vercel/node-bridge/types';
function rawBody(readable: Readable): Promise<Buffer> {
return new Promise((resolve, reject) => {
let bytes = 0;
const chunks: Buffer[] = [];
readable.on('error', reject);
readable.on('data', chunk => {
chunks.push(chunk);
bytes += chunk.length;
});
readable.on('end', () => {
resolve(Buffer.concat(chunks, bytes));
});
});
}
export async function createServerlessEventHandler(
entrypoint: string,
options: {
shouldAddHelpers: boolean;
useRequire: boolean;
}
): Promise<(request: IncomingMessage) => Promise<VercelProxyResponse>> {
const launcher = getVercelLauncher({
entrypointPath: entrypoint,
helpersPath: './helpers.js',
shouldAddHelpers: options.shouldAddHelpers,
useRequire: options.useRequire,
// not used
bridgePath: '',
sourcemapSupportPath: '',
});
const bridge: Bridge = launcher();
return async function (request: IncomingMessage) {
const body = await rawBody(request);
const event = {
Action: 'Invoke',
body: JSON.stringify({
method: request.method,
path: request.url,
headers: request.headers,
encoding: 'base64',
body: body.toString('base64'),
}),
};
return bridge.launcher(event, {
callbackWaitsForEmptyEventLoop: false,
});
};
}

View File

@@ -1,5 +1,6 @@
import { extname } from 'path';
import { pathToRegexp } from 'path-to-regexp';
import { debug } from '@vercel/build-utils';
export function getRegExpFromMatchers(matcherOrMatchers: unknown): string {
if (!matcherOrMatchers) {
@@ -55,3 +56,14 @@ export function entrypointToOutputPath(
}
return entrypoint;
}
export function logError(error: Error) {
console.error(error.message);
if (error.stack) {
// only show the stack trace if debug is enabled
// because it points to internals, not user code
const errorPrefixLength = 'Error: '.length;
const errorMessageLength = errorPrefixLength + error.message.length;
debug(error.stack.substring(errorMessageLength + 1));
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/python",
"version": "3.1.16",
"version": "3.1.20",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -22,7 +22,7 @@
"devDependencies": {
"@types/execa": "^0.9.0",
"@types/jest": "27.4.1",
"@vercel/build-utils": "5.4.4",
"@vercel/build-utils": "5.5.3",
"@vercel/ncc": "0.24.0",
"execa": "^1.0.0",
"typescript": "4.3.4"

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/redwood",
"version": "1.0.25",
"version": "1.0.29",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs",
@@ -27,6 +27,6 @@
"@types/aws-lambda": "8.10.19",
"@types/node": "*",
"@types/semver": "6.0.0",
"@vercel/build-utils": "5.4.4"
"@vercel/build-utils": "5.5.3"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/remix",
"version": "1.0.26",
"version": "1.0.30",
"license": "MIT",
"main": "./dist/index.js",
"homepage": "https://vercel.com/docs",
@@ -25,7 +25,7 @@
"devDependencies": {
"@types/jest": "27.5.1",
"@types/node": "*",
"@vercel/build-utils": "5.4.4",
"@vercel/build-utils": "5.5.3",
"typescript": "4.6.4"
}
}

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