Compare commits

..

18 Commits

Author SHA1 Message Date
Ethan Arrowood
92f5b6e0c9 Publish Stable
- vercel@28.13.0
 - @vercel/gatsby-plugin-vercel-builder@0.1.2
 - @vercel/next@3.3.18
 - @vercel/node@2.8.15
 - @vercel/static-build@1.2.0
2023-01-20 10:27:31 -07:00
JJ Kasper
ed6ce1149a [next] Ensure outputDirectory is normalized (#9269)
This ensures we normalize an absolute `outputDirectory` passed into the Next.js builder as this can cause incorrect resolving. We should probably normalize this higher up as well but this is a starting point. 

Fixes: [slack thread](https://vercel.slack.com/archives/C0289CGVAR2/p1674176040821579?thread_ts=1674168748.257019&cid=C0289CGVAR2)
2023-01-20 17:08:32 +00:00
Ethan Arrowood
fc3611fb80 [static-build] add gatsby v5 fixture (#9267)
Adds a gatsby v5 fixture using ssr and dsg to demonstrate plugin works

Co-authored-by: Nathan Rajlich <n@n8.io>
2023-01-20 03:02:26 -08:00
Nathan Rajlich
ed33c2b27c [gatsby-plugin-vercel-builder] Remove some leftover console.log calls (#9265) 2023-01-19 16:05:48 -08:00
Nathan Rajlich
a7a5bf1a12 [tests] Pass in builder to runBuildLambda() (#9262)
Removes the need for `next`/`static-build` to be present in the root `package.json` file.
2023-01-19 15:10:06 -08:00
JJ Kasper
cc687b3880 [next] Correct RSC route for app dir with trailingSlash (#9263) 2023-01-19 15:03:58 -08:00
Nathan Rajlich
053ec92d5f [node] Add build logs probe for TypeScript usage messages (#9261)
Follow-up to https://github.com/vercel/vercel/pull/9258, even though that issue seemed to only happen when linked to the monorepo locally. In any case, this test will ensure those log messages are intact for any other change around that part of the codebase in the future.
2023-01-19 21:39:27 +00:00
Ethan Arrowood
4838dc336a [static-build] run pnpm install --lockfile-only after injecting gatsby plugins (#9259)
Fixes a bug where after injecting plugins and modifying the users package.json, pnpm would fail normal install
2023-01-19 20:07:53 +00:00
Nathan Rajlich
eae45f4019 [node] Fix TypeScript built-in compiler log message check (#9258)
This check started breaking probably after the pnpm switch, so switch to a simpler solution that doesn't require comparing the paths.
2023-01-19 13:12:31 +00:00
Ethan Arrowood
02feb564a7 [static-build][fs-detectors] add gatsby builder plugin injection (#9252)
Modifies the `@vercel/static-build` injection logic in order to now inject both the analytics and builder gatsby plugins.
2023-01-19 03:34:47 +00:00
Nathan Rajlich
e174a06673 Publish Stable
- vercel@28.12.8
 - @vercel/gatsby-plugin-vercel-builder@0.1.1
 - @vercel/next@3.3.17
 - @vercel/remix@1.2.7
2023-01-18 15:19:12 -08:00
Nathan Rajlich
de034943af [gatsby-plugin-vercel-builder] Fix turbo cache outputs configuration (#9257)
The published version on npm was missing the compiled code because Turbo
was not configured to cache them properly.
2023-01-18 15:10:28 -08:00
JJ Kasper
b3862271a5 [next] Fix normalize route with middleware and basePath (#9255)
This ensures we properly match the index route when normalizing routes with `x-nextjs-data` for middleware. 

x-ref: [slack thread](https://vercel.slack.com/archives/C03S8ED1DKM/p1674034533265989)
2023-01-18 21:30:30 +00:00
Steven
aaceeef604 [tests] Add test for ./examples/remix (#9251)
- Related to #9249
2023-01-18 19:13:55 +00:00
Steven
ad107ecf79 [tests] Split more dev tests (#9230)
This PR attempts to balance the tests so they run concurrently and therefore faster.

I also sorted the tests so they are deterministic when splitting/chunking.
2023-01-18 17:35:45 +00:00
Steven
79ef5c3724 Publish Stable
- vercel@28.12.7
 - @vercel/client@12.3.2
 - @vercel/gatsby-plugin-vercel-builder@0.1.0
 - @vercel/next@3.3.16
 - @vercel/node-bridge@3.1.10
 - @vercel/node@2.8.14
 - @vercel/remix@1.2.6
2023-01-18 08:39:44 -05:00
Nathan Rajlich
02ff265074 [remix] Fix config file dynamic import (#9249)
Follow-up to https://github.com/vercel/vercel/pull/8793, which breaks dynamic import of the config file, causing some issues for remix users: https://github.com/orgs/vercel/discussions/1282

The underlying error is `MODULE_NOT_FOUND`.

`import()` should work with `file://` URIs, however, since it's using typescript, the `import` gets compiled to `require()`, which does not support `file://` protocol scheme.

Supersedes https://github.com/vercel/vercel/pull/9248.
2023-01-18 11:24:42 +00:00
Ethan Arrowood
ae89b8b8be [gatsby-plugin-vercel-builder] Implement @vercel/gatsby-plugin-vercel-builder (#9218)
<picture data-single-emoji=":gatsby:" title=":gatsby:"><img class="emoji" src="https://single-emoji.vercel.app/api/emoji/eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..Dli3tTuity9MEBnK.YyaiT9ASg3XvFmlx5q0Ovkdbkto2fgJGjIJhsLcraR_hqYG0DAC6CcBMiaARcI_hF0502EnqhkrHQLeYfEoxfomLi9iKk4WBAe-oSfsENsG9oAwzYdH4LA.VMtHCMaOXNOqpB2xvph3Kg" alt=":gatsby:" width="20" height="auto" align="absmiddle"></picture> 

Implement the Build Output API v3 Gatsby plugin for use within `@vercel/static-build`.

Supersedes https://github.com/vercel/vercel/pull/8259.
2023-01-18 02:24:32 +00:00
198 changed files with 50835 additions and 1850 deletions

View File

@@ -43,5 +43,8 @@ packages/static-build/test/cache-fixtures
# redwood
packages/redwood/test/fixtures
# remix
packages/remix/test/fixtures
# gatsby-plugin-vercel-analytics
packages/gatsby-plugin-vercel-analytics

View File

@@ -13,7 +13,6 @@
"@typescript-eslint/parser": "5.21.0",
"@vercel/build-utils": "*",
"@vercel/ncc": "0.24.0",
"@vercel/next": "*",
"async-retry": "1.2.3",
"buffer-replace": "1.0.0",
"create-svelte": "2.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "28.12.6",
"version": "28.13.0",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -44,13 +44,13 @@
"@vercel/build-utils": "5.9.0",
"@vercel/go": "2.2.30",
"@vercel/hydrogen": "0.0.44",
"@vercel/next": "3.3.15",
"@vercel/node": "2.8.13",
"@vercel/next": "3.3.18",
"@vercel/node": "2.8.15",
"@vercel/python": "3.1.40",
"@vercel/redwood": "1.0.51",
"@vercel/remix": "1.2.5",
"@vercel/remix": "1.2.7",
"@vercel/ruby": "1.3.56",
"@vercel/static-build": "1.1.7"
"@vercel/static-build": "1.2.0"
},
"devDependencies": {
"@alex_neo/jest-expect-message": "1.0.5",
@@ -93,7 +93,7 @@
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.3.1",
"@vercel/client": "12.3.2",
"@vercel/error-utils": "1.0.8",
"@vercel/frameworks": "1.2.4",
"@vercel/fs-detectors": "3.7.5",

View File

@@ -1,3 +1,4 @@
import { isIP } from 'net';
const { exec, fixture, testFixture, testFixtureStdio } = require('./utils.js');
test('[vercel dev] validate redirects', async () => {
@@ -110,3 +111,114 @@ test(
});
})
);
test(
'[vercel dev] Use `@vercel/python` with Flask requirements.txt',
testFixtureStdio('python-flask', async (testPath: any) => {
const name = 'Alice';
const year = new Date().getFullYear();
await testPath(200, `/api/user?name=${name}`, new RegExp(`Hello ${name}`));
await testPath(200, `/api/date`, new RegExp(`Current date is ${year}`));
await testPath(200, `/api/date.py`, new RegExp(`Current date is ${year}`));
await testPath(200, `/api/headers`, (body: any, res: any) => {
// @ts-ignore
const { host } = new URL(res.url);
expect(body).toBe(host);
});
})
);
test(
'[vercel dev] Use custom runtime from the "functions" property',
testFixtureStdio('custom-runtime', async (testPath: any) => {
await testPath(200, `/api/user`, /Hello, from Bash!/m);
await testPath(200, `/api/user.sh`, /Hello, from Bash!/m);
})
);
test(
'[vercel dev] Should work with nested `tsconfig.json` files',
testFixtureStdio('nested-tsconfig', async (testPath: any) => {
await testPath(200, `/`, /Nested tsconfig.json test page/);
await testPath(200, `/api`, 'Nested `tsconfig.json` API endpoint');
})
);
test(
'[vercel dev] Should force `tsc` option "module: commonjs" for `startDevServer()`',
testFixtureStdio('force-module-commonjs', async (testPath: any) => {
await testPath(200, `/`, /Force &quot;module: commonjs&quot; test page/);
await testPath(
200,
`/api`,
'Force "module: commonjs" JavaScript with ES Modules API endpoint'
);
await testPath(
200,
`/api/ts`,
'Force "module: commonjs" TypeScript API endpoint'
);
})
);
test(
'[vercel dev] should prioritize index.html over other file named index.*',
testFixtureStdio('index-html-priority', async (testPath: any) => {
await testPath(200, '/', 'This is index.html');
await testPath(200, '/index.css', 'This is index.css');
})
);
test(
'[vercel dev] Should support `*.go` API serverless functions',
testFixtureStdio('go', async (testPath: any) => {
await testPath(200, `/api`, 'This is the index page');
await testPath(200, `/api/index`, 'This is the index page');
await testPath(200, `/api/index.go`, 'This is the index page');
await testPath(200, `/api/another`, 'This is another page');
await testPath(200, '/api/another.go', 'This is another page');
await testPath(200, `/api/foo`, 'Req Path: /api/foo');
await testPath(200, `/api/bar`, 'Req Path: /api/bar');
})
);
test(
'[vercel dev] Should set the `ts-node` "target" to match Node.js version',
testFixtureStdio('node-ts-node-target', async (testPath: any) => {
await testPath(200, `/api/subclass`, '{"ok":true}');
await testPath(
200,
`/api/array`,
'{"months":[1,2,3,4,5,6,7,8,9,10,11,12]}'
);
await testPath(200, `/api/dump`, (body: any, res: any, isDev: any) => {
// @ts-ignore
const { host } = new URL(res.url);
const { env, headers } = JSON.parse(body);
// Test that the API endpoint receives the Vercel proxy request headers
expect(headers['x-forwarded-host']).toBe(host);
expect(headers['x-vercel-deployment-url']).toBe(host);
expect(isIP(headers['x-real-ip'])).toBeTruthy();
expect(isIP(headers['x-forwarded-for'])).toBeTruthy();
expect(isIP(headers['x-vercel-forwarded-for'])).toBeTruthy();
// Test that the API endpoint has the Vercel platform env vars defined.
expect(env.NOW_REGION).toMatch(/^[a-z]{3}\d$/);
if (isDev) {
// Only dev is tested because in production these are opt-in.
expect(env.VERCEL_URL).toBe(host);
expect(env.VERCEL_REGION).toBe('dev1');
}
});
})
);
test(
'[vercel dev] Do not fail if `src` is missing',
testFixtureStdio('missing-src-property', async (testPath: any) => {
await testPath(200, '/', /hello:index.txt/m);
await testPath(404, '/i-do-not-exist');
})
);

View File

@@ -1,196 +1,15 @@
import ms from 'ms';
import fs from 'fs-extra';
import { isIP } from 'net';
import { join } from 'path';
import { Response } from 'node-fetch';
const {
fetch,
sleep,
fixture,
testFixture,
testFixtureStdio,
validateResponseHeaders,
} = require('./utils.js');
test(
'[vercel dev] temporary directory listing',
testFixtureStdio(
'temporary-directory-listing',
async (_testPath: any, port: any) => {
const directory = fixture('temporary-directory-listing');
await fs.unlink(join(directory, 'index.txt')).catch(() => null);
await sleep(ms('20s'));
const firstResponse = await fetch(`http://localhost:${port}`);
validateResponseHeaders(firstResponse);
const body = await firstResponse.text();
console.log(body);
expect(firstResponse.status).toBe(404);
await fs.writeFile(join(directory, 'index.txt'), 'hello');
for (let i = 0; i < 20; i++) {
const response = await fetch(`http://localhost:${port}`);
validateResponseHeaders(response);
if (response.status === 200) {
const body = await response.text();
expect(body).toBe('hello');
}
await sleep(ms('1s'));
}
},
{ skipDeploy: true }
)
);
test('[vercel dev] add a `package.json` to trigger `@vercel/static-build`', async () => {
const directory = fixture('trigger-static-build');
await fs.unlink(join(directory, 'package.json')).catch(() => null);
await fs.unlink(join(directory, 'public', 'index.txt')).catch(() => null);
await fs.rmdir(join(directory, 'public')).catch(() => null);
const tester = testFixtureStdio(
'trigger-static-build',
async (_testPath: any, port: any) => {
{
const response = await fetch(`http://localhost:${port}`);
validateResponseHeaders(response);
const body = await response.text();
expect(body.trim()).toBe('hello:index.txt');
}
const rnd = Math.random().toString();
const pkg = {
private: true,
scripts: { build: `mkdir -p public && echo ${rnd} > public/index.txt` },
};
await fs.writeFile(join(directory, 'package.json'), JSON.stringify(pkg));
// Wait until file events have been processed
await sleep(ms('2s'));
{
const response = await fetch(`http://localhost:${port}`);
validateResponseHeaders(response);
const body = await response.text();
expect(body.trim()).toBe(rnd);
}
},
{ skipDeploy: true }
);
await tester();
});
test('[vercel dev] no build matches warning', async () => {
const directory = fixture('no-build-matches');
const { dev } = await testFixture(directory, {
stdio: ['ignore', 'pipe', 'pipe'],
});
try {
// start `vercel dev` detached in child_process
dev.unref();
dev.stderr.setEncoding('utf8');
await new Promise<void>(resolve => {
dev.stderr.on('data', (str: string) => {
if (str.includes('did not match any source files')) {
resolve();
}
});
});
} finally {
await dev.kill();
}
});
test(
'[vercel dev] do not recursivly check the path',
testFixtureStdio('handle-filesystem-missing', async (testPath: any) => {
await testPath(200, '/', /hello/m);
await testPath(404, '/favicon.txt');
})
);
test('[vercel dev] render warning for empty cwd dir', async () => {
const directory = fixture('empty');
const { dev, port } = await testFixture(directory, {
stdio: ['ignore', 'pipe', 'pipe'],
});
try {
dev.unref();
// Monitor `stderr` for the warning
dev.stderr.setEncoding('utf8');
const msg = 'There are no files inside your deployment.';
await new Promise<void>(resolve => {
dev.stderr.on('data', (str: string) => {
if (str.includes(msg)) {
resolve();
}
});
});
// Issue a request to ensure a 404 response
await sleep(ms('3s'));
const response = await fetch(`http://localhost:${port}`);
validateResponseHeaders(response);
expect(response.status).toBe(404);
} finally {
await dev.kill();
}
});
test('[vercel dev] do not rebuild for changes in the output directory', async () => {
const directory = fixture('output-is-source');
const { dev, port } = await testFixture(directory, {
stdio: ['ignore', 'pipe', 'pipe'],
});
try {
dev.unref();
let stderr: any = [];
const start = Date.now();
dev.stderr.on('data', (str: any) => stderr.push(str));
while (stderr.join('').includes('Ready') === false) {
await sleep(ms('3s'));
if (Date.now() - start > ms('30s')) {
console.log('stderr:', stderr.join(''));
break;
}
}
const resp1 = await fetch(`http://localhost:${port}`);
const text1 = await resp1.text();
expect(text1.trim()).toBe('hello first');
await fs.writeFile(join(directory, 'public', 'index.html'), 'hello second');
await sleep(ms('3s'));
const resp2 = await fetch(`http://localhost:${port}`);
const text2 = await resp2.text();
expect(text2.trim()).toBe('hello second');
} finally {
await dev.kill();
}
});
test(
'[vercel dev] 25-nextjs-src-dir',
testFixtureStdio('25-nextjs-src-dir', async (testPath: any) => {
@@ -324,117 +143,6 @@ test(
})
);
test(
'[vercel dev] Use `@vercel/python` with Flask requirements.txt',
testFixtureStdio('python-flask', async (testPath: any) => {
const name = 'Alice';
const year = new Date().getFullYear();
await testPath(200, `/api/user?name=${name}`, new RegExp(`Hello ${name}`));
await testPath(200, `/api/date`, new RegExp(`Current date is ${year}`));
await testPath(200, `/api/date.py`, new RegExp(`Current date is ${year}`));
await testPath(200, `/api/headers`, (body: any, res: any) => {
// @ts-ignore
const { host } = new URL(res.url);
expect(body).toBe(host);
});
})
);
test(
'[vercel dev] Use custom runtime from the "functions" property',
testFixtureStdio('custom-runtime', async (testPath: any) => {
await testPath(200, `/api/user`, /Hello, from Bash!/m);
await testPath(200, `/api/user.sh`, /Hello, from Bash!/m);
})
);
test(
'[vercel dev] Should work with nested `tsconfig.json` files',
testFixtureStdio('nested-tsconfig', async (testPath: any) => {
await testPath(200, `/`, /Nested tsconfig.json test page/);
await testPath(200, `/api`, 'Nested `tsconfig.json` API endpoint');
})
);
test(
'[vercel dev] Should force `tsc` option "module: commonjs" for `startDevServer()`',
testFixtureStdio('force-module-commonjs', async (testPath: any) => {
await testPath(200, `/`, /Force &quot;module: commonjs&quot; test page/);
await testPath(
200,
`/api`,
'Force "module: commonjs" JavaScript with ES Modules API endpoint'
);
await testPath(
200,
`/api/ts`,
'Force "module: commonjs" TypeScript API endpoint'
);
})
);
test(
'[vercel dev] should prioritize index.html over other file named index.*',
testFixtureStdio('index-html-priority', async (testPath: any) => {
await testPath(200, '/', 'This is index.html');
await testPath(200, '/index.css', 'This is index.css');
})
);
test(
'[vercel dev] Should support `*.go` API serverless functions',
testFixtureStdio('go', async (testPath: any) => {
await testPath(200, `/api`, 'This is the index page');
await testPath(200, `/api/index`, 'This is the index page');
await testPath(200, `/api/index.go`, 'This is the index page');
await testPath(200, `/api/another`, 'This is another page');
await testPath(200, '/api/another.go', 'This is another page');
await testPath(200, `/api/foo`, 'Req Path: /api/foo');
await testPath(200, `/api/bar`, 'Req Path: /api/bar');
})
);
test(
'[vercel dev] Should set the `ts-node` "target" to match Node.js version',
testFixtureStdio('node-ts-node-target', async (testPath: any) => {
await testPath(200, `/api/subclass`, '{"ok":true}');
await testPath(
200,
`/api/array`,
'{"months":[1,2,3,4,5,6,7,8,9,10,11,12]}'
);
await testPath(200, `/api/dump`, (body: any, res: any, isDev: any) => {
// @ts-ignore
const { host } = new URL(res.url);
const { env, headers } = JSON.parse(body);
// Test that the API endpoint receives the Vercel proxy request headers
expect(headers['x-forwarded-host']).toBe(host);
expect(headers['x-vercel-deployment-url']).toBe(host);
expect(isIP(headers['x-real-ip'])).toBeTruthy();
expect(isIP(headers['x-forwarded-for'])).toBeTruthy();
expect(isIP(headers['x-vercel-forwarded-for'])).toBeTruthy();
// Test that the API endpoint has the Vercel platform env vars defined.
expect(env.NOW_REGION).toMatch(/^[a-z]{3}\d$/);
if (isDev) {
// Only dev is tested because in production these are opt-in.
expect(env.VERCEL_URL).toBe(host);
expect(env.VERCEL_REGION).toBe('dev1');
}
});
})
);
test(
'[vercel dev] Do not fail if `src` is missing',
testFixtureStdio('missing-src-property', async (testPath: any) => {
await testPath(200, '/', /hello:index.txt/m);
await testPath(404, '/i-do-not-exist');
})
);
test(
'[vercel dev] Middleware that returns a 200 response',
testFixtureStdio('middleware-response', async (testPath: any) => {

View File

@@ -6,10 +6,189 @@ const {
fetch,
fixture,
sleep,
testFixture,
testFixtureStdio,
validateResponseHeaders,
} = require('./utils.js');
test(
'[vercel dev] temporary directory listing',
testFixtureStdio(
'temporary-directory-listing',
async (_testPath: any, port: any) => {
const directory = fixture('temporary-directory-listing');
await fs.unlink(join(directory, 'index.txt')).catch(() => null);
await sleep(ms('20s'));
const firstResponse = await fetch(`http://localhost:${port}`);
validateResponseHeaders(firstResponse);
const body = await firstResponse.text();
console.log(body);
expect(firstResponse.status).toBe(404);
await fs.writeFile(join(directory, 'index.txt'), 'hello');
for (let i = 0; i < 20; i++) {
const response = await fetch(`http://localhost:${port}`);
validateResponseHeaders(response);
if (response.status === 200) {
const body = await response.text();
expect(body).toBe('hello');
}
await sleep(ms('1s'));
}
},
{ skipDeploy: true }
)
);
test('[vercel dev] add a `package.json` to trigger `@vercel/static-build`', async () => {
const directory = fixture('trigger-static-build');
await fs.unlink(join(directory, 'package.json')).catch(() => null);
await fs.unlink(join(directory, 'public', 'index.txt')).catch(() => null);
await fs.rmdir(join(directory, 'public')).catch(() => null);
const tester = testFixtureStdio(
'trigger-static-build',
async (_testPath: any, port: any) => {
{
const response = await fetch(`http://localhost:${port}`);
validateResponseHeaders(response);
const body = await response.text();
expect(body.trim()).toBe('hello:index.txt');
}
const rnd = Math.random().toString();
const pkg = {
private: true,
scripts: { build: `mkdir -p public && echo ${rnd} > public/index.txt` },
};
await fs.writeFile(join(directory, 'package.json'), JSON.stringify(pkg));
// Wait until file events have been processed
await sleep(ms('2s'));
{
const response = await fetch(`http://localhost:${port}`);
validateResponseHeaders(response);
const body = await response.text();
expect(body.trim()).toBe(rnd);
}
},
{ skipDeploy: true }
);
await tester();
});
test('[vercel dev] no build matches warning', async () => {
const directory = fixture('no-build-matches');
const { dev } = await testFixture(directory, {
stdio: ['ignore', 'pipe', 'pipe'],
});
try {
// start `vercel dev` detached in child_process
dev.unref();
dev.stderr.setEncoding('utf8');
await new Promise<void>(resolve => {
dev.stderr.on('data', (str: string) => {
if (str.includes('did not match any source files')) {
resolve();
}
});
});
} finally {
await dev.kill();
}
});
test(
'[vercel dev] do not recursivly check the path',
testFixtureStdio('handle-filesystem-missing', async (testPath: any) => {
await testPath(200, '/', /hello/m);
await testPath(404, '/favicon.txt');
})
);
test('[vercel dev] render warning for empty cwd dir', async () => {
const directory = fixture('empty');
const { dev, port } = await testFixture(directory, {
stdio: ['ignore', 'pipe', 'pipe'],
});
try {
dev.unref();
// Monitor `stderr` for the warning
dev.stderr.setEncoding('utf8');
const msg = 'There are no files inside your deployment.';
await new Promise<void>(resolve => {
dev.stderr.on('data', (str: string) => {
if (str.includes(msg)) {
resolve();
}
});
});
// Issue a request to ensure a 404 response
await sleep(ms('3s'));
const response = await fetch(`http://localhost:${port}`);
validateResponseHeaders(response);
expect(response.status).toBe(404);
} finally {
await dev.kill();
}
});
test('[vercel dev] do not rebuild for changes in the output directory', async () => {
const directory = fixture('output-is-source');
const { dev, port } = await testFixture(directory, {
stdio: ['ignore', 'pipe', 'pipe'],
});
try {
dev.unref();
let stderr: any = [];
const start = Date.now();
dev.stderr.on('data', (str: any) => stderr.push(str));
while (stderr.join('').includes('Ready') === false) {
await sleep(ms('3s'));
if (Date.now() - start > ms('30s')) {
console.log('stderr:', stderr.join(''));
break;
}
}
const resp1 = await fetch(`http://localhost:${port}`);
const text1 = await resp1.text();
expect(text1.trim()).toBe('hello first');
await fs.writeFile(join(directory, 'public', 'index.html'), 'hello second');
await sleep(ms('3s'));
const resp2 = await fetch(`http://localhost:${port}`);
const text2 = await resp2.text();
expect(text2.trim()).toBe('hello second');
} finally {
await dev.kill();
}
});
test(
'[vercel dev] test cleanUrls serve correct content',
testFixtureStdio('test-clean-urls', async (testPath: any) => {

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "12.3.1",
"version": "12.3.2",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -23,7 +23,7 @@
"node": ">= 14"
},
"devDependencies": {
"@types/async-retry": "1.4.1",
"@types/async-retry": "1.4.5",
"@types/fs-extra": "7.0.0",
"@types/jest": "27.4.1",
"@types/minimatch": "3.0.5",

View File

@@ -12,7 +12,8 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"strict": true,
"target": "ES2020"
"target": "ES2020",
"skipLibCheck": true
},
"include": ["./src"]
}

View File

@@ -0,0 +1,4 @@
gatsby-node.*
!gatsby-node.ts
dist

View File

@@ -0,0 +1,28 @@
import path from 'path';
import type { GatsbyNode } from 'gatsby';
// this gets built separately, so import from "dist" instead of "src"
import { generateVercelBuildOutputAPI3Output } from './dist';
export const pluginOptionsSchema: GatsbyNode['pluginOptionsSchema'] = ({
Joi,
}) => {
return Joi.object({
exportPath: Joi.string().optional(),
});
};
export const onPostBuild: GatsbyNode['onPostBuild'] = async (
{ store },
pluginOptions
) => {
// validated by `pluginOptionSchema`
const exportPath = (pluginOptions?.exportPath ??
path.join('.vercel', 'output', 'config.json')) as string;
await generateVercelBuildOutputAPI3Output({
exportPath,
gatsbyStoreState: store.getState(),
});
};

View File

@@ -0,0 +1,33 @@
{
"name": "@vercel/gatsby-plugin-vercel-builder",
"version": "0.1.2",
"main": "dist/index.js",
"files": [
"dist",
"gatsby-node.ts",
"gatsby-node.js",
"gatsby-node.js.map"
],
"scripts": {
"build": "pnpm build:src && pnpm build:gatsby",
"build:gatsby": "tsc -p tsconfig.gatsby.json",
"build:src": "tsc -p tsconfig.src.json"
},
"dependencies": {
"@vercel/build-utils": "5.9.0",
"@vercel/node": "2.8.15",
"@vercel/routing-utils": "2.1.8",
"ajv": "8.12.0",
"esbuild": "0.16.17",
"etag": "1.8.1",
"fs-extra": "11.1.0"
},
"devDependencies": {
"@types/etag": "1.8.0",
"@types/fs-extra": "11.0.1",
"@types/node": "14.18.33",
"@types/react": "18.0.26",
"gatsby": "4.25.2",
"typescript": "4.3.4"
}
}

View File

@@ -0,0 +1,129 @@
import { join } from 'path';
import { getNodeVersion } from '@vercel/build-utils';
import { build } from 'esbuild';
import {
copy,
copyFile,
pathExists,
writeJson,
writeFileSync,
ensureFileSync,
} from 'fs-extra';
import type {
NodejsServerlessFunctionConfig,
PrerenderFunctionConfig,
} from './../types';
export const writeHandler = async ({
outDir,
handlerFile,
}: {
outDir: string;
handlerFile: string;
}) => {
const { major } = await getNodeVersion(process.cwd());
try {
return await build({
entryPoints: [handlerFile],
loader: { '.ts': 'ts' },
outfile: join(outDir, './index.js'),
format: 'cjs',
target: `node${major}`,
platform: 'node',
bundle: true,
minify: true,
define: {
'process.env.NODE_ENV': "'production'",
},
});
} catch (e: any) {
console.error('Failed to build lambda handler', e.message);
}
};
export const writeVCConfig = async ({
functionDir,
handler = 'index.js',
}: {
functionDir: string;
handler?: string;
}) => {
const { runtime } = await getNodeVersion(process.cwd());
const config: NodejsServerlessFunctionConfig = {
runtime,
handler,
launcherType: 'Nodejs',
shouldAddHelpers: true,
};
return writeJson(`${functionDir}/.vc-config.json`, config);
};
export const writePrerenderConfig = (outputPath: string) => {
const config: PrerenderFunctionConfig = {
expiration: false,
};
ensureFileSync(outputPath);
return writeFileSync(outputPath, JSON.stringify(config));
};
export async function movePageData({ functionDir }: { functionDir: string }) {
await copy(
join('.vercel', 'output', 'static', 'page-data'),
join(functionDir, 'page-data')
);
}
export async function copyFunctionLibs({
functionDir,
}: {
functionDir: string;
}) {
/* Copies the required libs for Serverless Functions from .cache to the <name>.func folder */
await Promise.allSettled(
[
{
src: join('.cache', 'query-engine'),
dest: join(functionDir, '.cache', 'query-engine'),
},
{
src: join('.cache', 'page-ssr'),
dest: join(functionDir, '.cache', 'page-ssr'),
},
// {
// src: join(functionDir, '.cache', 'query-engine', 'assets'),
// dest: join(functionDir, 'assets'),
// },
{
src: join('.cache', 'data', 'datastore'),
dest: join(functionDir, '.cache', 'data', 'datastore'),
},
{
src: join('.cache', 'caches'),
dest: join(functionDir, '.cache', 'caches'),
},
].map(({ src, dest }) => copy(src, dest))
);
}
export async function copyHTMLFiles({ functionDir }: { functionDir: string }) {
/* If available, copies the 404.html and 500.html files to the <name>.func/lib folder */
for (const htmlFile of ['404', '500']) {
if (await pathExists(join('public', `${htmlFile}.html`))) {
try {
await copyFile(
join('public', `${htmlFile}.html`),
join(functionDir, `${htmlFile}.html`)
);
} catch (e: any) {
console.error('Failed to copy HTML files', e.message);
process.exit(1);
}
}
}
}

View File

@@ -0,0 +1,58 @@
import os from 'os';
import { join } from 'path';
import etag from 'etag';
import { copySync, existsSync, readFileSync } from 'fs-extra';
import type { VercelRequest, VercelResponse } from '@vercel/node';
import { getGraphQLEngine, getPageSSRHelpers } from '../utils';
const TMP_DATA_PATH = join(os.tmpdir(), 'data/datastore');
const CUR_DATA_PATH = join(__dirname, '.cache/data/datastore');
if (!existsSync(TMP_DATA_PATH)) {
// Copies executable `data` files to the writable /tmp directory.
copySync(CUR_DATA_PATH, TMP_DATA_PATH);
}
export default async function handler(req: VercelRequest, res: VercelResponse) {
const splitPathName = req.url!.split('/')[2];
const pathName = splitPathName === `index` ? `/` : splitPathName;
if (
existsSync(join(__dirname, 'page-data', splitPathName, 'page-data.json'))
) {
/* Non-SSR/DSG pages already have a pre-generated page-data.json file.
Instead of generating this dynamically, we can directly serve this JSON. */
res.setHeader('Content-Type', 'application/json');
return res
.status(200)
.json(
readFileSync(
join(__dirname, 'page-data', splitPathName, 'page-data.json'),
'utf-8'
)
);
}
const { getData, renderPageData } = await getPageSSRHelpers();
const graphqlEngine = await getGraphQLEngine();
const data = await getData({
req,
graphqlEngine,
pathName,
});
const pageData = await renderPageData({ data });
if (data.serverDataHeaders) {
for (const [name, value] of Object.entries(data.serverDataHeaders)) {
res.setHeader(name, value);
}
}
res.setHeader('ETag', etag(JSON.stringify(pageData)));
return res.json(pageData);
}

View File

@@ -0,0 +1,40 @@
import { join } from 'path';
import os from 'os';
import { copySync, existsSync } from 'fs-extra';
import { getPageSSRHelpers, getGraphQLEngine } from '../utils';
import type { VercelRequest, VercelResponse } from '@vercel/node';
const TMP_DATA_PATH = join(os.tmpdir(), 'data/datastore');
const CUR_DATA_PATH = join(__dirname, '.cache/data/datastore');
if (!existsSync(TMP_DATA_PATH)) {
// Copies executable `data` files to the writable /tmp directory.
copySync(CUR_DATA_PATH, TMP_DATA_PATH);
}
export default async function handler(req: VercelRequest, res: VercelResponse) {
const graphqlEngine = await getGraphQLEngine();
const { getData, renderHTML } = await getPageSSRHelpers();
const data = await getData({
pathName: req.url as string,
graphqlEngine,
req,
});
const results = await renderHTML({ data });
if (data.serverDataHeaders) {
for (const [name, value] of Object.entries(data.serverDataHeaders)) {
res.setHeader(name, value);
}
}
if (data.serverDataStatus) {
res.statusCode = data.serverDataStatus;
}
res.send(results);
}

View File

@@ -0,0 +1,20 @@
import { join } from 'path';
import os from 'os';
const TMP_DATA_PATH = join(os.tmpdir(), 'data/datastore');
export async function getGraphQLEngine() {
const { GraphQLEngine } = (await import(
join(__dirname, '.cache/query-engine/index.js')
)) as typeof import('gatsby/dist/schema/graphql-engine/entry');
return new GraphQLEngine({ dbPath: TMP_DATA_PATH });
}
export async function getPageSSRHelpers() {
const { getData, renderPageData, renderHTML } = (await import(
join(__dirname, '.cache/page-ssr/index.js')
)) as typeof import('gatsby/dist/utils/page-ssr-module/entry');
return { getData, renderPageData, renderHTML };
}

View File

@@ -0,0 +1,126 @@
import { join } from 'path';
import { ensureDir } from 'fs-extra';
import { createSymlink } from '../utils/symlink';
import {
writeHandler,
writeVCConfig,
copyFunctionLibs,
movePageData,
copyHTMLFiles,
writePrerenderConfig,
} from '../handlers/build';
import { GatsbyFunction } from '../schemas';
import { Routes } from '../types';
export async function createServerlessFunctions({
dsgRoutes,
ssrRoutes,
}: Routes) {
/* Gatsby SSR/DSG on Vercel is enabled through Vercel Serverless Functions.
This plugin creates one Serverless Function called `_ssr.func` that is used by SSR and DSG pages through symlinks.
DSG is enabled through prerender functions.
*/
const functionName = '_ssr.func';
const functionDir = join('.vercel', 'output', 'functions', functionName);
const handlerFile = join(
__dirname,
'..',
'handlers',
'templates',
'./ssr-handler.js'
);
await ensureDir(functionDir);
await Promise.all([
writeHandler({ outDir: functionDir, handlerFile }),
copyFunctionLibs({ functionDir }),
copyHTMLFiles({ functionDir }),
writeVCConfig({ functionDir }),
]);
await Promise.all([
...ssrRoutes.map(async (pathName: string) => {
return createSymlink(pathName, functionName);
}),
...dsgRoutes.map(async (pathName: string) => {
writePrerenderConfig(
join(
'.vercel',
'output',
'functions',
`${pathName}.prerender-config.json`
)
);
return createSymlink(pathName, functionName);
}),
]);
}
export async function createPageDataFunction({ dsgRoutes, ssrRoutes }: Routes) {
/* Gatsby uses /page-data/<path>/page-data.json to fetch data. This plugin creates a
`_page-data.func` function that dynamically generates this data if it's not available in `static/page-data`. */
const functionName = '_page-data.func';
const functionDir = join('.vercel', 'output', 'functions', functionName);
const handlerFile = join(
__dirname,
'..',
'handlers',
'templates',
'./page-data.js'
);
await ensureDir(functionDir);
await Promise.all([
writeHandler({ outDir: functionDir, handlerFile }),
copyFunctionLibs({ functionDir }),
movePageData({ functionDir }),
writeVCConfig({ functionDir }),
]);
await Promise.all([
...ssrRoutes.map(async (pathName: string) => {
return createSymlink(
`page-data/${pathName}/page-data.json`,
functionName
);
}),
...dsgRoutes.map(async (pathName: string) => {
const funcPath = `page-data/${pathName}/page-data.json`;
writePrerenderConfig(
join(
'.vercel',
'output',
'functions',
`${funcPath}.prerender-config.json`
)
);
return createSymlink(funcPath, functionName);
}),
]);
}
export async function createAPIRoutes(functions: GatsbyFunction[]) {
const apiDir = join('.vercel', 'output', 'functions', 'api');
await ensureDir(apiDir);
await Promise.allSettled(
functions.map(async (func: GatsbyFunction) => {
const apiRouteDir = `${apiDir}/${func.functionRoute}.func`;
const handlerFile = func.originalAbsoluteFilePath;
await ensureDir(apiRouteDir);
await Promise.all([
writeHandler({ outDir: apiRouteDir, handlerFile }),
writeVCConfig({ functionDir: apiRouteDir }),
]);
})
);
}

View File

@@ -0,0 +1,10 @@
import { join } from 'path';
import { copy, ensureDir } from 'fs-extra';
export async function createStaticDir() {
const targetDir = join(process.cwd(), '.vercel', 'output', 'static');
await ensureDir(targetDir);
await copy(join(process.cwd(), 'public'), targetDir);
}

View File

@@ -0,0 +1,104 @@
import { join } from 'path';
import { getTransformedRoutes } from '@vercel/routing-utils';
import { pathExists, writeJson, remove, mkdirp } from 'fs-extra';
import { validateGatsbyState } from './schemas';
import {
createServerlessFunctions,
createPageDataFunction,
createAPIRoutes,
} from './helpers/functions';
import { createStaticDir } from './helpers/static';
export interface GenerateVercelBuildOutputAPI3OutputOptions {
exportPath: string;
gatsbyStoreState: {
pages: Map<string, unknown>;
redirects: unknown;
functions: unknown;
};
[x: string]: unknown;
}
import type { Config, Routes } from './types';
export async function generateVercelBuildOutputAPI3Output({
exportPath,
gatsbyStoreState,
}: GenerateVercelBuildOutputAPI3OutputOptions) {
const state = {
pages: Array.from(gatsbyStoreState.pages.entries()), // must transform from a Map for validation
redirects: gatsbyStoreState.redirects,
functions: gatsbyStoreState.functions,
};
if (validateGatsbyState(state)) {
console.log('▲ Creating Vercel build output');
await remove(join('.vercel', 'output'));
const { pages, redirects, functions } = state;
const { ssrRoutes, dsgRoutes } = pages.reduce<Routes>(
(acc, [, cur]) => {
if (cur.mode === 'SSR') {
acc.ssrRoutes.push(cur.path);
} else if (cur.mode === 'DSG') {
acc.dsgRoutes.push(cur.path);
}
return acc;
},
{
ssrRoutes: [],
dsgRoutes: [],
}
);
await createStaticDir();
await mkdirp(join('.cache', 'caches'));
const createPromises: Promise<void>[] = [];
if (functions.length > 0) createPromises.push(createAPIRoutes(functions));
if (ssrRoutes.length > 0 || dsgRoutes.length > 0) {
createPromises.push(createPageDataFunction({ ssrRoutes, dsgRoutes }));
createPromises.push(createServerlessFunctions({ ssrRoutes, dsgRoutes }));
}
await Promise.all(createPromises);
const vercelConfigPath = `${process.cwd()}/vercel.config.js`;
const vercelConfig: Config = (await pathExists(vercelConfigPath))
? require(vercelConfigPath).default
: {};
const { routes } = getTransformedRoutes({
...vercelConfig,
trailingSlash: false,
redirects: redirects.map(({ fromPath, toPath, isPermanent }) => ({
source: fromPath,
destination: toPath,
permanent: isPermanent,
})),
rewrites: [
{
source: '^/page-data(?:/(.*))/page-data\\.json$',
destination: '/_page-data',
},
],
});
const config: Config = {
version: 3,
routes: routes || undefined,
};
await writeJson(exportPath, config);
console.log('Vercel output has been generated');
} else {
throw new Error(
'Gatsby state validation error. Please file an issue https://vercel.com/help#issues'
);
}
}

View File

@@ -0,0 +1,82 @@
import type {
IGatsbyPage,
IGatsbyFunction,
IRedirect,
} from 'gatsby/dist/redux/types';
import Ajv, { JSONSchemaType } from 'ajv';
export type GatsbyPage = Pick<IGatsbyPage, 'mode' | 'path'>;
const GatsbyPageSchema: JSONSchemaType<GatsbyPage> = {
type: 'object',
properties: {
mode: {
type: 'string',
enum: ['SSG', 'DSG', 'SSR'],
},
path: {
type: 'string',
},
},
required: ['mode', 'path'],
} as const;
export interface GatsbyState {
pages: Array<[string, GatsbyPage]>;
redirects: Array<GatsbyRedirect>;
functions: Array<GatsbyFunction>;
}
export type GatsbyFunction = Pick<
IGatsbyFunction,
'functionRoute' | 'originalAbsoluteFilePath'
>;
const GatsbyFunctionSchema: JSONSchemaType<GatsbyFunction> = {
type: 'object',
properties: {
functionRoute: { type: 'string' },
originalAbsoluteFilePath: { type: 'string' },
},
required: ['functionRoute', 'originalAbsoluteFilePath'],
} as const;
export type GatsbyRedirect = Pick<
IRedirect,
'fromPath' | 'toPath' | 'isPermanent'
>;
const GatsbyRedirectSchema: JSONSchemaType<GatsbyRedirect> = {
type: 'object',
properties: {
fromPath: { type: 'string' },
toPath: { type: 'string' },
isPermanent: { type: 'boolean', nullable: true },
},
required: ['fromPath', 'toPath'],
} as const;
const GatsbyStateSchema: JSONSchemaType<GatsbyState> = {
type: 'object',
properties: {
pages: {
type: 'array',
items: {
type: 'array',
minItems: 2,
maxItems: 2,
items: [{ type: 'string' }, GatsbyPageSchema],
},
},
redirects: {
type: 'array',
items: GatsbyRedirectSchema,
},
functions: {
type: 'array',
items: GatsbyFunctionSchema,
},
},
required: ['pages', 'redirects', 'functions'],
additionalProperties: true,
} as const;
export const ajv = new Ajv({ allErrors: true });
export const validateGatsbyState = ajv.compile(GatsbyStateSchema);

View File

@@ -0,0 +1,126 @@
import { GatsbyPage } from './schemas';
export type Config = {
version: 3;
routes?: Route[];
images?: ImagesConfig;
wildcard?: WildcardConfig;
overrides?: OverrideConfig;
cache?: string[];
};
type Route = Source | Handler;
type Source = {
src: string;
dest?: string;
headers?: Record<string, string>;
methods?: string[];
continue?: boolean;
caseSensitive?: boolean;
check?: boolean;
status?: number;
has?: Array<HostHasField | HeaderHasField | CookieHasField | QueryHasField>;
missing?: Array<
HostHasField | HeaderHasField | CookieHasField | QueryHasField
>;
locale?: Locale;
middlewarePath?: string;
};
type Locale = {
redirect?: Record<string, string>;
cookie?: string;
};
type HostHasField = {
type: 'host';
value: string;
};
type HeaderHasField = {
type: 'header';
key: string;
value?: string;
};
type CookieHasField = {
type: 'cookie';
key: string;
value?: string;
};
type QueryHasField = {
type: 'query';
key: string;
value?: string;
};
type HandleValue =
| 'rewrite'
| 'filesystem' // check matches after the filesystem misses
| 'resource'
| 'miss' // check matches after every filesystem miss
| 'hit'
| 'error'; // check matches after error (500, 404, etc.)
type Handler = {
handle: HandleValue;
src?: string;
dest?: string;
status?: number;
};
type ImageFormat = 'image/avif' | 'image/webp';
type ImagesConfig = {
sizes: number[];
domains: string[];
minimumCacheTTL?: number; // seconds
formats?: ImageFormat[];
dangerouslyAllowSVG?: boolean;
contentSecurityPolicy?: string;
};
type WildCard = {
domain: string;
value: string;
};
type WildcardConfig = Array<WildCard>;
type Override = {
path?: string;
contentType?: string;
};
type OverrideConfig = Record<string, Override>;
type ServerlessFunctionConfig = {
handler: string;
runtime: string;
memory?: number;
maxDuration?: number;
environment?: Record<string, string>[];
allowQuery?: string[];
regions?: string[];
};
export type NodejsServerlessFunctionConfig = ServerlessFunctionConfig & {
launcherType: 'Nodejs';
shouldAddHelpers?: boolean; // default: false
shouldAddSourceMapSupport?: boolean; // default: false
};
export type PrerenderFunctionConfig = {
expiration: number | false;
group?: number;
bypassToken?: string;
fallback?: string;
allowQuery?: string[];
};
export interface Routes {
ssrRoutes: Array<GatsbyPage['path']>;
dsgRoutes: Array<GatsbyPage['path']>;
}

View File

@@ -0,0 +1,20 @@
import path, { join, normalize, sep } from 'path';
import { ensureDir, symlinkSync } from 'fs-extra';
const removeTrailingSlash = (str: string) => str.replace(/\/$/, '');
export const createSymlink = async (pathName: string, destName: string) => {
const functionName = removeTrailingSlash(pathName).split(sep).pop();
const dirPath = removeTrailingSlash(
join('.vercel', 'output', 'functions', normalize(join(pathName, '..')))
);
await ensureDir(dirPath);
symlinkSync(
path.relative(dirPath, join('.vercel', 'output', 'functions', destName)),
path.join(dirPath, `${functionName}.func`)
);
};

View File

@@ -0,0 +1,104 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
"declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
"declarationMap": true /* Create sourcemaps for d.ts files. */,
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true /* Create source map files for emitted JavaScript files. */,
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": []
}

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"declaration": false,
"declarationMap": false
},
"include": ["gatsby-node.ts"]
}

View File

@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src/**/*.ts"]
}

View File

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

View File

@@ -214,7 +214,7 @@ export const build: BuildV2 = async ({
repoRootPath = entryPath;
entryPath = path.join(entryPath, config.rootDirectory as string);
}
const outputDirectory = config.outputDirectory || '.next';
const outputDirectory = path.join('./', config.outputDirectory || '.next');
const dotNextStatic = path.join(entryPath, outputDirectory, 'static');
// TODO: remove after testing used for simulating root directory monorepo
// setting that can't be triggered with vercel.json
@@ -2595,7 +2595,7 @@ export const prepareCache: PrepareCache = async ({
debug('Preparing cache...');
const entryDirectory = path.dirname(entrypoint);
const entryPath = path.join(workPath, entryDirectory);
const outputDirectory = config.outputDirectory || '.next';
const outputDirectory = path.join('./', config.outputDirectory || '.next');
const nextVersionRange = await getNextVersionRange(entryPath);
const isLegacy = nextVersionRange && isLegacyNext(nextVersionRange);

View File

@@ -1060,9 +1060,9 @@ export async function serverBuild({
{
src: path.posix.join(
'^/',
entryDirectory,
trailingSlash ? '/' : '',
'$'
entryDirectory !== '.'
? `${entryDirectory}${trailingSlash ? '/$' : '$'}`
: '$'
),
has: [
{
@@ -1417,7 +1417,7 @@ export async function serverBuild({
src: `^${path.posix.join(
'/',
entryDirectory,
'/((?!.+\\.rsc).+)$'
'/((?!.+\\.rsc).+?)(?:/)?$'
)}`,
has: [
{

View File

@@ -0,0 +1,12 @@
/* eslint-env jest */
const path = require('path');
const { deployAndTest } = require('../../utils');
const ctx = {};
describe(`${__dirname.split(path.sep).pop()}`, () => {
it('should deploy and pass probe checks', async () => {
const info = await deployAndTest(__dirname);
Object.assign(ctx, info);
});
});

View File

@@ -0,0 +1,12 @@
{
"name": "test-root-dir",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@next/font": "13.1.2",
"next": "13.1.2",
"react": "18.2.0",
"react-dom": "18.2.0"
}
}

View File

@@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
module.exports = nextConfig;

View File

@@ -0,0 +1,12 @@
{
"name": "client",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {}
}

View File

@@ -0,0 +1,9 @@
import { NextResponse } from 'next/server';
export default function handler(req) {
return NextResponse.json({ hello: 'world', now: Date.now(), url: req.url });
}
export const config = {
runtime: 'edge',
};

View File

@@ -0,0 +1,5 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' });
}

View File

@@ -0,0 +1,3 @@
export default function Index() {
return <p>Index page</p>;
}

View File

@@ -0,0 +1,20 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next",
"config": {
"buildCommand": "cd packages/client && yarn build",
"outputDirectory": "/packages/client/.next"
}
}
],
"probes": [
{
"path": "/api/data/first",
"status": 200,
"mustContain": "\"hello\":\"world\""
}
]
}

View File

@@ -0,0 +1,7 @@
export default function AnotherPage(props) {
return (
<>
<p>hello from newroot/dashboard/another</p>
</>
);
}

View File

@@ -0,0 +1,10 @@
export default function Root({ children }) {
return (
<html className="this-is-another-document-html">
<head>
<title>{`hello world`}</title>
</head>
<body className="this-is-another-document-body">{children}</body>
</html>
);
}

View File

@@ -0,0 +1,7 @@
export default function ChangelogPage(props) {
return (
<>
<p>hello from app/dashboard/changelog</p>
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function HelloPage(props) {
return (
<>
<p>hello from app/dashboard/rootonly/hello</p>
</>
);
}

View File

@@ -0,0 +1,20 @@
'use client';
import { useState, useEffect } from 'react';
import style from './style.module.css';
import './style.css';
export default function ClientComponentRoute() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(1);
}, [count]);
return (
<>
<p className={style.red}>
hello from app/client-component-route. <b>count: {count}</b>
</p>
</>
);
}

View File

@@ -0,0 +1,3 @@
b {
color: blue;
}

View File

@@ -0,0 +1,3 @@
.red {
color: red;
}

View File

@@ -0,0 +1,20 @@
'use client';
import { useState, useEffect } from 'react';
import styles from './style.module.css';
import './style.css';
export default function ClientNestedLayout({ children }) {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(1);
}, []);
return (
<>
<h1 className={styles.red}>Client Nested. Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>{count}</button>
{children}
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function ClientPage() {
return (
<>
<p>hello from app/client-nested</p>
</>
);
}

View File

@@ -0,0 +1,3 @@
button {
color: red;
}

View File

@@ -0,0 +1,3 @@
.red {
color: red;
}

View File

@@ -0,0 +1,7 @@
export default function DeploymentsBreakdownPage(props) {
return (
<>
<p>hello from app/dashboard/(custom)/deployments/breakdown</p>
</>
);
}

View File

@@ -0,0 +1,8 @@
export default function CustomDashboardRootLayout({ children }) {
return (
<>
<h2>Custom dashboard</h2>
{children}
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function DeploymentsPage(props) {
return (
<>
<p>hello from app/dashboard/deployments/[id]. ID is: {props.params.id}</p>
</>
);
}

View File

@@ -0,0 +1,10 @@
export default function DeploymentsPage(props) {
return (
<>
<p>
hello from app/dashboard/deployments/[id]/settings. ID is:{' '}
{props.params.id}
</p>
</>
);
}

View File

@@ -0,0 +1,3 @@
export default function Page() {
return <p>catchall</p>;
}

View File

@@ -0,0 +1,7 @@
export default function DeploymentsInfoPage(props) {
return (
<>
<p>hello from app/dashboard/deployments/info</p>
</>
);
}

View File

@@ -0,0 +1,8 @@
export default function DeploymentsLayout({ message, children }) {
return (
<>
<h2>Deployments hello</h2>
{children}
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function IntegrationsPage(props) {
return (
<>
<p>hello from app/dashboard/integrations</p>
</>
);
}

View File

@@ -0,0 +1,8 @@
export default function DashboardLayout(props) {
return (
<>
<h1>Dashboard</h1>
{props.children}
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function DashboardPage(props) {
return (
<>
<p>hello from app/dashboard</p>
</>
);
}

View File

@@ -0,0 +1,11 @@
export default function IdLayout({ children, params }) {
return (
<>
<h3>
Id Layout. Params:{' '}
<span id="id-layout-params">{JSON.stringify(params)}</span>
</h3>
{children}
</>
);
}

View File

@@ -0,0 +1,11 @@
export default function IdPage({ children, params }) {
return (
<>
<p>
Id Page. Params:{' '}
<span id="id-page-params">{JSON.stringify(params)}</span>
</p>
{children}
</>
);
}

View File

@@ -0,0 +1,11 @@
export default function CategoryLayout({ children, params }) {
return (
<>
<h2>
Category Layout. Params:{' '}
<span id="category-layout-params">{JSON.stringify(params)}</span>{' '}
</h2>
{children}
</>
);
}

View File

@@ -0,0 +1,11 @@
export default function DynamicLayout({ children, params }) {
return (
<>
<h1>
Dynamic Layout. Params:{' '}
<span id="dynamic-layout-params">{JSON.stringify(params)}</span>
</h1>
{children}
</>
);
}

View File

@@ -0,0 +1,10 @@
export default function Root({ children }) {
return (
<html className="this-is-the-document-html">
<head>
<title>{`hello world`}</title>
</head>
<body className="this-is-the-document-body">{children}</body>
</html>
);
}

View File

@@ -0,0 +1,3 @@
export default function Page() {
return <p>index app page</p>;
}

View File

@@ -0,0 +1,7 @@
export default function DeploymentsPage(props) {
return (
<>
<p>hello from app/partial-match-[id]. ID is: {props.params.id}</p>
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function SharedComponentRoute() {
return (
<>
<p>hello from app/shared-component-route</p>
</>
);
}

View File

@@ -0,0 +1,9 @@
'use client';
export default function ShouldNotServeClientDotJs(props) {
return (
<>
<p>hello from app/should-not-serve-client</p>
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function ShouldNotServeServerDotJs(props) {
return (
<>
<p>hello from app/should-not-serve-server</p>
</>
);
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return <p id="loading-layout">Loading layout...</p>;
}

View File

@@ -0,0 +1,18 @@
import { use } from 'react';
async function getData() {
await new Promise(resolve => setTimeout(resolve, 1000));
return {
message: 'hello from slow layout',
};
}
export default function SlowLayout(props) {
const data = use(getData());
return (
<>
<p id="slow-layout-message">{data.message}</p>
{props.children}
</>
);
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return <p id="loading-page">Loading page...</p>;
}

View File

@@ -0,0 +1,13 @@
import { use } from 'react';
async function getData() {
await new Promise(resolve => setTimeout(resolve, 5000));
return {
message: 'hello from slow page',
};
}
export default function SlowPage(props) {
const data = use(getData());
return <h1 id="slow-page-message">{data.message}</h1>;
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return <p id="loading">Loading...</p>;
}

View File

@@ -0,0 +1,18 @@
import { use } from 'react';
async function getData() {
await new Promise(resolve => setTimeout(resolve, 5000));
return {
message: 'hello from slow layout',
};
}
export default function SlowLayout(props) {
const data = use(getData());
return (
<>
<p id="slow-layout-message">{data.message}</p>
{props.children}
</>
);
}

View File

@@ -0,0 +1,3 @@
export default function Page() {
return <h1 id="page-message">Hello World</h1>;
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return <p id="loading">Loading...</p>;
}

View File

@@ -0,0 +1,13 @@
import { use } from 'react';
async function getData() {
await new Promise(resolve => setTimeout(resolve, 5000));
return {
message: 'hello from slow page',
};
}
export default function SlowPage(props) {
const data = use(getData());
return <h1 id="slow-page-message">{data.message}</h1>;
}

View File

@@ -0,0 +1,10 @@
export const revalidate = 3;
export default function Page() {
return (
<>
<p>hello from /ssg</p>
<p>{Date.now()}</p>
</>
);
}

View File

@@ -0,0 +1,3 @@
export default function Page() {
return <p id="page">Page</p>;
}

View File

@@ -0,0 +1,12 @@
/* eslint-env jest */
const path = require('path');
const { deployAndTest } = require('../../utils');
const ctx = {};
describe(`${__dirname.split(path.sep).pop()}`, () => {
it('should deploy and pass probe checks', async () => {
const info = await deployAndTest(__dirname);
Object.assign(ctx, info);
});
});

View File

@@ -0,0 +1,16 @@
import { NextResponse } from 'next/server';
export function middleware(request) {
if (request.nextUrl.pathname === '/middleware-to-dashboard/') {
// TODO: this does not copy __flight__ and __flight_router_state_tree__
return NextResponse.rewrite(new URL('/dashboard', request.url));
}
if (
request.nextUrl.pathname === '/ssg/' &&
request.nextUrl.searchParams.get('override')
) {
request.nextUrl.pathname = '/overridden/';
return NextResponse.redirect(request.nextUrl);
}
}

View File

@@ -0,0 +1,14 @@
module.exports = {
trailingSlash: true,
experimental: {
appDir: true,
},
rewrites: async () => {
return [
{
source: '/rewritten-to-dashboard',
destination: '/dashboard',
},
];
},
};

View File

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

View File

@@ -0,0 +1,3 @@
export default function handler(req, res) {
return res.json({ hello: 'world' });
}

View File

@@ -0,0 +1,7 @@
export default function Page(props) {
return (
<>
<p>hello from pages/blog/[slug]</p>
</>
);
}

View File

@@ -0,0 +1 @@
hello world

View File

@@ -0,0 +1,169 @@
{
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
],
"probes": [
{
"path": "/dynamic/category-1/id-1/",
"status": 200,
"headers": {
"RSC": "1"
},
"fetchOptions": {
"redirect": "manual"
},
"mustContain": ":{",
"mustNotContain": "<html"
},
{
"path": "/ssg/",
"status": 200,
"mustContain": "hello from /ssg",
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
}
},
{
"path": "/ssg/",
"status": 200,
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
},
"headers": {
"RSC": "1"
},
"mustContain": ":{",
"mustNotContain": "<html"
},
{
"path": "/ssg/?override=1",
"status": 307,
"responseHeaders": {
"location": "/overridden/"
},
"fetchOptions": {
"redirect": "manual"
}
},
{
"path": "/ssg/?override=1",
"status": 307,
"responseHeaders": {
"location": "/overridden/"
},
"fetchOptions": {
"redirect": "manual"
}
},
{
"path": "/dashboard/deployments/123/settings/",
"status": 200,
"mustContain": "hello from app/dashboard/deployments/[id]/settings",
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
}
},
{
"path": "/dashboard/deployments/123/settings/",
"status": 200,
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
},
"headers": {
"RSC": "1"
},
"mustContain": ":{",
"mustNotContain": "<html"
},
{
"path": "/dashboard/deployments/catchall/something/",
"status": 200,
"mustContain": "catchall",
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
}
},
{
"path": "/dashboard/deployments/catchall/something/",
"status": 200,
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
},
"headers": {
"RSC": "1"
},
"mustContain": ":{",
"mustNotContain": "<html"
},
{
"path": "/dashboard/",
"status": 200,
"mustContain": "hello from app/dashboard",
"responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
}
},
{
"path": "/dashboard/",
"status": 200,
"headers": {
"RSC": "1"
},
"mustContain": ":{",
"mustNotContain": "<html"
},
{
"path": "/dashboard/",
"status": 200,
"headers": {
"RSC": "1"
},
"responseHeaders": {
"content-type": "application/octet-stream",
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch"
}
},
{
"path": "/dashboard/another/",
"status": 200,
"mustContain": "hello from newroot/dashboard/another"
},
{
"path": "/dashboard/deployments/123/",
"status": 200,
"mustContain": "hello from app/dashboard/deployments/[id]. ID is: <!-- -->123"
},
{
"path": "/",
"status": 200,
"mustContain": "index app page"
},
{
"path": "/blog/123/",
"status": 200,
"mustContain": "hello from pages/blog/[slug]"
},
{
"path": "/dynamic/category-1/id-1/",
"status": 200,
"mustContain": "{&quot;category&quot;:&quot;category-1&quot;,&quot;id&quot;:&quot;id-1&quot;}"
},
{
"path": "/dashboard/changelog/",
"status": 200,
"mustContain": "hello from app/dashboard/changelog"
},
{
"path": "/",
"status": 200,
"headers": {
"RSC": "1"
},
"mustContain": ":{",
"mustNotContain": "<html"
}
]
}

View File

@@ -24,18 +24,19 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
expect(isNaN(props.now)).toBe(false);
const { pageProps: data } = await fetch(
`${ctx.deploymentUrl}/docs/_next/data/testing-build-id/rewrite-to-another-site.json`
`${ctx.deploymentUrl}/docs/_next/data/testing-build-id/rewrite-to-another-site.json`,
{ headers: { 'x-nextjs-data': '1' } }
).then(res => res.json());
expect(isNaN(data.now)).toBe(false);
const revalidateRes = await fetch(
`${ctx.deploymentUrl}/docs/api/revalidate?urlPath=/docs/_sites/test-revalidate`
);
expect(revalidateRes.status).toBe(200);
expect(await revalidateRes.json()).toEqual({ revalidated: true });
await check(async () => {
const revalidateRes = await fetch(
`${ctx.deploymentUrl}/docs/api/revalidate?urlPath=/docs/_sites/test-revalidate`
);
expect(revalidateRes.status).toBe(200);
expect(await revalidateRes.json()).toEqual({ revalidated: true });
const newProps = await propsFromHtml();
console.log({ props, newProps });
@@ -52,7 +53,8 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
await check(async () => {
const { pageProps: newData } = await fetch(
`${ctx.deploymentUrl}/docs/_next/data/testing-build-id/rewrite-to-another-site.json`
`${ctx.deploymentUrl}/docs/_next/data/testing-build-id/rewrite-to-another-site.json`,
{ headers: { 'x-nextjs-data': '1' } }
).then(res => res.json());
console.log({ newData, data });
@@ -80,18 +82,19 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
expect(isNaN(props.now)).toBe(false);
const { pageProps: data } = await fetch(
`${ctx.deploymentUrl}/docs/_next/data/testing-build-id/financial.json?slug=financial`
`${ctx.deploymentUrl}/docs/_next/data/testing-build-id/financial.json?slug=financial`,
{ headers: { 'x-nextjs-data': '1' } }
).then(res => res.json());
expect(isNaN(data.now)).toBe(false);
const revalidateRes = await fetch(
`${ctx.deploymentUrl}/docs/api/revalidate?urlPath=/docs/financial`
);
expect(revalidateRes.status).toBe(200);
expect(await revalidateRes.json()).toEqual({ revalidated: true });
await check(async () => {
const revalidateRes = await fetch(
`${ctx.deploymentUrl}/docs/api/revalidate?urlPath=/docs/financial`
);
expect(revalidateRes.status).toBe(200);
expect(await revalidateRes.json()).toEqual({ revalidated: true });
const newProps = await propsFromHtml();
console.log({ props, newProps });
@@ -108,7 +111,8 @@ describe(`${__dirname.split(path.sep).pop()}`, () => {
await check(async () => {
const { pageProps: newData } = await fetch(
`${ctx.deploymentUrl}/docs/_next/data/testing-build-id/financial.json?slug=financial`
`${ctx.deploymentUrl}/docs/_next/data/testing-build-id/financial.json?slug=financial`,
{ headers: { 'x-nextjs-data': '1' } }
).then(res => res.json());
console.log({ newData, data });

View File

@@ -7,6 +7,15 @@
}
],
"probes": [
{
"path": "/docs/_next/data/testing-build-id/index.json",
"status": 200,
"headers": {
"x-nextjs-data": 1
},
"mustContain": "\"pageProps\":{",
"mustNotContain": "<html"
},
{
"path": "/docs/_next/data/testing-build-id/dynamic/static.json",
"status": 200,
@@ -44,7 +53,7 @@
"x-nextjs-data": 1
},
"mustContain": "site\":\"subdomain-1\"",
"mustNotContain": "<html>"
"mustNotContain": "<html"
},
{
"path": "/docs/redirect-me",
@@ -113,7 +122,7 @@
"fetchOptions": {
"redirect": "manual"
},
"mustNotContain": "<html>",
"mustNotContain": "<html",
"mustContain": "\"site\":\"subdomain-1\""
}
]

View File

@@ -2,8 +2,12 @@ process.env.NEXT_TELEMETRY_DISABLED = '1';
const path = require('path');
const fs = require('fs-extra');
const builder = require('../../');
const {
createRunBuildLambda,
} = require('../../../../test/lib/run-build-lambda');
const runBuildLambda = require('../../../../test/lib/run-build-lambda');
const runBuildLambda = createRunBuildLambda(builder);
jest.setTimeout(360000);

View File

@@ -6,10 +6,13 @@ import type { Context } from '../types';
import { duplicateWithConfig } from '../utils';
import fs from 'fs-extra';
import path from 'path';
import runBuildLambda from '../../../../test/lib/run-build-lambda';
import * as builder from '../../';
import { createRunBuildLambda } from '../../../../test/lib/run-build-lambda';
import { EdgeFunction, Files, streamToBuffer } from '@vercel/build-utils';
import { createHash } from 'crypto';
const runBuildLambda = createRunBuildLambda(builder);
const SIMPLE_PROJECT = path.resolve(
__dirname,
'..',

View File

@@ -1,6 +0,0 @@
describe('export', () => {
it('should require by path main', async () => {
const main = require('@vercel/next');
expect(main).toBeDefined();
});
});

View File

@@ -7,7 +7,8 @@
"module": "commonjs",
"outDir": "dist",
"sourceMap": false,
"declaration": false
"declaration": false,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node-bridge",
"version": "3.1.9",
"version": "3.1.10",
"license": "MIT",
"main": "./index.js",
"repository": {

View File

@@ -8,7 +8,8 @@
"strict": true,
"target": "ES2020",
"declaration": true,
"module": "commonjs"
"module": "commonjs",
"skipLibCheck": true
},
"include": ["helpers.ts", "bridge.js", "launcher.js"],
"exclude": ["node_modules"]

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "2.8.13",
"version": "2.8.15",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -32,7 +32,7 @@
"@edge-runtime/vm": "2.0.0",
"@types/node": "14.18.33",
"@vercel/build-utils": "5.9.0",
"@vercel/node-bridge": "3.1.9",
"@vercel/node-bridge": "3.1.10",
"@vercel/static-config": "2.0.11",
"edge-runtime": "2.0.0",
"esbuild": "0.14.47",

View File

@@ -1,6 +1,6 @@
import _ts from 'typescript';
import { NowBuildError } from '@vercel/build-utils';
import { relative, basename, resolve, dirname } from 'path';
import { relative, basename, dirname } from 'path';
import type _ts from 'typescript';
/*
* Fork of TS-Node - https://github.com/TypeStrong/ts-node
@@ -133,21 +133,20 @@ export function register(opts: Options = {}): Register {
// Require the TypeScript compiler and configuration.
const cwd = options.basePath || process.cwd();
const nowNodeBase = resolve(__dirname, '..', '..', '..');
let compiler: string;
const require_ = eval('require');
try {
compiler = require_.resolve(options.compiler || 'typescript', {
paths: [options.project || cwd, nowNodeBase],
paths: [options.project || cwd],
});
} catch (e) {
compiler = 'typescript';
}
//eslint-disable-next-line @typescript-eslint/no-var-requires
const ts: typeof _ts = require_(compiler);
if (compiler.startsWith(nowNodeBase)) {
if (compiler === 'typescript') {
console.log(
`Using built-in TypeScript ${ts.version} since "typescript" missing from "devDependencies"`
`Using built-in TypeScript ${ts.version} since "typescript" is missing from "devDependencies"`
);
} else {
console.log(`Using TypeScript ${ts.version} (local user-provided)`);

View File

@@ -4,7 +4,8 @@
"probes": [
{
"path": "/",
"mustContain": "declare namespace Test"
"mustContain": "declare namespace Test",
"logMustContain": "Using built-in TypeScript"
}
]
}

View File

@@ -24,7 +24,8 @@
"probes": [
{
"path": "/",
"mustContain": "`"
"mustContain": "`",
"logMustContain": "Using TypeScript 3.5.3 (local user-provided)"
},
{
"path": "/functions/es5.ts",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/remix",
"version": "1.2.5",
"version": "1.2.7",
"license": "MIT",
"main": "./dist/index.js",
"homepage": "https://vercel.com/docs",
@@ -11,9 +11,8 @@
},
"scripts": {
"build": "node build.js",
"test-integration-once": "pnpm test test/test.js",
"test": "jest --env node --verbose --bail --runInBand",
"test-unit": "pnpm test test/build.test.ts"
"test-integration-once": "pnpm test test/integration.test.ts",
"test": "jest --env node --verbose --bail --runInBand"
},
"files": [
"dist",
@@ -26,6 +25,6 @@
"@types/jest": "27.5.1",
"@types/node": "14.18.33",
"@vercel/build-utils": "5.9.0",
"typescript": "4.6.4"
"typescript": "4.9.4"
}
}

View File

@@ -24,7 +24,6 @@ import type {
} from '@vercel/build-utils';
import { nodeFileTrace } from '@vercel/nft';
import type { AppConfig } from './types';
import { pathToFileURL } from 'url';
import { findConfig } from './utils';
// Name of the Remix runtime adapter npm package for Vercel
@@ -169,9 +168,7 @@ export const build: BuildV2 = async ({
try {
if (remixConfigFile) {
const remixConfigModule = await import(
pathToFileURL(remixConfigFile).href
);
const remixConfigModule = await eval('import(remixConfigFile)');
const remixConfig: AppConfig = remixConfigModule?.default || {};
// If `serverBuildTarget === 'vercel'` then Remix will output a handler

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