Compare commits
20 Commits
@vercel/no
...
@vercel/ne
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f64625655b | ||
|
|
25a8189997 | ||
|
|
25c3e627cf | ||
|
|
1d6d8b530f | ||
|
|
e821cc0ae7 | ||
|
|
8ecbdc5d03 | ||
|
|
895224985b | ||
|
|
0f42a63c03 | ||
|
|
81e4c9e6fe | ||
|
|
a0a29dc836 | ||
|
|
c1f9d51d7a | ||
|
|
422f0558c1 | ||
|
|
f064ae2908 | ||
|
|
58c3e636f0 | ||
|
|
d5081367f3 | ||
|
|
0ee88366ff | ||
|
|
9ae42c9e92 | ||
|
|
62b8df4a8d | ||
|
|
73ec7f3018 | ||
|
|
2d24a75ca6 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "2.6.0",
|
||||
"version": "2.6.1-canary.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
@@ -989,7 +989,6 @@ function getRouteResult(
|
||||
rewriteRoutes.push({
|
||||
src: '^/api(/.*)?$',
|
||||
status: 404,
|
||||
continue: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2393,7 +2393,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes).toStrictEqual([
|
||||
@@ -2495,7 +2494,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2533,7 +2531,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2571,7 +2568,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2604,7 +2600,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2632,7 +2627,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2663,7 +2657,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2690,7 +2683,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2725,7 +2717,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
expect(errorRoutes).toStrictEqual([
|
||||
@@ -2820,7 +2811,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2853,7 +2843,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2887,7 +2876,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2913,7 +2901,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2937,7 +2924,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2962,7 +2948,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -2983,7 +2968,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`', async ()
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -3018,7 +3002,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -3076,7 +3059,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -3109,7 +3091,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -3143,7 +3124,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -3162,7 +3142,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -3186,7 +3165,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -3211,7 +3189,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
@@ -3232,7 +3209,6 @@ it('Test `detectRoutes` with `featHandleMiss=true`, `cleanUrls=true`, `trailingS
|
||||
{
|
||||
status: 404,
|
||||
src: '^/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "20.1.3",
|
||||
"version": "20.1.5-canary.2",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -61,7 +61,7 @@
|
||||
"node": ">= 10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.6.0",
|
||||
"@vercel/build-utils": "2.6.1-canary.0",
|
||||
"@vercel/go": "1.1.6",
|
||||
"@vercel/node": "1.8.5",
|
||||
"@vercel/python": "1.2.3",
|
||||
|
||||
14
packages/now-cli/src/commands/env/add.ts
vendored
@@ -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: `What’s the value of ${envName}?`,
|
||||
choices: SYSTEM_ENV_VALUES.map(value => ({ name: value, value })),
|
||||
choices: systemEnvValues.map(value => ({ name: value, value })),
|
||||
});
|
||||
|
||||
envValue = systemEnvValue;
|
||||
|
||||
12
packages/now-cli/src/util/env/get-system-env-values.ts
vendored
Normal 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);
|
||||
}
|
||||
32
packages/now-cli/src/util/env/system-env.ts
vendored
@@ -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',
|
||||
];
|
||||
4
packages/now-cli/test/dev/fixtures/30-next-image-optimization/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.next
|
||||
!public
|
||||
yarn.lock
|
||||
.vercel
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "canary",
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<h1>Home Page</h1>
|
||||
<hr />
|
||||
<h2>Optimized</h2>
|
||||
<Image src="/test.png" width="400" height="400" />
|
||||
<hr />
|
||||
<h2>Original</h2>
|
||||
<img src="/test.png" width="400" height="400" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="400.000000pt" height="400.000000pt" viewBox="0 0 400.000000 400.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<g transform="translate(0.000000,400.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M0 2000 l0 -2000 2000 0 2000 0 0 2000 0 2000 -2000 0 -2000 0 0
|
||||
-2000z m2401 118 l396 -693 -398 -3 c-220 -1 -578 -1 -798 0 l-398 3 396 693
|
||||
c217 380 398 692 401 692 3 0 184 -312 401 -692z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 635 B |
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"version": 2,
|
||||
"build": {
|
||||
"env": {
|
||||
"FORCE_BUILDER_TAG": "canary"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,14 +128,13 @@ async function testPath(
|
||||
status,
|
||||
path,
|
||||
expectedText,
|
||||
headers = {},
|
||||
method = 'GET',
|
||||
body = undefined
|
||||
expectedHeaders = {},
|
||||
fetchOpts = {}
|
||||
) {
|
||||
const opts = { redirect: 'manual-dont-change', method, body };
|
||||
const opts = { ...fetchOpts, redirect: 'manual-dont-change' };
|
||||
const url = `${origin}${path}`;
|
||||
const res = await fetch(url, opts);
|
||||
const msg = `Testing response from ${method} ${url}`;
|
||||
const msg = `Testing response from ${fetchOpts.method || 'GET'} ${url}`;
|
||||
console.log(msg);
|
||||
t.is(res.status, status, msg);
|
||||
validateResponseHeaders(t, res);
|
||||
@@ -150,8 +149,8 @@ async function testPath(
|
||||
expectedText.lastIndex = 0; // reset since we test twice
|
||||
t.regex(actualText, expectedText);
|
||||
}
|
||||
if (headers) {
|
||||
Object.entries(headers).forEach(([key, expectedValue]) => {
|
||||
if (expectedHeaders) {
|
||||
Object.entries(expectedHeaders).forEach(([key, expectedValue]) => {
|
||||
let actualValue = res.headers.get(key);
|
||||
if (key.toLowerCase() === 'location' && actualValue === '//') {
|
||||
// HACK: `node-fetch` has strange behavior for location header so fix it
|
||||
@@ -387,9 +386,12 @@ test(
|
||||
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 fetchOpts = {
|
||||
method: 'POST',
|
||||
body: '{"query":"{redwood{version}}"}',
|
||||
};
|
||||
const resBody = '{"data":{"redwood":{"version":"0.15.0"}}}';
|
||||
await testPath(200, '/api/graphql', resBody, {}, 'POST', reqBody);
|
||||
await testPath(200, '/api/graphql', resBody, {}, fetchOpts);
|
||||
},
|
||||
{ isExample: true }
|
||||
)
|
||||
@@ -945,12 +947,16 @@ test(
|
||||
'Access-Control-Allow-Methods':
|
||||
'GET, POST, OPTIONS, HEAD, PATCH, PUT, DELETE',
|
||||
};
|
||||
await testPath(200, '/', 'status api', headers, 'GET');
|
||||
await testPath(200, '/', 'status api', headers, 'POST');
|
||||
await testPath(200, '/api/status.js', 'status api', headers, 'GET');
|
||||
await testPath(200, '/api/status.js', 'status api', headers, 'POST');
|
||||
await testPath(204, '/', '', headers, 'OPTIONS');
|
||||
await testPath(204, '/api/status.js', '', headers, 'OPTIONS');
|
||||
await testPath(200, '/', 'status api', headers, { method: 'GET' });
|
||||
await testPath(200, '/', 'status api', headers, { method: 'POST' });
|
||||
await testPath(200, '/api/status.js', 'status api', headers, {
|
||||
method: 'GET',
|
||||
});
|
||||
await testPath(200, '/api/status.js', 'status api', headers, {
|
||||
method: 'POST',
|
||||
});
|
||||
await testPath(204, '/', '', headers, { method: 'OPTIONS' });
|
||||
await testPath(204, '/api/status.js', '', headers, { method: 'OPTIONS' });
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1595,6 +1601,61 @@ test(
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] 30-next-image-optimization',
|
||||
testFixtureStdio('30-next-image-optimization', async testPath => {
|
||||
const toUrl = (url, w, q) => {
|
||||
const query = new URLSearchParams();
|
||||
query.append('url', url);
|
||||
query.append('w', w);
|
||||
query.append('q', q);
|
||||
return `/_next/image?${query}`;
|
||||
};
|
||||
|
||||
const expectHeader = accept => ({
|
||||
'content-type': accept,
|
||||
'cache-control': 'public, max-age=0, must-revalidate',
|
||||
});
|
||||
const fetchOpts = accept => ({ method: 'GET', headers: { accept } });
|
||||
await testPath(200, '/', /Home Page/m);
|
||||
await testPath(
|
||||
200,
|
||||
toUrl('/test.jpg', 64, 100),
|
||||
null,
|
||||
expectHeader('image/webp'),
|
||||
fetchOpts('image/webp')
|
||||
);
|
||||
await testPath(
|
||||
200,
|
||||
toUrl('/test.png', 64, 90),
|
||||
null,
|
||||
expectHeader('image/webp'),
|
||||
fetchOpts('image/webp')
|
||||
);
|
||||
await testPath(
|
||||
200,
|
||||
toUrl('/test.gif', 64, 80),
|
||||
null,
|
||||
expectHeader('image/webp'),
|
||||
fetchOpts('image/webp')
|
||||
);
|
||||
await testPath(
|
||||
200,
|
||||
toUrl('/test.svg', 64, 70),
|
||||
null,
|
||||
expectHeader('image/svg+xml'),
|
||||
fetchOpts('image/webp')
|
||||
);
|
||||
await testPath(
|
||||
200,
|
||||
toUrl('/animated.gif', 64, 60),
|
||||
null,
|
||||
expectHeader('image/gif'),
|
||||
fetchOpts('image/gif')
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] Use `@vercel/python` with Flask requirements.txt',
|
||||
testFixtureStdio('python-flask', async testPath => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/client",
|
||||
"version": "9.0.4",
|
||||
"version": "9.0.5-canary.0",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"homepage": "https://vercel.com",
|
||||
@@ -37,7 +37,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "2.6.0",
|
||||
"@vercel/build-utils": "2.6.1-canary.0",
|
||||
"@zeit/fetch": "5.2.0",
|
||||
"async-retry": "1.2.3",
|
||||
"async-sema": "3.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/next",
|
||||
"version": "2.7.0",
|
||||
"version": "2.7.2-canary.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
|
||||
|
||||
@@ -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
|
||||
@@ -1769,12 +1769,7 @@ export async function build({
|
||||
if (nonDynamicSsg || isFallback) {
|
||||
outputPathData = outputPathData.replace(
|
||||
new RegExp(`${escapeStringRegexp(origRouteFileNoExt)}.json$`),
|
||||
`${routeFileNoExt}${
|
||||
routeFileNoExt !== origRouteFileNoExt &&
|
||||
origRouteFileNoExt === '/index'
|
||||
? '/index'
|
||||
: ''
|
||||
}.json`
|
||||
`${routeFileNoExt}.json`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2020,6 +2015,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 +2079,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 +2096,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 +2106,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 +2141,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 +2187,12 @@ export async function build({
|
||||
]
|
||||
: []),
|
||||
|
||||
// headers
|
||||
...headers,
|
||||
|
||||
// redirects
|
||||
...redirects,
|
||||
|
||||
// Make sure to 404 for the /404 path itself
|
||||
...(i18n
|
||||
? [
|
||||
|
||||
@@ -427,22 +427,22 @@
|
||||
},
|
||||
|
||||
{
|
||||
"path": "/_next/data/testing-build-id/en-US/index.json",
|
||||
"path": "/_next/data/testing-build-id/en-US.json",
|
||||
"status": 200,
|
||||
"mustContain": "\"locale\":\"en-US\""
|
||||
},
|
||||
{
|
||||
"path": "/_next/data/testing-build-id/en/index.json",
|
||||
"path": "/_next/data/testing-build-id/en.json",
|
||||
"status": 200,
|
||||
"mustContain": "\"locale\":\"en\""
|
||||
},
|
||||
{
|
||||
"path": "/_next/data/testing-build-id/fr/index.json",
|
||||
"path": "/_next/data/testing-build-id/fr.json",
|
||||
"status": 200,
|
||||
"mustContain": "\"locale\":\"fr\""
|
||||
},
|
||||
{
|
||||
"path": "/_next/data/testing-build-id/nl/index.json",
|
||||
"path": "/_next/data/testing-build-id/nl.json",
|
||||
"status": 200,
|
||||
"mustContain": "\"locale\":\"nl\""
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ const cheerio = require('cheerio');
|
||||
module.exports = function (ctx) {
|
||||
it('should revalidate content properly from /', async () => {
|
||||
const dataRes = await fetch(
|
||||
`${ctx.deploymentUrl}/_next/data/testing-build-id/en-US/index.json`
|
||||
`${ctx.deploymentUrl}/_next/data/testing-build-id/en-US.json`
|
||||
);
|
||||
expect(dataRes.status).toBe(200);
|
||||
await dataRes.json();
|
||||
@@ -36,7 +36,7 @@ module.exports = function (ctx) {
|
||||
|
||||
it('should revalidate content properly from /fr', async () => {
|
||||
const dataRes = await fetch(
|
||||
`${ctx.deploymentUrl}/_next/data/testing-build-id/fr/index.json`
|
||||
`${ctx.deploymentUrl}/_next/data/testing-build-id/fr.json`
|
||||
);
|
||||
expect(dataRes.status).toBe(200);
|
||||
await dataRes.json();
|
||||
@@ -67,7 +67,7 @@ module.exports = function (ctx) {
|
||||
|
||||
it('should revalidate content properly from /nl-NL', async () => {
|
||||
const dataRes = await fetch(
|
||||
`${ctx.deploymentUrl}/_next/data/testing-build-id/nl-NL/index.json`
|
||||
`${ctx.deploymentUrl}/_next/data/testing-build-id/nl-NL.json`
|
||||
);
|
||||
expect(dataRes.status).toBe(200);
|
||||
await dataRes.json();
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -435,22 +435,22 @@
|
||||
},
|
||||
|
||||
{
|
||||
"path": "/_next/data/testing-build-id/en-US/index.json",
|
||||
"path": "/_next/data/testing-build-id/en-US.json",
|
||||
"status": 200,
|
||||
"mustContain": "\"locale\":\"en-US\""
|
||||
},
|
||||
{
|
||||
"path": "/_next/data/testing-build-id/en/index.json",
|
||||
"path": "/_next/data/testing-build-id/en.json",
|
||||
"status": 200,
|
||||
"mustContain": "\"locale\":\"en\""
|
||||
},
|
||||
{
|
||||
"path": "/_next/data/testing-build-id/fr/index.json",
|
||||
"path": "/_next/data/testing-build-id/fr.json",
|
||||
"status": 200,
|
||||
"mustContain": "\"locale\":\"fr\""
|
||||
},
|
||||
{
|
||||
"path": "/_next/data/testing-build-id/nl/index.json",
|
||||
"path": "/_next/data/testing-build-id/nl.json",
|
||||
"status": 200,
|
||||
"mustContain": "\"locale\":\"nl\""
|
||||
},
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -80,6 +80,9 @@ export const routesSchema = {
|
||||
check: {
|
||||
type: 'boolean',
|
||||
},
|
||||
important: {
|
||||
type: 'boolean',
|
||||
},
|
||||
status: {
|
||||
type: 'integer',
|
||||
minimum: 100,
|
||||
|
||||
@@ -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/' },
|
||||
|
||||
@@ -72,6 +72,7 @@ describe('normalizeRoutes', () => {
|
||||
src: '^/missed-me$',
|
||||
headers: { 'Cache-Control': 'max-age=10' },
|
||||
continue: true,
|
||||
important: true,
|
||||
},
|
||||
{ handle: 'rewrite' },
|
||||
{ src: '^.*$', dest: '/somewhere' },
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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) {
|
||||
|
||||