Compare commits

..

6 Commits

Author SHA1 Message Date
Sean Massa
6d97e1673e Publish Stable
- vercel@28.12.5
 - @vercel/client@12.3.0
 - @vercel/node-bridge@3.1.9
 - @vercel/node@2.8.12
2023-01-17 13:46:58 -06:00
Nathan Rajlich
522565f6e5 [node-bridge] Add missing lazy dependencies (#9231)
These were missing from the compiled ncc build of `helpers.ts`, and thus causing an error at runtime because the deps are not available within the Serverless Function.
2023-01-14 01:41:50 +00:00
Steven
07bf81ab10 [client] Add sliding window delay when polling deployment complete (#9222)
The previous delay of 1500ms was causing some users to hit the API rate limits. This doesn't normally happen with a single deployment, but it can happen with several concurrent deployments (for example a monorepo with many projects).

We don't need to be polling so often, so this PR changed the polling delay to the following:

- During 0s-15s: check every 1 second
- During 15s-60s: check every 5 seconds
- During 1m-5m: check every 15 seconds
- During 5m-10m: check every 30 seconds
2023-01-14 00:27:14 +00:00
Steven
35024a4e3a [tests] Split up dev tests to increase concurrency (#9228)
This will speed up CI because we can run more tests concurrently.

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-13 18:53:57 -05:00
Sean Massa
c1df9bca19 [tests] skip private packages (#9229) 2023-01-13 17:24:50 -06:00
Nathan Rajlich
4c1cdd1f0f [client] Send empty directory entries to POST create deployment (#9118)
Update `@vercel/client` to send empty directory entries to the `POST` create deployment endpoint. This makes it so that CLI deployments will have empty directories re-populated in the build-container when doing `vc deploy`.

Follow-up to #9103.
2023-01-13 22:24:31 +00:00
18 changed files with 402 additions and 313 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "28.12.4",
"version": "28.12.5",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -45,7 +45,7 @@
"@vercel/go": "2.2.29",
"@vercel/hydrogen": "0.0.43",
"@vercel/next": "3.3.14",
"@vercel/node": "2.8.11",
"@vercel/node": "2.8.12",
"@vercel/python": "3.1.39",
"@vercel/redwood": "1.0.50",
"@vercel/remix": "1.2.4",
@@ -93,7 +93,7 @@
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.2.31",
"@vercel/client": "12.3.0",
"@vercel/error-utils": "1.0.8",
"@vercel/frameworks": "1.2.4",
"@vercel/fs-detectors": "3.7.4",

View File

@@ -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);
}
})
);

View 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);
}
})
);

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "12.2.31",
"version": "12.3.0",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",

View File

@@ -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);
}
}

View File

@@ -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.

View File

@@ -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 }> {

View File

@@ -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;

View 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');
}

View File

@@ -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) {

View File

@@ -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,
});
}
}

View File

@@ -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) {

View 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);
});
});

View File

@@ -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',

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node-bridge",
"version": "3.1.8",
"version": "3.1.9",
"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",

View File

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

12
pnpm-lock.yaml generated
View File

@@ -199,7 +199,7 @@ importers:
'@types/write-json-file': 2.2.1
'@types/yauzl-promise': 2.1.0
'@vercel/build-utils': 5.8.3
'@vercel/client': 12.2.31
'@vercel/client': 12.3.0
'@vercel/error-utils': 1.0.8
'@vercel/frameworks': 1.2.4
'@vercel/fs-detectors': 3.7.4
@@ -208,7 +208,7 @@ importers:
'@vercel/hydrogen': 0.0.43
'@vercel/ncc': 0.24.0
'@vercel/next': 3.3.14
'@vercel/node': 2.8.11
'@vercel/node': 2.8.12
'@vercel/python': 3.1.39
'@vercel/redwood': 1.0.50
'@vercel/remix': 1.2.4
@@ -737,7 +737,7 @@ importers:
'@vercel/build-utils': 5.8.3
'@vercel/ncc': 0.24.0
'@vercel/nft': 0.22.5
'@vercel/node-bridge': 3.1.8
'@vercel/node-bridge': 3.1.9
'@vercel/static-config': 2.0.11
content-type: 1.0.4
cookie: 0.4.0
@@ -793,6 +793,9 @@ importers:
specifiers:
'@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
@@ -801,6 +804,9 @@ importers:
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

2
utils/changelog.js vendored
View File

@@ -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`
);
}