Compare commits

..

26 Commits

Author SHA1 Message Date
JJ Kasper
895b233605 Publish Canary
- @vercel/frameworks@0.0.18-canary.3
 - vercel@20.0.0-canary.13
 - @vercel/next@2.6.19-canary.1
2020-08-05 11:31:00 -05:00
Joe Haddad
d9e6b5348b [next] Blocking Fallback (ISG) Support (#4985) 2020-08-05 11:49:45 -04:00
Mark Glagola
1934a64864 [cli][certs rm] (Major) Bump to v5 certs API (#4943)
Bumps certs API from `/v3` to  `/v5`.

_Removing auto generated certs will be blocked in `/v5`_
2020-08-04 23:38:53 +00:00
Steven
c412642668 [cli] Handle unauthorized errors when user is not logged in (#4975)
We don't need to report these errors to Sentry because the user attempted a command that requires them to login.

https://sentry.io/organizations/vercel/issues/1498276418/?project=1323225

This will print:
- `NOT_AUTHORIZED`: "The specified token is not valid. Use vc login to generate a new token."
- `TEAM_DELETED`: "Your team was deleted. You can switch to a different one using vc switch"

### Related 
- Related to #4082 
- Related to #3857
2020-08-04 22:34:55 +00:00
Steven
793fe9aee1 [tests] Update cancel action to 0.4.1 (#4980)
Bump `cancel-workflow-action` to [0.4.1](https://github.com/styfle/cancel-workflow-action/releases/tag/0.4.1) to fix a bug where the wrong branch was cancelled.
2020-08-04 17:15:25 -04:00
Joe Haddad
ddc54d2ca4 [next] Run prettier (#4979) 2020-08-04 14:01:33 -07:00
Steven
39e5f0a364 [frameworks][examples] Adjust redwood logo size (#4976)
- Rename redwood logo to redwoodjs to match framework slug
- Update size to 48x48 to match other logos
2020-08-04 15:49:09 -04:00
Steven
43ed9ec859 [cli] Fix tests for .gitignore append during project link (#4978)
Fixes tests from #4965
2020-08-04 18:58:22 +00:00
Kid
8ba44fca79 [cli] Fix .gitignore append during project link (#4965)
* [cli] fix .gitignore updating check

* Detect file EOL

* Semicolon

* Add trailing newline

Co-authored-by: Nathan Rajlich <n@n8.io>
2020-08-04 10:29:08 -07:00
Andy Bitz
27dbefaecf Publish Canary
- vercel@20.0.0-canary.12
 - @vercel/next@2.6.19-canary.0
2020-08-04 19:10:28 +02:00
Andy
b4a13913c7 [cli] Adjust output for recently changed domain commands (#4959)
* [cli] Adjust output for recently changed domain commands

* Update the inspect and list page

* Remove test

* Update packages/now-cli/src/commands/domains/inspect.ts

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

* Change output

* Remove workaround

* Update error

* Include contextName

Co-authored-by: Steven <steven@ceriously.com>
2020-08-04 18:49:46 +02:00
JJ Kasper
f842266b2e Publish Stable
- @vercel/next@2.6.18
2020-08-04 09:27:01 -05:00
JJ Kasper
ad0cc858ed Publish Canary
- vercel@20.0.0-canary.11
 - @vercel/next@2.6.18-canary.0
 - @vercel/node@1.7.5-canary.1
2020-08-04 09:06:29 -05:00
JJ Kasper
4bb7180de9 [next] Ensure trailing slash resolves to functions correctly (#4972)
This makes sure the routes mapping to the serverless functions handle the trailing slash being present or not. This also adds additional test cases to ensure it is functioning correctly
2020-08-04 03:35:50 +00:00
Steven
7a4faa480d [node][next] Bump node-file-trace to 0.8.2 (#4969)
Bump `node-file-trace` to [0.8.2](https://github.com/vercel/node-file-trace/releases/tag/0.8.2)
2020-08-03 11:09:52 -04:00
JJ Kasper
421be5d738 Publish Stable
- @vercel/next@2.6.17
2020-07-31 10:29:04 -05:00
JJ Kasper
b8eaf10974 Publish Canary
- vercel@20.0.0-canary.10
 - @vercel/next@2.6.17-canary.1
2020-07-31 10:20:59 -05:00
JJ Kasper
92a4bf27cf [next] Fix monorepo build edge case without build script (#4956)
This fixes an edge case where a monorepo Next.js app fails to build due to running `next build` manually instead of via `package.json` scripts like we previously were doing. 

Failing project with current `@vercel/next` running `next build` manually: https://vercel.com/jj4/test2020/15fhg1ko3
Succeeding project running changing back to running `next build` through a `package.json` script: https://vercel.com/jj4/test2020/dths5f1nv

x-ref: https://github.com/vercel/vercel/pull/4863
Fixes: https://github.com/vercel/next.js/issues/15713
2020-07-31 15:17:14 +00:00
Steven
2672838b64 Publish Canary
- vercel@20.0.0-canary.9
 - @vercel/next@2.6.17-canary.0
 - @vercel/redwood@0.0.2-canary.3
2020-07-31 09:15:51 -04:00
Steven
1c96071ddc [cli][redwood] Update redwood port detection in vc dev (#4937)
Fixes redwood support for `vc dev`
2020-07-31 13:14:04 +00:00
Steven
71cdf759da [tests] Ignore custom next 404 (#4955)
Commenting out this test assertion until we can fix it in #4946
2020-07-30 20:40:41 -04:00
Torsten Dittmann
93ebd213de [examples] Update deprecated and outdated Svelte example packages (#4706) 2020-07-30 15:44:39 -07:00
JJ Kasper
a32ba8f214 Publish Stable
- @vercel/next@2.6.16
2020-07-30 14:15:29 -05:00
JJ Kasper
d416f70a6e Publish Canary
- @vercel/frameworks@0.0.18-canary.2
 - vercel@20.0.0-canary.8
 - @vercel/next@2.6.16-canary.0
 - @vercel/node@1.7.5-canary.0
 - @vercel/routing-utils@1.8.4-canary.0
 - @vercel/redwood@0.0.2-canary.2
2020-07-30 13:45:26 -05:00
JJ Kasper
ba9e1dd0ba [routing-utils] Update header replacing to handle more cases (#4942)
This adds handling for more cases while updating header values to make sure to escape any characters that could break compiling with `path-to-regexp`

x-ref: https://github.com/vercel/next.js/pull/15592
2020-07-30 18:22:30 +00:00
Steven
d513f74b70 [frameworks][redwood] Bump to RedwoodJS to 0.15.0 (#4953)
Implements https://github.com/redwoodjs/redwood/pull/904
2020-07-30 10:52:04 -04:00
75 changed files with 21385 additions and 1056 deletions

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: styfle/cancel-workflow-action@0.3.2
- uses: styfle/cancel-workflow-action@0.4.1
with:
workflow_id: 849295, 849296, 849297, 849298
access_token: ${{ github.token }}

View File

@@ -1,4 +1,4 @@
![RedwoodJS Logo](https://github.com/vercel/vercel/blob/master/packages/frameworks/logos/redwood.svg)
![RedwoodJS Logo](https://github.com/vercel/vercel/blob/master/packages/frameworks/logos/redwoodjs.svg)
# RedwoodJS Example

View File

@@ -3,6 +3,6 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@redwoodjs/api": "0.14.0"
"@redwoodjs/api": "0.15.0"
}
}

View File

@@ -7,7 +7,7 @@
]
},
"devDependencies": {
"@redwoodjs/core": "0.14.0"
"@redwoodjs/core": "0.15.0"
},
"eslintConfig": {
"extends": "@redwoodjs/eslint-config"

View File

@@ -6,8 +6,8 @@
"defaults"
],
"dependencies": {
"@redwoodjs/router": "0.14.0",
"@redwoodjs/web": "0.14.0",
"@redwoodjs/router": "0.15.0",
"@redwoodjs/web": "0.15.0",
"prop-types": "^15.7.2",
"react": "^16.13.1",
"react-dom": "^16.13.1"

14339
examples/redwoodjs/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,17 +2,17 @@
"name": "svelte-app",
"version": "1.0.0",
"devDependencies": {
"@rollup/plugin-commonjs": "^13.0.0",
"@rollup/plugin-node-resolve": "^8.1.0",
"npm-run-all": "^4.1.5",
"rollup": "^1.10.1",
"rollup-plugin-commonjs": "^9.3.4",
"rollup": "^2.18.0",
"rollup-plugin-livereload": "^1.0.0",
"rollup-plugin-node-resolve": "^4.2.3",
"rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^4.0.4",
"rollup-plugin-terser": "^6.1.0",
"svelte": "^3.0.0"
},
"dependencies": {
"sirv-cli": "^0.4.4"
"sirv-cli": "^1.0.1"
},
"scripts": {
"build": "rollup -c",

View File

@@ -1,6 +1,6 @@
import svelte from 'rollup-plugin-svelte';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';

View File

@@ -686,7 +686,7 @@
"name": "RedwoodJS",
"slug": "redwoodjs",
"demo": "https://redwoodjs.now-examples.now.sh",
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/redwood.svg",
"logo": "https://raw.githubusercontent.com/vercel/vercel/master/packages/frameworks/logos/redwoodjs.svg",
"tagline": "RedwoodJS is a full-stack framework for the Jamstack.",
"description": "A RedwoodJS app, bootstraped with create-redwood-app.",
"website": "https://redwoodjs.com",
@@ -703,7 +703,7 @@
"value": "yarn rw db up --no-db-client --auto-approve && yarn rw build"
},
"devCommand": {
"value": "yarn rw dev"
"value": "yarn rw dev --fwd=\"--port=$PORT --open=false\""
},
"outputDirectory": {
"value": "RedwoodJS default"

View File

@@ -1 +0,0 @@
<svg fill="none" height="1000" viewBox="0 0 917 1000" width="917" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m249.557 144.582 194.171 132.54c4.383 2.918 9.502 4.516 14.755 4.606 5.261-.038 10.394-1.641 14.755-4.606l194.319-132.986c7.55-5.406 11.714-14.418 10.957-23.717-.757-9.298-6.322-17.507-14.646-21.6024l-194.171-96.13614c-7.366-3.573948-15.947-3.573948-23.313 0l-193.581 96.13614c-8.474 4.1174-14.113 12.4854-14.783 21.9354-.67 9.451 3.73 18.541 11.537 23.83zm274.879 174.144c.016 8.789 4.318 17.01 11.509 21.991l155.662 106.389c9.965 6.87 23.298 6.012 32.313-2.081l130.579-116.789c5.819-5.199 9.051-12.729 8.823-20.56s-3.892-15.158-10.004-20.005l-124.677-99.702c-9.062-7.199-21.704-7.68-31.28-1.189l-161.416 110.401c-7.064 4.89-11.35 12.914-11.509 21.545zm-387.163 144.724c6.292 5.652 9.526 13.988 8.706 22.437-.817 8.499-5.726 16.052-13.132 20.208l-92.9545 55.72c-9.4227 5.633-21.32 4.82-29.90183-2.041-8.5818-6.861-12.06543-18.346-8.75546-28.865l34.37839-108.172c2.6969-8.57 9.5328-15.175 18.1483-17.533 8.609-2.505 17.8924-.309 24.4928 5.795zm504.168 11.293-168.056-115.007c-8.931-6.01-20.578-6.01-29.509 0l-168.056 115.007c-6.684 4.626-10.919 12.061-11.509 20.208-.435 8.203 2.816 16.169 8.853 21.693l167.909 150.222c4.842 4.319 11.089 6.698 17.558 6.687 6.465-.002 12.708-2.38 17.558-6.687l167.908-150.222c6.056-5.501 9.265-13.5 8.705-21.693-.469-8.146-4.666-15.612-11.361-20.208zm-448.247-29.718-130.4316-116.79c-5.8687-5.331-9.1073-12.995-8.8528-20.95.1419-7.841 3.7705-15.204 9.8856-20.06l124.6768-100.296c9.126-7.179 21.793-7.658 31.428-1.189l161.269 110.401c7.484 4.908 11.998 13.293 11.998 22.288 0 8.994-4.514 17.38-11.998 22.288l-155.515 106.388c-10.025 6.841-23.376 5.985-32.46-2.08zm669.715 167.756-132.792-79.495c-9.862-5.943-22.415-4.739-30.985 2.972l-162.301 144.873c-6.846 6.114-10.062 15.362-8.499 24.441 1.563 9.08 7.681 16.698 16.171 20.135l225.157 91.233c3.088 1.283 6.397 1.939 9.738 1.932 10.449.033 19.936-6.142 24.197-15.751l69.79-156.314c5.68-12.37 1.157-27.062-10.476-34.026zm18.443-190.043 34.379 108.171h-.295c2.542 8.091 1.097 16.919-3.889 23.761-4.986 6.841-12.915 10.876-21.342 10.86-4.728.016-9.37-1.269-13.427-3.715l-93.102-55.72c-7.254-4.243-11.992-11.789-12.689-20.208-.87-8.456 2.373-16.814 8.705-22.436l59.019-52.6c6.668-5.976 15.881-8.156 24.493-5.795 8.609 2.459 15.423 9.098 18.148 17.682zm-492.511 282.761c1.587-9.042-1.597-18.266-8.41-24.368l-162.302-144.873c-8.57-7.711-21.123-8.915-30.985-2.972l-132.7921 79.495c-11.4977 6.995-16.0467 21.502-10.6233 33.878l69.9374 156.314c5.794 13.034 20.774 19.134 33.936 13.818l225.009-91.232c8.492-3.407 14.632-10.995 16.23-20.06zm79.675 44.577 180.598 73.105c8.83 3.779 14.93 12.084 15.935 21.694 1.143 9.729-3.178 19.291-11.214 24.814l-180.745 125.556c-4.331 3.043-9.473 4.7-14.754 4.755-5.277-.082-10.411-1.737-14.755-4.755l-180.597-125.556c-8.066-5.508-12.439-15.061-11.362-24.814 1.206-9.71 7.526-18.006 16.526-21.694l180.597-73.105c6.351-2.532 13.421-2.532 19.771 0z" fill="#bf4722" fill-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1 @@
<svg fill="none" width="48" height="48" viewBox="0 0 917 1000" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m249.557 144.582 194.171 132.54c4.383 2.918 9.502 4.516 14.755 4.606 5.261-.038 10.394-1.641 14.755-4.606l194.319-132.986c7.55-5.406 11.714-14.418 10.957-23.717-.757-9.298-6.322-17.507-14.646-21.6024l-194.171-96.13614c-7.366-3.573948-15.947-3.573948-23.313 0l-193.581 96.13614c-8.474 4.1174-14.113 12.4854-14.783 21.9354-.67 9.451 3.73 18.541 11.537 23.83zm274.879 174.144c.016 8.789 4.318 17.01 11.509 21.991l155.662 106.389c9.965 6.87 23.298 6.012 32.313-2.081l130.579-116.789c5.819-5.199 9.051-12.729 8.823-20.56s-3.892-15.158-10.004-20.005l-124.677-99.702c-9.062-7.199-21.704-7.68-31.28-1.189l-161.416 110.401c-7.064 4.89-11.35 12.914-11.509 21.545zm-387.163 144.724c6.292 5.652 9.526 13.988 8.706 22.437-.817 8.499-5.726 16.052-13.132 20.208l-92.9545 55.72c-9.4227 5.633-21.32 4.82-29.90183-2.041-8.5818-6.861-12.06543-18.346-8.75546-28.865l34.37839-108.172c2.6969-8.57 9.5328-15.175 18.1483-17.533 8.609-2.505 17.8924-.309 24.4928 5.795zm504.168 11.293-168.056-115.007c-8.931-6.01-20.578-6.01-29.509 0l-168.056 115.007c-6.684 4.626-10.919 12.061-11.509 20.208-.435 8.203 2.816 16.169 8.853 21.693l167.909 150.222c4.842 4.319 11.089 6.698 17.558 6.687 6.465-.002 12.708-2.38 17.558-6.687l167.908-150.222c6.056-5.501 9.265-13.5 8.705-21.693-.469-8.146-4.666-15.612-11.361-20.208zm-448.247-29.718-130.4316-116.79c-5.8687-5.331-9.1073-12.995-8.8528-20.95.1419-7.841 3.7705-15.204 9.8856-20.06l124.6768-100.296c9.126-7.179 21.793-7.658 31.428-1.189l161.269 110.401c7.484 4.908 11.998 13.293 11.998 22.288 0 8.994-4.514 17.38-11.998 22.288l-155.515 106.388c-10.025 6.841-23.376 5.985-32.46-2.08zm669.715 167.756-132.792-79.495c-9.862-5.943-22.415-4.739-30.985 2.972l-162.301 144.873c-6.846 6.114-10.062 15.362-8.499 24.441 1.563 9.08 7.681 16.698 16.171 20.135l225.157 91.233c3.088 1.283 6.397 1.939 9.738 1.932 10.449.033 19.936-6.142 24.197-15.751l69.79-156.314c5.68-12.37 1.157-27.062-10.476-34.026zm18.443-190.043 34.379 108.171h-.295c2.542 8.091 1.097 16.919-3.889 23.761-4.986 6.841-12.915 10.876-21.342 10.86-4.728.016-9.37-1.269-13.427-3.715l-93.102-55.72c-7.254-4.243-11.992-11.789-12.689-20.208-.87-8.456 2.373-16.814 8.705-22.436l59.019-52.6c6.668-5.976 15.881-8.156 24.493-5.795 8.609 2.459 15.423 9.098 18.148 17.682zm-492.511 282.761c1.587-9.042-1.597-18.266-8.41-24.368l-162.302-144.873c-8.57-7.711-21.123-8.915-30.985-2.972l-132.7921 79.495c-11.4977 6.995-16.0467 21.502-10.6233 33.878l69.9374 156.314c5.794 13.034 20.774 19.134 33.936 13.818l225.009-91.232c8.492-3.407 14.632-10.995 16.23-20.06zm79.675 44.577 180.598 73.105c8.83 3.779 14.93 12.084 15.935 21.694 1.143 9.729-3.178 19.291-11.214 24.814l-180.745 125.556c-4.331 3.043-9.473 4.7-14.754 4.755-5.277-.082-10.411-1.737-14.755-4.755l-180.597-125.556c-8.066-5.508-12.439-15.061-11.362-24.814 1.206-9.71 7.526-18.006 16.526-21.694l180.597-73.105c6.351-2.532 13.421-2.532 19.771 0z" fill="#bf4722" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "20.0.0-canary.7",
"version": "20.0.0-canary.13",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -64,10 +64,10 @@
"dependencies": {
"@vercel/build-utils": "2.4.3-canary.2",
"@vercel/go": "1.1.5-canary.0",
"@vercel/next": "2.6.15",
"@vercel/node": "1.7.4",
"@vercel/next": "2.6.19-canary.1",
"@vercel/node": "1.7.5-canary.1",
"@vercel/python": "1.2.2",
"@vercel/redwood": "0.0.2-canary.1",
"@vercel/redwood": "0.0.2-canary.3",
"@vercel/ruby": "1.2.3",
"@vercel/static-build": "0.17.7-canary.1",
"update-notifier": "4.1.0"

View File

@@ -7,7 +7,7 @@ import * as ERRORS from '../../util/errors-ts';
import { Output } from '../../util/output';
import deleteCertById from '../../util/certs/delete-cert-by-id';
import getCertById from '../../util/certs/get-cert-by-id';
import getCertsForDomain from '../../util/certs/get-certs-for-domain';
import { getCustomCertsForDomain } from '../../util/certs/get-custom-certs-for-domain';
import Client from '../../util/client';
import getScope from '../../util/get-scope';
import stamp from '../../util/output/stamp';
@@ -66,9 +66,17 @@ async function rm(
}
if (certs.length === 0) {
output.error(
`No certificates found by id "${id}" under ${chalk.bold(contextName)}`
);
if (id.includes('.')) {
output.error(
`No custom certificates found for "${id}" under ${chalk.bold(
contextName
)}`
);
} else {
output.error(
`No certificates found by id "${id}" under ${chalk.bold(contextName)}`
);
}
return 1;
}
@@ -101,7 +109,7 @@ async function getCertsToDelete(
) {
const cert = await getCertById(client, id);
if (cert instanceof ERRORS.CertNotFound) {
const certs = await getCertsForDomain(output, client, contextName, id);
const certs = await getCustomCertsForDomain(client, contextName, id);
if (certs instanceof ERRORS.CertsPermissionDenied) {
return certs;
}
@@ -125,12 +133,7 @@ function readConfirmation(output: Output, msg: string, certs: Cert[]) {
process.stdin
.on('data', d => {
process.stdin.pause();
resolve(
d
.toString()
.trim()
.toLowerCase() === 'y'
);
resolve(d.toString().trim().toLowerCase() === 'y');
})
.resume();
});

View File

@@ -128,7 +128,7 @@ export default async function add(
return 1;
}
const domainConfig = await getDomainConfig(client, contextName, domainName);
const domainConfig = await getDomainConfig(client, domainName);
if (domainConfig.misconfigured) {
output.warn(
@@ -142,7 +142,7 @@ export default async function add(
);
output.print(
` ${chalk.grey('b)')} ` +
`Change your domain nameservers to the intended set`
`Change your Domains's nameservers to the intended set`
);
output.print(
`\n${formatNSTable(

View File

@@ -71,8 +71,9 @@ export default async function buy(
const availableStamp = stamp();
const domainPrice = await getDomainPrice(client, domainName);
if (domainPrice instanceof ERRORS.UnsupportedTLD) {
output.error(`The TLD for ${param(domainName)} is not supported.`);
if (domainPrice instanceof Error) {
output.prettyError(domainPrice);
return 1;
}

View File

@@ -14,6 +14,7 @@ import inspect from './inspect';
import ls from './ls';
import rm from './rm';
import move from './move';
import verify from './verify';
import { getPkgName } from '../../util/pkg-name';
const help = () => {
@@ -81,6 +82,7 @@ const COMMAND_CONFIG = {
move: ['move'],
rm: ['rm', 'remove'],
transferIn: ['transfer-in'],
verify: ['verify'],
};
export default async function main(ctx: NowContext) {
@@ -119,6 +121,8 @@ export default async function main(ctx: NowContext) {
return rm(ctx, argv, args, output);
case 'transferIn':
return transferIn(ctx, argv, args, output);
case 'verify':
return verify(ctx, argv, args, output);
default:
return ls(ctx, argv, args, output);
}

View File

@@ -14,6 +14,8 @@ import getDomainPrice from '../../util/domains/get-domain-price';
import { getCommandName } from '../../util/pkg-name';
import { getDomainConfig } from '../../util/domains/get-domain-config';
import code from '../../util/output/code';
import wait from '../../util/output/wait';
import { getDomainRegistrar } from '../../util/domains/get-domain-registrar';
type Options = {
'--debug': boolean;
@@ -67,38 +69,26 @@ export default async function inspect(
}
output.debug(`Fetching domain info`);
const [domain, renewalPrice] = await Promise.all([
getDomainByName(client, contextName, domainName),
getDomainPrice(client, domainName, 'renewal')
.then(res => (res instanceof Error ? null : res.price))
.catch(() => null),
]);
if (!domain || domain instanceof DomainNotFound) {
output.error(
`Domain not found by "${domainName}" under ${chalk.bold(contextName)}`
);
output.log(`Run ${getCommandName(`domains ls`)} to see your domains.`);
return 1;
const cancelWait = wait(
`Fetching domain ${domainName} under ${chalk.bold(contextName)}`
);
const information = await fetchInformation({
output,
client,
contextName,
domainName,
cancelWait,
}).finally(() => {
cancelWait();
});
if (typeof information === 'number') {
return information;
}
if (domain instanceof DomainPermissionDenied) {
output.error(
`You don't have access to the domain ${domainName} under ${chalk.bold(
contextName
)}`
);
output.log(`Run ${getCommandName(`domains ls`)} to see your domains.`);
return 1;
}
const projects = await findProjectsForDomain(client, domainName);
if (projects instanceof Error) {
output.prettyError(projects);
return 1;
}
const domainConfig = await getDomainConfig(client, contextName, domainName);
const { domain, projects, renewalPrice, domainConfig } = information;
output.log(
`Domain ${domainName} found under ${chalk.bold(contextName)} ${chalk.gray(
@@ -108,46 +98,27 @@ export default async function inspect(
output.print('\n');
output.print(chalk.bold(' General\n\n'));
output.print(` ${chalk.cyan('Name')}\t\t\t${domain.name}\n`);
output.print(` ${chalk.cyan('Service Type')}\t\t${domain.serviceType}\n`);
output.print(
` ${chalk.cyan('Ordered At')}\t\t\t${formatDate(domain.orderedAt)}\n`
);
output.print(
` ${chalk.cyan('Transfer Started At')}\t\t${formatDate(
domain.transferStartedAt
)}\n`
);
output.print(
` ${chalk.cyan('Created At')}\t\t\t${formatDate(domain.createdAt)}\n`
);
output.print(
` ${chalk.cyan('Bought At')}\t\t\t${formatDate(domain.boughtAt)}\n`
);
output.print(
` ${chalk.cyan('Transferred At')}\t\t${formatDate(
domain.transferredAt
)}\n`
` ${chalk.cyan('Registrar')}\t\t\t${getDomainRegistrar(domain)}\n`
);
output.print(
` ${chalk.cyan('Expires At')}\t\t\t${formatDate(domain.expiresAt)}\n`
);
output.print(
` ${chalk.cyan('NS Verified At')}\t\t${formatDate(
domain.nsVerifiedAt
)}\n`
);
output.print(
` ${chalk.cyan('TXT Verified At')}\t\t${formatDate(
domain.txtVerifiedAt
)}\n`
);
output.print(` ${chalk.cyan('Edge Network')}\t\t${true}\n`);
if (renewalPrice && domain.boughtAt) {
output.print(
` ${chalk.cyan('Renewal Price')}\t\t$${renewalPrice} USD\n`
);
}
output.print(` ${chalk.cyan('CDN Enabled')}\t\t\t${true}\n`);
output.print(
` ${chalk.cyan('Creator')}\t\t\t${domain.creator.username}\n`
);
output.print(
` ${chalk.cyan('Created At')}\t\t\t${formatDate(domain.createdAt)}\n`
);
output.print('\n');
output.print(chalk.bold(' Nameservers\n\n'));
@@ -158,26 +129,6 @@ export default async function inspect(
);
output.print('\n');
if (domainConfig.misconfigured) {
output.warn(
`This domain is not configured properly. To configure it you should either:`
);
output.print(
` ${chalk.grey('a)')} ` +
`Set the following record on your DNS provider to continue: ` +
`${code(`A ${domainName} 76.76.21.21`)} ` +
`${chalk.grey('[recommended]')}\n`
);
output.print(
` ${chalk.grey('b)')} ` +
`Change your domain nameservers to the intended set detailed above.\n\n`
);
output.print(
` We will run a verification for you and you will receive an email upon completion.\n`
);
output.print(' Read more: https://vercel.link/domain-configuration\n\n');
}
if (Array.isArray(projects) && projects.length > 0) {
output.print(chalk.bold(' Projects\n'));
@@ -208,8 +159,109 @@ export default async function inspect(
.join('\n')
);
output.print('\n\n');
output.print('\n');
}
if (domainConfig.misconfigured) {
output.warn(
`This domain is not configured properly. To configure it you should either:`,
null,
null,
null,
{
boxen: {
margin: {
left: 2,
right: 0,
bottom: 0,
top: 0,
},
},
}
);
output.print(
` ${chalk.grey('a)')} ` +
`Set the following record on your DNS provider to continue: ` +
`${code(`A ${domainName} 76.76.21.21`)} ` +
`${chalk.grey('[recommended]')}\n`
);
output.print(
` ${chalk.grey('b)')} ` +
`Change your Domains's nameservers to the intended set detailed above.\n\n`
);
output.print(
` We will run a verification for you and you will receive an email upon completion.\n`
);
const contextNameConst = contextName;
const projectNames = Array.from(
new Set(projects.map(project => project.name))
);
if (projectNames.length) {
projectNames.forEach((name, index) => {
const prefix = index === 0 ? ' Read more:' : ' '.repeat(12);
output.print(
`${prefix} https://vercel.com/${contextNameConst}/${name}/settings/domains\n`
);
});
} else {
output.print(` Read more: https://vercel.link/domain-configuration\n`);
}
output.print('\n');
}
return null;
}
async function fetchInformation({
output,
client,
contextName,
domainName,
cancelWait,
}: {
output: Output;
client: Client;
contextName: string;
domainName: string;
cancelWait: () => void;
}) {
const [domain, renewalPrice] = await Promise.all([
getDomainByName(client, contextName, domainName, { ignoreWait: true }),
getDomainPrice(client, domainName, 'renewal')
.then(res => (res instanceof Error ? null : res.price))
.catch(() => null),
]);
if (domain instanceof DomainNotFound) {
cancelWait();
output.prettyError(domain);
return 1;
}
if (domain instanceof DomainPermissionDenied) {
cancelWait();
output.prettyError(domain);
output.log(`Run ${getCommandName(`domains ls`)} to see your domains.`);
return 1;
}
const projects = await findProjectsForDomain(client, domainName);
if (projects instanceof Error) {
cancelWait();
output.prettyError(projects);
return 1;
}
const domainConfig = await getDomainConfig(client, domainName);
return {
domain,
projects,
renewalPrice,
domainConfig,
};
}

View File

@@ -1,8 +1,8 @@
import ms from 'ms';
import psl from 'psl';
import chalk from 'chalk';
import plural from 'pluralize';
import wait from '../../util/output/wait';
import Client from '../../util/client';
import getDomains from '../../util/domains/get-domains';
import getScope from '../../util/get-scope';
@@ -10,28 +10,17 @@ import stamp from '../../util/output/stamp';
import { Output } from '../../util/output';
import formatTable from '../../util/format-table';
import { formatDateWithoutTime } from '../../util/format-date';
import { Domain, Project, NowContext } from '../../types';
import { getProjectsWithDomains } from '../../util/projects/get-projects-with-domains';
import { Domain, NowContext } from '../../types';
import getCommandFlags from '../../util/get-command-flags';
import { getCommandName } from '../../util/pkg-name';
import isDomainExternal from '../../util/domains/is-domain-external';
import { isPublicSuffix } from '../../util/domains/is-public-suffix';
import { getDomainRegistrar } from '../../util/domains/get-domain-registrar';
type Options = {
'--debug': boolean;
'--next': number;
};
interface DomainInfo {
domain: string;
apexDomain: string;
projectName: string | null;
dns: 'Vercel' | 'External';
configured: boolean;
expiresAt: number | null;
createdAt: number | null;
}
export default async function ls(
ctx: NowContext,
opts: Options,
@@ -75,29 +64,21 @@ export default async function ls(
return 1;
}
const [{ domains, pagination }, projects] = await Promise.all([
getDomains(client, contextName),
getProjectsWithDomains(client),
] as const);
const cancelWait = wait(`Fetching domains under ${chalk.bold(contextName)}`);
if (projects instanceof Error) {
output.prettyError(projects);
return 1;
}
const domainsInfo = createDomainsInfo(domains, projects);
const { domains, pagination } = await getDomains(client).finally(() => {
cancelWait();
});
output.log(
`${plural(
'project domain',
domainsInfo.length,
true
)} found under ${chalk.bold(contextName)} ${chalk.gray(lsStamp())}`
`${plural('Domain', domains.length, true)} found under ${chalk.bold(
contextName
)} ${chalk.gray(lsStamp())}`
);
if (domainsInfo.length > 0) {
if (domains.length > 0) {
output.print(
formatDomainsTable(domainsInfo).replace(/^(.*)/gm, `${' '.repeat(3)}$1`)
formatDomainsTable(domains).replace(/^(.*)/gm, `${' '.repeat(1)}$1`)
);
output.print('\n\n');
}
@@ -105,7 +86,7 @@ export default async function ls(
if (pagination && pagination.count === 20) {
const flags = getCommandFlags(opts, ['_', '--next']);
output.log(
`To display the next page run ${getCommandName(
`To display the next page, run ${getCommandName(
`domains ls${flags} --next ${pagination.next}`
)}`
);
@@ -114,92 +95,26 @@ export default async function ls(
return 0;
}
function createDomainsInfo(domains: Domain[], projects: Project[]) {
const info = new Map<string, DomainInfo>();
domains.forEach(domain => {
info.set(domain.name, {
domain: domain.name,
apexDomain: domain.name,
projectName: null,
expiresAt: domain.expiresAt || null,
createdAt: domain.createdAt,
configured: Boolean(domain.verified),
dns: isDomainExternal(domain) ? 'External' : 'Vercel',
});
projects.forEach(project => {
(project.alias || []).forEach(target => {
if (!target.domain.endsWith(domain.name)) return;
info.set(target.domain, {
domain: target.domain,
apexDomain: domain.name,
projectName: project.name,
expiresAt: domain.expiresAt || null,
createdAt: domain.createdAt || target.createdAt || null,
configured: Boolean(domain.verified),
dns: isDomainExternal(domain) ? 'External' : 'Vercel',
});
});
});
});
projects.forEach(project => {
(project.alias || []).forEach(target => {
if (info.has(target.domain)) return;
const { domain: apexDomain } = psl.parse(
target.domain
) as psl.ParsedDomain;
info.set(target.domain, {
domain: target.domain,
apexDomain: apexDomain || target.domain,
projectName: project.name,
expiresAt: null,
createdAt: target.createdAt || null,
configured: isPublicSuffix(target.domain),
dns: isPublicSuffix(target.domain) ? 'Vercel' : 'External',
});
});
});
const list = Array.from(info.values());
return list.sort((a, b) => {
if (a.apexDomain === b.apexDomain) {
if (a.apexDomain === a.domain) return -1;
if (b.apexDomain === b.domain) return 1;
return a.domain.localeCompare(b.domain);
}
return a.apexDomain.localeCompare(b.apexDomain);
});
}
function formatDomainsTable(domainsInfo: DomainInfo[]) {
function formatDomainsTable(domains: Domain[]) {
const current = Date.now();
const rows: string[][] = domainsInfo.map(info => {
const expiration = formatDateWithoutTime(info.expiresAt);
const age = info.createdAt ? ms(current - info.createdAt) : '-';
const rows: string[][] = domains.map(domain => {
const expiration = formatDateWithoutTime(domain.expiresAt);
const age = domain.createdAt ? ms(current - domain.createdAt) : '-';
return [
info.domain,
info.projectName || '-',
info.dns,
domain.name,
getDomainRegistrar(domain),
isDomainExternal(domain) ? 'Third Party' : 'Vercel',
expiration,
info.configured.toString(),
domain.creator.username,
chalk.gray(age),
];
});
const table = formatTable(
['domain', 'project', 'dns', 'expiration', 'configured', 'age'],
return formatTable(
['Domain', 'Registrar', 'Nameservers', 'Expiration', 'Creator', 'Age'],
['l', 'l', 'l', 'l', 'l', 'l'],
[{ rows }]
);
return table;
}

View File

@@ -71,8 +71,8 @@ export default async function transferIn(
checkTransfer(client, domainName),
]);
if (domainPrice instanceof ERRORS.UnsupportedTLD) {
output.error(`The TLD for ${param(domainName)} is not supported.`);
if (domainPrice instanceof Error) {
output.prettyError(domainPrice);
return 1;
}

View File

@@ -0,0 +1,33 @@
import { NowContext } from '../../types';
import { Output } from '../../util/output';
import { NowBuildError } from '@vercel/build-utils';
import { getCommandName } from '../../util/pkg-name';
export default async function verify(
_ctx: NowContext,
_opts: {},
args: string[],
output: Output
) {
const [domainName] = args;
if (!domainName) {
output.error(
`${getCommandName(`domains verify <domain>`)} expects one argument`
);
return 1;
}
const error = new NowBuildError({
code: 'domains_verify_command_deprecated',
message: `It's not necessary to verify Domains anymore. Instead, you can run ${getCommandName(
`domains inspect ${domainName}`
)} to see what you need to do in order to configure it properly.`,
link: 'https://vercel.link/domain-verification-via-cli',
});
output.prettyError(error);
return 0;
}

View File

@@ -640,6 +640,11 @@ const main = async argv_ => {
return 1;
}
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
output.prettyError(err);
return 1;
}
if (err instanceof APIError && 400 <= err.status && err.status <= 499) {
err.message = err.serverMessage;
output.prettyError(err);

View File

@@ -6,7 +6,7 @@ export default async function deleteCertById(
client: Client,
id: string
) {
return client.fetch(`/v3/now/certs/${id}`, {
method: 'DELETE'
return client.fetch(`/v5/now/certs/${id}`, {
method: 'DELETE',
});
}

View File

@@ -4,7 +4,7 @@ import * as ERRORS from '../errors-ts';
export default async function getCertById(client: Client, id: string) {
try {
return await client.fetch<Cert>(`/v3/now/certs/${id}`);
return await client.fetch<Cert>(`/v5/now/certs/${id}`);
} catch (error) {
if (error.code === 'cert_not_found') {
return new ERRORS.CertNotFound(id);

View File

@@ -1,6 +1,5 @@
import { stringify } from 'querystring';
import { Cert } from '../../types';
import { Output } from '../output';
import * as ERRORS from '../errors-ts';
import Client from '../client';
@@ -8,15 +7,14 @@ type Response = {
certs: Cert[];
};
export default async function getCertsForDomain(
output: Output,
export async function getCustomCertsForDomain(
client: Client,
context: string,
domain: string
) {
try {
const { certs } = await client.fetch<Response>(
`/v3/now/certs?${stringify({ domain })}`
`/v5/now/certs?${stringify({ domain, custom: true })}`
);
return certs;
} catch (error) {

View File

@@ -1578,6 +1578,13 @@ export default class DevServer {
debug(
`Checking build result's ${buildResult.routes.length} \`routes\` to match ${newUrl}`
);
for (const r of buildResult.routes) {
// This replace is necessary for `@vercel/redwood` but might be relevant
// for builders that wish to return routes and work with zero config.
if (r.dest) {
r.dest = r.dest.replace(/\$PORT/g, `${this.devProcessPort}`);
}
}
const matchedRoute = await devRouter(
newUrl,
req.method,

View File

@@ -4,6 +4,8 @@ import { Output } from '../output';
import Client from '../client';
import getDomainDNSRecords from './get-domain-dns-records';
import getDomains from '../domains/get-domains';
import wait from '../output/wait';
import chalk from 'chalk';
export type DomainRecordsItem = {
domainName: string;
@@ -58,6 +60,11 @@ async function getDomainNames(
contextName: string,
next?: number
) {
const { domains, pagination } = await getDomains(client, contextName, next);
return { domainNames: domains.map(domain => domain.name), pagination };
const cancelWait = wait(`Fetching domains under ${chalk.bold(contextName)}`);
try {
const { domains, pagination } = await getDomains(client, next);
return { domainNames: domains.map(domain => domain.name), pagination };
} finally {
cancelWait();
}
}

View File

@@ -38,7 +38,7 @@ export default async function importZonefile(
} catch (error) {
cancelWait();
if (error.code === 'not_found') {
return new DomainNotFound(domain);
return new DomainNotFound(domain, contextName);
}
if (error.code === 'invalid_domain') {

View File

@@ -8,24 +8,26 @@ type Response = {
domain: Domain;
};
async function getDomainByName(
export default async function getDomainByName(
client: Client,
contextName: string,
domainName: string
domainName: string,
options: {
ignoreWait?: boolean;
} = {}
) {
const cancelWait = wait(
`Fetching domain ${domainName} under ${chalk.bold(contextName)}`
);
const cancelWait = options.ignoreWait
? null
: wait(`Fetching domain ${domainName} under ${chalk.bold(contextName)}`);
try {
const { domain } = await client.fetch<Response>(
`/v4/domains/${encodeURIComponent(domainName)}`
);
cancelWait();
return domain;
} catch (error) {
cancelWait();
if (error.status === 404) {
return new DomainNotFound(domainName);
return new DomainNotFound(domainName, contextName);
}
if (error.status === 403) {
@@ -33,7 +35,7 @@ async function getDomainByName(
}
throw error;
} finally {
cancelWait?.();
}
}
export default getDomainByName;

View File

@@ -1,16 +1,7 @@
import chalk from 'chalk';
import Client from '../client';
import wait from '../output/wait';
import { DomainConfig } from '../../types';
export async function getDomainConfig(
client: Client,
contextName: string,
domainName: string
) {
const cancelWait = wait(
`Fetching domain config ${domainName} under ${chalk.bold(contextName)}`
);
export async function getDomainConfig(client: Client, domainName: string) {
try {
const config = await client.fetch<DomainConfig>(
`/v4/domains/${domainName}/config`
@@ -23,7 +14,5 @@ export async function getDomainConfig(
}
throw error;
} finally {
cancelWait();
}
}

View File

@@ -19,6 +19,11 @@ export default async function getDomainPrice(
if (error.code === 'unsupported_tld') {
return new UnsupportedTLD(name);
}
if (error.status < 500) {
return error;
}
throw error;
}
}

View File

@@ -0,0 +1,15 @@
import { Domain } from '../../types';
export type DomainRegistrar = 'Vercel' | 'Purchase in Process' | 'Third Party';
export function getDomainRegistrar(domain: Domain): DomainRegistrar {
if (domain.boughtAt) {
return 'Vercel';
}
if (typeof domain.orderedAt === 'number' && !domain.boughtAt) {
return 'Purchase in Process';
}
return 'Third Party';
}

View File

@@ -1,24 +1,15 @@
import chalk from 'chalk';
import { Domain, PaginationOptions } from '../../types';
import Client from '../client';
import wait from '../output/wait';
type Response = {
domains: Domain[];
pagination: PaginationOptions;
};
export default async function getDomains(
client: Client,
contextName: string,
next?: number
) {
export default async function getDomains(client: Client, next?: number) {
let domainUrl = `/v5/domains?limit=20`;
if (next) {
domainUrl += `&until=${next}`;
}
const cancelWait = wait(`Fetching domains under ${chalk.bold(contextName)}`);
const domains = await client.fetch<Response>(domainUrl);
cancelWait();
return domains;
return await client.fetch<Response>(domainUrl);
}

View File

@@ -30,12 +30,19 @@ export default async function purchaseDomainIfAvailable(
}
output.debug(`Domain ${domain} is available to be purchased`);
const domainPrice = await getDomainPrice(client, domain);
cancelWait();
const domainPrice = await getDomainPrice(client, domain).finally(() => {
cancelWait();
});
if (domainPrice instanceof ERRORS.UnsupportedTLD) {
return domainPrice;
}
if (domainPrice instanceof Error) {
throw domainPrice;
}
const { price, period } = domainPrice;
output.log(
`Domain not found, but you can buy it under ${chalk.bold(
@@ -68,6 +75,5 @@ export default async function purchaseDomainIfAvailable(
}
output.debug(`Domain ${domain} is not available to be purchased`);
cancelWait();
return false;
}

View File

@@ -4,6 +4,7 @@ import { NowBuildError } from '@vercel/build-utils';
import { NowError } from './now-error';
import code from './output/code';
import { getCommandName } from './pkg-name';
import chalk from 'chalk';
/**
* This error is thrown when there is an API error with a payload. The error
@@ -68,7 +69,9 @@ export class InvalidToken extends NowError<'NOT_AUTHORIZED', {}> {
constructor() {
super({
code: `NOT_AUTHORIZED`,
message: `The specified token is not valid`,
message: `The specified token is not valid. Use ${getCommandName(
`login`
)} to generate a new token.`,
meta: {},
});
}
@@ -183,11 +186,13 @@ export class DomainNotFound extends NowError<
'DOMAIN_NOT_FOUND',
{ domain: string }
> {
constructor(domain: string) {
constructor(domain: string, contextName?: string) {
super({
code: 'DOMAIN_NOT_FOUND',
meta: { domain },
message: `The domain ${domain} can't be found.`,
message: `Domain not found by "${domain}"${
contextName ? ` under ${chalk.bold(contextName)}` : ''
}.`,
});
}
}

View File

@@ -28,15 +28,11 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
str: string,
slug: string | null = null,
link: string | null = null,
action: string = 'Learn More'
) {
const prevTerm = process.env.TERM;
if (!prevTerm) {
// workaround for https://github.com/sindresorhus/term-size/issues/13
process.env.TERM = 'xterm';
action: string | null = 'Learn More',
options?: {
boxen?: boxen.Options;
}
) {
const details = slug ? `https://err.sh/now/${slug}` : link;
print(
@@ -52,12 +48,11 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
right: 1,
},
borderColor: 'yellow',
...options?.boxen,
}
)
);
print('\n');
process.env.TERM = prevTerm;
}
function note(str: string) {

View File

@@ -1,6 +1,4 @@
import chalk from 'chalk';
import Client from '../client';
import wait from '../output/wait';
import { Project } from '../../types';
import { URLSearchParams } from 'url';
@@ -8,9 +6,6 @@ export async function findProjectsForDomain(
client: Client,
domainName: string
): Promise<Project[] | Error> {
const cancelWait = wait(
`Searching project for domain ${chalk.bold(domainName)}`
);
try {
const limit = 50;
let result: Project[] = [];
@@ -30,7 +25,7 @@ export async function findProjectsForDomain(
}
const [latest] = response.sort((a, b) => b.updatedAt - a.updatedAt);
query.append('from', latest.updatedAt.toString());
query.set('from', latest.updatedAt.toString());
}
return result;
@@ -40,7 +35,5 @@ export async function findProjectsForDomain(
}
throw err;
} finally {
cancelWait();
}
}

View File

@@ -1,39 +0,0 @@
import Client from '../client';
import wait from '../output/wait';
import { Project } from '../../types';
import { URLSearchParams } from 'url';
export async function getProjectsWithDomains(
client: Client
): Promise<Project[] | Error> {
const cancelWait = wait(`Fetching projects with domains`);
try {
const limit = 50;
let result: Project[] = [];
const query = new URLSearchParams({
hasProductionDomains: '1',
limit: limit.toString(),
});
for (let i = 0; i < 1000; i++) {
const response = await client.fetch<Project[]>(`/v2/projects/?${query}`);
result.push(...response);
const [latest] = response.sort((a, b) => b.updatedAt - a.updatedAt);
query.append('from', latest.updatedAt.toString());
if (response.length !== limit) break;
}
return result;
} catch (err) {
if (err.status < 500) {
return err;
}
throw err;
} finally {
cancelWait();
}
}

View File

@@ -244,11 +244,12 @@ export async function linkFolderToProject(
const gitIgnore = await readFile(gitIgnorePath)
.then(buf => buf.toString())
.catch(() => null);
const EOL = gitIgnore && gitIgnore.includes('\r\n') ? '\r\n' : '\n';
if (!gitIgnore || !gitIgnore.split('\n').includes(VERCEL_DIR)) {
if (!gitIgnore || !gitIgnore.split(EOL).includes(VERCEL_DIR)) {
await writeFile(
gitIgnorePath,
gitIgnore ? `${gitIgnore}\n${VERCEL_DIR}` : VERCEL_DIR
gitIgnore ? `${gitIgnore}${EOL}${VERCEL_DIR}${EOL}` : `${VERCEL_DIR}${EOL}`
);
isGitIgnoreUpdated = true;
}

View File

@@ -20,6 +20,8 @@ let port = 3000;
const binaryPath = resolve(__dirname, `../../scripts/start.js`);
const fixture = name => join('test', 'dev', 'fixtures', name);
const fixtureAbsolute = name => join(__dirname, 'fixtures', name);
const exampleAbsolute = name =>
join(__dirname, '..', '..', '..', '..', 'examples', name);
let processCounter = 0;
const processList = new Map();
@@ -127,9 +129,10 @@ async function testPath(
path,
expectedText,
headers = {},
method = 'GET'
method = 'GET',
body = undefined
) {
const opts = { redirect: 'manual-dont-change', method };
const opts = { redirect: 'manual-dont-change', method, body };
const url = `${origin}${path}`;
const res = await fetch(url, opts);
const msg = `Testing response from ${method} ${url}`;
@@ -230,10 +233,18 @@ async function testFixture(directory, opts = {}, args = []) {
function testFixtureStdio(
directory,
fn,
{ expectedCode = 0, skipDeploy } = {}
{ expectedCode = 0, skipDeploy, isExample } = {}
) {
return async t => {
const cwd = fixtureAbsolute(directory);
const nodeMajor = Number(process.versions.node.split('.')[0]);
if (isExample && nodeMajor < 12) {
console.log(`Skipping ${directory} on Node ${process.version}`);
t.pass();
return;
}
const cwd = isExample
? exampleAbsolute(directory)
: fixtureAbsolute(directory);
const token = await fetchTokenWithRetry();
let deploymentUrl;
@@ -369,6 +380,21 @@ test.afterEach(async () => {
);
});
test(
'[vercel dev] redwoodjs example',
testFixtureStdio(
'redwoodjs',
async testPath => {
await testPath(200, '/', /<div id="redwood-app">/m);
await testPath(200, '/about', /<div id="redwood-app">/m);
const reqBody = '{"query":"{redwood{version}}"}';
const resBody = '{"data":{"redwood":{"version":"0.15.0"}}}';
await testPath(200, '/api/graphql', resBody, {}, 'POST', reqBody);
},
{ isExample: true }
)
);
test('[vercel dev] prints `npm install` errors', async t => {
const dir = fixture('runtime-not-installed');
const result = await exec(dir);
@@ -1149,7 +1175,8 @@ test(
await testPath(200, '/api/date', new RegExp(new Date().getFullYear()));
await testPath(200, '/contact', /Contact Page/);
await testPath(200, '/support', /Contact Page/);
await testPath(404, '/nothing', /Custom Next 404/);
// TODO: Fix this test assertion that fails intermittently
// await testPath(404, '/nothing', /Custom Next 404/);
})
);

View File

@@ -2621,39 +2621,6 @@ test('assign a domain to a project', async t => {
t.is(removeResponse.exitCode, 0, formatOutput(removeResponse));
});
test('list project domains', async t => {
const domain = `project-domain.${contextName}.now.sh`;
const directory = fixture('static-deployment');
const deploymentOutput = await execute([directory, '--public', '--confirm']);
t.is(deploymentOutput.exitCode, 0, formatOutput(deploymentOutput));
const host = deploymentOutput.stdout.trim().replace('https://', '');
const deployment = await apiFetch(
`/v10/now/deployments/unknown?url=${host}`
).then(resp => resp.json());
t.is(typeof deployment.name, 'string', JSON.stringify(deployment, null, 2));
const project = deployment.name;
const addOutput = await execute([
'domains',
'add',
domain,
project,
'--force',
]);
t.is(addOutput.exitCode, 0, formatOutput(addOutput));
const output = await execute(['domains', 'ls']);
t.is(output.exitCode, 0, formatOutput(output));
t.regex(output.stderr, new RegExp(domain), formatOutput(output));
t.regex(output.stderr, new RegExp(project), formatOutput(output));
const removeResponse = await execute(['rm', project, '-y']);
t.is(removeResponse.exitCode, 0, formatOutput(removeResponse));
});
test('ensure `github` and `scope` are not sent to the API', async t => {
const directory = fixture('github-and-scope-config');
const output = await execute([directory, '--confirm']);
@@ -2734,7 +2701,7 @@ test('should show prompts to set up project during first deploy', async t => {
// Ensure .gitignore is created
t.is(
(await readFile(path.join(directory, '.gitignore'))).toString(),
'.vercel'
'.vercel\n'
);
// Ensure .vercel/project.json and .vercel/README.txt are created
@@ -3354,7 +3321,7 @@ test('[vc link] should show prompts to set up project', async t => {
t.is(output.exitCode, 0, formatOutput(output));
// Ensure .gitignore is created
t.is((await readFile(path.join(dir, '.gitignore'))).toString(), '.vercel');
t.is((await readFile(path.join(dir, '.gitignore'))).toString(), '.vercel\n');
// Ensure .vercel/project.json and .vercel/README.txt are created
t.is(
@@ -3388,7 +3355,7 @@ test('[vc link --confirm] should not show prompts and autolink', async t => {
t.regex(stderr, /Linked to /m);
// Ensure .gitignore is created
t.is((await readFile(path.join(dir, '.gitignore'))).toString(), '.vercel');
t.is((await readFile(path.join(dir, '.gitignore'))).toString(), '.vercel\n');
// Ensure .vercel/project.json and .vercel/README.txt are created
t.is(
@@ -3472,7 +3439,7 @@ test('[vc dev] should show prompts to set up project', async t => {
await waitForPrompt(dev, chunk => chunk.includes('Linked to'));
// Ensure .gitignore is created
t.is((await readFile(path.join(dir, '.gitignore'))).toString(), '.vercel');
t.is((await readFile(path.join(dir, '.gitignore'))).toString(), '.vercel\n');
// Ensure .vercel/project.json and .vercel/README.txt are created
t.is(
@@ -3538,7 +3505,7 @@ test('[vc link] should show project prompts but not framework when `builds` defi
t.is(output.exitCode, 0, formatOutput(output));
// Ensure .gitignore is created
t.is((await readFile(path.join(dir, '.gitignore'))).toString(), '.vercel');
t.is((await readFile(path.join(dir, '.gitignore'))).toString(), '.vercel\n');
// Ensure .vercel/project.json and .vercel/README.txt are created
t.is(

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "2.6.15",
"version": "2.6.19-canary.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -26,7 +26,7 @@
"@types/resolve-from": "5.0.1",
"@types/semver": "6.0.0",
"@types/yazl": "2.4.1",
"@zeit/node-file-trace": "0.8.1",
"@zeit/node-file-trace": "0.8.2",
"async-sema": "3.0.1",
"buffer-crc32": "0.2.13",
"escape-string-regexp": "3.0.0",

View File

@@ -1,20 +1,3 @@
import buildUtils from './build-utils';
import url from 'url';
const {
createLambda,
debug,
download,
getLambdaOptionsFromFunction,
getNodeVersion,
getSpawnOptions,
getScriptName,
glob,
runNpmInstall,
runPackageJsonScript,
execCommand,
getNodeBinPath,
} = buildUtils;
import {
BuildOptions,
Config,
@@ -34,13 +17,17 @@ import {
convertRewrites,
} from '@vercel/routing-utils/dist/superstatic';
import { nodeFileTrace, NodeFileTraceReasons } from '@zeit/node-file-trace';
import { Sema } from 'async-sema';
import { ChildProcess, fork } from 'child_process';
import escapeStringRegexp from 'escape-string-regexp';
import findUp from 'find-up';
import { lstat, pathExists, readFile, remove, writeFile } from 'fs-extra';
import os from 'os';
import path from 'path';
import resolveFrom from 'resolve-from';
import semver from 'semver';
import url from 'url';
import buildUtils from './build-utils';
import createServerlessConfig from './create-serverless-config';
import nextLegacyVersions from './legacy-versions';
import {
@@ -66,8 +53,20 @@ import {
syncEnvVars,
validateEntrypoint,
} from './utils';
import findUp from 'find-up';
import { Sema } from 'async-sema';
const {
createLambda,
debug,
download,
getLambdaOptionsFromFunction,
getNodeVersion,
getSpawnOptions,
getScriptName,
glob,
runNpmInstall,
runPackageJsonScript,
execCommand,
getNodeBinPath,
} = buildUtils;
interface BuildParamsMeta {
isDev: boolean | undefined;
@@ -235,7 +234,7 @@ export const build = async ({
await download(files, workPath, meta);
const pkg = await readPackageJson(entryPath);
let pkg = await readPackageJson(entryPath);
const nextVersionRange = await getNextVersionRange(entryPath);
const nodeVersion = await getNodeVersion(entryPath, undefined, config, meta);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
@@ -331,17 +330,17 @@ export const build = async ({
]);
debug('Normalizing package.json');
const packageJson = normalizePackageJson(pkg);
debug('Normalized package.json result: ', packageJson);
await writePackageJson(entryPath, packageJson);
pkg = normalizePackageJson(pkg);
debug('Normalized package.json result: ', pkg);
await writePackageJson(entryPath, pkg);
}
const buildScriptName = getScriptName(pkg, [
let buildScriptName = getScriptName(pkg, [
'vercel-build',
'now-build',
'build',
]);
let { buildCommand } = config;
const { buildCommand } = config;
if (!buildScriptName && !buildCommand) {
console.log(
@@ -349,7 +348,15 @@ export const build = async ({
'If you need to define a different build step, please create a `vercel-build` script in your `package.json` ' +
'(e.g. `{ "scripts": { "vercel-build": "npm run prepare && next build" } }`).'
);
buildCommand = 'next build';
await writePackageJson(entryPath, {
...pkg,
scripts: {
'vercel-build': 'next build',
...pkg.scripts,
},
});
buildScriptName = 'vercel-build';
}
if (process.env.NPM_AUTH_TOKEN) {
@@ -438,7 +445,7 @@ export const build = async ({
for (const dataRoute of routesManifest.dataRoutes) {
const ssgDataRoute =
prerenderManifest.fallbackRoutes[dataRoute.page] ||
prerenderManifest.legacyBlockingRoutes[dataRoute.page];
prerenderManifest.blockingFallbackRoutes[dataRoute.page];
// we don't need to add routes for non-lazy SSG routes since
// they have outputs which would override the routes anyways
@@ -878,7 +885,7 @@ export const build = async ({
initialRevalidate === false &&
!canUsePreviewMode &&
!prerenderManifest.fallbackRoutes[route] &&
!prerenderManifest.legacyBlockingRoutes[route]
!prerenderManifest.blockingFallbackRoutes[route]
) {
// if the 404 page used getStaticProps we need to update static404Page
// since it wasn't populated from the staticPages group
@@ -1123,7 +1130,7 @@ export const build = async ({
src: `^${escapeStringRegexp(outputName).replace(
/\/index$/,
'(/|/index|)'
)}$`,
)}/?$`,
dest: `${path.join('/', currentLambdaGroup.lambdaIdentifier)}`,
headers: {
'x-nextjs-page': outputName,
@@ -1303,7 +1310,7 @@ export const build = async ({
if (!toRender) {
try {
const { pathname } = url.parse(req.url)
toRender = pathname
toRender = pathname.replace(/\\/$/, '')
} catch (_) {
// handle failing to parse url
res.statusCode = 400
@@ -1458,7 +1465,7 @@ export const build = async ({
if (isFallback || isBlocking) {
const pr = isFallback
? prerenderManifest.fallbackRoutes[routeKey]
: prerenderManifest.legacyBlockingRoutes[routeKey];
: prerenderManifest.blockingFallbackRoutes[routeKey];
initialRevalidate = 1; // TODO: should Next.js provide this default?
// @ts-ignore
if (initialRevalidate === false) {
@@ -1549,7 +1556,7 @@ export const build = async ({
Object.keys(prerenderManifest.fallbackRoutes).forEach(route =>
onPrerenderRoute(route, { isBlocking: false, isFallback: true })
);
Object.keys(prerenderManifest.legacyBlockingRoutes).forEach(route =>
Object.keys(prerenderManifest.blockingFallbackRoutes).forEach(route =>
onPrerenderRoute(route, { isBlocking: true, isFallback: false })
);
@@ -1559,7 +1566,7 @@ export const build = async ({
// Dynamic pages for lazy routes should be handled by the lambda flow.
[
...Object.entries(prerenderManifest.fallbackRoutes),
...Object.entries(prerenderManifest.legacyBlockingRoutes),
...Object.entries(prerenderManifest.blockingFallbackRoutes),
].forEach(([, { dataRouteRegex, dataRoute }]) => {
dataRoutes.push({
// Next.js provided data route regex

View File

@@ -1,15 +1,15 @@
import zlib from 'zlib';
import path from 'path';
import { FileFsRef, Files } from '@vercel/build-utils';
import { NowHeader, NowRewrite, Route, Source } from '@vercel/routing-utils';
import { Sema } from 'async-sema';
import crc32 from 'buffer-crc32';
import fs from 'fs-extra';
import path from 'path';
import resolveFrom from 'resolve-from';
import semver from 'semver';
import { ZipFile } from 'yazl';
import crc32 from 'buffer-crc32';
import { Sema } from 'async-sema';
import resolveFrom from 'resolve-from';
import zlib from 'zlib';
import buildUtils from './build-utils';
const { streamToBuffer, Lambda, NowBuildError, isSymbolicLink } = buildUtils;
import { Files, FileFsRef } from '@vercel/build-utils';
import { Route, Source, NowHeader, NowRewrite } from '@vercel/routing-utils';
type stringMap = { [key: string]: string };
@@ -198,7 +198,7 @@ async function getRoutes(
// If default pages dir isn't found check for `src/pages`
if (
!pagesDir &&
fileKeys.some((file) =>
fileKeys.some(file =>
file.startsWith(path.join(entryDirectory, 'src/pages'))
)
) {
@@ -260,7 +260,7 @@ async function getRoutes(
entryDirectory,
dynamicPages,
true
).then((arr) =>
).then(arr =>
arr.map((route: Source) => {
// convert to make entire RegExp match as one group
route.src = route.src
@@ -287,7 +287,7 @@ async function getRoutes(
};
// Only add the route if a page is not already using it
if (!routes.some((r) => (r as Source).src === route.src)) {
if (!routes.some(r => (r as Source).src === route.src)) {
routes.push(route);
}
}
@@ -420,7 +420,7 @@ export async function getDynamicRoutes(
dest: `${!isDev ? path.join('/', entryDirectory, page) : page}${
routeKeys
? `?${Object.keys(routeKeys)
.map((key) => `${routeKeys[key]}=$${key}`)
.map(key => `${routeKeys[key]}=$${key}`)
.join('&')}`
: ''
}`,
@@ -479,13 +479,13 @@ export async function getDynamicRoutes(
});
}
const pageMatchers = getSortedRoutes(dynamicPages).map((pageName) => ({
const pageMatchers = getSortedRoutes(dynamicPages).map(pageName => ({
pageName,
matcher: getRouteRegex && getRouteRegex(pageName).re,
}));
const routes: Source[] = [];
pageMatchers.forEach((pageMatcher) => {
pageMatchers.forEach(pageMatcher => {
// in `vercel dev` we don't need to prefix the destination
const dest = !isDev
? path.join('/', entryDirectory, pageMatcher.pageName)
@@ -693,7 +693,7 @@ export type NextPrerenderedRoutes = {
};
};
legacyBlockingRoutes: {
blockingFallbackRoutes: {
[route: string]: {
routeRegex: string;
dataRoute: string;
@@ -797,7 +797,7 @@ export async function getPrerenderManifest(
if (!hasManifest) {
return {
staticRoutes: {},
legacyBlockingRoutes: {},
blockingFallbackRoutes: {},
fallbackRoutes: {},
bypassToken: null,
omittedRoutes: [],
@@ -855,14 +855,14 @@ export async function getPrerenderManifest(
const ret: NextPrerenderedRoutes = {
staticRoutes: {},
legacyBlockingRoutes: {},
blockingFallbackRoutes: {},
fallbackRoutes: {},
bypassToken:
(manifest.preview && manifest.preview.previewModeId) || null,
omittedRoutes: [],
};
routes.forEach((route) => {
routes.forEach(route => {
const {
initialRevalidateSeconds,
dataRoute,
@@ -878,7 +878,7 @@ export async function getPrerenderManifest(
};
});
lazyRoutes.forEach((lazyRoute) => {
lazyRoutes.forEach(lazyRoute => {
const {
routeRegex,
fallback,
@@ -894,7 +894,7 @@ export async function getPrerenderManifest(
dataRouteRegex,
};
} else {
ret.legacyBlockingRoutes[lazyRoute] = {
ret.blockingFallbackRoutes[lazyRoute] = {
routeRegex,
dataRoute,
dataRouteRegex,
@@ -910,13 +910,13 @@ export async function getPrerenderManifest(
const ret: NextPrerenderedRoutes = {
staticRoutes: {},
legacyBlockingRoutes: {},
blockingFallbackRoutes: {},
fallbackRoutes: {},
bypassToken: manifest.preview.previewModeId,
omittedRoutes: [],
};
routes.forEach((route) => {
routes.forEach(route => {
const {
initialRevalidateSeconds,
dataRoute,
@@ -932,7 +932,7 @@ export async function getPrerenderManifest(
};
});
lazyRoutes.forEach((lazyRoute) => {
lazyRoutes.forEach(lazyRoute => {
const {
routeRegex,
fallback,
@@ -940,19 +940,24 @@ export async function getPrerenderManifest(
dataRouteRegex,
} = manifest.dynamicRoutes[lazyRoute];
if (!fallback) {
if (typeof fallback === 'string') {
ret.fallbackRoutes[lazyRoute] = {
routeRegex,
fallback,
dataRoute,
dataRouteRegex,
};
} else if (fallback === null) {
ret.blockingFallbackRoutes[lazyRoute] = {
routeRegex,
dataRoute,
dataRouteRegex,
};
} else {
// Fallback behavior is disabled, all routes would've been provided
// in the top-level `routes` key (`staticRoutes`).
ret.omittedRoutes.push(lazyRoute);
return;
}
ret.fallbackRoutes[lazyRoute] = {
routeRegex,
fallback,
dataRoute,
dataRouteRegex,
};
});
return ret;
@@ -960,7 +965,7 @@ export async function getPrerenderManifest(
default: {
return {
staticRoutes: {},
legacyBlockingRoutes: {},
blockingFallbackRoutes: {},
fallbackRoutes: {},
bypassToken: null,
omittedRoutes: [],

View File

@@ -1 +1,6 @@
module.exports = { trailingSlash: true };
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
trailingSlash: true,
};

View File

@@ -33,6 +33,48 @@
"path": "/test.txt",
"status": 200,
"mustContain": "this is a file"
},
{
"fetchOptions": { "redirect": "manual" },
"path": "/blog/post-1",
"status": 308,
"responseHeaders": {
"refresh": "/url=/blog/post-1/$/"
}
},
{
"fetchOptions": { "redirect": "manual" },
"path": "/blog/post-1/",
"status": 200,
"mustContain": "post: <!-- -->post-1"
},
{
"fetchOptions": { "redirect": "manual" },
"path": "/_next/data/testing-build-id/blog/post-1.json/",
"status": 308,
"responseHeaders": {
"refresh": "/url=/_next/data/testing-build-id/blog/post-1.json$/"
}
},
{
"fetchOptions": { "redirect": "manual" },
"path": "/_next/data/testing-build-id/blog/post-1.json",
"status": 200,
"mustContain": "\"post-1\""
},
{
"fetchOptions": { "redirect": "manual" },
"path": "/api/hello",
"status": 308,
"responseHeaders": {
"refresh": "/url=/api/hello/$/"
}
},
{
"fetchOptions": { "redirect": "manual" },
"path": "/api/hello/",
"status": 200,
"mustContain": "hello from API"
}
]
}

View File

@@ -1,3 +1,11 @@
export default function Page() {
return <p>nested page</p>;
}
export const getServerSideProps = () => {
return {
props: {
hello: 'world',
},
};
};

View File

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

View File

@@ -0,0 +1,11 @@
export default function Page({ post }) {
return <p>post: {post}</p>;
}
export const getServerSideProps = ({ params }) => {
return {
props: {
post: params.post,
},
};
};

View File

@@ -0,0 +1,108 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# IDEA
.idea/
*.iml

View File

@@ -0,0 +1,7 @@
module.exports = api => {
api.cache(true);
const presets = [require.resolve('next/babel')];
return { presets, plugins: [] };
};

View File

@@ -0,0 +1,31 @@
{
"version": 2,
"uploadNowJson": true,
"builds": [{ "src": "packages/www/package.json", "use": "@vercel/next" }],
"routes": [{ "src": "/(.*)", "dest": "/packages/www/$1" }],
"probes": [
{
"path": "/",
"status": 200,
"mustContain": "Hello World"
},
{
"logMustContain": "Your application is being built using `next build`"
},
{
"logMustContain": "WARNING: your application is being opted out of @vercel/next's optimized lambdas mode due to legacy routes"
},
{
"logMustNotContain": "WARNING: Your application is being opted out of \"@vercel/next\" optimized lambdas mode due to `functions` config"
},
{
"logMustNotContain": "Traced Next.js serverless functions for external files in"
},
{
"logMustNotContain": "All serverless functions created in"
},
{
"logMustNotContain": "Compressed shared serverless function files"
}
]
}

View File

@@ -0,0 +1,11 @@
{
"private": true,
"workspaces": [
"packages/*"
],
"dependencies": {
"next": "9.5.1",
"react": "16.13.1",
"react-dom": "16.13.1"
}
}

View File

@@ -0,0 +1,8 @@
module.exports = {
poweredByHeader: false,
webpack: (config, { defaultLoaders }) => {
defaultLoaders.babel.options.rootMode = 'upward';
return config;
},
};

View File

@@ -0,0 +1,6 @@
{
"name": "@vercel-crash-demo/www",
"version": "1.0.0",
"private": true,
"sideEffects": false
}

View File

@@ -0,0 +1,7 @@
import React from 'react';
const HelloWorld = () => (
<h1>Hello World</h1>
);
export default HelloWorld;

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -0,0 +1,62 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
],
"probes": [
{
"path": "/fixed/yellow",
"status": 200,
"mustContain": "yellow"
},
{ "delay": 2000 },
{
"path": "/fixed/yellow",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "HIT"
}
},
{
"path": "/_next/data/testing-build-id/fixed/yellow.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "HIT"
}
},
{
"path": "/fixed/yellow",
"status": 200,
"mustContain": "yellow"
},
{
"path": "/_next/data/testing-build-id/fixed/yellow.json",
"status": 200,
"mustContain": "yellow"
},
{
"path": "/regenerated/blue",
"status": 200,
"mustContain": "blue"
},
{ "delay": 2000 },
{
"path": "/regenerated/blue",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/HIT|STALE/"
}
},
{
"path": "/_next/data/testing-build-id/regenerated/blue.json",
"status": 200,
"responseHeaders": {
"x-vercel-cache": "/HIT|STALE/"
}
}
]
}

View File

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

View File

@@ -0,0 +1,23 @@
export default function TestPage({ slug, time }) {
return (
<>
Slug: <div id="slug">{slug}</div>
<br />
Time: <div id="time">{time}</div>
</>
);
}
export function getStaticProps({ params }) {
return {
props: {
slug: params.slug,
time: new Date().getTime(),
},
revalidate: false,
};
}
export function getStaticPaths() {
return { paths: [], fallback: 'unstable_blocking' };
}

View File

@@ -0,0 +1,23 @@
export default function TestPage({ slug, time }) {
return (
<>
Slug: <div id="slug">{slug}</div>
<br />
Time: <div id="time">{time}</div>
</>
);
}
export function getStaticProps({ params }) {
return {
props: {
slug: params.slug,
time: new Date().getTime(),
},
revalidate: true,
};
}
export function getStaticPaths() {
return { paths: [], fallback: 'unstable_blocking' };
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "1.7.4",
"version": "1.7.5-canary.1",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -33,7 +33,7 @@
"@types/etag": "1.8.0",
"@types/test-listen": "1.1.0",
"@zeit/ncc": "0.20.4",
"@zeit/node-file-trace": "0.8.1",
"@zeit/node-file-trace": "0.8.2",
"content-type": "1.0.4",
"cookie": "0.4.0",
"etag": "1.8.1",

View File

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

View File

@@ -218,18 +218,42 @@ function replaceSegments(
return destination;
}
function safelyCompile(str: string, indexes: { [k: string]: string }): string {
if (!str) {
return str;
function safelyCompile(
value: string,
indexes: { [k: string]: string }
): string {
if (!value) {
return value;
}
// path-to-regexp cannot compile question marks
return str
.split('?')
.map(part => {
const compiler = compile(part);
return compiler(indexes);
})
.join('?');
for (const key of Object.keys(indexes)) {
if (value.includes(`:${key}`)) {
value = value
.replace(
new RegExp(`:${key}\\*`, 'g'),
`:${key}--ESCAPED_PARAM_ASTERISK`
)
.replace(
new RegExp(`:${key}\\?`, 'g'),
`:${key}--ESCAPED_PARAM_QUESTION`
)
.replace(new RegExp(`:${key}\\+`, 'g'), `:${key}--ESCAPED_PARAM_PLUS`)
.replace(
new RegExp(`:${key}(?!\\w)`, 'g'),
`--ESCAPED_PARAM_COLON${key}`
);
}
}
value = value
.replace(/(:|\*|\?|\+|\(|\)|\{|\})/g, '\\$1')
.replace(/--ESCAPED_PARAM_PLUS/g, '+')
.replace(/--ESCAPED_PARAM_COLON/g, ':')
.replace(/--ESCAPED_PARAM_QUESTION/g, '?')
.replace(/--ESCAPED_PARAM_ASTERISK/g, '*');
// the value needs to start with a forward-slash to be compiled
// correctly
return compile(`/${value}`, { validate: false })(indexes).substr(1);
}
function toSegmentDest(index: number): string {

View File

@@ -508,6 +508,60 @@ test('convertHeaders', () => {
},
],
},
{
source: '/like/params/:path',
headers: [
{
key: 'x-path',
value: ':path',
},
{
key: 'some:path',
value: 'hi',
},
{
key: 'x-test',
value: 'some:value*',
},
{
key: 'x-test-2',
value: 'value*',
},
{
key: 'x-test-3',
value: ':value?',
},
{
key: 'x-test-4',
value: ':value+',
},
{
key: 'x-test-5',
value: 'something https:',
},
{
key: 'x-test-6',
value: ':hello(world)',
},
{
key: 'x-test-7',
value: 'hello(world)',
},
{
key: 'x-test-8',
value: 'hello{1,}',
},
{
key: 'x-test-9',
value: ':hello{1,2}',
},
{
key: 'content-security-policy',
value:
"default-src 'self'; img-src *; media-src media1.com media2.com; script-src userscripts.example.com/:path",
},
],
},
]);
const expected = [
@@ -526,6 +580,25 @@ test('convertHeaders', () => {
headers: { 'on-blog': '$1', $1: 'blog' },
continue: true,
},
{
continue: true,
headers: {
'content-security-policy':
"default-src 'self'; img-src *; media-src media1.com media2.com; script-src userscripts.example.com/$1",
some$1: 'hi',
'x-path': '$1',
'x-test': 'some:value*',
'x-test-2': 'value*',
'x-test-3': ':value?',
'x-test-4': ':value+',
'x-test-5': 'something https:',
'x-test-6': ':hello(world)',
'x-test-7': 'hello(world)',
'x-test-8': 'hello{1,}',
'x-test-9': ':hello{1,2}',
},
src: '^\\/like\\/params(?:\\/([^\\/]+?))$',
},
];
deepEqual(actual, expected);
@@ -534,12 +607,14 @@ test('convertHeaders', () => {
['hello/world/file.eot', 'another/font.ttf', 'dir/arial.font.css'],
['404.html'],
['/blog/first-post', '/blog/another/one'],
['/like/params/first', '/like/params/second'],
];
const mustNotMatch = [
['hello/file.jpg', 'hello/font-css', 'dir/arial.font-css'],
['403.html', '500.html'],
['/blogg', '/random'],
['/non-match', '/like/params', '/like/params/'],
];
assertRegexMatches(actual, mustMatch, mustNotMatch);

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/redwood",
"version": "0.0.2-canary.1",
"version": "0.0.2-canary.3",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs",
@@ -18,8 +18,7 @@
"prepublishOnly": "./build.sh"
},
"dependencies": {
"@netlify/zip-it-and-ship-it": "1.2.0",
"is-port-reachable": "3.0.0"
"@netlify/zip-it-and-ship-it": "1.2.0"
},
"devDependencies": {
"@types/aws-lambda": "8.10.19",

View File

@@ -1,5 +1,4 @@
import { join, dirname, relative, parse as parsePath, sep } from 'path';
import { ChildProcess, SpawnOptions } from 'child_process';
import {
BuildOptions,
Lambda,
@@ -13,54 +12,22 @@ import {
getSpawnOptions,
runNpmInstall,
execCommand,
spawnCommand,
readConfigFile,
FileBlob,
FileFsRef,
NowBuildError,
} from '@vercel/build-utils';
import { makeAwsLauncher } from './launcher';
const {
getDependencies,
// eslint-disable-next-line @typescript-eslint/no-var-requires
} = require('@netlify/zip-it-and-ship-it/src/dependencies.js');
//@ts-ignore
import isPortReachable from 'is-port-reachable';
interface RedwoodConfig {
web?: {
port?: number;
apiProxyPath?: string;
};
api?: {
port?: number;
};
browser?: {
open?: boolean;
};
}
const LAUNCHER_FILENAME = '___vc_launcher';
const BRIDGE_FILENAME = '___vc_bridge';
const HELPERS_FILENAME = '___vc_helpers';
const SOURCEMAP_SUPPORT_FILENAME = '__vc_sourcemap_support';
const entrypointToPort = new Map<string, number>();
const childProcesses = new Set<ChildProcess>();
export const version = 2;
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
async function waitForPort(port: number): Promise<boolean> {
for (let i = 0; i < 500; i++) {
if (await isPortReachable(port)) {
return true;
}
await sleep(100);
}
return false;
}
export async function build({
workPath,
files,
@@ -89,46 +56,10 @@ export async function build({
const {
buildCommand = 'yarn rw db up --no-db-client --auto-approve && yarn rw build',
devCommand = 'yarn rw dev',
} = config;
if (meta.isDev) {
const toml = await readConfigFile<RedwoodConfig>(
join(mountpoint, 'redwood.toml')
);
const webPort = toml?.web?.port || 8910;
const apiPort = toml?.web?.port || 8911;
let devPort = entrypointToPort.get(entrypoint);
if (typeof devPort === 'number') {
debug('`%s` server already running for %j', devCommand, entrypoint);
} else {
devPort = webPort;
entrypointToPort.set(entrypoint, devPort);
const opts: SpawnOptions = {
cwd: mountpoint,
stdio: 'inherit',
env: { ...spawnOpts.env, PORT: String(devPort) },
};
const child = spawnCommand(devCommand, opts);
child.on('exit', () => entrypointToPort.delete(entrypoint));
childProcesses.add(child);
const found = await waitForPort(devPort);
if (!found) {
throw new NowBuildError({
code: 'REDWOOD_PORT_UNAVAILABLE',
message: `Failed to detect a server running on port ${devPort}`,
action: 'More Details',
link:
'https://err.sh/vercel/vercel/now-static-build-failed-to-detect-a-server',
});
}
debug('Detected dev server for %j', entrypoint);
}
debug('Detected @vercel/redwood dev, returning routes...');
let srcBase = mountpoint.replace(/^\.\/?/, '');
@@ -138,16 +69,11 @@ export async function build({
return {
routes: [
{
src: `${srcBase}/api/(.*)`,
dest: `http://localhost:${apiPort}/$1`,
},
{
src: `${srcBase}/(.*)`,
dest: `http://localhost:${webPort}/$1`,
dest: `http://localhost:$PORT/$1`,
},
],
watch: [join(srcBase, '**/*')],
output: {},
};
}

View File

@@ -3,6 +3,6 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@redwoodjs/api": "0.14.0"
"@redwoodjs/api": "0.15.0"
}
}

View File

@@ -7,7 +7,7 @@
]
},
"devDependencies": {
"@redwoodjs/core": "0.14.0"
"@redwoodjs/core": "0.15.0"
},
"engines": {
"node": ">=12",

View File

@@ -14,7 +14,7 @@
"method": "POST",
"headers": { "Accept": "application/json" },
"body": { "query": "{ redwood { version } }" },
"mustContain": "0.14.0"
"mustContain": "0.15.0"
}
]
}

View File

@@ -6,8 +6,8 @@
"defaults"
],
"dependencies": {
"@redwoodjs/router": "0.14.0",
"@redwoodjs/web": "0.14.0",
"@redwoodjs/router": "0.15.0",
"@redwoodjs/web": "0.15.0",
"prop-types": "^15.7.2",
"react": "^16.13.1",
"react-dom": "^16.13.1"

File diff suppressed because it is too large Load Diff

View File

@@ -2292,10 +2292,10 @@
resolved "https://registry.yarnpkg.com/@zeit/ncc/-/ncc-0.20.4.tgz#00f0a25a88cac3712af4ba66561d9e281c6f05c9"
integrity sha512-fmq+F/QxPec+k/zvT7HiVpk7oiGFseS6brfT/AYqmCUp6QFRK7vZf2Ref46MImsg/g2W3g5X6SRvGRmOAvEfdA==
"@zeit/node-file-trace@0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@zeit/node-file-trace/-/node-file-trace-0.8.1.tgz#ace86b366c86f0522e953a2441dfc915fd485df4"
integrity sha512-r92pdExhK1cf9Ao5LaT/Ii7JLwv9O4o987cB1SiFIbAnxvW4+xqR1oCYRRvtjU1ildKKGpiBBjGRChyeYRcvxw==
"@zeit/node-file-trace@0.8.2":
version "0.8.2"
resolved "https://registry.yarnpkg.com/@zeit/node-file-trace/-/node-file-trace-0.8.2.tgz#a00d21a98015c4ea18c8b1104ad60ea91b42dcca"
integrity sha512-M6KR95Xz9af8kB8X7e4inhoIjVoKNT6WxjLQwPByAAdCP6JdCg3Fb0dbTh2WelKlAibUpfS9nANU/HUDcfedSA==
dependencies:
acorn "^7.1.1"
acorn-class-fields "^0.3.2"