Compare commits

..

14 Commits

Author SHA1 Message Date
JJ Kasper
895224985b Publish Canary
- @vercel/next@2.7.1-canary.0
2020-11-14 16:23:24 -06:00
JJ Kasper
0f42a63c03 [next] Ensure correct route order for i18n + custom-routes (#5421)
Co-authored-by: Steven <steven@ceriously.com>
2020-11-14 17:13:08 -05:00
dav-is
81e4c9e6fe Publish Canary
- @vercel/routing-utils@1.9.2-canary.1
2020-11-13 13:48:28 -05:00
Connor Davis
a0a29dc836 [routing-utils] Exclude /.well-known from trailing slash redirect (#5407)
`/.well-known` files shouldn't have trailing slashes added as these clients likely do not follow redirects

Specifically, `/.well-known/apple-developer-merchantid-domain-association` cannot have trailing slash.

### Related Issues

https://vercel.slack.com/archives/CLDDX2Y0G/p1605127589058800

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [x] Issue from task tracker has a link to this PR
2020-11-12 22:38:01 +00:00
luc
c1f9d51d7a Publish Canary
- vercel@20.1.5-canary.1
2020-11-12 22:06:16 +01:00
Luc Leray
422f0558c1 [cli] Rename "Provided by system" to "Reference ..." (#5415)
* Revert "Revert "[cli] (major) Update `vercel env` (#5372)" (#5410)"

This reverts commit 2d24a75ca6.

* fetch system env values from dedicated endpoint

* rename "Provided by System"
2020-11-12 22:05:24 +01:00
luc
f064ae2908 Publish Canary
- vercel@20.1.5-canary.0
 - @vercel/routing-utils@1.9.2-canary.0
2020-11-12 21:07:28 +01:00
Luc Leray
58c3e636f0 [cli] (Major) Update vc env (#5413)
* Revert "Revert "[cli] (major) Update `vercel env` (#5372)" (#5410)"

This reverts commit 2d24a75ca6.

* fetch system env values from dedicated endpoint
2020-11-12 21:06:25 +01:00
Connor Davis
d5081367f3 [routing-utils] Add important headers to disallow users from overriding (#5409)
If a builder wants to set a header that isn't allowed to be overridden by users, it should use `important: true`

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [x] Issue from task tracker has a link to this PR: CH-14921
2020-11-12 17:45:33 +00:00
luc
0ee88366ff Publish Stable
- vercel@20.1.4
2020-11-12 17:16:29 +01:00
luc
9ae42c9e92 Publish Canary
- vercel@20.1.4-canary.1
2020-11-12 16:59:28 +01:00
Luc Leray
62b8df4a8d [cli] Fix vc env rm with advanced env variables (#5411) 2020-11-12 16:58:41 +01:00
luc
73ec7f3018 Publish Canary
- vercel@20.1.4-canary.0
2020-11-12 15:31:54 +01:00
Luc Leray
2d24a75ca6 Revert "[cli] (major) Update vercel env (#5372)" (#5410)
* Revert "[cli] (major) Update `vercel env` (#5372)"

This reverts commit 9a57cc72dd.

* fix test

* do not change prompt UI
2020-11-12 15:30:47 +01:00
14 changed files with 443 additions and 76 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "20.1.3",
"version": "20.1.5-canary.1",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",

View File

@@ -18,7 +18,7 @@ import withSpinner from '../../util/with-spinner';
import { emoji, prependEmoji } from '../../util/emoji';
import { isKnownError } from '../../util/env/known-error';
import { getCommandName } from '../../util/pkg-name';
import { SYSTEM_ENV_VALUES } from '../../util/env/system-env';
import getSystemEnvValues from '../../util/env/get-system-env-values';
type Options = {
'--debug': boolean;
@@ -91,7 +91,10 @@ export default async function add(
name: `Secret (can be created using ${getCommandName('secret add')})`,
value: ProjectEnvType.Secret,
},
{ name: 'Provided by System', value: ProjectEnvType.System },
{
name: 'Reference to System Environment Variable',
value: ProjectEnvType.System,
},
],
})) as { inputEnvType: ProjectEnvType };
@@ -112,7 +115,10 @@ export default async function add(
}
}
const { envs } = await getEnvVariables(output, client, project.id);
const [{ envs }, { systemEnvValues }] = await Promise.all([
getEnvVariables(output, client, project.id),
getSystemEnvValues(output, client, project.id),
]);
const existing = new Set(
envs.filter(r => r.key === envName).map(r => r.target)
);
@@ -182,7 +188,7 @@ export default async function add(
name: 'systemEnvValue',
type: 'list',
message: `Whats the value of ${envName}?`,
choices: SYSTEM_ENV_VALUES.map(value => ({ name: value, value })),
choices: systemEnvValues.map(value => ({ name: value, value })),
});
envValue = systemEnvValue;

View File

@@ -0,0 +1,12 @@
import { Output } from '../output';
import Client from '../client';
export default async function getSystemEnvValues(
output: Output,
client: Client,
projectId: string
) {
output.debug(`Fetching System Environment Values of project ${projectId}`);
const url = `/v6/projects/${projectId}/system-env-values`;
return client.fetch<{ systemEnvValues: string[] }>(url);
}

View File

@@ -1,32 +0,0 @@
export const SYSTEM_ENV_VALUES = [
'VERCEL_URL',
'VERCEL_GITHUB_COMMIT_ORG',
'VERCEL_GITHUB_COMMIT_REF',
'VERCEL_GITHUB_ORG',
'VERCEL_GITHUB_DEPLOYMENT',
'VERCEL_GITHUB_COMMIT_REPO',
'VERCEL_GITHUB_REPO',
'VERCEL_GITHUB_COMMIT_AUTHOR_LOGIN',
'VERCEL_GITHUB_COMMIT_AUTHOR_NAME',
'VERCEL_GITHUB_COMMIT_SHA',
'VERCEL_GITLAB_DEPLOYMENT',
'VERCEL_GITLAB_PROJECT_NAMESPACE',
'VERCEL_GITLAB_PROJECT_NAME',
'VERCEL_GITLAB_PROJECT_ID',
'VERCEL_GITLAB_PROJECT_PATH',
'VERCEL_GITLAB_COMMIT_REF',
'VERCEL_GITLAB_COMMIT_SHA',
'VERCEL_GITLAB_COMMIT_MESSAGE',
'VERCEL_GITLAB_COMMIT_AUTHOR_LOGIN',
'VERCEL_GITLAB_COMMIT_AUTHOR_NAME',
'VERCEL_BITBUCKET_DEPLOYMENT',
'VERCEL_BITBUCKET_REPO_OWNER',
'VERCEL_BITBUCKET_REPO_SLUG',
'VERCEL_BITBUCKET_REPO_NAME',
'VERCEL_BITBUCKET_COMMIT_REF',
'VERCEL_BITBUCKET_COMMIT_SHA',
'VERCEL_BITBUCKET_COMMIT_MESSAGE',
'VERCEL_BITBUCKET_COMMIT_AUTHOR_NAME',
'VERCEL_BITBUCKET_COMMIT_AUTHOR_URL',
'VERCEL_BITBUCKET_COMMIT_AUTHOR_AVATAR',
];

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "2.7.0",
"version": "2.7.1-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",

View File

@@ -448,7 +448,7 @@ export async function build({
const prerenderManifest = await getPrerenderManifest(entryPath);
const headers: Route[] = [];
const rewrites: Route[] = [];
const redirects: Route[] = [];
let redirects: Route[] = [];
const dataRoutes: Route[] = [];
let dynamicRoutes: Route[] = [];
// whether they have enabled pages/404.js as the custom 404 page
@@ -2020,6 +2020,31 @@ export async function build({
const { i18n } = routesManifest || {};
let trailingSlashRedirect: Route | undefined;
if (i18n) {
redirects = redirects.filter(_redir => {
const redir = _redir as Source;
// detect the trailing slash redirect and make sure it's
// kept above the wildcard mapping to prevent erroneous redirects
// since non-continue routes come after continue the $wildcard
// route will come before the redirect otherwise and if the
// redirect is triggered it breaks locale mapping
const location =
redir.headers && (redir.headers.location || redir.headers.Location);
if (redir.status === 308 && (location === '/$1' || location === '/$1/')) {
// we set continue here to prevent the redirect from
// moving underneath i18n routes
redir.continue = true;
trailingSlashRedirect = redir;
return false;
}
return true;
});
}
return {
output: {
...publicDirectoryFiles,
@@ -2059,36 +2084,15 @@ export async function build({
- Builder rewrites
*/
routes: [
// headers
...headers,
// redirects
...redirects.map(_redir => {
if (i18n) {
const redir = _redir as Source;
// detect the trailing slash redirect and make sure it's
// kept above the wildcard mapping to prevent erroneous redirects
// since non-continue routes come after continue the $wildcard
// route will come before the redirect otherwise and if the
// redirect is triggered it breaks locale mapping
const location =
redir.headers && (redir.headers.location || redir.headers.Location);
if (
redir.status === 308 &&
(location === '/$1' || location === '/$1/')
) {
// we set continue true
redir.continue = true;
}
}
return _redir;
}),
// force trailingSlashRedirect to the very top so it doesn't
// conflict with i18n routes that don't have or don't have the
// trailing slash
...(trailingSlashRedirect ? [trailingSlashRedirect] : []),
...(i18n
? [
// Handle auto-adding current default locale to path based on $wildcard
// Handle auto-adding current default locale to path based on
// $wildcard
{
src: `^${path.join(
'/',
@@ -2097,8 +2101,8 @@ export async function build({
)}(?!(?:_next/.*|${i18n.locales
.map(locale => escapeStringRegexp(locale))
.join('|')})(?:/.*|$))(.*)$`,
// TODO: this needs to contain or not contain a trailing slash
// to prevent the trailing slash redirect from being triggered
// we aren't able to ensure trailing slash mode here
// so ensure this comes after the trailing slash redirect
dest: '$wildcard/$1',
continue: true,
},
@@ -2107,8 +2111,6 @@ export async function build({
...(i18n.domains && i18n.localeDetection !== false
? [
{
// TODO: enable redirecting between domains, will require
// updating the src with the desired locales to redirect
src: `^${path.join(
'/',
entryDirectory
@@ -2144,10 +2146,10 @@ export async function build({
...(i18n.localeDetection !== false
? [
{
// TODO: if default locale is included in this src it won't be
// visitable by users who prefer another language since a
// cookie isn't set signaling the default locale is preferred
// on redirect currently, investigate adding this
// TODO: if default locale is included in this src it won't
// be visitable by users who prefer another language since a
// cookie isn't set signaling the default locale is
// preferred on redirect currently, investigate adding this
src: '/',
locale: {
redirect: i18n.locales.reduce(
@@ -2190,6 +2192,12 @@ export async function build({
]
: []),
// headers
...headers,
// redirects
...redirects,
// Make sure to 404 for the /404 path itself
...(i18n
? [

View File

@@ -18,4 +18,81 @@ module.exports = {
},
],
},
async redirects() {
return [
{
source: '/en-US/redirect-1',
destination: '/somewhere-else',
permanent: false,
locale: false,
},
{
source: '/nl/redirect-2',
destination: '/somewhere-else',
permanent: false,
locale: false,
},
{
source: '/redirect-3',
destination: '/somewhere-else',
permanent: false,
},
];
},
async rewrites() {
return [
{
source: '/en-US/rewrite-1',
destination: '/another',
locale: false,
},
{
source: '/nl/rewrite-2',
destination: '/nl/another',
locale: false,
},
{
source: '/fr/rewrite-3',
destination: '/nl/another',
locale: false,
},
{
source: '/rewrite-4',
destination: '/another',
},
];
},
async headers() {
return [
{
source: '/en-US/add-header-1',
locale: false,
headers: [
{
key: 'x-hello',
value: 'world',
},
],
},
{
source: '/nl/add-header-2',
locale: false,
headers: [
{
key: 'x-hello',
value: 'world',
},
],
},
{
source: '/add-header-3',
headers: [
{
key: 'x-hello',
value: 'world',
},
],
},
];
},
};

View File

@@ -520,6 +520,280 @@
"path": "/_next/data/testing-build-id/fr/gsp/blocking/first.json",
"status": 200,
"mustContain": "\"catchall\":\"yes\""
},
{
"path": "/en-US/redirect-1",
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//somewhere-else/"
}
},
{
"path": "/en/redirect-1",
"fetchOptions": {
"redirect": "manual"
},
"status": 404
},
{
"path": "/nl/redirect-2",
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//somewhere-else/"
}
},
{
"path": "/en-US/redirect-2",
"fetchOptions": {
"redirect": "manual"
},
"status": 404
},
{
"path": "/redirect-3",
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//somewhere-else/"
}
},
{
"path": "/en-US/redirect-3",
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//somewhere-else/"
}
},
{
"path": "/fr/redirect-3",
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//somewhere-else/"
}
},
{
"path": "/nl-NL/redirect-3",
"fetchOptions": {
"redirect": "manual"
},
"status": 307,
"responseHeaders": {
"location": "//somewhere-else/"
}
},
{
"path": "/en-US/rewrite-1",
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "another page"
},
{
"path": "/en-US/rewrite-1",
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "lang=\"en-US\""
},
{
"path": "/nl/rewrite-1",
"fetchOptions": {
"redirect": "manual"
},
"status": 404
},
{
"path": "/nl/rewrite-2",
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "another page"
},
{
"path": "/nl/rewrite-2",
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "lang=\"nl\""
},
{
"path": "/rewrite-2",
"fetchOptions": {
"redirect": "manual"
},
"status": 404
},
{
"path": "/fr/rewrite-3",
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "another page"
},
{
"path": "/fr/rewrite-3",
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "lang=\"nl\""
},
{
"path": "/rewrite-3",
"fetchOptions": {
"redirect": "manual"
},
"status": 404
},
{
"path": "/rewrite-4",
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "another page"
},
{
"path": "/rewrite-4",
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "lang=\"en-US\""
},
{
"path": "/en/rewrite-4",
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "another page"
},
{
"path": "/en/rewrite-4",
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "lang=\"en\""
},
{
"path": "/fr/rewrite-4",
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "another page"
},
{
"path": "/fr/rewrite-4",
"fetchOptions": {
"redirect": "manual"
},
"status": 200,
"mustContain": "lang=\"fr\""
},
{
"path": "/en-US/add-header-1",
"fetchOptions": {
"redirect": "manual"
},
"status": 404,
"responseHeaders": {
"x-hello": "world"
}
},
{
"path": "/en/add-header-1",
"fetchOptions": {
"redirect": "manual"
},
"status": 404,
"responseHeaders": {
"x-hello": null
}
},
{
"path": "/nl/add-header-2",
"fetchOptions": {
"redirect": "manual"
},
"status": 404,
"responseHeaders": {
"x-hello": "world"
}
},
{
"path": "/en-US/add-header-2",
"fetchOptions": {
"redirect": "manual"
},
"status": 404,
"responseHeaders": {
"x-hello": null
}
},
{
"path": "/add-header-3",
"fetchOptions": {
"redirect": "manual"
},
"status": 404,
"responseHeaders": {
"x-hello": "world"
}
},
{
"path": "/en-US/add-header-3",
"fetchOptions": {
"redirect": "manual"
},
"status": 404,
"responseHeaders": {
"x-hello": "world"
}
},
{
"path": "/fr/add-header-3",
"fetchOptions": {
"redirect": "manual"
},
"status": 404,
"responseHeaders": {
"x-hello": "world"
}
},
{
"path": "/nl-NL/add-header-3",
"fetchOptions": {
"redirect": "manual"
},
"status": 404,
"responseHeaders": {
"x-hello": "world"
}
}
]
}

View File

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

View File

@@ -80,6 +80,9 @@ export const routesSchema = {
check: {
type: 'boolean',
},
important: {
type: 'boolean',
},
status: {
type: 'integer',
minimum: 100,

View File

@@ -119,6 +119,9 @@ export function convertHeaders(headers: NowHeader[]): Route[] {
export function convertTrailingSlash(enable: boolean, status = 308): Route[] {
const routes: Route[] = [];
if (enable) {
routes.push({
src: '^/\\.well-known(?:/.*)?$'
});
routes.push({
src: '^/((?:[^/]+/)*[^/\\.]+)$',
headers: { Location: '/$1/' },

View File

@@ -72,6 +72,7 @@ describe('normalizeRoutes', () => {
src: '^/missed-me$',
headers: { 'Cache-Control': 'max-age=10' },
continue: true,
important: true,
},
{ handle: 'rewrite' },
{ src: '^.*$', dest: '/somewhere' },

View File

@@ -671,6 +671,7 @@ test('convertHeaders', () => {
test('convertTrailingSlash enabled', () => {
const actual = convertTrailingSlash(true);
const expected = [
{ src: '^/\\.well-known(?:/.*)?$' },
{
src: '^/((?:[^/]+/)*[^/\\.]+)$',
headers: { Location: '/$1/' },
@@ -685,11 +686,23 @@ test('convertTrailingSlash enabled', () => {
deepEqual(actual, expected);
const mustMatch = [
[
'/.well-known',
'/.well-known/',
'/.well-known/asdf',
'/.well-known/asdf/',
],
['/dir', '/dir/foo', '/dir/foo/bar'],
['/foo.html/', '/dir/foo.html/', '/dir/foo/bar.css/', '/dir/about.map.js/'],
];
const mustNotMatch = [
[
'/swell-known',
'/swell-known/',
'/swell-known/asdf',
'/swell-known/asdf/',
],
[
'/',
'/index.html',

View File

@@ -251,7 +251,9 @@ async function testDeployment(
const expected = probe.responseHeaders[header];
const isEqual = Array.isArray(expected)
? expected.every(h => actual.includes(h))
: expected.startsWith('/') && expected.endsWith('/')
: typeof expected === 'string' &&
expected.startsWith('/') &&
expected.endsWith('/')
? new RegExp(expected.slice(1, -1)).test(actual)
: expected === actual;
if (!isEqual) {