mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-09 21:07:46 +00:00
https://vercel.com/blog/zeit-is-now-vercel * Updates all org packages from `@now` to `@vercel` * Updates Now CLI package name from `now` to `vercel` * Packages contains `"bin"` entries for _both_ `vercel` and `now` in the package.json * Updates `now-client` package name to `@vercel/client` (org scoped, for authenticity) There is also a new `publish-legacy.sh` script which ensures that all the legacy package names (i.e. `now`, `now-client`, `@now/node`, etc.) will still be published as well. We will remove this legacy publishing logic on Jan 1, 2021.
468 lines
13 KiB
JavaScript
Vendored
468 lines
13 KiB
JavaScript
Vendored
import url from 'url';
|
|
import test from 'ava';
|
|
import path from 'path';
|
|
import execa from 'execa';
|
|
import fs from 'fs-extra';
|
|
import fetch from 'node-fetch';
|
|
import listen from 'async-listen';
|
|
import { createServer } from 'http';
|
|
import createOutput from '../src/util/output';
|
|
import DevServer from '../src/util/dev/server';
|
|
import { installBuilders, getBuildUtils } from '../src/util/dev/builder-cache';
|
|
import parseListen from '../src/util/dev/parse-listen';
|
|
|
|
async function runNpmInstall(fixturePath) {
|
|
if (await fs.exists(path.join(fixturePath, 'package.json'))) {
|
|
return execa('yarn', ['install'], { cwd: fixturePath, shell: true });
|
|
}
|
|
}
|
|
|
|
const skipOnWindows = new Set([
|
|
'now-dev-default-builds-and-routes',
|
|
'now-dev-static-routes',
|
|
'now-dev-static-build-routing',
|
|
'now-dev-directory-listing',
|
|
'now-dev-api-with-public',
|
|
'now-dev-api-with-static',
|
|
'now-dev-custom-404',
|
|
]);
|
|
|
|
function testFixture(name, fn) {
|
|
return async t => {
|
|
if (process.platform === 'win32' && skipOnWindows.has(name)) {
|
|
console.log(`Skipping test "${name}" on Windows.`);
|
|
t.is(true, true);
|
|
return;
|
|
}
|
|
|
|
let server;
|
|
|
|
const fixturePath = path.join(__dirname, 'fixtures', 'unit', name);
|
|
|
|
await runNpmInstall(fixturePath);
|
|
|
|
try {
|
|
let readyResolve;
|
|
let readyPromise = new Promise(resolve => {
|
|
readyResolve = resolve;
|
|
});
|
|
|
|
const debug = true;
|
|
const output = createOutput({ debug });
|
|
const origReady = output.ready;
|
|
|
|
output.ready = msg => {
|
|
if (msg.toString().match(/Available at/)) {
|
|
readyResolve();
|
|
}
|
|
origReady(msg);
|
|
};
|
|
|
|
server = new DevServer(fixturePath, { output, debug });
|
|
|
|
await server.start(0);
|
|
await readyPromise;
|
|
|
|
await fn(t, server);
|
|
} finally {
|
|
await server.stop();
|
|
}
|
|
};
|
|
}
|
|
|
|
function validateResponseHeaders(t, res, podId = null) {
|
|
t.is(res.headers.get('server'), 'now');
|
|
t.truthy(res.headers.get('cache-control').length > 0);
|
|
t.truthy(
|
|
/^dev1::(dev1::)?[0-9a-z]{5}-[1-9][0-9]+-[a-f0-9]{12}$/.test(
|
|
res.headers.get('x-vercel-id')
|
|
)
|
|
);
|
|
if (podId) {
|
|
t.truthy(
|
|
res.headers.get('x-vercel-id').startsWith(`dev1::${podId}`) ||
|
|
res.headers.get('x-vercel-id').startsWith(`dev1::dev1::${podId}`)
|
|
);
|
|
}
|
|
}
|
|
|
|
test(
|
|
'[DevServer] Maintains query when invoking lambda',
|
|
testFixture('now-dev-query-invoke', async (t, server) => {
|
|
const res = await fetch(`${server.address}/something?url-param=a`);
|
|
validateResponseHeaders(t, res);
|
|
|
|
const text = await res.text();
|
|
const parsed = url.parse(text, true);
|
|
t.is(parsed.pathname, '/something');
|
|
t.is(parsed.query['url-param'], 'a');
|
|
t.is(parsed.query['route-param'], 'b');
|
|
})
|
|
);
|
|
|
|
test(
|
|
'[DevServer] Maintains query when proxy passing',
|
|
testFixture('now-dev-query-proxy', async (t, server) => {
|
|
const dest = createServer((req, res) => {
|
|
res.end(req.url);
|
|
});
|
|
await listen(dest, 0);
|
|
const { port } = dest.address();
|
|
|
|
try {
|
|
const res = await fetch(`${server.address}/${port}?url-param=a`);
|
|
validateResponseHeaders(t, res);
|
|
|
|
const text = await res.text();
|
|
const parsed = url.parse(text, true);
|
|
t.is(parsed.pathname, '/something');
|
|
t.is(parsed.query['url-param'], 'a');
|
|
t.is(parsed.query['route-param'], 'b');
|
|
} finally {
|
|
dest.close();
|
|
}
|
|
})
|
|
);
|
|
|
|
test(
|
|
'[DevServer] Maintains query when builder defines routes',
|
|
testFixture('now-dev-next', async (t, server) => {
|
|
const res = await fetch(`${server.address}/something?url-param=a`);
|
|
validateResponseHeaders(t, res);
|
|
|
|
const text = await res.text();
|
|
|
|
// Hacky way of getting the page payload from the response
|
|
// HTML since we don't have a HTML parser handy.
|
|
const json = text
|
|
.match(/<div>(.*)<\/div>/)[1]
|
|
.replace('</div>', '')
|
|
.replace(/"/g, '"');
|
|
const parsed = JSON.parse(json);
|
|
|
|
t.is(parsed.query['url-param'], 'a');
|
|
t.is(parsed.query['route-param'], 'b');
|
|
})
|
|
);
|
|
|
|
test(
|
|
'[DevServer] Allow `cache-control` to be overwritten',
|
|
testFixture('now-dev-headers', async (t, server) => {
|
|
const res = await fetch(
|
|
`${server.address}/?name=cache-control&value=immutable`
|
|
);
|
|
t.is(res.headers.get('cache-control'), 'immutable');
|
|
})
|
|
);
|
|
|
|
test(
|
|
'[DevServer] Sends `etag` header for static files',
|
|
testFixture('now-dev-headers', async (t, server) => {
|
|
if (process.platform === 'win32') {
|
|
console.log(
|
|
'Skipping "etag" test on windows since it yields a different result.'
|
|
);
|
|
t.is(true, true);
|
|
return;
|
|
}
|
|
const res = await fetch(`${server.address}/foo.txt`);
|
|
t.is(res.headers.get('etag'), '"d263af8ab880c0b97eb6c5c125b5d44f9e5addd9"');
|
|
t.is(await res.text(), 'hi\n');
|
|
})
|
|
);
|
|
|
|
test('[DevServer] Does not install builders if there are no builds', async t => {
|
|
const handler = data => {
|
|
if (data.includes('installing')) {
|
|
t.fail();
|
|
}
|
|
};
|
|
|
|
process.stdout.addListener('data', handler);
|
|
process.stderr.addListener('data', handler);
|
|
|
|
const output = createOutput({ debug: false });
|
|
await installBuilders(new Set(), undefined, output);
|
|
|
|
process.stdout.removeListener('data', handler);
|
|
process.stderr.removeListener('data', handler);
|
|
|
|
t.pass();
|
|
});
|
|
|
|
test('[DevServer] Installs canary build-utils if one more more builders is canary', t => {
|
|
t.is(
|
|
getBuildUtils(['@vercel/static', '@vercel/node@canary'], 'vercel'),
|
|
'@vercel/build-utils@canary'
|
|
);
|
|
t.is(
|
|
getBuildUtils(['@vercel/static', '@vercel/node@0.7.4-canary.0'], 'vercel'),
|
|
'@vercel/build-utils@canary'
|
|
);
|
|
t.is(
|
|
getBuildUtils(['@vercel/static', '@vercel/node@0.8.0'], 'vercel'),
|
|
'@vercel/build-utils@latest'
|
|
);
|
|
t.is(
|
|
getBuildUtils(['@vercel/static', '@vercel/node'], 'vercel'),
|
|
'@vercel/build-utils@latest'
|
|
);
|
|
t.is(
|
|
getBuildUtils(['@vercel/static'], 'vercel'),
|
|
'@vercel/build-utils@latest'
|
|
);
|
|
t.is(
|
|
getBuildUtils(['@vercel/md@canary'], 'vercel'),
|
|
'@vercel/build-utils@canary'
|
|
);
|
|
t.is(
|
|
getBuildUtils(['custom-builder'], 'vercel'),
|
|
'@vercel/build-utils@latest'
|
|
);
|
|
t.is(
|
|
getBuildUtils(['custom-builder@canary'], 'vercel'),
|
|
'@vercel/build-utils@canary'
|
|
);
|
|
t.is(getBuildUtils(['canary-bird'], 'vercel'), '@vercel/build-utils@latest');
|
|
t.is(
|
|
getBuildUtils(['canary-bird@4.0.0'], 'vercel'),
|
|
'@vercel/build-utils@latest'
|
|
);
|
|
t.is(
|
|
getBuildUtils(['canary-bird@canary'], 'vercel'),
|
|
'@vercel/build-utils@canary'
|
|
);
|
|
t.is(getBuildUtils(['@canary/bird'], 'vercel'), '@vercel/build-utils@latest');
|
|
t.is(
|
|
getBuildUtils(['@canary/bird@0.1.0'], 'vercel'),
|
|
'@vercel/build-utils@latest'
|
|
);
|
|
t.is(
|
|
getBuildUtils(['@canary/bird@canary'], 'vercel'),
|
|
'@vercel/build-utils@canary'
|
|
);
|
|
t.is(
|
|
getBuildUtils(['https://example.com'], 'vercel'),
|
|
'@vercel/build-utils@latest'
|
|
);
|
|
t.is(getBuildUtils([''], 'vercel'), '@vercel/build-utils@latest');
|
|
});
|
|
|
|
test(
|
|
'[DevServer] Test default builds and routes',
|
|
testFixture('now-dev-default-builds-and-routes', async (t, server) => {
|
|
let podId;
|
|
|
|
{
|
|
const res = await fetch(`${server.address}/`);
|
|
validateResponseHeaders(t, res);
|
|
podId = res.headers.get('x-vercel-id').match(/:(\w+)-/)[1];
|
|
const body = await res.text();
|
|
t.is(body.includes('hello, this is the frontend'), true);
|
|
}
|
|
|
|
{
|
|
const res = await fetch(`${server.address}/api/users`);
|
|
validateResponseHeaders(t, res, podId);
|
|
const body = await res.text();
|
|
t.is(body, 'users');
|
|
}
|
|
|
|
{
|
|
const res = await fetch(`${server.address}/api/users/1`);
|
|
validateResponseHeaders(t, res, podId);
|
|
const body = await res.text();
|
|
t.is(body, 'users/1');
|
|
}
|
|
|
|
{
|
|
const res = await fetch(`${server.address}/api/welcome`);
|
|
validateResponseHeaders(t, res, podId);
|
|
const body = await res.text();
|
|
t.is(body, 'hello and welcome');
|
|
}
|
|
})
|
|
);
|
|
|
|
test(
|
|
'[DevServer] Test `@vercel/static` routing',
|
|
testFixture('now-dev-static-routes', async (t, server) => {
|
|
{
|
|
const res = await fetch(`${server.address}/`);
|
|
const body = await res.text();
|
|
t.is(body, '<body>Hello!</body>\n');
|
|
}
|
|
})
|
|
);
|
|
|
|
test(
|
|
'[DevServer] Test `@vercel/static-build` routing',
|
|
testFixture('now-dev-static-build-routing', async (t, server) => {
|
|
{
|
|
const res = await fetch(`${server.address}/api/date`);
|
|
const body = await res.text();
|
|
t.is(body.startsWith('The current date:'), true);
|
|
}
|
|
})
|
|
);
|
|
|
|
test(
|
|
'[DevServer] Test directory listing',
|
|
testFixture('now-dev-directory-listing', async (t, server) => {
|
|
{
|
|
// Get directory listing
|
|
let res = await fetch(`${server.address}/`);
|
|
let body = await res.text();
|
|
t.is(res.status, 200);
|
|
t.truthy(body.includes('Index of'));
|
|
|
|
// Get a file
|
|
res = await fetch(`${server.address}/file.txt`);
|
|
body = await res.text();
|
|
t.is(res.status, 200);
|
|
t.is(body, 'Hello from file!\n');
|
|
|
|
// Invoke a lambda
|
|
res = await fetch(`${server.address}/lambda.js`);
|
|
body = await res.text();
|
|
t.is(res.status, 200);
|
|
t.is(body, 'Hello from Lambda!');
|
|
|
|
// Trigger a 404
|
|
res = await fetch(`${server.address}/does-not-exist`);
|
|
t.is(res.status, 404);
|
|
}
|
|
})
|
|
);
|
|
|
|
test(
|
|
'[DevServer] Test `public` directory with zero config',
|
|
testFixture('now-dev-api-with-public', async (t, server) => {
|
|
{
|
|
const res = await fetch(`${server.address}/api/user`);
|
|
const body = await res.text();
|
|
t.is(body, 'hello:user');
|
|
}
|
|
|
|
{
|
|
const res = await fetch(`${server.address}/`);
|
|
const body = await res.text();
|
|
t.is(body.startsWith('<h1>hello world</h1>'), true);
|
|
}
|
|
})
|
|
);
|
|
|
|
test(
|
|
'[DevServer] Test static files with zero config',
|
|
testFixture('now-dev-api-with-static', async (t, server) => {
|
|
{
|
|
const res = await fetch(`${server.address}/api/user`);
|
|
const body = await res.text();
|
|
t.is(body, 'bye:user');
|
|
}
|
|
|
|
{
|
|
const res = await fetch(`${server.address}/`);
|
|
const body = await res.text();
|
|
t.is(body.startsWith('<h1>goodbye world</h1>'), true);
|
|
}
|
|
})
|
|
);
|
|
|
|
test(
|
|
'[DevServer] 404 listing',
|
|
testFixture('now-dev-directory-listing', async (t, server) => {
|
|
{
|
|
// HTML response
|
|
const res = await fetch(`${server.address}/does-not-exist`, {
|
|
headers: {
|
|
Accept: 'text/html',
|
|
},
|
|
});
|
|
t.is(res.status, 404);
|
|
t.is(res.headers.get('content-type'), 'text/html; charset=utf-8');
|
|
const body = await res.text();
|
|
t.truthy(body.startsWith('<!DOCTYPE html>'));
|
|
}
|
|
|
|
{
|
|
// JSON response
|
|
const res = await fetch(`${server.address}/does-not-exist`, {
|
|
headers: {
|
|
Accept: 'application/json',
|
|
},
|
|
});
|
|
t.is(res.status, 404);
|
|
t.is(res.headers.get('content-type'), 'application/json');
|
|
const body = await res.text();
|
|
t.is(
|
|
body,
|
|
'{"error":{"code":404,"message":"The page could not be found."}}\n'
|
|
);
|
|
}
|
|
|
|
{
|
|
// Plain text response
|
|
const res = await fetch(`${server.address}/does-not-exist`);
|
|
t.is(res.status, 404);
|
|
const body = await res.text();
|
|
t.is(res.headers.get('content-type'), 'text/plain; charset=utf-8');
|
|
t.is(body, 'The page could not be found.\n\nNOT_FOUND\n');
|
|
}
|
|
})
|
|
);
|
|
|
|
test(
|
|
'[DevServer] custom 404 routes',
|
|
testFixture('now-dev-custom-404', async (t, server) => {
|
|
{
|
|
// Test custom 404 with static dest
|
|
const res = await fetch(`${server.address}/error.html`);
|
|
t.is(res.status, 404);
|
|
const body = await res.text();
|
|
t.is(body, '<div>Custom 404 page</div>\n');
|
|
}
|
|
|
|
{
|
|
// Test custom 404 with lambda dest
|
|
const res = await fetch(`${server.address}/error.js`);
|
|
t.is(res.status, 404);
|
|
const body = await res.text();
|
|
t.is(body, 'Custom 404 Lambda\n');
|
|
}
|
|
|
|
{
|
|
// Test regular 404 still works
|
|
const res = await fetch(`${server.address}/does-not-exist`);
|
|
t.is(res.status, 404);
|
|
const body = await res.text();
|
|
t.is(body, 'The page could not be found.\n\nNOT_FOUND\n');
|
|
}
|
|
})
|
|
);
|
|
|
|
test('[DevServer] parseListen()', t => {
|
|
t.deepEqual(parseListen('0'), [0]);
|
|
t.deepEqual(parseListen('3000'), [3000]);
|
|
t.deepEqual(parseListen('0.0.0.0'), [3000, '0.0.0.0']);
|
|
t.deepEqual(parseListen('127.0.0.1:3005'), [3005, '127.0.0.1']);
|
|
t.deepEqual(parseListen('tcp://127.0.0.1:5000'), [5000, '127.0.0.1']);
|
|
if (process.platform !== 'win32') {
|
|
t.deepEqual(parseListen('unix:/home/user/server.sock'), [
|
|
'/home/user/server.sock',
|
|
]);
|
|
t.deepEqual(parseListen('pipe:\\\\.\\pipe\\PipeName'), [
|
|
'\\\\.\\pipe\\PipeName',
|
|
]);
|
|
}
|
|
|
|
let err;
|
|
try {
|
|
parseListen('bad://url');
|
|
} catch (_err) {
|
|
err = _err;
|
|
}
|
|
t.truthy(err);
|
|
t.is(err.message, 'Unknown `--listen` scheme (protocol): bad:');
|
|
});
|