diff --git a/packages/cli/src/commands/dev/dev.ts b/packages/cli/src/commands/dev/dev.ts
index d864cc04d..665d95960 100644
--- a/packages/cli/src/commands/dev/dev.ts
+++ b/packages/cli/src/commands/dev/dev.ts
@@ -98,6 +98,12 @@ export default async function dev(
]);
}
+ // This is just for tests - can be removed once project settings
+ // are respected locally in `.vercel/project.json`
+ if (process.env.VERCEL_DEV_COMMAND) {
+ devCommand = process.env.VERCEL_DEV_COMMAND;
+ }
+
const devServer = new DevServer(cwd, {
output,
devCommand,
diff --git a/packages/cli/test/fixtures/unit/now-dev-api-with-public/api/[endpoint].js b/packages/cli/test/dev/fixtures/api-with-public/api/[endpoint].js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-api-with-public/api/[endpoint].js
rename to packages/cli/test/dev/fixtures/api-with-public/api/[endpoint].js
diff --git a/packages/cli/test/fixtures/unit/now-dev-api-with-public/public/index.html b/packages/cli/test/dev/fixtures/api-with-public/public/index.html
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-api-with-public/public/index.html
rename to packages/cli/test/dev/fixtures/api-with-public/public/index.html
diff --git a/packages/cli/test/fixtures/unit/now-dev-api-with-static/api/[endpoint].js b/packages/cli/test/dev/fixtures/api-with-static/api/[endpoint].js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-api-with-static/api/[endpoint].js
rename to packages/cli/test/dev/fixtures/api-with-static/api/[endpoint].js
diff --git a/packages/cli/test/fixtures/unit/now-dev-api-with-static/index.html b/packages/cli/test/dev/fixtures/api-with-static/index.html
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-api-with-static/index.html
rename to packages/cli/test/dev/fixtures/api-with-static/index.html
diff --git a/packages/cli/test/fixtures/unit/now-dev-custom-404/error.html b/packages/cli/test/dev/fixtures/custom-404/error.html
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-custom-404/error.html
rename to packages/cli/test/dev/fixtures/custom-404/error.html
diff --git a/packages/cli/test/fixtures/unit/now-dev-custom-404/error.js b/packages/cli/test/dev/fixtures/custom-404/error.js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-custom-404/error.js
rename to packages/cli/test/dev/fixtures/custom-404/error.js
diff --git a/packages/cli/test/fixtures/unit/now-dev-custom-404/vercel.json b/packages/cli/test/dev/fixtures/custom-404/vercel.json
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-custom-404/vercel.json
rename to packages/cli/test/dev/fixtures/custom-404/vercel.json
diff --git a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/.gitignore b/packages/cli/test/dev/fixtures/dev-server-and-routes/.gitignore
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/.gitignore
rename to packages/cli/test/dev/fixtures/dev-server-and-routes/.gitignore
diff --git a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/api/[endpoint].js b/packages/cli/test/dev/fixtures/dev-server-and-routes/api/[endpoint].js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/api/[endpoint].js
rename to packages/cli/test/dev/fixtures/dev-server-and-routes/api/[endpoint].js
diff --git a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/api/[endpoint]/[id].js b/packages/cli/test/dev/fixtures/dev-server-and-routes/api/[endpoint]/[id].js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/api/[endpoint]/[id].js
rename to packages/cli/test/dev/fixtures/dev-server-and-routes/api/[endpoint]/[id].js
diff --git a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/api/welcome.js b/packages/cli/test/dev/fixtures/dev-server-and-routes/api/welcome.js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/api/welcome.js
rename to packages/cli/test/dev/fixtures/dev-server-and-routes/api/welcome.js
diff --git a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/package.json b/packages/cli/test/dev/fixtures/dev-server-and-routes/package.json
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/package.json
rename to packages/cli/test/dev/fixtures/dev-server-and-routes/package.json
diff --git a/packages/cli/test/dev/fixtures/dev-server-and-routes/pages/index.js b/packages/cli/test/dev/fixtures/dev-server-and-routes/pages/index.js
new file mode 100644
index 000000000..40d0c682a
--- /dev/null
+++ b/packages/cli/test/dev/fixtures/dev-server-and-routes/pages/index.js
@@ -0,0 +1 @@
+export default () =>
hello, this is the frontend
;
diff --git a/packages/cli/test/fixtures/unit/now-dev-next/.gitignore b/packages/cli/test/dev/fixtures/dev-server-query/.gitignore
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-next/.gitignore
rename to packages/cli/test/dev/fixtures/dev-server-query/.gitignore
diff --git a/packages/cli/test/fixtures/unit/now-dev-next/package.json b/packages/cli/test/dev/fixtures/dev-server-query/package.json
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-next/package.json
rename to packages/cli/test/dev/fixtures/dev-server-query/package.json
diff --git a/packages/cli/test/fixtures/unit/now-dev-next/pages/index.js b/packages/cli/test/dev/fixtures/dev-server-query/pages/index.js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-next/pages/index.js
rename to packages/cli/test/dev/fixtures/dev-server-query/pages/index.js
diff --git a/packages/cli/test/fixtures/unit/now-dev-next/vercel.json b/packages/cli/test/dev/fixtures/dev-server-query/vercel.json
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-next/vercel.json
rename to packages/cli/test/dev/fixtures/dev-server-query/vercel.json
diff --git a/packages/cli/test/fixtures/unit/now-dev-directory-listing/file.txt b/packages/cli/test/dev/fixtures/directory-listing/file.txt
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-directory-listing/file.txt
rename to packages/cli/test/dev/fixtures/directory-listing/file.txt
diff --git a/packages/cli/test/fixtures/unit/now-dev-directory-listing/lambda.js b/packages/cli/test/dev/fixtures/directory-listing/lambda.js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-directory-listing/lambda.js
rename to packages/cli/test/dev/fixtures/directory-listing/lambda.js
diff --git a/packages/cli/test/fixtures/unit/now-dev-directory-listing/vercel.json b/packages/cli/test/dev/fixtures/directory-listing/vercel.json
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-directory-listing/vercel.json
rename to packages/cli/test/dev/fixtures/directory-listing/vercel.json
diff --git a/packages/cli/test/fixtures/unit/now-dev-headers/foo.txt b/packages/cli/test/dev/fixtures/headers/foo.txt
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-headers/foo.txt
rename to packages/cli/test/dev/fixtures/headers/foo.txt
diff --git a/packages/cli/test/fixtures/unit/now-dev-headers/index.js b/packages/cli/test/dev/fixtures/headers/index.js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-headers/index.js
rename to packages/cli/test/dev/fixtures/headers/index.js
diff --git a/packages/cli/test/fixtures/unit/now-dev-headers/vercel.json b/packages/cli/test/dev/fixtures/headers/vercel.json
similarity index 82%
rename from packages/cli/test/fixtures/unit/now-dev-headers/vercel.json
rename to packages/cli/test/dev/fixtures/headers/vercel.json
index 56a133e8c..cd498d652 100644
--- a/packages/cli/test/fixtures/unit/now-dev-headers/vercel.json
+++ b/packages/cli/test/dev/fixtures/headers/vercel.json
@@ -1,6 +1,5 @@
{
"version": 2,
- "name": "now-dev-headers",
"builds": [
{ "src": "index.js", "use": "@vercel/node" },
{ "src": "foo.txt", "use": "@vercel/static" }
diff --git a/packages/cli/test/fixtures/unit/now-dev-query-invoke/index.js b/packages/cli/test/dev/fixtures/node-query-invoke/index.js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-query-invoke/index.js
rename to packages/cli/test/dev/fixtures/node-query-invoke/index.js
diff --git a/packages/cli/test/fixtures/unit/now-dev-query-invoke/vercel.json b/packages/cli/test/dev/fixtures/node-query-invoke/vercel.json
similarity index 84%
rename from packages/cli/test/fixtures/unit/now-dev-query-invoke/vercel.json
rename to packages/cli/test/dev/fixtures/node-query-invoke/vercel.json
index 2b02c6fad..5374612e4 100644
--- a/packages/cli/test/fixtures/unit/now-dev-query-invoke/vercel.json
+++ b/packages/cli/test/dev/fixtures/node-query-invoke/vercel.json
@@ -1,6 +1,5 @@
{
"version": 2,
- "name": "now-dev-query-test",
"builds": [{ "src": "index.js", "use": "@vercel/node" }],
"routes": [
{
diff --git a/packages/cli/test/fixtures/unit/now-dev-request-body/api/data-events.js b/packages/cli/test/dev/fixtures/node-request-body/api/data-events.js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-request-body/api/data-events.js
rename to packages/cli/test/dev/fixtures/node-request-body/api/data-events.js
diff --git a/packages/cli/test/fixtures/unit/now-dev-request-body/api/req-body.js b/packages/cli/test/dev/fixtures/node-request-body/api/req-body.js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-request-body/api/req-body.js
rename to packages/cli/test/dev/fixtures/node-request-body/api/req-body.js
diff --git a/packages/cli/test/fixtures/unit/now-dev-query-proxy/index.js b/packages/cli/test/dev/fixtures/query-proxy/index.js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-query-proxy/index.js
rename to packages/cli/test/dev/fixtures/query-proxy/index.js
diff --git a/packages/cli/test/fixtures/unit/now-dev-query-proxy/vercel.json b/packages/cli/test/dev/fixtures/query-proxy/vercel.json
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-query-proxy/vercel.json
rename to packages/cli/test/dev/fixtures/query-proxy/vercel.json
diff --git a/packages/cli/test/fixtures/unit/now-dev-static-build-routing/.gitignore b/packages/cli/test/dev/fixtures/static-build-routing/.gitignore
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-static-build-routing/.gitignore
rename to packages/cli/test/dev/fixtures/static-build-routing/.gitignore
diff --git a/packages/cli/test/fixtures/unit/now-dev-static-build-routing/api/date.js b/packages/cli/test/dev/fixtures/static-build-routing/api/date.js
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-static-build-routing/api/date.js
rename to packages/cli/test/dev/fixtures/static-build-routing/api/date.js
diff --git a/packages/cli/test/fixtures/unit/now-dev-static-build-routing/package.json b/packages/cli/test/dev/fixtures/static-build-routing/package.json
similarity index 83%
rename from packages/cli/test/fixtures/unit/now-dev-static-build-routing/package.json
rename to packages/cli/test/dev/fixtures/static-build-routing/package.json
index ceaf2bbff..cb1eb86bb 100644
--- a/packages/cli/test/fixtures/unit/now-dev-static-build-routing/package.json
+++ b/packages/cli/test/dev/fixtures/static-build-routing/package.json
@@ -1,5 +1,5 @@
{
- "name": "now-dev-static-build-routing",
+ "name": "vc-dev-static-build-routing",
"private": true,
"scripts": {
"now-dev": "serve -l $PORT src",
diff --git a/packages/cli/test/fixtures/unit/now-dev-static-build-routing/src/index.html b/packages/cli/test/dev/fixtures/static-build-routing/src/index.html
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-static-build-routing/src/index.html
rename to packages/cli/test/dev/fixtures/static-build-routing/src/index.html
diff --git a/packages/cli/test/fixtures/unit/now-dev-static-build-routing/vercel.json b/packages/cli/test/dev/fixtures/static-build-routing/vercel.json
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-static-build-routing/vercel.json
rename to packages/cli/test/dev/fixtures/static-build-routing/vercel.json
diff --git a/packages/cli/test/fixtures/unit/now-dev-static-routes/vercel.json b/packages/cli/test/dev/fixtures/static-routes/vercel.json
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-static-routes/vercel.json
rename to packages/cli/test/dev/fixtures/static-routes/vercel.json
diff --git a/packages/cli/test/fixtures/unit/now-dev-static-routes/www/index.html b/packages/cli/test/dev/fixtures/static-routes/www/index.html
similarity index 100%
rename from packages/cli/test/fixtures/unit/now-dev-static-routes/www/index.html
rename to packages/cli/test/dev/fixtures/static-routes/www/index.html
diff --git a/packages/cli/test/dev/integration-1.test.ts b/packages/cli/test/dev/integration-1.test.ts
index 0e185623f..c2209714a 100644
--- a/packages/cli/test/dev/integration-1.test.ts
+++ b/packages/cli/test/dev/integration-1.test.ts
@@ -1,6 +1,9 @@
import os from 'os';
+import url from 'url';
import fs from 'fs-extra';
import { join } from 'path';
+import listen from 'async-listen';
+import { createServer } from 'http';
const {
exec,
@@ -8,8 +11,374 @@ const {
fixture,
testFixture,
testFixtureStdio,
+ validateResponseHeaders,
} = require('./utils.js');
+test('[vercel dev] should support request body', async () => {
+ const dir = fixture('node-request-body');
+ const { dev, port, readyResolver } = await testFixture(dir);
+
+ try {
+ await readyResolver;
+
+ const body = { hello: 'world' };
+
+ // Test that `req.body` works in dev
+ let res = await fetch(`http://localhost:${port}/api/req-body`, {
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ });
+ validateResponseHeaders(res);
+ expect(await res.json()).toMatchObject(body);
+
+ // Test that `req` "data" events work in dev
+ res = await fetch(`http://localhost:${port}/api/data-events`, {
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ });
+ } finally {
+ dev.kill('SIGTERM');
+ }
+});
+
+test('[vercel dev] should maintain query when invoking serverless function', async () => {
+ const dir = fixture('node-query-invoke');
+ const { dev, port, readyResolver } = await testFixture(dir);
+
+ try {
+ await readyResolver;
+
+ const res = await fetch(`http://localhost:${port}/something?url-param=a`);
+ validateResponseHeaders(res);
+
+ const text = await res.text();
+ const parsed = url.parse(text, true);
+ expect(parsed.pathname).toEqual('/something');
+ expect(parsed.query['url-param']).toEqual('a');
+ expect(parsed.query['route-param']).toEqual('b');
+ } finally {
+ dev.kill('SIGTERM');
+ }
+});
+
+test('[vercel dev] should maintain query when proxy passing', async () => {
+ const dir = fixture('query-proxy');
+ const { dev, port, readyResolver } = await testFixture(dir);
+ const dest = createServer((req, res) => {
+ res.end(req.url);
+ });
+
+ try {
+ await Promise.all([readyResolver, listen(dest, 0)]);
+
+ const destAddr = dest.address();
+ if (!destAddr || typeof destAddr === 'string') {
+ throw new Error('Unexpected HTTP address');
+ }
+
+ const res = await fetch(
+ `http://localhost:${port}/${destAddr.port}?url-param=a`
+ );
+ validateResponseHeaders(res);
+
+ const text = await res.text();
+ const parsed = url.parse(text, true);
+ expect(parsed.pathname).toEqual('/something');
+ expect(parsed.query['url-param']).toEqual('a');
+ expect(parsed.query['route-param']).toEqual('b');
+ } finally {
+ dest.close();
+ dev.kill('SIGTERM');
+ }
+});
+
+test('[vercel dev] should maintain query when dev server defines routes', async () => {
+ const dir = fixture('dev-server-query');
+ const { dev, port, readyResolver } = await testFixture(dir, {
+ env: {
+ VERCEL_DEV_COMMAND: 'next dev --port $PORT',
+ },
+ });
+
+ try {
+ await readyResolver;
+
+ const res = await fetch(`http://localhost:${port}/test?url-param=a`);
+ validateResponseHeaders(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(/(.*)<\/pre>/)![1]
+ .replace('', '')
+ .replace('', '')
+ .replace(/&/g, '&')
+ .replace(/"/g, '"');
+ const parsed = JSON.parse(json);
+ const query = url.parse(parsed.url, true).query;
+
+ expect(query['url-param']).toEqual('a');
+ expect(query['route-param']).toEqual('b');
+ } finally {
+ dev.kill('SIGTERM');
+ }
+});
+
+test('[vercel dev] should allow `cache-control` to be overwritten', async () => {
+ const dir = fixture('headers');
+ const { dev, port, readyResolver } = await testFixture(dir);
+
+ try {
+ await readyResolver;
+
+ const res = await fetch(
+ `http://localhost:${port}/?name=cache-control&value=immutable`
+ );
+ expect(res.headers.get('cache-control')).toEqual('immutable');
+ } finally {
+ dev.kill('SIGTERM');
+ }
+});
+
+test('[vercel dev] should send `etag` header for static files', async () => {
+ const dir = fixture('headers');
+ const { dev, port, readyResolver } = await testFixture(dir);
+
+ try {
+ await readyResolver;
+
+ const res = await fetch(`http://localhost:${port}/foo.txt`);
+ const expected = 'd263af8ab880c0b97eb6c5c125b5d44f9e5addd9';
+ expect(res.headers.get('etag')).toEqual(`"${expected}"`);
+ const body = await res.text();
+ expect(body.trim()).toEqual('hi');
+ } finally {
+ dev.kill('SIGTERM');
+ }
+});
+
+test('[vercel dev] should frontend dev server and routes', async () => {
+ const dir = fixture('dev-server-and-routes');
+ const { dev, port, readyResolver } = await testFixture(dir, {
+ env: {
+ VERCEL_DEV_COMMAND: 'next dev --port $PORT',
+ },
+ });
+
+ try {
+ await readyResolver;
+
+ let podId: string;
+
+ let res = await fetch(`http://localhost:${port}/`);
+ validateResponseHeaders(res);
+ podId = res.headers.get('x-vercel-id')!.match(/:(\w+)-/)![1];
+ let body = await res.text();
+ expect(body.includes('hello, this is the frontend')).toBeTruthy();
+
+ res = await fetch(`http://localhost:${port}/api/users`);
+ validateResponseHeaders(res, podId);
+ body = await res.text();
+ expect(body).toEqual('users');
+
+ res = await fetch(`http://localhost:${port}/api/users/1`);
+ validateResponseHeaders(res, podId);
+ body = await res.text();
+ expect(body).toEqual('users/1');
+
+ res = await fetch(`http://localhost:${port}/api/welcome`);
+ validateResponseHeaders(res, podId);
+ body = await res.text();
+ expect(body).toEqual('hello and welcome');
+ } finally {
+ dev.kill('SIGTERM');
+ }
+});
+
+test('[vercel dev] should support `@vercel/static` routing', async () => {
+ const dir = fixture('static-routes');
+ const { dev, port, readyResolver } = await testFixture(dir);
+
+ try {
+ await readyResolver;
+
+ const res = await fetch(`http://localhost:${port}/`);
+ expect(res.status).toEqual(200);
+ const body = await res.text();
+ expect(body.trim()).toEqual('Hello!');
+ } finally {
+ dev.kill('SIGTERM');
+ }
+});
+
+test('[vercel dev] should support `@vercel/static-build` routing', async () => {
+ const dir = fixture('static-build-routing');
+ const { dev, port, readyResolver } = await testFixture(dir);
+
+ try {
+ await readyResolver;
+
+ const res = await fetch(`http://localhost:${port}/api/date`);
+ expect(res.status).toEqual(200);
+ const body = await res.text();
+ expect(body.startsWith('The current date:')).toBeTruthy();
+ } finally {
+ dev.kill('SIGTERM');
+ }
+});
+
+test('[vercel dev] should support directory listing', async () => {
+ const dir = fixture('directory-listing');
+ const { dev, port, readyResolver } = await testFixture(dir);
+
+ try {
+ await readyResolver;
+
+ // Get directory listing
+ let res = await fetch(`http://localhost:${port}/`);
+ let body = await res.text();
+ expect(res.status).toEqual(200);
+ expect(body.includes('Index of')).toBeTruthy();
+
+ // Get a file
+ res = await fetch(`http://localhost:${port}/file.txt`);
+ body = await res.text();
+ expect(res.status).toEqual(200);
+ expect(body.trim()).toEqual('Hello from file!');
+
+ // Invoke a lambda
+ res = await fetch(`http://localhost:${port}/lambda.js`);
+ body = await res.text();
+ expect(res.status).toEqual(200);
+ expect(body).toEqual('Hello from Lambda!');
+
+ // Trigger a 404
+ res = await fetch(`http://localhost:${port}/does-not-exist`);
+ expect(res.status).toEqual(404);
+ } finally {
+ dev.kill('SIGTERM');
+ }
+});
+
+test('[vercel dev] should respond with 404 listing with Accept header support', async () => {
+ const dir = fixture('directory-listing');
+ const { dev, port, readyResolver } = await testFixture(dir);
+
+ try {
+ await readyResolver;
+
+ // HTML response
+ let res = await fetch(`http://localhost:${port}/does-not-exist`, {
+ headers: {
+ Accept: 'text/html',
+ },
+ });
+ expect(res.status).toEqual(404);
+ expect(res.headers.get('content-type')).toEqual('text/html; charset=utf-8');
+ let body = await res.text();
+ expect(body.startsWith('')).toBeTruthy();
+
+ // JSON response
+ res = await fetch(`http://localhost:${port}/does-not-exist`, {
+ headers: {
+ Accept: 'application/json',
+ },
+ });
+ expect(res.status).toEqual(404);
+ expect(res.headers.get('content-type')).toEqual('application/json');
+ body = await res.text();
+ expect(body).toEqual(
+ '{"error":{"code":404,"message":"The page could not be found."}}\n'
+ );
+
+ // Plain text response
+ res = await fetch(`http://localhost:${port}/does-not-exist`);
+ expect(res.status).toEqual(404);
+ body = await res.text();
+ expect(res.headers.get('content-type')).toEqual(
+ 'text/plain; charset=utf-8'
+ );
+ expect(body).toEqual('The page could not be found.\n\nNOT_FOUND\n');
+ } finally {
+ dev.kill('SIGTERM');
+ }
+});
+
+test('[vercel dev] should support `public` directory with zero config', async () => {
+ const dir = fixture('api-with-public');
+ const { dev, port, readyResolver } = await testFixture(dir);
+
+ try {
+ await readyResolver;
+
+ let res = await fetch(`http://localhost:${port}/api/user`);
+ let body = await res.text();
+ expect(body).toEqual('hello:user');
+
+ res = await fetch(`http://localhost:${port}/`);
+ body = await res.text();
+ expect(body.startsWith('hello world
')).toBeTruthy();
+ } finally {
+ dev.kill('SIGTERM');
+ }
+});
+
+test('[vercel dev] should support static files with zero config', async () => {
+ const dir = fixture('api-with-static');
+ const { dev, port, readyResolver } = await testFixture(dir);
+
+ try {
+ await readyResolver;
+
+ let res = await fetch(`http://localhost:${port}/api/user`);
+ let body = await res.text();
+ expect(body).toEqual('bye:user');
+
+ res = await fetch(`http://localhost:${port}/`);
+ body = await res.text();
+ expect(body.startsWith('goodbye world
')).toBeTruthy();
+ } finally {
+ dev.kill('SIGTERM');
+ }
+});
+
+test('[vercel dev] should support custom 404 routes', async () => {
+ const dir = fixture('custom-404');
+ const { dev, port, readyResolver } = await testFixture(dir);
+
+ try {
+ await readyResolver;
+
+ // Test custom 404 with static dest
+ let res = await fetch(`http://localhost:${port}/error.html`);
+ expect(res.status).toEqual(404);
+ let body = await res.text();
+ expect(body.trim()).toEqual('Custom 404 page
');
+
+ // Test custom 404 with lambda dest
+ res = await fetch(`http://localhost:${port}/error.js`);
+ expect(res.status).toEqual(404);
+ body = await res.text();
+ expect(body).toEqual('Custom 404 Lambda\n');
+
+ // Test regular 404 still works
+ res = await fetch(`http://localhost:${port}/does-not-exist`);
+ expect(res.status).toEqual(404);
+ body = await res.text();
+ expect(body).toEqual('The page could not be found.\n\nNOT_FOUND\n');
+ } finally {
+ dev.kill('SIGTERM');
+ }
+});
+
test('[vercel dev] prints `npm install` errors', async () => {
const dir = fixture('runtime-not-installed');
const result = await exec(dir);
diff --git a/packages/cli/test/dev/utils.js b/packages/cli/test/dev/utils.js
index 3a2cf2c9d..5db93f9db 100644
--- a/packages/cli/test/dev/utils.js
+++ b/packages/cli/test/dev/utils.js
@@ -96,10 +96,16 @@ function shouldSkip(name, versions) {
return false;
}
-function validateResponseHeaders(res) {
+function validateResponseHeaders(res, podId) {
if (res.status < 500) {
+ expect(res.headers.get('server')).toEqual('Vercel');
+ expect(res.headers.get('cache-control').length > 0).toBeTruthy();
expect(res.headers.get('x-vercel-id')).toBeTruthy();
- expect(res.headers.get('cache-control').length > 0).toBeTruthy;
+ if (podId) {
+ expect(
+ res.headers.get('x-vercel-id').includes(`::${podId}-`)
+ ).toBeTruthy();
+ }
}
}
diff --git a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/pages/index.js b/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/pages/index.js
deleted file mode 100644
index a6ae7f7d6..000000000
--- a/packages/cli/test/fixtures/unit/now-dev-default-builds-and-routes/pages/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export default () => hello, this is the frontend
diff --git a/packages/cli/test/unit/util/build/import-builders.test.ts b/packages/cli/test/unit/util/build/import-builders.test.ts
index 71d1fe346..41a575b58 100644
--- a/packages/cli/test/unit/util/build/import-builders.test.ts
+++ b/packages/cli/test/unit/util/build/import-builders.test.ts
@@ -1,3 +1,4 @@
+import ms from 'ms';
import { join } from 'path';
import { remove } from 'fs-extra';
import { getWriteableDirectory } from '@vercel/build-utils';
@@ -9,6 +10,8 @@ import {
import vercelNextPkg from '@vercel/next/package.json';
import vercelNodePkg from '@vercel/node/package.json';
+jest.setTimeout(ms('20 seconds'));
+
describe('importBuilders()', () => {
it('should import built-in Builders', async () => {
const specs = new Set(['@vercel/node', '@vercel/next']);
diff --git a/packages/cli/test/unit/util/dev/server.test.ts b/packages/cli/test/unit/util/dev/server.test.ts
deleted file mode 100644
index a9bd6578f..000000000
--- a/packages/cli/test/unit/util/dev/server.test.ts
+++ /dev/null
@@ -1,438 +0,0 @@
-import ms from 'ms';
-import url from 'url';
-import path from 'path';
-import execa from 'execa';
-import fs from 'fs-extra';
-import fetch, { Response } from 'node-fetch';
-import listen from 'async-listen';
-import { createServer } from 'http';
-import { client } from '../../../mocks/client';
-import DevServer from '../../../../src/util/dev/server';
-import { DevServerOptions } from '../../../../src/util/dev/types';
-
-const IS_WINDOWS = process.platform === 'win32';
-
-async function runNpmInstall(fixturePath: string) {
- if (await fs.pathExists(path.join(fixturePath, 'package.json'))) {
- return execa('yarn', ['install', '--network-timeout', '1000000'], {
- cwd: fixturePath,
- shell: true,
- });
- }
-}
-
-interface TestFixtureOptions extends Omit {
- skip?: boolean;
-}
-
-const testFixture =
- (
- name: string,
- fn: (server: DevServer) => Promise,
- options?: TestFixtureOptions
- ) =>
- async () => {
- if (options?.skip) {
- console.log('Skipping this test for this platform.');
- return;
- }
-
- let server: DevServer | undefined;
- const fixturePath = path.join(__dirname, '../../fixtures/unit', name);
- await runNpmInstall(fixturePath);
- try {
- server = new DevServer(fixturePath, {
- ...options,
- output: client.output,
- });
- await server.start(0);
- await fn(server);
- } finally {
- await server?.stop();
- }
- };
-
-function validateResponseHeaders(res: Response, podId?: string) {
- expect(res.headers.get('server')).toEqual('Vercel');
- expect(res.headers.get('cache-control')!.length > 0).toBeTruthy();
- expect(
- /^dev1::(dev1::)?[0-9a-z]{5}-[1-9][0-9]+-[a-f0-9]{12}$/.test(
- res.headers.get('x-vercel-id')!
- )
- ).toBeTruthy();
- if (podId) {
- expect(
- res.headers.get('x-vercel-id')!.startsWith(`dev1::${podId}`) ||
- res.headers.get('x-vercel-id')!.startsWith(`dev1::dev1::${podId}`)
- ).toBeTruthy();
- }
-}
-
-describe('DevServer', () => {
- jest.setTimeout(ms('2m'));
-
- it(
- 'should support request body',
- testFixture('now-dev-request-body', async server => {
- const body = { hello: 'world' };
-
- // Test that `req.body` works in dev
- let res = await fetch(`${server.address}/api/req-body`, {
- method: 'POST',
- headers: {
- 'content-type': 'application/json',
- },
- body: JSON.stringify(body),
- });
- validateResponseHeaders(res);
- expect(await res.json()).toMatchObject(body);
-
- // Test that `req` "data" events work in dev
- res = await fetch(`${server.address}/api/data-events`, {
- method: 'POST',
- headers: {
- 'content-type': 'application/json',
- },
- body: JSON.stringify(body),
- });
- expect(await res.json()).toMatchObject(body);
- })
- );
-
- it(
- 'should maintain query when invoking serverless function',
- testFixture('now-dev-query-invoke', async server => {
- const res = await fetch(`${server.address}/something?url-param=a`);
- validateResponseHeaders(res);
-
- const text = await res.text();
- const parsed = url.parse(text, true);
- expect(parsed.pathname).toEqual('/something');
- expect(parsed.query['url-param']).toEqual('a');
- expect(parsed.query['route-param']).toEqual('b');
- })
- );
-
- it(
- 'should maintain query when proxy passing',
- testFixture('now-dev-query-proxy', async server => {
- const dest = createServer((req, res) => {
- res.end(req.url);
- });
- await listen(dest, 0);
- const addr = dest.address();
- if (!addr || typeof addr === 'string') {
- throw new Error('Unexpected HTTP address');
- }
- const { port } = addr;
-
- try {
- const res = await fetch(`${server.address}/${port}?url-param=a`);
- validateResponseHeaders(res);
-
- const text = await res.text();
- const parsed = url.parse(text, true);
- expect(parsed.pathname).toEqual('/something');
- expect(parsed.query['url-param']).toEqual('a');
- expect(parsed.query['route-param']).toEqual('b');
- } finally {
- dest.close();
- }
- })
- );
-
- it(
- 'should maintain query when builder defines routes',
- testFixture(
- 'now-dev-next',
- async server => {
- const res = await fetch(`${server.address}/test?url-param=a`);
- validateResponseHeaders(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(/(.*)<\/pre>/)![1]
- .replace('', '')
- .replace('', '')
- .replace(/&/g, '&')
- .replace(/"/g, '"');
- const parsed = JSON.parse(json);
- const query = url.parse(parsed.url, true).query;
-
- expect(query['url-param']).toEqual('a');
- expect(query['route-param']).toEqual('b');
- },
- {
- devCommand: 'next dev --port $PORT',
- }
- )
- );
-
- it(
- 'should allow `cache-control` to be overwritten',
- testFixture('now-dev-headers', async server => {
- const res = await fetch(
- `${server.address}/?name=cache-control&value=immutable`
- );
- expect(res.headers.get('cache-control')).toEqual('immutable');
- })
- );
-
- it(
- 'should send `etag` header for static files',
- testFixture('now-dev-headers', async server => {
- const res = await fetch(`${server.address}/foo.txt`);
- const expected = IS_WINDOWS
- ? '9dc423ab77c2e0446cd355256efff2ea1be27cbf'
- : 'd263af8ab880c0b97eb6c5c125b5d44f9e5addd9';
- expect(res.headers.get('etag')).toEqual(`"${expected}"`);
- const body = await res.text();
- expect(body.trim()).toEqual('hi');
- })
- );
-
- it(
- 'should support default builds and routes',
- testFixture(
- 'now-dev-default-builds-and-routes',
- async server => {
- let podId: string;
-
- let res = await fetch(`${server.address}/`);
- validateResponseHeaders(res);
- podId = res.headers.get('x-vercel-id')!.match(/:(\w+)-/)![1];
- let body = await res.text();
- expect(body.includes('hello, this is the frontend')).toBeTruthy();
-
- res = await fetch(`${server.address}/api/users`);
- validateResponseHeaders(res, podId);
- body = await res.text();
- expect(body).toEqual('users');
-
- res = await fetch(`${server.address}/api/users/1`);
- validateResponseHeaders(res, podId);
- body = await res.text();
- expect(body).toEqual('users/1');
-
- res = await fetch(`${server.address}/api/welcome`);
- validateResponseHeaders(res, podId);
- body = await res.text();
- expect(body).toEqual('hello and welcome');
- },
- {
- devCommand: 'next dev --port $PORT',
- }
- )
- );
-
- it(
- 'should support `@vercel/static` routing',
- testFixture('now-dev-static-routes', async server => {
- const res = await fetch(`${server.address}/`);
- expect(res.status).toEqual(200);
- const body = await res.text();
- expect(body.trim()).toEqual('Hello!');
- })
- );
-
- it(
- 'should support `@vercel/static-build` routing',
- testFixture(
- 'now-dev-static-build-routing',
- async server => {
- const res = await fetch(`${server.address}/api/date`);
- expect(res.status).toEqual(200);
- const body = await res.text();
- expect(body.startsWith('The current date:')).toBeTruthy();
- },
- {
- // This test is currently failing on Windows, so skip for now:
- // > Creating initial build
- // $ serve -l $PORT src
- // 'serve' is not recognized as an internal or external command,
- // https://github.com/vercel/vercel/pull/6638/checks?check_run_id=3449662836
- skip: IS_WINDOWS,
- }
- )
- );
-
- it(
- 'should support directory listing',
- testFixture('now-dev-directory-listing', async server => {
- // Get directory listing
- let res = await fetch(`${server.address}/`);
- let body = await res.text();
- expect(res.status).toEqual(200);
- expect(body.includes('Index of')).toBeTruthy();
-
- // Get a file
- res = await fetch(`${server.address}/file.txt`);
- body = await res.text();
- expect(res.status).toEqual(200);
- expect(body.trim()).toEqual('Hello from file!');
-
- // Invoke a lambda
- res = await fetch(`${server.address}/lambda.js`);
- body = await res.text();
- expect(res.status).toEqual(200);
- expect(body).toEqual('Hello from Lambda!');
-
- // Trigger a 404
- res = await fetch(`${server.address}/does-not-exist`);
- expect(res.status).toEqual(404);
- })
- );
-
- it(
- 'should support `public` directory with zero config',
- testFixture('now-dev-api-with-public', async server => {
- let res = await fetch(`${server.address}/api/user`);
- let body = await res.text();
- expect(body).toEqual('hello:user');
-
- res = await fetch(`${server.address}/`);
- body = await res.text();
- expect(body.startsWith('hello world
')).toBeTruthy();
- })
- );
-
- it(
- 'should support static files with zero config',
- testFixture('now-dev-api-with-static', async server => {
- let res = await fetch(`${server.address}/api/user`);
- let body = await res.text();
- expect(body).toEqual('bye:user');
-
- res = await fetch(`${server.address}/`);
- body = await res.text();
- expect(body.startsWith('goodbye world
')).toBeTruthy();
- })
- );
-
- it(
- 'should respond with 404 listing with Accept header support',
- testFixture('now-dev-directory-listing', async server => {
- // HTML response
- let res = await fetch(`${server.address}/does-not-exist`, {
- headers: {
- Accept: 'text/html',
- },
- });
- expect(res.status).toEqual(404);
- expect(res.headers.get('content-type')).toEqual(
- 'text/html; charset=utf-8'
- );
- let body = await res.text();
- expect(body.startsWith('')).toBeTruthy();
-
- // JSON response
- res = await fetch(`${server.address}/does-not-exist`, {
- headers: {
- Accept: 'application/json',
- },
- });
- expect(res.status).toEqual(404);
- expect(res.headers.get('content-type')).toEqual('application/json');
- body = await res.text();
- expect(body).toEqual(
- '{"error":{"code":404,"message":"The page could not be found."}}\n'
- );
-
- // Plain text response
- res = await fetch(`${server.address}/does-not-exist`);
- expect(res.status).toEqual(404);
- body = await res.text();
- expect(res.headers.get('content-type')).toEqual(
- 'text/plain; charset=utf-8'
- );
- expect(body).toEqual('The page could not be found.\n\nNOT_FOUND\n');
- })
- );
-
- it(
- 'should support custom 404 routes',
- testFixture('now-dev-custom-404', async server => {
- // Test custom 404 with static dest
- let res = await fetch(`${server.address}/error.html`);
- expect(res.status).toEqual(404);
- let body = await res.text();
- expect(body.trim()).toEqual('Custom 404 page
');
-
- // Test custom 404 with lambda dest
- res = await fetch(`${server.address}/error.js`);
- expect(res.status).toEqual(404);
- body = await res.text();
- expect(body).toEqual('Custom 404 Lambda\n');
-
- // Test regular 404 still works
- res = await fetch(`${server.address}/does-not-exist`);
- expect(res.status).toEqual(404);
- body = await res.text();
- expect(body).toEqual('The page could not be found.\n\nNOT_FOUND\n');
- })
- );
-
- /*
- it(
- 'should support edge middleware',
- testFixture('edge-middleware', async server => {
- const response = await fetch(`${server.address}/index.html`);
- const body = await response.json();
- expect(body).toEqual(
- JSON.parse(
- fs.readFileSync(
- path.join(
- __dirname,
- '../../fixtures/unit/edge-middleware/response.json'
- ),
- 'utf8'
- )
- )
- );
- })
- );
-
- it(
- 'should work with middleware written in typescript',
- testFixture('edge-middleware-ts', async server => {
- const response = await fetch(`${server.address}/index.html`);
- const body = await response.text();
- expect(body).toStrictEqual('response');
- })
- );
-
- it(
- 'should render an error page when the middleware throws',
- testFixture('edge-middleware-error', async server => {
- const response = await fetch(`${server.address}/index.html`);
- const body = await response.text();
- expect(body).toStrictEqual(
- 'A server error has occurred\n\nEDGE_FUNCTION_INVOCATION_FAILED\n'
- );
- })
- );
-
- it(
- 'should render an error page when the middleware returns not a Response',
- testFixture('edge-middleware-invalid-response', async server => {
- const response = await fetch(`${server.address}/index.html`);
- const body = await response.text();
- expect(body).toStrictEqual(
- 'A server error has occurred\n\nEDGE_FUNCTION_INVOCATION_FAILED\n'
- );
- })
- );
-
- it(
- 'should run middleware in strict mode',
- testFixture('edge-middleware-strict', async server => {
- const response = await fetch(`${server.address}/index.html`);
- const body = await response.text();
- expect(body).toStrictEqual('is strict mode? yes');
- })
- );
- */
-});