mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-24 19:00:03 +00:00
Compare commits
11 Commits
@vercel/fr
...
@vercel/cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79ef5c3724 | ||
|
|
02ff265074 | ||
|
|
ae89b8b8be | ||
|
|
4ccdcde463 | ||
|
|
22d3ee160b | ||
|
|
6d97e1673e | ||
|
|
522565f6e5 | ||
|
|
07bf81ab10 | ||
|
|
35024a4e3a | ||
|
|
c1df9bca19 | ||
|
|
4c1cdd1f0f |
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
29
packages/build-utils/test/unit.glob.test.ts
vendored
29
packages/build-utils/test/unit.glob.test.ts
vendored
@@ -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',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "28.12.4",
|
||||
"version": "28.12.7",
|
||||
"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.11",
|
||||
"@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.16",
|
||||
"@vercel/node": "2.8.14",
|
||||
"@vercel/python": "3.1.40",
|
||||
"@vercel/redwood": "1.0.51",
|
||||
"@vercel/remix": "1.2.6",
|
||||
"@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.2.31",
|
||||
"@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",
|
||||
|
||||
@@ -1,16 +1,4 @@
|
||||
import { join } from 'path';
|
||||
import ms from 'ms';
|
||||
import fs, { mkdirp } from 'fs-extra';
|
||||
|
||||
const {
|
||||
exec,
|
||||
fetch,
|
||||
fixture,
|
||||
sleep,
|
||||
testFixture,
|
||||
testFixtureStdio,
|
||||
validateResponseHeaders,
|
||||
} = require('./utils.js');
|
||||
const { exec, fixture, testFixture, testFixtureStdio } = require('./utils.js');
|
||||
|
||||
test('[vercel dev] validate redirects', async () => {
|
||||
const directory = fixture('invalid-redirects');
|
||||
@@ -122,262 +110,3 @@ test(
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cleanUrls serve correct content',
|
||||
testFixtureStdio('test-clean-urls', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/about', 'About Page');
|
||||
await testPath(200, '/sub', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/index.html', 'Redirecting to / (308)', {
|
||||
Location: '/',
|
||||
});
|
||||
await testPath(308, '/about.html', 'Redirecting to /about (308)', {
|
||||
Location: '/about',
|
||||
});
|
||||
await testPath(308, '/sub/index.html', 'Redirecting to /sub (308)', {
|
||||
Location: '/sub',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html',
|
||||
'Redirecting to /sub/another (308)',
|
||||
{ Location: '/sub/another' }
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cleanUrls serve correct content when using `outputDirectory`',
|
||||
testFixtureStdio(
|
||||
'test-clean-urls-with-output-directory',
|
||||
async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/about', 'About Page');
|
||||
await testPath(200, '/sub', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/index.html', 'Redirecting to / (308)', {
|
||||
Location: '/',
|
||||
});
|
||||
await testPath(308, '/about.html', 'Redirecting to /about (308)', {
|
||||
Location: '/about',
|
||||
});
|
||||
await testPath(308, '/sub/index.html', 'Redirecting to /sub (308)', {
|
||||
Location: '/sub',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html',
|
||||
'Redirecting to /sub/another (308)',
|
||||
{ Location: '/sub/another' }
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] should serve custom 404 when `cleanUrls: true`',
|
||||
testFixtureStdio('test-clean-urls-custom-404', async (testPath: any) => {
|
||||
await testPath(200, '/', 'This is the home page');
|
||||
await testPath(200, '/about', 'The about page');
|
||||
await testPath(200, '/contact/me', 'Contact Me Subdirectory');
|
||||
await testPath(404, '/nothing', 'Custom 404 Page');
|
||||
await testPath(404, '/nothing/', 'Custom 404 Page');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cleanUrls and trailingSlash serve correct content',
|
||||
testFixtureStdio('test-clean-urls-trailing-slash', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/about/', 'About Page');
|
||||
await testPath(200, '/sub/', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another/', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
//TODO: fix this test so that location is `/` instead of `//`
|
||||
//await testPath(308, '/index.html', 'Redirecting to / (308)', { Location: '/' });
|
||||
await testPath(308, '/about.html', 'Redirecting to /about/ (308)', {
|
||||
Location: '/about/',
|
||||
});
|
||||
await testPath(308, '/sub/index.html', 'Redirecting to /sub/ (308)', {
|
||||
Location: '/sub/',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html',
|
||||
'Redirecting to /sub/another/ (308)',
|
||||
{
|
||||
Location: '/sub/another/',
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cors headers work with OPTIONS',
|
||||
testFixtureStdio('test-cors-routes', async (testPath: any) => {
|
||||
const headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers':
|
||||
'Content-Type, Authorization, Accept, Content-Length, Origin, User-Agent',
|
||||
'Access-Control-Allow-Methods':
|
||||
'GET, POST, OPTIONS, HEAD, PATCH, PUT, DELETE',
|
||||
};
|
||||
await testPath(200, '/', 'status api', headers, { method: 'GET' });
|
||||
await testPath(200, '/', 'status api', headers, { method: 'POST' });
|
||||
await testPath(200, '/api/status.js', 'status api', headers, {
|
||||
method: 'GET',
|
||||
});
|
||||
await testPath(200, '/api/status.js', 'status api', headers, {
|
||||
method: 'POST',
|
||||
});
|
||||
await testPath(204, '/', '', headers, { method: 'OPTIONS' });
|
||||
await testPath(204, '/api/status.js', '', headers, { method: 'OPTIONS' });
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test trailingSlash true serve correct content',
|
||||
testFixtureStdio('test-trailing-slash', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/index.html', 'Index Page');
|
||||
await testPath(200, '/about.html', 'About Page');
|
||||
await testPath(200, '/sub/', 'Sub Index Page');
|
||||
await testPath(200, '/sub/index.html', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another.html', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/about.html/', 'Redirecting to /about.html (308)', {
|
||||
Location: '/about.html',
|
||||
});
|
||||
await testPath(308, '/style.css/', 'Redirecting to /style.css (308)', {
|
||||
Location: '/style.css',
|
||||
});
|
||||
await testPath(308, '/sub', 'Redirecting to /sub/ (308)', {
|
||||
Location: '/sub/',
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] should serve custom 404 when `trailingSlash: true`',
|
||||
testFixtureStdio('test-trailing-slash-custom-404', async (testPath: any) => {
|
||||
await testPath(200, '/', 'This is the home page');
|
||||
await testPath(200, '/about.html', 'The about page');
|
||||
await testPath(200, '/contact/', 'Contact Subdirectory');
|
||||
await testPath(404, '/nothing/', 'Custom 404 Page');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test trailingSlash false serve correct content',
|
||||
testFixtureStdio('test-trailing-slash-false', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/index.html', 'Index Page');
|
||||
await testPath(200, '/about.html', 'About Page');
|
||||
await testPath(200, '/sub', 'Sub Index Page');
|
||||
await testPath(200, '/sub/index.html', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another.html', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/about.html/', 'Redirecting to /about.html (308)', {
|
||||
Location: '/about.html',
|
||||
});
|
||||
await testPath(308, '/sub/', 'Redirecting to /sub (308)', {
|
||||
Location: '/sub',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html/',
|
||||
'Redirecting to /sub/another.html (308)',
|
||||
{
|
||||
Location: '/sub/another.html',
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] throw when invalid builder routes detected',
|
||||
testFixtureStdio(
|
||||
'invalid-builder-routes',
|
||||
async (testPath: any) => {
|
||||
await testPath(
|
||||
500,
|
||||
'/',
|
||||
/Route at index 0 has invalid `src` regular expression/m
|
||||
);
|
||||
},
|
||||
{ skipDeploy: true }
|
||||
)
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] support legacy `@now` scope runtimes',
|
||||
testFixtureStdio('legacy-now-runtime', async (testPath: any) => {
|
||||
await testPath(200, '/', /A simple deployment with the Vercel API!/m);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] 00-list-directory',
|
||||
testFixtureStdio(
|
||||
'00-list-directory',
|
||||
async (testPath: any) => {
|
||||
await testPath(200, '/', /Files within/m);
|
||||
await testPath(200, '/', /test[0-3]\.txt/m);
|
||||
await testPath(200, '/', /\.well-known/m);
|
||||
await testPath(200, '/.well-known/keybase.txt', 'proof goes here');
|
||||
},
|
||||
{ projectSettings: { directoryListing: true } }
|
||||
)
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] 01-node',
|
||||
testFixtureStdio('01-node', async (testPath: any) => {
|
||||
await testPath(200, '/', /A simple deployment with the Vercel API!/m);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] add a `api/fn.ts` when `api` does not exist at startup`',
|
||||
testFixtureStdio('no-api', async (_testPath: any, port: any) => {
|
||||
const directory = fixture('no-api');
|
||||
const apiDir = join(directory, 'api');
|
||||
|
||||
try {
|
||||
{
|
||||
const response = await fetch(`http://localhost:${port}/api/new-file`);
|
||||
validateResponseHeaders(response);
|
||||
expect(response.status).toBe(404);
|
||||
}
|
||||
|
||||
const fileContents = `
|
||||
export const config = {
|
||||
runtime: 'edge'
|
||||
}
|
||||
|
||||
export default async function edge(request, event) {
|
||||
return new Response('from new file');
|
||||
}
|
||||
`;
|
||||
|
||||
await mkdirp(apiDir);
|
||||
await fs.writeFile(join(apiDir, 'new-file.js'), fileContents);
|
||||
|
||||
// Wait until file events have been processed
|
||||
await sleep(ms('1s'));
|
||||
|
||||
{
|
||||
const response = await fetch(`http://localhost:${port}/api/new-file`);
|
||||
validateResponseHeaders(response);
|
||||
const body = await response.text();
|
||||
expect(body.trim()).toBe('from new file');
|
||||
}
|
||||
} finally {
|
||||
await fs.remove(apiDir);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
270
packages/cli/test/dev/integration-5.test.ts
Normal file
270
packages/cli/test/dev/integration-5.test.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { join } from 'path';
|
||||
import ms from 'ms';
|
||||
import fs, { mkdirp } from 'fs-extra';
|
||||
|
||||
const {
|
||||
fetch,
|
||||
fixture,
|
||||
sleep,
|
||||
testFixtureStdio,
|
||||
validateResponseHeaders,
|
||||
} = require('./utils.js');
|
||||
|
||||
test(
|
||||
'[vercel dev] test cleanUrls serve correct content',
|
||||
testFixtureStdio('test-clean-urls', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/about', 'About Page');
|
||||
await testPath(200, '/sub', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/index.html', 'Redirecting to / (308)', {
|
||||
Location: '/',
|
||||
});
|
||||
await testPath(308, '/about.html', 'Redirecting to /about (308)', {
|
||||
Location: '/about',
|
||||
});
|
||||
await testPath(308, '/sub/index.html', 'Redirecting to /sub (308)', {
|
||||
Location: '/sub',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html',
|
||||
'Redirecting to /sub/another (308)',
|
||||
{ Location: '/sub/another' }
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cleanUrls serve correct content when using `outputDirectory`',
|
||||
testFixtureStdio(
|
||||
'test-clean-urls-with-output-directory',
|
||||
async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/about', 'About Page');
|
||||
await testPath(200, '/sub', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/index.html', 'Redirecting to / (308)', {
|
||||
Location: '/',
|
||||
});
|
||||
await testPath(308, '/about.html', 'Redirecting to /about (308)', {
|
||||
Location: '/about',
|
||||
});
|
||||
await testPath(308, '/sub/index.html', 'Redirecting to /sub (308)', {
|
||||
Location: '/sub',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html',
|
||||
'Redirecting to /sub/another (308)',
|
||||
{ Location: '/sub/another' }
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] should serve custom 404 when `cleanUrls: true`',
|
||||
testFixtureStdio('test-clean-urls-custom-404', async (testPath: any) => {
|
||||
await testPath(200, '/', 'This is the home page');
|
||||
await testPath(200, '/about', 'The about page');
|
||||
await testPath(200, '/contact/me', 'Contact Me Subdirectory');
|
||||
await testPath(404, '/nothing', 'Custom 404 Page');
|
||||
await testPath(404, '/nothing/', 'Custom 404 Page');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cleanUrls and trailingSlash serve correct content',
|
||||
testFixtureStdio('test-clean-urls-trailing-slash', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/about/', 'About Page');
|
||||
await testPath(200, '/sub/', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another/', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
//TODO: fix this test so that location is `/` instead of `//`
|
||||
//await testPath(308, '/index.html', 'Redirecting to / (308)', { Location: '/' });
|
||||
await testPath(308, '/about.html', 'Redirecting to /about/ (308)', {
|
||||
Location: '/about/',
|
||||
});
|
||||
await testPath(308, '/sub/index.html', 'Redirecting to /sub/ (308)', {
|
||||
Location: '/sub/',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html',
|
||||
'Redirecting to /sub/another/ (308)',
|
||||
{
|
||||
Location: '/sub/another/',
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test cors headers work with OPTIONS',
|
||||
testFixtureStdio('test-cors-routes', async (testPath: any) => {
|
||||
const headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers':
|
||||
'Content-Type, Authorization, Accept, Content-Length, Origin, User-Agent',
|
||||
'Access-Control-Allow-Methods':
|
||||
'GET, POST, OPTIONS, HEAD, PATCH, PUT, DELETE',
|
||||
};
|
||||
await testPath(200, '/', 'status api', headers, { method: 'GET' });
|
||||
await testPath(200, '/', 'status api', headers, { method: 'POST' });
|
||||
await testPath(200, '/api/status.js', 'status api', headers, {
|
||||
method: 'GET',
|
||||
});
|
||||
await testPath(200, '/api/status.js', 'status api', headers, {
|
||||
method: 'POST',
|
||||
});
|
||||
await testPath(204, '/', '', headers, { method: 'OPTIONS' });
|
||||
await testPath(204, '/api/status.js', '', headers, { method: 'OPTIONS' });
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test trailingSlash true serve correct content',
|
||||
testFixtureStdio('test-trailing-slash', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/index.html', 'Index Page');
|
||||
await testPath(200, '/about.html', 'About Page');
|
||||
await testPath(200, '/sub/', 'Sub Index Page');
|
||||
await testPath(200, '/sub/index.html', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another.html', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/about.html/', 'Redirecting to /about.html (308)', {
|
||||
Location: '/about.html',
|
||||
});
|
||||
await testPath(308, '/style.css/', 'Redirecting to /style.css (308)', {
|
||||
Location: '/style.css',
|
||||
});
|
||||
await testPath(308, '/sub', 'Redirecting to /sub/ (308)', {
|
||||
Location: '/sub/',
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] should serve custom 404 when `trailingSlash: true`',
|
||||
testFixtureStdio('test-trailing-slash-custom-404', async (testPath: any) => {
|
||||
await testPath(200, '/', 'This is the home page');
|
||||
await testPath(200, '/about.html', 'The about page');
|
||||
await testPath(200, '/contact/', 'Contact Subdirectory');
|
||||
await testPath(404, '/nothing/', 'Custom 404 Page');
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] test trailingSlash false serve correct content',
|
||||
testFixtureStdio('test-trailing-slash-false', async (testPath: any) => {
|
||||
await testPath(200, '/', 'Index Page');
|
||||
await testPath(200, '/index.html', 'Index Page');
|
||||
await testPath(200, '/about.html', 'About Page');
|
||||
await testPath(200, '/sub', 'Sub Index Page');
|
||||
await testPath(200, '/sub/index.html', 'Sub Index Page');
|
||||
await testPath(200, '/sub/another.html', 'Sub Another Page');
|
||||
await testPath(200, '/style.css', 'body { color: green }');
|
||||
await testPath(308, '/about.html/', 'Redirecting to /about.html (308)', {
|
||||
Location: '/about.html',
|
||||
});
|
||||
await testPath(308, '/sub/', 'Redirecting to /sub (308)', {
|
||||
Location: '/sub',
|
||||
});
|
||||
await testPath(
|
||||
308,
|
||||
'/sub/another.html/',
|
||||
'Redirecting to /sub/another.html (308)',
|
||||
{
|
||||
Location: '/sub/another.html',
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] throw when invalid builder routes detected',
|
||||
testFixtureStdio(
|
||||
'invalid-builder-routes',
|
||||
async (testPath: any) => {
|
||||
await testPath(
|
||||
500,
|
||||
'/',
|
||||
/Route at index 0 has invalid `src` regular expression/m
|
||||
);
|
||||
},
|
||||
{ skipDeploy: true }
|
||||
)
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] support legacy `@now` scope runtimes',
|
||||
testFixtureStdio('legacy-now-runtime', async (testPath: any) => {
|
||||
await testPath(200, '/', /A simple deployment with the Vercel API!/m);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] 00-list-directory',
|
||||
testFixtureStdio(
|
||||
'00-list-directory',
|
||||
async (testPath: any) => {
|
||||
await testPath(200, '/', /Files within/m);
|
||||
await testPath(200, '/', /test[0-3]\.txt/m);
|
||||
await testPath(200, '/', /\.well-known/m);
|
||||
await testPath(200, '/.well-known/keybase.txt', 'proof goes here');
|
||||
},
|
||||
{ projectSettings: { directoryListing: true } }
|
||||
)
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] 01-node',
|
||||
testFixtureStdio('01-node', async (testPath: any) => {
|
||||
await testPath(200, '/', /A simple deployment with the Vercel API!/m);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[vercel dev] add a `api/fn.ts` when `api` does not exist at startup`',
|
||||
testFixtureStdio('no-api', async (_testPath: any, port: any) => {
|
||||
const directory = fixture('no-api');
|
||||
const apiDir = join(directory, 'api');
|
||||
|
||||
try {
|
||||
{
|
||||
const response = await fetch(`http://localhost:${port}/api/new-file`);
|
||||
validateResponseHeaders(response);
|
||||
expect(response.status).toBe(404);
|
||||
}
|
||||
|
||||
const fileContents = `
|
||||
export const config = {
|
||||
runtime: 'edge'
|
||||
}
|
||||
|
||||
export default async function edge(request, event) {
|
||||
return new Response('from new file');
|
||||
}
|
||||
`;
|
||||
|
||||
await mkdirp(apiDir);
|
||||
await fs.writeFile(join(apiDir, 'new-file.js'), fileContents);
|
||||
|
||||
// Wait until file events have been processed
|
||||
await sleep(ms('1s'));
|
||||
|
||||
{
|
||||
const response = await fetch(`http://localhost:${port}/api/new-file`);
|
||||
validateResponseHeaders(response);
|
||||
const body = await response.text();
|
||||
expect(body.trim()).toBe('from new file');
|
||||
}
|
||||
} finally {
|
||||
await fs.remove(apiDir);
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/client",
|
||||
"version": "12.2.31",
|
||||
"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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import sleep from 'sleep-promise';
|
||||
import ms from 'ms';
|
||||
import { fetch, getApiDeploymentsUrl } from './utils';
|
||||
import { getPollingDelay } from './utils/get-polling-delay';
|
||||
import {
|
||||
isDone,
|
||||
isReady,
|
||||
@@ -47,6 +47,7 @@ export async function* checkDeploymentStatus(
|
||||
// Build polling
|
||||
debug('Waiting for builds and the deployment to complete...');
|
||||
const finishedEvents = new Set();
|
||||
const startTime = Date.now();
|
||||
|
||||
while (true) {
|
||||
// Deployment polling
|
||||
@@ -155,6 +156,8 @@ export async function* checkDeploymentStatus(
|
||||
};
|
||||
}
|
||||
|
||||
await sleep(ms('1.5s'));
|
||||
const elapsed = Date.now() - startTime;
|
||||
const duration = getPollingDelay(elapsed);
|
||||
await sleep(duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function buildCreateDeployment() {
|
||||
debug(`Provided 'path' is a single file`);
|
||||
}
|
||||
|
||||
let { fileList } = await buildFileTree(path, clientOptions, debug);
|
||||
const { fileList } = await buildFileTree(path, clientOptions, debug);
|
||||
|
||||
// This is a useful warning because it prevents people
|
||||
// from getting confused about a deployment that renders 404.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DeploymentFile } from './utils/hashes';
|
||||
import { FilesMap } from './utils/hashes';
|
||||
import { generateQueryString } from './utils/query-string';
|
||||
import { isReady, isAliasAssigned } from './utils/ready-state';
|
||||
import { checkDeploymentStatus } from './check-deployment-status';
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from './types';
|
||||
|
||||
async function* postDeployment(
|
||||
files: Map<string, DeploymentFile>,
|
||||
files: FilesMap,
|
||||
clientOptions: VercelClientOptions,
|
||||
deploymentOptions: DeploymentOptions
|
||||
): AsyncIterableIterator<{
|
||||
@@ -90,7 +90,7 @@ async function* postDeployment(
|
||||
}
|
||||
|
||||
function getDefaultName(
|
||||
files: Map<string, DeploymentFile>,
|
||||
files: FilesMap,
|
||||
clientOptions: VercelClientOptions
|
||||
): string {
|
||||
const debug = createDebug(clientOptions.debug);
|
||||
@@ -109,7 +109,7 @@ function getDefaultName(
|
||||
}
|
||||
|
||||
export async function* deploy(
|
||||
files: Map<string, DeploymentFile>,
|
||||
files: FilesMap,
|
||||
clientOptions: VercelClientOptions,
|
||||
deploymentOptions: DeploymentOptions
|
||||
): AsyncIterableIterator<{ type: string; payload: any }> {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { EventEmitter } from 'events';
|
||||
import retry from 'async-retry';
|
||||
import { Sema } from 'async-sema';
|
||||
|
||||
import { DeploymentFile } from './utils/hashes';
|
||||
import { DeploymentFile, FilesMap } from './utils/hashes';
|
||||
import { fetch, API_FILES, createDebug } from './utils';
|
||||
import { DeploymentError } from './errors';
|
||||
import { deploy } from './deploy';
|
||||
@@ -29,7 +29,7 @@ const isClientNetworkError = (err: Error) => {
|
||||
};
|
||||
|
||||
export async function* upload(
|
||||
files: Map<string, DeploymentFile>,
|
||||
files: FilesMap,
|
||||
clientOptions: VercelClientOptions,
|
||||
deploymentOptions: DeploymentOptions
|
||||
): AsyncIterableIterator<any> {
|
||||
@@ -98,6 +98,10 @@ export async function* upload(
|
||||
await semaphore.acquire();
|
||||
|
||||
const { data } = file;
|
||||
if (typeof data === 'undefined') {
|
||||
// Directories don't need to be uploaded
|
||||
return;
|
||||
}
|
||||
|
||||
uploadProgress.bytesUploaded = 0;
|
||||
|
||||
|
||||
14
packages/client/src/utils/get-polling-delay.ts
Normal file
14
packages/client/src/utils/get-polling-delay.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import ms from 'ms';
|
||||
|
||||
export function getPollingDelay(elapsed: number): number {
|
||||
if (elapsed <= ms('15s')) {
|
||||
return ms('1s');
|
||||
}
|
||||
if (elapsed <= ms('1m')) {
|
||||
return ms('5s');
|
||||
}
|
||||
if (elapsed <= ms('5m')) {
|
||||
return ms('15s');
|
||||
}
|
||||
return ms('30s');
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import { Sema } from 'async-sema';
|
||||
|
||||
export interface DeploymentFile {
|
||||
names: string[];
|
||||
data: Buffer;
|
||||
data?: Buffer;
|
||||
mode: number;
|
||||
}
|
||||
|
||||
export type FilesMap = Map<string | undefined, DeploymentFile>;
|
||||
|
||||
/**
|
||||
* Computes a hash for the given buf.
|
||||
*
|
||||
@@ -23,14 +25,12 @@ export function hash(buf: Buffer): string {
|
||||
* @param map with hashed files
|
||||
* @return {object}
|
||||
*/
|
||||
export const mapToObject = (
|
||||
map: Map<string, DeploymentFile>
|
||||
): { [key: string]: DeploymentFile } => {
|
||||
export const mapToObject = (map: FilesMap): Record<string, DeploymentFile> => {
|
||||
const obj: { [key: string]: DeploymentFile } = {};
|
||||
for (const [key, value] of map) {
|
||||
if (typeof key === 'undefined') continue;
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
@@ -43,8 +43,8 @@ export const mapToObject = (
|
||||
*/
|
||||
export async function hashes(
|
||||
files: string[],
|
||||
map = new Map<string, DeploymentFile>()
|
||||
): Promise<Map<string, DeploymentFile>> {
|
||||
map = new Map<string | undefined, DeploymentFile>()
|
||||
): Promise<FilesMap> {
|
||||
const semaphore = new Sema(100);
|
||||
|
||||
await Promise.all(
|
||||
@@ -54,15 +54,21 @@ export async function hashes(
|
||||
const stat = await fs.lstat(name);
|
||||
const mode = stat.mode;
|
||||
|
||||
let data: Buffer | null = null;
|
||||
if (stat.isSymbolicLink()) {
|
||||
const link = await fs.readlink(name);
|
||||
data = Buffer.from(link, 'utf8');
|
||||
} else {
|
||||
data = await fs.readFile(name);
|
||||
let data: Buffer | undefined;
|
||||
const isDirectory = stat.isDirectory();
|
||||
|
||||
let h: string | undefined;
|
||||
|
||||
if (!isDirectory) {
|
||||
if (stat.isSymbolicLink()) {
|
||||
const link = await fs.readlink(name);
|
||||
data = Buffer.from(link, 'utf8');
|
||||
} else {
|
||||
data = await fs.readFile(name);
|
||||
}
|
||||
h = hash(data);
|
||||
}
|
||||
|
||||
const h = hash(data);
|
||||
const entry = map.get(h);
|
||||
|
||||
if (entry) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DeploymentFile } from './hashes';
|
||||
import { FilesMap } from './hashes';
|
||||
import { FetchOptions } from '@zeit/fetch';
|
||||
import { nodeFetch, zeitFetch } from './fetch';
|
||||
import { join, sep, relative } from 'path';
|
||||
@@ -256,15 +256,15 @@ export const fetch = async (
|
||||
|
||||
export interface PreparedFile {
|
||||
file: string;
|
||||
sha: string;
|
||||
size: number;
|
||||
sha?: string;
|
||||
size?: number;
|
||||
mode: number;
|
||||
}
|
||||
|
||||
const isWin = process.platform.includes('win');
|
||||
|
||||
export const prepareFiles = (
|
||||
files: Map<string, DeploymentFile>,
|
||||
files: FilesMap,
|
||||
clientOptions: VercelClientOptions
|
||||
): PreparedFile[] => {
|
||||
const preparedFiles: PreparedFile[] = [];
|
||||
@@ -286,9 +286,9 @@ export const prepareFiles = (
|
||||
|
||||
preparedFiles.push({
|
||||
file: isWin ? fileName.replace(/\\/g, '/') : fileName,
|
||||
size: file.data.byteLength || file.data.length,
|
||||
size: file.data?.byteLength || file.data?.length,
|
||||
mode: file.mode,
|
||||
sha,
|
||||
sha: sha || undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,10 @@ export default function readdir(
|
||||
if (stats.isDirectory()) {
|
||||
readdir(filePath, ignores)
|
||||
.then(function (res) {
|
||||
if (res.length === 0) {
|
||||
// Empty directories get returned
|
||||
list.push(filePath);
|
||||
}
|
||||
list = list.concat(res);
|
||||
pending -= 1;
|
||||
if (!pending) {
|
||||
|
||||
41
packages/client/tests/unit.get-polling-delay.test.ts
Normal file
41
packages/client/tests/unit.get-polling-delay.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { getPollingDelay } from '../src/utils/get-polling-delay';
|
||||
|
||||
describe('getPollingDelay()', () => {
|
||||
it('should return 1 second', async () => {
|
||||
expect(getPollingDelay(0)).toBe(1000);
|
||||
expect(getPollingDelay(1000)).toBe(1000);
|
||||
expect(getPollingDelay(3000)).toBe(1000);
|
||||
expect(getPollingDelay(5000)).toBe(1000);
|
||||
expect(getPollingDelay(8000)).toBe(1000);
|
||||
expect(getPollingDelay(9000)).toBe(1000);
|
||||
expect(getPollingDelay(10000)).toBe(1000);
|
||||
expect(getPollingDelay(13000)).toBe(1000);
|
||||
expect(getPollingDelay(15000)).toBe(1000);
|
||||
});
|
||||
|
||||
it('should return 5 second', async () => {
|
||||
expect(getPollingDelay(15001)).toBe(5000);
|
||||
expect(getPollingDelay(16000)).toBe(5000);
|
||||
expect(getPollingDelay(23000)).toBe(5000);
|
||||
expect(getPollingDelay(36000)).toBe(5000);
|
||||
expect(getPollingDelay(59000)).toBe(5000);
|
||||
expect(getPollingDelay(60000)).toBe(5000);
|
||||
});
|
||||
|
||||
it('should return 15 second', async () => {
|
||||
expect(getPollingDelay(60001)).toBe(15000);
|
||||
expect(getPollingDelay(80000)).toBe(15000);
|
||||
expect(getPollingDelay(100000)).toBe(15000);
|
||||
expect(getPollingDelay(200000)).toBe(15000);
|
||||
expect(getPollingDelay(250000)).toBe(15000);
|
||||
expect(getPollingDelay(300000)).toBe(15000);
|
||||
});
|
||||
|
||||
it('should return 30 second', async () => {
|
||||
expect(getPollingDelay(300001)).toBe(30000);
|
||||
expect(getPollingDelay(400000)).toBe(30000);
|
||||
expect(getPollingDelay(1400000)).toBe(30000);
|
||||
expect(getPollingDelay(9400000)).toBe(30000);
|
||||
expect(getPollingDelay(99400000)).toBe(30000);
|
||||
});
|
||||
});
|
||||
@@ -24,7 +24,11 @@ describe('buildFileTree()', () => {
|
||||
noop
|
||||
);
|
||||
|
||||
const expectedFileList = toAbsolutePaths(cwd, ['.nowignore', 'index.txt']);
|
||||
const expectedFileList = toAbsolutePaths(cwd, [
|
||||
'.nowignore',
|
||||
'folder',
|
||||
'index.txt',
|
||||
]);
|
||||
expect(normalizeWindowsPaths(expectedFileList).sort()).toEqual(
|
||||
normalizeWindowsPaths(fileList).sort()
|
||||
);
|
||||
@@ -41,9 +45,14 @@ describe('buildFileTree()', () => {
|
||||
|
||||
it('should include symlinked files and directories', async () => {
|
||||
const cwd = fixture('symlinks');
|
||||
|
||||
// Also add an empty directory to make sure it's included
|
||||
await fs.mkdirp(join(cwd, 'empty'));
|
||||
|
||||
const { fileList } = await buildFileTree(cwd, { isDirectory: true }, noop);
|
||||
|
||||
const expectedFileList = toAbsolutePaths(cwd, [
|
||||
'empty',
|
||||
'folder-link',
|
||||
'folder/text.txt',
|
||||
'index.txt',
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"strict": true,
|
||||
"target": "ES2020"
|
||||
"target": "ES2020",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
4
packages/gatsby-plugin-vercel-builder/.gitignore
vendored
Normal file
4
packages/gatsby-plugin-vercel-builder/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
gatsby-node.*
|
||||
!gatsby-node.ts
|
||||
|
||||
build
|
||||
0
packages/gatsby-plugin-vercel-builder/README.md
Normal file
0
packages/gatsby-plugin-vercel-builder/README.md
Normal file
28
packages/gatsby-plugin-vercel-builder/gatsby-node.ts
Normal file
28
packages/gatsby-plugin-vercel-builder/gatsby-node.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import path from 'path';
|
||||
|
||||
import type { GatsbyNode } from 'gatsby';
|
||||
|
||||
// this gets built separately, so import from "build" instead of "src"
|
||||
import { generateVercelBuildOutputAPI3Output } from './build';
|
||||
|
||||
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(),
|
||||
});
|
||||
};
|
||||
33
packages/gatsby-plugin-vercel-builder/package.json
Normal file
33
packages/gatsby-plugin-vercel-builder/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@vercel/gatsby-plugin-vercel-builder",
|
||||
"version": "0.1.0",
|
||||
"main": "build/index.js",
|
||||
"files": [
|
||||
"build",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
129
packages/gatsby-plugin-vercel-builder/src/handlers/build.ts
Normal file
129
packages/gatsby-plugin-vercel-builder/src/handlers/build.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
20
packages/gatsby-plugin-vercel-builder/src/handlers/utils.ts
Normal file
20
packages/gatsby-plugin-vercel-builder/src/handlers/utils.ts
Normal 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 };
|
||||
}
|
||||
126
packages/gatsby-plugin-vercel-builder/src/helpers/functions.ts
Normal file
126
packages/gatsby-plugin-vercel-builder/src/helpers/functions.ts
Normal 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 }),
|
||||
]);
|
||||
})
|
||||
);
|
||||
}
|
||||
10
packages/gatsby-plugin-vercel-builder/src/helpers/static.ts
Normal file
10
packages/gatsby-plugin-vercel-builder/src/helpers/static.ts
Normal 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);
|
||||
}
|
||||
108
packages/gatsby-plugin-vercel-builder/src/index.ts
Normal file
108
packages/gatsby-plugin-vercel-builder/src/index.ts
Normal 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'
|
||||
);
|
||||
}
|
||||
}
|
||||
82
packages/gatsby-plugin-vercel-builder/src/schemas.ts
Normal file
82
packages/gatsby-plugin-vercel-builder/src/schemas.ts
Normal 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);
|
||||
126
packages/gatsby-plugin-vercel-builder/src/types.d.ts
vendored
Normal file
126
packages/gatsby-plugin-vercel-builder/src/types.d.ts
vendored
Normal 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']>;
|
||||
}
|
||||
20
packages/gatsby-plugin-vercel-builder/src/utils/symlink.ts
Normal file
20
packages/gatsby-plugin-vercel-builder/src/utils/symlink.ts
Normal 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`)
|
||||
);
|
||||
};
|
||||
104
packages/gatsby-plugin-vercel-builder/tsconfig.base.json
Normal file
104
packages/gatsby-plugin-vercel-builder/tsconfig.base.json
Normal 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": []
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"declaration": false,
|
||||
"declarationMap": false
|
||||
},
|
||||
"include": ["gatsby-node.ts"]
|
||||
}
|
||||
7
packages/gatsby-plugin-vercel-builder/tsconfig.src.json
Normal file
7
packages/gatsby-plugin-vercel-builder/tsconfig.src.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/next",
|
||||
"version": "3.3.14",
|
||||
"version": "3.3.16",
|
||||
"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",
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"module": "commonjs",
|
||||
"outDir": "dist",
|
||||
"sourceMap": false,
|
||||
"declaration": false
|
||||
"declaration": false,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/node-bridge",
|
||||
"version": "3.1.8",
|
||||
"version": "3.1.10",
|
||||
"license": "MIT",
|
||||
"main": "./index.js",
|
||||
"repository": {
|
||||
@@ -23,6 +23,9 @@
|
||||
"devDependencies": {
|
||||
"@types/aws-lambda": "8.10.19",
|
||||
"@types/node": "14.18.33",
|
||||
"content-type": "1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
"etag": "1.8.1",
|
||||
"execa": "3.2.0",
|
||||
"fs-extra": "10.0.0",
|
||||
"jsonlines": "0.1.1",
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/node",
|
||||
"version": "2.8.11",
|
||||
"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.8",
|
||||
"@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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/remix",
|
||||
"version": "1.2.4",
|
||||
"version": "1.2.6",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"homepage": "https://vercel.com/docs",
|
||||
@@ -12,8 +12,7 @@
|
||||
"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": "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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
100
packages/remix/test/build.test.ts
vendored
100
packages/remix/test/build.test.ts
vendored
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
9805
pnpm-lock.yaml
generated
9805
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
2
utils/changelog.js
vendored
2
utils/changelog.js
vendored
@@ -105,7 +105,7 @@ async function main() {
|
||||
const pub = Array.from(pkgs).join(',');
|
||||
console.log('To publish a stable release, execute the following:');
|
||||
console.log(
|
||||
`\nnpx lerna version --message "Publish Stable" --exact --force-publish=${pub}\n`
|
||||
`\npnpx lerna version --message "Publish Stable" --exact --no-private --force-publish=${pub}\n`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user