Compare commits

..

48 Commits

Author SHA1 Message Date
JJ Kasper
cc1cdbe610 Publish Stable
- @vercel/next@2.6.29
2020-10-20 06:15:03 -05:00
JJ Kasper
0b92f8ceee Publish Canary
- @vercel/next@2.6.29-canary.0
 - @vercel/routing-utils@1.9.1-canary.1
2020-10-20 05:08:26 -05:00
JJ Kasper
be82a88d1a [next] Add i18n support handling (#5298)
* Add initial locale output mapping

* Add additional probes

* Add locale specific 404 handling

* Add locale specific 404s, locale redirects, and more tests

* Update wildcard value

* Fix wildcard handling and update route type

* Update addLocale util

* Apply suggestions from code review

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

* Remove redundant console.error

Co-authored-by: Steven <steven@ceriously.com>
Co-authored-by: Leo Lamprecht <leo@zeit.co>
2020-10-20 12:05:06 +02:00
JJ Kasper
64da08f0f2 Publish Stable
- @vercel/next@2.6.28
2020-10-20 05:00:10 -05:00
Steven
2e957fce55 Publish Canary
- @vercel/next@2.6.28-canary.0
2020-10-16 18:19:07 -04:00
Steven
c7ead151f5 [next] Add support for Image Optimization (#5296)
This PR sends the results of the image optimization config from next.config.js to the build output.

x-ref: https://github.com/vercel/next.js/pull/17749
2020-10-16 17:56:14 -04:00
dav-is
e2baf9b00f Publish Canary
- @vercel/routing-utils@1.9.1-canary.0
2020-10-14 16:48:04 -04:00
Connor Davis
ba8ef7bc98 Add custom locale cookie (#5293) 2020-10-14 16:46:40 -04:00
JJ Kasper
c5d04e0d4f Publish Stable
- @vercel/next@2.6.27
2020-10-13 14:40:53 -05:00
JJ Kasper
b4f849418d Publish Canary
- @vercel/next@2.6.27-canary.0
2020-10-13 00:17:38 -05:00
JJ Kasper
b37c4f211e [next] Update next.config backup name to be deterministic (#5288) 2020-10-13 01:13:40 -04:00
Daniel Roe
f06efe167c [docs] Update contributing example of @vercel/nft (#5284) 2020-10-12 15:01:27 -04:00
dependabot[bot]
296d8da676 Bump next from 9.5.1 to 9.5.4 in /examples/nextjs (#5278)
Bumps [next](https://github.com/vercel/next.js) from 9.5.1 to 9.5.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/vercel/next.js/releases">next's releases</a>.</em></p>
<blockquote>
<h2>v9.5.4</h2>
<p><strong>This upgrade is <em>completely backwards compatible and recommended for all users on versions below 9.5.4.</em> For future security related communications of our OSS projects, please <a href="https://vercel.com/security">join this mailing list</a>.</strong></p>
<p>A security team from one of our partners noticed an issue in Next.js that allowed for open redirects to occur.</p>
<p>Specially encoded paths could be used with the trailing slash redirect to allow an open redirect to occur to an external site.</p>
<p>In general, this redirect does not directly harm users although can allow for phishing attacks by redirecting to an attackers domain from a trusted domain.</p>
<p>We recommend upgrading to the latest version of Next.js to improve the overall security of your application.</p>
<h2>How to Upgrade</h2>
<ul>
<li>We have released patch versions for both the stable and canary channels of Next.js.</li>
<li>To upgrade run <code>npm install next@latest --save</code></li>
</ul>
<h2>Impact</h2>
<ul>
<li><strong>Affected</strong>: Users of Next.js between 9.5.0 and 9.5.3</li>
<li><strong>Not affected</strong>: Deployments on Vercel (<a href="https://vercel.com">https://vercel.com</a>) are not affected</li>
<li><strong>Not affected</strong>: Deployments using <code>next export</code></li>
</ul>
<p>We recommend everyone to upgrade regardless of whether you can reproduce the issue or not.</p>
<h3>How to Assess Impact</h3>
<p>If you think users could have been affected, you can filter logs of affected sites by <code>%2F</code> with a 308 response.</p>
<h2>What is Being Done</h2>
<p>As Next.js has grown in popularity, it has received the attention of security teams and auditors. We are thankful to those that reached out for their investigation and discovery of the original bug and subsequent responsible disclosure.</p>
<p>We've landed a patch that ensures encoding is handled properly for these types of redirects so the open redirect can no longer occur.</p>
<p>Regression tests for this attack were added to the <a href="https://github.com/vercel/next.js/blob/canary/test/integration/production/test/security.js">security</a> integration test suite.</p>
<ul>
<li>We have notified known Next.js users in advance of this publication.</li>
<li>A public CVE was released.</li>
<li>If you want to stay on top of our security related news impacting Next.js or other Vercel projects, please <a href="https://zeit.co/security">join this mailing list</a>.</li>
<li>We encourage responsible disclosure of future issues. Please email us at <strong><a href="https://github.com/vercel/next.js/blob/HEAD/mailto:security@zeit.co">security@vercel.com</a>.</strong> We are actively monitoring this mailbox.</li>
</ul>
<hr />
<h3>Core Changes</h3>
<ul>
<li>Make the image post-processor ignore SVG images: <a href="https://github-redirect.dependabot.com/vercel/next.js/issues/16732">#16732</a></li>
<li>Only update lookups for dev overlay if mounted: <a href="https://github-redirect.dependabot.com/vercel/next.js/issues/16776">#16776</a></li>
<li>Ensure interpolating dynamic href values works correctly: <a href="https://github-redirect.dependabot.com/vercel/next.js/issues/16774">#16774</a></li>
<li>Add automatic reloading when editing GS(S)P methods: <a href="https://github-redirect.dependabot.com/vercel/next.js/issues/16744">#16744</a></li>
<li>Update to show build indicator while re-fetching GS(S)P data in dev: <a href="https://github-redirect.dependabot.com/vercel/next.js/issues/16789">#16789</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="6588108150"><code>6588108</code></a> v9.5.4</li>
<li><a href="7108567b06"><code>7108567</code></a> v9.5.4-canary.25</li>
<li><a href="5d79a8c0c4"><code>5d79a8c</code></a> Update workflow step to restore cache (<a href="https://github-redirect.dependabot.com/vercel/next.js/issues/17656">#17656</a>)</li>
<li><a href="4c38e3ed8e"><code>4c38e3e</code></a> fix typo (<a href="https://github-redirect.dependabot.com/vercel/next.js/issues/17653">#17653</a>)</li>
<li><a href="241f38eaa8"><code>241f38e</code></a> v9.5.4-canary.24</li>
<li><a href="7dec91175c"><code>7dec911</code></a> change anonymous functions to named in docs examples (<a href="https://github-redirect.dependabot.com/vercel/next.js/issues/17510">#17510</a>)</li>
<li><a href="1659e4da61"><code>1659e4d</code></a> Update migrating from Gatsby docs. (<a href="https://github-redirect.dependabot.com/vercel/next.js/issues/17636">#17636</a>)</li>
<li><a href="06a8b1ad67"><code>06a8b1a</code></a> Add docs on how to migrate from Gatsby. (<a href="https://github-redirect.dependabot.com/vercel/next.js/issues/17491">#17491</a>)</li>
<li><a href="04234cc312"><code>04234cc</code></a> Update to use hasNextSupport for custom-routes in next export check (<a href="https://github-redirect.dependabot.com/vercel/next.js/issues/17630">#17630</a>)</li>
<li><a href="742f5d9a46"><code>742f5d9</code></a> test(create-next-app): increase coverage (<a href="https://github-redirect.dependabot.com/vercel/next.js/issues/17507">#17507</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/vercel/next.js/compare/v9.5.1...v9.5.4">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=next&package-manager=npm_and_yarn&previous-version=9.5.1&new-version=9.5.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/configuring-github-dependabot-security-updates)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language

You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/vercel/vercel/network/alerts).

</details>
2020-10-09 14:32:15 +00:00
Steven
981e4183c6 Publish Stable
- @vercel/build-utils@2.5.4
 - vercel@20.1.2
 - @vercel/client@9.0.3
 - @vercel/node@1.8.4
 - @vercel/routing-utils@1.9.0
2020-10-08 09:10:16 -04:00
Steven
1706a00cb9 Publish Canary
- @vercel/build-utils@2.5.4-canary.0
 - vercel@20.1.2-canary.5
 - @vercel/client@9.0.3-canary.0
2020-10-06 09:10:41 -04:00
Steven
d7bd03cf1d [build-utils] Fix framework "CRA" when Next.js is dependency (#5246)
Fixes https://github.com/vercel/vercel/discussions/4288#discussioncomment-88998

This was fixed when "Framework: Other" in #5009 but this PR fixes it when "Framework: CRA" or any framework selection.
2020-10-06 13:09:02 +00:00
dav-is
e27ff8d2e0 Publish Canary
- vercel@20.1.2-canary.4
2020-10-05 18:14:54 -04:00
RAJPRAKASH
bc6da39cc2 [examples] Fix link to gatsby live example (#5205)
Fixes the link in the gatsby example back to the repo.

Co-authored-by: Steven <steven@ceriously.com>
2020-10-05 18:08:29 -04:00
dav-is
727109c64a Publish Canary
- @vercel/routing-utils@1.8.5-canary.0
2020-10-05 18:03:21 -04:00
Connor Davis
817410a8e2 [routing-utils] Add locale to routes schema (#5259)
We need to add a `locale` property to `routes`: CH-12345, CH-12349
2020-10-05 21:25:21 +00:00
Steven
e022e7b79c Publish Canary
- vercel@20.1.2-canary.3
2020-10-05 15:20:29 -04:00
Steven
dda6c24a3d [cli] Fix progress bar completion (#5260)
In some rare cases, the progress bar will still be in progress when the deployment completes, which causes the  "Inspect" URL to print on the same line as the progress bar instead of a new line.

This PR fixes that corner case.
2020-10-05 19:10:20 +00:00
Steven
7c922a4092 [cli] Remove 1.0 path alias rules (#5258)
We don't support routing path aliases anymore, now that we've completely shut down the ZEIT Now 1.0 infrastructure. This PR removes path alias rules from Vercel CLI.
2020-10-05 17:48:37 +00:00
Steven
376dec8f33 Publish Canary
- vercel@20.1.2-canary.2
2020-10-02 19:18:35 -04:00
Steven
16e101d262 [cli] Fix dev routing when using routes with a frontend framework (#5253)
There was a regression when introducing multiple phases such as `hit` and `miss` which caused `routes` to stop working because the transition from `null` phase to the `filesystem` phase resets any dest rewrites.

The workaround is to detect when we don't have a `filesystem` phase and exit early.
2020-10-02 22:46:53 +00:00
Nathan Rajlich
41ce1a4291 Publish Canary
- vercel@20.1.2-canary.1
 - @vercel/node@1.8.4-canary.0
2020-09-25 10:00:24 -07:00
Nathan Rajlich
449f35cf33 [node] Only use the project's local "typescript" when it has compatible ScriptTarget (#5220)
https://app.clubhouse.io/vercel/story/8304
2020-09-24 15:18:09 -07:00
Nathan Rajlich
d683402bba Publish Canary
- vercel@20.1.2-canary.0
2020-09-23 13:23:44 -07:00
Nathan Rajlich
2051a1cd9b [cli] Ensure the devProcessPort is set during "upgrade" events (#5226)
Related to: https://twitter.com/raymondcamden/status/1308838251902521348
2020-09-23 20:16:51 +00:00
Steven
1eeeaf23a1 Publish Stable
- @vercel/build-utils@2.5.3
 - vercel@20.1.1
 - @vercel/client@9.0.2
 - @vercel/next@2.6.26
 - @vercel/node@1.8.3
 - @vercel/static-build@0.17.9
2020-09-21 15:12:38 -04:00
Steven
f347164b6c Publish Canary
- vercel@20.1.1-canary.8
2020-09-18 17:51:03 -04:00
Steven
ea4be8a001 [tests] Delay deprecation message (#5210)
Follow up to #5158 to add longer delay.

There is an issue with npm consistency after publishing so we wait longer before deprecating the `now` package because its a little larger than the others.
2020-09-18 16:02:35 -04:00
JJ Kasper
19ac74d59e Publish Canary
- vercel@20.1.1-canary.7
 - @vercel/client@9.0.2-canary.5
 - @vercel/next@2.6.26-canary.3
 - @vercel/node@1.8.3-canary.2
 - @vercel/static-build@0.17.9-canary.1
2020-09-15 19:21:01 -05:00
JJ Kasper
9a87d4ea8e [next] Fix API page check (#5196)
This corrects the `isApiPage` check to not incorrectly detect `/api-docs` as an API route. 

Note: tests won't pass until a new canary with the below Next.js PR has been landed

x-ref: https://github.com/vercel/next.js/pull/17092
Closes: https://github.com/vercel/next.js/issues/17091
2020-09-15 15:28:19 +00:00
Albert Martin
35fd7b5f9c [static-build] Fix immutable cache header for Vue assets (#5070)
We have started migrating our projects to use Vue CLI with the Vercel zero-config builders. One particularly bug we ran into was due to the `Cache-Control` headers automatically applied on these resources that [differ from the documentation](https://vercel.com/docs/edge-network/caching#static-files):

> By default we return a `Cache-Control` header containing `public, max-age=0, must-revalidate` to prevent clients (e.g. browsers) from caching the file locally.

Instead of these headers, we were seeing `immutable` on all our resources. This is a good assumption for a vanilla Vue CLI setup because by default a content hash is used on every chunk, even the entry, which will change as soon as the file contents change. However, this may not always be the case, particularly on Vue CLI projects with any modifications to the build process, custom plugins, etc. that may exclude one or more files from the hash.

In our case this was breaking things in dramatic ways every time we deployed (and we deploy quite often!). Since the resources were being sent with the `immutable` header, the client browser was persisting these in cache and subsequent deployments with potentially breaking changes caused errors that were only resolved by clearing the client browser cache.

Example:

```
dist
├── css
│   ├── app.db598b39.css
│   └── common.css
├── js
│   ├── app.ac201ece.js
│   ├── chunk-587c608b.7d4361a1.js
│   ├── chunk-vendors.00676d62.js
│   └── common.js
└── index.html
```

In the above example, `common.css` and `common.js` are not hashed, but still have an `immutable` header preventing them from being re-validated by the client browser when a new deployment modifies their content.

To solve for this, I've updated the RegExp for Vue CLI to only apply the `immutable` caching header on files which have a content hash. This should solve for both vanilla Vue CLI setups and more advanced ones in which there may exist both files with and without a content hash.

Co-authored-by: Steven <steven@ceriously.com>
2020-09-14 09:08:15 -04:00
Steven
eb8db25845 [cli] Remove v1 messages and docs (#5183)
We removed v1 support in [20.0.0](https://github.com/vercel/vercel/releases/tag/vercel%4020.0.0) so this PR removes the unused documentation, warning messages, etc.
2020-09-11 22:46:32 +00:00
Steven
5c3cd17074 [tests] Cache token when possible (#5179)
This PR solves an issue where tests would sometimes be rate limited when generating a token. This can be solved by caching the token like we do for E2E tests.

Example failure: https://github.com/vercel/vercel/runs/1098030228#step:11:9376

I also update the `@vercel/node` tests to compare the error message, exactly like how we do it with `@vercel/static-build`.
2020-09-11 10:02:07 -04:00
Nathan Rajlich
72572ba6be Publish Canary
- @vercel/build-utils@2.5.3-canary.1
 - vercel@20.1.1-canary.6
 - @vercel/client@9.0.2-canary.4
 - @vercel/node@1.8.3-canary.1
 - @vercel/static-build@0.17.9-canary.0
2020-09-10 17:20:10 -07:00
Nathan Rajlich
b69a41a0aa [node] Use ts.readConfigFile() to parse user's tsconfig.json file (#5180)
TypeScript supports comments in the `tsconfig.json` file, which is not
compatible with `JSON.parse()` so we have to use the built-in function
to parse the config file.

Fixes #4835.
2020-09-10 17:18:30 -07:00
dependabot[bot]
7c35d7992b Bump node-fetch from 2.6.0 to 2.6.1 (#5176)
Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/bitinn/node-fetch/releases">node-fetch's releases</a>.</em></p>
<blockquote>
<h2>v2.6.1</h2>
<p><strong>This is an important security release. It is strongly recommended to update as soon as possible.</strong></p>
<p>See <a href="https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md#v261">CHANGELOG</a> for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md">node-fetch's changelog</a>.</em></p>
<blockquote>
<h2>v2.6.1</h2>
<p><strong>This is an important security release. It is strongly recommended to update as soon as possible.</strong></p>
<ul>
<li>Fix: honor the <code>size</code> option after following a redirect.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="b5e2e41b2b"><code>b5e2e41</code></a> update version number</li>
<li><a href="2358a6c256"><code>2358a6c</code></a> Honor the <code>size</code> option after following a redirect and revert data uri support</li>
<li><a href="8c197f8982"><code>8c197f8</code></a> docs: Fix typos and grammatical errors in README.md (<a href="https://github-redirect.dependabot.com/bitinn/node-fetch/issues/686">#686</a>)</li>
<li><a href="1e99050f94"><code>1e99050</code></a> fix: Change error message thrown with redirect mode set to error (<a href="https://github-redirect.dependabot.com/bitinn/node-fetch/issues/653">#653</a>)</li>
<li><a href="244e6f63d4"><code>244e6f6</code></a> docs: Show backers in README</li>
<li><a href="6a5d192034"><code>6a5d192</code></a> fix: Properly parse meta tag when parameters are reversed (<a href="https://github-redirect.dependabot.com/bitinn/node-fetch/issues/682">#682</a>)</li>
<li><a href="47a24a03eb"><code>47a24a0</code></a> chore: Add opencollective badge</li>
<li><a href="7b136627c5"><code>7b13662</code></a> chore: Add funding link</li>
<li><a href="5535c2ed47"><code>5535c2e</code></a> fix: Check for global.fetch before binding it (<a href="https://github-redirect.dependabot.com/bitinn/node-fetch/issues/674">#674</a>)</li>
<li><a href="1d5778ad0d"><code>1d5778a</code></a> docs: Add Discord badge</li>
<li>Additional commits viewable in <a href="https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1">compare view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a href="https://www.npmjs.com/~akepinski">akepinski</a>, a new releaser for node-fetch since your current version.</p>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=node-fetch&package-manager=npm_and_yarn&previous-version=2.6.0&new-version=2.6.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/configuring-github-dependabot-security-updates)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language

You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/vercel/vercel/network/alerts).

</details>
2020-09-10 22:50:00 +00:00
JJ Kasper
290fdc0506 Publish Canary
- @vercel/next@2.6.26-canary.2
2020-09-10 13:15:27 -05:00
JJ Kasper
fc2d9f99e6 [next] Optimize page loading for shared lambdas (#5156)
This updates to prevent loading all pages at once for shared lambdas and waits to require the page when it's needed

Fixes https://github.com/vercel/next.js/issues/16990
2020-09-10 14:24:36 +00:00
Steven
1fd24fb4fc Publish Canary
- vercel@20.1.1-canary.5
 - @vercel/next@2.6.26-canary.1
2020-09-09 20:40:33 -04:00
Steven
17c72f1d35 [next] Fix config functions src file (#5168)
The [functions](https://vercel.com/docs/configuration#project/functions) property matches source files and it wasn't working for `index.js` or `.ts` files because `next build` converts the source file `pages/api/index.js` to the output `.next/server/pages/api.js`. So this PR fixes the logic to reverse the output page to source file.
2020-09-09 23:41:12 +00:00
Steven
9c1154c108 [tests] Fix test for functions in vercel.json (#5169) 2020-09-09 18:08:14 -04:00
dav-is
613c384ce8 Publish Canary
- @vercel/build-utils@2.5.3-canary.0
 - vercel@20.1.1-canary.4
 - @vercel/client@9.0.2-canary.3
 - @vercel/next@2.6.26-canary.0
 - @vercel/node@1.8.3-canary.0
2020-09-09 09:59:50 -04:00
Gianmarco
a9598d14e3 [docs] Fix typo in error message (#5141) 2020-09-08 16:48:52 -04:00
Connor Davis
48935e92d8 Remove extra newline in warning (#5165) 2020-09-08 11:23:28 -04:00
95 changed files with 7296 additions and 498 deletions

View File

@@ -73,8 +73,8 @@ The logs of this deployment will contain the actual error which may help you to
Some of the Builders use `@vercel/nft` to tree-shake files before deployment. If you suspect an error with this tree-shaking mechanism, you can create the following script in your project:
```js
const trace = require('@vercel/nft');
trace(['path/to/entrypoint.js'], {
const { nodeFileTrace } = require('@vercel/nft');
nodeFileTrace(['path/to/entrypoint.js'], {
ts: true,
mixedModules: true,
})

View File

@@ -2,8 +2,8 @@
#### Why This Error Occurred
When supplying `regions` or `scale` settings, you
used an unknown or invalid dc identifier.
When supplying `regions` configuration, you
used an unknown or invalid DC identifier.
#### Possible Ways to Fix It
@@ -19,7 +19,7 @@ and DCs have to be in _lowercase_.
- `gru`
- `iad`
In `now-cli`, they currently are transformed to
In Vercel CLI, they currently are transformed to
DC identifiers before being sent to our APIs.
**Valid DC identifiers**:

View File

@@ -1,9 +0,0 @@
# Missing `--dotenv` Target
#### Why This Error Occurred
You specified a path as the value for the `--dotenv` flag, but the target of the path doesn't exist.
#### Possible Ways to Fix It
Make sure the target file you've specified exists and is readable by Vercel CLI. In addition, please ensure that the filename starts with a dot (example: `.env`) - then it should work.

View File

@@ -2,7 +2,7 @@
#### Why This Error Occurred
This error occurs when you have your application is not configured for Serverless Next.js build output.
This error occurs when your application is not configured for Serverless Next.js build output.
#### Possible Ways to Fix It

View File

@@ -1,32 +0,0 @@
# Can't Set `regions` and `scale` Options Simultaneously
#### Why This Error Occurred
Your deployment's configuration contains a `regions` and `scale`
configuration simultaneously.
#### Possible Ways to Fix It
The `regions` setting is intended to be used to scale the
deployment to the supplied regions or datacenters identifiers
with default scale settings.
```json
{
"regions": ["sfo", "bru", "gru", "iad"]
}
```
The `scale` object allows you to be more granular: you can decide a
`min` and `max` number of instances per region:
```json
{
"scale": {
"sfo": { "min": 0, "max": 10 }
}
}
```
To solve this problem, use only one of the two ways of deciding
where to scale your deployment to.

View File

@@ -1,36 +0,0 @@
# Invalid Region or DC Identifier
#### Why This Error Occurred
When supplying a region or DC identifier in `vercel scale`,
we weren't able to recognize the value as valid.
#### Possible Ways to Fix It
Check your `vercel scale` command make sure you are using a
valid string after the URL. Regions
and DCs have to be in _lowercase_.
**Valid region identifiers**:
- `all` (special, used to scale to all DCs, can only appear once)
- `sfo`
- `bru`
- `gru`
- `iad`
In Vercel CLI, they currently are transformed to
DC identifiers before being sent to our APIs.
**Valid DC identifiers**:
- `sfo1`
- `bru1`
- `gru1`
- `iad1`
To pass multiple ones, use a comma:
```
vercel scale my-url-123.now.sh sfo,bru,gru 1 5
```

View File

@@ -1,30 +0,0 @@
# `vercel scale ls` is deprecated
#### Why This Error Occurred
We have stopped supporting this command, in favor of
better alternatives.
`vercel scale ls` used to list all the scaling rules
for all your deployments. The output would be too long,
and it would often be hard to find the information
you needed in a long list of items.
#### Possible Ways to Fix It
Instead of using `vercel scale ls` to list all your deployments
and their scaling rules, first use `vercel ls` to find
your deployment:
```console
vercel ls
```
Then, select the URL of your deployment, which uniquely identifies it, and run:
```console
vercel inspect my-deployment-12345.now.sh
```
The `inspect` subcommand will give you your deployment's scale information, including what datacenters it's enabled on, the
current number of instances and minimums/maximums.

View File

@@ -1,12 +0,0 @@
# Scaling path alias
#### Why This Error Occurred
You tried to use `vercel scale` on a path alias (`vercel alias -r rules.json`).
#### Possible Ways to Fix It
Path aliases are routes to instances. Instances can be scaled independent from each other.
You can view path aliases by running `vercel alias ls <id>`.
Documentation for Path Aliases can be found [here](https://vercel.com/docs/features/path-aliases).

View File

@@ -1,16 +0,0 @@
# No minimum scale settings on Cloud v2 deployments
#### Why This Error Occurred
An attempt was made at scaling a Cloud v2 deployment with a `min` scale
setting. This isn't supported yet.
#### Possible Ways to Fix It
Ensure your scale settings (in `vercel.json`, the command you're running
or from a previous deployment who's alias you're trying to overwrite) has
the `min` scale setting set to `0`. You can do this by running
```
vercel scale <deployment> 0 10
```

View File

@@ -1,29 +0,0 @@
# Verification Timeout
#### Why This Error Occurred
After the deployment build completed and the deployment state was set to `READY`,
instances failed to initialize properly.
The CLI attempted to verify that the scale settings of your instances matched,
but it couldn't do so within the allotted time (defaults to 2 minutes).
#### Possible Ways to Fix It
Instance verification is the process of ensuring that after
your deployment is ready, we can actually run (instantiate) your code.
If you configured [regions or scale](https://vercel.com/docs/features/scaling),
we ensure the minimums and maximums are met for the regions you enabled.
If you think your code is taking too long to instantiate, this can be due
to slow boot up times. You can supply `--no-verify` to skip verification
if you are confident your code runs properly.
If your application is not listening on a HTTP port, we might be failing to
instantiate your deployment as well. It might not be showing any errors,
but the deployment instance is effectively not routable and cannot be
verified.
If your instances are crashing before an HTTP port is exposed, verification
will fail as well. Double check your logs (e.g.: by running `vercel logs <url>`)

View File

@@ -31,7 +31,7 @@ function Index() {
</h2>
<p>
<a
href="https://github.com/vercel/vercel/blob/master/gatsby"
href="https://github.com/vercel/vercel/tree/master/examples/gatsby"
target="_blank"
rel="noreferrer noopener"
>

View File

@@ -8,7 +8,7 @@
"start": "next start"
},
"dependencies": {
"next": "9.5.1",
"next": "9.5.4",
"react": "16.13.1",
"react-dom": "16.13.1"
}

View File

@@ -27,7 +27,7 @@
"husky": "3.0.4",
"json5": "2.1.1",
"lint-staged": "9.2.5",
"node-fetch": "2.6.0",
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"prettier": "2.0.5"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "2.5.2",
"version": "2.5.4",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -44,7 +44,7 @@
"js-yaml": "3.13.1",
"minimatch": "3.0.4",
"multistream": "2.1.1",
"node-fetch": "2.2.0",
"node-fetch": "2.6.1",
"semver": "6.1.1",
"ts-jest": "24.1.0",
"typescript": "3.9.3",

View File

@@ -458,7 +458,10 @@ function detectFrontBuilder(
config.outputDirectory = projectSettings.outputDirectory;
}
if (pkg && (framework !== null || createdAt < Date.parse('2020-03-01'))) {
if (
pkg &&
(framework === undefined || createdAt < Date.parse('2020-03-01'))
) {
const deps: PackageJson['dependencies'] = {
...pkg.dependencies,
...pkg.devDependencies,

View File

@@ -1080,6 +1080,46 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(errorRoutes).toStrictEqual([]);
});
it('Using "Create React App" framework with `next` in dependencies should NOT autodetect Next.js for new projects', async () => {
const pkg = {
scripts: {
dev: 'react-scripts start',
build: 'react-scripts build',
},
dependencies: {
next: '9.3.5',
react: '16.13.1',
'react-dom': '16.13.1',
'react-scripts': '2.1.1',
},
};
const files = ['package.json', 'src/index.js', 'public/favicon.ico'];
const projectSettings = {
framework: 'create-react-app',
buildCommand: 'react-scripts build',
createdAt: Date.parse('2020-07-01'),
};
const { builders, errorRoutes } = await detectBuilders(files, pkg, {
projectSettings,
featHandleMiss,
});
expect(builders).toEqual([
{
use: '@vercel/static-build',
src: 'package.json',
config: {
zeroConfig: true,
framework: projectSettings.framework,
buildCommand: projectSettings.buildCommand,
},
},
]);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
});
it('Using "Other" framework with Storybook should NOT autodetect Next.js for new projects', async () => {
const pkg = {
scripts: {

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "20.1.1-canary.3",
"version": "20.1.2",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -61,9 +61,9 @@
"node": ">= 10"
},
"dependencies": {
"@vercel/build-utils": "2.5.2",
"@vercel/build-utils": "2.5.4",
"@vercel/go": "1.1.6",
"@vercel/node": "1.8.2",
"@vercel/node": "1.8.4",
"@vercel/python": "1.2.3",
"@vercel/ruby": "1.2.4",
"update-notifier": "4.1.0"
@@ -147,7 +147,7 @@
"mri": "1.1.5",
"ms": "2.1.2",
"nanoid": "3.0.2",
"node-fetch": "2.6.0",
"node-fetch": "2.6.1",
"npm-package-arg": "6.1.0",
"nyc": "13.2.0",
"ora": "3.4.0",

View File

@@ -18,7 +18,7 @@ const help = () => {
${chalk.dim('Commands:')}
ls [app] Show all aliases (or per app name)
ls Show all aliases
set <deployment> <alias> Create a new alias
rm <alias> Remove an alias using its hostname
@@ -39,19 +39,19 @@ const help = () => {
-N, --next Show next page of results
${chalk.dim('Examples:')}
${chalk.gray('')} Add a new alias to ${chalk.underline('my-api.now.sh')}
${chalk.gray('')} Add a new alias to ${chalk.underline('my-api.vercel.app')}
${chalk.cyan(
`$ ${getPkgName()} alias set ${chalk.underline(
'api-ownv3nc9f8.now.sh'
)} ${chalk.underline('my-api.now.sh')}`
'api-ownv3nc9f8.vercel.app'
)} ${chalk.underline('my-api.vercel.app')}`
)}
Custom domains work as alias targets
${chalk.cyan(
`$ ${getPkgName()} alias set ${chalk.underline(
'api-ownv3nc9f8.now.sh'
'api-ownv3nc9f8.vercel.app'
)} ${chalk.underline('my-api.com')}`
)}

View File

@@ -1,6 +1,5 @@
import chalk from 'chalk';
import ms from 'ms';
import plural from 'pluralize';
import table from 'text-table';
import Now from '../../util';
import Client from '../../util/client.ts';
@@ -52,21 +51,17 @@ export default async function ls(ctx, opts, args, output) {
const lsStamp = stamp();
let cancelWait;
if (args.length > 1) {
if (args.length > 0) {
output.error(
`Invalid number of arguments. Usage: ${chalk.cyan(
`${getCommandName('alias ls [alias]')}`
`${getCommandName('alias ls')}`
)}`
);
return 1;
}
cancelWait = output.spinner(
args[0]
? `Fetching alias details for "${args[0]}" under ${chalk.bold(
contextName
)}`
: `Fetching aliases under ${chalk.bold(contextName)}`
`Fetching aliases under ${chalk.bold(contextName)}`
);
const { aliases, pagination } = await getAliases(
@@ -76,32 +71,8 @@ export default async function ls(ctx, opts, args, output) {
);
if (cancelWait) cancelWait();
if (args[0]) {
const alias = aliases.find(
item => item.uid === args[0] || item.alias === args[0]
);
if (!alias) {
output.error(`Could not match path alias for: ${args[0]}`);
now.close();
return 1;
}
if (opts['--json']) {
console.log(JSON.stringify({ rules: alias.rules }, null, 2));
} else {
const rules = alias.rules || [];
output.log(
`${rules.length} path alias ${plural(
'rule',
rules.length
)} found under ${chalk.bold(contextName)} ${lsStamp()}`
);
output.print(`${printPathAliasTable(rules)}\n`);
}
} else {
output.log(`aliases found under ${chalk.bold(contextName)} ${lsStamp()}`);
console.log(printAliasTable(aliases));
}
output.log(`aliases found under ${chalk.bold(contextName)} ${lsStamp()}`);
console.log(printAliasTable(aliases));
if (pagination && pagination.count === 20) {
const flags = getCommandFlags(opts, ['_', '--next']);
@@ -121,14 +92,10 @@ function printAliasTable(aliases) {
[
['source', 'url', 'age'].map(h => chalk.gray(h)),
...aliases.map(a => [
a.rules && a.rules.length
? chalk.cyan(`[${plural('rule', a.rules.length, true)}]`)
: // for legacy reasons, we might have situations
// where the deployment was deleted and the alias
// not collected appropriately, and we need to handle it
a.deployment && a.deployment.url
? a.deployment.url
: chalk.gray(''),
// for legacy reasons, we might have situations
// where the deployment was deleted and the alias
// not collected appropriately, and we need to handle it
a.deployment && a.deployment.url ? a.deployment.url : chalk.gray(''),
a.alias,
ms(Date.now() - new Date(a.createdAt)),
]),
@@ -140,21 +107,3 @@ function printAliasTable(aliases) {
}
).replace(/^/gm, ' ')}\n\n`;
}
function printPathAliasTable(rules) {
const header = [['pathname', 'method', 'dest'].map(s => chalk.gray(s))];
return `${table(
header.concat(
rules.map(rule => [
rule.pathname ? rule.pathname : chalk.cyan('[fallthrough]'),
rule.method ? rule.method : '*',
rule.dest,
])
),
{
align: ['l', 'l', 'l', 'l'],
hsep: ' '.repeat(6),
stringLength: strlen,
}
).replace(/^(.*)/gm, ' $1')}\n`;
}

View File

@@ -96,7 +96,7 @@ export default async function set(
return 1;
}
// For `now alias set <argument>`
// For `vercel alias set <argument>`
if (args.length === 1) {
const deployment = handleCertError(
output,
@@ -261,7 +261,7 @@ function handleSetupDomainError<T>(
{ extraSpace: ' ' }
)}\n\n`
);
output.print(' Read more: https://err.sh/now/domain-verification\n');
output.print(' Read more: https://err.sh/vercel/domain-verification\n');
return 1;
}
@@ -388,7 +388,7 @@ function handleCreateAliasError<T>(
}
if (error instanceof ERRORS.InvalidAlias) {
output.error(
`Invalid alias. Please confirm that the alias you provided is a valid hostname. Note: For \`now.sh\`, only sub and sub-sub domains are supported.`
`Invalid alias. Please confirm that the alias you provided is a valid hostname. Note: For \`vercel.app\`, only sub and sub-sub domains are supported.`
);
return 1;
}

View File

@@ -75,7 +75,7 @@ export default async function issue(
}
if (crtPath || keyPath || caPath) {
if (args.length !== 0 || (!crtPath || !keyPath || !caPath)) {
if (args.length !== 0 || !crtPath || !keyPath || !caPath) {
output.error(
`Invalid number of arguments to create a custom certificate entry. Usage:`
);
@@ -230,6 +230,8 @@ async function runStartOrder(
output.print(
` ${chalk.cyan(getCommandName(`certs issue ${cns.join(' ')}`))}\n`
);
output.print(' Read more: https://err.sh/now/solve-challenges-manually\n');
output.print(
' Read more: https://err.sh/vercel/solve-challenges-manually\n'
);
return 0;
}

View File

@@ -1,7 +1,5 @@
import chalk from 'chalk';
import logo from '../../util/output/logo';
import code from '../../util/output/code';
import note from '../../util/output/note';
import { getPkgName } from '../../util/pkg-name.ts';
export const help = () => `
@@ -70,12 +68,6 @@ export const help = () => `
--prod Create a production deployment
-c, --confirm Confirm default options and skip questions
${note(
`To view the usage information for Now 1.0, run ${code(
`${getPkgName()} help deploy-v1`
)}`
)}
${chalk.dim('Examples:')}
${chalk.gray('')} Deploy the current directory

View File

@@ -104,7 +104,7 @@ const printDeploymentStatus = async (
if (readyState !== 'READY') {
output.error(
`Your deployment failed. Please retry later. More: https://err.sh/now/deployment-error`
`Your deployment failed. Please retry later. More: https://err.sh/vercel/deployment-error`
);
return 1;
}
@@ -170,7 +170,6 @@ const printDeploymentStatus = async (
chalk.dim(
`${indication.action || 'Learn More'}: ${indication.link}`
) +
newline +
newline;
output.print(message + link);
}
@@ -248,11 +247,6 @@ export default async function main(
const { isFile, path } = pathValidation;
const autoConfirm = argv['--confirm'] || isFile;
// --no-scale
if (argv['--no-scale']) {
warn(`The option --no-scale is only supported on Now 1.0 deployments`);
}
// deprecate --name
if (argv['--name']) {
output.print(

View File

@@ -99,7 +99,7 @@ export default async function main(ctx: NowContext) {
'package.json'
)} must not contain ${cmd('now dev')}`
);
output.error(`Learn More: http://err.sh/now/now-dev-as-dev-script`);
output.error(`Learn More: http://err.sh/vercel/now-dev-as-dev-script`);
return 1;
}
if (scripts && scripts.dev && /\bvercel\b\W+\bdev\b/.test(scripts.dev)) {
@@ -108,7 +108,7 @@ export default async function main(ctx: NowContext) {
'package.json'
)} must not contain ${cmd('vercel dev')}`
);
output.error(`Learn More: http://err.sh/now/now-dev-as-dev-script`);
output.error(`Learn More: http://err.sh/vercel/now-dev-as-dev-script`);
return 1;
}
}

View File

@@ -117,12 +117,6 @@ export type Deployment = {
creator: { uid: string };
};
type PathAliasRule = {
pathname: string;
method: Array<'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'>;
dest: string;
};
export type Alias = {
uid: string;
alias: string;
@@ -137,13 +131,6 @@ export type Alias = {
email: string;
};
deploymentId?: string;
rules?: PathAliasRule[];
};
export type PathRule = {
dest: string;
pathname?: string;
method?: Array<string>;
};
export type DNSRecord = {

View File

@@ -67,13 +67,15 @@ export default function handleCertError<T>(
output.print(
` ${getCommandName(`certs issue --challenge-only <cns>`)}\n`
);
output.print(' Read more: https://err.sh/now/dns-configuration-error\n');
output.print(
' Read more: https://err.sh/vercel/dns-configuration-error\n'
);
} else {
output.print(
` We configured them for you, but the propagation may take a few minutes. Please try again later.\n`
);
output.print(
' Read more: https://err.sh/now/dns-configuration-error\n\n'
' Read more: https://err.sh/vercel/dns-configuration-error\n\n'
);
}
return 1;

View File

@@ -172,6 +172,10 @@ export default async function processDeployment({
if (event.type === 'created') {
deployingSpinner();
if (bar && !bar.complete) {
bar.tick(bar.total + 1);
}
now._host = event.payload.url;
await linkFolderToProject(

View File

@@ -923,9 +923,17 @@ export default class DevServer {
await once(this.watcher, 'ready');
// Configure the server to forward WebSocket "upgrade" events to the proxy.
this.server.on('upgrade', (req, socket, head) => {
this.server.on('upgrade', async (req, socket, head) => {
await this.startPromise;
if (!this.devProcessPort) {
this.output.debug(
`Detected "upgrade" event, but closing socket because no frontend dev server is running`
);
socket.destroy();
return;
}
const target = `http://localhost:${this.devProcessPort}`;
this.output.debug(`Detected upgrade event, proxying to ${target}`);
this.output.debug(`Detected "upgrade" event, proxying to ${target}`);
this.proxy.ws(req, socket, head, { target });
});
@@ -1364,6 +1372,7 @@ export default class DevServer {
const missRoutes = handleMap.get('miss') || [];
const hitRoutes = handleMap.get('hit') || [];
const errorRoutes = handleMap.get('error') || [];
const filesystemRoutes = handleMap.get('filesystem') || [];
const phases: (HandleValue | null)[] = [null, 'filesystem'];
let routeResult: RouteResult | null = null;
@@ -1481,6 +1490,11 @@ export default class DevServer {
// end the phase
break;
}
if (phase === null && filesystemRoutes.length === 0) {
// hack to skip the reset from null to filesystem
break;
}
}
if (!match && routeResult && errorRoutes.length > 0) {

View File

@@ -999,7 +999,7 @@ export class MissingDotenvVarsError extends NowError<
].join('\n');
}
message += '\nRead more: https://err.sh/now/missing-env-file';
message += '\nRead more: https://err.sh/vercel/missing-env-file';
super({
code: 'MISSING_DOTENV_VARS',

View File

@@ -186,7 +186,7 @@ export default class Now extends EventEmitter {
const { key } = error;
err.message =
`The env key ${key} has an invalid type: ${typeof env[key]}. ` +
'Please supply a String or a Number (https://err.sh/now-cli/env-value-invalid-type)';
'Please supply a String or a Number (https://err.sh/vercel-cli/env-value-invalid-type)';
} else if (code === 'unreferenced_build_specifications') {
const count = unreferencedBuildSpecs.length;
const prefix = count === 1 ? 'build' : 'builds';

View File

@@ -33,7 +33,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
boxen?: boxen.Options;
}
) {
const details = slug ? `https://err.sh/now/${slug}` : link;
const details = slug ? `https://err.sh/vercel/${slug}` : link;
print(
boxen(
@@ -66,7 +66,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
action = 'Learn More'
) {
print(`${chalk.red(`Error!`)} ${str}\n`);
const details = slug ? `https://err.sh/now/${slug}` : link;
const details = slug ? `https://err.sh/vercel/${slug}` : link;
if (details) {
print(`${chalk.bold(action)}: ${renderLink(details)}\n`);
}

View File

@@ -10,7 +10,7 @@ export default function error(...input: string[] | [APIError]) {
if (typeof input[0] === 'object') {
const { slug, message, link, action = 'Learn More' } = input[0];
messages = [message];
const details = slug ? `https://err.sh/now/${slug}` : link;
const details = slug ? `https://err.sh/vercel/${slug}` : link;
if (details) {
messages.push(`${chalk.bold(action)}: ${renderLink(details)}`);
}

View File

@@ -0,0 +1,3 @@
node_modules
.next
.vercel

View File

@@ -0,0 +1,8 @@
{
"private": true,
"dependencies": {
"next": "9.5.3",
"react": "16.13.1",
"react-dom": "16.13.1"
}
}

View File

@@ -0,0 +1,3 @@
module.exports = (_req, res) => {
res.end('Hello Routes');
};

View File

@@ -0,0 +1,10 @@
function Index() {
return (
<main>
<h1>Next.js with routes</h1>
<a href="/hello">/hello</a>
</main>
);
}
export default Index;

View File

@@ -0,0 +1,3 @@
{
"routes": [{ "src": "/hello", "dest": "/api/hello" }]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,68 @@
{
"compilerOptions": {
"module": "CommonJS"
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"module": "CommonJS",
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

View File

@@ -10,7 +10,7 @@ import retry from 'async-retry';
import { satisfies } from 'semver';
import { getDistTag } from '../../src/util/get-dist-tag';
import { version as cliVersion } from '../../package.json';
import { fetchTokenWithRetry } from '../../../../test/lib/deployment/now-deploy';
import { fetchCachedToken } from '../../../../test/lib/deployment/now-deploy';
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const isCanary = () => getDistTag(cliVersion) === 'canary';
@@ -245,7 +245,7 @@ function testFixtureStdio(
const cwd = isExample
? exampleAbsolute(directory)
: fixtureAbsolute(directory);
const token = await fetchTokenWithRetry();
const token = await fetchCachedToken();
let deploymentUrl;
// Deploy fixture and link project
@@ -1180,6 +1180,14 @@ test(
})
);
test(
'[vercel dev] 10a-nextjs-routes',
testFixtureStdio('10a-nextjs-routes', async testPath => {
await testPath(200, '/', /Next.js with routes/m);
await testPath(200, '/hello', /Hello Routes/m);
})
);
test(
'[vercel dev] 12-polymer-node',
testFixtureStdio(

View File

@@ -28,9 +28,6 @@ module.exports = async function prepare(session) {
'first.png': getImageFile(session, { size: 30 }),
'second.png': getImageFile(session, { size: 20 }),
},
'single-dotfile': {
'.testing': 'i am a dotfile',
},
'empty-directory': {},
'config-scope-property-email': {
'now.json': `{ "scope": "${session}@zeit.pub", "builds": [ { "src": "*.html", "use": "@now/static" } ] }`,
@@ -90,7 +87,7 @@ module.exports = async function prepare(session) {
}),
'now-build.js': `
const fs = require('fs');
fs.writeFileSync(
fs.writeFileSync(
'index.js',
fs.readFileSync('index.js', 'utf8')
.replace('BUILD_ENV_DEBUG', process.env.NOW_BUILDER_DEBUG ? 'on' : 'off'),
@@ -207,23 +204,6 @@ module.exports = async function prepare(session) {
},
}),
},
'alias-rules': {
'rules.json': JSON.stringify({
rules: [
// for example:
// { pathname: '/', dest: '' },
// { pathname: '/', dest: '', method: 'GET' }
// Will be generated by the actual test
],
}),
'invalid-rules.json': JSON.stringify({
what: { what: 0 },
}),
'invalid-type-rules.json': JSON.stringify({
rules: { what: 0 },
}),
'invalid-json-rules.json': '==ok',
},
'zero-config-next-js': {
'pages/index.js':
'export default () => <div><h1>Now CLI test</h1><p>Zero-config + Next.js</p></div>',

View File

@@ -2365,7 +2365,6 @@ test('invalid `--token`', async t => {
);
});
// We need to skip this test until `now-php` supports Runtime version 3
test('deploy a Lambda with a specific runtime', async t => {
const directory = fixture('lambda-with-php-runtime');
const output = await execute([directory, '--public', '--confirm']);
@@ -2374,7 +2373,8 @@ test('deploy a Lambda with a specific runtime', async t => {
const { host: url } = new URL(output.stdout);
const [build] = await getDeploymentBuildsByUrl(url);
const builds = await getDeploymentBuildsByUrl(url);
const build = builds.find(b => b.use && b.use.includes('php')) || builds[0];
t.is(build.use, 'vercel-php@0.1.0', JSON.stringify(build, null, 2));
});

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "9.0.2-canary.2",
"version": "9.0.3",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -37,14 +37,14 @@
]
},
"dependencies": {
"@vercel/build-utils": "2.5.2",
"@vercel/build-utils": "2.5.4",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",
"fs-extra": "8.0.1",
"ignore": "4.0.6",
"ms": "2.1.2",
"node-fetch": "2.6.0",
"node-fetch": "2.6.1",
"querystring": "^0.2.0",
"recursive-readdir": "2.2.2",
"sleep-promise": "8.0.1"

View File

@@ -1,10 +1,10 @@
const {
fetchTokenWithRetry,
fetchCachedToken,
// eslint-disable-next-line @typescript-eslint/no-var-requires
} = require('../../../test/lib/deployment/now-deploy.js');
export async function generateNewToken(): Promise<string> {
const token = await fetchTokenWithRetry();
const token = await fetchCachedToken();
return token;
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "2.6.25",
"version": "2.6.29",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -29,7 +29,7 @@
"@vercel/nft": "0.9.2",
"async-sema": "3.0.1",
"buffer-crc32": "0.2.13",
"escape-string-regexp": "3.0.0",
"escape-string-regexp": "2.0.0",
"execa": "2.0.4",
"find-up": "4.1.0",
"fs-extra": "7.0.0",

View File

@@ -59,7 +59,7 @@ export default async function createServerlessConfig(
const primaryConfigPath = path.join(entryPath, 'next.config.js');
const secondaryConfigPath = path.join(workPath, 'next.config.js');
const backupConfigName = `next.config.original.${Date.now()}.js`;
const backupConfigName = `next.config.__vercel_builder_backup__.js`;
const hasPrimaryConfig = fs.existsSync(primaryConfigPath);
const hasSecondaryConfig = fs.existsSync(secondaryConfigPath);

View File

@@ -19,6 +19,7 @@ import {
import { nodeFileTrace, NodeFileTraceReasons } from '@vercel/nft';
import { Sema } from 'async-sema';
import { ChildProcess, fork } from 'child_process';
// escape-string-regexp version must match Next.js version
import escapeStringRegexp from 'escape-string-regexp';
import findUp from 'find-up';
import { lstat, pathExists, readFile, remove, writeFile } from 'fs-extra';
@@ -31,6 +32,7 @@ import buildUtils from './build-utils';
import createServerlessConfig from './create-serverless-config';
import nextLegacyVersions from './legacy-versions';
import {
addLocaleOrDefault,
createLambdaFromPseudoLayers,
createPseudoLayer,
EnvConfig,
@@ -39,6 +41,7 @@ import {
getDynamicRoutes,
getExportIntent,
getExportStatus,
getImagesManifest,
getNextConfig,
getPathsInside,
getPrerenderManifest,
@@ -46,6 +49,7 @@ import {
getRoutesManifest,
getSourceFilePathFromPage,
isDynamicRoute,
normalizeLocalePath,
normalizePackageJson,
normalizePage,
PseudoLayer,
@@ -219,7 +223,12 @@ export const build = async ({
meta = {} as BuildParamsMeta,
}: BuildParamsType): Promise<{
routes: Route[];
images?: { domains: string[]; sizes: number[] };
output: Files;
wildcard?: Array<{
domain: string;
value: string;
}>;
watch?: string[];
childProcesses: ChildProcess[];
}> => {
@@ -302,6 +311,7 @@ export const build = async ({
return {
output: {},
images: undefined,
routes: await getRoutes(
entryPath,
entryDirectory,
@@ -367,7 +377,7 @@ export const build = async ({
}
console.log('Installing dependencies...');
await runNpmInstall(entryPath, ['--prefer-offline'], spawnOpts, meta);
await runNpmInstall(entryPath, [], spawnOpts, meta);
// Refetch Next version now that dependencies are installed.
// This will now resolve the actual installed Next version,
@@ -420,6 +430,7 @@ export const build = async ({
outputDirectory,
nextVersion
);
const imagesManifest = await getImagesManifest(entryPath, outputDirectory);
const prerenderManifest = await getPrerenderManifest(entryPath);
const headers: Route[] = [];
const rewrites: Route[] = [];
@@ -428,6 +439,24 @@ export const build = async ({
let dynamicRoutes: Route[] = [];
// whether they have enabled pages/404.js as the custom 404 page
let hasPages404 = false;
let buildId = '';
let escapedBuildId = '';
if (isLegacy || isSharedLambdas) {
try {
buildId = await readFile(
path.join(entryPath, outputDirectory, 'BUILD_ID'),
'utf8'
);
escapedBuildId = escapeStringRegexp(buildId);
} catch (err) {
throw new NowBuildError({
code: 'NOW_NEXT_NO_BUILD_ID',
message:
'The BUILD_ID file was not found in the Output Directory. Did you forget to run "next build" in your Build Command?',
});
}
}
if (routesManifest) {
switch (routesManifest.version) {
@@ -458,7 +487,7 @@ export const build = async ({
continue;
}
dataRoutes.push({
const route = {
src: (
dataRoute.namedDataRouteRegex || dataRoute.dataRouteRegex
).replace(/^\^/, `^${appMountPrefixNoTrailingSlash}`),
@@ -477,7 +506,31 @@ export const build = async ({
}`
),
check: true,
});
};
const { i18n } = routesManifest;
if (i18n) {
route.src = route.src.replace(
// we need to double escape the build ID here
// to replace it properly
`/${escapedBuildId}/`,
`/${escapedBuildId}/(?${
ssgDataRoute ? '<nextLocale>' : ':'
}${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})/`
);
// make sure to route to the correct prerender output
if (ssgDataRoute) {
route.dest = route.dest.replace(
`/${buildId}/`,
`/${buildId}/$nextLocale/`
);
}
}
dataRoutes.push(route);
}
}
@@ -519,6 +572,44 @@ export const build = async ({
}
}
if (imagesManifest) {
switch (imagesManifest.version) {
case 1: {
if (!imagesManifest.images) {
throw new NowBuildError({
code: 'NEXT_IMAGES_MISSING',
message:
'image-manifest.json "images" is required. Contact support if this continues to happen.',
});
}
const { images } = imagesManifest;
if (!Array.isArray(images.domains)) {
throw new NowBuildError({
code: 'NEXT_IMAGES_DOMAINS',
message:
'image-manifest.json "images.domains" must be an array. Contact support if this continues to happen.',
});
}
if (!Array.isArray(images.sizes)) {
throw new NowBuildError({
code: 'NEXT_IMAGES_DOMAINS',
message:
'image-manifest.json "images.sizes" must be an array. Contact support if this continues to happen.',
});
}
break;
}
default: {
throw new NowBuildError({
code: 'NEXT_IMAGES_VERSION_UNKNOWN',
message:
'This version of `@vercel/next` does not support the version of Next.js you are trying to deploy.\n' +
'Please upgrade your `@vercel/next` builder and try again. Contact support if this continues to happen.',
});
}
}
}
const userExport = await getExportStatus(entryPath);
if (userExport) {
@@ -564,6 +655,12 @@ export const build = async ({
return {
output,
images: imagesManifest?.images
? {
domains: imagesManifest.images.domains,
sizes: imagesManifest.images.sizes,
}
: undefined,
routes: [
// User headers
...headers,
@@ -645,12 +742,7 @@ export const build = async ({
if (isLegacy) {
debug('Running npm install --production...');
await runNpmInstall(
entryPath,
['--prefer-offline', '--production'],
spawnOpts,
meta
);
await runNpmInstall(entryPath, ['--production'], spawnOpts, meta);
}
if (process.env.NPM_AUTH_TOKEN) {
@@ -667,27 +759,7 @@ export const build = async ({
const staticPages: { [key: string]: FileFsRef } = {};
const dynamicPages: string[] = [];
let static404Page: string | undefined;
let buildId = '';
let page404Path = '';
let escapedBuildId = '';
if (isLegacy || isSharedLambdas) {
try {
buildId = await readFile(
path.join(entryPath, outputDirectory, 'BUILD_ID'),
'utf8'
);
escapedBuildId = escapeStringRegexp(buildId);
} catch (err) {
console.error(
'BUILD_ID not found in ".next". The "package.json" "build" script did not run "next build"'
);
throw new NowBuildError({
code: 'NOW_NEXT_NO_BUILD_ID',
message: 'Missing BUILD_ID',
});
}
}
if (isLegacy) {
const filesAfterBuild = await glob('**', entryPath);
@@ -757,13 +829,16 @@ export const build = async ({
],
};
const lambdaOptions = await getLambdaOptionsFromFunction({
sourceFile: await getSourceFilePathFromPage({
workPath: entryPath,
page,
}),
config,
});
let lambdaOptions = {};
if (config && config.functions) {
lambdaOptions = await getLambdaOptionsFromFunction({
sourceFile: await getSourceFilePathFromPage({
workPath: entryPath,
page,
}),
config,
});
}
debug(`Creating serverless function for page: "${page}"...`);
lambdas[path.join(entryDirectory, pathname)] = await createLambda({
@@ -793,7 +868,10 @@ export const build = async ({
Object.keys(staticPageFiles).forEach((page: string) => {
const pathname = page.replace(/\.html$/, '');
const routeName = normalizePage(pathname);
const routeName = normalizeLocalePath(
normalizePage(pathname),
routesManifest?.i18n?.locales
).pathname;
// Prerendered routes emit a `.html` file but should not be treated as a
// static page.
@@ -826,6 +904,17 @@ export const build = async ({
? path.join(entryDirectory, '_errors/404')
: undefined;
// TODO: locale specific 404s
const { i18n } = routesManifest || {};
if (!static404Page && i18n) {
static404Page = staticPages[
path.join(entryDirectory, i18n.defaultLocale, '404')
]
? path.join(entryDirectory, i18n.defaultLocale, '404')
: undefined;
}
// > 1 because _error is a lambda but isn't used if a static 404 is available
const pageKeys = Object.keys(pages);
let hasLambdas = !static404Page || pageKeys.length > 1;
@@ -869,7 +958,7 @@ export const build = async ({
};
const isApiPage = (page: string) =>
page.replace(/\\/g, '/').match(/serverless\/pages\/api/);
page.replace(/\\/g, '/').match(/serverless\/pages\/api(\/|\.js$)/);
const canUsePreviewMode = Object.keys(pages).some(page =>
isApiPage(pages[page].fsPath)
@@ -1138,25 +1227,36 @@ export const build = async ({
};
}
const pageLambdaRoute: Route = {
src: `^${escapeStringRegexp(outputName).replace(
/\/index$/,
'(/|/index|)'
)}/?$`,
dest: `${path.join('/', currentLambdaGroup.lambdaIdentifier)}`,
headers: {
'x-nextjs-page': outputName,
},
check: true,
const addPageLambdaRoute = (escapedOutputPath: string) => {
const pageLambdaRoute: Route = {
src: `^${escapedOutputPath.replace(/\/index$/, '(/|/index|)')}/?$`,
dest: `${path.join('/', currentLambdaGroup.lambdaIdentifier)}`,
headers: {
'x-nextjs-page': outputName,
},
check: true,
};
// we only need to add the additional routes if shared lambdas
// is enabled
if (routeIsDynamic) {
dynamicPageLambdaRoutes.push(pageLambdaRoute);
dynamicPageLambdaRoutesMap[outputName] = pageLambdaRoute;
} else {
pageLambdaRoutes.push(pageLambdaRoute);
}
};
// we only need to add the additional routes if shared lambdas
// is enabled
if (routeIsDynamic) {
dynamicPageLambdaRoutes.push(pageLambdaRoute);
dynamicPageLambdaRoutesMap[outputName] = pageLambdaRoute;
const { i18n } = routesManifest || {};
if (i18n) {
addPageLambdaRoute(
`[/]?(?:${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})?${escapeStringRegexp(outputName)}`
);
} else {
pageLambdaRoutes.push(pageLambdaRoute);
addPageLambdaRoute(escapeStringRegexp(outputName));
}
if (page === '_error.js' || (hasPages404 && page === '404.js')) {
@@ -1285,7 +1385,32 @@ export const build = async ({
new Set(prerenderManifest.omittedRoutes)
).then(arr =>
arr.map(route => {
route.src = route.src.replace('^', `^${dynamicPrefix}`);
const { i18n } = routesManifest || {};
if (i18n) {
const { pathname } = url.parse(route.dest!);
const isFallback = prerenderManifest.fallbackRoutes[pathname!];
route.src = route.src.replace(
'^',
`^${dynamicPrefix ? `${dynamicPrefix}[/]?` : '[/]?'}(?${
isFallback ? '<nextLocale>' : ':'
}${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})?`
);
if (isFallback) {
// ensure destination has locale prefix to match prerender output
// path so that the prerender object is used
route.dest = route.dest!.replace(
`${path.join('/', entryDirectory, '/')}`,
`${path.join('/', entryDirectory, '$nextLocale', '/')}`
);
}
} else {
route.src = route.src.replace('^', `^${dynamicPrefix}`);
}
return route;
})
);
@@ -1318,13 +1443,38 @@ export const build = async ({
/\/\/ __LAUNCHER_PAGE_HANDLER__/g,
`
const url = require('url');
${
routesManifest?.i18n
? `
function stripLocalePath(pathname) {
// first item will be empty string from splitting at first char
const pathnameParts = pathname.split('/')
;(${JSON.stringify(
routesManifest.i18n.locales
)}).some((locale) => {
if (pathnameParts[1].toLowerCase() === locale.toLowerCase()) {
pathnameParts.splice(1, 1)
pathname = pathnameParts.join('/') || '/'
return true
}
return false
})
return pathname
}
`
: `function stripLocalePath(pathname) { return pathname }`
}
page = function(req, res) {
try {
const pages = {
${groupPageKeys
.map(
page =>
`'${page}': require('./${path.join(
`'${page}': () => require('./${path.join(
'./',
group.pages[page].pageFileName
)}')`
@@ -1333,7 +1483,7 @@ export const build = async ({
${
'' /*
creates a mapping of the page and the page's module e.g.
'/about': require('./.next/serverless/pages/about.js')
'/about': () => require('./.next/serverless/pages/about.js')
*/
}
}
@@ -1342,7 +1492,7 @@ export const build = async ({
if (!toRender) {
try {
const { pathname } = url.parse(req.url)
toRender = pathname.replace(/\\/$/, '')
toRender = stripLocalePath(pathname).replace(/\\/$/, '')
} catch (_) {
// handle failing to parse url
res.statusCode = 400
@@ -1361,6 +1511,7 @@ export const build = async ({
.replace(new RegExp('/_next/data/${escapedBuildId}/'), '/')
.replace(/\\.json$/, '')
toRender = stripLocalePath(toRender)
currentPage = pages[toRender]
}
@@ -1393,7 +1544,10 @@ export const build = async ({
res.statusCode = 500
return res.end('internal server error')
}
const method = currentPage.render || currentPage.default || currentPage
const mod = currentPage()
const method = mod.render || mod.default || mod
return method(req, res)
} catch (err) {
console.error('Unhandled error during request:', err)
@@ -1468,7 +1622,15 @@ export const build = async ({
let prerenderGroup = 1;
const onPrerenderRoute = (
routeKey: string,
{ isBlocking, isFallback }: { isBlocking: boolean; isFallback: boolean }
{
isBlocking,
isFallback,
locale,
}: {
isBlocking: boolean;
isFallback: boolean;
locale?: string;
}
) => {
if (isBlocking && isFallback) {
throw new NowBuildError({
@@ -1478,7 +1640,22 @@ export const build = async ({
}
// Get the route file as it'd be mounted in the builder output
const routeFileNoExt = routeKey === '/' ? '/index' : routeKey;
let routeFileNoExt = routeKey === '/' ? '/index' : routeKey;
const origRouteFileNoExt = routeFileNoExt;
const nonDynamicSsg =
!isFallback &&
!isBlocking &&
!prerenderManifest.staticRoutes[routeKey].srcRoute;
// if there isn't a srcRoute then it's a non-dynamic SSG page and
if (nonDynamicSsg || isFallback) {
routeFileNoExt = addLocaleOrDefault(
routeFileNoExt,
routesManifest,
locale
);
}
const htmlFsRef = isBlocking
? // Blocking pages do not have an HTML fallback
@@ -1488,7 +1665,11 @@ export const build = async ({
pagesDir,
isFallback
? // Fallback pages have a special file.
prerenderManifest.fallbackRoutes[routeKey].fallback
addLocaleOrDefault(
prerenderManifest.fallbackRoutes[routeKey].fallback,
routesManifest,
locale
)
: // Otherwise, the route itself should exist as a static HTML
// file.
`${routeFileNoExt}.html`
@@ -1527,14 +1708,25 @@ export const build = async ({
}
const outputPathPage = path.posix.join(entryDirectory, routeFileNoExt);
const outputPathPageOrig = path.posix.join(
entryDirectory,
origRouteFileNoExt
);
let lambda: undefined | Lambda;
const outputPathData = path.posix.join(entryDirectory, dataRoute);
let outputPathData = path.posix.join(entryDirectory, dataRoute);
if (nonDynamicSsg || isFallback) {
outputPathData = outputPathData.replace(
new RegExp(`${escapeStringRegexp(origRouteFileNoExt)}.json$`),
`${routeFileNoExt}.json`
);
}
if (isSharedLambdas) {
const outputSrcPathPage = path.join(
'/',
srcRoute == null
? outputPathPage
? outputPathPageOrig
: path.join(entryDirectory, srcRoute === '/' ? '/index' : srcRoute)
);
@@ -1543,7 +1735,7 @@ export const build = async ({
} else {
const outputSrcPathPage =
srcRoute == null
? outputPathPage
? outputPathPageOrig
: path.posix.join(
entryDirectory,
srcRoute === '/' ? '/index' : srcRoute
@@ -1592,6 +1784,18 @@ export const build = async ({
++prerenderGroup;
}
if ((nonDynamicSsg || isFallback) && routesManifest?.i18n && !locale) {
// load each locale
for (const locale of routesManifest.i18n.locales) {
if (locale === routesManifest.i18n.defaultLocale) continue;
onPrerenderRoute(routeKey, {
isBlocking,
isFallback,
locale,
});
}
}
};
Object.keys(prerenderManifest.staticRoutes).forEach(route =>
@@ -1720,6 +1924,8 @@ export const build = async ({
}
}
const { i18n } = routesManifest || {};
return {
output: {
...publicDirectoryFiles,
@@ -1730,6 +1936,23 @@ export const build = async ({
...staticFiles,
...staticDirectoryFiles,
},
wildcard: i18n?.domains
? i18n?.domains.map(item => {
return {
domain: item.domain,
value:
item.defaultLocale === i18n.defaultLocale
? ''
: `/${item.defaultLocale}`,
};
})
: undefined,
images: imagesManifest?.images
? {
domains: imagesManifest.images.domains,
sizes: imagesManifest.images.sizes,
}
: undefined,
/*
Desired routes order
- Runtime headers
@@ -1745,14 +1968,130 @@ export const build = async ({
...headers,
// redirects
...redirects,
...redirects.map(redir => {
if (i18n) {
// detect the trailing slash redirect and make sure it's
// kept above the wildcard mapping to prevent erroneous redirects
// since non-continue routes come after continue the $wildcard
// route will come before the redirect otherwise and if the
// redirect is triggered it breaks locale mapping
if (
redir.status === 308 &&
(redir.dest === '/$1' || redir.dest === '/$1/')
) {
// we set continue true
(redir as any).continue = true;
}
}
return redir;
}),
...(i18n
? [
// Handle auto-adding current default locale to path based on $wildcard
{
src: `^${path.join(
'/',
entryDirectory,
'/'
)}(?!(?:_next/.*|${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})(?:/.*|$))(.*)$`,
// TODO: this needs to contain or not contain a trailing slash
// to prevent the trailing slash redirect from being triggered
dest: '$wildcard/$1',
continue: true,
},
// Handle redirecting to locale specific domains
...(i18n.domains
? [
{
// TODO: enable redirecting between domains, will require
// updating the src with the desired locales to redirect
src: '/',
locale: {
redirect: i18n.domains.reduce(
(prev: Record<string, string>, item) => {
prev[item.defaultLocale] = `http${
item.http ? '' : 's'
}://${item.domain}/`;
return prev;
},
{}
),
cookie: 'NEXT_LOCALE',
},
continue: true,
},
]
: []),
// Handle redirecting to locale paths
{
// TODO: enable redirecting between paths, will require
// updating the src with the desired locales to redirect.
// if default locale is included in this src it won't be visitable
// by users who prefer another language since the cookie isn't set
// on redirect currently like in `next start`
src: '/',
locale: {
redirect: i18n.locales.reduce(
(prev: Record<string, string>, locale) => {
prev[locale] =
locale === i18n.defaultLocale ? `/` : `/${locale}`;
return prev;
},
{}
),
cookie: 'NEXT_LOCALE',
},
continue: true,
},
{
src: `^${path.join('/', entryDirectory)}$`,
dest: `/${i18n.defaultLocale}`,
continue: true,
},
// Auto-prefix non-locale path with default locale
{
src: `^${path.join(
'/',
entryDirectory,
'/'
)}(?!(?:_next/.*|${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})(?:/.*|$))(.*)$`,
dest: `/${i18n.defaultLocale}/$1`,
continue: true,
},
]
: []),
// Make sure to 404 for the /404 path itself
{
src: path.join('/', entryDirectory, '404'),
status: 404,
continue: true,
},
...(i18n
? [
{
src: `${path.join(
'/',
entryDirectory,
'/'
)}(?:${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})?[/]?404`,
status: 404,
continue: true,
},
]
: [
{
src: path.join('/', entryDirectory, '404'),
status: 404,
continue: true,
},
]),
// Next.js page lambdas, `static/` folder, reserved assets, and `public/`
// folder
@@ -1782,6 +2121,40 @@ export const build = async ({
dest: '$0',
},
// remove default locale prefix to check public files
...(i18n
? [
{
src: `${path.join(
'/',
entryDirectory,
i18n.defaultLocale,
'/'
)}(.*)`,
dest: `${path.join('/', entryDirectory, '/')}$1`,
check: true,
},
]
: []),
// for non-shared lambdas remove locale prefix if present
// to allow checking for lambda
...(isSharedLambdas || !i18n
? []
: [
{
src: `${path.join(
'/',
entryDirectory,
'/'
)}(?:${i18n?.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})/(.*)`,
dest: '/$1',
check: true,
},
]),
// routes that are called after each rewrite or after routes
// if there no rewrites
{ handle: 'rewrite' },
@@ -1822,39 +2195,60 @@ export const build = async ({
// Custom Next.js 404 page
{ handle: 'error' } as Handler,
isSharedLambdas
? {
src: path.join('/', entryDirectory, '.*'),
// if static 404 is not present but we have pages/404.js
// it is a lambda due to _app getInitialProps
dest: path.join(
'/',
(static404Page
? static404Page
: pageLambdaMap[page404Path]) as string
),
status: 404,
headers: {
'x-nextjs-page': page404Path,
...(i18n && static404Page
? [
{
src: `${path.join(
'/',
entryDirectory,
'/'
)}(?<nextLocale>${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})(/.*|$)`,
dest: '/$nextLocale/404',
status: 404,
},
}
: {
src: path.join('/', entryDirectory, '.*'),
// if static 404 is not present but we have pages/404.js
// it is a lambda due to _app getInitialProps
dest: static404Page
? path.join('/', static404Page)
: path.join(
'/',
entryDirectory,
hasPages404 &&
lambdas[path.join('./', entryDirectory, '404')]
? '404'
: '_error'
),
status: 404,
},
{
src: path.join('/', entryDirectory, '.*'),
dest: `/${i18n.defaultLocale}/404`,
status: 404,
},
]
: [
isSharedLambdas
? {
src: path.join('/', entryDirectory, '.*'),
// if static 404 is not present but we have pages/404.js
// it is a lambda due to _app getInitialProps
dest: path.join(
'/',
(static404Page
? static404Page
: pageLambdaMap[page404Path]) as string
),
status: 404,
headers: {
'x-nextjs-page': page404Path,
},
}
: {
src: path.join('/', entryDirectory, '.*'),
// if static 404 is not present but we have pages/404.js
// it is a lambda due to _app getInitialProps
dest: static404Page
? path.join('/', static404Page)
: path.join(
'/',
entryDirectory,
hasPages404 &&
lambdas[path.join('./', entryDirectory, '404')]
? '404'
: '_error'
),
status: 404,
},
]),
]),
],
watch: [],

View File

@@ -326,6 +326,15 @@ export type RoutesManifest = {
namedDataRouteRegex?: string;
routeKeys?: { [named: string]: string };
}>;
i18n?: {
defaultLocale: string;
locales: string[];
domains?: Array<{
http?: boolean;
domain: string;
defaultLocale: string;
}>;
};
};
export async function getRoutesManifest(
@@ -502,6 +511,38 @@ export async function getDynamicRoutes(
return routes;
}
type ImagesManifest = {
version: number;
images: {
sizes: number[];
domains: string[];
};
};
export async function getImagesManifest(
entryPath: string,
outputDirectory: string
): Promise<ImagesManifest | undefined> {
const pathImagesManifest = path.join(
entryPath,
outputDirectory,
'images-manifest.json'
);
const hasImagesManifest = await fs
.access(pathImagesManifest)
.then(() => true)
.catch(() => false);
if (!hasImagesManifest) {
return undefined;
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
const imagesManifest: ImagesManifest = require(pathImagesManifest);
return imagesManifest;
}
function syncEnvVars(base: EnvConfig, removeEnv: EnvConfig, addEnv: EnvConfig) {
// Remove any env vars from `removeEnv`
// that are not present in the `addEnv`
@@ -1000,11 +1041,76 @@ async function getSourceFilePathFromPage({
workPath: string;
page: string;
}) {
let fsPath = path.join(workPath, 'pages', page);
if (await usesSrcDirectory(workPath)) {
return path.join('src', 'pages', page);
fsPath = path.join(workPath, 'src', 'pages', page);
}
return path.join('pages', page);
if (fs.existsSync(fsPath)) {
return path.relative(workPath, fsPath);
}
const extensionless = fsPath.slice(0, -3); // remove ".js"
fsPath = extensionless + '.ts';
if (fs.existsSync(fsPath)) {
return path.relative(workPath, fsPath);
}
if (isDirectory(extensionless)) {
fsPath = path.join(extensionless, 'index.js');
if (fs.existsSync(fsPath)) {
return path.relative(workPath, fsPath);
}
fsPath = path.join(extensionless, 'index.ts');
if (fs.existsSync(fsPath)) {
return path.relative(workPath, fsPath);
}
}
console.log(`WARNING: Unable to find source file for page ${page}`);
return '';
}
function isDirectory(path: string) {
return fs.existsSync(path) && fs.lstatSync(path).isDirectory();
}
export function normalizeLocalePath(
pathname: string,
locales?: string[]
): {
detectedLocale?: string;
pathname: string;
} {
let detectedLocale: string | undefined;
// first item will be empty string from splitting at first char
const pathnameParts = pathname.split('/');
(locales || []).some(locale => {
if (pathnameParts[1].toLowerCase() === locale.toLowerCase()) {
detectedLocale = locale;
pathnameParts.splice(1, 1);
pathname = pathnameParts.join('/') || '/';
return true;
}
return false;
});
return {
pathname,
detectedLocale,
};
}
export function addLocaleOrDefault(
pathname: string,
routesManifest?: RoutesManifest,
locale?: string
) {
if (!routesManifest?.i18n) return pathname;
if (!locale) locale = routesManifest.i18n.defaultLocale;
return locale ? `/${locale}${pathname}` : pathname;
}
export {

View File

@@ -0,0 +1,20 @@
module.exports = {
experimental: {
i18n: {
locales: ['nl-NL', 'nl-BE', 'nl', 'fr-BE', 'fr', 'en-US', 'en'],
defaultLocale: 'en-US',
// TODO: testing locale domains support, will require custom
// testing set-up as test accounts are used currently
domains: [
{
domain: 'example.be',
defaultLocale: 'nl-BE',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
],
},
},
};

View File

@@ -0,0 +1,318 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next",
"config": {
"sharedLambdas": false
}
}
],
"probes": [
{
"path": "/",
"headers": {
"accept-language": "en;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//en/"
}
},
{
"path": "/",
"headers": {
"accept-language": "nl;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//nl/"
}
},
{
"path": "/",
"headers": {
"accept-language": "nl-NL;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//nl-NL/"
}
},
{
"path": "/",
"headers": {
"accept-language": "fr;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//fr/"
}
},
{
"path": "/",
"headers": {
"accept-language": "en-US;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "index page"
},
{
"path": "/en-US",
"headers": {
"accept-language": "nl;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "index page"
},
{
"path": "/",
"status": 200,
"mustContain": "index page"
},
{
"path": "/",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/en",
"status": 200,
"mustContain": "index page"
},
{
"path": "/en",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/fr",
"status": 200,
"mustContain": "index page"
},
{
"path": "/fr",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/nl",
"status": 200,
"mustContain": "index page"
},
{
"path": "/nl",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/nl-NL",
"status": 200,
"mustContain": "index page"
},
{
"path": "/nl-NL",
"status": 200,
"mustContain": ">nl-NL<"
},
{
"path": "/non-existent",
"status": 404
},
{
"path": "/fr/non-existent",
"status": 404,
"mustContain": "lang=\"fr\""
},
{
"path": "/en/non-existent",
"status": 404,
"mustContain": "lang=\"en\""
},
{
"path": "/en-US/non-existent",
"status": 404,
"mustContain": "lang=\"en-US\""
},
{
"path": "/nl/non-existent",
"status": 404,
"mustContain": "lang=\"nl\""
},
{
"path": "/nl-NL/non-existent",
"status": 404,
"mustContain": "lang=\"nl-NL\""
},
{
"path": "/hello.txt",
"status": 200,
"mustContain": "hello world!"
},
{
"path": "/gsp",
"status": 200,
"mustContain": "gsp page"
},
{
"path": "/gsp",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/en/gsp",
"status": 200,
"mustContain": "gsp page"
},
{
"path": "/en/gsp",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/nl/gsp",
"status": 200,
"mustContain": "gsp page"
},
{
"path": "/nl/gsp",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/fr/gsp",
"status": 200,
"mustContain": "gsp page"
},
{
"path": "/fr/gsp",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/gssp",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/gssp",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/en/gssp",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/en/gssp",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/nl/gssp",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/nl/gssp",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/fr/gssp",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/fr/gssp",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/gssp/first",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/gssp/first",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/gssp/first",
"status": 200,
"mustContain": "slug\":\"first\""
},
{
"path": "/en/gssp/first",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/en/gssp/first",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/en/gssp/first",
"status": 200,
"mustContain": "slug\":\"first\""
},
{
"path": "/nl/gssp/first",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/nl/gssp/first",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/nl/gssp/first",
"status": 200,
"mustContain": "slug\":\"first\""
},
{
"path": "/fr/gssp/first",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/fr/gssp/first",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/fr/gssp/first",
"status": 200,
"mustContain": "slug\":\"first\""
}
]
}

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1,31 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="another">another page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getServerSideProps = ({ locale, locales }) => {
return {
props: {
locale,
locales,
},
};
};

View File

@@ -0,0 +1,21 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="auto-export">auto-export page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
</>
);
}

View File

@@ -0,0 +1,44 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
if (router.isFallback) return 'Loading...';
return (
<>
<p id="gsp">gsp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getStaticProps = ({ params, locale, locales }) => {
return {
props: {
params,
locale,
locales,
},
};
};
export const getStaticPaths = () => {
return {
// the default locale will be used since one isn't defined here
paths: ['first', 'second'].map(slug => ({
params: { slug },
})),
fallback: true,
};
};

View File

@@ -0,0 +1,32 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="gsp">gsp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
// TODO: should non-dynamic GSP pages pre-render for each locale?
export const getStaticProps = ({ locale, locales }) => {
return {
props: {
locale,
locales,
},
};
};

View File

@@ -0,0 +1,46 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
if (router.isFallback) return 'Loading...';
return (
<>
<p id="gsp">gsp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getStaticProps = ({ params, locale, locales }) => {
return {
props: {
params,
locale,
locales,
},
};
};
export const getStaticPaths = () => {
return {
paths: [
{ params: { slug: 'first' } },
'/gsp/no-fallback/second',
{ params: { slug: 'first' }, locale: 'en-US' },
'/nl-NL/gsp/no-fallback/second',
],
fallback: false,
};
};

View File

@@ -0,0 +1,32 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="gssp">gssp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getServerSideProps = ({ params, locale, locales }) => {
return {
props: {
params,
locale,
locales,
},
};
};

View File

@@ -0,0 +1,31 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="gssp">gssp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getServerSideProps = ({ locale, locales }) => {
return {
props: {
locale,
locales,
},
};
};

View File

@@ -0,0 +1,46 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="index">index page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/another">
<a id="to-another">to /another</a>
</Link>
<br />
<Link href="/gsp">
<a id="to-gsp">to /gsp</a>
</Link>
<br />
<Link href="/gsp/fallback/first">
<a id="to-fallback-first">to /gsp/fallback/first</a>
</Link>
<br />
<Link href="/gsp/fallback/hello">
<a id="to-fallback-hello">to /gsp/fallback/hello</a>
</Link>
<br />
<Link href="/gsp/no-fallback/first">
<a id="to-no-fallback-first">to /gsp/no-fallback/first</a>
</Link>
<br />
<Link href="/gssp">
<a id="to-gssp">to /gssp</a>
</Link>
<br />
<Link href="/gssp/first">
<a id="to-gssp-slug">to /gssp/first</a>
</Link>
<br />
</>
);
}

View File

@@ -0,0 +1,54 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
const { nextLocale } = router.query;
return (
<>
<p id="links">links page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/another" locale={nextLocale}>
<a id="to-another">to /another</a>
</Link>
<br />
<Link href="/gsp" locale={nextLocale}>
<a id="to-gsp">to /gsp</a>
</Link>
<br />
<Link href="/gsp/fallback/first" locale={nextLocale}>
<a id="to-fallback-first">to /gsp/fallback/first</a>
</Link>
<br />
<Link href="/gsp/fallback/hello" locale={nextLocale}>
<a id="to-fallback-hello">to /gsp/fallback/hello</a>
</Link>
<br />
<Link href="/gsp/no-fallback/first" locale={nextLocale}>
<a id="to-no-fallback-first">to /gsp/no-fallback/first</a>
</Link>
<br />
<Link href="/gssp" locale={nextLocale}>
<a id="to-gssp">to /gssp</a>
</Link>
<br />
<Link href="/gssp/first" locale={nextLocale}>
<a id="to-gssp-slug">to /gssp/first</a>
</Link>
<br />
</>
);
}
// make SSR page so we have query values immediately
export const getServerSideProps = () => {
return {
props: {},
};
};

View File

@@ -0,0 +1 @@
hello world!

View File

@@ -0,0 +1,20 @@
module.exports = {
experimental: {
i18n: {
locales: ['nl-NL', 'nl-BE', 'nl', 'fr-BE', 'fr', 'en-US', 'en'],
defaultLocale: 'en-US',
// TODO: testing locale domains support, will require custom
// testing set-up as test accounts are used currently
domains: [
{
domain: 'example.be',
defaultLocale: 'nl-BE',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
],
},
},
};

View File

@@ -0,0 +1,315 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
],
"probes": [
{
"path": "/",
"headers": {
"accept-language": "en;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//en/"
}
},
{
"path": "/",
"headers": {
"accept-language": "nl;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//nl/"
}
},
{
"path": "/",
"headers": {
"accept-language": "nl-NL;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//nl-NL/"
}
},
{
"path": "/",
"headers": {
"accept-language": "fr;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//fr/"
}
},
{
"path": "/",
"headers": {
"accept-language": "en-US;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "index page"
},
{
"path": "/en-US",
"headers": {
"accept-language": "nl;q=0.9"
},
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "index page"
},
{
"path": "/",
"status": 200,
"mustContain": "index page"
},
{
"path": "/",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/en",
"status": 200,
"mustContain": "index page"
},
{
"path": "/en",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/fr",
"status": 200,
"mustContain": "index page"
},
{
"path": "/fr",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/nl",
"status": 200,
"mustContain": "index page"
},
{
"path": "/nl",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/nl-NL",
"status": 200,
"mustContain": "index page"
},
{
"path": "/nl-NL",
"status": 200,
"mustContain": ">nl-NL<"
},
{
"path": "/non-existent",
"status": 404
},
{
"path": "/fr/non-existent",
"status": 404,
"mustContain": "lang=\"fr\""
},
{
"path": "/en/non-existent",
"status": 404,
"mustContain": "lang=\"en\""
},
{
"path": "/en-US/non-existent",
"status": 404,
"mustContain": "lang=\"en-US\""
},
{
"path": "/nl/non-existent",
"status": 404,
"mustContain": "lang=\"nl\""
},
{
"path": "/nl-NL/non-existent",
"status": 404,
"mustContain": "lang=\"nl-NL\""
},
{
"path": "/hello.txt",
"status": 200,
"mustContain": "hello world!"
},
{
"path": "/gsp",
"status": 200,
"mustContain": "gsp page"
},
{
"path": "/gsp",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/en/gsp",
"status": 200,
"mustContain": "gsp page"
},
{
"path": "/en/gsp",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/nl/gsp",
"status": 200,
"mustContain": "gsp page"
},
{
"path": "/nl/gsp",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/fr/gsp",
"status": 200,
"mustContain": "gsp page"
},
{
"path": "/fr/gsp",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/gssp",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/gssp",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/en/gssp",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/en/gssp",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/nl/gssp",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/nl/gssp",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/fr/gssp",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/fr/gssp",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/gssp/first",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/gssp/first",
"status": 200,
"mustContain": ">en-US<"
},
{
"path": "/gssp/first",
"status": 200,
"mustContain": "slug\":\"first\""
},
{
"path": "/en/gssp/first",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/en/gssp/first",
"status": 200,
"mustContain": ">en<"
},
{
"path": "/en/gssp/first",
"status": 200,
"mustContain": "slug\":\"first\""
},
{
"path": "/nl/gssp/first",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/nl/gssp/first",
"status": 200,
"mustContain": ">nl<"
},
{
"path": "/nl/gssp/first",
"status": 200,
"mustContain": "slug\":\"first\""
},
{
"path": "/fr/gssp/first",
"status": 200,
"mustContain": "gssp page"
},
{
"path": "/fr/gssp/first",
"status": 200,
"mustContain": ">fr<"
},
{
"path": "/fr/gssp/first",
"status": 200,
"mustContain": "slug\":\"first\""
}
]
}

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1,31 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="another">another page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getServerSideProps = ({ locale, locales }) => {
return {
props: {
locale,
locales,
},
};
};

View File

@@ -0,0 +1,21 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="auto-export">auto-export page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
</>
);
}

View File

@@ -0,0 +1,44 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
if (router.isFallback) return 'Loading...';
return (
<>
<p id="gsp">gsp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getStaticProps = ({ params, locale, locales }) => {
return {
props: {
params,
locale,
locales,
},
};
};
export const getStaticPaths = () => {
return {
// the default locale will be used since one isn't defined here
paths: ['first', 'second'].map(slug => ({
params: { slug },
})),
fallback: true,
};
};

View File

@@ -0,0 +1,32 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="gsp">gsp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
// TODO: should non-dynamic GSP pages pre-render for each locale?
export const getStaticProps = ({ locale, locales }) => {
return {
props: {
locale,
locales,
},
};
};

View File

@@ -0,0 +1,46 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
if (router.isFallback) return 'Loading...';
return (
<>
<p id="gsp">gsp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getStaticProps = ({ params, locale, locales }) => {
return {
props: {
params,
locale,
locales,
},
};
};
export const getStaticPaths = () => {
return {
paths: [
{ params: { slug: 'first' } },
'/gsp/no-fallback/second',
{ params: { slug: 'first' }, locale: 'en-US' },
'/nl-NL/gsp/no-fallback/second',
],
fallback: false,
};
};

View File

@@ -0,0 +1,32 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="gssp">gssp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getServerSideProps = ({ params, locale, locales }) => {
return {
props: {
params,
locale,
locales,
},
};
};

View File

@@ -0,0 +1,31 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="gssp">gssp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/">
<a id="to-index">to /</a>
</Link>
<br />
</>
);
}
export const getServerSideProps = ({ locale, locales }) => {
return {
props: {
locale,
locales,
},
};
};

View File

@@ -0,0 +1,46 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
return (
<>
<p id="index">index page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/another">
<a id="to-another">to /another</a>
</Link>
<br />
<Link href="/gsp">
<a id="to-gsp">to /gsp</a>
</Link>
<br />
<Link href="/gsp/fallback/first">
<a id="to-fallback-first">to /gsp/fallback/first</a>
</Link>
<br />
<Link href="/gsp/fallback/hello">
<a id="to-fallback-hello">to /gsp/fallback/hello</a>
</Link>
<br />
<Link href="/gsp/no-fallback/first">
<a id="to-no-fallback-first">to /gsp/no-fallback/first</a>
</Link>
<br />
<Link href="/gssp">
<a id="to-gssp">to /gssp</a>
</Link>
<br />
<Link href="/gssp/first">
<a id="to-gssp-slug">to /gssp/first</a>
</Link>
<br />
</>
);
}

View File

@@ -0,0 +1,54 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Page(props) {
const router = useRouter();
const { nextLocale } = router.query;
return (
<>
<p id="links">links page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
<p id="router-as-path">{router.asPath}</p>
<Link href="/another" locale={nextLocale}>
<a id="to-another">to /another</a>
</Link>
<br />
<Link href="/gsp" locale={nextLocale}>
<a id="to-gsp">to /gsp</a>
</Link>
<br />
<Link href="/gsp/fallback/first" locale={nextLocale}>
<a id="to-fallback-first">to /gsp/fallback/first</a>
</Link>
<br />
<Link href="/gsp/fallback/hello" locale={nextLocale}>
<a id="to-fallback-hello">to /gsp/fallback/hello</a>
</Link>
<br />
<Link href="/gsp/no-fallback/first" locale={nextLocale}>
<a id="to-no-fallback-first">to /gsp/no-fallback/first</a>
</Link>
<br />
<Link href="/gssp" locale={nextLocale}>
<a id="to-gssp">to /gssp</a>
</Link>
<br />
<Link href="/gssp/first" locale={nextLocale}>
<a id="to-gssp-slug">to /gssp/first</a>
</Link>
<br />
</>
);
}
// make SSR page so we have query values immediately
export const getServerSideProps = () => {
return {
props: {},
};
};

View File

@@ -0,0 +1 @@
hello world!

View File

@@ -7,6 +7,15 @@
"functions": {
"src/pages/api/memory.js": {
"memory": 128
},
"src/pages/api/index.js": {
"memory": 192
},
"src/pages/api/sub/index.ts": {
"memory": 128
},
"src/pages/api/sub/another.ts": {
"memory": 192
}
}
}
@@ -18,6 +27,21 @@
"status": 200,
"mustContain": "128"
},
{
"path": "/api",
"status": 200,
"mustContain": "192"
},
{
"path": "/api/sub",
"status": 200,
"mustContain": "128"
},
{
"path": "/api/sub/another",
"status": 200,
"mustContain": "192"
},
{
"logMustContain": "WARNING: Your application is being opted out of \"@vercel/next\" optimized lambdas mode due to `functions` config"
}

View File

@@ -1,7 +1,11 @@
{
"private": true,
"dependencies": {
"@types/node": "^12.12.56",
"@types/react": "^16.9.49",
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6"
"react": "^16.13.1",
"react-dom": "^16.13.1",
"typescript": "^4.0.2"
}
}

View File

@@ -0,0 +1,3 @@
export default function (req, res) {
res.end(`${process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE}`);
}

View File

@@ -0,0 +1,3 @@
export default function (req: any, res: any) {
res.end(`${process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE}`);
}

View File

@@ -0,0 +1,3 @@
export default function (req: any, res: any) {
res.end(`${process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE}`);
}

View File

@@ -135,6 +135,27 @@
"path": "/_next/data/testing-build-id/nofallback/nope.json",
"status": 404
},
{
"path": "/api-docs/first",
"status": 200,
"mustContain": "API Docs"
},
{
"path": "/api-docs/second",
"status": 200,
"mustContain": "Loading..."
},
{
"path": "/_next/data/testing-build-id/api-docs/first.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/HIT|STALE|PRERENDER/"
}
},
{
"path": "/_next/data/testing-build-id/api-docs/second.json",
"status": 200
},
{
"logMustNotContain": "WARNING: your application is being opted out of @vercel/next's optimized lambdas mode due to legacy routes"
},

View File

@@ -0,0 +1,27 @@
import { useRouter } from 'next/router';
export const getStaticProps = () => {
return {
props: {
hello: 'world',
},
};
};
export const getStaticPaths = () => {
return {
paths: ['/api-docs/first'],
fallback: true,
};
};
export default function Slug(props) {
if (useRouter().isFallback) return 'Loading...';
return (
<>
<p id="api-docs">API Docs</p>
<p id="props">{JSON.stringify(props)}</p>
</>
);
}

View File

@@ -207,7 +207,9 @@ it(
expect(contents.some(name => name === 'next.config.js')).toBeTruthy();
expect(
contents.some(name => name.includes('next.config.original.'))
contents.some(name =>
name.includes('next.config.__vercel_builder_backup__')
)
).toBeTruthy();
},
FOUR_MINUTES
@@ -278,7 +280,9 @@ it(
expect(contents.some(name => name === 'next.config.js')).toBeTruthy();
expect(
contents.some(name => name.includes('next.config.original.'))
contents.some(name =>
name.includes('next.config.__vercel_builder_backup__')
)
).toBeTruthy();
},
FOUR_MINUTES
@@ -345,7 +349,9 @@ it(
expect(contents.some(name => name === 'next.config.js')).toBeTruthy();
expect(
contents.some(name => name.includes('next.config.original.'))
contents.some(name =>
name.includes('next.config.__vercel_builder_backup__')
)
).toBeTruthy();
},
FOUR_MINUTES
@@ -380,7 +386,9 @@ it(
expect(contents.some(name => name === 'next.config.js')).toBeTruthy();
expect(
contents.some(name => name.includes('next.config.original.'))
contents.some(name =>
name.includes('next.config.__vercel_builder_backup__')
)
).toBeFalsy();
},
FOUR_MINUTES
@@ -422,7 +430,9 @@ it(
expect(contents.some(name => name === 'next.config.js')).toBeTruthy();
expect(
contents.some(name => name.includes('next.config.original.'))
contents.some(name =>
name.includes('next.config.__vercel_builder_backup__')
)
).toBeFalsy();
},
FOUR_MINUTES

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "1.8.2",
"version": "1.8.4",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -38,7 +38,7 @@
"cookie": "0.4.0",
"etag": "1.8.1",
"mkdirp-promise": "5.0.1",
"node-fetch": "2.6.0",
"node-fetch": "2.6.1",
"source-map-support": "0.5.12",
"test-listen": "1.1.0"
}

View File

@@ -8,38 +8,57 @@ if (!entrypoint) {
throw new Error('`VERCEL_DEV_ENTRYPOINT` must be defined');
}
import fs from 'fs';
import { join } from 'path';
import { register } from 'ts-node';
// Use the project's version of TypeScript if available,
// otherwise fall back to using the copy that `@now/node` uses.
let compiler: string;
try {
compiler = require.resolve('typescript', {
paths: [process.cwd()],
});
} catch (e) {
compiler = 'typescript';
}
type TypescriptModule = typeof import('typescript');
// Assume Node 10
let target = 'es2018';
const resolveTypescript = (p: string): string => {
try {
return require.resolve('typescript', {
paths: [p],
});
} catch (_) {
return '';
}
};
const requireTypescript = (p: string): TypescriptModule => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require(p) as TypescriptModule;
};
let ts: TypescriptModule | null = null;
// Assume Node 10 as the lowest common denominator
let target = 'ES2018';
const nodeMajor = Number(process.versions.node.split('.')[0]);
if (nodeMajor >= 14) {
target = 'es2020';
target = 'ES2020';
} else if (nodeMajor >= 12) {
target = 'es2019';
target = 'ES2019';
}
// Use the project's version of Typescript if available and supports `target`
let compiler = resolveTypescript(process.cwd());
if (compiler) {
ts = requireTypescript(compiler);
if (!(target in ts.ScriptTarget)) {
ts = null;
}
}
// Otherwise fall back to using the copy that `@vercel/node` uses
if (!ts) {
compiler = resolveTypescript(join(__dirname, '..'));
ts = requireTypescript(compiler);
}
if (tsconfig) {
try {
const tsconfigParsed = JSON.parse(fs.readFileSync(tsconfig, 'utf8'));
if (
tsconfigParsed.compilerOptions &&
tsconfigParsed.compilerOptions.target
) {
target = tsconfigParsed.compilerOptions.target;
const { config } = ts.readConfigFile(tsconfig, ts.sys.readFile);
if (config?.compilerOptions?.target) {
target = config.compilerOptions.target;
}
} catch (err) {
if (err.code !== 'ENOENT') {
@@ -63,7 +82,6 @@ register({
});
import { createServer, Server, IncomingMessage, ServerResponse } from 'http';
import { join } from 'path';
import { Readable } from 'stream';
import { Bridge } from './bridge';
import { getNowLauncher } from './launcher';

View File

@@ -28,26 +28,32 @@ beforeAll(async () => {
const fixturesPath = path.resolve(__dirname, 'fixtures');
const testsThatFailToBuild = new Set(['45-noEmitOnError-true']);
const testsThatFailToBuild = new Map([
[
'45-noEmitOnError-true',
`index.ts(3,19): error TS2339: Property 'thisDoesNotExist' does not exist on type 'IncomingMessage'.\n`,
],
]);
// eslint-disable-next-line no-restricted-syntax
for (const fixture of fs.readdirSync(fixturesPath)) {
if (testsThatFailToBuild.has(fixture)) {
const errMsg = testsThatFailToBuild.get(fixture);
if (errMsg) {
// eslint-disable-next-line no-loop-func
it(`should not build ${fixture}`, async () => {
it(`should fail to build ${fixture}`, async () => {
try {
await testDeployment(
{ builderUrl, buildUtilsUrl },
path.join(fixturesPath, fixture)
);
} catch (err) {
expect(err.message).toMatch(/is ERROR/);
expect(err).toBeTruthy();
expect(err.deployment).toBeTruthy();
expect(err.deployment.errorMessage).toBe(errMsg);
}
});
continue; //eslint-disable-line
}
// eslint-disable-next-line no-loop-func
it(`should build ${fixture}`, async () => {
await expect(
testDeployment(

View File

@@ -3,7 +3,7 @@
"strict": true,
"esModuleInterop": true,
"lib": ["esnext"],
"target": "esnext",
"target": "es2018",
"module": "commonjs",
"outDir": "dist",
"sourceMap": false,

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/routing-utils",
"version": "1.8.4",
"version": "1.9.1-canary.1",
"description": "Vercel routing utilities",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",

View File

@@ -36,6 +36,40 @@ export const routesSchema = {
},
},
},
locale: {
type: 'object',
additionalProperties: false,
properties: {
redirect: {
type: 'object',
additionalProperties: false,
minProperties: 1,
maxProperties: 100,
patternProperties: {
'^.{1,256}$': {
type: 'string',
maxLength: 4096,
},
},
},
value: {
type: 'string',
maxLength: 4096,
},
path: {
type: 'string',
maxLength: 4096,
},
cookie: {
type: 'string',
maxLength: 4096,
},
default: {
type: 'string',
maxLength: 4096,
},
},
},
handle: {
type: 'string',
maxLength: 32,

View File

@@ -17,6 +17,10 @@ export type Source = {
continue?: boolean;
check?: boolean;
status?: number;
locale?: {
redirect: Record<string, string>;
cookie: string;
};
};
export type Handler = {

View File

@@ -36,6 +36,21 @@ describe('normalizeRoutes', () => {
test('accepts valid routes', () => {
const routes = [
{
src: '^(?:/(?<value>en|fr))?(?<path>/.*)$',
locale: {
value: '$value',
path: '$path',
default: 'en',
cookie: 'NEXT_LOCALE',
},
},
{
src: '^/(?:en/?|fr/?)$',
locale: {
redirect: { en: '/en', fr: '/fr' },
},
},
{ src: '^/about$' },
{
src: '^/blog$',

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/static-build",
"version": "0.17.8",
"version": "0.17.9",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/build-step",
@@ -27,7 +27,7 @@
"get-port": "5.0.0",
"is-port-reachable": "2.0.1",
"ms": "2.1.2",
"node-fetch": "2.6.0",
"node-fetch": "2.6.1",
"typescript": "3.9.3"
}
}

View File

@@ -225,7 +225,7 @@ const frameworkList: Framework[] = [
continue: true,
},
{
src: '^/(img|js|css|fonts|media)/.*',
src: '^/(img|js|css|fonts|media)/[^/]+\\.[0-9a-f]{8}\\.*',
headers: { 'cache-control': 'max-age=31536000, immutable' },
continue: true,
},

View File

@@ -154,23 +154,21 @@ async function fetchWithAuth(url, opts = {}) {
if (!opts.headers) opts.headers = {};
if (!opts.headers.Authorization) {
currentCount += 1;
if (!token || currentCount === MAX_COUNT) {
currentCount = 0;
// used for health checks
token = process.env.VERCEL_TOKEN || process.env.NOW_TOKEN;
if (!token) {
// used by GH Actions
token = await fetchTokenWithRetry();
}
}
opts.headers.Authorization = `Bearer ${token}`;
opts.headers.Authorization = `Bearer ${await fetchCachedToken()}`;
}
return await fetchApi(url, opts);
}
async function fetchCachedToken() {
currentCount += 1;
if (!token || currentCount === MAX_COUNT) {
currentCount = 0;
token = await fetchTokenWithRetry();
}
return token;
}
async function fetchTokenWithRetry(retries = 5) {
const {
NOW_TOKEN,
@@ -248,5 +246,6 @@ module.exports = {
fetchWithAuth,
nowDeploy,
fetchTokenWithRetry,
fetchCachedToken,
fileModeSymbol,
};

View File

@@ -33,7 +33,13 @@ for tag in $tags; do
echo "Running \`npm publish $npm_tag\` in \"$(pwd)\""
npm publish $npm_tag
sleep 5 # Wait after publish before deprecate to avoid E405. https://github.com/vercel/vercel/runs/1076007594#step:6:649
if [[ "$now_name" = "now" ]]; then
sleep 30 # Wait after publish before deprecate to avoid E405. https://github.com/vercel/vercel/runs/1120597936#step:6:882
else
sleep 5 # Wait after publish before deprecate to avoid E405. https://github.com/vercel/vercel/runs/1076007594#step:6:649
fi
echo "Running \`npm deprecate -f $now_name@$version\` in favor of $vc_name"
npm deprecate -f "$now_name@$version" "\"$now_name\" is deprecated and will stop receiving updates on December 31, 2020. Please use \"$vc_name\" instead."
done

View File

@@ -4793,21 +4793,16 @@ escape-html@1.0.3:
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
escape-string-regexp@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-3.0.0.tgz#1dad9cc28aed682be0de197280f79911a5fccd61"
integrity sha512-11dXIUC3umvzEViLP117d0KN6LJzZxh5+9F4E/7WLAAw7GrHk8NpUR+g9iJi/pe9C0py4F8rs0hreyRCwlAuZg==
escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
escape-string-regexp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
escodegen@^1.8.0, escodegen@^1.9.1:
version "1.14.2"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.2.tgz#14ab71bf5026c2aa08173afba22c6f3173284a84"
@@ -8328,16 +8323,16 @@ node-fetch-npm@^2.0.2:
json-parse-better-errors "^1.0.0"
safe-buffer "^5.1.1"
node-fetch@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.2.0.tgz#4ee79bde909262f9775f731e3656d0db55ced5b5"
integrity sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA==
node-fetch@2.6.0, node-fetch@^2.2.0, node-fetch@^2.2.1, node-fetch@^2.3.0, node-fetch@^2.5.0:
node-fetch@2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
node-fetch@2.6.1, node-fetch@^2.2.0, node-fetch@^2.2.1, node-fetch@^2.3.0, node-fetch@^2.5.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-gyp-build@^4.2.2:
version "4.2.3"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739"