Compare commits

..

36 Commits

Author SHA1 Message Date
Steven
e19446f89c Publish Stable
- @vercel/frameworks@0.0.16
 - @vercel/build-utils@2.4.1
 - vercel@19.1.2
 - @vercel/client@8.2.0
 - @vercel/go@1.1.3
 - @vercel/next@2.6.11
 - @vercel/node@1.7.2
 - @vercel/ruby@1.2.3
 - @vercel/static-build@0.17.5
2020-07-10 08:20:44 -07:00
JJ Kasper
ea3233502d Publish Stable
- @vercel/next@2.6.10
2020-07-09 21:30:31 -05:00
JJ Kasper
0a8810b64f Publish Canary
- vercel@19.1.2-canary.22
 - @vercel/next@2.6.10-canary.2
2020-07-09 21:29:18 -05:00
JJ Kasper
f6b373f0f4 [next] Fix preview mode with dynamic routes in shared lambdas (#4812)
This fixes dynamic routes missing inside of shared lambdas for non-revalidate pages that leverage preview mode which causes them to not be able to render. Additional tests have also been added to ensure preview mode is working properly
2020-07-10 02:22:17 +00:00
Steven
7ad2a99cd7 Publish Canary
- @vercel/frameworks@0.0.16-canary.2
 - @vercel/build-utils@2.4.1-canary.3
 - vercel@19.1.2-canary.21
 - @vercel/client@8.1.1-canary.3
 - @vercel/next@2.6.10-canary.1
 - @vercel/static-build@0.17.5-canary.1
2020-07-09 16:42:50 -07:00
JJ Kasper
0349eea494 [next] Add pages with invalid route regexes (#4813)
This adds failing page names to our tests which should be resolved once the correct regexes is being generated for the routes-manifest in Next.js

Co-authored-by: Joe Haddad <joe.haddad@zeit.co>
Co-authored-by: Steven <steven@ceriously.com>
2020-07-09 19:37:13 -04:00
Luc
ed4d006fb7 replace "more details" with "learn more" (#4828)
Follow up to PR #4825. Replace more "More details" with "Learn More".
2020-07-09 19:31:04 -04:00
Steven
12a9d203e9 [cli] Handle 400 errors as user errors (#4827)
When a user performs an action such as trying to add a domain they don't own, the API returns a 400-499 response code.

Previously, we would save this error in Sentry but this PR changes the behavior so that the error is printed locally for the user but not saved in Sentry.
2020-07-09 21:57:18 +00:00
Luc
ac1f4cf789 [cli] Handle API Error action property (#4825)
* handle error action

* fix test
2020-07-09 22:14:30 +02:00
Steven
b284ca350a Revert "[frameworks] Remove blitz demo (#4816)" (#4826)
This reverts commit f11eb32b2c.

Reverts PR #4816

This PR adds back the Blitz.js demo now that we confirmed the example works and is deployed to the correct account.
2020-07-09 13:28:38 -04:00
Steven
a43bf6c912 [cli][static-build] Use titlecase for "Project Settings" (#4820)
"project settings" should be "Project Settings"
2020-07-09 11:56:11 -04:00
Nathan Rajlich
62410806bb Publish Canary
- vercel@19.1.2-canary.20
 - @vercel/next@2.6.10-canary.0
2020-07-08 16:24:18 -07:00
Steven
47e3111cab [all] Change prettier config arrowParens: avoid (#4819)
In PR #4760, Prettier was upgraded to 2.0 which changed the default configuration for `arrowParens` from `avoid` to `always`. This PR changes it back to `avoid` so we don't have PRs changing unnecessary lines during in the commit hook.

See the [Prettier 2.0 Announcement](https://prettier.io/blog/2020/03/21/2.0.0.html#change-default-value-for-arrowparens-to-always-7430httpsgithubcomprettierprettierpull7430-by-kachkaevhttpsgithubcomkachkaev) for more.
2020-07-08 18:46:48 -04:00
Nathan Rajlich
135f35002f [cli] Remove caching of vercel.json config in vc dev (#4808)
This is a re-introduction of #4697, which was reverted for performance
resons. But now that #4760 and #4793 have landed, we can enable this
behavior again.
2020-07-08 22:37:55 +00:00
Steven
ee40052cee Publish Stable
- @vercel/next@2.6.9
2020-07-08 13:53:18 -07:00
JJ Kasper
86b730c1cd Publish Canary
- vercel@19.1.2-canary.19
 - @vercel/next@2.6.9-canary.1
2020-07-08 15:08:29 -05:00
JJ Kasper
b440249c26 [next] Don't use potentially invalid routeKeys/regexes (#4803)
The first version of `routeKeys` which was Array based could contain invalid named regexes which would break the build during route validation so this disables using them in that version of the manifest instead of trying to leverage them still. It also adds an additional test fixture with this version of the manifest to ensure it is still working properly
2020-07-08 20:05:00 +00:00
Steven
5380c12569 Publish Canary
- @vercel/frameworks@0.0.16-canary.1
 - @vercel/build-utils@2.4.1-canary.2
 - vercel@19.1.2-canary.18
 - @vercel/client@8.1.1-canary.2
 - @vercel/next@2.6.9-canary.0
2020-07-08 11:27:30 -07:00
Steven
f11eb32b2c [frameworks] Remove blitz demo (#4816)
This is not ready yet. We need to confirm Blitz is working before we deploy the demo.
2020-07-08 14:19:55 -04:00
Brandon Bayer
3d40e343ac [frameworks][examples] Add Blitz.js (#4465)
This adds [Blitz.js](https://github.com/blitz-js/blitz) to the zero-config frameworks.

Blitz compiles to a Next.js app, so it uses the same `@vercel/next` builder.

The change in `packages/now-build-utils/src/detect-builders.ts` was made according to @styfle's suggestion in Slack.

---

**This is a rerun of #4219 which had to be reverted because of this:**

> It looks like the @vercel/next package still requires next in the dependencies.
>
> When I've deployed the example I've got only a file listing, but when I added next to the dependencies it worked. I'll revert for now.

This new PR fixes that problem with a small refactor to `@vercel/next`.

`@vercel/next` was getting the Next version two different ways:

1. By checking project root package.json for `next` in deps or devDeps
2. By trying to resolve the installed next version and get `version` from its package.json

My refactor consolidates those two approaches by changing the existing `getNextVersion()` function to first try and resolve the real next version. If that fails, fall back to checking the project root.

Blitz bundles Next, so the real next package version should always be resolved.
2020-07-08 13:18:01 -04:00
Nathan Rajlich
80f525796f [cli] Make filesystem globbing faster in vercel dev (#4793)
The current code uses `getAllProjectFiles()` which globs for every file
in the project, not considering the `.vercelignore` file and default
ignore list. For example, every file in the `node_modules` directory is
selected, just for `vercel dev` to manually ignore them afterwards,
which is very slow and wasteful. For a simple Next.js project, this
globbing was taking over 3 seconds on my machine 🤯!

Solution is to use the `staticFiles()` function which is the same as
`vercel dev` uses at boot-up time. This function properly considers the
ignore list and thus is much faster, and the manual filtering is no
longer necessary by `vercel dev`. For the same simple Next.js project,
this function takes on average 10 milliseconds.

Also did a bit of cleanup and removed the `getAllProjectFiles()`
function since it is no longer used anywhere.
2020-07-07 20:30:21 +00:00
Nathan Rajlich
af4ad358f2 [cli] Print a warning if both "functions" and "builds" are defined in vercel.json in vercel dev (#4795)
This matches the error message that is used in production when using the
`vercel dev` command.
2020-07-07 19:41:29 +00:00
Nathan Rajlich
e6033d7a2d [client] Add @vercel/build-utils as a "dependency" (#4804)
Fixes: https://github.com/philcockfield/sample.vercel.client-error
2020-07-07 18:56:31 +00:00
JJ Kasper
d3148dffaa Publish Stable
- @vercel/next@2.6.8
2020-07-06 08:40:44 -05:00
JJ Kasper
30048cf4ff Publish Canary
- vercel@19.1.2-canary.17
 - @vercel/next@2.6.8-canary.7
2020-07-06 08:31:35 -05:00
JJ Kasper
07c65fa5c8 [next] Use correct data route regex (#4778)
For `getServerSideProps` routes the non-named data route was being used for routes causing dynamic route params that were being trusted from the proxy to be incorrect. Our tests weren't failing after trusting the values from the proxy was landed in Next.js on canary due to the `21-server-props` test suite not running against the latest canary which is updated in this PR. 

Note: the below Next.js PR is not needed or can be reverted if this PR is landed

x-ref: https://github.com/vercel/next.js/pull/14829
2020-07-05 21:13:01 +00:00
Nathan Rajlich
411ec64986 [cli] Reduce number of times getNowConfig() is invoked in vercel dev (#4760)
* [cli] Reduce number of times `getNowConfig()` is invoked in `vercel dev`

When using the `vercel dev` command, the `getNowConfig()` function was
being invoked many times per HTTP-request. It should only be read _once_
per HTTP request as it may be an expensive function depending on the
project size.

This change reduces the number of times the function is called to once
per HTTP request, and passes around the resulting `nowConfig` object to
the functions that require it.

* Fix default values

* Remove unnecessary type narrowing

* Remove one more `getNowConfig()` invocation

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-07-02 17:25:07 -07:00
Joe Haddad
e4d2cc704c Add Lockfile to Fix Gatsby Example (#4771)
This adds the Lockfile from Gatsby's official starter to fix deployments.

```
❯ npx gatsby-cli new ~/Desktop/scratch/test-default
❯ cp ~/Desktop/scratch/test-default/yarn.lock examples/gatsby
❯ cd examples/gatsby
❯ yarn
❯ git add examples/gatsby/yarn.lock
```
2020-07-02 21:06:44 +00:00
Joe Haddad
38db720586 Regenerate yarn.lock file (#4769) 2020-07-02 11:20:35 -07:00
Joe Haddad
c26c7886be [next] Skip Lambda Deployment for Static Sites (#4748) 2020-07-02 11:51:02 -04:00
JJ Kasper
b0e5d308ca [next] Add additiontal test cases for basePath support (#4747)
This adds some additional test cases for `basePath` support in Next.js to ensure it is working properly with `@vercel/next`
2020-07-02 09:40:15 +00:00
JJ Kasper
609b98cc73 Publish Canary
- vercel@19.1.2-canary.16
 - @vercel/next@2.6.8-canary.6
2020-07-01 14:46:28 -05:00
JJ Kasper
e0ec6c792b [next] Re-enable shared lambdas by default (#4757)
This re-enables the shared lambdas optimization by default and also adds additional tests to ensure that when a `now.json` or `vercel.json` contains either `routes` or `functions` configs the shared lambda optimization is disabled. 

Additional test deployments done:

- minimal `now.json` with `builds` config [deploy](https://shared-lambdas-tests-d646fsqju.vercel.app)
- `now.json` with `functions` config [deploy](https://shared-lambdas-tests-ahnuosp4s.vercel.app)
- `now.json` with `routes` config [deploy](https://shared-lambdas-tests-gulam3jda.vercel.app)
- minimal `vercel.json` with `builds` config [deploy](https://shared-lambdas-tests-7ic7wzirs.vercel.app)
- `vercel.json` with `functions` config [deploy](https://shared-lambdas-tests-7ic7wzirs.vercel.app)
- `vercel.json` with `routes` config [deploy](https://shared-lambdas-tests-rja2391tq.vercel.app)
2020-07-01 19:44:09 +00:00
Nathan Rajlich
b29db2fd1d [cli] Pin @vercel/static-build to v0.17.5-canary.0 (#4751)
It got de-synced in fa0f1b90b4.
2020-07-01 10:59:22 -07:00
JJ Kasper
b604ced99d Publish Canary
- vercel@19.1.2-canary.15
 - @vercel/next@2.6.8-canary.5
2020-07-01 10:53:18 -05:00
JJ Kasper
b7fd69517e [next] Fix dynamic route mapping with shared lambdas (#4740)
This fixes mapping dynamic routes to their shared lambdas failing from the `dynamicPageLambdaRoute` not being injected directly after the dynamic route due to dynamic routes now containing a `query` which caused the route lookup to fail. 

This is resolved by making sure to parse the pathname while injecting the `dynamicPageLambdaRoute` so the route can be looked up correctly. This also adds additional test cases to prevent regressing on this along with updating existing fixtures for custom routes being de-experimentalized
2020-07-01 07:30:41 +00:00
127 changed files with 14137 additions and 1323 deletions

View File

@@ -0,0 +1,4 @@
module.exports = {
presets: ["next/babel"],
plugins: [],
}

View File

@@ -0,0 +1,10 @@
module.exports = {
extends: ["react-app", "plugin:jsx-a11y/recommended"],
plugins: ["jsx-a11y"],
rules: {
"import/no-anonymous-default-export": "error",
"import/no-webpack-loader-syntax": "off",
"react/react-in-jsx-scope": "off", // React is always in scope with Blitz
"jsx-a11y/anchor-is-valid": "off", //Doesn't play well with Blitz/Next <Link> usage
},
}

56
examples/blitzjs/.gitignore vendored Normal file
View File

@@ -0,0 +1,56 @@
# dependencies
node_modules
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.pnp.*
.npm
web_modules/
# blitz
/.blitz/
/.next/
*.sqlite
.now
.vercel
.blitz-console-history
blitz-log.log
# misc
.DS_Store
# local env files
.env
.envrc
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Testing
coverage
*.lcov
.nyc_output
lib-cov
# Caches
*.tsbuildinfo
.eslintcache
.node_repl_history
.yarn-integrity
# Serverless directories
.serverless/
# Stores VSCode versions used for testing VSCode extensions
.vscode-test

1
examples/blitzjs/.npmrc Normal file
View File

@@ -0,0 +1 @@
save-exact=true

View File

@@ -0,0 +1,6 @@
.gitkeep
.env
*.ico
*.lock
db/migrations

View File

@@ -0,0 +1,21 @@
![Blitz Logo](https://github.com/vercel/vercel/blob/master/packages/frameworks/logos/blitz.svg)
This is a [Blitz.js](https://blitzjs.com/) project bootstrapped with `blitz new`.
## Getting Started
First, run the development server:
```bash
npx blitz start
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
## Learn More
To learn more about Blitz.js, view [Blitzjs.com](https://blitzjs.com)
## Deploy on Vercel
View the [documentation on deploying to Vercel](https://blitzjs.com/docs/deploy-vercel)

View File

View File

@@ -0,0 +1,21 @@
import React from "react"
export default class ErrorBoundary extends React.Component<{
fallback: (error: any) => React.ReactNode
}> {
state = { hasError: false, error: null }
static getDerivedStateFromError(error: any) {
return {
hasError: true,
error,
}
}
render() {
if (this.state.hasError) {
return this.props.fallback(this.state.error)
}
return this.props.children
}
}

View File

View File

@@ -0,0 +1,5 @@
import { AppProps } from "blitz"
export default function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}

View File

@@ -0,0 +1,23 @@
import { Document, Html, DocumentHead, Main, BlitzScript /*DocumentContext*/ } from "blitz"
class MyDocument extends Document {
// Only uncomment if you need to customize this behaviour
// static async getInitialProps(ctx: DocumentContext) {
// const initialProps = await Document.getInitialProps(ctx)
// return {...initialProps}
// }
render() {
return (
<Html lang="en">
<DocumentHead />
<body>
<Main />
<BlitzScript />
</body>
</Html>
)
}
}
export default MyDocument

View File

@@ -0,0 +1,197 @@
import { Head, Link } from "blitz"
const Home = () => (
<div className="container">
<Head>
<title>blitzjs</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<div className="logo">
<img src="/logo.png" alt="blitz.js" />
</div>
<p>1. Run this command in your terminal:</p>
<pre>
<code>blitz generate all project name:string</code>
</pre>
<p>2. Then run this command:</p>
<pre>
<code>blitz db migrate</code>
</pre>
<p>
3. Go to{" "}
<Link href="/projects">
<a>/projects</a>
</Link>
</p>
<div className="buttons">
<a
className="button"
href="https://github.com/blitz-js/blitz/blob/master/USER_GUIDE.md?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
<a
className="button-outline"
href="https://github.com/blitz-js/blitz"
target="_blank"
rel="noopener noreferrer"
>
Github Repo
</a>
<a
className="button-outline"
href="https://slack.blitzjs.com"
target="_blank"
rel="noopener noreferrer"
>
Slack Community
</a>
</div>
</main>
<footer>
<a
href="https://blitzjs.com?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
target="_blank"
rel="noopener noreferrer"
>
Powered by Blitz.js
</a>
</footer>
<style jsx>{`
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
main {
padding: 5rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
main p {
font-size: 1.2rem;
}
footer {
width: 100%;
height: 60px;
border-top: 1px solid #eaeaea;
display: flex;
justify-content: center;
align-items: center;
background-color: #45009d;
}
footer a {
display: flex;
justify-content: center;
align-items: center;
}
footer a {
color: #f4f4f4;
text-decoration: none;
}
.logo {
margin-bottom: 2rem;
}
.logo img {
width: 300px;
}
.buttons {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 0.5rem;
margin-top: 6rem;
}
a.button {
background-color: #6700eb;
padding: 1rem 2rem;
color: #f4f4f4;
text-align: center;
}
a.button:hover {
background-color: #45009d;
}
a.button-outline {
border: 2px solid #6700eb;
padding: 1rem 2rem;
color: #6700eb;
text-align: center;
}
a.button-outline:hover {
border-color: #45009d;
color: #45009d;
}
pre {
background: #fafafa;
border-radius: 5px;
padding: 0.75rem;
}
code {
font-size: 0.9rem;
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}
.grid {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
max-width: 800px;
margin-top: 3rem;
}
@media (max-width: 600px) {
.grid {
width: 100%;
flex-direction: column;
}
}
`}</style>
<style jsx global>{`
@import url("https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;700&display=swap");
html,
body {
padding: 0;
margin: 0;
font-family: "Libre Franklin", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
box-sizing: border-box;
}
`}</style>
</div>
)
export default Home

View File

@@ -0,0 +1,15 @@
module.exports = {
/*
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
// Note: we provide webpack above so you should not `require` it
// Perform customizations to webpack config
// Important: return the modified config
return config
},
webpackDevMiddleware: (config) => {
// Perform customizations to webpack dev middleware config
// Important: return the modified config
return config
},
*/
}

View File

@@ -0,0 +1,15 @@
import { PrismaClient } from "@prisma/client"
export * from "@prisma/client"
let prisma: PrismaClient
if (process.env.NODE_ENV === "production") {
prisma = new PrismaClient()
} else {
// Ensure the prisma instance is re-used during hot-reloading
// Otherwise, a new client will be created on every reload
global["prisma"] = global["prisma"] || new PrismaClient()
prisma = global["prisma"]
}
export default prisma

View File

View File

@@ -0,0 +1,27 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource sqlite {
provider = "sqlite"
url = "file:./db.sqlite"
}
// SQLite is easy to start with, but if you use Postgres in production
// you should also use it in development with the following:
//datasource postgresql {
// provider = "postgresql"
// url = env("DATABASE_URL")
//}
generator client {
provider = "prisma-client-js"
}
// --------------------------------------
//model Project {
// id Int @default(autoincrement()) @id
// name String
//}

View File

View File

@@ -0,0 +1,55 @@
{
"name": "blitzjs",
"version": "1.0.0",
"scripts": {
"start": "blitz start",
"studio": "blitz db studio",
"build": "blitz build",
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
"test": "echo \"No tests yet\""
},
"browserslist": [
"defaults"
],
"prettier": {
"semi": false,
"printWidth": 100
},
"husky": {
"hooks": {
"pre-commit": "lint-staged && pretty-quick --staged",
"pre-push": "blitz test"
}
},
"lint-staged": {
"*.{js,ts,tsx}": [
"eslint --fix"
]
},
"dependencies": {
"@prisma/cli": "latest",
"@prisma/client": "latest",
"blitz": "latest",
"react": "experimental",
"react-dom": "experimental"
},
"devDependencies": {
"@types/react": "16.9.36",
"@typescript-eslint/eslint-plugin": "2.x",
"@typescript-eslint/parser": "2.x",
"babel-eslint": "10.1.0",
"eslint": "6.8.0",
"eslint-config-react-app": "5.2.1",
"eslint-plugin-flowtype": "4.7.0",
"eslint-plugin-import": "2.21.2",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-react": "7.20.0",
"eslint-plugin-react-hooks": "3.0.0",
"husky": "4.2.5",
"lint-staged": "10.2.10",
"prettier": "2.0.5",
"pretty-quick": "2.0.1",
"typescript": "3.9.5"
},
"private": true
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"baseUrl": "./",
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}

View File

11913
examples/gatsby/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,7 @@
"lint-staged": "9.2.5",
"node-fetch": "2.6.0",
"npm-package-arg": "6.1.0",
"prettier": "1.18.2"
"prettier": "2.0.5"
},
"scripts": {
"lerna": "lerna",
@@ -64,7 +64,8 @@
},
"prettier": {
"trailingComma": "es5",
"singleQuote": true
"singleQuote": true,
"arrowParens": "avoid"
},
"eslintConfig": {
"root": true,

View File

@@ -1,4 +1,32 @@
[
{
"name": "Blitz.js",
"slug": "blitzjs",
"demo": "https://blitzjs.now-examples.now.sh",
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/blitz.svg",
"tagline": "Blitz.js: The Fullstack React Framework",
"description": "A brand new Blitz.js app: the output of running `blitz new`",
"website": "https://blitzjs.com",
"detectors": {
"every": [
{
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"blitz\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`npm run build` or `blitz build`"
},
"devCommand": {
"value": "blitz start"
},
"outputDirectory": {
"placeholder": "Next.js default"
}
}
},
{
"name": "Next.js",
"slug": "nextjs",

View File

@@ -0,0 +1,30 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M95.4242 249.857H173.991C203.89 249.857 232.049 263.909 250.026 287.799L327.526 390.789C328.991 392.736 329.212 395.349 328.095 397.513L283.421 484.069C281.278 488.221 275.532 488.71 272.719 484.978L95.4242 249.857Z" fill="url(#paint0_linear)"/>
<g filter="url(#filter0_d)">
<path d="M404.558 249.991H325.991C296.093 249.991 267.933 235.939 249.956 212.049L172.456 109.059C170.991 107.112 170.771 104.499 171.888 102.335L216.561 15.7794C218.705 11.6267 224.45 11.1382 227.264 14.8695L404.558 249.991Z" fill="url(#paint1_linear)"/>
</g>
</g>
<defs>
<filter id="filter0_d" x="71.1812" y="-39.6553" width="433.377" height="437.646" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="48"/>
<feGaussianBlur stdDeviation="50"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.270588 0 0 0 0 0 0 0 0 0 0.615686 0 0 0 0.2 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<linearGradient id="paint0_linear" x1="163.936" y1="392.775" x2="316.429" y2="155.244" gradientUnits="userSpaceOnUse">
<stop stop-color="#6700EB"/>
<stop offset="1" stop-color="#45009D"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="336.047" y1="107.073" x2="183.554" y2="344.604" gradientUnits="userSpaceOnUse">
<stop stop-color="#6700EB"/>
<stop offset="1" stop-color="#45009D"/>
</linearGradient>
<clipPath id="clip0">
<rect width="500" height="500" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/frameworks",
"version": "0.0.16-canary.0",
"version": "0.0.16",
"main": "frameworks.json",
"license": "UNLICENSED",
"scripts": {

View File

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

View File

@@ -462,7 +462,7 @@ function detectFrontBuilder(
});
}
if (framework === 'nextjs') {
if (framework === 'nextjs' || framework === 'blitzjs') {
return { src: 'package.json', use: `@vercel/next${withTag}`, config };
}
@@ -496,7 +496,7 @@ function getMissingBuildScriptError() {
code: 'missing_build_script',
message:
'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
'\nMore details: https://vercel.com/docs/v2/platform/frequently-asked-questions#missing-build-script',
'\nLearn More: https://vercel.com/docs/v2/platform/frequently-asked-questions#missing-build-script',
};
}

View File

@@ -34,7 +34,7 @@ interface Props {
*/
link?: string;
/**
* Optional "action" to display before the `link`, such as "More details".
* Optional "action" to display before the `link`, such as "Learn More".
*/
action?: string;
}

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "19.1.2-canary.14",
"version": "19.1.2",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -62,13 +62,13 @@
"node": ">= 10"
},
"dependencies": {
"@vercel/build-utils": "2.4.1-canary.1",
"@vercel/go": "1.1.3-canary.1",
"@vercel/next": "2.6.8-canary.4",
"@vercel/node": "1.7.2-canary.1",
"@vercel/build-utils": "2.4.1",
"@vercel/go": "1.1.3",
"@vercel/next": "2.6.11",
"@vercel/node": "1.7.2",
"@vercel/python": "1.2.2",
"@vercel/ruby": "1.2.3-canary.0",
"@vercel/static-build": "0.17.4-canary.1"
"@vercel/ruby": "1.2.3",
"@vercel/static-build": "0.17.5"
},
"devDependencies": {
"@sentry/node": "5.5.0",

View File

@@ -354,7 +354,7 @@ export default async function main(
path,
sourcePath,
project
? `To change your project settings, go to https://vercel.com/${org.slug}/${project.name}/settings`
? `To change your Project Settings, go to https://vercel.com/${org.slug}/${project.name}/settings`
: ''
)) === false
) {

View File

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

View File

@@ -46,6 +46,7 @@ import reportError from './util/report-error';
import getConfig from './util/get-config';
import * as ERRORS from './util/errors-ts';
import { NowError } from './util/now-error';
import { APIError } from './util/errors-ts.ts';
import { SENTRY_DSN } from './util/constants.ts';
import getUpdateCommand from './util/get-update-command';
import { metrics, shouldCollectMetrics } from './util/metrics.ts';
@@ -190,8 +191,9 @@ const main = async argv_ => {
} catch (err) {
console.error(
error(
`${'An unexpected error occurred while trying to find the ' +
'global directory: '}${err.message}`
`An unexpected error occurred while trying to find the global directory: ${
err.message
}`
)
);
@@ -204,8 +206,10 @@ const main = async argv_ => {
} catch (err) {
console.error(
error(
`${'An unexpected error occurred while trying to create the ' +
`global directory "${hp(VERCEL_DIR)}" `}${err.message}`
`${
'An unexpected error occurred while trying to create the ' +
`global directory "${hp(VERCEL_DIR)}" `
}${err.message}`
)
);
}
@@ -219,8 +223,10 @@ const main = async argv_ => {
} catch (err) {
console.error(
error(
`${'An unexpected error occurred while trying to find the ' +
`config file "${hp(VERCEL_CONFIG_PATH)}" `}${err.message}`
`${
'An unexpected error occurred while trying to find the ' +
`config file "${hp(VERCEL_CONFIG_PATH)}" `
}${err.message}`
)
);
@@ -235,8 +241,10 @@ const main = async argv_ => {
} catch (err) {
console.error(
error(
`${'An unexpected error occurred while trying to read the ' +
`config in "${hp(VERCEL_CONFIG_PATH)}" `}${err.message}`
`${
'An unexpected error occurred while trying to read the ' +
`config in "${hp(VERCEL_CONFIG_PATH)}" `
}${err.message}`
)
);
@@ -267,8 +275,10 @@ const main = async argv_ => {
} catch (err) {
console.error(
error(
`${'An unexpected error occurred while trying to write the ' +
`default config to "${hp(VERCEL_CONFIG_PATH)}" `}${err.message}`
`${
'An unexpected error occurred while trying to write the ' +
`default config to "${hp(VERCEL_CONFIG_PATH)}" `
}${err.message}`
)
);
@@ -283,8 +293,10 @@ const main = async argv_ => {
} catch (err) {
console.error(
error(
`${'An unexpected error occurred while trying to find the ' +
`auth file "${hp(VERCEL_AUTH_CONFIG_PATH)}" `}${err.message}`
`${
'An unexpected error occurred while trying to find the ' +
`auth file "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${err.message}`
)
);
@@ -301,8 +313,10 @@ const main = async argv_ => {
} catch (err) {
console.error(
error(
`${'An unexpected error occurred while trying to read the ' +
`auth config in "${hp(VERCEL_AUTH_CONFIG_PATH)}" `}${err.message}`
`${
'An unexpected error occurred while trying to read the ' +
`auth config in "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${err.message}`
)
);
@@ -326,10 +340,10 @@ const main = async argv_ => {
} catch (err) {
console.error(
error(
`${'An unexpected error occurred while trying to write the ' +
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `}${
err.message
}`
`${
'An unexpected error occurred while trying to write the ' +
`default config to "${hp(VERCEL_AUTH_CONFIG_PATH)}" `
}${err.message}`
)
);
return 1;
@@ -639,6 +653,12 @@ const main = async argv_ => {
return 1;
}
if (err instanceof APIError && 400 <= err.status && err.status <= 499) {
err.message = err.serverMessage;
output.prettyError(err);
return 1;
}
if (shouldCollectMetrics) {
metric
.event(eventCategory, '1', pkg.version)

View File

@@ -4,7 +4,7 @@ import PCRE from 'pcre-to-regexp';
import isURL from './is-url';
import DevServer from './server';
import { HttpHeadersConfig, RouteResult } from './types';
import { NowConfig, HttpHeadersConfig, RouteResult } from './types';
import { isHandler, Route, HandleValue } from '@vercel/routing-utils';
export function resolveRouteParameters(
@@ -50,6 +50,7 @@ export async function devRouter(
reqMethod?: string,
routes?: Route[],
devServer?: DevServer,
nowConfig?: NowConfig,
previousHeaders?: HttpHeadersConfig,
missRoutes?: Route[],
phase?: HandleValue | null
@@ -117,9 +118,12 @@ export async function devRouter(
continue;
}
if (routeConfig.check && devServer && phase !== 'hit') {
if (routeConfig.check && devServer && nowConfig && phase !== 'hit') {
const { pathname = '/' } = url.parse(destPath);
const hasDestFile = await devServer.hasFilesystem(pathname);
const hasDestFile = await devServer.hasFilesystem(
pathname,
nowConfig
);
if (!hasDestFile) {
if (routeConfig.status && phase !== 'miss') {
@@ -131,6 +135,7 @@ export async function devRouter(
reqMethod,
missRoutes,
devServer,
nowConfig,
combinedHeaders,
[],
'miss'

View File

@@ -12,7 +12,7 @@ import { randomBytes } from 'crypto';
import serveHandler from 'serve-handler';
import { watch, FSWatcher } from 'chokidar';
import { parse as parseDotenv } from 'dotenv';
import { basename, dirname, extname, join } from 'path';
import path, { isAbsolute, basename, dirname, extname, join } from 'path';
import once from '@tootallnate/once';
import directoryTemplate from 'serve-handler/src/directory';
import getPort from 'get-port';
@@ -49,7 +49,7 @@ import getNowConfigPath from '../config/local-path';
import { MissingDotenvVarsError } from '../errors-ts';
import cliPkg from '../pkg';
import { getVercelDirectory } from '../projects/link';
import { staticFiles as getFiles, getAllProjectFiles } from '../get-files';
import { staticFiles as getFiles } from '../get-files';
import { validateConfig } from './validate';
import { devRouter, getRoutesTypes } from './router';
import getMimeType from './mime-type';
@@ -90,6 +90,10 @@ interface FSEvent {
path: string;
}
type WithFileNameSymbol<T> = T & {
[fileNameSymbol]: string;
};
function sortBuilders(buildA: Builder, buildB: Builder) {
if (buildA && buildA.use && isOfficialRuntime('static-build', buildA.use)) {
return 1;
@@ -113,7 +117,6 @@ export default class DevServer {
public address: string;
public devCacheDir: string;
private cachedNowConfig: NowConfig | null;
private caseSensitive: boolean;
private apiDir: string | null;
private apiExtensions: Set<string>;
@@ -148,7 +151,6 @@ export default class DevServer {
this.devCommand = options.devCommand;
this.projectSettings = options.projectSettings;
this.frameworkSlug = options.frameworkSlug;
this.cachedNowConfig = null;
this.caseSensitive = false;
this.apiDir = null;
this.apiExtensions = new Set();
@@ -172,10 +174,8 @@ export default class DevServer {
this.watchAggregationEvents = [];
this.watchAggregationTimeout = 500;
this.filter = path => Boolean(path);
this.podId = Math.random()
.toString(32)
.slice(-5);
this.filter = (path) => Boolean(path);
this.podId = Math.random().toString(32).slice(-5);
this.devServerPids = new Set();
}
@@ -213,8 +213,8 @@ export default class DevServer {
}
}
events = events.filter(event =>
distPaths.every(distPath => !event.path.startsWith(distPath))
events = events.filter((event) =>
distPaths.every((distPath) => !event.path.startsWith(distPath))
);
// First, update the `files` mapping of source files
@@ -228,16 +228,7 @@ export default class DevServer {
}
}
const nowConfig = await this.getNowConfig(false);
// Update the env vars configuration
const nowConfigBuild = nowConfig.build || {};
const [runEnv, buildEnv] = await Promise.all([
this.getLocalEnv('.env', nowConfig.env),
this.getLocalEnv('.env.build', nowConfigBuild.env),
]);
const allEnv = { ...buildEnv, ...runEnv };
this.envConfigs = { buildEnv, runEnv, allEnv };
const nowConfig = await this.getNowConfig();
// Update the build matches in case an entrypoint was created or deleted
await this.updateBuildMatches(nowConfig);
@@ -283,18 +274,19 @@ export default class DevServer {
for (const [result, [requestPath, match]] of needsRebuild) {
if (
requestPath === null ||
(await shouldServe(match, this.files, requestPath, this))
(await shouldServe(match, this.files, requestPath, this, nowConfig))
) {
this.triggerBuild(
match,
requestPath,
null,
nowConfig,
result,
filesChangedArray,
filesRemovedArray
).catch((err: Error) => {
this.output.warn(
`An error occurred while rebuilding ${match.src}:`
`An error occurred while rebuilding \`${match.src}\`:`
);
console.error(err.stack);
});
@@ -378,7 +370,7 @@ export default class DevServer {
this,
fileList
);
const sources = matches.map(m => m.src);
const sources = matches.map((m) => m.src);
if (isInitial && fileList.length === 0) {
this.output.warn('There are no files inside your deployment.');
@@ -505,57 +497,35 @@ export default class DevServer {
return {};
}
async getNowConfig(canUseCache: boolean = true): Promise<NowConfig> {
clearNowConfigPromise = () => {
this.getNowConfigPromise = null;
};
getNowConfig(): Promise<NowConfig> {
if (this.getNowConfigPromise) {
return this.getNowConfigPromise;
}
this.getNowConfigPromise = this._getNowConfig(canUseCache);
try {
return await this.getNowConfigPromise;
} finally {
this.getNowConfigPromise = null;
}
this.getNowConfigPromise = this._getNowConfig();
// Clean up the promise once it has resolved
const clear = this.clearNowConfigPromise;
this.getNowConfigPromise.finally(clear);
return this.getNowConfigPromise;
}
async _getNowConfig(canUseCache: boolean = true): Promise<NowConfig> {
if (canUseCache && this.cachedNowConfig) {
return this.cachedNowConfig;
}
async _getNowConfig(): Promise<NowConfig> {
const configPath = getNowConfigPath(this.cwd);
const pkg = await this.getPackageJson();
// The default empty `vercel.json` is used to serve all files as static
// when no `vercel.json` is present
let configPath = 'vercel.json';
let config: NowConfig = {
version: 2,
[fileNameSymbol]: configPath,
};
try {
configPath = getNowConfigPath(this.cwd);
this.output.debug(`Reading ${configPath}`);
config = JSON.parse(await fs.readFile(configPath, 'utf8'));
config[fileNameSymbol] = basename(configPath);
} catch (err) {
if (err.code === 'ENOENT') {
this.output.debug(err.toString());
} else if (err.name === 'SyntaxError') {
this.output.warn(
`There is a syntax error in ${configPath}: ${err.message}`
);
} else {
throw err;
}
}
const allFiles = await getAllProjectFiles(this.cwd, this.output);
const files = allFiles.filter(this.filter);
this.output.debug(
`Found ${allFiles.length} and ` +
`filtered out ${allFiles.length - files.length} files`
);
const [
pkg = null,
// The default empty `vercel.json` is used to serve all
// files as static when no `vercel.json` is present
config = { version: 2, [fileNameSymbol]: 'vercel.json' },
] = await Promise.all([
this.readJsonFile<PackageJson>('package.json'),
this.readJsonFile<NowConfig>(configPath),
]);
await this.validateNowConfig(config);
const { error: routeError, routes: maybeRoutes } = getTransformedRoutes({
@@ -572,6 +542,11 @@ export default class DevServer {
const featHandleMiss = true; // enable for zero config
const { projectSettings, cleanUrls, trailingSlash } = config;
const opts = { output: this.output, isBuilds: true };
const files = (await getFiles(this.cwd, config, opts)).map((f) =>
relative(this.cwd, f)
);
let {
builders,
warnings,
@@ -595,7 +570,7 @@ export default class DevServer {
}
if (warnings && warnings.length > 0) {
warnings.forEach(warning => this.output.warn(warning.message));
warnings.forEach((warning) => this.output.warn(warning.message));
}
if (builders) {
@@ -605,6 +580,8 @@ export default class DevServer {
config.builds = config.builds || [];
config.builds.push(...builders);
delete config.functions;
}
let routes: Route[] = [];
@@ -638,34 +615,50 @@ export default class DevServer {
await this.validateNowConfig(config);
this.cachedNowConfig = config;
this.caseSensitive = hasNewRoutingProperties(config);
this.apiDir = detectApiDirectory(config.builds || []);
this.apiExtensions = detectApiExtensions(config.builds || []);
// Update the env vars configuration
const [runEnv, buildEnv] = await Promise.all([
this.getLocalEnv('.env', config.env),
this.getLocalEnv('.env.build', config.build?.env),
]);
const allEnv = { ...buildEnv, ...runEnv };
this.envConfigs = { buildEnv, runEnv, allEnv };
return config;
}
async getPackageJson(): Promise<PackageJson | null> {
const pkgPath = join(this.cwd, 'package.json');
let pkg: PackageJson | null = null;
this.output.debug('Reading `package.json` file');
async readJsonFile<T>(
filePath: string
): Promise<WithFileNameSymbol<T> | void> {
let rel, abs;
if (isAbsolute(filePath)) {
rel = path.relative(this.cwd, filePath);
abs = filePath;
} else {
rel = filePath;
abs = join(this.cwd, filePath);
}
this.output.debug(`Reading \`${rel}\` file`);
try {
pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
const raw = await fs.readFile(abs, 'utf8');
const parsed: WithFileNameSymbol<T> = JSON.parse(raw);
parsed[fileNameSymbol] = rel;
return parsed;
} catch (err) {
if (err.code === 'ENOENT') {
this.output.debug('No `package.json` file present');
this.output.debug(`No \`${rel}\` file present`);
} else if (err.name === 'SyntaxError') {
this.output.warn(
`There is a syntax error in the \`package.json\` file: ${err.message}`
`There is a syntax error in the \`${rel}\` file: ${err.message}`
);
} else {
throw err;
}
}
return pkg;
}
async tryValidateOrExit(
@@ -761,15 +754,7 @@ export default class DevServer {
const { ig } = await getVercelIgnore(this.cwd);
this.filter = ig.createFilter();
// Retrieve the path of the native module
const nowConfig = await this.getNowConfig(false);
const nowConfigBuild = nowConfig.build || {};
const [runEnv, buildEnv] = await Promise.all([
this.getLocalEnv('.env', nowConfig.env),
this.getLocalEnv('.env.build', nowConfigBuild.env),
]);
const allEnv = { ...buildEnv, ...runEnv };
this.envConfigs = { buildEnv, runEnv, allEnv };
const nowConfig = await this.getNowConfig();
const opts = { output: this.output, isBuilds: true };
const files = await getFiles(this.cwd, nowConfig, opts);
@@ -797,11 +782,11 @@ export default class DevServer {
// get their "build matches" invalidated so that the new version is used.
this.updateBuildersTimeout = setTimeout(() => {
this.updateBuildersPromise = updateBuilders(builders, this.output)
.then(updatedBuilders => {
.then((updatedBuilders) => {
this.updateBuildersPromise = null;
this.invalidateBuildMatches(nowConfig, updatedBuilders);
})
.catch(err => {
.catch((err) => {
this.updateBuildersPromise = null;
this.output.error(`Failed to update builders: ${err.message}`);
this.output.debug(err.stack);
@@ -1142,6 +1127,7 @@ export default class DevServer {
match: BuildMatch,
requestPath: string | null,
req: http.IncomingMessage | null,
nowConfig: NowConfig,
previousBuildResult?: BuildResult,
filesChanged?: string[],
filesRemoved?: string[]
@@ -1157,10 +1143,11 @@ export default class DevServer {
// A build for `buildKey` is already in progress, so don't trigger
// another rebuild for this request - just wait on the existing one.
let msg = `De-duping build "${buildKey}"`;
if (req) msg += ` for "${req.method} ${req.url}"`;
if (req) {
msg += ` for "${req.method} ${req.url}"`;
}
this.output.debug(msg);
} else {
const nowConfig = await this.getNowConfig();
if (previousBuildResult) {
// Tear down any `output` assets from a previous build, so that they
// are not available to be served while the rebuild is in progress.
@@ -1171,7 +1158,9 @@ export default class DevServer {
}
}
let msg = `Building asset "${buildKey}"`;
if (req) msg += ` for "${req.method} ${req.url}"`;
if (req) {
msg += ` for "${req.method} ${req.url}"`;
}
this.output.debug(msg);
buildPromise = executeBuild(
nowConfig,
@@ -1252,7 +1241,7 @@ export default class DevServer {
const { status, headers, dest } = routeResult;
const location = headers['location'] || dest;
if (status && location && (300 <= status && status <= 399)) {
if (status && location && 300 <= status && status <= 399) {
this.output.debug(`Route found with redirect status code ${status}`);
await this.sendRedirect(req, res, nowRequestId, location, status);
return true;
@@ -1342,6 +1331,7 @@ export default class DevServer {
req.method,
phaseRoutes,
this,
nowConfig,
prevHeaders,
missRoutes,
phase
@@ -1369,7 +1359,8 @@ export default class DevServer {
this.buildMatches,
this.files,
routeResult.dest,
this
this,
nowConfig
);
if (
@@ -1392,6 +1383,7 @@ export default class DevServer {
req.method,
missRoutes,
this,
nowConfig,
routeResult.headers,
[],
'miss'
@@ -1401,7 +1393,8 @@ export default class DevServer {
this.buildMatches,
this.files,
routeResult.dest,
this
this,
nowConfig
);
if (
await this.exitWithStatus(
@@ -1424,6 +1417,7 @@ export default class DevServer {
req.method,
hitRoutes,
this,
nowConfig,
routeResult.headers,
[],
'hit'
@@ -1438,6 +1432,7 @@ export default class DevServer {
req.method,
errorRoutes,
this,
nowConfig,
routeResult.headers,
[],
'error'
@@ -1447,7 +1442,8 @@ export default class DevServer {
this.buildMatches,
this.files,
routeResultForError.dest,
this
this,
nowConfig
);
if (matchForError) {
@@ -1535,7 +1531,8 @@ export default class DevServer {
newUrl,
req.method,
buildResult.routes,
this
this,
nowConfig
);
if (matchedRoute.found && callLevel === 0) {
debug(`Found matching route ${matchedRoute.dest} for ${newUrl}`);
@@ -1631,7 +1628,7 @@ export default class DevServer {
let foundAsset = findAsset(match, requestPath, nowConfig);
if (!foundAsset && callLevel === 0) {
await this.triggerBuild(match, buildRequestPath, req);
await this.triggerBuild(match, buildRequestPath, req, nowConfig);
// Since the `asset` was just built, resolve again to get the new asset
foundAsset = findAsset(match, requestPath, nowConfig);
@@ -1663,8 +1660,9 @@ export default class DevServer {
const { asset, assetKey } = foundAsset;
debug(
`Serving asset: [${asset.type}] ${assetKey} ${(asset as any)
.contentType || ''}`
`Serving asset: [${asset.type}] ${assetKey} ${
(asset as any).contentType || ''
}`
);
/* eslint-disable no-case-declarations */
@@ -1783,7 +1781,7 @@ export default class DevServer {
const dirs: Set<string> = new Set();
const files = Array.from(this.buildMatches.keys())
.filter(p => {
.filter((p) => {
const base = basename(p);
if (
base === 'now.json' ||
@@ -1804,7 +1802,7 @@ export default class DevServer {
}
return true;
})
.map(p => {
.map((p) => {
let base = basename(p);
let ext = '';
let type = 'file';
@@ -1856,8 +1854,17 @@ export default class DevServer {
return true;
}
async hasFilesystem(dest: string): Promise<boolean> {
if (await findBuildMatch(this.buildMatches, this.files, dest, this, true)) {
async hasFilesystem(dest: string, nowConfig: NowConfig): Promise<boolean> {
if (
await findBuildMatch(
this.buildMatches,
this.files,
dest,
this,
nowConfig,
true
)
) {
return true;
}
return false;
@@ -2038,13 +2045,23 @@ async function findBuildMatch(
files: BuilderInputs,
requestPath: string,
devServer: DevServer,
isFilesystem?: boolean
nowConfig: NowConfig,
isFilesystem = false
): Promise<BuildMatch | null> {
requestPath = requestPath.replace(/^\//, '');
let bestIndexMatch: undefined | BuildMatch;
for (const match of matches.values()) {
if (await shouldServe(match, files, requestPath, devServer, isFilesystem)) {
if (
await shouldServe(
match,
files,
requestPath,
devServer,
nowConfig,
isFilesystem
)
) {
if (!isIndex(match.src)) {
return match;
} else {
@@ -2066,14 +2083,14 @@ async function shouldServe(
files: BuilderInputs,
requestPath: string,
devServer: DevServer,
isFilesystem?: boolean
nowConfig: NowConfig,
isFilesystem = false
): Promise<boolean> {
const {
src,
config,
builderWithPkg: { builder },
} = match;
const nowConfig = await devServer.getNowConfig();
const cleanSrc = src.endsWith('.html') ? src.slice(0, -5) : src;
const trimmedPath = requestPath.endsWith('/')
? requestPath.slice(0, -1)
@@ -2117,7 +2134,7 @@ async function shouldServe(
return true;
} else if (
!isFilesystem &&
(await findMatchingRoute(match, requestPath, devServer))
(await findMatchingRoute(match, requestPath, devServer, nowConfig))
) {
// If there's no `shouldServe()` function and no matched asset, then look
// up if there's a matching build route on the `match` that has already
@@ -2130,7 +2147,8 @@ async function shouldServe(
async function findMatchingRoute(
match: BuildMatch,
requestPath: string,
devServer: DevServer
devServer: DevServer,
nowConfig: NowConfig
): Promise<RouteResult | void> {
const reqUrl = `/${requestPath}`;
for (const buildResult of match.buildResults.values()) {
@@ -2139,7 +2157,8 @@ async function findMatchingRoute(
reqUrl,
undefined,
buildResult.routes,
devServer
devServer,
nowConfig
);
if (route.found) {
return route;
@@ -2195,7 +2214,7 @@ function isIndex(path: string): boolean {
function minimatches(files: string[], pattern: string): boolean {
return files.some(
file => file === pattern || minimatch(file, pattern, { dot: true })
(file) => file === pattern || minimatch(file, pattern, { dot: true })
);
}
@@ -2225,7 +2244,7 @@ function needsBlockingBuild(buildMatch: BuildMatch): boolean {
}
async function sleep(n: number) {
return new Promise(resolve => setTimeout(resolve, n));
return new Promise((resolve) => setTimeout(resolve, n));
}
async function checkForPort(

View File

@@ -34,10 +34,9 @@ const vercelConfigSchema = {
};
const ajv = new Ajv();
const validate = ajv.compile(vercelConfigSchema);
export function validateConfig(config: NowConfig): NowBuildError | null {
const validate = ajv.compile(vercelConfigSchema);
if (!validate(config)) {
if (validate.errors && validate.errors[0]) {
const error = validate.errors[0];
@@ -48,5 +47,14 @@ export function validateConfig(config: NowConfig): NowBuildError | null {
}
}
if (config.functions && config.builds) {
return new NowBuildError({
code: 'FUNCTIONS_AND_BUILDS',
message:
'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.',
link: 'https://vercel.link/functions-and-builds',
});
}
return null;
}

View File

@@ -14,6 +14,7 @@ export class APIError extends Error {
status: number;
serverMessage: string;
link?: string;
action?: string;
retryAfter: number | null | 'never';
[key: string]: any;

View File

@@ -1,7 +1,7 @@
import { resolve, join } from 'path';
import { resolve } from 'path';
import ignore from 'ignore';
import dockerignore from '@zeit/dockerignore';
import _glob, { IOptions } from 'glob';
import _glob, { IOptions as GlobOptions } from 'glob';
import fs from 'fs-extra';
import { getVercelIgnore } from '@vercel/client';
import IGNORED from './ignored';
@@ -12,11 +12,11 @@ import { NowConfig } from './dev/types';
type NullableString = string | null;
const flatten = (
function flatten(
arr: NullableString[] | NullableString[][],
res: NullableString[] = []
) => {
for (let cur of arr) {
): NullableString[] {
for (const cur of arr) {
if (Array.isArray(cur)) {
flatten(cur, res);
} else {
@@ -24,21 +24,17 @@ const flatten = (
}
}
return res;
};
}
const glob = async function(pattern: string, options: IOptions) {
return new Promise<string[]>((resolve, reject) => {
_glob(pattern, options, (error, files) => {
if (error) {
reject(error);
} else {
resolve(files);
}
async function glob(pattern: string, options: GlobOptions): Promise<string[]> {
return new Promise((resolve, reject) => {
_glob(pattern, options, (err, files) => {
err ? reject(err) : resolve(files);
});
});
};
}
interface WalkSyncOptions {
interface WalkOptions {
output: Output;
}
@@ -51,27 +47,27 @@ interface WalkSyncOptions {
* - `output` {Object} "output" helper object
* @returns {Array}
*/
const walkSync = async (
async function walk(
dir: string,
path: string,
filelist: string[] = [],
opts: WalkSyncOptions
) => {
opts: WalkOptions
) {
const { debug } = opts.output;
const dirc = await fs.readdir(asAbsolute(dir, path));
for (let file of dirc) {
file = asAbsolute(file, dir);
try {
const file_stat = await fs.stat(file);
filelist = file_stat.isDirectory()
? await walkSync(file, path, filelist, opts)
const fileStat = await fs.stat(file);
filelist = fileStat.isDirectory()
? await walk(file, path, filelist, opts)
: filelist.concat(file);
} catch (e) {
debug(`Ignoring invalid file ${file}`);
}
}
return filelist;
};
}
interface FilesInWhitelistOptions {
output: Output;
@@ -85,7 +81,7 @@ interface FilesInWhitelistOptions {
* - `output` {Object} "output" helper object
* @returns {Array} the expanded list of whitelisted files.
*/
const getFilesInWhitelist = async function(
const getFilesInWhitelist = async function (
whitelist: string[],
path: string,
opts: FilesInWhitelistOptions
@@ -97,10 +93,10 @@ const getFilesInWhitelist = async function(
whitelist.map(async (file: string) => {
file = asAbsolute(file, path);
try {
const file_stat = await fs.stat(file);
if (file_stat.isDirectory()) {
const dir_files = await walkSync(file, path, [], opts);
files.push(...dir_files);
const fileStat = await fs.stat(file);
if (fileStat.isDirectory()) {
const dirFiles = await walk(file, path, [], opts);
files.push(...dirFiles);
} else {
files.push(file);
}
@@ -117,7 +113,7 @@ const getFilesInWhitelist = async function(
* because ignore doesn't like them :|
*/
const clearRelative = function(str: string) {
const clearRelative = function (str: string) {
return str.replace(/(\n|^)\.\//g, '$1');
};
@@ -127,7 +123,7 @@ const clearRelative = function(str: string) {
* @return {String} results or `''`
*/
const maybeRead = async function<T>(path: string, default_: T) {
const maybeRead = async function <T>(path: string, default_: T) {
try {
return await fs.readFile(path, 'utf8');
} catch (err) {
@@ -143,7 +139,7 @@ const maybeRead = async function<T>(path: string, default_: T) {
* @param {String} parent full path
*/
const asAbsolute = function(path: string, parent: string) {
const asAbsolute = function (path: string, parent: string) {
if (path[0] === '/') {
return path;
}
@@ -272,7 +268,7 @@ export async function npm(
const search = Array.prototype.concat.apply(
[],
await Promise.all(
search_.map(file =>
search_.map((file) =>
glob(file, { cwd: path, absolute: true, dot: true })
)
)
@@ -364,7 +360,7 @@ export async function docker(
const search_ = ['.'];
// Convert all filenames into absolute paths
const search = search_.map(file => asAbsolute(file, path));
const search = search_.map((file) => asAbsolute(file, path));
// Compile list of ignored patterns and files
const dockerIgnore = await maybeRead(resolve(path, '.dockerignore'), null);
@@ -382,7 +378,7 @@ export async function docker(
.createFilter();
const prefixLength = path.length + 1;
const accepts = function(file: string) {
const accepts = function (file: string) {
const relativePath = file.substr(prefixLength);
if (relativePath === '') {
@@ -415,24 +411,6 @@ export async function docker(
return uniqueStrings(files);
}
/**
* Get a list of all files inside the project folder
*
* @param {String} of the current working directory
* @param {Object} output instance
* @return {Array} of {String}s with the found files
*/
export async function getAllProjectFiles(cwd: string, { debug }: Output) {
// We need a slash at the end to remove it later on from the matched files
const current = join(resolve(cwd), '/');
debug(`Searching files inside of ${current}`);
const list = await glob('**', { cwd: current, absolute: true, nodir: true });
// We need to replace \ with / for windows
return list.map(file => file.replace(current.replace(/\\/g, '/'), ''));
}
interface ExplodeOptions {
accepts: (file: string) => boolean;
output: Output;
@@ -482,7 +460,7 @@ async function explode(
if (s.isDirectory()) {
const all = await fs.readdir(file);
/* eslint-disable no-use-before-define */
const recursive = many(all.map(subdir => asAbsolute(subdir, file)));
const recursive = many(all.map((subdir) => asAbsolute(subdir, file)));
return (recursive as any) as Promise<string | null>;
/* eslint-enable no-use-before-define */
}
@@ -494,7 +472,7 @@ async function explode(
return path;
};
const many = (all: string[]) => Promise.all(all.map(file => list(file)));
const many = (all: string[]) => Promise.all(all.map((file) => list(file)));
const arrayOfArrays = await many(paths);
return flatten(arrayOfArrays).filter(notNull);
}

View File

@@ -44,8 +44,8 @@ export default async function editProjectSettings(
output.print(
!framework.slug
? `No framework detected. Default project settings:\n`
: `Auto-detected project settings (${chalk.bold(framework.name)}):\n`
? `No framework detected. Default Project Settings:\n`
: `Auto-detected Project Settings (${chalk.bold(framework.name)}):\n`
);
settings.framework = framework.slug;

View File

@@ -27,7 +27,8 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
function warn(
str: string,
slug: string | null = null,
link: string | null = null
link: string | null = null,
action: string = 'Learn More'
) {
const prevTerm = process.env.TERM;
@@ -42,7 +43,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
boxen(
chalk.bold.yellow('WARN! ') +
str +
(details ? `\nMore details: ${renderLink(details)}` : ''),
(details ? `\n${action}: ${renderLink(details)}` : ''),
{
padding: {
top: 0,
@@ -67,7 +68,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
str: string,
slug?: string,
link?: string,
action = 'More details'
action = 'Learn More'
) {
print(`${chalk.red(`Error!`)} ${str}\n`);
const details = slug ? `https://err.sh/now/${slug}` : link;

View File

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

View File

@@ -73,7 +73,7 @@ async function getLinkFromDir(dir: string): Promise<ProjectLink | null> {
if (!ajv.validate(linkSchema, link)) {
throw new Error(
`Project settings are invalid. To link your project again, remove the ${dir} directory.`
`Project Settings are invalid. To link your project again, remove the ${dir} directory.`
);
}
@@ -87,7 +87,7 @@ async function getLinkFromDir(dir: string): Promise<ProjectLink | null> {
// link file can't be read
if (error.name === 'SyntaxError') {
throw new Error(
`Project settings could not be retrieved. To link your project again, remove the ${dir} directory.`
`Project Settings could not be retrieved. To link your project again, remove the ${dir} directory.`
);
}

View File

@@ -6,7 +6,6 @@ export default async function responseError(
fallbackMessage = null,
parsedBody = {}
) {
let message;
let bodyError;
if (res.status >= 400 && res.status < 500) {
@@ -20,12 +19,9 @@ export default async function responseError(
// Some APIs wrongly return `err` instead of `error`
bodyError = body.error || body.err || body;
message = bodyError.message;
}
if (message == null) {
message = fallbackMessage === null ? 'Response Error' : fallbackMessage;
}
const msg = bodyError?.message || fallbackMessage || 'Response Error';
return new APIError(message, res, bodyError);
return new APIError(msg, res, bodyError);
}

View File

@@ -1,18 +1,17 @@
import test from 'ava';
import { validateConfig } from '../src/util/dev/validate';
test('[dev-validate] should not error with empty config', async t => {
test('[dev-validate] should not error with empty config', async (t) => {
const config = {};
const error = validateConfig(config);
t.deepEqual(error, null);
});
test('[dev-validate] should not error with complete config', async t => {
test('[dev-validate] should not error with complete config', async (t) => {
const config = {
version: 2,
public: true,
regions: ['sfo1', 'iad1'],
builds: [{ src: 'package.json', use: '@vercel/next' }],
cleanUrls: true,
headers: [{ source: '/', headers: [{ key: 'x-id', value: '123' }] }],
rewrites: [{ source: '/help', destination: '/support' }],
@@ -24,7 +23,7 @@ test('[dev-validate] should not error with complete config', async t => {
t.deepEqual(error, null);
});
test('[dev-validate] should not error with builds and routes', async t => {
test('[dev-validate] should not error with builds and routes', async (t) => {
const config = {
builds: [{ src: 'api/index.js', use: '@vercel/node' }],
routes: [{ src: '/(.*)', dest: '/api/index.js' }],
@@ -33,7 +32,7 @@ test('[dev-validate] should not error with builds and routes', async t => {
t.deepEqual(error, null);
});
test('[dev-validate] should error with invalid rewrites due to additional property and offer suggestion', async t => {
test('[dev-validate] should error with invalid rewrites due to additional property and offer suggestion', async (t) => {
const config = {
rewrites: [{ src: '/(.*)', dest: '/api/index.js' }],
};
@@ -48,7 +47,7 @@ test('[dev-validate] should error with invalid rewrites due to additional proper
);
});
test('[dev-validate] should error with invalid routes due to additional property and offer suggestion', async t => {
test('[dev-validate] should error with invalid routes due to additional property and offer suggestion', async (t) => {
const config = {
routes: [{ source: '/(.*)', destination: '/api/index.js' }],
};
@@ -63,7 +62,7 @@ test('[dev-validate] should error with invalid routes due to additional property
);
});
test('[dev-validate] should error with invalid routes array type', async t => {
test('[dev-validate] should error with invalid routes array type', async (t) => {
const config = {
routes: { src: '/(.*)', dest: '/api/index.js' },
};
@@ -75,7 +74,7 @@ test('[dev-validate] should error with invalid routes array type', async t => {
);
});
test('[dev-validate] should error with invalid redirects array object', async t => {
test('[dev-validate] should error with invalid redirects array object', async (t) => {
const config = {
redirects: [
{
@@ -94,7 +93,7 @@ test('[dev-validate] should error with invalid redirects array object', async t
);
});
test('[dev-validate] should error with invalid redirects.permanent poperty', async t => {
test('[dev-validate] should error with invalid redirects.permanent poperty', async (t) => {
const config = {
redirects: [{ source: '/', destination: '/go', permanent: 'yes' }],
};
@@ -109,7 +108,7 @@ test('[dev-validate] should error with invalid redirects.permanent poperty', asy
);
});
test('[dev-validate] should error with invalid cleanUrls type', async t => {
test('[dev-validate] should error with invalid cleanUrls type', async (t) => {
const config = {
cleanUrls: 'true',
};
@@ -124,7 +123,7 @@ test('[dev-validate] should error with invalid cleanUrls type', async t => {
);
});
test('[dev-validate] should error with invalid trailingSlash type', async t => {
test('[dev-validate] should error with invalid trailingSlash type', async (t) => {
const config = {
trailingSlash: [true],
};
@@ -139,7 +138,7 @@ test('[dev-validate] should error with invalid trailingSlash type', async t => {
);
});
test('[dev-validate] should error with invalid headers property', async t => {
test('[dev-validate] should error with invalid headers property', async (t) => {
const config = {
headers: [{ 'Content-Type': 'text/html' }],
};
@@ -154,7 +153,7 @@ test('[dev-validate] should error with invalid headers property', async t => {
);
});
test('[dev-validate] should error with invalid headers.source type', async t => {
test('[dev-validate] should error with invalid headers.source type', async (t) => {
const config = {
headers: [{ source: [{ 'Content-Type': 'text/html' }] }],
};
@@ -169,7 +168,7 @@ test('[dev-validate] should error with invalid headers.source type', async t =>
);
});
test('[dev-validate] should error with invalid headers additional property', async t => {
test('[dev-validate] should error with invalid headers additional property', async (t) => {
const config = {
headers: [{ source: '/', stuff: [{ 'Content-Type': 'text/html' }] }],
};
@@ -184,7 +183,7 @@ test('[dev-validate] should error with invalid headers additional property', asy
);
});
test('[dev-validate] should error with invalid headers wrong nested headers type', async t => {
test('[dev-validate] should error with invalid headers wrong nested headers type', async (t) => {
const config = {
headers: [{ source: '/', headers: [{ 'Content-Type': 'text/html' }] }],
};
@@ -199,7 +198,7 @@ test('[dev-validate] should error with invalid headers wrong nested headers type
);
});
test('[dev-validate] should error with invalid headers wrong nested headers additional property', async t => {
test('[dev-validate] should error with invalid headers wrong nested headers additional property', async (t) => {
const config = {
headers: [
{ source: '/', headers: [{ key: 'Content-Type', val: 'text/html' }] },
@@ -216,7 +215,7 @@ test('[dev-validate] should error with invalid headers wrong nested headers addi
);
});
test('[dev-validate] should error with too many redirects', async t => {
test('[dev-validate] should error with too many redirects', async (t) => {
const config = {
redirects: Array.from({ length: 5000 }).map((_, i) => ({
source: `/${i}`,
@@ -234,7 +233,7 @@ test('[dev-validate] should error with too many redirects', async t => {
);
});
test('[dev-validate] should error with too many nested headers', async t => {
test('[dev-validate] should error with too many nested headers', async (t) => {
const config = {
headers: [
{
@@ -260,3 +259,25 @@ test('[dev-validate] should error with too many nested headers', async t => {
'https://vercel.com/docs/configuration#project/headers'
);
});
test('[dev-validate] should error with "functions" and "builds"', async (t) => {
const config = {
builds: [
{
src: 'index.html',
use: '@vercel/static',
},
],
functions: {
'api/test.js': {
memory: 1024,
},
},
};
const error = validateConfig(config);
t.deepEqual(
error.message,
'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.'
);
t.deepEqual(error.link, 'https://vercel.link/functions-and-builds');
});

View File

@@ -1 +1 @@
{ "env": { "FOO": "bar" } }
{"env":{"FOO":"bar"}}

View File

@@ -1 +1 @@
{ "builds": [{ "src": "index.js", "use": "@vercel/node@canary" }] }
{"builds":[{"src":"index.js","use":"@vercel/node@canary"}]}

View File

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

View File

@@ -411,7 +411,6 @@ test('[vercel dev] `vercel.json` should be invalidated if deleted', async t => {
{
// Env var should not be set after `vercel.json` is deleted
await fs.remove(configPath);
await sleep(1000);
const res = await fetch(`http://localhost:${port}/api`);
const body = await res.json();
@@ -454,7 +453,6 @@ test('[vercel dev] reflects changes to config and env without restart', async t
],
};
await fs.writeJSON(configPath, config);
await sleep(1000);
const res = await fetch(`http://localhost:${port}/?foo=bar`);
const body = await res.json();
@@ -476,7 +474,6 @@ test('[vercel dev] reflects changes to config and env without restart', async t
],
};
await fs.writeJSON(configPath, config);
await sleep(1000);
const res = await fetch(`http://localhost:${port}/?foo=baz`);
const body = await res.json();
@@ -495,7 +492,6 @@ test('[vercel dev] reflects changes to config and env without restart', async t
},
};
await fs.writeJSON(configPath, config);
await sleep(1000);
const res = await fetch(`http://localhost:${port}/?foo=baz`);
const body = await res.json();
@@ -514,7 +510,6 @@ test('[vercel dev] reflects changes to config and env without restart', async t
},
};
await fs.writeJSON(configPath, config);
await sleep(1000);
const res = await fetch(`http://localhost:${port}/?foo=boo`);
const body = await res.json();

View File

@@ -219,9 +219,7 @@ const createUser = async () => {
email = user.email;
contextName = user.username;
session = Math.random()
.toString(36)
.split('.')[1];
session = Math.random().toString(36).split('.')[1];
},
{ retries: 3, factor: 1 }
);
@@ -2365,7 +2363,7 @@ test('fail to deploy a Lambda with an incorrect value for of memory', async t =>
t.is(output.exitCode, 1, formatOutput(output));
t.regex(output.stderr, /steps of 64/gm, formatOutput(output));
t.regex(output.stderr, /More details/gm, formatOutput(output));
t.regex(output.stderr, /Learn More/gm, formatOutput(output));
});
test('deploy a Lambda with 3 seconds of maxDuration', async t => {
@@ -2470,9 +2468,7 @@ test('change user', async t => {
test('should show prompts to set up project', async t => {
const directory = fixture('project-link');
const projectName = `project-link-${
Math.random()
.toString(36)
.split('.')[1]
Math.random().toString(36).split('.')[1]
}`;
// remove previously linked project if it exists
@@ -2598,9 +2594,7 @@ test('should show prompts to set up project', async t => {
test('should prefill "project name" prompt with folder name', async t => {
const projectName = `static-deployment-${
Math.random()
.toString(36)
.split('.')[1]
Math.random().toString(36).split('.')[1]
}`;
const src = fixture('static-deployment');
@@ -2648,9 +2642,7 @@ test('should prefill "project name" prompt with folder name', async t => {
test('should prefill "project name" prompt with --name', async t => {
const directory = fixture('static-deployment');
const projectName = `static-deployment-${
Math.random()
.toString(36)
.split('.')[1]
Math.random().toString(36).split('.')[1]
}`;
// remove previously linked project if it exists
@@ -2708,9 +2700,7 @@ test('should prefill "project name" prompt with --name', async t => {
test('should prefill "project name" prompt with now.json `name`', async t => {
const directory = fixture('static-deployment');
const projectName = `static-deployment-${
Math.random()
.toString(36)
.split('.')[1]
Math.random().toString(36).split('.')[1]
}`;
// remove previously linked project if it exists

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "8.1.1-canary.1",
"version": "8.2.0",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -38,6 +38,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "2.4.1",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/go",
"version": "1.1.3-canary.1",
"version": "1.1.3",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "2.6.8-canary.4",
"version": "2.6.11",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -36,6 +36,7 @@
"get-port": "5.0.0",
"resolve-from": "5.0.0",
"semver": "6.1.1",
"set-cookie-parser": "2.4.6",
"typescript": "3.9.3",
"yazl": "https://github.com/ijjk/yazl#70949c55b482647669ce37023017b1514c42b33c"
}

View File

@@ -1,4 +1,5 @@
import buildUtils from './build-utils';
import url from 'url';
const {
createLambda,
debug,
@@ -14,18 +15,18 @@ const {
} = buildUtils;
import {
Lambda,
BuildOptions,
Config,
FileBlob,
FileFsRef,
Files,
Lambda,
NowBuildError,
PackageJson,
PrepareCacheOptions,
Prerender,
NowBuildError,
} from '@vercel/build-utils';
import { Route, Handler } from '@vercel/routing-utils';
import { Handler, Route } from '@vercel/routing-utils';
import {
convertHeaders,
convertRedirects,
@@ -33,6 +34,7 @@ import {
} from '@vercel/routing-utils/dist/superstatic';
import nodeFileTrace, { NodeFileTraceReasons } from '@zeit/node-file-trace';
import { ChildProcess, fork } from 'child_process';
import escapeStringRegexp from 'escape-string-regexp';
import {
lstat,
pathExists,
@@ -45,7 +47,6 @@ import path from 'path';
import resolveFrom from 'resolve-from';
import semver from 'semver';
import createServerlessConfig from './create-serverless-config';
import escapeStringRegexp from 'escape-string-regexp';
import nextLegacyVersions from './legacy-versions';
import {
createLambdaFromPseudoLayers,
@@ -70,7 +71,7 @@ import {
syncEnvVars,
validateEntrypoint,
} from './utils';
// import findUp from 'find-up';
import findUp from 'find-up';
import { Sema } from 'async-sema';
interface BuildParamsMeta {
@@ -90,7 +91,7 @@ export const version = 2;
const htmlContentType = 'text/html; charset=utf-8';
const nowDevChildProcesses = new Set<ChildProcess>();
['SIGINT', 'SIGTERM'].forEach(signal => {
['SIGINT', 'SIGTERM'].forEach((signal) => {
process.once(signal as NodeJS.Signals, () => {
for (const child of nowDevChildProcesses) {
debug(
@@ -107,7 +108,13 @@ const MAX_AGE_ONE_YEAR = 31536000;
/**
* Read package.json from files
*/
async function readPackageJson(entryPath: string) {
async function readPackageJson(
entryPath: string
): Promise<{
scripts?: { [key: string]: string };
dependencies?: { [key: string]: string };
devDependencies?: { [key: string]: string };
}> {
const packagePath = path.join(entryPath, 'package.json');
try {
@@ -138,15 +145,38 @@ async function writeNpmRc(workPath: string, token: string) {
);
}
function getNextVersion(packageJson: {
dependencies?: { [key: string]: string };
devDependencies?: { [key: string]: string };
}) {
let nextVersion;
if (packageJson.dependencies && packageJson.dependencies.next) {
nextVersion = packageJson.dependencies.next;
} else if (packageJson.devDependencies && packageJson.devDependencies.next) {
nextVersion = packageJson.devDependencies.next;
/**
* Get the installed Next version.
*/
function getRealNextVersion(entryPath: string): string | false {
try {
// First try to resolve the `next` dependency and get the real version from its
// package.json. This allows the builder to be used with frameworks like Blitz that
// bundle Next but where Next isn't in the project root's package.json
const nextVersion: string = require(resolveFrom(
entryPath,
'next/package.json'
)).version;
debug(`Detected Next.js version: ${nextVersion}`);
return nextVersion;
} catch (_ignored) {
debug(
`Could not identify real Next.js version, ensure it is defined as a project dependency.`
);
return false;
}
}
/**
* Get the package.json Next version.
*/
async function getNextVersionRange(entryPath: string): Promise<string | false> {
let nextVersion: string | false = false;
const pkg = await readPackageJson(entryPath);
if (pkg.dependencies && pkg.dependencies.next) {
nextVersion = pkg.dependencies.next;
} else if (pkg.devDependencies && pkg.devDependencies.next) {
nextVersion = pkg.devDependencies.next;
}
return nextVersion;
}
@@ -217,60 +247,43 @@ export const build = async ({
await download(files, workPath, meta);
const pkg = await readPackageJson(entryPath);
const nextVersion = getNextVersion(pkg);
const nextVersionRange = await getNextVersionRange(entryPath);
const nodeVersion = await getNodeVersion(entryPath, undefined, config, meta);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
if (!nextVersion) {
throw new NowBuildError({
code: 'NEXT_NO_VERSION',
message:
'No Next.js version could be detected in "package.json". Make sure `"next"` is installed in "dependencies" or "devDependencies"',
});
const nowJsonPath = await findUp(['now.json', 'vercel.json'], {
cwd: path.join(workPath, path.dirname(entrypoint)),
});
let hasLegacyRoutes = false;
const hasFunctionsConfig = !!config.functions;
if (nowJsonPath) {
const nowJsonData = JSON.parse(await readFile(nowJsonPath, 'utf8'));
if (Array.isArray(nowJsonData.routes) && nowJsonData.routes.length > 0) {
hasLegacyRoutes = true;
console.warn(
`WARNING: your application is being opted out of @vercel/next's optimized lambdas mode due to legacy routes in ${path.basename(
nowJsonPath
)}. http://err.sh/vercel/vercel/next-legacy-routes-optimized-lambdas`
);
}
}
// let nowJsonPath = Object.keys(files).find(file => {
// return file.endsWith('now.json') || file.endsWith('vercel.json')
// })
// if (nowJsonPath) nowJsonPath = files[nowJsonPath].fsPath
// if (!nowJsonPath) {
// nowJsonPath = await findUp(['now.json', 'vercel.json'], {
// cwd: path.join(workPath, path.dirname(entrypoint))
// })
// }
// let hasLegacyRoutes = false;
// const hasFunctionsConfig = !!config.functions;
// if (nowJsonPath) {
// const nowJsonData = JSON.parse(await readFile(nowJsonPath, 'utf8'));
// if (Array.isArray(nowJsonData.routes) && nowJsonData.routes.length > 0) {
// hasLegacyRoutes = true;
// console.warn(
// `WARNING: your application is being opted out of @vercel/next's optimized lambdas mode due to legacy routes in ${path.basename(
// nowJsonPath
// )}. http://err.sh/vercel/vercel/next-legacy-routes-optimized-lambdas`
// );
// }
// }
// if (hasFunctionsConfig) {
// console.warn(
// `WARNING: Your application is being opted out of "@vercel/next" optimized lambdas mode due to \`functions\` config.\nMore info: http://err.sh/vercel/vercel/next-functions-config-optimized-lambdas`
// );
// }
if (hasFunctionsConfig) {
console.warn(
`WARNING: Your application is being opted out of "@vercel/next" optimized lambdas mode due to \`functions\` config.\nMore info: http://err.sh/vercel/vercel/next-functions-config-optimized-lambdas`
);
}
// default to true but still allow opting out with the config
const isSharedLambdas = !!config.sharedLambdas;
// !hasLegacyRoutes &&
// !hasFunctionsConfig &&
// typeof config.sharedLambdas === 'undefined'
// ? true
// : !!config.sharedLambdas;
const isSharedLambdas =
!hasLegacyRoutes &&
!hasFunctionsConfig &&
typeof config.sharedLambdas === 'undefined'
? true
: !!config.sharedLambdas;
if (meta.isDev) {
let childProcess: ChildProcess | undefined;
@@ -315,7 +328,7 @@ export const build = async ({
console.warn('WARNING: You should not upload the `.next` directory.');
}
const isLegacy = isLegacyNext(nextVersion);
const isLegacy = nextVersionRange && isLegacyNext(nextVersionRange);
let shouldRunScript = 'now-build';
debug(`MODE: ${isLegacy ? 'legacy' : 'serverless'}`);
@@ -369,18 +382,20 @@ export const build = async ({
console.log('Installing dependencies...');
await runNpmInstall(entryPath, ['--prefer-offline'], spawnOpts, meta);
let realNextVersion: string | undefined;
try {
realNextVersion = require(resolveFrom(entryPath, 'next/package.json'))
.version;
debug(`Detected Next.js version: ${realNextVersion}`);
} catch (_ignored) {
debug(`Could not identify real Next.js version, that's OK!`);
// Refetch Next version now that dependencies are installed.
// This will now resolve the actual installed Next version,
// even if Next isn't in the project package.json
const nextVersion = getRealNextVersion(entryPath);
if (!nextVersion) {
throw new NowBuildError({
code: 'NEXT_NO_VERSION',
message:
'No Next.js version could be detected in your project. Make sure `"next"` is installed in "dependencies" or "devDependencies"',
});
}
if (!isLegacy) {
await createServerlessConfig(workPath, entryPath, realNextVersion);
await createServerlessConfig(workPath, entryPath, nextVersion);
}
debug('Running user script...');
@@ -417,7 +432,7 @@ export const build = async ({
const routesManifest = await getRoutesManifest(
entryPath,
outputDirectory,
realNextVersion
nextVersion
);
const prerenderManifest = await getPrerenderManifest(entryPath);
const headers: Route[] = [];
@@ -458,21 +473,19 @@ export const build = async ({
}
dataRoutes.push({
src: dataRoute.dataRouteRegex.replace(
/^\^/,
`^${appMountPrefixNoTrailingSlash}`
),
src: (
dataRoute.namedDataRouteRegex || dataRoute.dataRouteRegex
).replace(/^\^/, `^${appMountPrefixNoTrailingSlash}`),
dest: path.join(
'/',
entryDirectory,
// make sure to route SSG data route to the data prerender
// output, we don't do this for SSP routes since they don't
// have a separate data output
(ssgDataRoute && ssgDataRoute.dataRoute) || dataRoute.page,
`${
`${(ssgDataRoute && ssgDataRoute.dataRoute) || dataRoute.page}${
dataRoute.routeKeys
? `?${Object.keys(dataRoute.routeKeys)
.map(key => `${dataRoute.routeKeys![key]}=$${key}`)
.map((key) => `${dataRoute.routeKeys![key]}=$${key}`)
.join('&')}`
: ''
}`
@@ -703,7 +716,7 @@ export const build = async ({
);
const nodeModules = excludeFiles(
await glob('node_modules/**', entryPath),
file => file.startsWith('node_modules/.cache')
(file) => file.startsWith('node_modules/.cache')
);
const launcherFiles = {
'now__bridge.js': new FileFsRef({
@@ -732,7 +745,7 @@ export const build = async ({
const launcherData = await readFile(launcherPath, 'utf8');
await Promise.all(
Object.keys(pages).map(async page => {
Object.keys(pages).map(async (page) => {
// These default pages don't have to be handled as they'd always 404
if (['_app.js', '_error.js', '_document.js'].includes(page)) {
return;
@@ -848,10 +861,7 @@ export const build = async ({
// Assume tracing to be safe, bail if we know we don't need it.
let requiresTracing = hasLambdas;
try {
if (
realNextVersion &&
semver.lt(realNextVersion, ExperimentalTraceVersion)
) {
if (nextVersion && semver.lt(nextVersion, ExperimentalTraceVersion)) {
debug(
'Next.js version is too old for us to trace the required dependencies.\n' +
'Assuming Next.js has handled it!'
@@ -870,6 +880,7 @@ export const build = async ({
[filePath: string]: FileFsRef;
};
let canUsePreviewMode = false;
let pseudoLayerBytes = 0;
let apiPseudoLayerBytes = 0;
const pseudoLayers: PseudoLayer[] = [];
@@ -892,11 +903,12 @@ export const build = async ({
const apiPages: string[] = [];
const nonApiPages: string[] = [];
const allPagePaths = Object.keys(pages).map(page => pages[page].fsPath);
const allPagePaths = Object.keys(pages).map((page) => pages[page].fsPath);
for (const page of allPagePaths) {
if (isApiPage(page)) {
apiPages.push(page);
canUsePreviewMode = true;
} else {
nonApiPages.push(page);
}
@@ -907,10 +919,10 @@ export const build = async ({
reasons: apiReasons,
} = await nodeFileTrace(apiPages, { base: workPath });
const { fileList, reasons: nonApiReasons } = await nodeFileTrace(
nonApiPages,
{ base: workPath }
);
const {
fileList,
reasons: nonApiReasons,
} = await nodeFileTrace(nonApiPages, { base: workPath });
debug(`node-file-trace result for pages: ${fileList}`);
@@ -984,7 +996,7 @@ export const build = async ({
debug(
'detected (legacy) assets to be bundled with serverless function:'
);
assetKeys.forEach(assetFile => debug(`\t${assetFile}`));
assetKeys.forEach((assetFile) => debug(`\t${assetFile}`));
debug(
'\nPlease upgrade to Next.js 9.1 to leverage modern asset handling.'
);
@@ -1124,7 +1136,7 @@ export const build = async ({
}
} else {
await Promise.all(
pageKeys.map(async page => {
pageKeys.map(async (page) => {
// These default pages don't have to be handled as they'd always 404
if (['_app.js', '_document.js'].includes(page)) {
return;
@@ -1205,8 +1217,8 @@ export const build = async ({
false,
routesManifest,
new Set(prerenderManifest.omittedRoutes)
).then(arr =>
arr.map(route => {
).then((arr) =>
arr.map((route) => {
route.src = route.src.replace('^', `^${dynamicPrefix}`);
return route;
})
@@ -1216,6 +1228,21 @@ export const build = async ({
const launcherPath = path.join(__dirname, 'templated-launcher-shared.js');
const launcherData = await readFile(launcherPath, 'utf8');
// we need to include the prerenderManifest.omittedRoutes here
// for the page to be able to be matched in the lambda for preview mode
const completeDynamicRoutes = await getDynamicRoutes(
entryPath,
entryDirectory,
dynamicPages,
false,
routesManifest
).then((arr) =>
arr.map((route) => {
route.src = route.src.replace('^', `^${dynamicPrefix}`);
return route;
})
);
await Promise.all(
[...apiLambdaGroups, ...pageLambdaGroups].map(
async function buildLambdaGroup(group: LambdaGroup) {
@@ -1230,7 +1257,7 @@ export const build = async ({
const pages = {
${groupPageKeys
.map(
page =>
(page) =>
`'${page}': require('./${path.join(
'./',
group.pages[page].pageFileName
@@ -1275,7 +1302,7 @@ export const build = async ({
// for prerendered dynamic routes (/blog/post-1) we need to
// find the match since it won't match the page directly
const dynamicRoutes = ${JSON.stringify(
dynamicRoutes.map(route => ({
completeDynamicRoutes.map((route) => ({
src: route.src,
dest: route.dest,
}))
@@ -1461,33 +1488,41 @@ export const build = async ({
message: 'invariant: htmlFsRef != null && jsonFsRef != null',
});
}
if (!canUsePreviewMode) {
htmlFsRef.contentType = htmlContentType;
prerenders[outputPathPage] = htmlFsRef;
prerenders[outputPathData] = jsonFsRef;
}
}
prerenders[outputPathPage] = new Prerender({
expiration: initialRevalidate,
lambda,
fallback: htmlFsRef,
group: prerenderGroup,
bypassToken: prerenderManifest.bypassToken,
});
prerenders[outputPathData] = new Prerender({
expiration: initialRevalidate,
lambda,
fallback: jsonFsRef,
group: prerenderGroup,
bypassToken: prerenderManifest.bypassToken,
});
if (prerenders[outputPathPage] == null) {
prerenders[outputPathPage] = new Prerender({
expiration: initialRevalidate,
lambda,
fallback: htmlFsRef,
group: prerenderGroup,
bypassToken: prerenderManifest.bypassToken,
});
prerenders[outputPathData] = new Prerender({
expiration: initialRevalidate,
lambda,
fallback: jsonFsRef,
group: prerenderGroup,
bypassToken: prerenderManifest.bypassToken,
});
++prerenderGroup;
++prerenderGroup;
}
};
Object.keys(prerenderManifest.staticRoutes).forEach(route =>
Object.keys(prerenderManifest.staticRoutes).forEach((route) =>
onPrerenderRoute(route, { isBlocking: false, isFallback: false })
);
Object.keys(prerenderManifest.fallbackRoutes).forEach(route =>
Object.keys(prerenderManifest.fallbackRoutes).forEach((route) =>
onPrerenderRoute(route, { isBlocking: false, isFallback: true })
);
Object.keys(prerenderManifest.legacyBlockingRoutes).forEach(route =>
Object.keys(prerenderManifest.legacyBlockingRoutes).forEach((route) =>
onPrerenderRoute(route, { isBlocking: true, isFallback: false })
);
@@ -1551,7 +1586,7 @@ export const build = async ({
// We need to delete lambdas from output instead of omitting them from the
// start since we rely on them for powering Preview Mode (read above in
// onPrerenderRoute).
prerenderManifest.omittedRoutes.forEach(routeKey => {
prerenderManifest.omittedRoutes.forEach((routeKey) => {
// Get the route file as it'd be mounted in the builder output
const routeFileNoExt = path.posix.join(
entryDirectory,
@@ -1566,6 +1601,7 @@ export const build = async ({
delete lambdas[routeFileNoExt];
});
}
const mergedDataRoutesLambdaRoutes = [];
const mergedDynamicRoutesLambdaRoutes = [];
if (isSharedLambdas) {
@@ -1580,12 +1616,30 @@ export const build = async ({
mergedDynamicRoutesLambdaRoutes.push(route);
if (pageLambdaMap[route.dest!]) {
const { pathname } = url.parse(route.dest!);
if (pathname && pageLambdaMap[pathname]) {
mergedDynamicRoutesLambdaRoutes.push(
dynamicPageLambdaRoutesMap[route.dest!]
dynamicPageLambdaRoutesMap[pathname]
);
}
}
for (let i = 0; i < dataRoutes.length; i++) {
const route = dataRoutes[i];
mergedDataRoutesLambdaRoutes.push(route);
const { pathname } = url.parse(route.dest!);
if (
pathname &&
pageLambdaMap[pathname] &&
dynamicPageLambdaRoutesMap[pathname]
) {
mergedDataRoutesLambdaRoutes.push(dynamicPageLambdaRoutesMap[pathname]);
}
}
}
return {
@@ -1655,7 +1709,7 @@ export const build = async ({
{ handle: 'rewrite' },
// /_next/data routes for getServerProps/getStaticProps pages
...dataRoutes,
...(isSharedLambdas ? mergedDataRoutesLambdaRoutes : dataRoutes),
// re-check page routes to map them to the lambda
...pageLambdaRoutes,
@@ -1695,9 +1749,12 @@ export const build = async ({
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),
dest: path.join(
'/',
(static404Page
? static404Page
: pageLambdaMap[page404Path]) as string
),
status: 404,
headers: {
@@ -1737,14 +1794,8 @@ export const prepareCache = async ({
const entryPath = path.join(workPath, entryDirectory);
const outputDirectory = config.outputDirectory || '.next';
const pkg = await readPackageJson(entryPath);
const nextVersion = getNextVersion(pkg);
if (!nextVersion)
throw new NowBuildError({
code: 'NEXT_VERSION_PARSE_FAILED',
message: 'Could not parse Next.js version',
});
const isLegacy = isLegacyNext(nextVersion);
const nextVersionRange = await getNextVersionRange(entryPath);
const isLegacy = nextVersionRange && isLegacyNext(nextVersionRange);
if (isLegacy) {
// skip caching legacy mode (swapping deps between all and production can get bug-prone)

View File

@@ -198,7 +198,7 @@ async function getRoutes(
// If default pages dir isn't found check for `src/pages`
if (
!pagesDir &&
fileKeys.some(file =>
fileKeys.some((file) =>
file.startsWith(path.join(entryDirectory, 'src/pages'))
)
) {
@@ -260,7 +260,7 @@ async function getRoutes(
entryDirectory,
dynamicPages,
true
).then(arr =>
).then((arr) =>
arr.map((route: Source) => {
// convert to make entire RegExp match as one group
route.src = route.src
@@ -287,7 +287,7 @@ async function getRoutes(
};
// Only add the route if a page is not already using it
if (!routes.some(r => (r as Source).src === route.src)) {
if (!routes.some((r) => (r as Source).src === route.src)) {
routes.push(route);
}
}
@@ -363,17 +363,18 @@ export async function getRoutesManifest(
// eslint-disable-next-line @typescript-eslint/no-var-requires
const routesManifest: RoutesManifest = require(pathRoutesManifest);
// massage temporary array based routeKeys from v1/v2 of routes
// manifest into new object based
for (const route of [
...(routesManifest.dataRoutes || []),
...(routesManifest.dynamicRoutes || []),
]) {
// remove temporary array based routeKeys from v1/v2 of routes
// manifest since it can result in invalid routes
for (const route of routesManifest.dataRoutes || []) {
if (Array.isArray(route.routeKeys)) {
route.routeKeys = route.routeKeys.reduce((prev, cur) => {
prev[cur] = cur;
return prev;
}, {});
delete route.routeKeys;
delete route.namedDataRouteRegex;
}
}
for (const route of routesManifest.dynamicRoutes || []) {
if (Array.isArray(route.routeKeys)) {
delete route.routeKeys;
delete route.namedRegex;
}
}
@@ -419,7 +420,7 @@ export async function getDynamicRoutes(
dest: `${!isDev ? path.join('/', entryDirectory, page) : page}${
routeKeys
? `?${Object.keys(routeKeys)
.map(key => `${routeKeys[key]}=$${key}`)
.map((key) => `${routeKeys[key]}=$${key}`)
.join('&')}`
: ''
}`,
@@ -478,13 +479,13 @@ export async function getDynamicRoutes(
});
}
const pageMatchers = getSortedRoutes(dynamicPages).map(pageName => ({
const pageMatchers = getSortedRoutes(dynamicPages).map((pageName) => ({
pageName,
matcher: getRouteRegex && getRouteRegex(pageName).re,
}));
const routes: Source[] = [];
pageMatchers.forEach(pageMatcher => {
pageMatchers.forEach((pageMatcher) => {
// in `vercel dev` we don't need to prefix the destination
const dest = !isDev
? path.join('/', entryDirectory, pageMatcher.pageName)
@@ -861,7 +862,7 @@ export async function getPrerenderManifest(
omittedRoutes: [],
};
routes.forEach(route => {
routes.forEach((route) => {
const {
initialRevalidateSeconds,
dataRoute,
@@ -877,7 +878,7 @@ export async function getPrerenderManifest(
};
});
lazyRoutes.forEach(lazyRoute => {
lazyRoutes.forEach((lazyRoute) => {
const {
routeRegex,
fallback,
@@ -915,7 +916,7 @@ export async function getPrerenderManifest(
omittedRoutes: [],
};
routes.forEach(route => {
routes.forEach((route) => {
const {
initialRevalidateSeconds,
dataRoute,
@@ -931,7 +932,7 @@ export async function getPrerenderManifest(
};
});
lazyRoutes.forEach(lazyRoute => {
lazyRoutes.forEach((lazyRoute) => {
const {
routeRegex,
fallback,

View File

@@ -0,0 +1,38 @@
{
"version": 2,
"uploadNowJson": true,
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
],
"probes": [
{
"path": "/teams/invite/hello",
"mustContain": "hello from /teams/invite/[inviteCode] hello"
},
{
"path": "/groups/first",
"mustContain": "hello from /groups/[id] first"
},
{
"path": "/groups/second",
"mustContain": "hello from /groups/[id] second"
},
{
"path": "/groups/third",
"mustContain": "loading..."
},
{
"path": "/another/invite/hello",
"mustContain": "hello from /[teamSlug]/[project]/[id]"
},
{
"logMustNotContain": "WARNING: your application is being opted out of @vercel/next's optimized lambdas mode due to legacy routes"
},
{
"logMustNotContain": "WARNING: Your application is being opted out of \"@vercel/next\" optimized lambdas mode due to `functions` config"
}
]
}

View File

@@ -0,0 +1 @@
export default () => 'hello from /[teamSlug]/[project]/[id]'

View File

@@ -0,0 +1,18 @@
import { useRouter } from 'next/router'
export const getStaticProps = ({ params }) => {
return {
props: {
id: params.id
}
}
}
export const getStaticPaths = () => ({
paths: ['first', 'second'].map(id => ({ params: { id }})),
fallback: true
})
export default ({ id }) => useRouter().isFallback
? `loading...`
: `hello from /groups/[id] ${id}`

View File

@@ -0,0 +1,9 @@
export const getServerSideProps = ({ params }) => {
return {
props: {
code: params.inviteCode
}
}
}
export default ({ code }) => `hello from /teams/invite/[inviteCode] ${code}`

View File

@@ -0,0 +1,3 @@
export default (_, res) => {
res.send('OK');
};

View File

@@ -17,6 +17,9 @@
"path": "/api/memory",
"status": 200,
"mustContain": "128"
},
{
"logMustContain": "WARNING: Your application is being opted out of \"@vercel/next\" optimized lambdas mode due to `functions` config"
}
]
}

View File

@@ -1,82 +1,80 @@
module.exports = {
experimental: {
async redirects() {
return [
{
source: '/redir1',
destination: '/redir2',
permanent: true,
},
{
source: '/redir2',
destination: '/hello',
permanent: false,
},
{
source: '/redir/:path',
destination: '/:path',
permanent: false,
},
];
},
async rewrites() {
return [
{
source: '/rewrite1',
destination: '/rewrite2',
},
{
source: '/rewrite2',
destination: '/hello',
},
{
source: '/rewrite/:first/:second',
destination: '/rewrite-2/hello/:second',
},
{
source: '/rewrite-2/:first/:second',
destination: '/params',
},
{
source: '/add-header',
destination: '/hello',
},
{
source: '/catchall-header/:path*',
destination: '/hello',
},
];
},
async redirects() {
return [
{
source: '/redir1',
destination: '/redir2',
permanent: true,
},
{
source: '/redir2',
destination: '/hello',
permanent: false,
},
{
source: '/redir/:path',
destination: '/:path',
permanent: false,
},
];
},
async rewrites() {
return [
{
source: '/rewrite1',
destination: '/rewrite2',
},
{
source: '/rewrite2',
destination: '/hello',
},
{
source: '/rewrite/:first/:second',
destination: '/rewrite-2/hello/:second',
},
{
source: '/rewrite-2/:first/:second',
destination: '/params',
},
{
source: '/add-header',
destination: '/hello',
},
{
source: '/catchall-header/:path*',
destination: '/hello',
},
];
},
async headers() {
return [
{
source: '/add-header',
headers: [
{
key: 'x-hello',
value: 'world',
},
{
key: 'x-another',
value: 'value',
},
],
},
{
source: '/catchall-header/:path*',
headers: [
{
key: 'x-hello',
value: 'world',
},
{
key: 'x-another',
value: 'value',
},
],
},
];
},
async headers() {
return [
{
source: '/add-header',
headers: [
{
key: 'x-hello',
value: 'world',
},
{
key: 'x-another',
value: 'value',
},
],
},
{
source: '/catchall-header/:path*',
headers: [
{
key: 'x-hello',
value: 'world',
},
{
key: 'x-another',
value: 'value',
},
],
},
];
},
};

View File

@@ -1,5 +1,6 @@
{
"version": 2,
"uploadNowJson": true,
"builds": [{ "src": "package.json", "use": "@vercel/next" }],
"probes": [
{
@@ -61,6 +62,12 @@
"x-hello": "world",
"x-another": "value"
}
},
{
"logMustNotContain": "WARNING: your application is being opted out of @vercel/next's optimized lambdas mode due to legacy routes"
},
{
"logMustNotContain": "WARNING: Your application is being opted out of \"@vercel/next\" optimized lambdas mode due to `functions` config"
}
]
}

View File

@@ -2,14 +2,12 @@ module.exports = {
generateBuildId() {
return 'testing-build-id';
},
experimental: {
async rewrites() {
return [
{
source: '/:path*',
destination: '/params',
},
];
},
async rewrites() {
return [
{
source: '/:path*',
destination: '/params',
},
];
},
};

View File

@@ -1,11 +1,15 @@
{
"version": 2,
"uploadNowJson": true,
"builds": [{ "src": "packages/web/next.config.js", "use": "@vercel/next" }],
"routes": [{ "src": "/(.*)", "dest": "/packages/web/$1", "continue": true }],
"probes": [
{
"path": "/",
"mustContain": "hello world <!-- -->6"
},
{
"logMustContain": "WARNING: your application is being opted out of @vercel/next's optimized lambdas mode due to legacy routes"
}
]
}

View File

@@ -3,23 +3,21 @@ module.exports = {
return 'testing-build-id';
},
exportPathMap: d => d,
experimental: {
async rewrites() {
return [
{
source: '/first',
destination: '/',
},
];
},
async redirects() {
return [
{
source: '/second',
destination: '/about',
permanent: false,
},
];
},
async rewrites() {
return [
{
source: '/first',
destination: '/',
},
];
},
async redirects() {
return [
{
source: '/second',
destination: '/about',
permanent: false,
},
];
},
};

View File

@@ -35,6 +35,26 @@
{
"path": "/_next/__NEXT_SCRIPT__(/another)",
"mustNotContain": "hello from another"
},
{
"path": "/docs/api/my-slug",
"mustContain": "index slug: my-slug"
},
{
"path": "/docs/api/my-slug/another",
"mustContain": "another slug: my-slug"
},
{
"path": "/docs/api/hello",
"mustContain": "hello from hello"
},
{
"path": "/docs/blog/post-1",
"mustContain": "index post: post-1"
},
{
"path": "/docs/blog/post-1/comments",
"mustContain": "comments post: post-1"
}
]
}

View File

@@ -0,0 +1 @@
export default (req, res) => res.end(`another slug: ${req.query.slug}`)

View File

@@ -0,0 +1 @@
export default (req, res) => res.end(`index slug: ${req.query.slug}`)

View File

@@ -0,0 +1 @@
export default (req, res) => res.end('hello from hello');

View File

@@ -0,0 +1,9 @@
export const getServerSideProps = ({ params }) => ({
props: {
post: params.post
}
})
export default function Comment({ post }) {
return `comments post: ${post}`
}

View File

@@ -0,0 +1,9 @@
export const getServerSideProps = ({ params }) => ({
props: {
post: params.post
}
})
export default function Post({ post }) {
return `index post: ${post}`
}

View File

@@ -0,0 +1,3 @@
export default (_, res) => {
res.send('OK');
};

View File

@@ -1,6 +1,6 @@
{
"dependencies": {
"next": "9.2.3-canary.13",
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}

View File

@@ -1,7 +1,7 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getServerProps() {
export async function getServerSideProps() {
return {
props: {
world: 'world',

View File

@@ -1,7 +1,7 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getServerProps() {
export async function getServerSideProps() {
return {
props: {
world: 'world',

View File

@@ -1,7 +1,7 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getServerProps ({ params }) {
export async function getServerSideProps({ params }) {
return {
props: {
post: params.post,

View File

@@ -1,19 +1,19 @@
import React from 'react'
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getServerProps ({ params }) {
export async function getServerSideProps({ params }) {
if (params.post === 'post-10') {
await new Promise(resolve => {
setTimeout(() => resolve(), 1000)
})
await new Promise((resolve) => {
setTimeout(() => resolve(), 1000);
});
}
return {
props: {
post: params.post,
time: (await import('perf_hooks')).performance.now()
time: (await import('perf_hooks')).performance.now(),
},
}
};
}
export default ({ post, time }) => {
@@ -22,5 +22,5 @@ export default ({ post, time }) => {
<p>Post: {post}</p>
<span>time: {time}</span>
</>
)
}
);
};

View File

@@ -1,7 +1,7 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function unstable_getServerProps() {
export async function getServerSideProps() {
return {
props: {
world: 'world',

View File

@@ -1,159 +0,0 @@
/* eslint-env jest */
const fetch = require('node-fetch');
const cheerio = require('cheerio');
module.exports = function(ctx) {
it('should revalidate content properly from pathname', async () => {
const res = await fetch(`${ctx.deploymentUrl}/another`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const initialTime = $('#time').text();
const initialRandom = $('#random').text();
expect($('#hello').text()).toBe('hello: world');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/another`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
expect($('#hello').text()).toBe('hello: world');
expect(initialTime).not.toBe($('#time').text());
expect(initialRandom).not.toBe($('#random').text());
});
it('should revalidate content properly from dynamic pathname', async () => {
const res = await fetch(`${ctx.deploymentUrl}/blog/post-123`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const initialTime = $('#time').text();
const initialRandom = $('#random').text();
expect($('#post').text()).toBe('Post: post-123');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/blog/post-123`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
expect($('#post').text()).toBe('Post: post-123');
expect(initialTime).not.toBe($('#time').text());
expect(initialRandom).not.toBe($('#random').text());
});
it('should revalidate content properly from dynamic pathnames', async () => {
const res = await fetch(`${ctx.deploymentUrl}/blog/post-123/comment-321`);
expect(res.status).toBe(200);
let $ = cheerio.load(await res.text());
const initialTime = $('#time').text();
const initialRandom = $('#random').text();
expect($('#post').text()).toBe('Post: post-123');
expect($('#comment').text()).toBe('Comment: comment-321');
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(`${ctx.deploymentUrl}/blog/post-123/comment-321`);
expect(res2.status).toBe(200);
$ = cheerio.load(await res2.text());
expect($('#post').text()).toBe('Post: post-123');
expect($('#comment').text()).toBe('Comment: comment-321');
expect(initialTime).not.toBe($('#time').text());
expect(initialRandom).not.toBe($('#random').text());
});
it('should revalidate content properly from /_next/data pathname', async () => {
const res = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/another.json`
);
expect(res.status).toBe(200);
const { pageProps: data } = await res.json();
const initialTime = data.time;
const initialRandom = data.random;
expect(data.world).toBe('world');
expect(isNaN(initialTime)).toBe(false);
expect(isNaN(initialRandom)).toBe(false);
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/another.json`
);
expect(res2.status).toBe(200);
const { pageProps: data2 } = await res2.json();
expect(data2.world).toBe('world');
expect(isNaN(data2.time)).toBe(false);
expect(isNaN(data2.random)).toBe(false);
expect(initialTime).not.toBe(data2.time);
expect(initialRandom).not.toBe(data2.random);
});
it('should revalidate content properly from /_next/data dynamic pathname', async () => {
const res = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/blog/post-123.json`
);
expect(res.status).toBe(200);
const { pageProps: data } = await res.json();
const initialTime = data.time;
const initialRandom = data.random;
expect(data.post).toBe('post-123');
expect(isNaN(initialTime)).toBe(false);
expect(isNaN(initialRandom)).toBe(false);
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/blog/post-123.json`
);
expect(res2.status).toBe(200);
const { pageProps: data2 } = await res2.json();
expect(data2.post).toBe('post-123');
expect(isNaN(data2.time)).toBe(false);
expect(isNaN(data2.random)).toBe(false);
expect(initialTime).not.toBe(data2.time);
expect(initialRandom).not.toBe(data2.random);
});
it('should revalidate content properly from /_next/data dynamic pathnames', async () => {
const res = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/blog/post-123/comment-321.json`
);
expect(res.status).toBe(200);
const { pageProps: data } = await res.json();
const initialTime = data.time;
const initialRandom = data.random;
expect(data.post).toBe('post-123');
expect(data.comment).toBe('comment-321');
expect(isNaN(initialTime)).toBe(false);
expect(isNaN(initialRandom)).toBe(false);
// wait for revalidation to occur
await new Promise(resolve => setTimeout(resolve, 2000));
const res2 = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/blog/post-123/comment-321.json`
);
expect(res2.status).toBe(200);
const { pageProps: data2 } = await res2.json();
expect(data2.post).toBe('post-123');
expect(data2.comment).toBe('comment-321');
expect(isNaN(data2.time)).toBe(false);
expect(isNaN(data2.random)).toBe(false);
expect(initialTime).not.toBe(data2.time);
expect(initialRandom).not.toBe(data2.random);
});
};

View File

@@ -1,146 +0,0 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next",
"config": {
"sharedLambdas": true
}
}
],
"probes": [
{
"path": "/lambda",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "MISS"
}
},
{
"path": "/forever",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "PRERENDER"
}
},
{ "delay": 2000 },
{
"path": "/forever",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "HIT"
}
},
{
"path": "/blog/post-3",
"status": 200,
"mustContain": "loading..."
},
{ "delay": 2000 },
{
"path": "/blog/post-3",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/HIT|STALE/"
}
},
{
"path": "/_next/data/testing-build-id/blog/post-4.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "MISS"
}
},
{ "delay": 2000 },
{
"path": "/_next/data/testing-build-id/blog/post-4.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/HIT|STALE/"
}
},
{
"path": "/blog/post-3",
"status": 200,
"mustContain": "post-3"
},
{
"path": "/blog/post-3/comment-3",
"status": 200,
"mustContain": "loading..."
},
{
"path": "/_next/data/testing-build-id/lambda.json",
"status": 404
},
{
"path": "/_next/data/testing-build-id/forever.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/PRERENDER|HIT/"
}
},
{ "delay": 2000 },
{
"path": "/blog/post-3/comment-3",
"status": 200,
"mustContain": "comment-3"
},
{
"path": "/_next/data/testing-build-id/forever.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "HIT"
}
},
{
"path": "/_next/data/testing-build-id/another2.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "PRERENDER"
}
},
{ "delay": 2000 },
{
"path": "/_next/data/testing-build-id/another2.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "HIT"
}
},
{
"path": "/nofallback/one",
"status": 200,
"mustContain": "one"
},
{
"path": "/nofallback/two",
"status": 200,
"mustContain": "two"
},
{
"path": "/nofallback/nope",
"status": 404,
"mustContain": "This page could not be found"
},
{
"path": "/_next/data/testing-build-id/nofallback/one.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/HIT|STALE|PRERENDER/"
}
},
{
"path": "/_next/data/testing-build-id/nofallback/two.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/HIT|STALE|PRERENDER/"
}
},
{
"path": "/_next/data/testing-build-id/nofallback/nope.json",
"status": 404
}
]
}

View File

@@ -1,23 +0,0 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function getStaticProps() {
return {
props: {
world: 'world',
random: Math.random(),
time: new Date().getTime(),
},
unstable_revalidate: 1,
};
}
export default ({ world, time, random }) => {
return (
<>
<p id="hello">hello: {world}</p>
<span id="time">time: {time}</span>
<span id="random">random: {random}</span>
</>
);
};

View File

@@ -1,21 +0,0 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function getStaticProps() {
return {
props: {
world: 'world',
time: new Date().getTime(),
},
unstable_revalidate: 5,
};
}
export default ({ world, time }) => {
return (
<>
<p>hello: {world}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -1,40 +0,0 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function getStaticPaths() {
return {
paths: [
'/blog/post-1/comment-1',
{ params: { post: 'post-2', comment: 'comment-2' } },
'/blog/post-1337/comment-1337',
'/blog/post-123/comment-321'
],
fallback: true,
};
}
// eslint-disable-next-line camelcase
export async function getStaticProps({ params }) {
return {
props: {
post: params.post,
random: Math.random(),
comment: params.comment,
time: new Date().getTime(),
},
unstable_revalidate: 1,
};
}
export default ({ post, comment, time, random }) => {
if (!post) return <p>loading...</p>;
return (
<>
<p id='post'>Post: {post}</p>
<p id='comment'>Comment: {comment}</p>
<span id='time'>time: {time}</span>
<span id='random'>random: {random}</span>
</>
);
};

View File

@@ -1,43 +0,0 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function getStaticPaths() {
return {
paths: [
'/blog/post-1',
{ params: { post: 'post-2' } },
'/blog/post-123',
],
fallback: true,
};
}
// eslint-disable-next-line camelcase
export async function getStaticProps({ params }) {
if (params.post === 'post-10') {
await new Promise(resolve => {
setTimeout(() => resolve(), 1000);
});
}
return {
props: {
post: params.post,
random: Math.random(),
time: (await import('perf_hooks')).performance.now(),
},
unstable_revalidate: 1,
};
}
export default ({ post, time, random }) => {
if (!post) return <p>loading...</p>;
return (
<>
<p id='post'>Post: {post}</p>
<span id='time'>time: {time}</span>
<span id='random'>random: {random}</span>
</>
);
};

View File

@@ -1,21 +0,0 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function getStaticProps() {
return {
props: {
world: 'world',
time: new Date().getTime(),
},
unstable_revalidate: false,
};
}
export default ({ world, time }) => {
return (
<>
<p>hello: {world}</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -1 +0,0 @@
export default () => 'Hi';

View File

@@ -1,5 +0,0 @@
const Page = ({ data }) => <p>{data} world</p>;
Page.getInitialProps = () => ({ data: 'hello' });
export default Page;

View File

@@ -1,31 +0,0 @@
import React from 'react';
// eslint-disable-next-line camelcase
export async function getStaticPaths() {
return {
paths: ['/nofallback/one', { params: { slug: 'two' } }],
fallback: false,
};
}
// eslint-disable-next-line camelcase
export async function getStaticProps({ params }) {
return {
props: {
slug: params.slug,
time: (await import('perf_hooks')).performance.now(),
},
unstable_revalidate: 10,
};
}
export default ({ slug, time }) => {
return (
<>
<p>
Slug ({slug.length}): {slug}
</p>
<span>time: {time}</span>
</>
);
};

View File

@@ -1,5 +1,6 @@
{
"version": 2,
"uploadNowJson": true,
"builds": [{ "src": "package.json", "use": "@vercel/next" }],
"probes": [
{
@@ -133,6 +134,12 @@
{
"path": "/_next/data/testing-build-id/nofallback/nope.json",
"status": 404
},
{
"logMustNotContain": "WARNING: your application is being opted out of @vercel/next's optimized lambdas mode due to legacy routes"
},
{
"logMustNotContain": "WARNING: Your application is being opted out of \"@vercel/next\" optimized lambdas mode due to `functions` config"
}
]
}

View File

@@ -0,0 +1,3 @@
export default (_, res) => {
res.send('OK');
};

View File

@@ -2,218 +2,216 @@ module.exports = {
generateBuildId() {
return 'testing-build-id';
},
experimental: {
async rewrites() {
return [
{
source: '/to-another',
destination: '/another/one',
},
{
source: '/nav',
destination: '/404',
},
{
source: '/hello-world',
destination: '/static/hello.txt',
},
{
source: '/',
destination: '/another',
},
{
source: '/another',
destination: '/multi-rewrites',
},
{
source: '/first',
destination: '/hello',
},
{
source: '/second',
destination: '/hello-again',
},
{
source: '/to-hello',
destination: '/hello',
},
{
source: '/(.*)-:id(\\d+).html',
destination: '/blog/:id',
},
{
source: '/blog/post-1',
destination: '/blog/post-2',
},
{
source: '/test/:path',
destination: '/:path',
},
{
source: '/test-overwrite/:something/:another',
destination: '/params/this-should-be-the-value',
},
{
source: '/params/:something',
destination: '/with-params',
},
{
source: '/query-rewrite/:section/:name',
destination: '/with-params?first=:section&second=:name',
},
{
source: '/hidden/_next/:path*',
destination: '/_next/:path*',
},
{
source: '/api-hello',
destination: '/api/hello',
},
{
source: '/api-hello-regex/(.*)',
destination: '/api/hello?name=:1',
},
{
source: '/api-hello-param/:name',
destination: '/api/hello?hello=:name',
},
{
source: '/api-dynamic-param/:name',
destination: '/api/dynamic/:name?hello=:name',
},
{
source: '/:path/post-321',
destination: '/with-params',
},
{
source: '/a/catch-all/:path*',
destination: '/a/catch-all',
},
];
},
async redirects() {
return [
{
source: '/redirect/me/to-about/:lang',
destination: '/:lang/about',
permanent: false,
},
{
source: '/docs/router-status/:code',
destination: '/docs/v2/network/status-codes#:code',
statusCode: 301,
},
{
source: '/docs/github',
destination: '/docs/v2/advanced/now-for-github',
statusCode: 301,
},
{
source: '/docs/v2/advanced/:all(.*)',
destination: '/docs/v2/more/:all',
statusCode: 301,
},
{
source: '/hello/:id/another',
destination: '/blog/:id',
permanent: false,
},
{
source: '/redirect1',
destination: '/',
permanent: false,
},
{
source: '/redirect2',
destination: '/',
statusCode: 301,
},
{
source: '/redirect3',
destination: '/another',
statusCode: 302,
},
{
source: '/redirect4',
destination: '/',
permanent: true,
},
{
source: '/redir-chain1',
destination: '/redir-chain2',
statusCode: 301,
},
{
source: '/redir-chain2',
destination: '/redir-chain3',
statusCode: 302,
},
{
source: '/redir-chain3',
destination: '/',
statusCode: 303,
},
{
source: '/to-external',
destination: 'https://google.com',
permanent: false,
},
{
source: '/query-redirect/:section/:name',
destination: '/with-params?first=:section&second=:name',
permanent: false,
},
{
source: '/named-like-unnamed/:0',
destination: '/:0',
permanent: false,
},
{
source: '/redirect-override',
destination: '/thank-you-next',
permanent: false,
},
];
},
async rewrites() {
return [
{
source: '/to-another',
destination: '/another/one',
},
{
source: '/nav',
destination: '/404',
},
{
source: '/hello-world',
destination: '/static/hello.txt',
},
{
source: '/',
destination: '/another',
},
{
source: '/another',
destination: '/multi-rewrites',
},
{
source: '/first',
destination: '/hello',
},
{
source: '/second',
destination: '/hello-again',
},
{
source: '/to-hello',
destination: '/hello',
},
{
source: '/(.*)-:id(\\d+).html',
destination: '/blog/:id',
},
{
source: '/blog/post-1',
destination: '/blog/post-2',
},
{
source: '/test/:path',
destination: '/:path',
},
{
source: '/test-overwrite/:something/:another',
destination: '/params/this-should-be-the-value',
},
{
source: '/params/:something',
destination: '/with-params',
},
{
source: '/query-rewrite/:section/:name',
destination: '/with-params?first=:section&second=:name',
},
{
source: '/hidden/_next/:path*',
destination: '/_next/:path*',
},
{
source: '/api-hello',
destination: '/api/hello',
},
{
source: '/api-hello-regex/(.*)',
destination: '/api/hello?name=:1',
},
{
source: '/api-hello-param/:name',
destination: '/api/hello?hello=:name',
},
{
source: '/api-dynamic-param/:name',
destination: '/api/dynamic/:name?hello=:name',
},
{
source: '/:path/post-321',
destination: '/with-params',
},
{
source: '/a/catch-all/:path*',
destination: '/a/catch-all',
},
];
},
async redirects() {
return [
{
source: '/redirect/me/to-about/:lang',
destination: '/:lang/about',
permanent: false,
},
{
source: '/docs/router-status/:code',
destination: '/docs/v2/network/status-codes#:code',
statusCode: 301,
},
{
source: '/docs/github',
destination: '/docs/v2/advanced/now-for-github',
statusCode: 301,
},
{
source: '/docs/v2/advanced/:all(.*)',
destination: '/docs/v2/more/:all',
statusCode: 301,
},
{
source: '/hello/:id/another',
destination: '/blog/:id',
permanent: false,
},
{
source: '/redirect1',
destination: '/',
permanent: false,
},
{
source: '/redirect2',
destination: '/',
statusCode: 301,
},
{
source: '/redirect3',
destination: '/another',
statusCode: 302,
},
{
source: '/redirect4',
destination: '/',
permanent: true,
},
{
source: '/redir-chain1',
destination: '/redir-chain2',
statusCode: 301,
},
{
source: '/redir-chain2',
destination: '/redir-chain3',
statusCode: 302,
},
{
source: '/redir-chain3',
destination: '/',
statusCode: 303,
},
{
source: '/to-external',
destination: 'https://google.com',
permanent: false,
},
{
source: '/query-redirect/:section/:name',
destination: '/with-params?first=:section&second=:name',
permanent: false,
},
{
source: '/named-like-unnamed/:0',
destination: '/:0',
permanent: false,
},
{
source: '/redirect-override',
destination: '/thank-you-next',
permanent: false,
},
];
},
async headers() {
return [
{
source: '/add-header',
headers: [
{
key: 'x-custom-header',
value: 'hello world',
},
{
key: 'x-another-header',
value: 'hello again',
},
],
},
{
source: '/my-headers/(.*)',
headers: [
{
key: 'x-first-header',
value: 'first',
},
{
key: 'x-second-header',
value: 'second',
},
],
},
{
source: '/:path*',
headers: [
{
key: 'x-something',
value: 'applied-everywhere',
},
],
},
];
},
async headers() {
return [
{
source: '/add-header',
headers: [
{
key: 'x-custom-header',
value: 'hello world',
},
{
key: 'x-another-header',
value: 'hello again',
},
],
},
{
source: '/my-headers/(.*)',
headers: [
{
key: 'x-first-header',
value: 'first',
},
{
key: 'x-second-header',
value: 'second',
},
],
},
{
source: '/:path*',
headers: [
{
key: 'x-something',
value: 'applied-everywhere',
},
],
},
];
},
};

View File

@@ -1,19 +1,22 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@vercel/next" }],
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
],
"probes": [
// should handle one-to-one rewrite successfully
{
"path": "/first",
"mustContain": "hello"
},
// should handle chained rewrites successfully
{
"path": "/",
"mustContain": "multi-rewrites"
},
// should not match dynamic route immediately after applying header
{
"path": "/blog/post-321",
@@ -23,7 +26,6 @@
"path": "/blog/post-321",
"mustNotContain": "post-321"
},
// should handle chained redirects successfully
{
"path": "/redir-chain1",
@@ -55,7 +57,6 @@
"redirect": "manual"
}
},
// should redirect successfully with permanent: false
{
"path": "/redirect1",
@@ -67,7 +68,6 @@
"redirect": "manual"
}
},
// should redirect with params successfully
{
"path": "/hello/123/another",
@@ -79,7 +79,6 @@
"redirect": "manual"
}
},
// should redirect with hash successfully
{
"path": "/docs/router-status/500",
@@ -91,7 +90,6 @@
"redirect": "manual"
}
},
// should redirect successfully with provided statusCode
{
"path": "/redirect2",
@@ -103,25 +101,21 @@
"redirect": "manual"
}
},
// should server static files through a rewrite
{
"path": "/hello-world",
"mustContain": "hello world!"
},
// should rewrite with params successfully
{
"path": "/test/hello",
"mustContain": "Hello"
},
// should double redirect successfully
{
"path": "/docs/github",
"mustContain": "hi there"
},
// should allow params in query for rewrite
{
"path": "/query-rewrite/hello/world?a=b",
@@ -143,13 +137,11 @@
"path": "/query-rewrite/hello/world?a=b",
"mustContain": "\"second\":\"world\""
},
// should not allow rewrite to override page file
{
"path": "/nav",
"mustContain": "to-hello"
},
// show allow redirect to override the page
{
"path": "/redirect-override",
@@ -161,31 +153,26 @@
"redirect": "manual"
}
},
// should match a page after a rewrite
{
"path": "/to-hello",
"mustContain": "Hello"
},
// should match dynamic route after rewrite
{
"path": "/blog/post-1",
"mustContain": "post-2"
},
// should match public file after rewrite
{
"path": "/blog/data.json",
"mustContain": "\"hello\": \"world\""
},
// should match /_next file after rewrite
{
"path": "/hidden/_next/__NEXT_SCRIPT__(/hello)",
"mustContain": "createElement"
},
// should allow redirecting to external resource
{
"path": "/to-external",
@@ -197,7 +184,6 @@
"redirect": "manual"
}
},
// should apply headers for exact match
{
"path": "/add-header",
@@ -206,7 +192,6 @@
"x-another-header": "hello again"
}
},
// should apply headers for multi match
{
"path": "/my-headers/first",
@@ -215,19 +200,16 @@
"x-second-header": "second"
}
},
// should handle basic api rewrite successfully
{
"path": "/api-hello",
"mustContain": "{\"query\":{}}"
},
// should handle api rewrite with param successfully
{
"path": "/api-hello-param/hello",
"mustContain": "{\"query\":{\"hello\":\"hello\",\"name\":\"hello\"}}"
},
// should handle encoded value in the pathname correctly
{
"path": "/redirect/me/to-about/%5Cgoogle.com",
@@ -239,21 +221,18 @@
"redirect": "manual"
}
},
// should apply un-named multi-match correctly
{
"path": "/hello/post-123.html",
"status": 200,
"mustContain": "123"
},
// should rewrite to catch-all with dash in segment name
{
"path": "/catchall-dash/hello/world",
"status": 200,
"mustContain": "hello/world"
},
// should rewrite and normalize catch-all rewrite param
{
"path": "/a/catch-all/hello/world",

View File

@@ -0,0 +1,13 @@
export const getServerSideProps = ({ params }) => {
console.log({ params });
return {
props: {
params,
},
};
};
export default function Page(props) {
return <p id="props">{JSON.stringify(props)}</p>;
}

View File

@@ -0,0 +1,13 @@
export const getServerSideProps = ({ params }) => {
console.log({ params });
return {
props: {
params,
},
};
};
export default function Page(props) {
return <p id="props">{JSON.stringify(props)}</p>;
}

View File

@@ -1,5 +1,6 @@
{
"version": 2,
"uploadNowJson": true,
"routes": [
{ "src": "/(.*)", "dest": "/packages/webapp/$1", "continue": true }
],
@@ -31,6 +32,9 @@
{
"path": "/non-existent",
"status": 404
},
{
"logMustContain": "WARNING: your application is being opted out of @vercel/next's optimized lambdas mode due to legacy routes"
}
]
}

View File

@@ -1,5 +1,6 @@
{
"version": 2,
"uploadNowJson": true,
"routes": [
{ "src": "/(.*)", "dest": "/packages/webapp/$1", "continue": true }
],
@@ -25,6 +26,9 @@
{
"path": "/non-existent",
"status": 404
},
{
"logMustContain": "WARNING: your application is being opted out of @vercel/next's optimized lambdas mode due to legacy routes"
}
]
}

View File

@@ -0,0 +1,5 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
};

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