Compare commits

..

10 Commits

Author SHA1 Message Date
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
Steven
4ccdcde463 Publish Stable
- @vercel/build-utils@5.9.0
 - vercel@28.12.6
 - @vercel/client@12.3.1
 - @vercel/fs-detectors@3.7.5
 - @vercel/go@2.2.30
 - @vercel/hydrogen@0.0.44
 - @vercel/next@3.3.15
 - @vercel/node@2.8.13
 - @vercel/python@3.1.40
 - @vercel/redwood@1.0.51
 - @vercel/remix@1.2.5
 - @vercel/ruby@1.3.56
 - @vercel/static-build@1.1.7
2023-01-17 19:25:26 -05:00
Nathan Rajlich
22d3ee160b [build-utils] Add includeDirectories option to glob() (#9245)
Instead of always including empty directories in `glob()`, make it an
opt-in behavior because technically it's a breaking change to include
them by default.
2023-01-17 19:21:40 -05:00
50 changed files with 9935 additions and 1690 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

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "5.8.3",
"version": "5.9.0",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",

View File

@@ -6,7 +6,9 @@ import { lstat, Stats } from 'fs-extra';
import { normalizePath } from './normalize-path';
import FileFsRef from '../file-fs-ref';
export type GlobOptions = vanillaGlob_.IOptions;
export interface GlobOptions extends vanillaGlob_.IOptions {
includeDirectories?: boolean;
}
const vanillaGlob = promisify(vanillaGlob_);
@@ -73,18 +75,20 @@ export default async function glob(
}
// Add empty directory entries
for (const relativePath of dirs) {
if (dirsWithEntries.has(relativePath)) continue;
if (options.includeDirectories) {
for (const relativePath of dirs) {
if (dirsWithEntries.has(relativePath)) continue;
let finalPath = relativePath;
if (mountpoint) {
finalPath = path.join(mountpoint, finalPath);
let finalPath = relativePath;
if (mountpoint) {
finalPath = path.join(mountpoint, finalPath);
}
const fsPath = normalizePath(path.join(options.cwd, relativePath));
const stat = statCache[fsPath];
results[finalPath] = new FileFsRef({ mode: stat.mode, fsPath });
}
const fsPath = normalizePath(path.join(options.cwd, relativePath));
const stat = statCache[fsPath];
results[finalPath] = new FileFsRef({ mode: stat.mode, fsPath });
}
return results;

View File

@@ -4,7 +4,7 @@ import { tmpdir } from 'os';
import { glob, isDirectory } from '../src';
describe('glob()', () => {
it('should return entries for empty directories', async () => {
it('should not return entries for empty directories by default', async () => {
const dir = await fs.mkdtemp(join(tmpdir(), 'build-utils-test'));
try {
await Promise.all([
@@ -19,6 +19,33 @@ describe('glob()', () => {
]);
const files = await glob('**', dir);
const fileNames = Object.keys(files).sort();
expect(fileNames).toHaveLength(2);
expect(fileNames).toEqual(['dir-with-file/data.json', 'root.txt']);
expect(isDirectory(files['dir-with-file/data.json'].mode)).toEqual(false);
expect(isDirectory(files['root.txt'].mode)).toEqual(false);
expect(files['dir-with-file']).toBeUndefined();
expect(files['another/subdir']).toBeUndefined();
expect(files['empty-dir']).toBeUndefined();
} finally {
await fs.remove(dir);
}
});
it('should return entries for empty directories with `includeDirectories: true`', async () => {
const dir = await fs.mkdtemp(join(tmpdir(), 'build-utils-test'));
try {
await Promise.all([
fs.writeFile(join(dir, 'root.txt'), 'file at the root'),
fs.mkdirp(join(dir, 'empty-dir')),
fs
.mkdirp(join(dir, 'dir-with-file'))
.then(() =>
fs.writeFile(join(dir, 'dir-with-file/data.json'), '{"a":"b"}')
),
fs.mkdirp(join(dir, 'another/subdir')),
]);
const files = await glob('**', { cwd: dir, includeDirectories: true });
const fileNames = Object.keys(files).sort();
expect(fileNames).toHaveLength(4);
expect(fileNames).toEqual([
'another/subdir',

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "28.12.5",
"version": "28.12.8",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -41,16 +41,16 @@
"node": ">= 14"
},
"dependencies": {
"@vercel/build-utils": "5.8.3",
"@vercel/go": "2.2.29",
"@vercel/hydrogen": "0.0.43",
"@vercel/next": "3.3.14",
"@vercel/node": "2.8.12",
"@vercel/python": "3.1.39",
"@vercel/redwood": "1.0.50",
"@vercel/remix": "1.2.4",
"@vercel/ruby": "1.3.55",
"@vercel/static-build": "1.1.6"
"@vercel/build-utils": "5.9.0",
"@vercel/go": "2.2.30",
"@vercel/hydrogen": "0.0.44",
"@vercel/next": "3.3.17",
"@vercel/node": "2.8.14",
"@vercel/python": "3.1.40",
"@vercel/redwood": "1.0.51",
"@vercel/remix": "1.2.7",
"@vercel/ruby": "1.3.56",
"@vercel/static-build": "1.1.7"
},
"devDependencies": {
"@alex_neo/jest-expect-message": "1.0.5",
@@ -93,10 +93,10 @@
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.3.0",
"@vercel/client": "12.3.2",
"@vercel/error-utils": "1.0.8",
"@vercel/frameworks": "1.2.4",
"@vercel/fs-detectors": "3.7.4",
"@vercel/fs-detectors": "3.7.5",
"@vercel/fun": "1.0.4",
"@vercel/ncc": "0.24.0",
"@vercel/routing-utils": "2.1.8",

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.0",
"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",
@@ -43,7 +43,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "5.8.3",
"@vercel/build-utils": "5.9.0",
"@vercel/routing-utils": "2.1.8",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/fs-detectors",
"version": "3.7.4",
"version": "3.7.5",
"description": "Vercel filesystem detectors",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -35,7 +35,7 @@
"@types/minimatch": "3.0.5",
"@types/node": "14.18.33",
"@types/semver": "7.3.10",
"@vercel/build-utils": "5.8.3",
"@vercel/build-utils": "5.9.0",
"typescript": "4.3.4"
}
}

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.1",
"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.14",
"@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,108 @@
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,
};
console.log(state);
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,
};
console.log(config);
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/go",
"version": "2.2.29",
"version": "2.2.30",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
@@ -36,7 +36,7 @@
"@types/node": "14.18.33",
"@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0",
"@vercel/build-utils": "5.8.3",
"@vercel/build-utils": "5.9.0",
"@vercel/ncc": "0.24.0",
"async-retry": "1.3.1",
"execa": "^1.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/hydrogen",
"version": "0.0.43",
"version": "0.0.44",
"license": "MIT",
"main": "./dist/index.js",
"homepage": "https://vercel.com/docs",
@@ -21,7 +21,7 @@
"devDependencies": {
"@types/jest": "27.5.1",
"@types/node": "14.18.33",
"@vercel/build-utils": "5.8.3",
"@vercel/build-utils": "5.9.0",
"@vercel/static-config": "2.0.11",
"execa": "3.2.0",
"fs-extra": "11.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "3.3.14",
"version": "3.3.17",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -45,7 +45,7 @@
"@types/semver": "6.0.0",
"@types/text-table": "0.2.1",
"@types/webpack-sources": "3.2.0",
"@vercel/build-utils": "5.8.3",
"@vercel/build-utils": "5.9.0",
"@vercel/nft": "0.22.5",
"@vercel/routing-utils": "2.1.8",
"async-sema": "3.0.1",

View File

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

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

@@ -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.12",
"version": "2.8.14",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -31,8 +31,8 @@
"dependencies": {
"@edge-runtime/vm": "2.0.0",
"@types/node": "14.18.33",
"@vercel/build-utils": "5.8.3",
"@vercel/node-bridge": "3.1.9",
"@vercel/build-utils": "5.9.0",
"@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 @@
{
"name": "@vercel/python",
"version": "3.1.39",
"version": "3.1.40",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -23,7 +23,7 @@
"@types/execa": "^0.9.0",
"@types/jest": "27.4.1",
"@types/node": "14.18.33",
"@vercel/build-utils": "5.8.3",
"@vercel/build-utils": "5.9.0",
"@vercel/ncc": "0.24.0",
"execa": "^1.0.0",
"typescript": "4.3.4"

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/redwood",
"version": "1.0.50",
"version": "1.0.51",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs",
@@ -27,7 +27,7 @@
"@types/aws-lambda": "8.10.19",
"@types/node": "14.18.33",
"@types/semver": "6.0.0",
"@vercel/build-utils": "5.8.3",
"@vercel/build-utils": "5.9.0",
"execa": "3.2.0",
"fs-extra": "11.1.0",
"typescript": "4.3.4"

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/remix",
"version": "1.2.4",
"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",
@@ -25,7 +24,7 @@
"devDependencies": {
"@types/jest": "27.5.1",
"@types/node": "14.18.33",
"@vercel/build-utils": "5.8.3",
"typescript": "4.6.4"
"@vercel/build-utils": "5.9.0",
"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

View File

@@ -1,5 +1,4 @@
import { dirname, join, relative } from 'path';
import { pathToFileURL } from 'url';
import { glob } from '@vercel/build-utils';
import type { PrepareCache } from '@vercel/build-utils';
import type { AppConfig } from './types';
@@ -16,9 +15,7 @@ export const prepareCache: PrepareCache = async ({
try {
const remixConfigFile = findConfig(entrypointFsDirname, 'remix.config');
if (remixConfigFile) {
const remixConfigModule = await import(
pathToFileURL(remixConfigFile).href
);
const remixConfigModule = await eval('import(remixConfigFile)');
const remixConfig: AppConfig = remixConfigModule?.default || {};
if (remixConfig.cacheDirectory) {
cacheDirectory = remixConfig.cacheDirectory;

View File

@@ -1,100 +0,0 @@
import assert from 'assert';
import { join } from 'path';
import { NodejsLambda } from '@vercel/build-utils';
import { build, prepareCache } from '../src';
jest.setTimeout(10 * 60 * 1000);
const fixture = (name: string) => join(__dirname, 'fixtures', name);
describe('build()', () => {
it('should build fixture "01-remix-basics"', async () => {
if (process.platform === 'win32') {
// Fails on Windows (https://github.com/vercel/vercel/runs/6484955910):
// > 'remix' is not recognized as an internal or external command,
console.log('Skipping test on Windows.');
return;
}
const workPath = fixture('01-remix-basics');
const result = await build({
files: {},
entrypoint: 'package.json',
workPath,
repoRootPath: workPath,
config: {},
});
assert('output' in result);
const names = Object.keys(result.output);
expect('favicon.ico' in result.output).toEqual(true);
expect(names.filter(n => n.startsWith('build/')).length).toBeGreaterThan(5);
const render = result.output.render;
expect(render.type).toEqual('Lambda');
expect((render as NodejsLambda).launcherType).toEqual('Nodejs');
const cache = await prepareCache({
files: {},
entrypoint: 'package.json',
workPath,
repoRootPath: workPath,
config: {},
});
const cacheNames = Object.keys(cache);
// Assert `node_modules` was cached
const nodeModulesFiles = cacheNames.filter(n =>
n.startsWith('node_modules/')
);
expect(nodeModulesFiles.length).toBeGreaterThanOrEqual(10);
// Assert `.cache` was cached
const dotCacheFiles = cacheNames.filter(n => n.startsWith('.cache/'));
expect(dotCacheFiles.length).toBeGreaterThanOrEqual(4);
});
it('should build fixture "02-remix-basics-mjs"', async () => {
if (process.platform === 'win32') {
// Fails on Windows (https://github.com/vercel/vercel/runs/6484955910):
// > 'remix' is not recognized as an internal or external command,
console.log('Skipping test on Windows.');
return;
}
const workPath = fixture('02-remix-basics-mjs');
const result = await build({
files: {},
entrypoint: 'package.json',
workPath,
repoRootPath: workPath,
config: {},
});
assert('output' in result);
const names = Object.keys(result.output);
expect('favicon.ico' in result.output).toEqual(true);
expect(names.filter(n => n.startsWith('build/')).length).toBeGreaterThan(5);
const render = result.output.render;
expect(render.type).toEqual('Lambda');
expect((render as NodejsLambda).launcherType).toEqual('Nodejs');
const cache = await prepareCache({
files: {},
entrypoint: 'package.json',
workPath,
repoRootPath: workPath,
config: {},
});
const cacheNames = Object.keys(cache);
// Assert `node_modules` was cached
const nodeModulesFiles = cacheNames.filter(n =>
n.startsWith('node_modules/')
);
expect(nodeModulesFiles.length).toBeGreaterThanOrEqual(10);
// Assert `.cache` was cached
const dotCacheFiles = cacheNames.filter(n => n.startsWith('.cache/'));
expect(dotCacheFiles.length).toBeGreaterThanOrEqual(4);
});
});

View File

@@ -1,5 +1,5 @@
const fs = require('fs');
const path = require('path');
const { join } = require('path');
const {
testDeployment,
@@ -7,14 +7,21 @@ const {
jest.setTimeout(12 * 60 * 1000);
const fixturesPath = path.resolve(__dirname, 'fixtures');
const fixturesPath = join(__dirname, 'fixtures');
const exampleAbsolute = (name: string) =>
join(__dirname, '..', '..', '..', 'examples', name);
// eslint-disable-next-line no-restricted-syntax
for (const fixture of fs.readdirSync(fixturesPath)) {
// eslint-disable-next-line no-loop-func
it(`should build ${fixture}`, async () => {
await expect(
testDeployment(path.join(fixturesPath, fixture))
testDeployment(join(fixturesPath, fixture))
).resolves.toBeDefined();
});
}
it(`should build remix example`, async () => {
const example = exampleAbsolute('remix');
await expect(testDeployment(example)).resolves.toBeDefined();
});

View File

@@ -1,7 +1,7 @@
{
"name": "@vercel/ruby",
"author": "Nathan Cahill <nathan@nathancahill.com>",
"version": "1.3.55",
"version": "1.3.56",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
@@ -22,7 +22,7 @@
"devDependencies": {
"@types/fs-extra": "8.0.0",
"@types/semver": "6.0.0",
"@vercel/build-utils": "5.8.3",
"@vercel/build-utils": "5.9.0",
"@vercel/ncc": "0.24.0",
"execa": "2.0.4",
"fs-extra": "^7.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/static-build",
"version": "1.1.6",
"version": "1.1.7",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/build-step",
@@ -37,7 +37,7 @@
"@types/node": "14.18.33",
"@types/node-fetch": "2.5.4",
"@types/promise-timeout": "1.3.0",
"@vercel/build-utils": "5.8.3",
"@vercel/build-utils": "5.9.0",
"@vercel/frameworks": "1.2.4",
"@vercel/ncc": "0.24.0",
"@vercel/routing-utils": "2.1.8",

9799
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,11 @@
"outputMode": "new-only",
"outputs": ["helpers.js", "source-map-support.js"]
},
"@vercel/gatsby-plugin-vercel-builder#build": {
"dependsOn": ["^build"],
"outputMode": "new-only",
"outputs": ["dist/**", "gatsby-node.js"]
},
"vercel#build": {
"dependsOn": ["^build"],
"outputMode": "new-only",

View File

@@ -60,7 +60,8 @@ async function getChunkedTests() {
([packagePathAndName, scriptNames]) => {
const [packagePath, packageName] = packagePathAndName.split(',');
return Object.entries(scriptNames).flatMap(([scriptName, testPaths]) => {
return intoChunks(NUMBER_OF_CHUNKS, testPaths).flatMap(
const sortedTestPaths = testPaths.sort((a, b) => a.localeCompare(b));
return intoChunks(NUMBER_OF_CHUNKS, sortedTestPaths).flatMap(
(chunk, chunkNumber, allChunks) => {
const runners = runnersMap.get(scriptName) || ['ubuntu-latest'];
@@ -145,7 +146,7 @@ function intoChunks(totalChunks, arr) {
async function main() {
try {
const chunks = await getChunkedTests(process.argv[2]);
const chunks = await getChunkedTests();
// TODO: pack and build the runtimes for each package and cache it so we only deploy it once
console.log(JSON.stringify(chunks));
} catch (e) {