mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-24 03:39:11 +00:00
Compare commits
62 Commits
@now/cgi@1
...
@now/next@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfb7c9e632 | ||
|
|
06e8472cf7 | ||
|
|
92404135d8 | ||
|
|
8b5cc23d7c | ||
|
|
e10b42bfdc | ||
|
|
36f7ec4836 | ||
|
|
f89d1c463e | ||
|
|
5dc652eba9 | ||
|
|
821b2bd50b | ||
|
|
8d5f6f1fa3 | ||
|
|
617f610c07 | ||
|
|
3229ba0106 | ||
|
|
d16a6e4716 | ||
|
|
4e5156fb0b | ||
|
|
3c013cc36b | ||
|
|
1ed83135be | ||
|
|
0e71ff2df1 | ||
|
|
d8e98ee476 | ||
|
|
1bc1e8a9b1 | ||
|
|
274fdeb49a | ||
|
|
d15c90b42f | ||
|
|
5be2917c47 | ||
|
|
5f08b24546 | ||
|
|
cc3026ffcb | ||
|
|
8cb6e56679 | ||
|
|
7da5ff4b1d | ||
|
|
5d1069d464 | ||
|
|
6e1a72f557 | ||
|
|
6f97a2cc7a | ||
|
|
6e402bffc3 | ||
|
|
e41d0f8e68 | ||
|
|
1a046744f2 | ||
|
|
a9bf011f2c | ||
|
|
60c76b3290 | ||
|
|
c7ff1e7044 | ||
|
|
e2deaef54a | ||
|
|
b2c7386c83 | ||
|
|
3dce84c3cf | ||
|
|
443b1ac158 | ||
|
|
be3dca3d94 | ||
|
|
cb135e4329 | ||
|
|
55c73b68bb | ||
|
|
eb793ba139 | ||
|
|
00908e8ebe | ||
|
|
5ce7ff86a4 | ||
|
|
4b34ee2993 | ||
|
|
646bd288ba | ||
|
|
8aba6f1ff8 | ||
|
|
35e5e328aa | ||
|
|
bdd4953d62 | ||
|
|
e938b298e2 | ||
|
|
b67f324e10 | ||
|
|
1dfa286af5 | ||
|
|
d4391bd4cc | ||
|
|
53eb71f26d | ||
|
|
92ffd654b5 | ||
|
|
36b83f1606 | ||
|
|
7a79f620c0 | ||
|
|
f3b286ecf3 | ||
|
|
adf31c3fcc | ||
|
|
deacdfc47c | ||
|
|
ac9badbe9e |
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1,3 +1,7 @@
|
||||
# Ignore test fixtures in GitHub Languages
|
||||
# See https://github.com/github/linguist#vendored-code
|
||||
packages/*/test/* linguist-vendored
|
||||
|
||||
# Go build fails with Windows line endings.
|
||||
*.go text eol=lf
|
||||
go.mod text eol=lf
|
||||
|
||||
14
.github/CONTRIBUTING.md
vendored
14
.github/CONTRIBUTING.md
vendored
@@ -97,13 +97,9 @@ Sometimes you want to test changes to a Builder against an existing project, may
|
||||
|
||||
## Add a New Framework
|
||||
|
||||
You can add support for a new framework by creating a Pull Request for this repository by following the steps below.
|
||||
You can add support for a new Framework by creating a Pull Request for this repository and following the steps below:
|
||||
|
||||
1. Add the framework to the `@now/frameworks` package.
|
||||
The file is located in `packages/frameworks/frameworks.json`. You can copy the structure of an existing one and adjust the required fields. Note that the `settings` property either contains a `value` or a `placeholder`. The `value` property is used when something is not configurable, the `placeholder` is used when something is configurable and can be changed with configuration. An example would be the output directory for Hugo, it's `public` by default but can be changed through its config file, so we use `placeholder` with an explanation of what can be used.
|
||||
2. Add an example to the `examples/` directory.
|
||||
The name of the directory should equal the slug of the framework used in `@now/frameworks`.
|
||||
The `.github/EXAMPLE_README_TEMPLATE.md` file can be used to create a README for the example.
|
||||
3. Update the `@now/static-build` package
|
||||
The files `packages/now-static-build/src/frameworks.ts` has to be extended. You can add default routes that will always be applied to projects that use this framework or specify some paths that will be cached to speed up the build process.
|
||||
4. After your PR has been merged and released other users can use `now init` to get the example and deploy it to ZEIT Now.
|
||||
1. Add the Framework to the `@now/frameworks` package: The file is located in `packages/frameworks/frameworks.json`. You can copy the structure of an existing one and adjust the required fields. Note that the `settings` property either contains a `value` or a `placeholder`. The `value` property is used when something is not configurable, the `placeholder` is used when something is configurable and can be changed with configuration. An example would be the Output Directory for Hugo, it's `public` by default but can be changed through its config file, so we use `placeholder` with an explanation of what can be used.
|
||||
2. Add an example to the `examples/` directory: The name of the directory should equal the slug of the framework used in `@now/frameworks`. The `.github/EXAMPLE_README_TEMPLATE.md` file can be used to create a `README.md` file for the example.
|
||||
3. Update the `@now/static-build` package: The file `packages/now-static-build/src/frameworks.ts` has to be extended. You can add default routes that will always be applied to projects that use this Framework or specify some paths that will be cached to speed up the build process.
|
||||
4. After your Pull Request has been merged and released, other users can select the example on the ZEIT Now dashboard and deploy it.
|
||||
|
||||
2
.github/EXAMPLE_README_TEMPLATE.md
vendored
2
.github/EXAMPLE_README_TEMPLATE.md
vendored
@@ -6,7 +6,7 @@ This directory is a brief example of a [Name](site-link) site that can be deploy
|
||||
|
||||
Deploy your own [Name] project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now-examples/tree/master/example-directory)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now-examples/tree/master/example-directory)
|
||||
|
||||
### How We Created This Example
|
||||
|
||||
|
||||
3
.github/workflows/cancel.yml
vendored
3
.github/workflows/cancel.yml
vendored
@@ -4,13 +4,14 @@ on:
|
||||
branches:
|
||||
- '*'
|
||||
- '!master'
|
||||
|
||||
jobs:
|
||||
cancel:
|
||||
name: 'Cancel Previous Runs'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 3
|
||||
steps:
|
||||
- uses: styfle/cancel-workflow-action@master
|
||||
- uses: styfle/cancel-workflow-action@0.2.0
|
||||
with:
|
||||
workflow_id: 435869
|
||||
access_token: ${{ secrets.GITHUB_WORKFLOW_TOKEN }}
|
||||
|
||||
22
.github/workflows/continuous-integration.yml
vendored
22
.github/workflows/continuous-integration.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
node: [10, 12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@@ -23,7 +23,8 @@ jobs:
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- uses: actions/setup-node@v1
|
||||
- run: yarn install && yarn run build
|
||||
- run: yarn install
|
||||
- run: yarn run build
|
||||
- run: yarn run test-lint
|
||||
- run: yarn run test-unit --clean false
|
||||
- name: Upload Artifact
|
||||
@@ -41,7 +42,8 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- run: yarn install && yarn run build
|
||||
- run: yarn install
|
||||
- run: yarn run build
|
||||
- run: yarn test-integration-once --clean false
|
||||
|
||||
test-now-cli:
|
||||
@@ -57,8 +59,11 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run: yarn install && yarn run build
|
||||
- name: Install Hugo
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run: yarn install
|
||||
- run: yarn run build
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
@@ -77,8 +82,11 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run: yarn install && yarn run build
|
||||
- name: Install Hugo
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run: yarn install
|
||||
- run: yarn run build
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
[](https://github.com/zeit/now/actions?workflow=CI)
|
||||
[](https://spectrum.chat/zeit)
|
||||
@@ -21,7 +21,7 @@ now # Deploy to the cloud
|
||||
|
||||
## Documentation
|
||||
|
||||
For details on how to use Now CLI, check out our [documentation](https://zeit.co/docs).
|
||||
For details on how to use Now CLI, check out our [documentation](https://zeit.co/docs/now-cli).
|
||||
|
||||
## Caught a Bug?
|
||||
|
||||
|
||||
@@ -2,17 +2,19 @@ import { NowRequest, NowResponse } from '@now/node';
|
||||
import { withApiHandler } from './_lib/util/with-api-handler';
|
||||
import frameworkList, { Framework } from '../packages/frameworks';
|
||||
|
||||
const frameworks: Framework[] = (frameworkList as Framework[]).map(
|
||||
framework => {
|
||||
delete framework.detectors;
|
||||
const frameworks = (frameworkList as Framework[]).map(frameworkItem => {
|
||||
const framework = {
|
||||
...frameworkItem,
|
||||
hasDetectors: Boolean(frameworkItem.detectors),
|
||||
detectors: undefined,
|
||||
};
|
||||
|
||||
if (framework.logo) {
|
||||
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
||||
}
|
||||
|
||||
return framework;
|
||||
if (framework.logo) {
|
||||
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
||||
}
|
||||
);
|
||||
|
||||
return framework;
|
||||
});
|
||||
|
||||
export default withApiHandler(async function(
|
||||
req: NowRequest,
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of an [AMP](https://amp.dev/) site that can be
|
||||
|
||||
Deploy your own AMP project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/amp)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/amp)
|
||||
|
||||
_Live Example: https://amp.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Angular Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of an [Angular](https://angular.io/) app that
|
||||
|
||||
Deploy your own Angular project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/angular)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/angular)
|
||||
|
||||
_Live Example: https://angular.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Assemble](http://assemble.io/) app that
|
||||
|
||||
Deploy your own Assemble project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/assemble)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/assemble)
|
||||
|
||||
_Live Example: https://assemble.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of an [Aurelia](https://aurelia.io/) app that
|
||||
|
||||
Deploy your own Aurelia project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/aurelia)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/aurelia)
|
||||
|
||||
_Live Example: https://aurelia.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Brunch](https://brunch.io/) site that ca
|
||||
|
||||
Deploy your own Brunch project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/brunch)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/brunch)
|
||||
|
||||
_Live Example: https://brunch.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Charge.js](https://charge.js.org/) site
|
||||
|
||||
Deploy your own Charge.js project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/charge)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/charge)
|
||||
|
||||
_Live Example: https://charge.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# React Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [React](https://reactjs.org/) app with [S
|
||||
|
||||
Deploy your own React project, along with Serverless Functions, with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/create-react-app-functions)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/create-react-app-functions)
|
||||
|
||||
_Live Example: https://create-react-app.now-examples.now.sh/_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of using a Custom Build script that can be dep
|
||||
|
||||
Deploy your own Custom Built project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/custom-build)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/custom-build)
|
||||
|
||||
_Live Example: https://custom-build.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Docusaurus Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Docusaurus](https://docusaurus.io/) site
|
||||
|
||||
Deploy your own Docusaurus project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/docusaurus)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/docusaurus)
|
||||
|
||||
_Live Example: https://docusaurus.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Docz](https://www.docz.site/) site that
|
||||
|
||||
Deploy your own Docz project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/docz)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/docz)
|
||||
|
||||
_Live Example: https://docz.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Eleventy Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Eleventy](https://www.11ty.io/) site tha
|
||||
|
||||
Deploy your own Eleventy project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/eleventy)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/eleventy)
|
||||
|
||||
_Live Example: https://eleventy.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Ember Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of an [Ember](https://emberjs.com/) app that c
|
||||
|
||||
Deploy your own Ember project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/ember)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/ember)
|
||||
|
||||
_Live Example: https://ember.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Foundation](https://foundation.zurb.com/
|
||||
|
||||
Deploy your own Foundation project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/foundation)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/foundation)
|
||||
|
||||
_Live Example: https://foundation.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Gatsby Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Gatsby](https://www.gatsbyjs.org/) app w
|
||||
|
||||
Deploy your own Gatsby project, along with Serverless Functions, with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/gatsby)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/gatsby)
|
||||
|
||||
_Live Example: https://gatsby.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Gridsome Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Gridsome](https://gridsome.org/) app tha
|
||||
|
||||
Deploy your own Gridsome project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/gridsome)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/gridsome)
|
||||
|
||||
_Live Example: https://gridsome.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Hexo Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Hexo](https://hexo.io/) site that can be
|
||||
|
||||
Deploy your own Hexo project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/hexo)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/hexo)
|
||||
|
||||
_Live Example: https://hexo.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Hugo](https://gohugo.io/) app that can b
|
||||
|
||||
Deploy your own Hugo project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/hugo)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/hugo)
|
||||
|
||||
_Live Example: https://hugo.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [HyperApp](https://github.com/jorgebucara
|
||||
|
||||
Deploy your own HyperApp project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/hyperapp)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/hyperapp)
|
||||
|
||||
_Live Example: https://hyperapp.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of an [Ionic React](https://ionicframework.com
|
||||
|
||||
Deploy your own Ionic React project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/ionic-react)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/ionic-react)
|
||||
|
||||
_Live Example: https://ionic-react.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Jekyll](https://jekyllrb.com/) site that
|
||||
|
||||
Deploy your own Jekyll project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/jekyll)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/jekyll)
|
||||
|
||||
_Live Example: https://jekyll.now-examples.now.sh_
|
||||
|
||||
@@ -25,18 +25,3 @@ You can deploy your new Jekyll project with a single command from your terminal
|
||||
```shell
|
||||
$ now
|
||||
```
|
||||
|
||||
### Build Command
|
||||
|
||||
The default build command is `jekyll build`.
|
||||
|
||||
If you wish to change the build command, add a `package.json` file with the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "jekyll build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Marko.js](https://markojs.com/) app that
|
||||
|
||||
Deploy your own Marko.js project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/marko)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/marko)
|
||||
|
||||
_Live Example: https://marko.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [mdx-deck](https://github.com/jxnblk/mdx-
|
||||
|
||||
Deploy your own mdx-deck project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/mdx-deck)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/mdx-deck)
|
||||
|
||||
_Live Example: https://mdx-deck.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Metalsmith](https://metalsmith.io/) app
|
||||
|
||||
Deploy your own Metalsmith project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/metalsmith)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/metalsmith)
|
||||
|
||||
_Live Example: https://metalsmith.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Middleman](https://middlemanapp.com/) si
|
||||
|
||||
Deploy your own Middleman project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/middleman)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/middleman)
|
||||
|
||||
_Live Example: https://middleman.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Mithril](https://mithril.js.org/) app th
|
||||
|
||||
Deploy your own Mithril project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/mithril)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/mithril)
|
||||
|
||||
_Live Example: https://mithril.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [MkDocs](https://www.mkdocs.org/) site th
|
||||
|
||||
Deploy your own MkDocs project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/mkdocs)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/mkdocs)
|
||||
|
||||
_Live Example: https://mkdocs.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Next.js Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Next.js](https://nextjs.org) app that ca
|
||||
|
||||
Deploy your own Next.js project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/nextjs)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/nextjs)
|
||||
|
||||
_Live Example: https://nextjs.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Nuxt.js](https://nuxtjs.org) app that ca
|
||||
|
||||
Deploy your own Nuxt.js project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/nuxtjs)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/nuxtjs)
|
||||
|
||||
_Live Example: https://nuxtjs.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Pelican](https://docs.getpelican.com/en/
|
||||
|
||||
Deploy your own Pelican project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/pelican)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/pelican)
|
||||
|
||||
_Live Example: https://pelican.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Polymer Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Polymer](https://www.polymer-project.org
|
||||
|
||||
Deploy your own Polymer project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/polymer)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/polymer)
|
||||
|
||||
_Live Example: https://polymer.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Preact Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Preact](https://preactjs.com/) app that
|
||||
|
||||
Deploy your own Preact project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/preact)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/preact)
|
||||
|
||||
_Live Example: https://preact.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Riot.js](https://riot.js.org/) app that
|
||||
|
||||
Deploy your own Riot.js project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/riot)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/riot)
|
||||
|
||||
_Live Example: https://riot.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Saber Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Saber](https://saber.land) site that can
|
||||
|
||||
Deploy your own Saber project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/saber)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/saber)
|
||||
|
||||
_Live Example: https://saber.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Sapper](https://sapper.svelte.dev/) app
|
||||
|
||||
Deploy your own Sapper project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/sapper)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/sapper)
|
||||
|
||||
_Live Example: https://sapper.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Stencil](https://stenciljs.com/) app tha
|
||||
|
||||
Deploy your own Stencil project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/stencil)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/stencil)
|
||||
|
||||
_Live Example: https://stencil.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Storybook](https://storybook.js.org/) ap
|
||||
|
||||
Deploy your own Storybook project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/storybook)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/storybook)
|
||||
|
||||
_Live Example: https://storybook.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Svelte](https://svelte.dev/) app with [S
|
||||
|
||||
Deploy your own Svelte project, along with Serverless Functions, with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/svelte)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/svelte)
|
||||
|
||||
_Live Example: https://svelte.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# UmiJS Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [UmiJS](https://umijs.org/) app that can
|
||||
|
||||
Deploy your own UmiJS project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/umijs)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/umijs)
|
||||
|
||||
_Live Example: https://umijs.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a vanilla site that can be deployed with ZE
|
||||
|
||||
Deploy your own vanilla website, along with Serverless Functions, with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/vanilla)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/vanilla)
|
||||
|
||||
_Live Example: https://vanilla.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Vue.js Example
|
||||
|
||||
@@ -8,7 +8,7 @@ This directory is a brief example of a [Vue.js](https://vuejs.org/) app that can
|
||||
|
||||
Deploy your own Vue.js project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/vue)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/vue)
|
||||
|
||||
_Live Example: https://vue.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [VuePress](https://vuepress.vuejs.org/) a
|
||||
|
||||
Deploy your own VuePress project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/vuepress)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/vuepress)
|
||||
|
||||
_Live Example: https://vuepress.now-examples.now.sh_
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This directory is a brief example of a [Zola](https://www.getzola.org/) site tha
|
||||
|
||||
Deploy your own Zola project with ZEIT Now.
|
||||
|
||||
[](https://zeit.co/new/project?template=https://github.com/zeit/now/tree/master/examples/zola)
|
||||
[](https://zeit.co/import/project?template=https://github.com/zeit/now/tree/master/examples/zola)
|
||||
|
||||
_Live Example: https://zola.now-examples.now.sh_
|
||||
|
||||
|
||||
5
now.json
5
now.json
@@ -1,9 +1,8 @@
|
||||
{
|
||||
"redirects": [
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/",
|
||||
"destination": "https://zeit.co/new",
|
||||
"statusCode": 307
|
||||
"destination": "/api/frameworks"
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
},
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"placeholder": "`next build` or `build` from `package.json`"
|
||||
"placeholder": "`build` from `package.json` or `next build`"
|
||||
},
|
||||
"devCommand": {
|
||||
"value": "next dev --port $PORT"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/frameworks",
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.10",
|
||||
"main": "frameworks.json",
|
||||
"license": "UNLICENSED"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/build-utils",
|
||||
"version": "1.3.9",
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,432 +0,0 @@
|
||||
import { parse as parsePath, extname } from 'path';
|
||||
import { Route, Source } from '@now/routing-utils';
|
||||
import { Builder } from './types';
|
||||
import { getIgnoreApiFilter, sortFiles } from './detect-builders';
|
||||
|
||||
function escapeName(name: string) {
|
||||
const special = '[]^$.|?*+()'.split('');
|
||||
|
||||
for (const char of special) {
|
||||
name = name.replace(new RegExp(`\\${char}`, 'g'), `\\${char}`);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
function joinPath(...segments: string[]) {
|
||||
const joinedPath = segments.join('/');
|
||||
return joinedPath.replace(/\/{2,}/g, '/');
|
||||
}
|
||||
|
||||
function concatArrayOfText(texts: string[]): string {
|
||||
if (texts.length <= 2) {
|
||||
return texts.join(' and ');
|
||||
}
|
||||
|
||||
const last = texts.pop();
|
||||
return `${texts.join(', ')}, and ${last}`;
|
||||
}
|
||||
|
||||
// Takes a filename or foldername, strips the extension
|
||||
// gets the part between the "[]" brackets.
|
||||
// It will return `null` if there are no brackets
|
||||
// and therefore no segment.
|
||||
function getSegmentName(segment: string): string | null {
|
||||
const { name } = parsePath(segment);
|
||||
|
||||
if (name.startsWith('[') && name.endsWith(']')) {
|
||||
return name.slice(1, -1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function createRouteFromPath(
|
||||
filePath: string,
|
||||
featHandleMiss: boolean,
|
||||
cleanUrls: boolean
|
||||
): { route: Source; isDynamic: boolean } {
|
||||
const parts = filePath.split('/');
|
||||
|
||||
let counter = 1;
|
||||
const query: string[] = [];
|
||||
let isDynamic = false;
|
||||
|
||||
const srcParts = parts.map((segment, i): string => {
|
||||
const name = getSegmentName(segment);
|
||||
const isLast = i === parts.length - 1;
|
||||
|
||||
if (name !== null) {
|
||||
// We can't use `URLSearchParams` because `$` would get escaped
|
||||
query.push(`${name}=$${counter++}`);
|
||||
isDynamic = true;
|
||||
return `([^/]+)`;
|
||||
} else if (isLast) {
|
||||
const { name: fileName, ext } = parsePath(segment);
|
||||
const isIndex = fileName === 'index';
|
||||
const prefix = isIndex ? '\\/' : '';
|
||||
|
||||
const names = [
|
||||
isIndex ? prefix : `${fileName}\\/`,
|
||||
prefix + escapeName(fileName),
|
||||
featHandleMiss && cleanUrls
|
||||
? ''
|
||||
: prefix + escapeName(fileName) + escapeName(ext),
|
||||
].filter(Boolean);
|
||||
|
||||
// Either filename with extension, filename without extension
|
||||
// or nothing when the filename is `index`.
|
||||
// When `cleanUrls: true` then do *not* add the filename with extension.
|
||||
return `(${names.join('|')})${isIndex ? '?' : ''}`;
|
||||
}
|
||||
|
||||
return segment;
|
||||
});
|
||||
|
||||
const { name: fileName, ext } = parsePath(filePath);
|
||||
const isIndex = fileName === 'index';
|
||||
const queryString = `${query.length ? '?' : ''}${query.join('&')}`;
|
||||
|
||||
const src = isIndex
|
||||
? `^/${srcParts.slice(0, -1).join('/')}${srcParts.slice(-1)[0]}$`
|
||||
: `^/${srcParts.join('/')}$`;
|
||||
|
||||
let route: Source;
|
||||
if (featHandleMiss) {
|
||||
const extensionless = ext ? filePath.slice(0, -ext.length) : filePath;
|
||||
route = {
|
||||
src,
|
||||
dest: `/${extensionless}${queryString}`,
|
||||
check: true,
|
||||
};
|
||||
} else {
|
||||
route = {
|
||||
src,
|
||||
dest: `/${filePath}${queryString}`,
|
||||
};
|
||||
}
|
||||
return { route, isDynamic };
|
||||
}
|
||||
|
||||
// Check if the path partially matches and has the same
|
||||
// name for the path segment at the same position
|
||||
function partiallyMatches(pathA: string, pathB: string): boolean {
|
||||
const partsA = pathA.split('/');
|
||||
const partsB = pathB.split('/');
|
||||
|
||||
const long = partsA.length > partsB.length ? partsA : partsB;
|
||||
const short = long === partsA ? partsB : partsA;
|
||||
|
||||
let index = 0;
|
||||
|
||||
for (const segmentShort of short) {
|
||||
const segmentLong = long[index];
|
||||
|
||||
const nameLong = getSegmentName(segmentLong);
|
||||
const nameShort = getSegmentName(segmentShort);
|
||||
|
||||
// If there are no segments or the paths differ we
|
||||
// return as they are not matching
|
||||
if (segmentShort !== segmentLong && (!nameLong || !nameShort)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nameLong !== nameShort) {
|
||||
return true;
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Counts how often a path occurs when all placeholders
|
||||
// got resolved, so we can check if they have conflicts
|
||||
function pathOccurrences(filePath: string, files: string[]): string[] {
|
||||
const getAbsolutePath = (unresolvedPath: string): string => {
|
||||
const { dir, name } = parsePath(unresolvedPath);
|
||||
const parts = joinPath(dir, name).split('/');
|
||||
return parts.map(part => part.replace(/\[.*\]/, '1')).join('/');
|
||||
};
|
||||
|
||||
const currentAbsolutePath = getAbsolutePath(filePath);
|
||||
|
||||
return files.reduce((prev: string[], file: string): string[] => {
|
||||
const absolutePath = getAbsolutePath(file);
|
||||
|
||||
if (absolutePath === currentAbsolutePath) {
|
||||
prev.push(file);
|
||||
} else if (partiallyMatches(filePath, file)) {
|
||||
prev.push(file);
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, []);
|
||||
}
|
||||
|
||||
// Checks if a placeholder with the same name is used
|
||||
// multiple times inside the same path
|
||||
function getConflictingSegment(filePath: string): string | null {
|
||||
const segments = new Set<string>();
|
||||
|
||||
for (const segment of filePath.split('/')) {
|
||||
const name = getSegmentName(segment);
|
||||
|
||||
if (name !== null && segments.has(name)) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
segments.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function sortFilesBySegmentCount(fileA: string, fileB: string): number {
|
||||
const lengthA = fileA.split('/').length;
|
||||
const lengthB = fileB.split('/').length;
|
||||
|
||||
if (lengthA > lengthB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (lengthA < lengthB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Paths that have the same segment length but
|
||||
// less placeholders are preferred
|
||||
const countSegments = (prev: number, segment: string) =>
|
||||
getSegmentName(segment) ? prev + 1 : 0;
|
||||
const segmentLengthA = fileA.split('/').reduce(countSegments, 0);
|
||||
const segmentLengthB = fileB.split('/').reduce(countSegments, 0);
|
||||
|
||||
if (segmentLengthA > segmentLengthB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (segmentLengthA < segmentLengthB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
interface ApiRoutesResult {
|
||||
defaultRoutes: Source[] | null;
|
||||
dynamicRoutes: Source[] | null;
|
||||
error: { [key: string]: string } | null;
|
||||
}
|
||||
|
||||
interface RoutesResult {
|
||||
defaultRoutes: Route[] | null;
|
||||
redirectRoutes: Route[] | null;
|
||||
error: { [key: string]: string } | null;
|
||||
}
|
||||
|
||||
async function detectApiRoutes(
|
||||
files: string[],
|
||||
builders: Builder[],
|
||||
featHandleMiss: boolean,
|
||||
cleanUrls: boolean
|
||||
): Promise<ApiRoutesResult> {
|
||||
if (!files || files.length === 0) {
|
||||
return {
|
||||
defaultRoutes: null,
|
||||
dynamicRoutes: null,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
// The deepest routes need to be
|
||||
// the first ones to get handled
|
||||
const sortedFiles = files
|
||||
.filter(getIgnoreApiFilter(builders))
|
||||
.sort(sortFiles)
|
||||
.sort(sortFilesBySegmentCount);
|
||||
|
||||
const defaultRoutes: Source[] = [];
|
||||
const dynamicRoutes: Source[] = [];
|
||||
|
||||
for (const file of sortedFiles) {
|
||||
// We only consider every file in the api directory
|
||||
// as we will strip extensions as well as resolving "[segments]"
|
||||
if (
|
||||
!file.startsWith('api/') &&
|
||||
!builders.some(b => b.src === file && b.config && b.config.functions)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const conflictingSegment = getConflictingSegment(file);
|
||||
|
||||
if (conflictingSegment) {
|
||||
return {
|
||||
defaultRoutes: null,
|
||||
dynamicRoutes: null,
|
||||
error: {
|
||||
code: 'conflicting_path_segment',
|
||||
message:
|
||||
`The segment "${conflictingSegment}" occurs more than ` +
|
||||
`one time in your path "${file}". Please make sure that ` +
|
||||
`every segment in a path is unique`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const occurrences = pathOccurrences(file, sortedFiles).filter(
|
||||
name => name !== file
|
||||
);
|
||||
|
||||
if (occurrences.length > 0) {
|
||||
const messagePaths = concatArrayOfText(
|
||||
occurrences.map(name => `"${name}"`)
|
||||
);
|
||||
|
||||
return {
|
||||
defaultRoutes: null,
|
||||
dynamicRoutes: null,
|
||||
error: {
|
||||
code: 'conflicting_file_path',
|
||||
message:
|
||||
`Two or more files have conflicting paths or names. ` +
|
||||
`Please make sure path segments and filenames, without their extension, are unique. ` +
|
||||
`The path "${file}" has conflicts with ${messagePaths}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const out = createRouteFromPath(file, featHandleMiss, cleanUrls);
|
||||
if (out.isDynamic) {
|
||||
dynamicRoutes.push(out.route);
|
||||
}
|
||||
defaultRoutes.push(out.route);
|
||||
}
|
||||
|
||||
return { defaultRoutes, dynamicRoutes, error: null };
|
||||
}
|
||||
|
||||
function getPublicBuilder(builders: Builder[]): Builder | null {
|
||||
const builder = builders.find(
|
||||
builder =>
|
||||
builder.use === '@now/static' &&
|
||||
/^.*\/\*\*\/\*$/.test(builder.src) &&
|
||||
builder.config &&
|
||||
builder.config.zeroConfig === true
|
||||
);
|
||||
|
||||
return builder || null;
|
||||
}
|
||||
|
||||
export function detectOutputDirectory(builders: Builder[]): string | null {
|
||||
// TODO: We eventually want to save the output directory to
|
||||
// builder.config.outputDirectory so it is only detected once
|
||||
const publicBuilder = getPublicBuilder(builders);
|
||||
return publicBuilder ? publicBuilder.src.replace('/**/*', '') : null;
|
||||
}
|
||||
|
||||
export function detectApiDirectory(builders: Builder[]): string | null {
|
||||
// TODO: We eventually want to save the api directory to
|
||||
// builder.config.apiDirectory so it is only detected once
|
||||
const found = builders.some(
|
||||
b => b.config && b.config.zeroConfig && b.src.startsWith('api/')
|
||||
);
|
||||
return found ? 'api' : null;
|
||||
}
|
||||
|
||||
export function detectApiExtensions(builders: Builder[]): Set<string> {
|
||||
return new Set<string>(
|
||||
builders
|
||||
.filter(
|
||||
b =>
|
||||
b.config && b.config.zeroConfig && b.src && b.src.startsWith('api/')
|
||||
)
|
||||
.map(b => extname(b.src))
|
||||
.filter(Boolean)
|
||||
);
|
||||
}
|
||||
|
||||
export async function detectRoutes(
|
||||
files: string[],
|
||||
builders: Builder[],
|
||||
featHandleMiss = false,
|
||||
cleanUrls = false,
|
||||
trailingSlash?: boolean
|
||||
): Promise<RoutesResult> {
|
||||
const result = await detectApiRoutes(
|
||||
files,
|
||||
builders,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
);
|
||||
const { dynamicRoutes, defaultRoutes: allRoutes, error } = result;
|
||||
if (error) {
|
||||
return { defaultRoutes: null, redirectRoutes: null, error };
|
||||
}
|
||||
const directory = detectOutputDirectory(builders);
|
||||
const defaultRoutes: Route[] = [];
|
||||
const redirectRoutes: Route[] = [];
|
||||
if (allRoutes && allRoutes.length > 0) {
|
||||
const hasApiRoutes = allRoutes.some(
|
||||
r => r.dest && r.dest.startsWith('/api/')
|
||||
);
|
||||
if (featHandleMiss) {
|
||||
defaultRoutes.push({ handle: 'miss' });
|
||||
const extSet = detectApiExtensions(builders);
|
||||
if (extSet.size > 0) {
|
||||
const exts = Array.from(extSet)
|
||||
.map(ext => ext.slice(1))
|
||||
.join('|');
|
||||
const extGroup = `(?:\\.(?:${exts}))`;
|
||||
if (cleanUrls) {
|
||||
redirectRoutes.push({
|
||||
src: `^/(api(?:.+)?)/index${extGroup}?/?$`,
|
||||
headers: { Location: trailingSlash ? '/$1/' : '/$1' },
|
||||
status: 308,
|
||||
});
|
||||
redirectRoutes.push({
|
||||
src: `^/api/(.+)${extGroup}/?$`,
|
||||
headers: { Location: trailingSlash ? '/api/$1/' : '/api/$1' },
|
||||
status: 308,
|
||||
});
|
||||
} else {
|
||||
defaultRoutes.push({
|
||||
src: `^/api/(.+)${extGroup}$`,
|
||||
dest: '/api/$1',
|
||||
check: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (dynamicRoutes) {
|
||||
defaultRoutes.push(...dynamicRoutes);
|
||||
}
|
||||
if (hasApiRoutes) {
|
||||
defaultRoutes.push({
|
||||
src: '^/api(/.*)?$',
|
||||
status: 404,
|
||||
continue: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
defaultRoutes.push(...allRoutes);
|
||||
if (hasApiRoutes) {
|
||||
defaultRoutes.push({
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!featHandleMiss && directory) {
|
||||
defaultRoutes.push({
|
||||
src: '/(.*)',
|
||||
dest: `/${directory}/$1`,
|
||||
});
|
||||
}
|
||||
|
||||
return { defaultRoutes, redirectRoutes, error };
|
||||
}
|
||||
@@ -64,12 +64,11 @@ export {
|
||||
};
|
||||
|
||||
export {
|
||||
detectRoutes,
|
||||
detectBuilders,
|
||||
detectOutputDirectory,
|
||||
detectApiDirectory,
|
||||
detectApiExtensions,
|
||||
} from './detect-routes';
|
||||
export { detectBuilders } from './detect-builders';
|
||||
} from './detect-builders';
|
||||
export { detectFramework } from './detect-framework';
|
||||
export { DetectorFilesystem } from './detectors/filesystem';
|
||||
export { readConfigFile } from './fs/read-config-file';
|
||||
|
||||
@@ -8,6 +8,7 @@ interface PrerenderOptions {
|
||||
lambda: Lambda;
|
||||
fallback: FileBlob | FileFsRef | FileRef | null;
|
||||
group?: number;
|
||||
bypassToken?: string | null /* optional to be non-breaking change */;
|
||||
}
|
||||
|
||||
export class Prerender {
|
||||
@@ -16,8 +17,15 @@ export class Prerender {
|
||||
public lambda: Lambda;
|
||||
public fallback: FileBlob | FileFsRef | FileRef | null;
|
||||
public group?: number;
|
||||
public bypassToken: string | null;
|
||||
|
||||
constructor({ expiration, lambda, fallback, group }: PrerenderOptions) {
|
||||
constructor({
|
||||
expiration,
|
||||
lambda,
|
||||
fallback,
|
||||
group,
|
||||
bypassToken,
|
||||
}: PrerenderOptions) {
|
||||
this.type = 'Prerender';
|
||||
this.expiration = expiration;
|
||||
this.lambda = lambda;
|
||||
@@ -32,6 +40,22 @@ export class Prerender {
|
||||
}
|
||||
this.group = group;
|
||||
|
||||
if (bypassToken == null) {
|
||||
this.bypassToken = null;
|
||||
} else if (typeof bypassToken === 'string') {
|
||||
if (bypassToken.length < 32) {
|
||||
// Enforce 128 bits of entropy for safety reasons (UUIDv4 size)
|
||||
throw new Error(
|
||||
'The `bypassToken` argument for `Prerender` must be 32 characters or more.'
|
||||
);
|
||||
}
|
||||
this.bypassToken = bypassToken;
|
||||
} else {
|
||||
throw new Error(
|
||||
'The `bypassToken` argument for `Prerender` must be a `string`.'
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof fallback === 'undefined') {
|
||||
throw new Error(
|
||||
'The `fallback` argument for `Prerender` needs to be a `FileBlob`, `FileFsRef`, `FileRef`, or null.'
|
||||
|
||||
@@ -4,7 +4,7 @@ const {
|
||||
packAndDeploy,
|
||||
testDeployment,
|
||||
} = require('../../../test/lib/deployment/test-deployment');
|
||||
const { glob, detectBuilders, detectRoutes } = require('../');
|
||||
const { glob, detectBuilders } = require('../');
|
||||
|
||||
jest.setTimeout(4 * 60 * 1000);
|
||||
|
||||
@@ -110,8 +110,7 @@ it('Test `detectBuilders` and `detectRoutes`', async () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
const { builders, defaultRoutes } = await detectBuilders(files, pkg);
|
||||
|
||||
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
||||
await fs.writeFile(
|
||||
@@ -126,7 +125,7 @@ it('Test `detectBuilders` and `detectRoutes`', async () => {
|
||||
expect(deployment).toBeDefined();
|
||||
});
|
||||
|
||||
it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
|
||||
it('Test `detectBuilders` with `index` files', async () => {
|
||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
||||
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
|
||||
const fileList = await glob('**', fixture);
|
||||
@@ -192,8 +191,7 @@ it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
const { builders, defaultRoutes } = await detectBuilders(files, pkg);
|
||||
|
||||
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
||||
await fs.writeFile(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Source, Route } from '@now/routing-utils';
|
||||
import { detectBuilders, detectRoutes } from '../src';
|
||||
import { detectBuilders } from '../src';
|
||||
import {
|
||||
detectOutputDirectory,
|
||||
detectApiDirectory,
|
||||
@@ -499,7 +499,7 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
expect(errors!.length).toBe(1);
|
||||
expect(errors![0].code).toBe('invalid_function_source');
|
||||
expect(errors![0].code).toBe('unused_function');
|
||||
});
|
||||
|
||||
it('do not allow empty functions', async () => {
|
||||
@@ -648,14 +648,14 @@ describe('Test `detectBuilders`', () => {
|
||||
|
||||
const files = ['dist/index.html', 'dist/style.css'];
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { projectSettings });
|
||||
const { builders, defaultRoutes } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
});
|
||||
|
||||
expect(builders!.length).toBe(1);
|
||||
expect(builders![0].src).toBe('dist/**/*');
|
||||
expect(builders![0].use).toBe('@now/static');
|
||||
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
|
||||
expect(defaultRoutes!.length).toBe(1);
|
||||
expect((defaultRoutes![0] as any).src).toBe('/(.*)');
|
||||
expect((defaultRoutes![0] as any).dest).toBe('/dist/$1');
|
||||
@@ -668,14 +668,14 @@ describe('Test `detectBuilders`', () => {
|
||||
|
||||
const files = ['api/user.ts', 'output/index.html', 'output/style.css'];
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { projectSettings });
|
||||
const { builders, defaultRoutes } = await detectBuilders(files, null, {
|
||||
projectSettings,
|
||||
});
|
||||
|
||||
expect(builders!.length).toBe(2);
|
||||
expect(builders![1].src).toBe('output/**/*');
|
||||
expect(builders![1].use).toBe('@now/static');
|
||||
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
|
||||
expect(defaultRoutes!.length).toBe(3);
|
||||
expect((defaultRoutes![1] as any).status).toBe(404);
|
||||
expect((defaultRoutes![2] as any).src).toBe('/(.*)');
|
||||
@@ -819,8 +819,7 @@ it('Test `detectRoutes`', async () => {
|
||||
{
|
||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files);
|
||||
expect(defaultRoutes!.length).toBe(3);
|
||||
expect((defaultRoutes![0] as any).dest).toBe('/api/team.js');
|
||||
expect((defaultRoutes![1] as any).dest).toBe('/api/user.go');
|
||||
@@ -831,41 +830,36 @@ it('Test `detectRoutes`', async () => {
|
||||
{
|
||||
const files = ['api/user.go', 'api/user.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders!);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
const { errors } = await detectBuilders(files);
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[user].go', 'api/[team]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders!);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
const { errors } = await detectBuilders(files);
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[team]/[team].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders!);
|
||||
expect(error!.code).toBe('conflicting_path_segment');
|
||||
const { errors } = await detectBuilders(files);
|
||||
expect(errors![0]!.code).toBe('conflicting_path_segment');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date/index.go'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, error } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes, errors } = await detectBuilders(files);
|
||||
expect(defaultRoutes).toBe(null);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files);
|
||||
expect(defaultRoutes!.length).toBe(3);
|
||||
}
|
||||
|
||||
@@ -876,8 +870,7 @@ it('Test `detectRoutes`', async () => {
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files);
|
||||
expect((defaultRoutes![2] as any).status).toBe(404);
|
||||
expect((defaultRoutes![2] as any).src).toBe('^/api(/.*)?$');
|
||||
expect((defaultRoutes![3] as any).src).toBe('/(.*)');
|
||||
@@ -892,8 +885,7 @@ it('Test `detectRoutes`', async () => {
|
||||
};
|
||||
const files = ['public/index.html', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files, pkg);
|
||||
expect((defaultRoutes![1] as any).status).toBe(404);
|
||||
expect((defaultRoutes![1] as any).src).toBe('^/api(/.*)?$');
|
||||
expect(defaultRoutes!.length).toBe(2);
|
||||
@@ -902,8 +894,7 @@ it('Test `detectRoutes`', async () => {
|
||||
{
|
||||
const files = ['public/index.html'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files);
|
||||
|
||||
expect(defaultRoutes!.length).toBe(1);
|
||||
}
|
||||
@@ -911,8 +902,7 @@ it('Test `detectRoutes`', async () => {
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files);
|
||||
|
||||
expect(defaultRoutes!.length).toBe(3);
|
||||
expect((defaultRoutes![0] as any).src).toBe(
|
||||
@@ -928,8 +918,7 @@ it('Test `detectRoutes`', async () => {
|
||||
{
|
||||
const files = ['api/date.js', 'api/[date]/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files);
|
||||
|
||||
expect(defaultRoutes!.length).toBe(3);
|
||||
expect((defaultRoutes![0] as any).src).toBe(
|
||||
@@ -953,8 +942,7 @@ it('Test `detectRoutes`', async () => {
|
||||
'api/food.ts',
|
||||
'api/ts/gold.ts',
|
||||
];
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { builders, defaultRoutes } = await detectBuilders(files);
|
||||
|
||||
expect(builders!.length).toBe(4);
|
||||
expect(builders![0].use).toBe('@now/node');
|
||||
@@ -969,8 +957,7 @@ it('Test `detectRoutes`', async () => {
|
||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
||||
const files = ['api/user.php'];
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { functions });
|
||||
const { defaultRoutes } = await detectRoutes(files, builders!);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, { functions });
|
||||
|
||||
expect(defaultRoutes!.length).toBe(2);
|
||||
expect((defaultRoutes![0] as any).dest).toBe('/api/user.php');
|
||||
@@ -983,12 +970,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -1007,49 +991,40 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
const files = ['api/user.go', 'api/user.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders!, featHandleMiss);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
const { errors } = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[user].go', 'api/[team]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders!, featHandleMiss);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
const { errors } = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[team]/[team].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders!, featHandleMiss);
|
||||
expect(error!.code).toBe('conflicting_path_segment');
|
||||
const { errors } = await detectBuilders(files, null, { featHandleMiss });
|
||||
expect(errors![0]!.code).toBe('conflicting_path_segment');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date/index.go'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, error } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes, errors } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toBe(null);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -1082,12 +1057,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -1126,12 +1098,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
|
||||
const files = ['public/index.html', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, pkg, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -1155,24 +1124,18 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
const files = ['public/index.html'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -1191,12 +1154,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
const files = ['api/date.js', 'api/[date]/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -1226,12 +1186,9 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
'api/food.ts',
|
||||
'api/ts/gold.ts',
|
||||
];
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
featHandleMiss,
|
||||
});
|
||||
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
@@ -1249,12 +1206,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
||||
const files = ['api/user.php'];
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { functions });
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, {
|
||||
functions,
|
||||
featHandleMiss,
|
||||
});
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
@@ -1268,8 +1223,11 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
});
|
||||
|
||||
it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async () => {
|
||||
const featHandleMiss = true;
|
||||
const cleanUrls = true;
|
||||
const options = {
|
||||
featHandleMiss: true,
|
||||
cleanUrls: true,
|
||||
};
|
||||
|
||||
const testHeaders = (redirectRoutes: Route[] | null) => {
|
||||
if (!redirectRoutes || redirectRoutes.length === 0) {
|
||||
throw new Error('Expected one redirect but found none');
|
||||
@@ -1281,12 +1239,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1328,65 +1284,43 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
const files = ['api/user.go', 'api/user.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
const { errors } = await detectBuilders(files, null, options);
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[user].go', 'api/[team]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
const { errors } = await detectBuilders(files, null, options);
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[team]/[team].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
);
|
||||
expect(error!.code).toBe('conflicting_path_segment');
|
||||
const { errors } = await detectBuilders(files, null, options);
|
||||
expect(errors![0]!.code).toBe('conflicting_path_segment');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date/index.go'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, error } = await detectRoutes(
|
||||
const { defaultRoutes, errors } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
expect(defaultRoutes).toBe(null);
|
||||
expect(error!.code).toBe('conflicting_file_path');
|
||||
expect(errors![0]!.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1416,12 +1350,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1457,12 +1389,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
|
||||
const files = ['public/index.html', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
pkg,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1483,25 +1413,17 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
const files = ['public/index.html'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
);
|
||||
const { defaultRoutes } = await detectBuilders(files, null, options);
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1517,12 +1439,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
const files = ['api/date.js', 'api/[date]/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1549,12 +1469,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
'api/food.ts',
|
||||
'api/ts/gold.ts',
|
||||
];
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1568,12 +1486,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
||||
const files = ['api/user.php'];
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { functions });
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls
|
||||
null,
|
||||
{ functions, ...options }
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1584,9 +1500,12 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
});
|
||||
|
||||
it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingSlash=true`', async () => {
|
||||
const featHandleMiss = true;
|
||||
const cleanUrls = true;
|
||||
const trailingSlash = true;
|
||||
const options = {
|
||||
featHandleMiss: true,
|
||||
cleanUrls: true,
|
||||
trailingSlash: true,
|
||||
};
|
||||
|
||||
const testHeaders = (redirectRoutes: Route[] | null) => {
|
||||
if (!redirectRoutes || redirectRoutes.length === 0) {
|
||||
throw new Error('Expected one redirect but found none');
|
||||
@@ -1598,13 +1517,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1649,13 +1565,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1685,13 +1598,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1727,13 +1637,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
|
||||
const files = ['public/index.html', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
pkg,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1754,13 +1661,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1776,13 +1680,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
const files = ['api/date.js', 'api/[date]/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1809,13 +1710,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
'api/food.ts',
|
||||
'api/ts/gold.ts',
|
||||
];
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
options
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
@@ -1829,13 +1727,10 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.8' } };
|
||||
const files = ['api/user.php'];
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { functions });
|
||||
const { defaultRoutes, redirectRoutes } = await detectRoutes(
|
||||
const { defaultRoutes, redirectRoutes } = await detectBuilders(
|
||||
files,
|
||||
builders!,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
null,
|
||||
{ functions, ...options }
|
||||
);
|
||||
testHeaders(redirectRoutes);
|
||||
expect(defaultRoutes).toStrictEqual([
|
||||
|
||||
8
packages/now-build-utils/test/unit.test.js
vendored
8
packages/now-build-utils/test/unit.test.js
vendored
@@ -11,6 +11,10 @@ const {
|
||||
} = require('../dist');
|
||||
|
||||
it('should re-create symlinks properly', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping test on windows');
|
||||
return;
|
||||
}
|
||||
const files = await glob('**', path.join(__dirname, 'symlinks'));
|
||||
assert.equal(Object.keys(files).length, 2);
|
||||
|
||||
@@ -29,6 +33,10 @@ it('should re-create symlinks properly', async () => {
|
||||
});
|
||||
|
||||
it('should create zip files with symlinks properly', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping test on windows');
|
||||
return;
|
||||
}
|
||||
const files = await glob('**', path.join(__dirname, 'symlinks'));
|
||||
assert.equal(Object.keys(files).length, 2);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
[](https://spectrum.chat/zeit)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now",
|
||||
"version": "17.0.0",
|
||||
"version": "17.0.5-canary.1",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Now",
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "node ./scripts/preinstall.js",
|
||||
"test-unit": "nyc ava test/*unit.js --serial --fail-fast --verbose",
|
||||
"test-unit": "nyc ava test/unit.js test/dev-builder.unit.js test/dev-router.unit.js test/dev-server.unit.js --serial --fail-fast --verbose",
|
||||
"test-integration": "ava test/integration.js --serial --fail-fast",
|
||||
"test-integration-v1": "ava test/integration-v1.js --serial --fail-fast",
|
||||
"test-integration-now-dev": "ava test/dev/integration.js --serial --fail-fast --verbose",
|
||||
@@ -109,7 +109,7 @@
|
||||
"chalk": "2.4.2",
|
||||
"chokidar": "2.1.6",
|
||||
"clipboardy": "2.1.0",
|
||||
"codecov": "3.1.0",
|
||||
"codecov": "3.6.5",
|
||||
"cpy": "7.2.0",
|
||||
"credit-card": "3.0.1",
|
||||
"date-fns": "1.29.0",
|
||||
@@ -179,6 +179,6 @@
|
||||
"which-promise": "1.0.0",
|
||||
"write-json-file": "2.2.0",
|
||||
"xdg-app-paths": "5.1.0",
|
||||
"yarn": "1.17.3"
|
||||
"yarn": "1.22.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ export default async ctx => {
|
||||
contextName,
|
||||
output,
|
||||
stats,
|
||||
localConfig || {},
|
||||
localConfig,
|
||||
parts.latestArgs
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ import { inputRootDirectory } from '../../util/input/input-root-directory';
|
||||
import validatePaths, {
|
||||
validateRootDirectory,
|
||||
} from '../../util/validate-paths';
|
||||
import { readLocalConfig } from '../../util/config/files';
|
||||
|
||||
const addProcessEnv = async (log, env) => {
|
||||
let val;
|
||||
@@ -94,7 +95,6 @@ const printDeploymentStatus = async (
|
||||
},
|
||||
deployStamp,
|
||||
isClipboardEnabled,
|
||||
quiet,
|
||||
isFile
|
||||
) => {
|
||||
const isProdDeployment = target === 'production';
|
||||
@@ -144,11 +144,6 @@ const printDeploymentStatus = async (
|
||||
.catch(error => output.debug(`Error copying to clipboard: ${error}`));
|
||||
}
|
||||
|
||||
// write to stdout
|
||||
if (quiet) {
|
||||
process.stdout.write(`https://${deploymentUrl}`);
|
||||
}
|
||||
|
||||
output.print(
|
||||
prependEmoji(
|
||||
`${isProdDeployment ? 'Production' : 'Preview'}: ${chalk.bold(
|
||||
@@ -244,13 +239,6 @@ export default async function main(
|
||||
const { isFile, path } = pathValidation;
|
||||
const autoConfirm = argv['--confirm'] || isFile;
|
||||
|
||||
// build `meta`
|
||||
const meta = Object.assign(
|
||||
{},
|
||||
parseMeta(localConfig.meta),
|
||||
parseMeta(argv['--meta'])
|
||||
);
|
||||
|
||||
// --no-scale
|
||||
if (argv['--no-scale']) {
|
||||
warn(`The option --no-scale is only supported on Now 1.0 deployments`);
|
||||
@@ -266,7 +254,125 @@ export default async function main(
|
||||
);
|
||||
}
|
||||
|
||||
if (localConfig && localConfig.name) {
|
||||
const client = new Client({
|
||||
apiUrl: ctx.apiUrl,
|
||||
token: ctx.authConfig.token,
|
||||
debug: debugEnabled,
|
||||
});
|
||||
|
||||
// retrieve `project` and `org` from .now
|
||||
const link = await getLinkedProject(output, client, path);
|
||||
|
||||
if (link.status === 'error') {
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
let { org, project, status } = link;
|
||||
let newProjectName = null;
|
||||
let rootDirectory = project ? project.rootDirectory : null;
|
||||
|
||||
if (status === 'not_linked') {
|
||||
const shouldStartSetup =
|
||||
autoConfirm ||
|
||||
(await confirm(
|
||||
`Set up and deploy ${chalk.cyan(`“${toHumanPath(path)}”`)}?`,
|
||||
true
|
||||
));
|
||||
|
||||
if (!shouldStartSetup) {
|
||||
output.print(`Aborted. Project not set up.\n`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
org = await selectOrg(
|
||||
output,
|
||||
'Which scope do you want to deploy to?',
|
||||
client,
|
||||
ctx.config.currentTeam,
|
||||
autoConfirm
|
||||
);
|
||||
|
||||
// We use `localConfig` here to read the name
|
||||
// even though the `now.json` file can change
|
||||
// afterwards, this is fine since the property
|
||||
// will be deprecated and can be replaced with
|
||||
// user input.
|
||||
const detectedProjectName = getProjectName({
|
||||
argv,
|
||||
nowConfig: localConfig || {},
|
||||
isFile,
|
||||
paths,
|
||||
});
|
||||
|
||||
const projectOrNewProjectName = await inputProject(
|
||||
output,
|
||||
client,
|
||||
org,
|
||||
detectedProjectName,
|
||||
autoConfirm
|
||||
);
|
||||
|
||||
if (typeof projectOrNewProjectName === 'string') {
|
||||
newProjectName = projectOrNewProjectName;
|
||||
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
|
||||
} else {
|
||||
project = projectOrNewProjectName;
|
||||
rootDirectory = project.rootDirectory;
|
||||
|
||||
// we can already link the project
|
||||
await linkFolderToProject(
|
||||
output,
|
||||
path,
|
||||
{
|
||||
projectId: project.id,
|
||||
orgId: org.id,
|
||||
},
|
||||
project.name,
|
||||
org.slug
|
||||
);
|
||||
status = 'linked';
|
||||
}
|
||||
}
|
||||
|
||||
const sourcePath = rootDirectory ? join(path, rootDirectory) : path;
|
||||
|
||||
if (
|
||||
rootDirectory &&
|
||||
(await validateRootDirectory(
|
||||
output,
|
||||
path,
|
||||
sourcePath,
|
||||
project
|
||||
? `To change your project settings, go to https://zeit.co/${org.slug}/${project.name}/settings`
|
||||
: ''
|
||||
)) === false
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If Root Directory is used we'll try to read the config
|
||||
// from there instead and use it if it exists.
|
||||
if (rootDirectory) {
|
||||
const rootDirectoryConfig = readLocalConfig(sourcePath);
|
||||
|
||||
if (rootDirectoryConfig) {
|
||||
debug(`Read local config from root directory (${rootDirectory})`);
|
||||
localConfig = rootDirectoryConfig;
|
||||
} else if (localConfig) {
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`The ${highlight(
|
||||
'now.json'
|
||||
)} file should be inside of the provided root directory.`,
|
||||
emoji('warning')
|
||||
)}\n`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
localConfig = localConfig || {};
|
||||
|
||||
if (localConfig.name) {
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`The ${code('name')} property in ${highlight(
|
||||
@@ -316,6 +422,13 @@ export default async function main(
|
||||
}
|
||||
}
|
||||
|
||||
// build `meta`
|
||||
const meta = Object.assign(
|
||||
{},
|
||||
parseMeta(localConfig.meta),
|
||||
parseMeta(argv['--meta'])
|
||||
);
|
||||
|
||||
// Merge dotenv config, `env` from now.json, and `--env` / `-e` arguments
|
||||
const deploymentEnv = Object.assign(
|
||||
{},
|
||||
@@ -373,97 +486,6 @@ export default async function main(
|
||||
target = 'production';
|
||||
}
|
||||
|
||||
const client = new Client({
|
||||
apiUrl: ctx.apiUrl,
|
||||
token: ctx.authConfig.token,
|
||||
debug: debugEnabled,
|
||||
});
|
||||
|
||||
// retrieve `project` and `org` from .now
|
||||
const link = await getLinkedProject(output, client, path);
|
||||
|
||||
if (link.status === 'error') {
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
let { org, project, status } = link;
|
||||
let newProjectName = null;
|
||||
let rootDirectory = project ? project.rootDirectory : null;
|
||||
|
||||
if (status === 'not_linked') {
|
||||
const shouldStartSetup =
|
||||
autoConfirm ||
|
||||
(await confirm(
|
||||
`Set up and deploy ${chalk.cyan(`“${toHumanPath(path)}”`)}?`,
|
||||
true
|
||||
));
|
||||
|
||||
if (!shouldStartSetup) {
|
||||
output.print(`Aborted. Project not set up.\n`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
org = await selectOrg(
|
||||
output,
|
||||
'Which scope do you want to deploy to?',
|
||||
client,
|
||||
ctx.config.currentTeam,
|
||||
autoConfirm
|
||||
);
|
||||
|
||||
const detectedProjectName = getProjectName({
|
||||
argv,
|
||||
nowConfig: localConfig,
|
||||
isFile,
|
||||
paths,
|
||||
});
|
||||
|
||||
const projectOrNewProjectName = await inputProject(
|
||||
output,
|
||||
client,
|
||||
org,
|
||||
detectedProjectName,
|
||||
autoConfirm
|
||||
);
|
||||
|
||||
if (typeof projectOrNewProjectName === 'string') {
|
||||
newProjectName = projectOrNewProjectName;
|
||||
rootDirectory = await inputRootDirectory(path, output, autoConfirm);
|
||||
} else {
|
||||
project = projectOrNewProjectName;
|
||||
rootDirectory = project.rootDirectory;
|
||||
|
||||
// we can already link the project
|
||||
await linkFolderToProject(
|
||||
output,
|
||||
path,
|
||||
{
|
||||
projectId: project.id,
|
||||
orgId: org.id,
|
||||
},
|
||||
project.name,
|
||||
org.slug
|
||||
);
|
||||
status = 'linked';
|
||||
}
|
||||
}
|
||||
|
||||
const sourcePath = rootDirectory ? join(path, rootDirectory) : path;
|
||||
|
||||
if (
|
||||
rootDirectory &&
|
||||
(await validateRootDirectory(
|
||||
output,
|
||||
path,
|
||||
sourcePath,
|
||||
project
|
||||
? `To change your project settings, go to https://zeit.co/${org.slug}/${project.name}/settings`
|
||||
: ''
|
||||
)) === false
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const currentTeam = org.type === 'team' ? org.id : undefined;
|
||||
const now = new Now({ apiUrl, token, debug: debugEnabled, currentTeam });
|
||||
let deployStamp = stamp();
|
||||
@@ -630,7 +652,12 @@ export default async function main(
|
||||
output.error('Build failed');
|
||||
output.error(
|
||||
`Check your logs at ${now.url}/_logs or run ${code(
|
||||
`now logs ${now.url}`
|
||||
`now logs ${now.url}`,
|
||||
{
|
||||
// Backticks are interpreted as part of the URL, causing CMD+Click
|
||||
// behavior to fail in editors like VSCode.
|
||||
backticks: false,
|
||||
}
|
||||
)}`
|
||||
);
|
||||
|
||||
@@ -659,7 +686,6 @@ export default async function main(
|
||||
deployment,
|
||||
deployStamp,
|
||||
!argv['--no-clipboard'],
|
||||
quiet,
|
||||
isFile
|
||||
);
|
||||
}
|
||||
|
||||
@@ -852,7 +852,7 @@ async function sync({
|
||||
const { url } = now;
|
||||
const dcs =
|
||||
deploymentType !== 'static' && deployment.scale
|
||||
? chalk` ({bold ${Object.keys(deployment.scale).join(', ')})`
|
||||
? ` (${ chalk.bold(Object.keys(deployment.scale).join(', ')) })`
|
||||
: '';
|
||||
|
||||
if (isTTY) {
|
||||
|
||||
@@ -83,6 +83,7 @@ export default async function processDeployment({
|
||||
deployStamp,
|
||||
force,
|
||||
nowConfig,
|
||||
quiet,
|
||||
} = args;
|
||||
|
||||
const { debug } = output;
|
||||
@@ -179,6 +180,10 @@ export default async function processDeployment({
|
||||
|
||||
printInspectUrl(output, event.payload.url, deployStamp, org.slug);
|
||||
|
||||
if (quiet) {
|
||||
process.stdout.write(`https://${event.payload.url}`);
|
||||
}
|
||||
|
||||
if (queuedSpinner === null) {
|
||||
queuedSpinner =
|
||||
event.payload.readyState === 'QUEUED'
|
||||
|
||||
@@ -7,7 +7,6 @@ import { delimiter, dirname, join } from 'path';
|
||||
import { fork, ChildProcess } from 'child_process';
|
||||
import { createFunction } from '@zeit/fun';
|
||||
import { Builder, File, Lambda, FileBlob, FileFsRef } from '@now/build-utils';
|
||||
import which from 'which';
|
||||
import plural from 'pluralize';
|
||||
import minimatch from 'minimatch';
|
||||
import _treeKill from 'tree-kill';
|
||||
@@ -44,12 +43,6 @@ interface BuildMessageResult extends BuildMessage {
|
||||
|
||||
const treeKill = promisify(_treeKill);
|
||||
|
||||
let nodeBinPromise: Promise<string>;
|
||||
|
||||
async function getNodeBin(): Promise<string> {
|
||||
return which.sync('node', { nothrow: true }) || process.execPath;
|
||||
}
|
||||
|
||||
async function createBuildProcess(
|
||||
match: BuildMatch,
|
||||
buildEnv: EnvConfig,
|
||||
@@ -57,13 +50,8 @@ async function createBuildProcess(
|
||||
output: Output,
|
||||
yarnPath?: string
|
||||
): Promise<ChildProcess> {
|
||||
if (!nodeBinPromise) {
|
||||
nodeBinPromise = getNodeBin();
|
||||
}
|
||||
const [execPath, modulePath] = await Promise.all([
|
||||
nodeBinPromise,
|
||||
builderModulePathPromise,
|
||||
]);
|
||||
const { execPath } = process;
|
||||
const modulePath = await builderModulePathPromise;
|
||||
|
||||
// Ensure that `node` is in the builder's `PATH`
|
||||
let PATH = `${dirname(execPath)}${delimiter}${process.env.PATH}`;
|
||||
|
||||
@@ -11,7 +11,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, delimiter } from 'path';
|
||||
import { basename, dirname, extname, join } from 'path';
|
||||
import { getTransformedRoutes, HandleValue } from '@now/routing-utils';
|
||||
import directoryTemplate from 'serve-handler/src/directory';
|
||||
import getPort from 'get-port';
|
||||
@@ -24,10 +24,8 @@ import {
|
||||
FileFsRef,
|
||||
PackageJson,
|
||||
detectBuilders,
|
||||
detectRoutes,
|
||||
detectApiDirectory,
|
||||
detectApiExtensions,
|
||||
execAsync,
|
||||
spawnCommand,
|
||||
} from '@now/build-utils';
|
||||
|
||||
@@ -549,10 +547,19 @@ export default class DevServer {
|
||||
const featHandleMiss = true; // enable for zero config
|
||||
const { projectSettings, cleanUrls, trailingSlash } = config;
|
||||
|
||||
let { builders, warnings, errors } = await detectBuilders(files, pkg, {
|
||||
let {
|
||||
builders,
|
||||
warnings,
|
||||
errors,
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
} = await detectBuilders(files, pkg, {
|
||||
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest',
|
||||
functions: config.functions,
|
||||
...(projectSettings ? { projectSettings } : {}),
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash,
|
||||
});
|
||||
|
||||
if (errors) {
|
||||
@@ -569,32 +576,15 @@ export default class DevServer {
|
||||
builders = builders.filter(filterFrontendBuilds);
|
||||
}
|
||||
|
||||
const {
|
||||
defaultRoutes,
|
||||
redirectRoutes,
|
||||
error: routesError,
|
||||
} = await detectRoutes(
|
||||
files,
|
||||
builders,
|
||||
featHandleMiss,
|
||||
cleanUrls,
|
||||
trailingSlash
|
||||
);
|
||||
|
||||
config.builds = config.builds || [];
|
||||
config.builds.push(...builders);
|
||||
|
||||
if (routesError) {
|
||||
this.output.error(routesError.message);
|
||||
await this.exit();
|
||||
} else {
|
||||
const routes: RouteConfig[] = [];
|
||||
const { routes: nowConfigRoutes } = config;
|
||||
routes.push(...(redirectRoutes || []));
|
||||
routes.push(...(nowConfigRoutes || []));
|
||||
routes.push(...(defaultRoutes || []));
|
||||
config.routes = routes;
|
||||
}
|
||||
const routes: RouteConfig[] = [];
|
||||
const { routes: nowConfigRoutes } = config;
|
||||
routes.push(...(redirectRoutes || []));
|
||||
routes.push(...(nowConfigRoutes || []));
|
||||
routes.push(...(defaultRoutes || []));
|
||||
config.routes = routes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1633,18 +1623,14 @@ export default class DevServer {
|
||||
}
|
||||
|
||||
async runDevCommand() {
|
||||
if (!this.devCommand) return;
|
||||
const { devCommand, cwd } = this;
|
||||
|
||||
const cwd = this.cwd;
|
||||
|
||||
const { stdout: yarnBinStdout } = await execAsync('yarn', ['bin'], {
|
||||
cwd,
|
||||
});
|
||||
|
||||
const yarnBinPath = yarnBinStdout.trim();
|
||||
if (!devCommand) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.output.log(
|
||||
`Running Dev Command ${chalk.cyan.bold(`“${this.devCommand}”`)}`
|
||||
`Running Dev Command ${chalk.cyan.bold(`“${devCommand}”`)}`
|
||||
);
|
||||
|
||||
const port = await getPort();
|
||||
@@ -1653,16 +1639,19 @@ export default class DevServer {
|
||||
...process.env,
|
||||
...this.buildEnv,
|
||||
NOW_REGION: 'dev1',
|
||||
PORT: `${port}`,
|
||||
};
|
||||
|
||||
const devCommand = this.devCommand
|
||||
// This is necesary so that the dev command in the Project
|
||||
// will work cross-platform (especially Windows).
|
||||
let command = devCommand
|
||||
.replace(/\$PORT/g, `${port}`)
|
||||
.replace(/%PORT%/g, `${port}`);
|
||||
|
||||
this.output.debug(
|
||||
`Starting dev command with parameters : ${JSON.stringify({
|
||||
cwd: this.cwd,
|
||||
devCommand,
|
||||
cwd,
|
||||
command,
|
||||
port,
|
||||
})}`
|
||||
);
|
||||
@@ -1671,17 +1660,21 @@ export default class DevServer {
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (!isNpxAvailable) {
|
||||
env.PATH = `${yarnBinPath}${delimiter}${env.PATH}`;
|
||||
if (isNpxAvailable) {
|
||||
command = `npx --no-install ${command}`;
|
||||
} else {
|
||||
const isYarnAvailable = await which('yarn')
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (isYarnAvailable) {
|
||||
command = `yarn run --silent ${command}`;
|
||||
}
|
||||
}
|
||||
|
||||
this.output.debug('Spawning dev command');
|
||||
this.output.debug(`PATH is ${env.PATH}`);
|
||||
this.output.debug(`Spawning dev command: ${command}`);
|
||||
|
||||
const p = spawnCommand(
|
||||
isNpxAvailable ? `npx --no-install ${devCommand}` : devCommand,
|
||||
{ stdio: 'inherit', cwd, env }
|
||||
);
|
||||
const p = spawnCommand(command, { stdio: 'inherit', cwd, env });
|
||||
|
||||
p.on('exit', () => {
|
||||
this.devProcessPort = undefined;
|
||||
|
||||
@@ -4,6 +4,7 @@ import chalk from 'chalk';
|
||||
// The equivalent of <code>, for embedding anything
|
||||
// you may want to take a look at ./cmd.js
|
||||
|
||||
export default function code(cmd: string): string {
|
||||
return `${chalk.gray('`')}${chalk.bold(cmd)}${chalk.gray('`')}`;
|
||||
export default function code(cmd: string, { backticks = true } = {}): string {
|
||||
const tick = backticks ? chalk.gray('`') : '';
|
||||
return `${tick}${chalk.bold(cmd)}${tick}`;
|
||||
}
|
||||
|
||||
53
packages/now-cli/test/dev-server.unit.js
vendored
53
packages/now-cli/test/dev-server.unit.js
vendored
@@ -5,7 +5,7 @@ import execa from 'execa';
|
||||
import fs from 'fs-extra';
|
||||
import fetch from 'node-fetch';
|
||||
import listen from 'async-listen';
|
||||
import { request, createServer } from 'http';
|
||||
import { createServer } from 'http';
|
||||
import createOutput from '../src/util/output';
|
||||
import DevServer from '../src/util/dev/server';
|
||||
import { installBuilders, getBuildUtils } from '../src/util/dev/builder-cache';
|
||||
@@ -13,12 +13,28 @@ import parseListen from '../src/util/dev/parse-listen';
|
||||
|
||||
async function runNpmInstall(fixturePath) {
|
||||
if (await fs.exists(path.join(fixturePath, 'package.json'))) {
|
||||
return execa('yarn', ['install'], { cwd: fixturePath });
|
||||
return execa('yarn', ['install'], { cwd: fixturePath, shell: true });
|
||||
}
|
||||
}
|
||||
|
||||
const skipOnWindows = new Set([
|
||||
'now-dev-default-builds-and-routes',
|
||||
'now-dev-static-routes',
|
||||
'now-dev-static-build-routing',
|
||||
'now-dev-directory-listing',
|
||||
'now-dev-api-with-public',
|
||||
'now-dev-api-with-static',
|
||||
'now-dev-custom-404',
|
||||
]);
|
||||
|
||||
function testFixture(name, fn) {
|
||||
return async t => {
|
||||
if (process.platform === 'win32' && skipOnWindows.has(name)) {
|
||||
console.log(`Skipping test "${name}" on Windows.`);
|
||||
t.is(true, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let server;
|
||||
|
||||
const fixturePath = path.join(__dirname, 'fixtures', 'unit', name);
|
||||
@@ -68,14 +84,6 @@ function validateResponseHeaders(t, res, podId = null) {
|
||||
}
|
||||
}
|
||||
|
||||
function get(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(url, resolve)
|
||||
.on('error', reject)
|
||||
.end();
|
||||
});
|
||||
}
|
||||
|
||||
test(
|
||||
'[DevServer] Maintains query when invoking lambda',
|
||||
testFixture('now-dev-query-invoke', async (t, server) => {
|
||||
@@ -138,16 +146,23 @@ test(
|
||||
test(
|
||||
'[DevServer] Allow `cache-control` to be overwritten',
|
||||
testFixture('now-dev-headers', async (t, server) => {
|
||||
const res = await get(
|
||||
const res = await fetch(
|
||||
`${server.address}/?name=cache-control&value=immutable`
|
||||
);
|
||||
t.is(res.headers['cache-control'], 'immutable');
|
||||
t.is(res.headers.get('cache-control'), 'immutable');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[DevServer] Sends `etag` header for static files',
|
||||
testFixture('now-dev-headers', async (t, server) => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log(
|
||||
'Skipping "etag" test on windows since it yields a different result.'
|
||||
);
|
||||
t.is(true, true);
|
||||
return;
|
||||
}
|
||||
const res = await fetch(`${server.address}/foo.txt`);
|
||||
t.is(res.headers.get('etag'), '"d263af8ab880c0b97eb6c5c125b5d44f9e5addd9"');
|
||||
t.is(await res.text(), 'hi\n');
|
||||
@@ -400,12 +415,14 @@ test('[DevServer] parseListen()', t => {
|
||||
t.deepEqual(parseListen('0.0.0.0'), [3000, '0.0.0.0']);
|
||||
t.deepEqual(parseListen('127.0.0.1:3005'), [3005, '127.0.0.1']);
|
||||
t.deepEqual(parseListen('tcp://127.0.0.1:5000'), [5000, '127.0.0.1']);
|
||||
t.deepEqual(parseListen('unix:/home/user/server.sock'), [
|
||||
'/home/user/server.sock',
|
||||
]);
|
||||
t.deepEqual(parseListen('pipe:\\\\.\\pipe\\PipeName'), [
|
||||
'\\\\.\\pipe\\PipeName',
|
||||
]);
|
||||
if (process.platform !== 'win32') {
|
||||
t.deepEqual(parseListen('unix:/home/user/server.sock'), [
|
||||
'/home/user/server.sock',
|
||||
]);
|
||||
t.deepEqual(parseListen('pipe:\\\\.\\pipe\\PipeName'), [
|
||||
'\\\\.\\pipe\\PipeName',
|
||||
]);
|
||||
}
|
||||
|
||||
let err;
|
||||
try {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
SKIP_PREFLIGHT_CHECK=true
|
||||
@@ -22,3 +22,4 @@ npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
!yarn.lock
|
||||
!.env
|
||||
|
||||
@@ -109,13 +109,14 @@ function validateResponseHeaders(t, res) {
|
||||
async function exec(directory, args = []) {
|
||||
return execa(binaryPath, ['dev', directory, ...args], {
|
||||
reject: false,
|
||||
shell: true,
|
||||
env: { __NOW_SKIP_DEV_COMMAND: 1 },
|
||||
});
|
||||
}
|
||||
|
||||
async function runNpmInstall(fixturePath) {
|
||||
if (await fs.exists(path.join(fixturePath, 'package.json'))) {
|
||||
return execa('yarn', ['install'], { cwd: fixturePath });
|
||||
return execa('yarn', ['install'], { cwd: fixturePath, shell: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +124,7 @@ async function getPackedBuilderPath(builderDirName) {
|
||||
const packagePath = path.join(__dirname, '..', '..', '..', builderDirName);
|
||||
const output = await execa('npm', ['pack'], {
|
||||
cwd: packagePath,
|
||||
shell: true,
|
||||
});
|
||||
|
||||
if (output.exitCode !== 0 || output.stdout.trim() === '') {
|
||||
@@ -160,6 +162,7 @@ async function testFixture(directory, opts = {}, args = []) {
|
||||
{
|
||||
reject: false,
|
||||
detached: true,
|
||||
shell: true,
|
||||
stdio: 'pipe',
|
||||
...opts,
|
||||
env: { ...opts.env, __NOW_SKIP_DEV_COMMAND: 1 },
|
||||
@@ -226,6 +229,7 @@ function testFixtureStdio(directory, fn) {
|
||||
let printedOutput = false;
|
||||
|
||||
dev = execa(binaryPath, ['dev', dir, '-l', port], {
|
||||
shell: true,
|
||||
env: { __NOW_SKIP_DEV_COMMAND: 1 },
|
||||
});
|
||||
|
||||
@@ -638,7 +642,7 @@ test(
|
||||
await testPath(200, '/about/', 'About Page');
|
||||
await testPath(200, '/sub/', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another/', 'Sub Another Page');
|
||||
await testPath(200, '/style.css/', 'body { color: green }');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/index.html', '', { Location: '/' });
|
||||
await testPath(308, '/about.html', '', { Location: '/about/' });
|
||||
await testPath(308, '/sub/index.html', '', { Location: '/sub/' });
|
||||
@@ -653,17 +657,15 @@ test(
|
||||
'[now dev] test trailingSlash true serve correct content',
|
||||
testFixtureStdio('test-trailing-slash', async (t, port, testPath) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/index.html/', 'Index Page');
|
||||
await testPath(200, '/about.html/', 'About Page');
|
||||
await testPath(200, '/index.html', 'Index Page');
|
||||
await testPath(200, '/about.html', 'About Page');
|
||||
await testPath(200, '/sub/', 'Sub Index Page');
|
||||
await testPath(200, '/sub/index.html/', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another.html/', 'Sub Another Page');
|
||||
await testPath(200, '/style.css/', 'body { color: green }');
|
||||
await testPath(308, '/about.html', '', { Location: '/about.html/' });
|
||||
await testPath(200, '/sub/index.html', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another.html', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/about.html/', '', { Location: '/about.html' });
|
||||
await testPath(308, '/style.css/', '', { Location: '/style.css' });
|
||||
await testPath(308, '/sub', '', { Location: '/sub/' });
|
||||
await testPath(308, '/sub/another.html', '', {
|
||||
Location: '/sub/another.html/',
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
@@ -772,17 +774,17 @@ test('[now dev] 03-aurelia', async t => {
|
||||
await tester(t);
|
||||
});
|
||||
|
||||
// test(
|
||||
// '[now dev] 04-create-react-app-node',
|
||||
// testFixtureStdio('create-react-app', async(t, port) => {
|
||||
// const response = await fetch(`http://localhost:${port}`);
|
||||
test(
|
||||
'[now dev] 04-create-react-app',
|
||||
testFixtureStdio('04-create-react-app', async (t, port) => {
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
// validateResponseHeaders(t, response);
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
// const body = await response.text();
|
||||
// t.regex(body, /React App/gm);
|
||||
// })
|
||||
// );
|
||||
const body = await response.text();
|
||||
t.regex(body, /React App/gm);
|
||||
})
|
||||
);
|
||||
|
||||
test('[now dev] 05-gatsby', async t => {
|
||||
if (shouldSkip(t, '05-gatsby', '>^6.14.0 || ^8.10.0 || >=9.10.0')) return;
|
||||
|
||||
@@ -156,33 +156,6 @@ RUN mkdir /public
|
||||
RUN echo hello > /public/index.html
|
||||
`,
|
||||
},
|
||||
'build-env': {
|
||||
'now.json': JSON.stringify({
|
||||
version: 1,
|
||||
type: 'static',
|
||||
build: {
|
||||
env: { FOO: 'bar' },
|
||||
},
|
||||
}),
|
||||
Dockerfile: `
|
||||
FROM alpine
|
||||
ARG FOO
|
||||
RUN mkdir /public
|
||||
RUN echo $FOO > /public/index.html
|
||||
`,
|
||||
},
|
||||
'build-env-arg': {
|
||||
'now.json': JSON.stringify({
|
||||
version: 1,
|
||||
type: 'static',
|
||||
}),
|
||||
Dockerfile: `
|
||||
FROM alpine
|
||||
ARG NONCE
|
||||
RUN mkdir /public
|
||||
RUN echo $NONCE > /public/index.html
|
||||
`,
|
||||
},
|
||||
'build-env-debug': {
|
||||
'now.json':
|
||||
'{ "builds": [ { "src": "index.js", "use": "@now/node" } ], "version": 2 }',
|
||||
@@ -487,6 +460,14 @@ CMD ["node", "index.js"]`,
|
||||
},
|
||||
'project-root-directory': {
|
||||
'src/index.html': '<h1>I am a website.</h1>',
|
||||
'src/now.json': JSON.stringify({
|
||||
rewrites: [
|
||||
{
|
||||
source: '/i-do-exist',
|
||||
destination: '/',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
73
packages/now-cli/test/integration-v1.js
vendored
73
packages/now-cli/test/integration-v1.js
vendored
@@ -1859,79 +1859,6 @@ test('deploy a static build deployment', async t => {
|
||||
t.is(content.trim(), 'hello');
|
||||
});
|
||||
|
||||
test('use build-env', async t => {
|
||||
const directory = fixture('build-env');
|
||||
|
||||
const { stdout, stderr, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[directory, '--public', '--name', session, ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(exitCode);
|
||||
|
||||
// Ensure the exit code is right
|
||||
t.is(exitCode, 0);
|
||||
|
||||
// Test if the output is really a URL
|
||||
const deploymentUrl = pickUrl(stdout);
|
||||
const { href, host } = new URL(deploymentUrl);
|
||||
t.is(host.split('-')[0], session);
|
||||
|
||||
await waitForDeployment(href);
|
||||
|
||||
// get the content
|
||||
const response = await fetch(href);
|
||||
const content = await response.text();
|
||||
t.is(content.trim(), 'bar');
|
||||
});
|
||||
|
||||
test('use `--build-env` CLI flag', async t => {
|
||||
const directory = fixture('build-env-arg');
|
||||
const nonce = Math.random()
|
||||
.toString(36)
|
||||
.substring(2);
|
||||
|
||||
const { stderr, stdout, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[
|
||||
directory,
|
||||
'--public',
|
||||
'--name',
|
||||
session,
|
||||
'--build-env',
|
||||
`NONCE=${nonce}`,
|
||||
...defaultArgs,
|
||||
],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(exitCode);
|
||||
|
||||
// Ensure the exit code is right
|
||||
t.is(exitCode, 0, `Received:\n"${stderr}"\n"${stdout}"`);
|
||||
|
||||
// Test if the output is really a URL
|
||||
const deploymentUrl = pickUrl(stdout);
|
||||
const { href, host } = new URL(deploymentUrl);
|
||||
t.is(host.split('-')[0], session);
|
||||
|
||||
await waitForDeployment(href);
|
||||
|
||||
// get the content
|
||||
const response = await fetch(href);
|
||||
const content = await response.text();
|
||||
t.is(content.trim(), nonce);
|
||||
});
|
||||
|
||||
test('use `--debug` CLI flag', async t => {
|
||||
const directory = fixture('build-env-debug');
|
||||
|
||||
|
||||
97
packages/now-cli/test/integration.js
vendored
97
packages/now-cli/test/integration.js
vendored
@@ -1329,80 +1329,6 @@ test('deploying more than 1 path should fail', async t => {
|
||||
t.true(stderr.trim().endsWith(`Can't deploy more than one path.`));
|
||||
});
|
||||
|
||||
test('use build-env', async t => {
|
||||
const directory = fixture('build-env');
|
||||
|
||||
const { stdout, stderr, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[directory, '--public', '--name', session, ...defaultArgs, '--confirm'],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(exitCode);
|
||||
|
||||
// Ensure the exit code is right
|
||||
t.is(exitCode, 0);
|
||||
|
||||
// Test if the output is really a URL
|
||||
const deploymentUrl = pickUrl(stdout);
|
||||
const { href, host } = new URL(deploymentUrl);
|
||||
t.is(host.split('-')[0], session);
|
||||
|
||||
await waitForDeployment(href);
|
||||
|
||||
// get the content
|
||||
const response = await fetch(href);
|
||||
const content = await response.text();
|
||||
t.is(content.trim(), 'bar');
|
||||
});
|
||||
|
||||
test('use `--build-env` CLI flag', async t => {
|
||||
const directory = fixture('build-env-arg');
|
||||
const nonce = Math.random()
|
||||
.toString(36)
|
||||
.substring(2);
|
||||
|
||||
const { stderr, stdout, exitCode } = await execa(
|
||||
binaryPath,
|
||||
[
|
||||
directory,
|
||||
'--public',
|
||||
'--name',
|
||||
session,
|
||||
'--build-env',
|
||||
`NONCE=${nonce}`,
|
||||
...defaultArgs,
|
||||
'--confirm',
|
||||
],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(stderr);
|
||||
console.log(stdout);
|
||||
console.log(exitCode);
|
||||
|
||||
// Ensure the exit code is right
|
||||
t.is(exitCode, 0, `Received:\n"${stderr}"\n"${stdout}"`);
|
||||
|
||||
// Test if the output is really a URL
|
||||
const deploymentUrl = pickUrl(stdout);
|
||||
const { href, host } = new URL(deploymentUrl);
|
||||
t.is(host.split('-')[0], session);
|
||||
|
||||
await waitForDeployment(href);
|
||||
|
||||
// get the content
|
||||
const response = await fetch(href);
|
||||
const content = await response.text();
|
||||
t.is(content.trim(), nonce);
|
||||
});
|
||||
|
||||
test('use `--debug` CLI flag', async t => {
|
||||
const directory = fixture('build-env-debug');
|
||||
|
||||
@@ -2279,17 +2205,19 @@ test('should prefill "project name" prompt with now.json `name`', async t => {
|
||||
|
||||
let isDeprecated = false;
|
||||
|
||||
await waitForPrompt(now, chunk => {
|
||||
if (chunk.includes('The `name` property in now.json is deprecated')) {
|
||||
now.stderr.on('data', data => {
|
||||
if (
|
||||
data.toString().includes('The `name` property in now.json is deprecated')
|
||||
) {
|
||||
isDeprecated = true;
|
||||
}
|
||||
});
|
||||
|
||||
await waitForPrompt(now, chunk => {
|
||||
return /Set up and deploy [^?]+\?/.test(chunk);
|
||||
});
|
||||
now.stdin.write('yes\n');
|
||||
|
||||
t.is(isDeprecated, true);
|
||||
|
||||
await waitForPrompt(now, chunk =>
|
||||
chunk.includes('Which scope do you want to deploy to?')
|
||||
);
|
||||
@@ -2318,6 +2246,8 @@ test('should prefill "project name" prompt with now.json `name`', async t => {
|
||||
const output = await now;
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
|
||||
t.is(isDeprecated, true);
|
||||
|
||||
// clean up
|
||||
await remove(path.join(directory, 'now.json'));
|
||||
});
|
||||
@@ -2448,9 +2378,14 @@ test('use `rootDirectory` from project when deploying', async t => {
|
||||
const secondResult = await execute([directory, '--public']);
|
||||
t.is(secondResult.exitCode, 0, formatOutput(secondResult));
|
||||
|
||||
const pageResponse = await fetch(secondResult.stdout);
|
||||
t.is(pageResponse.status, 200);
|
||||
t.regex(await pageResponse.text(), /I am a website/gm);
|
||||
const pageResponse1 = await fetch(secondResult.stdout);
|
||||
t.is(pageResponse1.status, 200);
|
||||
t.regex(await pageResponse1.text(), /I am a website/gm);
|
||||
|
||||
// Ensures that the `now.json` file has been applied
|
||||
const pageResponse2 = await fetch(`${secondResult.stdout}/i-do-exist`);
|
||||
t.is(pageResponse2.status, 200);
|
||||
t.regex(await pageResponse2.text(), /I am a website/gm);
|
||||
|
||||
await apiFetch(`/v2/projects/${projectId}`, {
|
||||
method: 'DELETE',
|
||||
|
||||
37
packages/now-cli/test/unit.js
vendored
37
packages/now-cli/test/unit.js
vendored
@@ -1,4 +1,4 @@
|
||||
import { join } from 'path';
|
||||
import { join, sep } from 'path';
|
||||
import { send } from 'micro';
|
||||
import test from 'ava';
|
||||
import sinon from 'sinon';
|
||||
@@ -26,7 +26,7 @@ import getUpdateCommand from '../src/util/get-update-command';
|
||||
import { isCanary } from '../src/util/is-canary';
|
||||
|
||||
const output = createOutput({ debug: false });
|
||||
const prefix = `${join(__dirname, 'fixtures', 'unit')}/`;
|
||||
const prefix = `${join(__dirname, 'fixtures', 'unit')}${sep}`;
|
||||
const base = path => path.replace(prefix, '');
|
||||
const fixture = name => join(prefix, name);
|
||||
|
||||
@@ -37,7 +37,8 @@ const getNpmFiles = async dir => {
|
||||
strict: false,
|
||||
});
|
||||
|
||||
return getNpmFiles_(dir, pkg, nowConfig, { hasNowJson, output });
|
||||
const files = await getNpmFiles_(dir, pkg, nowConfig, { hasNowJson, output });
|
||||
return normalizeWindowsPaths(files);
|
||||
};
|
||||
|
||||
const getDockerFiles = async dir => {
|
||||
@@ -46,7 +47,8 @@ const getDockerFiles = async dir => {
|
||||
strict: false,
|
||||
});
|
||||
|
||||
return getDockerFiles_(dir, nowConfig, { hasNowJson, output });
|
||||
const files = await getDockerFiles_(dir, nowConfig, { hasNowJson, output });
|
||||
return normalizeWindowsPaths(files);
|
||||
};
|
||||
|
||||
const getStaticFiles = async (dir, isBuilds = false) => {
|
||||
@@ -56,7 +58,22 @@ const getStaticFiles = async (dir, isBuilds = false) => {
|
||||
strict: false,
|
||||
});
|
||||
|
||||
return getStaticFiles_(dir, nowConfig, { hasNowJson, output, isBuilds });
|
||||
const files = await getStaticFiles_(dir, nowConfig, {
|
||||
hasNowJson,
|
||||
output,
|
||||
isBuilds,
|
||||
});
|
||||
return normalizeWindowsPaths(files);
|
||||
};
|
||||
|
||||
const normalizeWindowsPaths = files => {
|
||||
if (process.platform === 'win32') {
|
||||
const prefix = 'D:/a/now/now/packages/now-cli/test/fixtures/unit/';
|
||||
return files.map(f =>
|
||||
f.replace(/\\/g, '/').slice(prefix.length)
|
||||
);
|
||||
}
|
||||
return files;
|
||||
};
|
||||
|
||||
test('`files`', async t => {
|
||||
@@ -273,6 +290,11 @@ test('extensionless main', async t => {
|
||||
});
|
||||
|
||||
test('hashes', async t => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping "hashes" test on Windows');
|
||||
t.is(true, true);
|
||||
return;
|
||||
}
|
||||
const files = await getNpmFiles(fixture('hashes'));
|
||||
const hashes = await hash(files);
|
||||
t.is(hashes.size, 3);
|
||||
@@ -542,6 +564,11 @@ test('support `package.json:now.type` to bypass multiple manifests error', async
|
||||
});
|
||||
|
||||
test('friendly error for malformed JSON', async t => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping "friendly error for malformed JSON" test on Windows');
|
||||
t.is(true, true);
|
||||
return;
|
||||
}
|
||||
const err = await t.throwsAsync(() =>
|
||||
readMetadata(fixture('json-syntax-error'), {
|
||||
quiet: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now-client",
|
||||
"version": "7.0.0",
|
||||
"version": "7.0.1-canary.0",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://zeit.co",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { DeploymentFile } from './hashes';
|
||||
import { parse as parseUrl } from 'url';
|
||||
import { FetchOptions } from '@zeit/fetch';
|
||||
import { nodeFetch, zeitFetch } from './fetch';
|
||||
import { join, sep } from 'path';
|
||||
import { join, sep, relative } from 'path';
|
||||
import qs from 'querystring';
|
||||
import ignore from 'ignore';
|
||||
import { pkgVersion } from '../pkg';
|
||||
@@ -191,9 +191,10 @@ export const prepareFiles = (
|
||||
|
||||
if (clientOptions.isDirectory) {
|
||||
// Directory
|
||||
fileName = clientOptions.path
|
||||
? name.substring(clientOptions.path.length + 1)
|
||||
: name;
|
||||
fileName =
|
||||
typeof clientOptions.path === 'string'
|
||||
? relative(clientOptions.path, name)
|
||||
: name;
|
||||
} else {
|
||||
// Array of files or single file
|
||||
const segments = name.split(sep);
|
||||
|
||||
@@ -87,10 +87,12 @@ describe('create v2 deployment', () => {
|
||||
});
|
||||
|
||||
it('will create a v2 deployment with correct file permissions', async () => {
|
||||
let error = null;
|
||||
for await (const event of createDeployment(
|
||||
{
|
||||
token,
|
||||
path: path.resolve(__dirname, 'fixtures', 'v2-file-permissions'),
|
||||
skipAutoDetectionConfirmation: true,
|
||||
},
|
||||
{
|
||||
name: 'now-client-tests-v2',
|
||||
@@ -104,9 +106,16 @@ describe('create v2 deployment', () => {
|
||||
if (event.type === 'ready') {
|
||||
deployment = event.payload;
|
||||
break;
|
||||
} else if (event.type === 'error') {
|
||||
error = event.payload;
|
||||
console.error(error.message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
expect(error).toBe(null);
|
||||
expect(deployment.readyState).toEqual('READY');
|
||||
|
||||
const url = `https://${deployment.url}/api/index.js`;
|
||||
console.log('testing url ' + url);
|
||||
const response = await fetch_(url);
|
||||
@@ -116,10 +125,12 @@ describe('create v2 deployment', () => {
|
||||
});
|
||||
|
||||
it('will create a v2 deployment and ignore files specified in .nowignore', async () => {
|
||||
let error = null;
|
||||
for await (const event of createDeployment(
|
||||
{
|
||||
token,
|
||||
path: path.resolve(__dirname, 'fixtures', 'nowignore'),
|
||||
skipAutoDetectionConfirmation: true,
|
||||
},
|
||||
{
|
||||
name: 'now-client-tests-v2',
|
||||
@@ -132,11 +143,17 @@ describe('create v2 deployment', () => {
|
||||
)) {
|
||||
if (event.type === 'ready') {
|
||||
deployment = event.payload;
|
||||
expect(deployment.readyState).toEqual('READY');
|
||||
break;
|
||||
} else if (event.type === 'error') {
|
||||
error = event.payload;
|
||||
console.error(error.message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
expect(error).toBe(null);
|
||||
expect(deployment.readyState).toEqual('READY');
|
||||
|
||||
const index = await fetch_(`https://${deployment.url}`);
|
||||
expect(index.status).toBe(200);
|
||||
expect(await index.text()).toBe('Hello World!');
|
||||
|
||||
@@ -123,7 +123,7 @@ export async function createGo(
|
||||
|
||||
export async function downloadGo(
|
||||
dir = GO_DIR,
|
||||
version = '1.12',
|
||||
version = '1.13.7',
|
||||
platform = process.platform,
|
||||
arch = process.arch
|
||||
) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
shouldServe,
|
||||
Files,
|
||||
debug,
|
||||
Meta,
|
||||
} from '@now/build-utils';
|
||||
|
||||
import { createGo, getAnalyzedEntrypoint, OUT_EXTENSION } from './go-helpers';
|
||||
@@ -23,15 +24,6 @@ interface Analyzed {
|
||||
functionName: string;
|
||||
watch: string[];
|
||||
}
|
||||
interface BuildParamsMeta {
|
||||
isDev: boolean | undefined;
|
||||
}
|
||||
interface BuildParamsType extends BuildOptions {
|
||||
files: Files;
|
||||
entrypoint: string;
|
||||
workPath: string;
|
||||
meta: BuildParamsMeta;
|
||||
}
|
||||
|
||||
// Initialize private git repo for Go Modules
|
||||
async function initPrivateGit(credentials: string) {
|
||||
@@ -45,6 +37,32 @@ async function initPrivateGit(credentials: string) {
|
||||
await writeFile(join(homedir(), '.git-credentials'), credentials);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since `go build` does not support files that begin with a square bracket,
|
||||
* we must rename to something temporary to support Path Segments.
|
||||
* The output file is not renamed because v3 builders can't rename outputs
|
||||
* which works great for this feature. We also need to add a suffix during `now dev`
|
||||
* since the entrypoint is already stripped of its suffix before build() is called.
|
||||
*/
|
||||
async function getRenamedEntrypoint(
|
||||
entrypoint: string,
|
||||
files: Files,
|
||||
meta: Meta
|
||||
) {
|
||||
const filename = basename(entrypoint);
|
||||
if (filename.startsWith('[')) {
|
||||
const suffix = meta.isDev && !entrypoint.endsWith('.go') ? '.go' : '';
|
||||
const newEntrypoint = entrypoint.replace('/[', '/now-bracket[') + suffix;
|
||||
const file = files[entrypoint];
|
||||
delete files[entrypoint];
|
||||
files[newEntrypoint] = file;
|
||||
debug(`Renamed entrypoint from ${entrypoint} to ${newEntrypoint}`);
|
||||
entrypoint = newEntrypoint;
|
||||
}
|
||||
|
||||
return entrypoint;
|
||||
}
|
||||
|
||||
export const version = 3;
|
||||
|
||||
export async function build({
|
||||
@@ -52,8 +70,8 @@ export async function build({
|
||||
entrypoint,
|
||||
config,
|
||||
workPath,
|
||||
meta = {} as BuildParamsMeta,
|
||||
}: BuildParamsType) {
|
||||
meta = {},
|
||||
}: BuildOptions) {
|
||||
if (process.env.GIT_CREDENTIALS && !meta.isDev) {
|
||||
debug('Initialize Git credentials...');
|
||||
await initPrivateGit(process.env.GIT_CREDENTIALS);
|
||||
@@ -70,7 +88,7 @@ We highly recommend you leverage Go Modules in your project.
|
||||
Learn more: https://github.com/golang/go/wiki/Modules
|
||||
`);
|
||||
}
|
||||
|
||||
entrypoint = await getRenamedEntrypoint(entrypoint, files, meta);
|
||||
const entrypointArr = entrypoint.split(sep);
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
@@ -79,22 +97,19 @@ Learn more: https://github.com/golang/go/wiki/Modules
|
||||
getWriteableDirectory(),
|
||||
]);
|
||||
|
||||
const forceMove = Boolean(meta.isDev);
|
||||
const srcPath = join(goPath, 'src', 'lambda');
|
||||
let downloadedFiles;
|
||||
if (meta.isDev) {
|
||||
downloadedFiles = await download(files, workPath, meta);
|
||||
} else {
|
||||
downloadedFiles = await download(files, srcPath);
|
||||
}
|
||||
let downloadPath = meta.isDev ? workPath : srcPath;
|
||||
let downloadedFiles = await download(files, downloadPath, meta);
|
||||
|
||||
debug(`Parsing AST for "${entrypoint}"`);
|
||||
let analyzed: string;
|
||||
try {
|
||||
let goModAbsPathDir = '';
|
||||
for (const file of Object.keys(downloadedFiles)) {
|
||||
if (file === 'go.mod') {
|
||||
goModAbsPathDir = dirname(downloadedFiles[file].fsPath);
|
||||
}
|
||||
const fileName = 'go.mod';
|
||||
if (fileName in downloadedFiles) {
|
||||
goModAbsPathDir = dirname(downloadedFiles[fileName].fsPath);
|
||||
debug(`Found ${fileName} file in "${goModAbsPathDir}"`);
|
||||
}
|
||||
analyzed = await getAnalyzedEntrypoint(
|
||||
downloadedFiles[entrypoint].fsPath,
|
||||
@@ -118,27 +133,18 @@ Learn more: https://zeit.co/docs/v2/advanced/builders/#go
|
||||
const parsedAnalyzed = JSON.parse(analyzed) as Analyzed;
|
||||
|
||||
if (meta.isDev) {
|
||||
let base = null;
|
||||
|
||||
if (config && config.zeroConfig) {
|
||||
base = workPath;
|
||||
} else {
|
||||
base = dirname(downloadedFiles['now.json'].fsPath);
|
||||
}
|
||||
|
||||
const destNow = join(
|
||||
base,
|
||||
// Create cache so Go rebuilds fast with `now dev`
|
||||
goPath = join(
|
||||
workPath,
|
||||
'.now',
|
||||
'cache',
|
||||
basename(entrypoint, '.go'),
|
||||
'src',
|
||||
'lambda'
|
||||
'now-go',
|
||||
basename(entrypoint, '.go')
|
||||
);
|
||||
// this will ensure Go rebuilt fast
|
||||
goPath = join(base, '.now', 'cache', basename(entrypoint, '.go'));
|
||||
const destNow = join(goPath, 'src', 'lambda');
|
||||
await download(downloadedFiles, destNow);
|
||||
|
||||
downloadedFiles = await glob('**', destNow);
|
||||
downloadPath = destNow;
|
||||
}
|
||||
|
||||
// find `go.mod` in downloadedFiles
|
||||
@@ -147,15 +153,38 @@ Learn more: https://zeit.co/docs/v2/advanced/builders/#go
|
||||
let goModPath = '';
|
||||
let isGoModInRootDir = false;
|
||||
for (const file of Object.keys(downloadedFiles)) {
|
||||
const fileDirname = dirname(downloadedFiles[file].fsPath);
|
||||
const { fsPath } = downloadedFiles[file];
|
||||
const fileDirname = dirname(fsPath);
|
||||
if (file === 'go.mod') {
|
||||
isGoModExist = true;
|
||||
isGoModInRootDir = true;
|
||||
goModPath = fileDirname;
|
||||
} else if (file.endsWith('go.mod') && !file.endsWith('vendor')) {
|
||||
} else if (file.endsWith('go.mod')) {
|
||||
if (entrypointDirname === fileDirname) {
|
||||
isGoModExist = true;
|
||||
goModPath = fileDirname;
|
||||
debug(`Found file dirname equals entrypoint dirname: ${fileDirname}`);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isGoModInRootDir && config.zeroConfig && file === 'api/go.mod') {
|
||||
// We didn't find `/go.mod` but we found `/api/go.mod` so move it to the root
|
||||
isGoModExist = true;
|
||||
isGoModInRootDir = true;
|
||||
goModPath = join(fileDirname, '..');
|
||||
const pathParts = fsPath.split(sep);
|
||||
pathParts.pop(); // Remove go.mod
|
||||
pathParts.pop(); // Remove api
|
||||
pathParts.push('go.mod');
|
||||
const newFsPath = pathParts.join(sep);
|
||||
debug(`Moving api/go.mod to root: ${fsPath} to ${newFsPath}`);
|
||||
await move(fsPath, newFsPath, { overwrite: forceMove });
|
||||
const oldSumPath = join(dirname(fsPath), 'go.sum');
|
||||
const newSumPath = join(dirname(newFsPath), 'go.sum');
|
||||
if (await pathExists(oldSumPath)) {
|
||||
debug(`Moving api/go.sum to root: ${oldSumPath} to ${newSumPath}`);
|
||||
await move(oldSumPath, newSumPath, { overwrite: forceMove });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -165,10 +194,13 @@ Learn more: https://zeit.co/docs/v2/advanced/builders/#go
|
||||
const includedFiles: Files = {};
|
||||
|
||||
if (config && config.includeFiles) {
|
||||
for (const pattern of config.includeFiles) {
|
||||
const files = await glob(pattern, input);
|
||||
for (const assetName of Object.keys(files)) {
|
||||
includedFiles[assetName] = files[assetName];
|
||||
const patterns = Array.isArray(config.includeFiles)
|
||||
? config.includeFiles
|
||||
: [config.includeFiles];
|
||||
for (const pattern of patterns) {
|
||||
const fsFiles = await glob(pattern, input);
|
||||
for (const [assetName, asset] of Object.entries(fsFiles)) {
|
||||
includedFiles[assetName] = asset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,7 +253,6 @@ Learn more: https://zeit.co/docs/v2/advanced/builders/#go
|
||||
if (isGoModExist) {
|
||||
const goModContents = await readFile(join(goModPath, 'go.mod'), 'utf8');
|
||||
const usrModName = goModContents.split('\n')[0].split(' ')[1];
|
||||
|
||||
if (entrypointArr.length > 1 && isGoModInRootDir) {
|
||||
const cleanPackagePath = [...entrypointArr];
|
||||
cleanPackagePath.pop();
|
||||
@@ -235,18 +266,14 @@ Learn more: https://zeit.co/docs/v2/advanced/builders/#go
|
||||
.replace('__NOW_HANDLER_PACKAGE_NAME', goPackageName)
|
||||
.replace('__NOW_HANDLER_FUNC_NAME', goFuncName);
|
||||
|
||||
if (meta.isDev && isGoModExist && isGoModInRootDir) {
|
||||
await writeFile(
|
||||
join(dirname(downloadedFiles['go.mod'].fsPath), mainModGoFileName),
|
||||
mainModGoContents
|
||||
);
|
||||
} else if (isGoModExist && isGoModInRootDir) {
|
||||
await writeFile(join(srcPath, mainModGoFileName), mainModGoContents);
|
||||
if (isGoModExist && isGoModInRootDir) {
|
||||
debug('[mod-root] Write main file to ' + downloadPath);
|
||||
await writeFile(join(downloadPath, mainModGoFileName), mainModGoContents);
|
||||
} else if (isGoModExist && !isGoModInRootDir) {
|
||||
// using `go.mod` path to write main__mod__.go
|
||||
debug('[mod-other] Write main file to ' + goModPath);
|
||||
await writeFile(join(goModPath, mainModGoFileName), mainModGoContents);
|
||||
} else {
|
||||
// using `entrypointDirname` to write main__mod__.go
|
||||
debug('[entrypoint] Write main file to ' + entrypointDirname);
|
||||
await writeFile(
|
||||
join(entrypointDirname, mainModGoFileName),
|
||||
mainModGoContents
|
||||
@@ -257,11 +284,6 @@ Learn more: https://zeit.co/docs/v2/advanced/builders/#go
|
||||
try {
|
||||
// default path
|
||||
let finalDestination = join(entrypointDirname, packageName, entrypoint);
|
||||
let forceMove = false;
|
||||
|
||||
if (meta.isDev) {
|
||||
forceMove = true;
|
||||
}
|
||||
|
||||
// if `entrypoint` include folder, only use filename
|
||||
if (entrypointArr.length > 1) {
|
||||
@@ -286,10 +308,8 @@ Learn more: https://zeit.co/docs/v2/advanced/builders/#go
|
||||
}
|
||||
|
||||
let baseGoModPath = '';
|
||||
if (meta.isDev && isGoModExist && isGoModInRootDir) {
|
||||
baseGoModPath = dirname(downloadedFiles['go.mod'].fsPath);
|
||||
} else if (isGoModExist && isGoModInRootDir) {
|
||||
baseGoModPath = srcPath;
|
||||
if (isGoModExist && isGoModInRootDir) {
|
||||
baseGoModPath = downloadPath;
|
||||
} else if (isGoModExist && !isGoModInRootDir) {
|
||||
baseGoModPath = goModPath;
|
||||
} else {
|
||||
@@ -302,12 +322,12 @@ Learn more: https://zeit.co/docs/v2/advanced/builders/#go
|
||||
await move(
|
||||
join(baseGoModPath, 'go.mod.bk'),
|
||||
join(baseGoModPath, 'go.mod'),
|
||||
{ overwrite: true }
|
||||
{ overwrite: forceMove }
|
||||
);
|
||||
await move(
|
||||
join(baseGoModPath, 'go.sum.bk'),
|
||||
join(baseGoModPath, 'go.sum'),
|
||||
{ overwrite: true }
|
||||
{ overwrite: forceMove }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -337,12 +357,12 @@ Learn more: https://zeit.co/docs/v2/advanced/builders/#go
|
||||
await move(
|
||||
join(baseGoModPath, 'go.mod'),
|
||||
join(baseGoModPath, 'go.mod.bk'),
|
||||
{ overwrite: true }
|
||||
{ overwrite: forceMove }
|
||||
);
|
||||
await move(
|
||||
join(baseGoModPath, 'go.sum'),
|
||||
join(baseGoModPath, 'go.sum.bk'),
|
||||
{ overwrite: true }
|
||||
{ overwrite: forceMove }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/go",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/go",
|
||||
|
||||
16
packages/now-go/test/fixtures/08-include-files/another.go
vendored
Normal file
16
packages/now-go/test/fixtures/08-include-files/another.go
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package cowsay
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Handler function
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
bts, err := ioutil.ReadFile("templates/another.txt")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
fmt.Fprintf(w, string(bts))
|
||||
}
|
||||
@@ -7,12 +7,23 @@
|
||||
"config": {
|
||||
"includeFiles": ["templates/**"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"src": "another.go",
|
||||
"use": "@now/go",
|
||||
"config": {
|
||||
"includeFiles": "templates/**"
|
||||
}
|
||||
}
|
||||
],
|
||||
"probes": [
|
||||
{
|
||||
"path": "/",
|
||||
"mustContain": "foobar from file"
|
||||
},
|
||||
{
|
||||
"path": "/another.go",
|
||||
"mustContain": "another text file"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
1
packages/now-go/test/fixtures/08-include-files/templates/another.txt
vendored
Normal file
1
packages/now-go/test/fixtures/08-include-files/templates/another.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
another text file
|
||||
3
packages/now-go/test/fixtures/17-go-mod-api/api/_pkg/somepackage/somepackage.go
vendored
Normal file
3
packages/now-go/test/fixtures/17-go-mod-api/api/_pkg/somepackage/somepackage.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package somepackage
|
||||
|
||||
const Foo = "Dependency:RANDOMNESS_PLACEHOLDER"
|
||||
5
packages/now-go/test/fixtures/17-go-mod-api/api/go.mod
vendored
Normal file
5
packages/now-go/test/fixtures/17-go-mod-api/api/go.mod
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
module github.com/zeit/does-not-exist
|
||||
|
||||
go 1.13
|
||||
|
||||
require github.com/dhruvbird/go-cowsay v0.0.0-20131019225157-6fd7bd0281c0 // indirect
|
||||
2
packages/now-go/test/fixtures/17-go-mod-api/api/go.sum
vendored
Normal file
2
packages/now-go/test/fixtures/17-go-mod-api/api/go.sum
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
github.com/dhruvbird/go-cowsay v0.0.0-20131019225157-6fd7bd0281c0 h1:t/CyPFyQ/Y5zHoxCd7hZ+15fOYIwKPsTxyqTmBlA2A8=
|
||||
github.com/dhruvbird/go-cowsay v0.0.0-20131019225157-6fd7bd0281c0/go.mod h1:Pj0EWCBFS61kh+c6d7rUu6496j3XEAL/QnDQ3D0V8Js=
|
||||
12
packages/now-go/test/fixtures/17-go-mod-api/api/v1/routes/someroute.go
vendored
Normal file
12
packages/now-go/test/fixtures/17-go-mod-api/api/v1/routes/someroute.go
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/zeit/does-not-exist/api/_pkg/somepackage"
|
||||
)
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello %v", somepackage.Foo)
|
||||
}
|
||||
16
packages/now-go/test/fixtures/17-go-mod-api/now.json
vendored
Normal file
16
packages/now-go/test/fixtures/17-go-mod-api/now.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [
|
||||
{
|
||||
"src": "api/v1/**/*.go",
|
||||
"use": "@now/go",
|
||||
"config": { "zeroConfig": true }
|
||||
}
|
||||
],
|
||||
"probes": [
|
||||
{
|
||||
"path": "/api/v1/routes/someroute.go",
|
||||
"mustContain": "Hello Dependency:RANDOMNESS_PLACEHOLDER"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
packages/now-go/test/fixtures/20-square-brackets/api/[hello].go
vendored
Normal file
10
packages/now-go/test/fixtures/20-square-brackets/api/[hello].go
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "hello:RANDOMNESS_PLACEHOLDER")
|
||||
}
|
||||
10
packages/now-go/test/fixtures/20-square-brackets/api/sub/[hi].go
vendored
Normal file
10
packages/now-go/test/fixtures/20-square-brackets/api/sub/[hi].go
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "hi:RANDOMNESS_PLACEHOLDER")
|
||||
}
|
||||
11
packages/now-go/test/fixtures/20-square-brackets/now.json
vendored
Normal file
11
packages/now-go/test/fixtures/20-square-brackets/now.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "api/**/*.go", "use": "@now/go" }],
|
||||
"probes": [
|
||||
{
|
||||
"path": "/api/[hello].go",
|
||||
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
|
||||
},
|
||||
{ "path": "/api/sub/[hi].go", "mustContain": "hi:RANDOMNESS_PLACEHOLDER" }
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/next",
|
||||
"version": "2.3.13",
|
||||
"version": "2.3.17",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/next-js",
|
||||
|
||||
@@ -20,9 +20,9 @@ import {
|
||||
} from '@now/build-utils';
|
||||
import { Route, Source } from '@now/routing-utils';
|
||||
import {
|
||||
convertHeaders,
|
||||
convertRedirects,
|
||||
convertRewrites,
|
||||
convertHeaders,
|
||||
} from '@now/routing-utils/dist/superstatic';
|
||||
import nodeFileTrace, { NodeFileTraceReasons } from '@zeit/node-file-trace';
|
||||
import { ChildProcess, fork } from 'child_process';
|
||||
@@ -92,6 +92,8 @@ const nowDevChildProcesses = new Set<ChildProcess>();
|
||||
});
|
||||
});
|
||||
|
||||
const MAX_AGE_ONE_YEAR = 31536000;
|
||||
|
||||
/**
|
||||
* Read package.json from files
|
||||
*/
|
||||
@@ -340,6 +342,8 @@ export const build = async ({
|
||||
const redirects: Route[] = [];
|
||||
const nextBasePathRoute: Route[] = [];
|
||||
let nextBasePath: string | undefined;
|
||||
// whether they have enabled pages/404.js as the custom 404 page
|
||||
let hasPages404 = false;
|
||||
|
||||
if (routesManifest) {
|
||||
switch (routesManifest.version) {
|
||||
@@ -352,6 +356,10 @@ export const build = async ({
|
||||
headers.push(...convertHeaders(routesManifest.headers));
|
||||
}
|
||||
|
||||
if (routesManifest.pages404) {
|
||||
hasPages404 = true;
|
||||
}
|
||||
|
||||
if (routesManifest.basePath && routesManifest.basePath !== '/') {
|
||||
nextBasePath = routesManifest.basePath;
|
||||
|
||||
@@ -449,7 +457,9 @@ export const build = async ({
|
||||
),
|
||||
// Next.js assets contain a hash or entropy in their filenames, so they
|
||||
// are guaranteed to be unique and cacheable indefinitely.
|
||||
headers: { 'cache-control': 'public,max-age=31536000,immutable' },
|
||||
headers: {
|
||||
'cache-control': `public,max-age=${MAX_AGE_ONE_YEAR},immutable`,
|
||||
},
|
||||
continue: true,
|
||||
},
|
||||
{
|
||||
@@ -459,6 +469,20 @@ export const build = async ({
|
||||
// folder
|
||||
{ handle: 'filesystem' },
|
||||
|
||||
// This needs to come directly after handle: filesystem to make sure to
|
||||
// 404 and clear the cache header for _next requests
|
||||
{
|
||||
src: path.join(
|
||||
'/',
|
||||
entryDirectory,
|
||||
'_next/static/(?:[^/]+/pages|chunks|runtime|css|media)/.+'
|
||||
),
|
||||
headers: {
|
||||
'cache-control': '',
|
||||
},
|
||||
status: 404,
|
||||
},
|
||||
|
||||
...rewrites,
|
||||
// Dynamic routes
|
||||
// TODO: do we want to do this?: ...dynamicRoutes,
|
||||
@@ -498,6 +522,7 @@ export const build = async ({
|
||||
const staticPages: { [key: string]: FileFsRef } = {};
|
||||
const dynamicPages: string[] = [];
|
||||
const dynamicDataRoutes: Array<Source> = [];
|
||||
let static404Page: string | undefined;
|
||||
|
||||
const appMountPrefixNoTrailingSlash = path.posix
|
||||
.join('/', entryDirectory)
|
||||
@@ -612,18 +637,18 @@ export const build = async ({
|
||||
|
||||
// Prerendered routes emit a `.html` file but should not be treated as a
|
||||
// static page.
|
||||
// Lazily prerendered routes do not have a `.html` file so we don't need
|
||||
// to check/skip it here.
|
||||
// Lazily prerendered routes have a fallback `.html` file on newer
|
||||
// Next.js versions so we need to also not treat it as a static page here.
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
prerenderManifest.routes,
|
||||
routeName
|
||||
)
|
||||
prerenderManifest.routes[routeName] ||
|
||||
(prerenderManifest.lazyRoutes[routeName] &&
|
||||
prerenderManifest.lazyRoutes[routeName].fallback)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const staticRoute = path.join(entryDirectory, pathname);
|
||||
|
||||
staticPages[staticRoute] = staticPageFiles[page];
|
||||
staticPages[staticRoute].contentType = htmlContentType;
|
||||
|
||||
@@ -633,9 +658,18 @@ export const build = async ({
|
||||
}
|
||||
});
|
||||
|
||||
const pageKeys = Object.keys(pages);
|
||||
// this can be either 404.html in latest versions
|
||||
// or _errors/404.html versions while this was experimental
|
||||
static404Page =
|
||||
staticPages['404'] && hasPages404
|
||||
? '404'
|
||||
: staticPages['_errors/404']
|
||||
? '_errors/404'
|
||||
: undefined;
|
||||
|
||||
// > 1 because _error is a lambda but isn't used if a static 404 is available
|
||||
const hasLambdas = !staticPages['_errors/404'] || pageKeys.length > 1;
|
||||
const pageKeys = Object.keys(pages);
|
||||
const hasLambdas = !static404Page || pageKeys.length > 1;
|
||||
|
||||
if (pageKeys.length === 0) {
|
||||
const nextConfig = await getNextConfig(workPath, entryPath);
|
||||
@@ -781,8 +815,13 @@ export const build = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't create _error lambda if we have a static 404 page
|
||||
if (staticPages['_errors/404'] && page === '_error.js') {
|
||||
// Don't create _error lambda if we have a static 404 page or
|
||||
// pages404 is enabled and 404.js is present
|
||||
if (
|
||||
page === '_error.js' &&
|
||||
((static404Page && staticPages[static404Page]) ||
|
||||
(hasPages404 && pages['404.js']))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -811,10 +850,10 @@ export const build = async ({
|
||||
config,
|
||||
});
|
||||
|
||||
const outputName = path.join(entryDirectory, pathname);
|
||||
|
||||
if (requiresTracing) {
|
||||
lambdas[
|
||||
path.join(entryDirectory, pathname)
|
||||
] = await createLambdaFromPseudoLayers({
|
||||
lambdas[outputName] = await createLambdaFromPseudoLayers({
|
||||
files: {
|
||||
...launcherFiles,
|
||||
[requiresTracing ? pageFileName : 'page.js']: pages[page],
|
||||
@@ -825,7 +864,7 @@ export const build = async ({
|
||||
...lambdaOptions,
|
||||
});
|
||||
} else {
|
||||
lambdas[path.join(entryDirectory, pathname)] = await createLambda({
|
||||
lambdas[outputName] = await createLambda({
|
||||
files: {
|
||||
...launcherFiles,
|
||||
...assets,
|
||||
@@ -848,12 +887,18 @@ export const build = async ({
|
||||
const onPrerenderRoute = (routeKey: string, isLazy: boolean) => {
|
||||
// Get the route file as it'd be mounted in the builder output
|
||||
const routeFileNoExt = routeKey === '/' ? '/index' : routeKey;
|
||||
const lazyHtmlFallback =
|
||||
isLazy && prerenderManifest.lazyRoutes[routeKey].fallback;
|
||||
|
||||
const htmlFsRef = isLazy
|
||||
? null
|
||||
: new FileFsRef({
|
||||
fsPath: path.join(pagesDir, `${routeFileNoExt}.html`),
|
||||
});
|
||||
const htmlFsRef =
|
||||
isLazy && !lazyHtmlFallback
|
||||
? null
|
||||
: new FileFsRef({
|
||||
fsPath: path.join(
|
||||
pagesDir,
|
||||
`${lazyHtmlFallback || routeFileNoExt + '.html'}`
|
||||
),
|
||||
});
|
||||
const jsonFsRef = isLazy
|
||||
? null
|
||||
: new FileFsRef({
|
||||
@@ -889,34 +934,39 @@ export const build = async ({
|
||||
);
|
||||
const outputPathData = path.posix.join(entryDirectory, dataRoute);
|
||||
|
||||
const lambda = lambdas[outputSrcPathPage];
|
||||
if (lambda == null) {
|
||||
throw new Error(`Unable to find lambda for route: ${routeFileNoExt}`);
|
||||
}
|
||||
|
||||
if (initialRevalidate === false) {
|
||||
if (htmlFsRef == null || jsonFsRef == null) {
|
||||
throw new Error('invariant: htmlFsRef != null && jsonFsRef != null');
|
||||
}
|
||||
htmlFsRef.contentType = htmlContentType;
|
||||
prerenders[outputPathPage] = htmlFsRef;
|
||||
prerenders[outputPathData] = jsonFsRef;
|
||||
} else {
|
||||
const lambda = lambdas[outputSrcPathPage];
|
||||
if (lambda == null) {
|
||||
throw new Error(`Unable to find lambda for route: ${routeFileNoExt}`);
|
||||
}
|
||||
|
||||
prerenders[outputPathPage] = new Prerender({
|
||||
expiration: initialRevalidate,
|
||||
lambda,
|
||||
fallback: htmlFsRef,
|
||||
group: prerenderGroup,
|
||||
});
|
||||
prerenders[outputPathData] = new Prerender({
|
||||
expiration: initialRevalidate,
|
||||
lambda,
|
||||
fallback: jsonFsRef,
|
||||
group: prerenderGroup,
|
||||
});
|
||||
|
||||
++prerenderGroup;
|
||||
}
|
||||
|
||||
prerenders[outputPathPage] = new Prerender({
|
||||
expiration:
|
||||
initialRevalidate === false
|
||||
? MAX_AGE_ONE_YEAR * 10
|
||||
: initialRevalidate,
|
||||
lambda,
|
||||
fallback: htmlFsRef,
|
||||
group: prerenderGroup,
|
||||
bypassToken: prerenderManifest.bypassToken,
|
||||
});
|
||||
prerenders[outputPathData] = new Prerender({
|
||||
expiration:
|
||||
initialRevalidate === false
|
||||
? MAX_AGE_ONE_YEAR * 10
|
||||
: initialRevalidate,
|
||||
lambda,
|
||||
fallback: jsonFsRef,
|
||||
group: prerenderGroup,
|
||||
bypassToken: prerenderManifest.bypassToken,
|
||||
});
|
||||
|
||||
++prerenderGroup;
|
||||
};
|
||||
|
||||
Object.keys(prerenderManifest.routes).forEach(route =>
|
||||
@@ -1030,7 +1080,9 @@ export const build = async ({
|
||||
),
|
||||
// Next.js assets contain a hash or entropy in their filenames, so they
|
||||
// are guaranteed to be unique and cacheable indefinitely.
|
||||
headers: { 'cache-control': 'public,max-age=31536000,immutable' },
|
||||
headers: {
|
||||
'cache-control': `public,max-age=${MAX_AGE_ONE_YEAR},immutable`,
|
||||
},
|
||||
continue: true,
|
||||
},
|
||||
{ src: path.join('/', entryDirectory, '_next(?!/data(?:/|$))(?:/.*)?') },
|
||||
@@ -1038,6 +1090,20 @@ export const build = async ({
|
||||
// folder
|
||||
{ handle: 'filesystem' },
|
||||
|
||||
// This needs to come directly after handle: filesystem to make sure to
|
||||
// 404 and clear the cache header for _next requests
|
||||
{
|
||||
src: path.join(
|
||||
'/',
|
||||
entryDirectory,
|
||||
'_next/static/(?:[^/]+/pages|chunks|runtime|css|media)/.+'
|
||||
),
|
||||
headers: {
|
||||
'cache-control': '',
|
||||
},
|
||||
status: 404,
|
||||
},
|
||||
|
||||
...rewrites,
|
||||
// Dynamic routes
|
||||
...dynamicRoutes,
|
||||
@@ -1051,7 +1117,13 @@ export const build = async ({
|
||||
dest: path.join(
|
||||
'/',
|
||||
entryDirectory,
|
||||
staticPages['_errors/404'] ? '_errors/404' : '_error'
|
||||
static404Page
|
||||
? static404Page
|
||||
: // if static 404 is not present but we have pages/404.js
|
||||
// it is a lambda due to _app getInitialProps
|
||||
hasPages404 && lambdas['404']
|
||||
? '404'
|
||||
: '_error'
|
||||
),
|
||||
status: 404,
|
||||
},
|
||||
|
||||
@@ -306,6 +306,7 @@ type RoutesManifestRegex = {
|
||||
};
|
||||
|
||||
export type RoutesManifest = {
|
||||
pages404: boolean;
|
||||
basePath: string | undefined;
|
||||
redirects: (Redirect & RoutesManifestRegex)[];
|
||||
rewrites: (NowRewrite & RoutesManifestRegex)[];
|
||||
@@ -613,6 +614,8 @@ export async function createLambdaFromPseudoLayers({
|
||||
}
|
||||
|
||||
export type NextPrerenderedRoutes = {
|
||||
bypassToken: string | null;
|
||||
|
||||
routes: {
|
||||
[route: string]: {
|
||||
initialRevalidate: number | false;
|
||||
@@ -623,6 +626,7 @@ export type NextPrerenderedRoutes = {
|
||||
|
||||
lazyRoutes: {
|
||||
[route: string]: {
|
||||
fallback?: string;
|
||||
routeRegex: string;
|
||||
dataRoute: string;
|
||||
dataRouteRegex: string;
|
||||
@@ -712,7 +716,7 @@ export async function getPrerenderManifest(
|
||||
.catch(() => false);
|
||||
|
||||
if (!hasManifest) {
|
||||
return { routes: {}, lazyRoutes: {} };
|
||||
return { routes: {}, lazyRoutes: {}, bypassToken: null };
|
||||
}
|
||||
|
||||
const manifest: {
|
||||
@@ -726,11 +730,15 @@ export async function getPrerenderManifest(
|
||||
};
|
||||
dynamicRoutes: {
|
||||
[key: string]: {
|
||||
fallback?: string;
|
||||
routeRegex: string;
|
||||
dataRoute: string;
|
||||
dataRouteRegex: string;
|
||||
};
|
||||
};
|
||||
preview?: {
|
||||
previewModeId: string;
|
||||
};
|
||||
} = JSON.parse(await fs.readFile(pathPrerenderManifest, 'utf8'));
|
||||
|
||||
switch (manifest.version) {
|
||||
@@ -738,7 +746,12 @@ export async function getPrerenderManifest(
|
||||
const routes = Object.keys(manifest.routes);
|
||||
const lazyRoutes = Object.keys(manifest.dynamicRoutes);
|
||||
|
||||
const ret: NextPrerenderedRoutes = { routes: {}, lazyRoutes: {} };
|
||||
const ret: NextPrerenderedRoutes = {
|
||||
routes: {},
|
||||
lazyRoutes: {},
|
||||
bypassToken:
|
||||
(manifest.preview && manifest.preview.previewModeId) || null,
|
||||
};
|
||||
|
||||
routes.forEach(route => {
|
||||
const {
|
||||
@@ -759,17 +772,23 @@ export async function getPrerenderManifest(
|
||||
lazyRoutes.forEach(lazyRoute => {
|
||||
const {
|
||||
routeRegex,
|
||||
fallback,
|
||||
dataRoute,
|
||||
dataRouteRegex,
|
||||
} = manifest.dynamicRoutes[lazyRoute];
|
||||
|
||||
ret.lazyRoutes[lazyRoute] = { routeRegex, dataRoute, dataRouteRegex };
|
||||
ret.lazyRoutes[lazyRoute] = {
|
||||
routeRegex,
|
||||
fallback,
|
||||
dataRoute,
|
||||
dataRouteRegex,
|
||||
};
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
default: {
|
||||
return { routes: {}, lazyRoutes: {} };
|
||||
return { routes: {}, lazyRoutes: {}, bypassToken: null };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,16 @@
|
||||
"responseHeaders": {
|
||||
"cache-control": "public,max-age=31536000,immutable"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/_next/static/invalid-build-id/pages/index.js",
|
||||
"notResponseHeaders": {
|
||||
"cache-control": "public,max-age=31536000,immutable"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/_next/static/invalid-build-id/pages/non-existent.js",
|
||||
"mustNotContain": "final route"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export default () => 'hi from final route'
|
||||
@@ -0,0 +1 @@
|
||||
export default () => 'hi from another route'
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user